From 8145eb7c9193c692d7dd2d22f7db0707f7481e8f Mon Sep 17 00:00:00 2001
From: storyxc
Date: Sun, 10 Sep 2023 18:00:44 +0000
Subject: [PATCH] =?UTF-8?q?github=20actions=E8=87=AA=E5=8A=A8=E9=83=A8?=
=?UTF-8?q?=E7=BD=B2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
404.html | 22 +
CNAME | 1 +
.../chain-of-responsibility-pattern.html | 125 +
actions/designpattern/strategy-pattern.html | 673 +++
actions/env/cicd-vue.html | 137 +
actions/env/docker-desktop-windows.html | 25 +
actions/env/git-proxy.html | 41 +
actions/env/git-repo-multi-remote.html | 55 +
actions/env/github-actions.html | 137 +
actions/env/mysql-troubleshoot.html | 157 +
actions/env/powershell-beautify.html | 43 +
actions/env/startup-script-macos.html | 69 +
actions/env/terminal-proxy-macos.html | 27 +
actions/index.html | 25 +
actions/tools/book-searcher.html | 51 +
actions/tools/file-consistency-check.html | 25 +
actions/tools/git-cmd.html | 31 +
actions/tools/iterm2-oh-my-zsh.html | 25 +
actions/tools/iterm2-ssh-conn-config.html | 25 +
actions/tools/linux-time-machine.html | 29 +
actions/tools/markdown-syntax.html | 123 +
actions/tools/sketch.html | 25 +
actions/tools/typora-picgo-qiniu.html | 25 +
...n-of-responsibility-pattern.md.e0e027d4.js | 101 +
...responsibility-pattern.md.e0e027d4.lean.js | 1 +
...ignpattern_strategy-pattern.md.23b27876.js | 649 +++
...ttern_strategy-pattern.md.23b27876.lean.js | 1 +
assets/actions_env_cicd-vue.md.a50e1517.js | 113 +
.../actions_env_cicd-vue.md.a50e1517.lean.js | 1 +
..._env_docker-desktop-windows.md.5f59bd6b.js | 1 +
...docker-desktop-windows.md.5f59bd6b.lean.js | 1 +
assets/actions_env_git-proxy.md.0bf9d81f.js | 17 +
.../actions_env_git-proxy.md.0bf9d81f.lean.js | 1 +
...s_env_git-repo-multi-remote.md.c9a74a94.js | 31 +
..._git-repo-multi-remote.md.c9a74a94.lean.js | 1 +
.../actions_env_github-actions.md.bcabb284.js | 113 +
...ons_env_github-actions.md.bcabb284.lean.js | 1 +
...ions_env_mysql-troubleshoot.md.ca79ae83.js | 133 +
...env_mysql-troubleshoot.md.ca79ae83.lean.js | 1 +
...ons_env_powershell-beautify.md.a053ec8f.js | 19 +
...nv_powershell-beautify.md.a053ec8f.lean.js | 1 +
...ns_env_startup-script-macos.md.0507c211.js | 45 +
...v_startup-script-macos.md.0507c211.lean.js | 1 +
...ns_env_terminal-proxy-macos.md.90a20fc3.js | 3 +
...v_terminal-proxy-macos.md.90a20fc3.lean.js | 1 +
assets/actions_index.md.3e437531.js | 1 +
assets/actions_index.md.3e437531.lean.js | 1 +
...actions_tools_book-searcher.md.8e834212.js | 27 +
...ns_tools_book-searcher.md.8e834212.lean.js | 1 +
...ools_file-consistency-check.md.7d5c9dfa.js | 1 +
...file-consistency-check.md.7d5c9dfa.lean.js | 1 +
assets/actions_tools_git-cmd.md.2cccda47.js | 7 +
.../actions_tools_git-cmd.md.2cccda47.lean.js | 1 +
...ions_tools_iterm2-oh-my-zsh.md.b5d4f68e.js | 1 +
...tools_iterm2-oh-my-zsh.md.b5d4f68e.lean.js | 1 +
...ools_iterm2-ssh-conn-config.md.91112d85.js | 1 +
...iterm2-ssh-conn-config.md.91112d85.lean.js | 1 +
...ns_tools_linux-time-machine.md.c1ed2026.js | 5 +
...ols_linux-time-machine.md.c1ed2026.lean.js | 1 +
...tions_tools_markdown-syntax.md.49464579.js | 99 +
..._tools_markdown-syntax.md.49464579.lean.js | 1 +
assets/actions_tools_sketch.md.349c9ded.js | 1 +
.../actions_tools_sketch.md.349c9ded.lean.js | 1 +
...ns_tools_typora-picgo-qiniu.md.e45de354.js | 1 +
...ols_typora-picgo-qiniu.md.e45de354.lean.js | 1 +
assets/app.53da8f68.js | 1 +
assets/chunks/VPAlgoliaSearchBox.377a8f85.js | 17 +
assets/chunks/framework.b637c96f.js | 2 +
assets/chunks/theme.0d739576.js | 1 +
...mpose_docker-compose-syntax.md.0195f5ef.js | 79 +
..._docker-compose-syntax.md.0195f5ef.lean.js | 1 +
...r_Docker_common-instruction.md.e2127946.js | 11 +
...ker_common-instruction.md.e2127946.lean.js | 1 +
...er_Docker_dockerfile-syntax.md.55c6cc9e.js | 101 +
...cker_dockerfile-syntax.md.55c6cc9e.lean.js | 1 +
assets/docker_index.md.a98dedbd.js | 1 +
assets/docker_index.md.a98dedbd.lean.js | 1 +
assets/frontend_base_css.md.70afca28.js | 391 ++
assets/frontend_base_css.md.70afca28.lean.js | 1 +
assets/frontend_base_html.md.7eca1008.js | 1 +
assets/frontend_base_html.md.7eca1008.lean.js | 1 +
.../frontend_base_javascript.md.4c1235ab.js | 1461 +++++
...ontend_base_javascript.md.4c1235ab.lean.js | 1 +
assets/frontend_base_jquery.md.5c027802.js | 115 +
.../frontend_base_jquery.md.5c027802.lean.js | 1 +
assets/frontend_base_nodejs.md.b2ea33da.js | 29 +
.../frontend_base_nodejs.md.b2ea33da.lean.js | 1 +
.../frontend_base_typescript.md.c2a79275.js | 1371 +++++
...ontend_base_typescript.md.c2a79275.lean.js | 1 +
assets/frontend_base_webpack.md.606bbc01.js | 141 +
.../frontend_base_webpack.md.606bbc01.lean.js | 1 +
assets/frontend_framework_vue.md.c825351a.js | 949 ++++
...frontend_framework_vue.md.c825351a.lean.js | 1 +
assets/frontend_index.md.971a7af8.js | 1 +
assets/frontend_index.md.971a7af8.lean.js | 1 +
...thers_elementplus-el-upload.md.90eed779.js | 603 ++
..._elementplus-el-upload.md.90eed779.lean.js | 1 +
...d_others_hackingwithswift-1.md.4aa127cc.js | 853 +++
...ers_hackingwithswift-1.md.4aa127cc.lean.js | 1 +
...d_others_hackingwithswift-2.md.d6de0bd5.js | 1413 +++++
...ers_hackingwithswift-2.md.d6de0bd5.lean.js | 1 +
...rontend_others_swift-syntax.md.fe62a728.js | 305 +
...nd_others_swift-syntax.md.fe62a728.lean.js | 1 +
...ntend_others_swiftui-syntax.md.e9ffe4fb.js | 319 ++
..._others_swiftui-syntax.md.e9ffe4fb.lean.js | 1 +
assets/golang_base_go-template.md.498dda04.js | 391 ++
...olang_base_go-template.md.498dda04.lean.js | 1 +
.../golang_base_golang-syntax.md.4fddf075.js | 5003 ++++++++++++++++
...ang_base_golang-syntax.md.4fddf075.lean.js | 1 +
assets/golang_cli_cobra.md.4b826d1d.js | 369 ++
assets/golang_cli_cobra.md.4b826d1d.lean.js | 1 +
assets/golang_index.md.d3c28484.js | 1 +
assets/golang_index.md.d3c28484.lean.js | 1 +
.../golang_tools_redis-cleaner.md.40f7c6d6.js | 143 +
...ng_tools_redis-cleaner.md.40f7c6d6.lean.js | 1 +
assets/golang_web_gin.md.11770ac9.js | 491 ++
assets/golang_web_gin.md.11770ac9.lean.js | 1 +
assets/index.md.b912301d.js | 1 +
assets/index.md.b912301d.lean.js | 1 +
.../inter-italic-cyrillic-ext.33bd5a8e.woff2 | Bin 0 -> 28332 bytes
assets/inter-italic-cyrillic.ea42a392.woff2 | Bin 0 -> 17824 bytes
assets/inter-italic-greek-ext.4fbe9427.woff2 | Bin 0 -> 12188 bytes
assets/inter-italic-greek.8f4463c4.woff2 | Bin 0 -> 23264 bytes
assets/inter-italic-latin-ext.bd8920cc.woff2 | Bin 0 -> 63552 bytes
assets/inter-italic-latin.bd3b6f56.woff2 | Bin 0 -> 46048 bytes
assets/inter-italic-vietnamese.6ce511fb.woff2 | Bin 0 -> 8784 bytes
.../inter-roman-cyrillic-ext.e75737ce.woff2 | Bin 0 -> 26600 bytes
assets/inter-roman-cyrillic.5f2c6c8c.woff2 | Bin 0 -> 16780 bytes
assets/inter-roman-greek-ext.ab0619bc.woff2 | Bin 0 -> 11808 bytes
assets/inter-roman-greek.d5a6d92a.woff2 | Bin 0 -> 21776 bytes
assets/inter-roman-latin-ext.0030eebd.woff2 | Bin 0 -> 59608 bytes
assets/inter-roman-latin.2ed14f66.woff2 | Bin 0 -> 42464 bytes
assets/inter-roman-vietnamese.14ce25a6.woff2 | Bin 0 -> 8492 bytes
...a_base_class-string-leaning.md.4e8cae7e.js | 89 +
...e_class-string-leaning.md.4e8cae7e.lean.js | 1 +
.../java_base_hashmap-learning.md.df9393b3.js | 569 ++
..._base_hashmap-learning.md.df9393b3.lean.js | 1 +
.../java_base_java8-features.md.f30c3c86.js | 133 +
...va_base_java8-features.md.f30c3c86.lean.js | 1 +
.../java_database_mysql-index.md.62831b6a.js | 23 +
...a_database_mysql-index.md.62831b6a.lean.js | 1 +
.../java_database_otter-sync.md.09fa7cb4.js | 123 +
...va_database_otter-sync.md.09fa7cb4.lean.js | 1 +
.../java_database_sql-optimize.md.05ce16c6.js | 7 +
..._database_sql-optimize.md.05ce16c6.lean.js | 1 +
...ava_devtool_maven-lifecycle.md.5e300ad4.js | 1 +
...evtool_maven-lifecycle.md.5e300ad4.lean.js | 1 +
...ol_nexus-resolve-dependency.md.4e726be9.js | 229 +
...xus-resolve-dependency.md.4e726be9.lean.js | 1 +
...ation-properties-annotation.md.13dd4d8a.js | 7 +
...-properties-annotation.md.13dd4d8a.lean.js | 1 +
assets/java_framework_cors.md.82f9a3ca.js | 55 +
.../java_framework_cors.md.82f9a3ca.lean.js | 1 +
...mework_dubbo-timeout-config.md.082e251f.js | 119 +
...k_dubbo-timeout-config.md.082e251f.lean.js | 1 +
..._framework_easyexcel-export.md.fb937ddb.js | 75 +
...ework_easyexcel-export.md.fb937ddb.lean.js | 1 +
..._framework_java-log-history.md.d166bc1a.js | 1 +
...ework_java-log-history.md.d166bc1a.lean.js | 1 +
..._java-spi-spring-autoconfig.md.ade568b4.js | 1 +
...-spi-spring-autoconfig.md.ade568b4.lean.js | 1 +
...va_framework_logback-config.md.b681fe4e.js | 183 +
...amework_logback-config.md.b681fe4e.lean.js | 1 +
...rk_mybatis-generator-config.md.110870b6.js | 737 +++
...batis-generator-config.md.110870b6.lean.js | 1 +
...ework_mybatisplus-generator.md.8fa65e2c.js | 813 +++
..._mybatisplus-generator.md.8fa65e2c.lean.js | 1 +
...ramework_netty-websocket-im.md.f7aa793e.js | 419 ++
...ork_netty-websocket-im.md.f7aa793e.lean.js | 1 +
...k_poi-event-mode-read-excel.md.e347bd15.js | 1809 ++++++
...-event-mode-read-excel.md.e347bd15.lean.js | 1 +
...amework_spring-cloud-config.md.77e12e5f.js | 121 +
...rk_spring-cloud-config.md.77e12e5f.lean.js | 1 +
...ramework_spring-conditional.md.3073718c.js | 1 +
...ork_spring-conditional.md.3073718c.lean.js | 1 +
...va_framework_spring-session.md.ffbd32c0.js | 103 +
...amework_spring-session.md.ffbd32c0.lean.js | 1 +
...framework_spring-validation.md.30615ab9.js | 177 +
...work_spring-validation.md.30615ab9.lean.js | 1 +
...ringcloud-graceful-shutdown.md.d2bca1bf.js | 241 +
...loud-graceful-shutdown.md.d2bca1bf.lean.js | 1 +
..._springmvc-process-analysis.md.8c3c2704.js | 617 ++
...ngmvc-process-analysis.md.8c3c2704.lean.js | 1 +
...a_framework_swagger-knife4j.md.cc715b99.js | 377 ++
...mework_swagger-knife4j.md.cc715b99.lean.js | 1 +
assets/java_framework_xxljob.md.af36d1ed.js | 1 +
.../java_framework_xxljob.md.af36d1ed.lean.js | 1 +
assets/java_index.md.79fb62a6.js | 1 +
assets/java_index.md.79fb62a6.lean.js | 1 +
...middleware_distributed-lock.md.5fa563fa.js | 143 +
...eware_distributed-lock.md.5fa563fa.lean.js | 1 +
.../java_middleware_kafka-cmd.md.18a019ea.js | 1 +
...a_middleware_kafka-cmd.md.18a019ea.lean.js | 1 +
...kafka-duplicate-consumption.md.a73bf055.js | 1 +
...-duplicate-consumption.md.a73bf055.lean.js | 1 +
...a_middleware_kafka-learning.md.2bbce33d.js | 1 +
...dleware_kafka-learning.md.2bbce33d.lean.js | 1 +
...a_middleware_kafka-practice.md.75806e8f.js | 305 +
...dleware_kafka-practice.md.75806e8f.lean.js | 1 +
assets/java_middleware_mq.md.fea7fbe2.js | 59 +
assets/java_middleware_mq.md.fea7fbe2.lean.js | 1 +
...cpu-high-usage-troubleshoot.md.1379a509.js | 19 +
...igh-usage-troubleshoot.md.1379a509.lean.js | 1 +
..._feign-request-userip-issue.md.930eb1d2.js | 129 +
...n-request-userip-issue.md.930eb1d2.lean.js | 1 +
...ava_others_linux-openoffice.md.9a0887a5.js | 1 +
...thers_linux-openoffice.md.9a0887a5.lean.js | 1 +
...rs_mybatis-classpath-config.md.0b37e160.js | 1 +
...batis-classpath-config.md.0b37e160.lean.js | 1 +
.../java_others_openjdk-jstack.md.930c4727.js | 1 +
..._others_openjdk-jstack.md.930c4727.lean.js | 1 +
.../java_others_poi-zip-closed.md.afe5c706.js | 15 +
..._others_poi-zip-closed.md.afe5c706.lean.js | 1 +
..._ribbon-refresh-canal-issue.md.8755fb26.js | 69 +
...on-refresh-canal-issue.md.8755fb26.lean.js | 1 +
.../java_others_wechat-decrypt.md.100b3cb2.js | 131 +
..._others_wechat-decrypt.md.100b3cb2.lean.js | 1 +
.../linux_applications_canal.md.3ae80630.js | 1 +
...nux_applications_canal.md.3ae80630.lean.js | 1 +
.../linux_applications_ffmpeg.md.5ad36ed6.js | 1 +
...ux_applications_ffmpeg.md.5ad36ed6.lean.js | 1 +
.../linux_applications_grafana.md.467af2ab.js | 35 +
...x_applications_grafana.md.467af2ab.lean.js | 1 +
...linux_applications_iptables.md.68ca0c19.js | 11 +
..._applications_iptables.md.68ca0c19.lean.js | 1 +
...applications_linux-selenium.md.863b0150.js | 37 +
...cations_linux-selenium.md.863b0150.lean.js | 1 +
.../linux_applications_nginx.md.58ec0ced.js | 53 +
...nux_applications_nginx.md.58ec0ced.lean.js | 1 +
.../linux_applications_screen.md.cf914696.js | 13 +
...ux_applications_screen.md.cf914696.lean.js | 1 +
...liyun-ssh-login-private-key.md.7bfbef57.js | 17 +
...-ssh-login-private-key.md.7bfbef57.lean.js | 1 +
...env_arch-linux-installation.md.ea3f396e.js | 81 +
...rch-linux-installation.md.ea3f396e.lean.js | 1 +
.../linux_env_bash-shortcuts.md.266683ba.js | 1 +
...nux_env_bash-shortcuts.md.266683ba.lean.js | 1 +
.../linux_env_centos7-firewall.md.1cddd4e6.js | 5 +
...x_env_centos7-firewall.md.1cddd4e6.lean.js | 1 +
assets/linux_env_link-alias.md.fecd9518.js | 1 +
.../linux_env_link-alias.md.fecd9518.lean.js | 1 +
assets/linux_env_linux-acl.md.41edb4f5.js | 1 +
.../linux_env_linux-acl.md.41edb4f5.lean.js | 1 +
assets/linux_env_linux-nfs.md.a1f6d1de.js | 3 +
.../linux_env_linux-nfs.md.a1f6d1de.lean.js | 1 +
...vate-key-server-refused-key.md.bd3056fb.js | 15 +
...key-server-refused-key.md.bd3056fb.lean.js | 1 +
assets/linux_env_linux-swap.md.d568c10d.js | 1 +
.../linux_env_linux-swap.md.d568c10d.lean.js | 1 +
.../linux_env_linux-vm-setup.md.b0939b1f.js | 489 ++
...nux_env_linux-vm-setup.md.b0939b1f.lean.js | 1 +
...ython3-centos7-installation.md.d1e22041.js | 21 +
...3-centos7-installation.md.d1e22041.lean.js | 1 +
assets/linux_env_ubuntu-server.md.2d555ee7.js | 35 +
...inux_env_ubuntu-server.md.2d555ee7.lean.js | 1 +
.../linux_hardware_linux-disk.md.bbf7d29a.js | 5 +
...ux_hardware_linux-disk.md.bbf7d29a.lean.js | 1 +
assets/linux_index.md.7bb09efb.js | 1 +
assets/linux_index.md.7bb09efb.lean.js | 1 +
...hon_base_python-concurrency.md.ffcb462e.js | 1023 ++++
...ase_python-concurrency.md.ffcb462e.lean.js | 1 +
...ython_base_python-decorator.md.d04d5f2d.js | 477 ++
..._base_python-decorator.md.d04d5f2d.lean.js | 1 +
.../python_base_python-syntax.md.2c659c14.js | 751 +++
...hon_base_python-syntax.md.2c659c14.lean.js | 1 +
...aptcha-identification-login.md.1e498a6f.js | 247 +
...a-identification-login.md.1e498a6f.lean.js | 1 +
...l-spider-full-station-crawl.md.354f19be.js | 229 +
...der-full-station-crawl.md.354f19be.lean.js | 1 +
...ributed-incremental-crawler.md.514a2ef0.js | 15 +
...ed-incremental-crawler.md.514a2ef0.lean.js | 1 +
...ython_crawler_incr-bilibili.md.5327913a.js | 635 +++
..._crawler_incr-bilibili.md.5327913a.lean.js | 1 +
...hreaded-crawling-pear-video.md.b228a8e6.js | 151 +
...ed-crawling-pear-video.md.b228a8e6.lean.js | 1 +
...on-ffmpeg-download-bilibili.md.9a1336f8.js | 133 +
...mpeg-download-bilibili.md.9a1336f8.lean.js | 1 +
...hon_crawler_requests-module.md.7ac89454.js | 473 ++
...rawler_requests-module.md.7ac89454.lean.js | 1 +
...hon_crawler_scrapy-advanced.md.b53f74db.js | 473 ++
...rawler_scrapy-advanced.md.b53f74db.lean.js | 1 +
...python_crawler_scrapy-basic.md.aa7d8ee9.js | 281 +
...n_crawler_scrapy-basic.md.aa7d8ee9.lean.js | 1 +
...hon_crawler_selenium-module.md.68f258de.js | 375 ++
...rawler_selenium-module.md.68f258de.lean.js | 1 +
...crawler_xiaohongshu-crawler.md.8beb052a.js | 1 +
...er_xiaohongshu-crawler.md.8beb052a.lean.js | 1 +
assets/python_index.md.da404463.js | 1 +
assets/python_index.md.da404463.lean.js | 1 +
...n_others_alfred-file-opener.md.32b52455.js | 43 +
...ers_alfred-file-opener.md.32b52455.lean.js | 1 +
...ython_others_argparse-basic.md.2172efcb.js | 359 ++
..._others_argparse-basic.md.2172efcb.lean.js | 1 +
...python_others_excel-ssh-rds.md.c4749b2d.js | 195 +
...n_others_excel-ssh-rds.md.c4749b2d.lean.js | 1 +
assets/python_others_pandas.md.014c66d5.js | 31 +
.../python_others_pandas.md.014c66d5.lean.js | 1 +
...others_windows-proxy-switch.md.412f4a54.js | 29 +
...s_windows-proxy-switch.md.412f4a54.lean.js | 1 +
...ython_others_youtube-upload.md.e5af72eb.js | 381 ++
..._others_youtube-upload.md.e5af72eb.lean.js | 1 +
assets/python_web_django-basic.md.bc6b21b4.js | 111 +
...ython_web_django-basic.md.bc6b21b4.lean.js | 1 +
.../python_web_pymysql-usage.md.662ffc55.js | 113 +
...thon_web_pymysql-usage.md.662ffc55.lean.js | 1 +
assets/style.89c45cea.css | 1 +
assets/tinker_index.md.93cd3244.js | 1 +
assets/tinker_index.md.93cd3244.lean.js | 1 +
...nternal-service-using-https.md.aa15aa02.js | 57 +
...al-service-using-https.md.aa15aa02.lean.js | 1 +
assets/tinker_network_frp.md.328b0d1e.js | 47 +
assets/tinker_network_frp.md.328b0d1e.lean.js | 1 +
...r_network_home-server-setup.md.1d34772d.js | 1249 ++++
...work_home-server-setup.md.1d34772d.lean.js | 1 +
...etwork_modem-to-bridge-mode.md.2e848579.js | 1 +
...k_modem-to-bridge-mode.md.2e848579.lean.js | 1 +
...tinker_network_openwrt-ipv6.md.24185378.js | 1 +
...r_network_openwrt-ipv6.md.24185378.lean.js | 1 +
assets/tinker_network_openwrt.md.c0552d88.js | 23 +
...tinker_network_openwrt.md.c0552d88.lean.js | 1 +
assets/tinker_network_pt.md.84a25333.js | 1 +
assets/tinker_network_pt.md.84a25333.lean.js | 1 +
...er_network_ups-nut-shutdown.md.ffa6bbea.js | 55 +
...twork_ups-nut-shutdown.md.ffa6bbea.lean.js | 1 +
...nker_network_windows-webdav.md.7fe33aec.js | 1 +
...network_windows-webdav.md.7fe33aec.lean.js | 1 +
...eck-after-abnormal-shutdown.md.7d4edc36.js | 1 +
...fter-abnormal-shutdown.md.7d4edc36.lean.js | 1 +
assets/tinker_vm_pve-truenas.md.f4f0d25b.js | 13 +
.../tinker_vm_pve-truenas.md.f4f0d25b.lean.js | 1 +
.../tinker_vm_vmware-network.md.091c5f0b.js | 1 +
...nker_vm_vmware-network.md.091c5f0b.lean.js | 1 +
.../Docker-compose/docker-compose-syntax.html | 103 +
docker/Docker/common-instruction.html | 35 +
docker/Docker/dockerfile-syntax.html | 125 +
docker/index.html | 25 +
favicon.ico | Bin 0 -> 15406 bytes
frontend/base/css.html | 415 ++
frontend/base/html.html | 25 +
frontend/base/javascript.html | 1485 +++++
frontend/base/jquery.html | 139 +
frontend/base/nodejs.html | 53 +
frontend/base/typescript.html | 1395 +++++
frontend/base/webpack.html | 165 +
frontend/framework/vue.html | 973 ++++
frontend/index.html | 25 +
frontend/others/elementplus-el-upload.html | 627 ++
frontend/others/hackingwithswift-1.html | 877 +++
frontend/others/hackingwithswift-2.html | 1437 +++++
frontend/others/swift-syntax.html | 329 ++
frontend/others/swiftui-syntax.html | 343 ++
golang/base/go-template.html | 415 ++
golang/base/golang-syntax.html | 5027 +++++++++++++++++
golang/cli/cobra.html | 393 ++
golang/index.html | 25 +
golang/tools/redis-cleaner.html | 167 +
golang/web/gin.html | 515 ++
hashmap.json | 1 +
index.html | 25 +
java/base/class-string-leaning.html | 113 +
java/base/hashmap-learning.html | 593 ++
java/base/java8-features.html | 157 +
java/database/mysql-index.html | 47 +
java/database/otter-sync.html | 147 +
java/database/sql-optimize.html | 31 +
java/devtool/maven-lifecycle.html | 25 +
java/devtool/nexus-resolve-dependency.html | 253 +
.../configuration-properties-annotation.html | 31 +
java/framework/cors.html | 79 +
java/framework/dubbo-timeout-config.html | 143 +
java/framework/easyexcel-export.html | 99 +
java/framework/java-log-history.html | 25 +
.../framework/java-spi-spring-autoconfig.html | 25 +
java/framework/logback-config.html | 207 +
java/framework/mybatis-generator-config.html | 761 +++
java/framework/mybatisplus-generator.html | 837 +++
java/framework/netty-websocket-im.html | 443 ++
java/framework/poi-event-mode-read-excel.html | 1833 ++++++
java/framework/spring-cloud-config.html | 145 +
java/framework/spring-conditional.html | 25 +
java/framework/spring-session.html | 127 +
java/framework/spring-validation.html | 201 +
.../springcloud-graceful-shutdown.html | 265 +
.../framework/springmvc-process-analysis.html | 641 +++
java/framework/swagger-knife4j.html | 401 ++
java/framework/xxljob.html | 25 +
java/index.html | 25 +
java/middleware/distributed-lock.html | 167 +
java/middleware/kafka-cmd.html | 25 +
.../kafka-duplicate-consumption.html | 25 +
java/middleware/kafka-learning.html | 25 +
java/middleware/kafka-practice.html | 329 ++
java/middleware/mq.html | 83 +
java/others/cpu-high-usage-troubleshoot.html | 43 +
java/others/feign-request-userip-issue.html | 153 +
java/others/linux-openoffice.html | 25 +
java/others/mybatis-classpath-config.html | 25 +
java/others/openjdk-jstack.html | 25 +
java/others/poi-zip-closed.html | 39 +
java/others/ribbon-refresh-canal-issue.html | 93 +
java/others/wechat-decrypt.html | 155 +
linux/applications/canal.html | 25 +
linux/applications/ffmpeg.html | 25 +
linux/applications/grafana.html | 59 +
linux/applications/iptables.html | 35 +
linux/applications/linux-selenium.html | 61 +
linux/applications/nginx.html | 77 +
linux/applications/screen.html | 37 +
linux/env/aliyun-ssh-login-private-key.html | 41 +
linux/env/arch-linux-installation.html | 105 +
linux/env/bash-shortcuts.html | 25 +
linux/env/centos7-firewall.html | 29 +
linux/env/link-alias.html | 25 +
linux/env/linux-acl.html | 25 +
linux/env/linux-nfs.html | 27 +
.../linux-private-key-server-refused-key.html | 39 +
linux/env/linux-swap.html | 25 +
linux/env/linux-vm-setup.html | 513 ++
linux/env/python3-centos7-installation.html | 45 +
linux/env/ubuntu-server.html | 59 +
linux/hardware/linux-disk.html | 29 +
linux/index.html | 25 +
logo.png | Bin 0 -> 104890 bytes
python/base/python-concurrency.html | 1047 ++++
python/base/python-decorator.html | 501 ++
python/base/python-syntax.html | 775 +++
.../crawler/captcha-identification-login.html | 271 +
.../crawl-spider-full-station-crawl.html | 253 +
.../distributed-incremental-crawler.html | 39 +
python/crawler/incr-bilibili.html | 659 +++
.../multithreaded-crawling-pear-video.html | 175 +
.../python-ffmpeg-download-bilibili.html | 157 +
python/crawler/requests-module.html | 497 ++
python/crawler/scrapy-advanced.html | 497 ++
python/crawler/scrapy-basic.html | 305 +
python/crawler/selenium-module.html | 399 ++
python/crawler/xiaohongshu-crawler.html | 25 +
python/index.html | 25 +
python/others/alfred-file-opener.html | 67 +
python/others/argparse-basic.html | 383 ++
python/others/excel-ssh-rds.html | 219 +
python/others/pandas.html | 55 +
python/others/windows-proxy-switch.html | 53 +
python/others/youtube-upload.html | 405 ++
python/web/django-basic.html | 135 +
python/web/pymysql-usage.html | 137 +
tinker/index.html | 25 +
.../access-internal-service-using-https.html | 81 +
tinker/network/frp.html | 71 +
tinker/network/home-server-setup.html | 1273 +++++
tinker/network/modem-to-bridge-mode.html | 25 +
tinker/network/openwrt-ipv6.html | 25 +
tinker/network/openwrt.html | 47 +
tinker/network/pt.html | 25 +
tinker/network/ups-nut-shutdown.html | 79 +
tinker/network/windows-webdav.html | 25 +
...ve-disk-check-after-abnormal-shutdown.html | 25 +
tinker/vm/pve-truenas.html | 37 +
tinker/vm/vmware-network.html | 25 +
459 files changed, 68921 insertions(+)
create mode 100644 404.html
create mode 100644 CNAME
create mode 100644 actions/designpattern/chain-of-responsibility-pattern.html
create mode 100644 actions/designpattern/strategy-pattern.html
create mode 100644 actions/env/cicd-vue.html
create mode 100644 actions/env/docker-desktop-windows.html
create mode 100644 actions/env/git-proxy.html
create mode 100644 actions/env/git-repo-multi-remote.html
create mode 100644 actions/env/github-actions.html
create mode 100644 actions/env/mysql-troubleshoot.html
create mode 100644 actions/env/powershell-beautify.html
create mode 100644 actions/env/startup-script-macos.html
create mode 100644 actions/env/terminal-proxy-macos.html
create mode 100644 actions/index.html
create mode 100644 actions/tools/book-searcher.html
create mode 100644 actions/tools/file-consistency-check.html
create mode 100644 actions/tools/git-cmd.html
create mode 100644 actions/tools/iterm2-oh-my-zsh.html
create mode 100644 actions/tools/iterm2-ssh-conn-config.html
create mode 100644 actions/tools/linux-time-machine.html
create mode 100644 actions/tools/markdown-syntax.html
create mode 100644 actions/tools/sketch.html
create mode 100644 actions/tools/typora-picgo-qiniu.html
create mode 100644 assets/actions_designpattern_chain-of-responsibility-pattern.md.e0e027d4.js
create mode 100644 assets/actions_designpattern_chain-of-responsibility-pattern.md.e0e027d4.lean.js
create mode 100644 assets/actions_designpattern_strategy-pattern.md.23b27876.js
create mode 100644 assets/actions_designpattern_strategy-pattern.md.23b27876.lean.js
create mode 100644 assets/actions_env_cicd-vue.md.a50e1517.js
create mode 100644 assets/actions_env_cicd-vue.md.a50e1517.lean.js
create mode 100644 assets/actions_env_docker-desktop-windows.md.5f59bd6b.js
create mode 100644 assets/actions_env_docker-desktop-windows.md.5f59bd6b.lean.js
create mode 100644 assets/actions_env_git-proxy.md.0bf9d81f.js
create mode 100644 assets/actions_env_git-proxy.md.0bf9d81f.lean.js
create mode 100644 assets/actions_env_git-repo-multi-remote.md.c9a74a94.js
create mode 100644 assets/actions_env_git-repo-multi-remote.md.c9a74a94.lean.js
create mode 100644 assets/actions_env_github-actions.md.bcabb284.js
create mode 100644 assets/actions_env_github-actions.md.bcabb284.lean.js
create mode 100644 assets/actions_env_mysql-troubleshoot.md.ca79ae83.js
create mode 100644 assets/actions_env_mysql-troubleshoot.md.ca79ae83.lean.js
create mode 100644 assets/actions_env_powershell-beautify.md.a053ec8f.js
create mode 100644 assets/actions_env_powershell-beautify.md.a053ec8f.lean.js
create mode 100644 assets/actions_env_startup-script-macos.md.0507c211.js
create mode 100644 assets/actions_env_startup-script-macos.md.0507c211.lean.js
create mode 100644 assets/actions_env_terminal-proxy-macos.md.90a20fc3.js
create mode 100644 assets/actions_env_terminal-proxy-macos.md.90a20fc3.lean.js
create mode 100644 assets/actions_index.md.3e437531.js
create mode 100644 assets/actions_index.md.3e437531.lean.js
create mode 100644 assets/actions_tools_book-searcher.md.8e834212.js
create mode 100644 assets/actions_tools_book-searcher.md.8e834212.lean.js
create mode 100644 assets/actions_tools_file-consistency-check.md.7d5c9dfa.js
create mode 100644 assets/actions_tools_file-consistency-check.md.7d5c9dfa.lean.js
create mode 100644 assets/actions_tools_git-cmd.md.2cccda47.js
create mode 100644 assets/actions_tools_git-cmd.md.2cccda47.lean.js
create mode 100644 assets/actions_tools_iterm2-oh-my-zsh.md.b5d4f68e.js
create mode 100644 assets/actions_tools_iterm2-oh-my-zsh.md.b5d4f68e.lean.js
create mode 100644 assets/actions_tools_iterm2-ssh-conn-config.md.91112d85.js
create mode 100644 assets/actions_tools_iterm2-ssh-conn-config.md.91112d85.lean.js
create mode 100644 assets/actions_tools_linux-time-machine.md.c1ed2026.js
create mode 100644 assets/actions_tools_linux-time-machine.md.c1ed2026.lean.js
create mode 100644 assets/actions_tools_markdown-syntax.md.49464579.js
create mode 100644 assets/actions_tools_markdown-syntax.md.49464579.lean.js
create mode 100644 assets/actions_tools_sketch.md.349c9ded.js
create mode 100644 assets/actions_tools_sketch.md.349c9ded.lean.js
create mode 100644 assets/actions_tools_typora-picgo-qiniu.md.e45de354.js
create mode 100644 assets/actions_tools_typora-picgo-qiniu.md.e45de354.lean.js
create mode 100644 assets/app.53da8f68.js
create mode 100644 assets/chunks/VPAlgoliaSearchBox.377a8f85.js
create mode 100644 assets/chunks/framework.b637c96f.js
create mode 100644 assets/chunks/theme.0d739576.js
create mode 100644 assets/docker_Docker-compose_docker-compose-syntax.md.0195f5ef.js
create mode 100644 assets/docker_Docker-compose_docker-compose-syntax.md.0195f5ef.lean.js
create mode 100644 assets/docker_Docker_common-instruction.md.e2127946.js
create mode 100644 assets/docker_Docker_common-instruction.md.e2127946.lean.js
create mode 100644 assets/docker_Docker_dockerfile-syntax.md.55c6cc9e.js
create mode 100644 assets/docker_Docker_dockerfile-syntax.md.55c6cc9e.lean.js
create mode 100644 assets/docker_index.md.a98dedbd.js
create mode 100644 assets/docker_index.md.a98dedbd.lean.js
create mode 100644 assets/frontend_base_css.md.70afca28.js
create mode 100644 assets/frontend_base_css.md.70afca28.lean.js
create mode 100644 assets/frontend_base_html.md.7eca1008.js
create mode 100644 assets/frontend_base_html.md.7eca1008.lean.js
create mode 100644 assets/frontend_base_javascript.md.4c1235ab.js
create mode 100644 assets/frontend_base_javascript.md.4c1235ab.lean.js
create mode 100644 assets/frontend_base_jquery.md.5c027802.js
create mode 100644 assets/frontend_base_jquery.md.5c027802.lean.js
create mode 100644 assets/frontend_base_nodejs.md.b2ea33da.js
create mode 100644 assets/frontend_base_nodejs.md.b2ea33da.lean.js
create mode 100644 assets/frontend_base_typescript.md.c2a79275.js
create mode 100644 assets/frontend_base_typescript.md.c2a79275.lean.js
create mode 100644 assets/frontend_base_webpack.md.606bbc01.js
create mode 100644 assets/frontend_base_webpack.md.606bbc01.lean.js
create mode 100644 assets/frontend_framework_vue.md.c825351a.js
create mode 100644 assets/frontend_framework_vue.md.c825351a.lean.js
create mode 100644 assets/frontend_index.md.971a7af8.js
create mode 100644 assets/frontend_index.md.971a7af8.lean.js
create mode 100644 assets/frontend_others_elementplus-el-upload.md.90eed779.js
create mode 100644 assets/frontend_others_elementplus-el-upload.md.90eed779.lean.js
create mode 100644 assets/frontend_others_hackingwithswift-1.md.4aa127cc.js
create mode 100644 assets/frontend_others_hackingwithswift-1.md.4aa127cc.lean.js
create mode 100644 assets/frontend_others_hackingwithswift-2.md.d6de0bd5.js
create mode 100644 assets/frontend_others_hackingwithswift-2.md.d6de0bd5.lean.js
create mode 100644 assets/frontend_others_swift-syntax.md.fe62a728.js
create mode 100644 assets/frontend_others_swift-syntax.md.fe62a728.lean.js
create mode 100644 assets/frontend_others_swiftui-syntax.md.e9ffe4fb.js
create mode 100644 assets/frontend_others_swiftui-syntax.md.e9ffe4fb.lean.js
create mode 100644 assets/golang_base_go-template.md.498dda04.js
create mode 100644 assets/golang_base_go-template.md.498dda04.lean.js
create mode 100644 assets/golang_base_golang-syntax.md.4fddf075.js
create mode 100644 assets/golang_base_golang-syntax.md.4fddf075.lean.js
create mode 100644 assets/golang_cli_cobra.md.4b826d1d.js
create mode 100644 assets/golang_cli_cobra.md.4b826d1d.lean.js
create mode 100644 assets/golang_index.md.d3c28484.js
create mode 100644 assets/golang_index.md.d3c28484.lean.js
create mode 100644 assets/golang_tools_redis-cleaner.md.40f7c6d6.js
create mode 100644 assets/golang_tools_redis-cleaner.md.40f7c6d6.lean.js
create mode 100644 assets/golang_web_gin.md.11770ac9.js
create mode 100644 assets/golang_web_gin.md.11770ac9.lean.js
create mode 100644 assets/index.md.b912301d.js
create mode 100644 assets/index.md.b912301d.lean.js
create mode 100644 assets/inter-italic-cyrillic-ext.33bd5a8e.woff2
create mode 100644 assets/inter-italic-cyrillic.ea42a392.woff2
create mode 100644 assets/inter-italic-greek-ext.4fbe9427.woff2
create mode 100644 assets/inter-italic-greek.8f4463c4.woff2
create mode 100644 assets/inter-italic-latin-ext.bd8920cc.woff2
create mode 100644 assets/inter-italic-latin.bd3b6f56.woff2
create mode 100644 assets/inter-italic-vietnamese.6ce511fb.woff2
create mode 100644 assets/inter-roman-cyrillic-ext.e75737ce.woff2
create mode 100644 assets/inter-roman-cyrillic.5f2c6c8c.woff2
create mode 100644 assets/inter-roman-greek-ext.ab0619bc.woff2
create mode 100644 assets/inter-roman-greek.d5a6d92a.woff2
create mode 100644 assets/inter-roman-latin-ext.0030eebd.woff2
create mode 100644 assets/inter-roman-latin.2ed14f66.woff2
create mode 100644 assets/inter-roman-vietnamese.14ce25a6.woff2
create mode 100644 assets/java_base_class-string-leaning.md.4e8cae7e.js
create mode 100644 assets/java_base_class-string-leaning.md.4e8cae7e.lean.js
create mode 100644 assets/java_base_hashmap-learning.md.df9393b3.js
create mode 100644 assets/java_base_hashmap-learning.md.df9393b3.lean.js
create mode 100644 assets/java_base_java8-features.md.f30c3c86.js
create mode 100644 assets/java_base_java8-features.md.f30c3c86.lean.js
create mode 100644 assets/java_database_mysql-index.md.62831b6a.js
create mode 100644 assets/java_database_mysql-index.md.62831b6a.lean.js
create mode 100644 assets/java_database_otter-sync.md.09fa7cb4.js
create mode 100644 assets/java_database_otter-sync.md.09fa7cb4.lean.js
create mode 100644 assets/java_database_sql-optimize.md.05ce16c6.js
create mode 100644 assets/java_database_sql-optimize.md.05ce16c6.lean.js
create mode 100644 assets/java_devtool_maven-lifecycle.md.5e300ad4.js
create mode 100644 assets/java_devtool_maven-lifecycle.md.5e300ad4.lean.js
create mode 100644 assets/java_devtool_nexus-resolve-dependency.md.4e726be9.js
create mode 100644 assets/java_devtool_nexus-resolve-dependency.md.4e726be9.lean.js
create mode 100644 assets/java_framework_configuration-properties-annotation.md.13dd4d8a.js
create mode 100644 assets/java_framework_configuration-properties-annotation.md.13dd4d8a.lean.js
create mode 100644 assets/java_framework_cors.md.82f9a3ca.js
create mode 100644 assets/java_framework_cors.md.82f9a3ca.lean.js
create mode 100644 assets/java_framework_dubbo-timeout-config.md.082e251f.js
create mode 100644 assets/java_framework_dubbo-timeout-config.md.082e251f.lean.js
create mode 100644 assets/java_framework_easyexcel-export.md.fb937ddb.js
create mode 100644 assets/java_framework_easyexcel-export.md.fb937ddb.lean.js
create mode 100644 assets/java_framework_java-log-history.md.d166bc1a.js
create mode 100644 assets/java_framework_java-log-history.md.d166bc1a.lean.js
create mode 100644 assets/java_framework_java-spi-spring-autoconfig.md.ade568b4.js
create mode 100644 assets/java_framework_java-spi-spring-autoconfig.md.ade568b4.lean.js
create mode 100644 assets/java_framework_logback-config.md.b681fe4e.js
create mode 100644 assets/java_framework_logback-config.md.b681fe4e.lean.js
create mode 100644 assets/java_framework_mybatis-generator-config.md.110870b6.js
create mode 100644 assets/java_framework_mybatis-generator-config.md.110870b6.lean.js
create mode 100644 assets/java_framework_mybatisplus-generator.md.8fa65e2c.js
create mode 100644 assets/java_framework_mybatisplus-generator.md.8fa65e2c.lean.js
create mode 100644 assets/java_framework_netty-websocket-im.md.f7aa793e.js
create mode 100644 assets/java_framework_netty-websocket-im.md.f7aa793e.lean.js
create mode 100644 assets/java_framework_poi-event-mode-read-excel.md.e347bd15.js
create mode 100644 assets/java_framework_poi-event-mode-read-excel.md.e347bd15.lean.js
create mode 100644 assets/java_framework_spring-cloud-config.md.77e12e5f.js
create mode 100644 assets/java_framework_spring-cloud-config.md.77e12e5f.lean.js
create mode 100644 assets/java_framework_spring-conditional.md.3073718c.js
create mode 100644 assets/java_framework_spring-conditional.md.3073718c.lean.js
create mode 100644 assets/java_framework_spring-session.md.ffbd32c0.js
create mode 100644 assets/java_framework_spring-session.md.ffbd32c0.lean.js
create mode 100644 assets/java_framework_spring-validation.md.30615ab9.js
create mode 100644 assets/java_framework_spring-validation.md.30615ab9.lean.js
create mode 100644 assets/java_framework_springcloud-graceful-shutdown.md.d2bca1bf.js
create mode 100644 assets/java_framework_springcloud-graceful-shutdown.md.d2bca1bf.lean.js
create mode 100644 assets/java_framework_springmvc-process-analysis.md.8c3c2704.js
create mode 100644 assets/java_framework_springmvc-process-analysis.md.8c3c2704.lean.js
create mode 100644 assets/java_framework_swagger-knife4j.md.cc715b99.js
create mode 100644 assets/java_framework_swagger-knife4j.md.cc715b99.lean.js
create mode 100644 assets/java_framework_xxljob.md.af36d1ed.js
create mode 100644 assets/java_framework_xxljob.md.af36d1ed.lean.js
create mode 100644 assets/java_index.md.79fb62a6.js
create mode 100644 assets/java_index.md.79fb62a6.lean.js
create mode 100644 assets/java_middleware_distributed-lock.md.5fa563fa.js
create mode 100644 assets/java_middleware_distributed-lock.md.5fa563fa.lean.js
create mode 100644 assets/java_middleware_kafka-cmd.md.18a019ea.js
create mode 100644 assets/java_middleware_kafka-cmd.md.18a019ea.lean.js
create mode 100644 assets/java_middleware_kafka-duplicate-consumption.md.a73bf055.js
create mode 100644 assets/java_middleware_kafka-duplicate-consumption.md.a73bf055.lean.js
create mode 100644 assets/java_middleware_kafka-learning.md.2bbce33d.js
create mode 100644 assets/java_middleware_kafka-learning.md.2bbce33d.lean.js
create mode 100644 assets/java_middleware_kafka-practice.md.75806e8f.js
create mode 100644 assets/java_middleware_kafka-practice.md.75806e8f.lean.js
create mode 100644 assets/java_middleware_mq.md.fea7fbe2.js
create mode 100644 assets/java_middleware_mq.md.fea7fbe2.lean.js
create mode 100644 assets/java_others_cpu-high-usage-troubleshoot.md.1379a509.js
create mode 100644 assets/java_others_cpu-high-usage-troubleshoot.md.1379a509.lean.js
create mode 100644 assets/java_others_feign-request-userip-issue.md.930eb1d2.js
create mode 100644 assets/java_others_feign-request-userip-issue.md.930eb1d2.lean.js
create mode 100644 assets/java_others_linux-openoffice.md.9a0887a5.js
create mode 100644 assets/java_others_linux-openoffice.md.9a0887a5.lean.js
create mode 100644 assets/java_others_mybatis-classpath-config.md.0b37e160.js
create mode 100644 assets/java_others_mybatis-classpath-config.md.0b37e160.lean.js
create mode 100644 assets/java_others_openjdk-jstack.md.930c4727.js
create mode 100644 assets/java_others_openjdk-jstack.md.930c4727.lean.js
create mode 100644 assets/java_others_poi-zip-closed.md.afe5c706.js
create mode 100644 assets/java_others_poi-zip-closed.md.afe5c706.lean.js
create mode 100644 assets/java_others_ribbon-refresh-canal-issue.md.8755fb26.js
create mode 100644 assets/java_others_ribbon-refresh-canal-issue.md.8755fb26.lean.js
create mode 100644 assets/java_others_wechat-decrypt.md.100b3cb2.js
create mode 100644 assets/java_others_wechat-decrypt.md.100b3cb2.lean.js
create mode 100644 assets/linux_applications_canal.md.3ae80630.js
create mode 100644 assets/linux_applications_canal.md.3ae80630.lean.js
create mode 100644 assets/linux_applications_ffmpeg.md.5ad36ed6.js
create mode 100644 assets/linux_applications_ffmpeg.md.5ad36ed6.lean.js
create mode 100644 assets/linux_applications_grafana.md.467af2ab.js
create mode 100644 assets/linux_applications_grafana.md.467af2ab.lean.js
create mode 100644 assets/linux_applications_iptables.md.68ca0c19.js
create mode 100644 assets/linux_applications_iptables.md.68ca0c19.lean.js
create mode 100644 assets/linux_applications_linux-selenium.md.863b0150.js
create mode 100644 assets/linux_applications_linux-selenium.md.863b0150.lean.js
create mode 100644 assets/linux_applications_nginx.md.58ec0ced.js
create mode 100644 assets/linux_applications_nginx.md.58ec0ced.lean.js
create mode 100644 assets/linux_applications_screen.md.cf914696.js
create mode 100644 assets/linux_applications_screen.md.cf914696.lean.js
create mode 100644 assets/linux_env_aliyun-ssh-login-private-key.md.7bfbef57.js
create mode 100644 assets/linux_env_aliyun-ssh-login-private-key.md.7bfbef57.lean.js
create mode 100644 assets/linux_env_arch-linux-installation.md.ea3f396e.js
create mode 100644 assets/linux_env_arch-linux-installation.md.ea3f396e.lean.js
create mode 100644 assets/linux_env_bash-shortcuts.md.266683ba.js
create mode 100644 assets/linux_env_bash-shortcuts.md.266683ba.lean.js
create mode 100644 assets/linux_env_centos7-firewall.md.1cddd4e6.js
create mode 100644 assets/linux_env_centos7-firewall.md.1cddd4e6.lean.js
create mode 100644 assets/linux_env_link-alias.md.fecd9518.js
create mode 100644 assets/linux_env_link-alias.md.fecd9518.lean.js
create mode 100644 assets/linux_env_linux-acl.md.41edb4f5.js
create mode 100644 assets/linux_env_linux-acl.md.41edb4f5.lean.js
create mode 100644 assets/linux_env_linux-nfs.md.a1f6d1de.js
create mode 100644 assets/linux_env_linux-nfs.md.a1f6d1de.lean.js
create mode 100644 assets/linux_env_linux-private-key-server-refused-key.md.bd3056fb.js
create mode 100644 assets/linux_env_linux-private-key-server-refused-key.md.bd3056fb.lean.js
create mode 100644 assets/linux_env_linux-swap.md.d568c10d.js
create mode 100644 assets/linux_env_linux-swap.md.d568c10d.lean.js
create mode 100644 assets/linux_env_linux-vm-setup.md.b0939b1f.js
create mode 100644 assets/linux_env_linux-vm-setup.md.b0939b1f.lean.js
create mode 100644 assets/linux_env_python3-centos7-installation.md.d1e22041.js
create mode 100644 assets/linux_env_python3-centos7-installation.md.d1e22041.lean.js
create mode 100644 assets/linux_env_ubuntu-server.md.2d555ee7.js
create mode 100644 assets/linux_env_ubuntu-server.md.2d555ee7.lean.js
create mode 100644 assets/linux_hardware_linux-disk.md.bbf7d29a.js
create mode 100644 assets/linux_hardware_linux-disk.md.bbf7d29a.lean.js
create mode 100644 assets/linux_index.md.7bb09efb.js
create mode 100644 assets/linux_index.md.7bb09efb.lean.js
create mode 100644 assets/python_base_python-concurrency.md.ffcb462e.js
create mode 100644 assets/python_base_python-concurrency.md.ffcb462e.lean.js
create mode 100644 assets/python_base_python-decorator.md.d04d5f2d.js
create mode 100644 assets/python_base_python-decorator.md.d04d5f2d.lean.js
create mode 100644 assets/python_base_python-syntax.md.2c659c14.js
create mode 100644 assets/python_base_python-syntax.md.2c659c14.lean.js
create mode 100644 assets/python_crawler_captcha-identification-login.md.1e498a6f.js
create mode 100644 assets/python_crawler_captcha-identification-login.md.1e498a6f.lean.js
create mode 100644 assets/python_crawler_crawl-spider-full-station-crawl.md.354f19be.js
create mode 100644 assets/python_crawler_crawl-spider-full-station-crawl.md.354f19be.lean.js
create mode 100644 assets/python_crawler_distributed-incremental-crawler.md.514a2ef0.js
create mode 100644 assets/python_crawler_distributed-incremental-crawler.md.514a2ef0.lean.js
create mode 100644 assets/python_crawler_incr-bilibili.md.5327913a.js
create mode 100644 assets/python_crawler_incr-bilibili.md.5327913a.lean.js
create mode 100644 assets/python_crawler_multithreaded-crawling-pear-video.md.b228a8e6.js
create mode 100644 assets/python_crawler_multithreaded-crawling-pear-video.md.b228a8e6.lean.js
create mode 100644 assets/python_crawler_python-ffmpeg-download-bilibili.md.9a1336f8.js
create mode 100644 assets/python_crawler_python-ffmpeg-download-bilibili.md.9a1336f8.lean.js
create mode 100644 assets/python_crawler_requests-module.md.7ac89454.js
create mode 100644 assets/python_crawler_requests-module.md.7ac89454.lean.js
create mode 100644 assets/python_crawler_scrapy-advanced.md.b53f74db.js
create mode 100644 assets/python_crawler_scrapy-advanced.md.b53f74db.lean.js
create mode 100644 assets/python_crawler_scrapy-basic.md.aa7d8ee9.js
create mode 100644 assets/python_crawler_scrapy-basic.md.aa7d8ee9.lean.js
create mode 100644 assets/python_crawler_selenium-module.md.68f258de.js
create mode 100644 assets/python_crawler_selenium-module.md.68f258de.lean.js
create mode 100644 assets/python_crawler_xiaohongshu-crawler.md.8beb052a.js
create mode 100644 assets/python_crawler_xiaohongshu-crawler.md.8beb052a.lean.js
create mode 100644 assets/python_index.md.da404463.js
create mode 100644 assets/python_index.md.da404463.lean.js
create mode 100644 assets/python_others_alfred-file-opener.md.32b52455.js
create mode 100644 assets/python_others_alfred-file-opener.md.32b52455.lean.js
create mode 100644 assets/python_others_argparse-basic.md.2172efcb.js
create mode 100644 assets/python_others_argparse-basic.md.2172efcb.lean.js
create mode 100644 assets/python_others_excel-ssh-rds.md.c4749b2d.js
create mode 100644 assets/python_others_excel-ssh-rds.md.c4749b2d.lean.js
create mode 100644 assets/python_others_pandas.md.014c66d5.js
create mode 100644 assets/python_others_pandas.md.014c66d5.lean.js
create mode 100644 assets/python_others_windows-proxy-switch.md.412f4a54.js
create mode 100644 assets/python_others_windows-proxy-switch.md.412f4a54.lean.js
create mode 100644 assets/python_others_youtube-upload.md.e5af72eb.js
create mode 100644 assets/python_others_youtube-upload.md.e5af72eb.lean.js
create mode 100644 assets/python_web_django-basic.md.bc6b21b4.js
create mode 100644 assets/python_web_django-basic.md.bc6b21b4.lean.js
create mode 100644 assets/python_web_pymysql-usage.md.662ffc55.js
create mode 100644 assets/python_web_pymysql-usage.md.662ffc55.lean.js
create mode 100644 assets/style.89c45cea.css
create mode 100644 assets/tinker_index.md.93cd3244.js
create mode 100644 assets/tinker_index.md.93cd3244.lean.js
create mode 100644 assets/tinker_network_access-internal-service-using-https.md.aa15aa02.js
create mode 100644 assets/tinker_network_access-internal-service-using-https.md.aa15aa02.lean.js
create mode 100644 assets/tinker_network_frp.md.328b0d1e.js
create mode 100644 assets/tinker_network_frp.md.328b0d1e.lean.js
create mode 100644 assets/tinker_network_home-server-setup.md.1d34772d.js
create mode 100644 assets/tinker_network_home-server-setup.md.1d34772d.lean.js
create mode 100644 assets/tinker_network_modem-to-bridge-mode.md.2e848579.js
create mode 100644 assets/tinker_network_modem-to-bridge-mode.md.2e848579.lean.js
create mode 100644 assets/tinker_network_openwrt-ipv6.md.24185378.js
create mode 100644 assets/tinker_network_openwrt-ipv6.md.24185378.lean.js
create mode 100644 assets/tinker_network_openwrt.md.c0552d88.js
create mode 100644 assets/tinker_network_openwrt.md.c0552d88.lean.js
create mode 100644 assets/tinker_network_pt.md.84a25333.js
create mode 100644 assets/tinker_network_pt.md.84a25333.lean.js
create mode 100644 assets/tinker_network_ups-nut-shutdown.md.ffa6bbea.js
create mode 100644 assets/tinker_network_ups-nut-shutdown.md.ffa6bbea.lean.js
create mode 100644 assets/tinker_network_windows-webdav.md.7fe33aec.js
create mode 100644 assets/tinker_network_windows-webdav.md.7fe33aec.lean.js
create mode 100644 assets/tinker_vm_pve-disk-check-after-abnormal-shutdown.md.7d4edc36.js
create mode 100644 assets/tinker_vm_pve-disk-check-after-abnormal-shutdown.md.7d4edc36.lean.js
create mode 100644 assets/tinker_vm_pve-truenas.md.f4f0d25b.js
create mode 100644 assets/tinker_vm_pve-truenas.md.f4f0d25b.lean.js
create mode 100644 assets/tinker_vm_vmware-network.md.091c5f0b.js
create mode 100644 assets/tinker_vm_vmware-network.md.091c5f0b.lean.js
create mode 100644 docker/Docker-compose/docker-compose-syntax.html
create mode 100644 docker/Docker/common-instruction.html
create mode 100644 docker/Docker/dockerfile-syntax.html
create mode 100644 docker/index.html
create mode 100644 favicon.ico
create mode 100644 frontend/base/css.html
create mode 100644 frontend/base/html.html
create mode 100644 frontend/base/javascript.html
create mode 100644 frontend/base/jquery.html
create mode 100644 frontend/base/nodejs.html
create mode 100644 frontend/base/typescript.html
create mode 100644 frontend/base/webpack.html
create mode 100644 frontend/framework/vue.html
create mode 100644 frontend/index.html
create mode 100644 frontend/others/elementplus-el-upload.html
create mode 100644 frontend/others/hackingwithswift-1.html
create mode 100644 frontend/others/hackingwithswift-2.html
create mode 100644 frontend/others/swift-syntax.html
create mode 100644 frontend/others/swiftui-syntax.html
create mode 100644 golang/base/go-template.html
create mode 100644 golang/base/golang-syntax.html
create mode 100644 golang/cli/cobra.html
create mode 100644 golang/index.html
create mode 100644 golang/tools/redis-cleaner.html
create mode 100644 golang/web/gin.html
create mode 100644 hashmap.json
create mode 100644 index.html
create mode 100644 java/base/class-string-leaning.html
create mode 100644 java/base/hashmap-learning.html
create mode 100644 java/base/java8-features.html
create mode 100644 java/database/mysql-index.html
create mode 100644 java/database/otter-sync.html
create mode 100644 java/database/sql-optimize.html
create mode 100644 java/devtool/maven-lifecycle.html
create mode 100644 java/devtool/nexus-resolve-dependency.html
create mode 100644 java/framework/configuration-properties-annotation.html
create mode 100644 java/framework/cors.html
create mode 100644 java/framework/dubbo-timeout-config.html
create mode 100644 java/framework/easyexcel-export.html
create mode 100644 java/framework/java-log-history.html
create mode 100644 java/framework/java-spi-spring-autoconfig.html
create mode 100644 java/framework/logback-config.html
create mode 100644 java/framework/mybatis-generator-config.html
create mode 100644 java/framework/mybatisplus-generator.html
create mode 100644 java/framework/netty-websocket-im.html
create mode 100644 java/framework/poi-event-mode-read-excel.html
create mode 100644 java/framework/spring-cloud-config.html
create mode 100644 java/framework/spring-conditional.html
create mode 100644 java/framework/spring-session.html
create mode 100644 java/framework/spring-validation.html
create mode 100644 java/framework/springcloud-graceful-shutdown.html
create mode 100644 java/framework/springmvc-process-analysis.html
create mode 100644 java/framework/swagger-knife4j.html
create mode 100644 java/framework/xxljob.html
create mode 100644 java/index.html
create mode 100644 java/middleware/distributed-lock.html
create mode 100644 java/middleware/kafka-cmd.html
create mode 100644 java/middleware/kafka-duplicate-consumption.html
create mode 100644 java/middleware/kafka-learning.html
create mode 100644 java/middleware/kafka-practice.html
create mode 100644 java/middleware/mq.html
create mode 100644 java/others/cpu-high-usage-troubleshoot.html
create mode 100644 java/others/feign-request-userip-issue.html
create mode 100644 java/others/linux-openoffice.html
create mode 100644 java/others/mybatis-classpath-config.html
create mode 100644 java/others/openjdk-jstack.html
create mode 100644 java/others/poi-zip-closed.html
create mode 100644 java/others/ribbon-refresh-canal-issue.html
create mode 100644 java/others/wechat-decrypt.html
create mode 100644 linux/applications/canal.html
create mode 100644 linux/applications/ffmpeg.html
create mode 100644 linux/applications/grafana.html
create mode 100644 linux/applications/iptables.html
create mode 100644 linux/applications/linux-selenium.html
create mode 100644 linux/applications/nginx.html
create mode 100644 linux/applications/screen.html
create mode 100644 linux/env/aliyun-ssh-login-private-key.html
create mode 100644 linux/env/arch-linux-installation.html
create mode 100644 linux/env/bash-shortcuts.html
create mode 100644 linux/env/centos7-firewall.html
create mode 100644 linux/env/link-alias.html
create mode 100644 linux/env/linux-acl.html
create mode 100644 linux/env/linux-nfs.html
create mode 100644 linux/env/linux-private-key-server-refused-key.html
create mode 100644 linux/env/linux-swap.html
create mode 100644 linux/env/linux-vm-setup.html
create mode 100644 linux/env/python3-centos7-installation.html
create mode 100644 linux/env/ubuntu-server.html
create mode 100644 linux/hardware/linux-disk.html
create mode 100644 linux/index.html
create mode 100644 logo.png
create mode 100644 python/base/python-concurrency.html
create mode 100644 python/base/python-decorator.html
create mode 100644 python/base/python-syntax.html
create mode 100644 python/crawler/captcha-identification-login.html
create mode 100644 python/crawler/crawl-spider-full-station-crawl.html
create mode 100644 python/crawler/distributed-incremental-crawler.html
create mode 100644 python/crawler/incr-bilibili.html
create mode 100644 python/crawler/multithreaded-crawling-pear-video.html
create mode 100644 python/crawler/python-ffmpeg-download-bilibili.html
create mode 100644 python/crawler/requests-module.html
create mode 100644 python/crawler/scrapy-advanced.html
create mode 100644 python/crawler/scrapy-basic.html
create mode 100644 python/crawler/selenium-module.html
create mode 100644 python/crawler/xiaohongshu-crawler.html
create mode 100644 python/index.html
create mode 100644 python/others/alfred-file-opener.html
create mode 100644 python/others/argparse-basic.html
create mode 100644 python/others/excel-ssh-rds.html
create mode 100644 python/others/pandas.html
create mode 100644 python/others/windows-proxy-switch.html
create mode 100644 python/others/youtube-upload.html
create mode 100644 python/web/django-basic.html
create mode 100644 python/web/pymysql-usage.html
create mode 100644 tinker/index.html
create mode 100644 tinker/network/access-internal-service-using-https.html
create mode 100644 tinker/network/frp.html
create mode 100644 tinker/network/home-server-setup.html
create mode 100644 tinker/network/modem-to-bridge-mode.html
create mode 100644 tinker/network/openwrt-ipv6.html
create mode 100644 tinker/network/openwrt.html
create mode 100644 tinker/network/pt.html
create mode 100644 tinker/network/ups-nut-shutdown.html
create mode 100644 tinker/network/windows-webdav.html
create mode 100644 tinker/vm/pve-disk-check-after-abnormal-shutdown.html
create mode 100644 tinker/vm/pve-truenas.html
create mode 100644 tinker/vm/vmware-network.html
diff --git a/404.html b/404.html
new file mode 100644
index 000000000..39130e9b3
--- /dev/null
+++ b/404.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ 404 | 故事
+
+
+
+
+
+
+
+
+
+
+
+ Skip to content 404
PAGE NOT FOUND
But if you don't change your direction, and if you keep looking, you may end up where you are heading.
+
+
+
+
\ No newline at end of file
diff --git a/CNAME b/CNAME
new file mode 100644
index 000000000..15d602378
--- /dev/null
+++ b/CNAME
@@ -0,0 +1 @@
+blog.storyxc.com
diff --git a/actions/designpattern/chain-of-responsibility-pattern.html b/actions/designpattern/chain-of-responsibility-pattern.html
new file mode 100644
index 000000000..8dee33d8d
--- /dev/null
+++ b/actions/designpattern/chain-of-responsibility-pattern.html
@@ -0,0 +1,125 @@
+
+
+
+
+
+ 责任链模式 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/designpattern/strategy-pattern.html b/actions/designpattern/strategy-pattern.html
new file mode 100644
index 000000000..b0c3dcf0d
--- /dev/null
+++ b/actions/designpattern/strategy-pattern.html
@@ -0,0 +1,673 @@
+
+
+
+
+
+ 策略模式的具体实现 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/cicd-vue.html b/actions/env/cicd-vue.html
new file mode 100644
index 000000000..f6457ebc9
--- /dev/null
+++ b/actions/env/cicd-vue.html
@@ -0,0 +1,137 @@
+
+
+
+
+
+ docker+jenkins+gitee自动化部署vue项目 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/docker-desktop-windows.html b/actions/env/docker-desktop-windows.html
new file mode 100644
index 000000000..d6cde476c
--- /dev/null
+++ b/actions/env/docker-desktop-windows.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Windows下Docker Desktop安装 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/git-proxy.html b/actions/env/git-proxy.html
new file mode 100644
index 000000000..8348eaecd
--- /dev/null
+++ b/actions/env/git-proxy.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ git配置socks5代理解决github上down代码慢的问题 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/git-repo-multi-remote.html b/actions/env/git-repo-multi-remote.html
new file mode 100644
index 000000000..423721b64
--- /dev/null
+++ b/actions/env/git-repo-multi-remote.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+ git配置多ssh-key && Gitee 和 Github 同步更新 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/github-actions.html b/actions/env/github-actions.html
new file mode 100644
index 000000000..72756408a
--- /dev/null
+++ b/actions/env/github-actions.html
@@ -0,0 +1,137 @@
+
+
+
+
+
+ 使用github actions进行持续部署 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/mysql-troubleshoot.html b/actions/env/mysql-troubleshoot.html
new file mode 100644
index 000000000..69648132a
--- /dev/null
+++ b/actions/env/mysql-troubleshoot.html
@@ -0,0 +1,157 @@
+
+
+
+
+
+ mysql启动报错排查及处理 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/powershell-beautify.html b/actions/env/powershell-beautify.html
new file mode 100644
index 000000000..df71377f1
--- /dev/null
+++ b/actions/env/powershell-beautify.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+ powershell美化 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/startup-script-macos.html b/actions/env/startup-script-macos.html
new file mode 100644
index 000000000..ef1bade2e
--- /dev/null
+++ b/actions/env/startup-script-macos.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+ macos开机自动执行脚本 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/env/terminal-proxy-macos.html b/actions/env/terminal-proxy-macos.html
new file mode 100644
index 000000000..167a5d400
--- /dev/null
+++ b/actions/env/terminal-proxy-macos.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+ macOS开启终端的代理 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/index.html b/actions/index.html
new file mode 100644
index 000000000..97f21f306
--- /dev/null
+++ b/actions/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Actions | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/book-searcher.html b/actions/tools/book-searcher.html
new file mode 100644
index 000000000..d8204d8b5
--- /dev/null
+++ b/actions/tools/book-searcher.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+ book-searcher电子书镜像站点 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/file-consistency-check.html b/actions/tools/file-consistency-check.html
new file mode 100644
index 000000000..d3d635254
--- /dev/null
+++ b/actions/tools/file-consistency-check.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ 各系统下校验文件一致性 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/git-cmd.html b/actions/tools/git-cmd.html
new file mode 100644
index 000000000..269d7e196
--- /dev/null
+++ b/actions/tools/git-cmd.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ git命令整理 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/iterm2-oh-my-zsh.html b/actions/tools/iterm2-oh-my-zsh.html
new file mode 100644
index 000000000..3c8727e9e
--- /dev/null
+++ b/actions/tools/iterm2-oh-my-zsh.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ iterm2配合oh-my-zsh配置个性主题终端 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/iterm2-ssh-conn-config.html b/actions/tools/iterm2-ssh-conn-config.html
new file mode 100644
index 000000000..f46f04ae5
--- /dev/null
+++ b/actions/tools/iterm2-ssh-conn-config.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ iterm2配置ssh快速连接 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/linux-time-machine.html b/actions/tools/linux-time-machine.html
new file mode 100644
index 000000000..3ed21f6a1
--- /dev/null
+++ b/actions/tools/linux-time-machine.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+ linux设置macOS时间机器server | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/markdown-syntax.html b/actions/tools/markdown-syntax.html
new file mode 100644
index 000000000..c32d0a5a1
--- /dev/null
+++ b/actions/tools/markdown-syntax.html
@@ -0,0 +1,123 @@
+
+
+
+
+
+ Markdown基础语法 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/sketch.html b/actions/tools/sketch.html
new file mode 100644
index 000000000..4a8bd8eb7
--- /dev/null
+++ b/actions/tools/sketch.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Sketch | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/actions/tools/typora-picgo-qiniu.html b/actions/tools/typora-picgo-qiniu.html
new file mode 100644
index 000000000..2e1f06481
--- /dev/null
+++ b/actions/tools/typora-picgo-qiniu.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Typora、PicGo、七牛云实现markdown图片自动上传图床 | 故事
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/actions_designpattern_chain-of-responsibility-pattern.md.e0e027d4.js b/assets/actions_designpattern_chain-of-responsibility-pattern.md.e0e027d4.js
new file mode 100644
index 000000000..95a369509
--- /dev/null
+++ b/assets/actions_designpattern_chain-of-responsibility-pattern.md.e0e027d4.js
@@ -0,0 +1,101 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"责任链模式","description":"","frontmatter":{},"headers":[],"relativePath":"actions/designpattern/chain-of-responsibility-pattern.md","filePath":"actions/designpattern/chain-of-responsibility-pattern.md","lastUpdated":1694368780000}'),p={name:"actions/designpattern/chain-of-responsibility-pattern.md"},o=l(`责任链模式 背景 根据不同的订单结算金额规则配置,来计算出每一条订单商品的结算金额,每条规则都有自己的匹配条件,匹配上的则应用该条规则所配置的结算金额计算公式。客户可以配置多条不同的规则,组成一条规则链。每条订单商品记录按规则的顺序依次进行条件匹配,如果匹配上则停止,匹配不上继续,直至规则链结束,没有匹配则使用默认规则进行兜底。
责任链模式 责任链模式(Chain of Responsibility Pattern)是一种软件设计模式,它可以让多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。在责任链模式中,每个对象都有其对应的处理请求的方法,如果一个对象不能够处理该请求,那么它会将这个请求传递给下一个对象来处理,直到找到能够处理该请求的对象为止。
责任链模式通常由以下几个角色组成:
抽象处理者(Handler):定义了处理请求的接口,同时也可以实现一些公共的处理逻辑; 具体处理者(Concrete Handler):继承自抽象处理者,实现了具体的处理方法,如果能够处理该请求,则处理请求;否则将请求转发给下一个处理者; 客户端(Client):创建请求对象,并将请求对象传递给第一个处理者; 请求对象(Request):封装了需要处理的数据和请求类型等信息。 责任链模式的优点在于它能够降低系统的耦合度,增强系统的可扩展性和灵活性。同时,由于责任链模式中的处理者之间是松散耦合的,因此可以方便地增加或删除处理者,而不会影响到其他部分的功能。
实现 上述需求,其实就是典型的责任链模式的应用场景。而责任链模式一般有两种实现:指针和集合的方式。
指针模式是最常见的责任链模式实现方式之一。在这种模式下,每个处理对象都持有一个指向下一个处理对象的引用,形成一个链表。当请求到达一个处理对象时,如果该对象无法处理该请求,则将请求传递给链表中的下一个对象,直到找到能够处理该请求的对象为止。
集合模式是另一种责任链模式实现方式。与指针模式不同的是,集合模式下,所有的处理对象被封装在一个集合中,每个对象都具有相同的处理机会。当请求到达集合时,集合中的每个对象都有机会去处理请求,直到有一个对象成功地处理了请求或者所有对象都无法处理该请求为止。
责任链 java public abstract class HandlerChain < T , R extends Rule > {
+
+ protected List<Handler< T , R >> handlers;
+
+ public void setHandlers (List<Handler< T , R >> handlers ) {
+ this .handlers = handlers;
+ }
+
+ public List<Handler< T , R >> getHandlers () {
+ return handlers;
+ }
+
+ public void handle (T t ) {
+ if (CollUtil. isNotEmpty (handlers)) {
+ for (Handler< T , R > handler : handlers) {
+ if ( ! handler. handle (t)) {
+ break ;
+ }
+ }
+ }
+ }
+
+ public void clear () {
+ if (CollUtil. isNotEmpty (handlers)) {
+ handlers. clear ();
+ }
+ }
+}
public abstract class HandlerChain < T , R extends Rule > {
+
+ protected List<Handler< T , R >> handlers;
+
+ public void setHandlers (List<Handler< T , R >> handlers ) {
+ this .handlers = handlers;
+ }
+
+ public List<Handler< T , R >> getHandlers () {
+ return handlers;
+ }
+
+ public void handle (T t ) {
+ if (CollUtil. isNotEmpty (handlers)) {
+ for (Handler< T , R > handler : handlers) {
+ if ( ! handler. handle (t)) {
+ break ;
+ }
+ }
+ }
+ }
+
+ public void clear () {
+ if (CollUtil. isNotEmpty (handlers)) {
+ handlers. clear ();
+ }
+ }
+}
处理器 java public abstract class Handler < T , R extends Rule > {
+
+ protected CommonDynamicParam param;
+ protected R rule;
+
+ public abstract boolean handle (T t );
+
+ public CommonDynamicParam getParam () {
+ return param;
+ }
+
+ public void setParam (CommonDynamicParam param ) {
+ this .param = param;
+ }
+
+ public R getRule () {
+ return rule;
+ }
+
+ public void setRule (R rule ) {
+ this .rule = rule;
+ this .param = JSON. parseObject (rule. getSettlementCondition (), CommonDynamicParam.class);
+ }
+}
public abstract class Handler < T , R extends Rule > {
+
+ protected CommonDynamicParam param;
+ protected R rule;
+
+ public abstract boolean handle (T t );
+
+ public CommonDynamicParam getParam () {
+ return param;
+ }
+
+ public void setParam (CommonDynamicParam param ) {
+ this .param = param;
+ }
+
+ public R getRule () {
+ return rule;
+ }
+
+ public void setRule (R rule ) {
+ this .rule = rule;
+ this .param = JSON. parseObject (rule. getSettlementCondition (), CommonDynamicParam.class);
+ }
+}
大致流程 实现具体的Handler处理器逻辑(handle)
初始化处理器集合List<Handler>
,并交给责任链对象HandlerChain管理
调用责任链的handle方法处理对象
`,19),e=[o];function t(c,r,E,y,i,d){return a(),n("div",null,e)}const u=s(p,[["render",t]]);export{h as __pageData,u as default};
diff --git a/assets/actions_designpattern_chain-of-responsibility-pattern.md.e0e027d4.lean.js b/assets/actions_designpattern_chain-of-responsibility-pattern.md.e0e027d4.lean.js
new file mode 100644
index 000000000..eb52f4d87
--- /dev/null
+++ b/assets/actions_designpattern_chain-of-responsibility-pattern.md.e0e027d4.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"责任链模式","description":"","frontmatter":{},"headers":[],"relativePath":"actions/designpattern/chain-of-responsibility-pattern.md","filePath":"actions/designpattern/chain-of-responsibility-pattern.md","lastUpdated":1694368780000}'),p={name:"actions/designpattern/chain-of-responsibility-pattern.md"},o=l("",19),e=[o];function t(c,r,E,y,i,d){return a(),n("div",null,e)}const u=s(p,[["render",t]]);export{h as __pageData,u as default};
diff --git a/assets/actions_designpattern_strategy-pattern.md.23b27876.js b/assets/actions_designpattern_strategy-pattern.md.23b27876.js
new file mode 100644
index 000000000..408319abe
--- /dev/null
+++ b/assets/actions_designpattern_strategy-pattern.md.23b27876.js
@@ -0,0 +1,649 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const g=JSON.parse('{"title":"策略模式的具体实现","description":"","frontmatter":{},"headers":[],"relativePath":"actions/designpattern/strategy-pattern.md","filePath":"actions/designpattern/strategy-pattern.md","lastUpdated":1694368780000}'),p={name:"actions/designpattern/strategy-pattern.md"},o=l(`策略模式的具体实现 背景 开发中经常有这样一种场景,一个接口需要处理的请求中的内容包含多种不同的类型。比如支付系统,订单支付的时候可能是支付宝支付,微信支付或者银联支付等。又或者是订单系统,订单可能是普通订单,可能是团购订单,也可能是秒杀订单。前阵子做的一个预览Office文件的功能也与之类似,文件的类型不同,也需要采取不同的处理方案。这时候最简单的做法就是在controller中写n多个if else:
java if ( "excel" . equals (file.getType)) {
+ //***
+} else if ( "word" . equals (file. getType ()){
+ //***
+} ……
if ( "excel" . equals (file.getType)) {
+ //***
+} else if ( "word" . equals (file. getType ()){
+ //***
+} ……
如果后面再加其他的类型,那就继续加if else语句,这样代码就会变的很丑陋,而且每次都需要对controller代码进行修改,后续的扩展很麻烦。所以这种情况通常会采用策略模式来进行处理,这样我们的代码会变得更加优雅,方便后续的维护。
策略模式 策略模式是一种行为模式,主要作用是在程序运行时动态切换一个类的行为或者算法。我们需要做的就是创建一个定义行为的Strategy接口以及它的具体策略实现类,以及一个策略的上下文来动态切换策略。
下面将介绍具体的实现方案,以订单系统为例
环境搭建
xml < parent >
+ < groupId >org.springframework.boot</ groupId >
+ < artifactId >spring-boot-starter-parent</ artifactId >
+ < version >2.1.4.RELEASE</ version >
+</ parent >
+< dependencies >
+ < dependency >
+ < groupId >org.projectlombok</ groupId >
+ < artifactId >lombok</ artifactId >
+ < version >1.18.6</ version >
+ </ dependency >
+ < dependency >
+ < groupId >org.springframework.boot</ groupId >
+ < artifactId >spring-boot-starter-web</ artifactId >
+ < version >2.1.4.RELEASE</ version >
+ </ dependency >
+ < dependency >
+ < groupId >org.apache.commons</ groupId >
+ < artifactId >commons-lang3</ artifactId >
+ < version >3.8.1</ version >
+ </ dependency >
+</ dependencies >
< parent >
+ < groupId >org.springframework.boot</ groupId >
+ < artifactId >spring-boot-starter-parent</ artifactId >
+ < version >2.1.4.RELEASE</ version >
+</ parent >
+< dependencies >
+ < dependency >
+ < groupId >org.projectlombok</ groupId >
+ < artifactId >lombok</ artifactId >
+ < version >1.18.6</ version >
+ </ dependency >
+ < dependency >
+ < groupId >org.springframework.boot</ groupId >
+ < artifactId >spring-boot-starter-web</ artifactId >
+ < version >2.1.4.RELEASE</ version >
+ </ dependency >
+ < dependency >
+ < groupId >org.apache.commons</ groupId >
+ < artifactId >commons-lang3</ artifactId >
+ < version >3.8.1</ version >
+ </ dependency >
+</ dependencies >
订单实体类
java @ Data
+public class Order {
+ private String code;
+ private BigDecimal price;
+ /**
+ * 1: 普通订单
+ * 2: 秒杀订单
+ * 3: 团购订单
+ */
+ private String type;
+}
@ Data
+public class Order {
+ private String code;
+ private BigDecimal price;
+ /**
+ * 1: 普通订单
+ * 2: 秒杀订单
+ * 3: 团购订单
+ */
+ private String type;
+}
抽象策略接口
java public interface OrderStrategy {
+ String handleOrder (Order order );
+}
public interface OrderStrategy {
+ String handleOrder (Order order );
+}
策略具体实现
java @ Component
+public class NormalHandler implements OrderStrategy {
+
+ @ Override
+ public String handleOrder (Order order ) {
+ return "普通订单处理完毕" ;
+ }
+}
+
+@ Component
+public class GroupHandler implements OrderStrategy {
+ @ Override
+ public String handleOrder (Order order ) {
+ return "团购订单处理完毕" ;
+ }
+}
+
+@ Component
+public class SecKillHandler implements OrderStrategy {
+ @ Override
+ public String handleOrder (Order order ) {
+ return "秒杀订单处理完毕" ;
+ }
+}
@ Component
+public class NormalHandler implements OrderStrategy {
+
+ @ Override
+ public String handleOrder (Order order ) {
+ return "普通订单处理完毕" ;
+ }
+}
+
+@ Component
+public class GroupHandler implements OrderStrategy {
+ @ Override
+ public String handleOrder (Order order ) {
+ return "团购订单处理完毕" ;
+ }
+}
+
+@ Component
+public class SecKillHandler implements OrderStrategy {
+ @ Override
+ public String handleOrder (Order order ) {
+ return "秒杀订单处理完毕" ;
+ }
+}
SpringUtils
@Component
+public class SpringUtils implements ApplicationContextAware {
+
+ private static ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ SpringUtils.applicationContext = applicationContext;
+ }
+
+ //获取applicationContext
+ private static ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+ //通过name获取 Bean.
+ public static Object getBean(String name){
+ return getApplicationContext().getBean(name);
+ }
+
+ //通过class获取Bean.
+ public static <T> T getBean(Class<T> clazz){
+ return getApplicationContext().getBean(clazz);
+ }
+
+ //通过name,以及Clazz返回指定的Bean
+ public static <T> T getBean(String name,Class<T> clazz){
+ return getApplicationContext().getBean(name, clazz);
+ }
+
+}
@Component
+public class SpringUtils implements ApplicationContextAware {
+
+ private static ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ SpringUtils.applicationContext = applicationContext;
+ }
+
+ //获取applicationContext
+ private static ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+ //通过name获取 Bean.
+ public static Object getBean(String name){
+ return getApplicationContext().getBean(name);
+ }
+
+ //通过class获取Bean.
+ public static <T> T getBean(Class<T> clazz){
+ return getApplicationContext().getBean(clazz);
+ }
+
+ //通过name,以及Clazz返回指定的Bean
+ public static <T> T getBean(String name,Class<T> clazz){
+ return getApplicationContext().getBean(name, clazz);
+ }
+
+}
第一种实现 可以采取配置的方式,将不同类型和对应的handler的bean name配置在配置文件或者是数据库中,这样我们在context中可以直接获取配置文件中的bean name或者去数据库中查询,然后从spring容器中获取对应的bean并调用处理方法即可。
以将映射关系持久化到数据库为例,我们需要建一张表来维护类型和具体处理器之间的关系 字段为type和对应处理器的bean名称
controller
java @ RestController
+@ RequestMapping ( "/api/order" )
+public class OrderController {
+
+ @ Autowired
+ private IOrderService orderService;
+
+
+ @ GetMapping ( "/{type}" )
+ public String handleOrder (@ PathVariable String type ){
+ return orderService. handleOrder (type);
+ }
+}
@ RestController
+@ RequestMapping ( "/api/order" )
+public class OrderController {
+
+ @ Autowired
+ private IOrderService orderService;
+
+
+ @ GetMapping ( "/{type}" )
+ public String handleOrder (@ PathVariable String type ){
+ return orderService. handleOrder (type);
+ }
+}
service
java @ Service
+public class OrderServiceImpl implements IOrderService {
+
+ @ Autowired
+ private OrderStrategyContext context;
+
+ @ Override
+ public String handleOrder (String type ) {
+ return context. getBean (type). handleOrder (type);
+ }
+}
@ Service
+public class OrderServiceImpl implements IOrderService {
+
+ @ Autowired
+ private OrderStrategyContext context;
+
+ @ Override
+ public String handleOrder (String type ) {
+ return context. getBean (type). handleOrder (type);
+ }
+}
context
java @ Component
+public class OrderStrategyContext {
+ @ Autowired
+ private StrategyMapper mapper;
+
+ public OrderStrategy getBean (String type ){
+ String beanName = mapper. getBeanName (type);
+ return SpringUtils. getBean (beanName);
+ }
+
+}
@ Component
+public class OrderStrategyContext {
+ @ Autowired
+ private StrategyMapper mapper;
+
+ public OrderStrategy getBean (String type ){
+ String beanName = mapper. getBeanName (type);
+ return SpringUtils. getBean (beanName);
+ }
+
+}
第二种实现 第一种方案相较于无尽的if else已经好很多了,但是还是需要增加配置文件或者数据库中新建表来维护类型和对应处理器的映射关系。还可以直接自定义注解来实现这个关系的对应。
自定义注解HandlerType
java @ Target ({ElementType.TYPE})
+@ Retention (RetentionPolicy.RUNTIME)
+@ Documented
+@ Inherited
+public @ interface HandlerType {
+ String value ();
+}
@ Target ({ElementType.TYPE})
+@ Retention (RetentionPolicy.RUNTIME)
+@ Documented
+@ Inherited
+public @ interface HandlerType {
+ String value ();
+}
然后在每个具体策略类上加上注解
java @ Component
+@ HandlerType ( "1" )
+public class NormalHandler implements OrderStrategy {
+
+ @ Override
+ public String handleOrder (String type ) {
+ return "普通订单处理完毕" ;
+ }
+}
@ Component
+@ HandlerType ( "1" )
+public class NormalHandler implements OrderStrategy {
+
+ @ Override
+ public String handleOrder (String type ) {
+ return "普通订单处理完毕" ;
+ }
+}
java @ Component
+@ HandlerType ( "2" )
+public class GroupHandler implements OrderStrategy {
+ @ Override
+ public String handleOrder (String type ) {
+ return "团购订单处理完毕" ;
+ }
+}
@ Component
+@ HandlerType ( "2" )
+public class GroupHandler implements OrderStrategy {
+ @ Override
+ public String handleOrder (String type ) {
+ return "团购订单处理完毕" ;
+ }
+}
java @ Component
+@ HandlerType ( "3" )
+public class SecKillHandler implements OrderStrategy {
+
+ @ Override
+ public String handleOrder (String type ) {
+ return "秒杀订单处理完毕" ;
+ }
+}
@ Component
+@ HandlerType ( "3" )
+public class SecKillHandler implements OrderStrategy {
+
+ @ Override
+ public String handleOrder (String type ) {
+ return "秒杀订单处理完毕" ;
+ }
+}
策略上下文context修改为
java public class OrderStrategyContext {
+
+ private Map< String , Class > handlerMap;
+
+ public OrderStrategyContext (Map< String , Class > handlerMap ){
+ this .handlerMap = handlerMap;
+ }
+
+ public OrderStrategy getBean (String type ){
+ Class clazz = handlerMap. get (type);
+ if (clazz == null ) {
+ throw new IllegalArgumentException ( "not found handler for type :" + type);
+ }
+ return (OrderStrategy) SpringUtils. getBean (clazz);
+ }
+}
public class OrderStrategyContext {
+
+ private Map< String , Class > handlerMap;
+
+ public OrderStrategyContext (Map< String , Class > handlerMap ){
+ this .handlerMap = handlerMap;
+ }
+
+ public OrderStrategy getBean (String type ){
+ Class clazz = handlerMap. get (type);
+ if (clazz == null ) {
+ throw new IllegalArgumentException ( "not found handler for type :" + type);
+ }
+ return (OrderStrategy) SpringUtils. getBean (clazz);
+ }
+}
自定义注解后,我们需要将注解的value和对应策略类的bean_name放到上下文的handlerMap中,并将策略上下文对象注册到spring容器里,需要一个处理类HandlerProcessor
java @ Component
+public class HandlerProcessor implements BeanFactoryPostProcessor {
+
+ private static final String HANDLE_PACKAGE = "com.test.handler" ;
+
+ @ Override
+ public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory ) throws BeansException {
+ Map< String , Class > map = new HashMap<>();
+ ClassScaner. scan (HANDLE_PACKAGE, HandlerType.class). forEach (clazz -> {
+ //获取注解中对应的类型
+ String type = clazz. getAnnotation (HandlerType.class). value ();
+ //注解的类型值作为key,对应的类作为value,存储在map中
+ map. put (type,clazz);
+ });
+ //初始化HandlerContext,注册到Spring容器中
+ OrderStrategyContext context = new OrderStrategyContext (map);
+ beanFactory. registerSingleton (OrderStrategyContext.class. getName (),context);
+ }
+}
@ Component
+public class HandlerProcessor implements BeanFactoryPostProcessor {
+
+ private static final String HANDLE_PACKAGE = "com.test.handler" ;
+
+ @ Override
+ public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory ) throws BeansException {
+ Map< String , Class > map = new HashMap<>();
+ ClassScaner. scan (HANDLE_PACKAGE, HandlerType.class). forEach (clazz -> {
+ //获取注解中对应的类型
+ String type = clazz. getAnnotation (HandlerType.class). value ();
+ //注解的类型值作为key,对应的类作为value,存储在map中
+ map. put (type,clazz);
+ });
+ //初始化HandlerContext,注册到Spring容器中
+ OrderStrategyContext context = new OrderStrategyContext (map);
+ beanFactory. registerSingleton (OrderStrategyContext.class. getName (),context);
+ }
+}
ClassScaner
java public class ClassScaner implements ResourceLoaderAware {
+
+ private final List< TypeFilter > includeFilters = new LinkedList< TypeFilter >();
+ private final List< TypeFilter > excludeFilters = new LinkedList< TypeFilter >();
+
+ private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver ();
+ private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory ( this .resourcePatternResolver);
+
+ @ SafeVarargs
+ public static Set<Class< ? >> scan ( String [] basePackages , Class< ? extends Annotation >... annotations ) {
+ ClassScaner cs = new ClassScaner ();
+
+ if (ArrayUtils. isNotEmpty (annotations)) {
+ for (Class anno : annotations) {
+ cs. addIncludeFilter ( new AnnotationTypeFilter (anno));
+ }
+ }
+
+ Set<Class< ? >> classes = new HashSet<>();
+ for (String s : basePackages) {
+ classes. addAll (cs. doScan (s));
+ }
+
+ return classes;
+ }
+
+ @ SafeVarargs
+ public static Set<Class< ? >> scan (String basePackages , Class< ? extends Annotation >... annotations ) {
+ return ClassScaner. scan (StringUtils. tokenizeToStringArray (basePackages, ",; \\t\\n " ), annotations);
+ }
+
+ public final ResourceLoader getResourceLoader () {
+ return this .resourcePatternResolver;
+ }
+
+ @ Override
+ public void setResourceLoader (ResourceLoader resourceLoader ) {
+ this .resourcePatternResolver = ResourcePatternUtils
+ . getResourcePatternResolver (resourceLoader);
+ this .metadataReaderFactory = new CachingMetadataReaderFactory (
+ resourceLoader);
+ }
+
+ public void addIncludeFilter (TypeFilter includeFilter ) {
+ this .includeFilters. add (includeFilter);
+ }
+
+ public void addExcludeFilter (TypeFilter excludeFilter ) {
+ this .excludeFilters. add ( 0 , excludeFilter);
+ }
+
+ public void resetFilters ( boolean useDefaultFilters ) {
+ this .includeFilters. clear ();
+ this .excludeFilters. clear ();
+ }
+
+ public Set<Class< ? >> doScan (String basePackage ) {
+ Set<Class< ? >> classes = new HashSet<>();
+ try {
+ String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ + org.springframework.util.ClassUtils
+ . convertClassNameToResourcePath (SystemPropertyUtils
+ . resolvePlaceholders (basePackage))
+ + "/**/*.class" ;
+ Resource [] resources = this .resourcePatternResolver
+ . getResources (packageSearchPath);
+
+ for ( int i = 0 ; i < resources.length; i ++ ) {
+ Resource resource = resources[i];
+ if (resource. isReadable ()) {
+ MetadataReader metadataReader = this .metadataReaderFactory. getMetadataReader (resource);
+ if ((includeFilters. size () == 0 && excludeFilters. size () == 0 ) || matches (metadataReader)) {
+ try {
+ classes. add (Class. forName (metadataReader
+ . getClassMetadata (). getClassName ()));
+ } catch (ClassNotFoundException e ) {
+ e. printStackTrace ();
+ }
+ }
+ }
+ }
+ } catch (IOException ex ) {
+ throw new BeanDefinitionStoreException (
+ "I/O failure during classpath scanning" , ex);
+ }
+ return classes;
+ }
+
+ protected boolean matches (MetadataReader metadataReader ) throws IOException {
+ for (TypeFilter tf : this .excludeFilters) {
+ if (tf. match (metadataReader, this .metadataReaderFactory)) {
+ return false ;
+ }
+ }
+ for (TypeFilter tf : this .includeFilters) {
+ if (tf. match (metadataReader, this .metadataReaderFactory)) {
+ return true ;
+ }
+ }
+ return false ;
+ }
+}
public class ClassScaner implements ResourceLoaderAware {
+
+ private final List< TypeFilter > includeFilters = new LinkedList< TypeFilter >();
+ private final List< TypeFilter > excludeFilters = new LinkedList< TypeFilter >();
+
+ private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver ();
+ private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory ( this .resourcePatternResolver);
+
+ @ SafeVarargs
+ public static Set<Class< ? >> scan ( String [] basePackages , Class< ? extends Annotation >... annotations ) {
+ ClassScaner cs = new ClassScaner ();
+
+ if (ArrayUtils. isNotEmpty (annotations)) {
+ for (Class anno : annotations) {
+ cs. addIncludeFilter ( new AnnotationTypeFilter (anno));
+ }
+ }
+
+ Set<Class< ? >> classes = new HashSet<>();
+ for (String s : basePackages) {
+ classes. addAll (cs. doScan (s));
+ }
+
+ return classes;
+ }
+
+ @ SafeVarargs
+ public static Set<Class< ? >> scan (String basePackages , Class< ? extends Annotation >... annotations ) {
+ return ClassScaner. scan (StringUtils. tokenizeToStringArray (basePackages, ",; \\t\\n " ), annotations);
+ }
+
+ public final ResourceLoader getResourceLoader () {
+ return this .resourcePatternResolver;
+ }
+
+ @ Override
+ public void setResourceLoader (ResourceLoader resourceLoader ) {
+ this .resourcePatternResolver = ResourcePatternUtils
+ . getResourcePatternResolver (resourceLoader);
+ this .metadataReaderFactory = new CachingMetadataReaderFactory (
+ resourceLoader);
+ }
+
+ public void addIncludeFilter (TypeFilter includeFilter ) {
+ this .includeFilters. add (includeFilter);
+ }
+
+ public void addExcludeFilter (TypeFilter excludeFilter ) {
+ this .excludeFilters. add ( 0 , excludeFilter);
+ }
+
+ public void resetFilters ( boolean useDefaultFilters ) {
+ this .includeFilters. clear ();
+ this .excludeFilters. clear ();
+ }
+
+ public Set<Class< ? >> doScan (String basePackage ) {
+ Set<Class< ? >> classes = new HashSet<>();
+ try {
+ String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ + org.springframework.util.ClassUtils
+ . convertClassNameToResourcePath (SystemPropertyUtils
+ . resolvePlaceholders (basePackage))
+ + "/**/*.class" ;
+ Resource [] resources = this .resourcePatternResolver
+ . getResources (packageSearchPath);
+
+ for ( int i = 0 ; i < resources.length; i ++ ) {
+ Resource resource = resources[i];
+ if (resource. isReadable ()) {
+ MetadataReader metadataReader = this .metadataReaderFactory. getMetadataReader (resource);
+ if ((includeFilters. size () == 0 && excludeFilters. size () == 0 ) || matches (metadataReader)) {
+ try {
+ classes. add (Class. forName (metadataReader
+ . getClassMetadata (). getClassName ()));
+ } catch (ClassNotFoundException e ) {
+ e. printStackTrace ();
+ }
+ }
+ }
+ }
+ } catch (IOException ex ) {
+ throw new BeanDefinitionStoreException (
+ "I/O failure during classpath scanning" , ex);
+ }
+ return classes;
+ }
+
+ protected boolean matches (MetadataReader metadataReader ) throws IOException {
+ for (TypeFilter tf : this .excludeFilters) {
+ if (tf. match (metadataReader, this .metadataReaderFactory)) {
+ return false ;
+ }
+ }
+ for (TypeFilter tf : this .includeFilters) {
+ if (tf. match (metadataReader, this .metadataReaderFactory)) {
+ return true ;
+ }
+ }
+ return false ;
+ }
+}
第三种方案 扫描指定的包还是很麻烦,还可以直接使用ioc容器来直接进行操作 将OrderStrategyContext进行修改,不再需要processor类
java @ Component
+public class OrderStrategyContext implements ApplicationContextAware , CommandLineRunner {
+
+ private Map< String , Object > handlerMap = new HashMap<>();
+
+ public OrderStrategy getInstance (String type ) {
+ Object obj = handlerMap. get (type);
+ if (obj == null ) {
+ throw new IllegalArgumentException ( "handler not found for type : " + type);
+ }
+ if (obj instanceof OrderStrategy) {
+ return (OrderStrategy) obj;
+ } else {
+ throw new IllegalArgumentException ( "handler not found for type : " + type);
+ }
+ }
+
+ private ApplicationContext context;
+
+ @ Override
+ public void setApplicationContext (ApplicationContext applicationContext ) throws BeansException {
+ this .context = applicationContext;
+ }
+
+
+ @ Override
+ public void run (String... args ) throws Exception {
+ this . loadBean ();
+ }
+
+ public void loadBean () {
+ Map< String , Object > beansWithAnnotation = context. getBeansWithAnnotation (HandlerType.class);
+ beansWithAnnotation. forEach ((handlerBeanName,handlerBean) -> {
+ Class< ? > clazz = handlerBean. getClass ();
+ HandlerType annotation = clazz. getAnnotation (HandlerType.class);
+ String annotationValue = annotation. value ();
+ handlerMap. put (annotationValue,handlerBean);
+ });
+ }
+}
@ Component
+public class OrderStrategyContext implements ApplicationContextAware , CommandLineRunner {
+
+ private Map< String , Object > handlerMap = new HashMap<>();
+
+ public OrderStrategy getInstance (String type ) {
+ Object obj = handlerMap. get (type);
+ if (obj == null ) {
+ throw new IllegalArgumentException ( "handler not found for type : " + type);
+ }
+ if (obj instanceof OrderStrategy) {
+ return (OrderStrategy) obj;
+ } else {
+ throw new IllegalArgumentException ( "handler not found for type : " + type);
+ }
+ }
+
+ private ApplicationContext context;
+
+ @ Override
+ public void setApplicationContext (ApplicationContext applicationContext ) throws BeansException {
+ this .context = applicationContext;
+ }
+
+
+ @ Override
+ public void run (String... args ) throws Exception {
+ this . loadBean ();
+ }
+
+ public void loadBean () {
+ Map< String , Object > beansWithAnnotation = context. getBeansWithAnnotation (HandlerType.class);
+ beansWithAnnotation. forEach ((handlerBeanName,handlerBean) -> {
+ Class< ? > clazz = handlerBean. getClass ();
+ HandlerType annotation = clazz. getAnnotation (HandlerType.class);
+ String annotationValue = annotation. value ();
+ handlerMap. put (annotationValue,handlerBean);
+ });
+ }
+}
启动项目并测试
`,46),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const u=s(p,[["render",t]]);export{g as __pageData,u as default};
diff --git a/assets/actions_designpattern_strategy-pattern.md.23b27876.lean.js b/assets/actions_designpattern_strategy-pattern.md.23b27876.lean.js
new file mode 100644
index 000000000..977fd7624
--- /dev/null
+++ b/assets/actions_designpattern_strategy-pattern.md.23b27876.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const g=JSON.parse('{"title":"策略模式的具体实现","description":"","frontmatter":{},"headers":[],"relativePath":"actions/designpattern/strategy-pattern.md","filePath":"actions/designpattern/strategy-pattern.md","lastUpdated":1694368780000}'),p={name:"actions/designpattern/strategy-pattern.md"},o=l("",46),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const u=s(p,[["render",t]]);export{g as __pageData,u as default};
diff --git a/assets/actions_env_cicd-vue.md.a50e1517.js b/assets/actions_env_cicd-vue.md.a50e1517.js
new file mode 100644
index 000000000..4440b457b
--- /dev/null
+++ b/assets/actions_env_cicd-vue.md.a50e1517.js
@@ -0,0 +1,113 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const g=JSON.parse('{"title":"docker+jenkins+gitee自动化部署vue项目","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/cicd-vue.md","filePath":"actions/env/cicd-vue.md","lastUpdated":1694368780000}'),p={name:"actions/env/cicd-vue.md"},o=l(`docker+jenkins+gitee自动化部署vue项目 之前个人博客 一直用的travisCI部署在github page上,但是偶尔会抽风无法访问。之前一直偷懒没部署jenkins,手动部署到云服务器又比较麻烦,打包上传很浪费时间,这次就直接动手一步到位,在自己服务器上部署下jekins。
docker启动jenkins 最开始用的jenkins中文社区的镜像发现有个很恶心的问题,jenkins版本比较低而且安装了NodeJS插件后在全局工具配置中配置NodeJS安装环境时无法选择版本,所以还是官方镜像比较靠谱。
bash # 拉取官方镜像
+docker pull jenkins/jenkins:lts
+lts: Pulling from jenkins/jenkins
+4c25b3090c26: Pull complete
+750d566fdd60: Pull complete
+2718cc36ca02: Pull complete
+5678b027ee14: Pull complete
+c839cd2df78d: Pull complete
+50861a5addda: Pull complete
+ff2b028e5cf5: Pull complete
+ee710b58f452: Pull complete
+2625c929bb0e: Pull complete
+6a6bf9181c04: Pull complete
+bee5e6792ac4: Pull complete
+6cc5edd2133e: Pull complete
+c07b16426ded: Pull complete
+e9ac42647ae3: Pull complete
+fa925738a490: Pull complete
+4a08c3886279: Pull complete
+2d43fec22b7e: Pull complete
+Digest: sha256:a942c30fc3bcf269a1c32ba27eb4a470148eff9aba086911320031a3c3943e6c
+Status: Downloaded newer image for jenkins/jenkins:lts
+docker.io/jenkins/jenkins:lts
+# 启动jenkins
+docker run --name jenkins -dp 8099 :8080 -v /story/dist:/story/dist -v ~/jenkins_data:/var/jenkins_home -u root -e TZ="Asia/Shanghai" -v /etc/localtime:/etc/localtime:ro jenkins/jenkins:lts
+# 参数说明 --name 指定容器名为jenkins -d 后台启动 -p 将容器的8080端口映射到宿主机的8099端口
+# -v 挂载宿主机目录 宿主机和容器的目录会同步 -u 指定用户为root 这里是必须的 不然后续操作文件系统会报无权限
+# 挂载时区的目录是因为镜像中的linux系统默认时区非北京时间,会导致时间显示不正确
# 拉取官方镜像
+docker pull jenkins/jenkins:lts
+lts: Pulling from jenkins/jenkins
+4c25b3090c26: Pull complete
+750d566fdd60: Pull complete
+2718cc36ca02: Pull complete
+5678b027ee14: Pull complete
+c839cd2df78d: Pull complete
+50861a5addda: Pull complete
+ff2b028e5cf5: Pull complete
+ee710b58f452: Pull complete
+2625c929bb0e: Pull complete
+6a6bf9181c04: Pull complete
+bee5e6792ac4: Pull complete
+6cc5edd2133e: Pull complete
+c07b16426ded: Pull complete
+e9ac42647ae3: Pull complete
+fa925738a490: Pull complete
+4a08c3886279: Pull complete
+2d43fec22b7e: Pull complete
+Digest: sha256:a942c30fc3bcf269a1c32ba27eb4a470148eff9aba086911320031a3c3943e6c
+Status: Downloaded newer image for jenkins/jenkins:lts
+docker.io/jenkins/jenkins:lts
+# 启动jenkins
+docker run --name jenkins -dp 8099 :8080 -v /story/dist:/story/dist -v ~/jenkins_data:/var/jenkins_home -u root -e TZ="Asia/Shanghai" -v /etc/localtime:/etc/localtime:ro jenkins/jenkins:lts
+# 参数说明 --name 指定容器名为jenkins -d 后台启动 -p 将容器的8080端口映射到宿主机的8099端口
+# -v 挂载宿主机目录 宿主机和容器的目录会同步 -u 指定用户为root 这里是必须的 不然后续操作文件系统会报无权限
+# 挂载时区的目录是因为镜像中的linux系统默认时区非北京时间,会导致时间显示不正确
启动后直接访问本机8099端口:http://localhost:8099/
(我这里是本地测试,实际请替换成自己的服务器地址) apt安装 shell curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \\
+ /usr/share/keyrings/jenkins-keyring.asc > /dev/null
+
+sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/" > /etc/apt/sources.list.d/jenkins.list'
+
+sudo apt update
+
+# Jenkins requires Java 11 or 17 since Jenkins 2.357 and LTS 2.361.1.
+apt install openjdk-17-jdk
+
+sudo apt install jenkins
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \\
+ /usr/share/keyrings/jenkins-keyring.asc > /dev/null
+
+sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/" > /etc/apt/sources.list.d/jenkins.list'
+
+sudo apt update
+
+# Jenkins requires Java 11 or 17 since Jenkins 2.357 and LTS 2.361.1.
+apt install openjdk-17-jdk
+
+sudo apt install jenkins
bash docker exec -it jenkins /bin/bash
+cat /var/jenkins_home/secrets/initialAdminPassword
docker exec -it jenkins /bin/bash
+cat /var/jenkins_home/secrets/initialAdminPassword
或者直接在上面挂载的目录查询但要改一下路径
bash cat ~/jenkins_data/secrets/initialAdminPassword
cat ~/jenkins_data/secrets/initialAdminPassword
实例配置
完成
jenkins配置自动部署 安装node和gitee插件 Manage Jenkins ---> Manage Plugins ---> 可选插件分别搜索gitee和nodejs
选择install without restart
安装完毕后返回工作台
配置node环境 Manage Jenkins ---> Global Tool Configuration ---> NodeJS
新增NodeJS取别名后保存即可
新建自动部署任务 新建任务 工作台点击新建Item,输入任务名称后选择freestyle project确定
配置gitee相关内容 源码管理
构建触发器
去gitee仓库中配置webhook内容
仓库的管理tab页添加webhook
url和webhook密码分别填写后保存
在这个页面点击测试,如果看到xxx has been accepted即为成功。
构建环境 选择前面已经配置好的node环境即可
构建 首先在任务面板中点击立即构建,这样才会生成工作空间
我这里选择执行shell
然后就是写个简单的脚本执行打包,替换的工作
第一步cd进入的目录是当前任务的工作空间,这里要把vuepress替换成自己的任务名称即可
TIP
这里涉及到文件系统操作的内容rm cp等命令需要root用户才能执行,所以在启动docker容器的时候必须使用-u root参数指定root用户,否则打包会失败,操作文件时会提示无权限
配置完毕保存即可
测试 点击立即构建或者往gitee仓库推送一次更新即可触发构建任务,然后等待构建完成即可。
如果是第一次执行构建,jenkins还会自动安装解压nodejs。
通过脚本替换完打包好的dist后,通过nginx配置部署静态项目即可。
jenkins打包跨平台docker镜像并推送镜像仓库 这里是用宿主机直接安装的jenkins
shell #!/bin/sh
+cd /var/lib/jenkins/workspace/xxx
+node -v
+npm -v
+docker -v
+
+npm install
+npm run build
+docker buildx ls
+docker buildx create --use --name jenkinsbuilder
+docker buildx ls
+
+# Dockerfile
+cat > Dockerfile << EOF
+// doSomething
+EOF
+
+docker login xxx.com --username= 'xxx' --password== 'xxx'
+docker buildx build --platform linux/amd64,linux/arm64 -t xxx/xxx . --push
#!/bin/sh
+cd /var/lib/jenkins/workspace/xxx
+node -v
+npm -v
+docker -v
+
+npm install
+npm run build
+docker buildx ls
+docker buildx create --use --name jenkinsbuilder
+docker buildx ls
+
+# Dockerfile
+cat > Dockerfile << EOF
+// doSomething
+EOF
+
+docker login xxx.com --username= 'xxx' --password== 'xxx'
+docker buildx build --platform linux/amd64,linux/arm64 -t xxx/xxx . --push
`,69),e=[o];function c(t,r,y,i,E,F){return n(),a("div",null,e)}const u=s(p,[["render",c]]);export{g as __pageData,u as default};
diff --git a/assets/actions_env_cicd-vue.md.a50e1517.lean.js b/assets/actions_env_cicd-vue.md.a50e1517.lean.js
new file mode 100644
index 000000000..bae555b8a
--- /dev/null
+++ b/assets/actions_env_cicd-vue.md.a50e1517.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const g=JSON.parse('{"title":"docker+jenkins+gitee自动化部署vue项目","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/cicd-vue.md","filePath":"actions/env/cicd-vue.md","lastUpdated":1694368780000}'),p={name:"actions/env/cicd-vue.md"},o=l("",69),e=[o];function c(t,r,y,i,E,F){return n(),a("div",null,e)}const u=s(p,[["render",c]]);export{g as __pageData,u as default};
diff --git a/assets/actions_env_docker-desktop-windows.md.5f59bd6b.js b/assets/actions_env_docker-desktop-windows.md.5f59bd6b.js
new file mode 100644
index 000000000..284f00cd9
--- /dev/null
+++ b/assets/actions_env_docker-desktop-windows.md.5f59bd6b.js
@@ -0,0 +1 @@
+import{_ as e,o,c as t,Q as s}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"Windows下Docker Desktop安装","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/docker-desktop-windows.md","filePath":"actions/env/docker-desktop-windows.md","lastUpdated":1694368780000}'),i={name:"actions/env/docker-desktop-windows.md"},l=s('Windows下Docker Desktop安装 官网下载安装包Docker Desktop Installer.exe
运行安装并重启
提示WSL2的linux内核安装不完整,点击链接跟随教程操作
执行指引中的命令
步骤 1 - 启用适用于 Linux 的 Windows 子系统dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
步骤 2 - 检查运行 WSL 2 的要求,不同架构的系统版本要求不同 步骤 3 - 启用虚拟机功能,前提是开启虚拟化,如何开启虚拟化不再赘述dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
步骤 4 - 下载 Linux 内核更新包,地址:https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi 步骤 5 - 将 WSL 2 设置为默认版本wsl --set-default-version 2
步骤 6 - 安装所选的 Linux 分发,官方提供的方案是在微软商店中下载,因为网络原因很难实现,采取手动下载或命令行下载,参考地址https://docs.microsoft.com/zh-cn/windows/wsl/install-manual
,下载完成后cd到下载的目录,执行Add-AppxPackage .\\filename
即可 查看所有安装的分发版本wsl --list --all
卸载指定的分发版本wsl --unregister <DistributionName>
打开已安装的分发版本,在安装后会提示创建linux账户和密码 ',2),a=[l];function n(d,r,c,p,w,_){return o(),t("div",null,a)}const k=e(i,[["render",n]]);export{m as __pageData,k as default};
diff --git a/assets/actions_env_docker-desktop-windows.md.5f59bd6b.lean.js b/assets/actions_env_docker-desktop-windows.md.5f59bd6b.lean.js
new file mode 100644
index 000000000..8aa50eca2
--- /dev/null
+++ b/assets/actions_env_docker-desktop-windows.md.5f59bd6b.lean.js
@@ -0,0 +1 @@
+import{_ as e,o,c as t,Q as s}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"Windows下Docker Desktop安装","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/docker-desktop-windows.md","filePath":"actions/env/docker-desktop-windows.md","lastUpdated":1694368780000}'),i={name:"actions/env/docker-desktop-windows.md"},l=s("",2),a=[l];function n(d,r,c,p,w,_){return o(),t("div",null,a)}const k=e(i,[["render",n]]);export{m as __pageData,k as default};
diff --git a/assets/actions_env_git-proxy.md.0bf9d81f.js b/assets/actions_env_git-proxy.md.0bf9d81f.js
new file mode 100644
index 000000000..46f3a96e6
--- /dev/null
+++ b/assets/actions_env_git-proxy.md.0bf9d81f.js
@@ -0,0 +1,17 @@
+import{_ as s,o as n,c as a,Q as o}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"git配置socks5代理解决github上down代码慢的问题","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/git-proxy.md","filePath":"actions/env/git-proxy.md","lastUpdated":1694368780000}'),p={name:"actions/env/git-proxy.md"},l=o(`git配置socks5代理解决github上down代码慢的问题 bash # 设置代理
+git config --global http.proxy 'socks5://127.0.0.1:10880'
+
+git config --global https.proxy 'socks5://127.0.0.1:10880'
+
+# 取消代理
+git config --global --unset http.proxy
+
+git config --global --unset https.proxy
# 设置代理
+git config --global http.proxy 'socks5://127.0.0.1:10880'
+
+git config --global https.proxy 'socks5://127.0.0.1:10880'
+
+# 取消代理
+git config --global --unset http.proxy
+
+git config --global --unset https.proxy
端口号根据自己本地的代理端口填写
`,3),t=[l];function e(c,r,y,i,E,F){return n(),a("div",null,t)}const h=s(p,[["render",e]]);export{d as __pageData,h as default};
diff --git a/assets/actions_env_git-proxy.md.0bf9d81f.lean.js b/assets/actions_env_git-proxy.md.0bf9d81f.lean.js
new file mode 100644
index 000000000..054e733fc
--- /dev/null
+++ b/assets/actions_env_git-proxy.md.0bf9d81f.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as o}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"git配置socks5代理解决github上down代码慢的问题","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/git-proxy.md","filePath":"actions/env/git-proxy.md","lastUpdated":1694368780000}'),p={name:"actions/env/git-proxy.md"},l=o("",3),t=[l];function e(c,r,y,i,E,F){return n(),a("div",null,t)}const h=s(p,[["render",e]]);export{d as __pageData,h as default};
diff --git a/assets/actions_env_git-repo-multi-remote.md.c9a74a94.js b/assets/actions_env_git-repo-multi-remote.md.c9a74a94.js
new file mode 100644
index 000000000..8c176dfe8
--- /dev/null
+++ b/assets/actions_env_git-repo-multi-remote.md.c9a74a94.js
@@ -0,0 +1,31 @@
+import{_ as s,o as a,c as n,Q as e}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"git配置多ssh-key && Gitee 和 Github 同步更新","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/git-repo-multi-remote.md","filePath":"actions/env/git-repo-multi-remote.md","lastUpdated":1694368780000}'),p={name:"actions/env/git-repo-multi-remote.md"},l=e(`git配置多ssh-key && Gitee 和 Github 同步更新 配置多ssh-key gitee或者gitlab账号和个人git账号同时在一台机器上使用时,可以为不同git服务器设置不同的ssh-key
生成一个个人github的ssh-key
ssh-keygen -t rsa -C 'xxxxx@163.com' -f ~/.ssh/github_id_rsa
生成一个gitee的ssh-key
ssh-keygen -t rsa -C 'xxxxx@company.cn' -f ~/.ssh/gitee_id_rsa
在~/.ssh
下新建config文件vim ~/.ssh/config
,添加以下内容
txt # gitee
+Host gitee.com
+HostName gitee.com
+PreferredAuthentications publickey
+IdentityFile ~/.ssh/gitee_id_rsa
+# github
+Host github.com
+HostName github.com
+PreferredAuthentications publickey
+IdentityFile ~/.ssh/github_id_rsa
# gitee
+Host gitee.com
+HostName gitee.com
+PreferredAuthentications publickey
+IdentityFile ~/.ssh/gitee_id_rsa
+# github
+Host github.com
+HostName github.com
+PreferredAuthentications publickey
+IdentityFile ~/.ssh/github_id_rsa
分别在gitee和github中添加前两步生成的对应地址的公钥
ssh命令测试
bash ssh -T git@gitee.com
+ssh -T git@github.com
ssh -T git@gitee.com
+ssh -T git@github.com
如果看到 hi xxx!。。。内容则证明配置成功
Gitee 和 Github 同步更新 假设我们有一个项目同时在github和gitee上都有仓库,当直接使用git clone
命令拉取的代码默认remote为origin,如果要分别更新,我们要分别在两个本地仓库中push。这时我们可以给本地仓库添加多个origin,然后更新的时候分别推送即可实现一个本地仓库分别推送两个不同的远程仓库。
删除原有的remote地址
git remote remove origin
添加新的远程仓库地址(gitee)
bash git remote add 远程仓库名 远程仓库地址
+eg: git remote add gitee git@gitee.com:xxx/xxx.git
git remote add 远程仓库名 远程仓库地址
+eg: git remote add gitee git@gitee.com:xxx/xxx.git
添加新的远程仓库地址(github)
bash git remote add 远程仓库名 远程仓库地址
+eg: git remote add github git@github.com:xxx/xxx.git
git remote add 远程仓库名 远程仓库地址
+eg: git remote add github git@github.com:xxx/xxx.git
再次查看git remote
:
推送的时候git push 远程仓库名
即可
修改配置文件一次推送多个仓库 修改仓库下.git/config文件,新增内容
sh [remote "all" ]
+ url = repo1.git
+ url = repo2.git
+ url = repo3.git
[remote "all" ]
+ url = repo1.git
+ url = repo2.git
+ url = repo3.git
直接git push all
`,12),o=[l];function t(c,i,r,y,E,g){return a(),n("div",null,o)}const F=s(p,[["render",t]]);export{h as __pageData,F as default};
diff --git a/assets/actions_env_git-repo-multi-remote.md.c9a74a94.lean.js b/assets/actions_env_git-repo-multi-remote.md.c9a74a94.lean.js
new file mode 100644
index 000000000..00894ab8c
--- /dev/null
+++ b/assets/actions_env_git-repo-multi-remote.md.c9a74a94.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as e}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"git配置多ssh-key && Gitee 和 Github 同步更新","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/git-repo-multi-remote.md","filePath":"actions/env/git-repo-multi-remote.md","lastUpdated":1694368780000}'),p={name:"actions/env/git-repo-multi-remote.md"},l=e("",12),o=[l];function t(c,i,r,y,E,g){return a(),n("div",null,o)}const F=s(p,[["render",t]]);export{h as __pageData,F as default};
diff --git a/assets/actions_env_github-actions.md.bcabb284.js b/assets/actions_env_github-actions.md.bcabb284.js
new file mode 100644
index 000000000..259770614
--- /dev/null
+++ b/assets/actions_env_github-actions.md.bcabb284.js
@@ -0,0 +1,113 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"使用github actions进行持续部署","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/github-actions.md","filePath":"actions/env/github-actions.md","lastUpdated":1694368780000}'),p={name:"actions/env/github-actions.md"},o=l(`使用github actions进行持续部署 Github Actions官方文档https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#name
Github Actions是什么 Github推出的持续集成工具
使用 配置 Github Actions的配置文件叫做workflow文件,需要存放在repo根路径下的./github/workflows
目录中。workflow文件使用yaml
格式编写,文件名可以自定义,后缀统一为yml
,一个repo中可以有多个workflow,Github只要发现./github/workflows
目录中有.yml
文件就会自动运行。
本博客的workflow文件:
yml # 自定义当前执行文件的名称
+name : vuepress
+# 整个流程在main分支发生push事件时触发
+on :
+ push :
+ branches :
+ - main
+jobs :
+ build-and-deploy :
+ runs-on : ubuntu-latest # 运行在ubuntu-latest环境的虚拟机中
+ strategy :
+ matrix : # 矩阵
+ node-version : [ 10.x ]
+ steps : # 每个 job 由多个 step 构成,它会从上至下依次执行。
+ # 获取仓库源码
+ - name : Checkout
+ uses : actions/checkout@v2 # github actions提供了一些官方的action,例如checkout @v2是action的版本
+ # 安装node
+ - name : Use Node.js \${{ matrix.node-version }} # 定义好的node版本
+ uses : actions/setup-node@v1 # 作用:安装nodejs
+ with :
+ node-version : \${{ matrix.node-version }} # 定义好的node版本
+ # 构建和部署
+ - name : Deploy
+ env : # 环境变量
+ GITHUB_TOKEN : \${{ secrets.vuepress_actions_access_token }}
+ run : npm install && npm run deploy # npm run deploy需要在package.json中定义"deploy: bash deploy.sh"
# 自定义当前执行文件的名称
+name : vuepress
+# 整个流程在main分支发生push事件时触发
+on :
+ push :
+ branches :
+ - main
+jobs :
+ build-and-deploy :
+ runs-on : ubuntu-latest # 运行在ubuntu-latest环境的虚拟机中
+ strategy :
+ matrix : # 矩阵
+ node-version : [ 10.x ]
+ steps : # 每个 job 由多个 step 构成,它会从上至下依次执行。
+ # 获取仓库源码
+ - name : Checkout
+ uses : actions/checkout@v2 # github actions提供了一些官方的action,例如checkout @v2是action的版本
+ # 安装node
+ - name : Use Node.js \${{ matrix.node-version }} # 定义好的node版本
+ uses : actions/setup-node@v1 # 作用:安装nodejs
+ with :
+ node-version : \${{ matrix.node-version }} # 定义好的node版本
+ # 构建和部署
+ - name : Deploy
+ env : # 环境变量
+ GITHUB_TOKEN : \${{ secrets.vuepress_actions_access_token }}
+ run : npm install && npm run deploy # npm run deploy需要在package.json中定义"deploy: bash deploy.sh"
steps:
checkout源码到workflow中 安装指定版本的node环境 从仓库的设置中读取配置好的access_token,安装依赖,执行项目中的deploy.sh脚本 配置access_token 在github个人设置页面找到开发者设置
选择个人access tokens,选择生成新token
将生成好的token保存,关闭页面后将不会再显示此token,然后回到仓库保存
Deploy.sh shell #!/usr/bin/env sh
+
+# 确保脚本抛出遇到的错误
+set -e
+
+# 生成静态文件
+npm run build
+
+# 进入生成的文件夹
+cd docs/.vuepress/dist
+
+# 如果是发布到自定义域名
+echo 'blog.storyxc.com' > CNAME
+
+if [ -z "\${ GITHUB_TOKEN }" ]; then
+ echo "GITHUB_TOKEN is not set"
+ exit 1
+else
+ msg = 'github actions自动部署'
+ githubUrl = https://storyxc: \${GITHUB_TOKEN} @github.com/storyxc/vuepress.git
+ git config --global user.name "storyxc"
+ git config --global user.email "storyxc@163.com"
+fi
+
+git init
+git add -A
+git commit -m "\${ msg }"
+
+git push -f $githubUrl master:gh-pages
+
+cd -
#!/usr/bin/env sh
+
+# 确保脚本抛出遇到的错误
+set -e
+
+# 生成静态文件
+npm run build
+
+# 进入生成的文件夹
+cd docs/.vuepress/dist
+
+# 如果是发布到自定义域名
+echo 'blog.storyxc.com' > CNAME
+
+if [ -z "\${ GITHUB_TOKEN }" ]; then
+ echo "GITHUB_TOKEN is not set"
+ exit 1
+else
+ msg = 'github actions自动部署'
+ githubUrl = https://storyxc: \${GITHUB_TOKEN} @github.com/storyxc/vuepress.git
+ git config --global user.name "storyxc"
+ git config --global user.email "storyxc@163.com"
+fi
+
+git init
+git add -A
+git commit -m "\${ msg }"
+
+git push -f $githubUrl master:gh-pages
+
+cd -
这里我是用了github pages发布,然后配置了自定义域名,这个域名要在服务商域名解析配置CNAME,然后在仓库的page页面添加自定义域名即可
流程梳理 配置workflow,推送代码到触发job的分支 github actions会根据workflow中的配置拉代码,打包,然后调用deploy.sh,将生成的静态文件推送到gh-page分支 gh-page分支配置github page,自定义域名 `,23),e=[o];function t(c,r,y,E,i,F){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{h as __pageData,d as default};
diff --git a/assets/actions_env_github-actions.md.bcabb284.lean.js b/assets/actions_env_github-actions.md.bcabb284.lean.js
new file mode 100644
index 000000000..447941792
--- /dev/null
+++ b/assets/actions_env_github-actions.md.bcabb284.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"使用github actions进行持续部署","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/github-actions.md","filePath":"actions/env/github-actions.md","lastUpdated":1694368780000}'),p={name:"actions/env/github-actions.md"},o=l("",23),e=[o];function t(c,r,y,E,i,F){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{h as __pageData,d as default};
diff --git a/assets/actions_env_mysql-troubleshoot.md.ca79ae83.js b/assets/actions_env_mysql-troubleshoot.md.ca79ae83.js
new file mode 100644
index 000000000..ab5b314d6
--- /dev/null
+++ b/assets/actions_env_mysql-troubleshoot.md.ca79ae83.js
@@ -0,0 +1,133 @@
+import{_ as s,o as a,c as n,Q as e}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"mysql启动报错排查及处理","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/mysql-troubleshoot.md","filePath":"actions/env/mysql-troubleshoot.md","lastUpdated":1694368780000}'),p={name:"actions/env/mysql-troubleshoot.md"},o=e(`mysql启动报错排查及处理 今天访问我自己的老博客(www.storyxc.com )发现网站挂掉了,ssh上去看了一下nginx和我自己的java后台博客服务都挂掉了,可能是阿里云抽风服务器重启了。然后重启了nignx和服务访问了一下,查询一直在pending,再去看后台日志,发现获取不到连接,估计是mysql也挂了。
txt org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
+### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.mysql.cj.jdbc.exceptions.Communi
+cationsException: Communications link failure
+
+The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
+### The error may exist in class path resource [mapper/ArticleDao.xml]
+### The error may involve com.storyxc.mapper.ArticleDao.queryHotArticle
+### The error occurred while executing a query
+### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.mysql.cj.jdbc.exceptions.CommunicationsException: Communic
+ations link failure
+
+The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
+ at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77) ~[mybatis-spring-1.3.1.jar!/:1.3.1]
+ at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446) ~[mybatis-spring-1.3.1.jar!/:1.3.1]
+ at com.sun.proxy.$Proxy61.selectList(Unknown Source) ~[na:na]
+ at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230) ~[mybatis-spring-1.3.1.jar!/:1.3.1]
+ at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137) ~[mybatis-3.4.5.jar!/:3.4.5]
+ at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75) ~[mybatis-3.4.5.jar!/:3.4.5]
+ at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59) ~[mybatis-3.4.5.jar!/:3.4.5]
+ at com.sun.proxy.$Proxy82.queryHotArticle(Unknown Source) ~[na:na]
+ at com.storyxc.service.impl.ArticleServiceImpl.queryHotArticle(ArticleServiceImpl.java:113) ~[classes!/:1.0-SNAPSHOT]
+ at com.storyxc.service.impl.ArticleServiceImpl$$FastClassBySpringCGLIB$$edb0e759.invoke(<generated>) ~[classes!/:1.0-SNAPSHOT]
+ at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
+ at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684) ~[spring-aop-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
+ at com.storyxc.service.impl.ArticleServiceImpl$$EnhancerBySpringCGLIB$$5695fd69.queryHotArticle(<generated>) ~[classes!/:1.0-SNAPSHOT]
+ at com.storyxc.controller.ArticleController.queryHotArticle(ArticleController.java:73) ~[classes!/:1.0-SNAPSHOT]
+ at com.storyxc.controller.ArticleController$$FastClassBySpringCGLIB$$954e681b.invoke(<generated>) ~[classes!/:1.0-SNAPSHOT]
+ at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
+ at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684) ~[spring-aop-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
+ at com.storyxc.controller.ArticleController$$EnhancerBySpringCGLIB$$7f3f634c.queryHotArticle(<generated>) ~[classes!/:1.0-SNAPSHOT]
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_282]
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_282]
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_282]
+ at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_282]
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
+### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.mysql.cj.jdbc.exceptions.Communi
+cationsException: Communications link failure
+
+The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
+### The error may exist in class path resource [mapper/ArticleDao.xml]
+### The error may involve com.storyxc.mapper.ArticleDao.queryHotArticle
+### The error occurred while executing a query
+### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.mysql.cj.jdbc.exceptions.CommunicationsException: Communic
+ations link failure
+
+The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
+ at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77) ~[mybatis-spring-1.3.1.jar!/:1.3.1]
+ at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446) ~[mybatis-spring-1.3.1.jar!/:1.3.1]
+ at com.sun.proxy.$Proxy61.selectList(Unknown Source) ~[na:na]
+ at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230) ~[mybatis-spring-1.3.1.jar!/:1.3.1]
+ at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137) ~[mybatis-3.4.5.jar!/:3.4.5]
+ at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75) ~[mybatis-3.4.5.jar!/:3.4.5]
+ at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59) ~[mybatis-3.4.5.jar!/:3.4.5]
+ at com.sun.proxy.$Proxy82.queryHotArticle(Unknown Source) ~[na:na]
+ at com.storyxc.service.impl.ArticleServiceImpl.queryHotArticle(ArticleServiceImpl.java:113) ~[classes!/:1.0-SNAPSHOT]
+ at com.storyxc.service.impl.ArticleServiceImpl$$FastClassBySpringCGLIB$$edb0e759.invoke(<generated>) ~[classes!/:1.0-SNAPSHOT]
+ at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
+ at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684) ~[spring-aop-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
+ at com.storyxc.service.impl.ArticleServiceImpl$$EnhancerBySpringCGLIB$$5695fd69.queryHotArticle(<generated>) ~[classes!/:1.0-SNAPSHOT]
+ at com.storyxc.controller.ArticleController.queryHotArticle(ArticleController.java:73) ~[classes!/:1.0-SNAPSHOT]
+ at com.storyxc.controller.ArticleController$$FastClassBySpringCGLIB$$954e681b.invoke(<generated>) ~[classes!/:1.0-SNAPSHOT]
+ at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
+ at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684) ~[spring-aop-5.1.6.RELEASE.jar!/:5.1.6.RELEASE]
+ at com.storyxc.controller.ArticleController$$EnhancerBySpringCGLIB$$7f3f634c.queryHotArticle(<generated>) ~[classes!/:1.0-SNAPSHOT]
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_282]
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_282]
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_282]
+ at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_282]
然后尝试重启mysql:service mysql start
直接报错:Starting MySQL...The server quit without updating PID file [FAILED]b/mysql/iz2ze09hymnzdn4lgmltlmz.pid).
但就这样的报错没法排查,试图看下mysql的错误日志:less /var/log/mysql/error.log
结果没有,
这才想起来当时没给mysql配置错误日志路径。
给mysql配置错误文件的路径:vim /etc/my.cnf
在[mysqld]下面加一行:log_error=/var/log/mysql/error.log
,然后创建/var/log/mysql这个目录
再次启动,依旧报错,但这次我们可以去看错误日志了。继续 less /var/log/mysql/error.log
txt 210627 00:34:02 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
+2021-06-27 00:34:03 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
+2021-06-27 00:34:03 3127 [Note] Plugin 'FEDERATED' is disabled.
+2021-06-27 00:34:03 3127 [Note] InnoDB: Using atomics to ref count buffer pool pages
+2021-06-27 00:34:03 3127 [Note] InnoDB: The InnoDB memory heap is disabled
+2021-06-27 00:34:03 3127 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
+2021-06-27 00:34:03 3127 [Note] InnoDB: Memory barrier is not used
+2021-06-27 00:34:03 3127 [Note] InnoDB: Compressed tables use zlib 1.2.3
+2021-06-27 00:34:03 3127 [Note] InnoDB: Using Linux native AIO
+2021-06-27 00:34:03 3127 [Note] InnoDB: Not using CPU crc32 instructions
+2021-06-27 00:34:03 3127 [Note] InnoDB: Initializing buffer pool, size = 128.0M
+InnoDB: mmap(136019968 bytes) failed; errno 12
+2021-06-27 00:34:03 3127 [ERROR] InnoDB: Cannot allocate memory for the buffer pool
+2021-06-27 00:34:03 3127 [ERROR] Plugin 'InnoDB' init function returned error.
+2021-06-27 00:34:03 3127 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
+2021-06-27 00:34:03 3127 [ERROR] Unknown/unsupported storage engine: InnoDB
+2021-06-27 00:34:03 3127 [ERROR] Aborting
+
+2021-06-27 00:34:03 3127 [Note] Binlog end
210627 00:34:02 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
+2021-06-27 00:34:03 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
+2021-06-27 00:34:03 3127 [Note] Plugin 'FEDERATED' is disabled.
+2021-06-27 00:34:03 3127 [Note] InnoDB: Using atomics to ref count buffer pool pages
+2021-06-27 00:34:03 3127 [Note] InnoDB: The InnoDB memory heap is disabled
+2021-06-27 00:34:03 3127 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
+2021-06-27 00:34:03 3127 [Note] InnoDB: Memory barrier is not used
+2021-06-27 00:34:03 3127 [Note] InnoDB: Compressed tables use zlib 1.2.3
+2021-06-27 00:34:03 3127 [Note] InnoDB: Using Linux native AIO
+2021-06-27 00:34:03 3127 [Note] InnoDB: Not using CPU crc32 instructions
+2021-06-27 00:34:03 3127 [Note] InnoDB: Initializing buffer pool, size = 128.0M
+InnoDB: mmap(136019968 bytes) failed; errno 12
+2021-06-27 00:34:03 3127 [ERROR] InnoDB: Cannot allocate memory for the buffer pool
+2021-06-27 00:34:03 3127 [ERROR] Plugin 'InnoDB' init function returned error.
+2021-06-27 00:34:03 3127 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
+2021-06-27 00:34:03 3127 [ERROR] Unknown/unsupported storage engine: InnoDB
+2021-06-27 00:34:03 3127 [ERROR] Aborting
+
+2021-06-27 00:34:03 3127 [Note] Binlog end
查看内存情况:free -h
,由于我买的是阿里云的轻量级应用服务器-穷逼版,只有2G内存
bash total used free shared buff/cache available
+ Mem: 1.8 G 1.2 G 245 M 88 M 323 M 268 M
+ Swap: 0 B 0 B 0 B
total used free shared buff/cache available
+ Mem: 1.8 G 1.2 G 245 M 88 M 323 M 268 M
+ Swap: 0 B 0 B 0 B
swap为0,执行命令建立临时分区
bash dd if=/dev/zero of=/swap bs= 1 M count= 128 //创建一个swap文件,大小为128M
+mkswap /swap //将swap文件变为swap分区文件
+swapon /swap //将其映射为swap分区
dd if=/dev/zero of=/swap bs= 1 M count= 128 //创建一个swap文件,大小为128M
+mkswap /swap //将swap文件变为swap分区文件
+swapon /swap //将其映射为swap分区
再次查看内存:
bash total used free shared buff/cache available
+Mem: 1.8 G 1.3 G 82 M 88 M 463 M 236 M
+Swap: 127 M 0 B 127 M
total used free shared buff/cache available
+Mem: 1.8 G 1.3 G 82 M 88 M 463 M 236 M
+Swap: 127 M 0 B 127 M
swap分区已存在,执行命令使系统重启swap分区自动加载:vim /etc/fstab
bash /swap swap swap defaults 0 0
/swap swap swap defaults 0 0
再次启动 还是他喵不行,执行命令查看下当前内存占用大户。
ps -eo pid,ppid,%mem,%cpu,cmd --sort=-%mem | head
bash PID PPID %MEM %CPU CMD
+19872 1 14.0 45.7 ./phpupdate
+20228 1 14.0 45.7 /etc/phpupdate
+ 9372 1 8.0 1.3 java -jar storyxc.jar
+27880 1 3.1 0.0 /opt/openoffice4/program/soffice.bin -headless -accept=socket,host=127.0.0.1,port=8100 ; urp ; -nofirststartwizard
+13389 1 2.6 0.0 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
+23244 21287 2.3 4.7 ./networkmanager 15
+13322 1 1.3 0.0 /usr/bin/containerd
+ 489 455 1.0 0.1 CmsGoAgent-Worker start
+19905 1 0.6 0.0 ./phpguard
PID PPID %MEM %CPU CMD
+19872 1 14.0 45.7 ./phpupdate
+20228 1 14.0 45.7 /etc/phpupdate
+ 9372 1 8.0 1.3 java -jar storyxc.jar
+27880 1 3.1 0.0 /opt/openoffice4/program/soffice.bin -headless -accept=socket,host=127.0.0.1,port=8100 ; urp ; -nofirststartwizard
+13389 1 2.6 0.0 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
+23244 21287 2.3 4.7 ./networkmanager 15
+13322 1 1.3 0.0 /usr/bin/containerd
+ 489 455 1.0 0.1 CmsGoAgent-Worker start
+19905 1 0.6 0.0 ./phpguard
好家伙,从哪冒出来的phpupdate 直接全杀掉,世界瞬间清净了,腾出来600M的内存,
再次启动mysql,成功,问题解决。
`,24),l=[o];function t(r,c,y,i,E,F){return a(),n("div",null,l)}const m=s(p,[["render",t]]);export{C as __pageData,m as default};
diff --git a/assets/actions_env_mysql-troubleshoot.md.ca79ae83.lean.js b/assets/actions_env_mysql-troubleshoot.md.ca79ae83.lean.js
new file mode 100644
index 000000000..d18a04ddd
--- /dev/null
+++ b/assets/actions_env_mysql-troubleshoot.md.ca79ae83.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as e}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"mysql启动报错排查及处理","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/mysql-troubleshoot.md","filePath":"actions/env/mysql-troubleshoot.md","lastUpdated":1694368780000}'),p={name:"actions/env/mysql-troubleshoot.md"},o=e("",24),l=[o];function t(r,c,y,i,E,F){return a(),n("div",null,l)}const m=s(p,[["render",t]]);export{C as __pageData,m as default};
diff --git a/assets/actions_env_powershell-beautify.md.a053ec8f.js b/assets/actions_env_powershell-beautify.md.a053ec8f.js
new file mode 100644
index 000000000..53d42fc79
--- /dev/null
+++ b/assets/actions_env_powershell-beautify.md.a053ec8f.js
@@ -0,0 +1,19 @@
+import{_ as s,o as a,c as o,Q as n}from"./chunks/framework.b637c96f.js";const F=JSON.parse('{"title":"powershell美化","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/powershell-beautify.md","filePath":"actions/env/powershell-beautify.md","lastUpdated":1694368780000}'),l={name:"actions/env/powershell-beautify.md"},e=n(`powershell美化 https://ohmyposh.dev/docs/
安装windows terminal和powershell 安装oh-my-posh shell Set-ExecutionPolicy Bypass -Scope Process -Force ; Invoke-Expression ((New-Object System.Net.WebClient ).DownloadString( 'https://ohmyposh.dev/install.ps1' ))
Set-ExecutionPolicy Bypass -Scope Process -Force ; Invoke-Expression ((New-Object System.Net.WebClient ).DownloadString( 'https://ohmyposh.dev/install.ps1' ))
安装Nerd Fonts 如果不安装Nerd Fonts会有乱码情况,oh-my-posh推荐安装Meslo LGM NF 字体,也可以从https://www.nerdfonts.com/font-downloads自行选择下载。下载后解压放到C:\\windows\\Fonts文件夹中。编辑Windows Terminal默认设置将默认字体改为喜欢的Nerd Fonts。
编辑profile code $PROFILE
或notepad $PROFILE
shell oh-my-posh init pwsh | Invoke-Expression # 默认主题
+
+oh-my-posh init pwsh --config C: \\U sers \\s tory \\A ppData \\L ocal \\P rograms \\o h-my-posh \\t hemes \\r obbyrussel.omp.json | Invoke-Expression # --config可以配置喜欢的主题
+
+--config 'https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/jandedobbeleer.omp.json' # 也可以配置远程主题
oh-my-posh init pwsh | Invoke-Expression # 默认主题
+
+oh-my-posh init pwsh --config C: \\U sers \\s tory \\A ppData \\L ocal \\P rograms \\o h-my-posh \\t hemes \\r obbyrussel.omp.json | Invoke-Expression # --config可以配置喜欢的主题
+
+--config 'https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/jandedobbeleer.omp.json' # 也可以配置远程主题
shell # 设置预测文本来源为历史记录
+Set-PSReadLineOption -PredictionSource History
+# 设置向上键为后向搜索历史记录
+Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward
+# 设置向下键为前向搜索历史纪录
+Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward
# 设置预测文本来源为历史记录
+Set-PSReadLineOption -PredictionSource History
+# 设置向上键为后向搜索历史记录
+Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward
+# 设置向下键为前向搜索历史纪录
+Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward
windows安装后默认的主题文件夹为:C:\\Users\\[your username]\\AppData\\Local\\Programs\\oh-my-posh\\themes
,也可以通过echo $env:POSH_THEMES_PATH
命令查看主题的路径
挑选喜欢的主题 配置立即生效:
效果图:
默认:
jandedobbeleer:
`,19),p=[e];function t(r,c,y,i,E,h){return a(),o("div",null,p)}const m=s(l,[["render",t]]);export{F as __pageData,m as default};
diff --git a/assets/actions_env_powershell-beautify.md.a053ec8f.lean.js b/assets/actions_env_powershell-beautify.md.a053ec8f.lean.js
new file mode 100644
index 000000000..4e650587d
--- /dev/null
+++ b/assets/actions_env_powershell-beautify.md.a053ec8f.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as o,Q as n}from"./chunks/framework.b637c96f.js";const F=JSON.parse('{"title":"powershell美化","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/powershell-beautify.md","filePath":"actions/env/powershell-beautify.md","lastUpdated":1694368780000}'),l={name:"actions/env/powershell-beautify.md"},e=n("",19),p=[e];function t(r,c,y,i,E,h){return a(),o("div",null,p)}const m=s(l,[["render",t]]);export{F as __pageData,m as default};
diff --git a/assets/actions_env_startup-script-macos.md.0507c211.js b/assets/actions_env_startup-script-macos.md.0507c211.js
new file mode 100644
index 000000000..7e8a3f115
--- /dev/null
+++ b/assets/actions_env_startup-script-macos.md.0507c211.js
@@ -0,0 +1,45 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"macos开机自动执行脚本","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/startup-script-macos.md","filePath":"actions/env/startup-script-macos.md","lastUpdated":1694368780000}'),p={name:"actions/env/startup-script-macos.md"},o=l(`macos开机自动执行脚本 linux开机启动可以用systemd很方便的实现,mac上稍微复杂一些,需要自己写个.plist文件
简介 launchd 是 Mac OS 下用于初始化系统环境的关键进程,它是内核装载成功之后在 OS 环境下启动的第一个进程,可以用来控制服务的自动启动或者关闭。
它的作用就是我们平时说的守护进程,简单来说,用户守护进程是作为系统的一部分运行在后台的非图形化程序。
采用这种方式来配置自启动项很简单,只需要一个 plist 文件,该文件存在的目录有:
用户登陆前 LaunchDaemons:
~/Library/LaunchDaemons
用户登录后 LaunchAgents:
~/Library/LaunchAgents
脚本 xml <? xml version = "1.0" encoding = "UTF-8" ?>
+<! DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+< plist version = "1.0" >
+ < dict >
+ < key >KeepAlive</ key >
+ < dict >
+ < key >SuccessfulExit</ key >
+ < false />
+ </ dict >
+ < key >Label</ key >
+ < string >com.storyxc.frpc</ string >
+ < key >ProgramArguments</ key >
+ < array >
+ < string >/Users/story/project/widget/frp/frpc</ string >
+ < string >-c</ string >
+ < string >/Users/story/project/widget/frp/frpc.ini</ string >
+ </ array >
+ < key >RunAtLoad</ key >
+ < true />
+ </ dict >
+</ plist >
<? xml version = "1.0" encoding = "UTF-8" ?>
+<! DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+< plist version = "1.0" >
+ < dict >
+ < key >KeepAlive</ key >
+ < dict >
+ < key >SuccessfulExit</ key >
+ < false />
+ </ dict >
+ < key >Label</ key >
+ < string >com.storyxc.frpc</ string >
+ < key >ProgramArguments</ key >
+ < array >
+ < string >/Users/story/project/widget/frp/frpc</ string >
+ < string >-c</ string >
+ < string >/Users/story/project/widget/frp/frpc.ini</ string >
+ </ array >
+ < key >RunAtLoad</ key >
+ < true />
+ </ dict >
+</ plist >
将脚本命名为frpc.plist,然后移动到~/Library/LaunchAgents/
下
载入plist文件 启动服务:
launchctl [load|enable|bootstrap] -w plist_path
卸载服务:
launchctl [unload|disable|bootout] -w plist_path
设置别名 zsh # frpc启动、停止
+alias frpc.start='launchctl load -w ~/Library/LaunchAgents/frpc.plist'
+alias frpc.stop='launchctl unload -w ~/Library/LaunchAgents/frpc.plist'
# frpc启动、停止
+alias frpc.start='launchctl load -w ~/Library/LaunchAgents/frpc.plist'
+alias frpc.stop='launchctl unload -w ~/Library/LaunchAgents/frpc.plist'
`,20),t=[o];function e(c,r,E,y,i,g){return a(),n("div",null,t)}const h=s(p,[["render",e]]);export{u as __pageData,h as default};
diff --git a/assets/actions_env_startup-script-macos.md.0507c211.lean.js b/assets/actions_env_startup-script-macos.md.0507c211.lean.js
new file mode 100644
index 000000000..f6221653e
--- /dev/null
+++ b/assets/actions_env_startup-script-macos.md.0507c211.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"macos开机自动执行脚本","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/startup-script-macos.md","filePath":"actions/env/startup-script-macos.md","lastUpdated":1694368780000}'),p={name:"actions/env/startup-script-macos.md"},o=l("",20),t=[o];function e(c,r,E,y,i,g){return a(),n("div",null,t)}const h=s(p,[["render",e]]);export{u as __pageData,h as default};
diff --git a/assets/actions_env_terminal-proxy-macos.md.90a20fc3.js b/assets/actions_env_terminal-proxy-macos.md.90a20fc3.js
new file mode 100644
index 000000000..7671a892f
--- /dev/null
+++ b/assets/actions_env_terminal-proxy-macos.md.90a20fc3.js
@@ -0,0 +1,3 @@
+import{_ as s,o as a,c as o,Q as p}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"macOS开启终端的代理","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/terminal-proxy-macos.md","filePath":"actions/env/terminal-proxy-macos.md","lastUpdated":1694368780000}'),e={name:"actions/env/terminal-proxy-macos.md"},t=p(`macOS开启终端的代理 例如要走socks5的代理
bash export https_proxy = socks5://127.0.0.1:10880
export https_proxy = socks5://127.0.0.1:10880
这样配置只对当前终端有效,不会影响其他
bash export http_proxy = socks5://127.0.0.1:10880
+export https_proxy = socks5://127.0.0.1:10880
export http_proxy = socks5://127.0.0.1:10880
+export https_proxy = socks5://127.0.0.1:10880
修改后保存,然后source ~/.zshrc
立即成效。重启终端后即可全局代理。
也可以通过alias建立个别名,这样可以快速开启代理,编辑.zshrc 添加
alias proxy_on='export https_proxy=socks5://127.0.0.1:10880'
`,10),n=[t];function l(c,r,i,d,y,h){return a(),o("div",null,n)}const x=s(e,[["render",l]]);export{m as __pageData,x as default};
diff --git a/assets/actions_env_terminal-proxy-macos.md.90a20fc3.lean.js b/assets/actions_env_terminal-proxy-macos.md.90a20fc3.lean.js
new file mode 100644
index 000000000..1aec22bb1
--- /dev/null
+++ b/assets/actions_env_terminal-proxy-macos.md.90a20fc3.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as o,Q as p}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"macOS开启终端的代理","description":"","frontmatter":{},"headers":[],"relativePath":"actions/env/terminal-proxy-macos.md","filePath":"actions/env/terminal-proxy-macos.md","lastUpdated":1694368780000}'),e={name:"actions/env/terminal-proxy-macos.md"},t=p("",10),n=[t];function l(c,r,i,d,y,h){return a(),o("div",null,n)}const x=s(e,[["render",l]]);export{m as __pageData,x as default};
diff --git a/assets/actions_index.md.3e437531.js b/assets/actions_index.md.3e437531.js
new file mode 100644
index 000000000..e138704e6
--- /dev/null
+++ b/assets/actions_index.md.3e437531.js
@@ -0,0 +1 @@
+import{_ as t,o as a,c as n,k as e,a as s}from"./chunks/framework.b637c96f.js";const x=JSON.parse('{"title":"Actions","description":"","frontmatter":{},"headers":[],"relativePath":"actions/index.md","filePath":"actions/index.md","lastUpdated":1694368780000}'),o={name:"actions/index.md"},i=e("h1",{id:"actions",tabindex:"-1"},[s("Actions "),e("a",{class:"header-anchor",href:"#actions","aria-label":'Permalink to "Actions"'},"")],-1),c=e("ul",null,[e("li",null,"工具"),e("li",null,"环境"),e("li",null,"设计模式")],-1),l=[i,c];function r(d,_,p,h,m,u){return a(),n("div",null,l)}const k=t(o,[["render",r]]);export{x as __pageData,k as default};
diff --git a/assets/actions_index.md.3e437531.lean.js b/assets/actions_index.md.3e437531.lean.js
new file mode 100644
index 000000000..e138704e6
--- /dev/null
+++ b/assets/actions_index.md.3e437531.lean.js
@@ -0,0 +1 @@
+import{_ as t,o as a,c as n,k as e,a as s}from"./chunks/framework.b637c96f.js";const x=JSON.parse('{"title":"Actions","description":"","frontmatter":{},"headers":[],"relativePath":"actions/index.md","filePath":"actions/index.md","lastUpdated":1694368780000}'),o={name:"actions/index.md"},i=e("h1",{id:"actions",tabindex:"-1"},[s("Actions "),e("a",{class:"header-anchor",href:"#actions","aria-label":'Permalink to "Actions"'},"")],-1),c=e("ul",null,[e("li",null,"工具"),e("li",null,"环境"),e("li",null,"设计模式")],-1),l=[i,c];function r(d,_,p,h,m,u){return a(),n("div",null,l)}const k=t(o,[["render",r]]);export{x as __pageData,k as default};
diff --git a/assets/actions_tools_book-searcher.md.8e834212.js b/assets/actions_tools_book-searcher.md.8e834212.js
new file mode 100644
index 000000000..bc922fc51
--- /dev/null
+++ b/assets/actions_tools_book-searcher.md.8e834212.js
@@ -0,0 +1,27 @@
+import{_ as s,o as a,c as n,Q as e}from"./chunks/framework.b637c96f.js";const b=JSON.parse('{"title":"book-searcher电子书镜像站点","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/book-searcher.md","filePath":"actions/tools/book-searcher.md","lastUpdated":1694368780000}'),o={name:"actions/tools/book-searcher.md"},l=e(`book-searcher电子书镜像站点 项目地址:https://github.com/book-searcher-org/book-searcher
docker-compose.yml yml version : '3'
+
+services :
+ book-searcher :
+ image : ghcr.io/book-searcher-org/book-searcher:latest
+ container_name : book-searcher
+ restart : always
+ ports :
+ - "7070:7070"
+ volumes :
+ - ./index:/index
version : '3'
+
+services :
+ book-searcher :
+ image : ghcr.io/book-searcher-org/book-searcher:latest
+ container_name : book-searcher
+ restart : always
+ ports :
+ - "7070:7070"
+ volumes :
+ - ./index:/index
下载index文件 https://zh.annas-archive.org/datasets
https://onedrive.caomingjun.com/zh-CN/🖥软件/zlib-searcher/
启动容器配置IPFS网关 txt https://cloudflare-ipfs.com
+https://dweb.link
+https://ipfs.io
+https://dw.oho.im
https://cloudflare-ipfs.com
+https://dweb.link
+https://ipfs.io
+https://dw.oho.im
`,8),p=[l];function r(t,c,i,h,E,y){return a(),n("div",null,p)}const k=s(o,[["render",r]]);export{b as __pageData,k as default};
diff --git a/assets/actions_tools_book-searcher.md.8e834212.lean.js b/assets/actions_tools_book-searcher.md.8e834212.lean.js
new file mode 100644
index 000000000..289574b33
--- /dev/null
+++ b/assets/actions_tools_book-searcher.md.8e834212.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as e}from"./chunks/framework.b637c96f.js";const b=JSON.parse('{"title":"book-searcher电子书镜像站点","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/book-searcher.md","filePath":"actions/tools/book-searcher.md","lastUpdated":1694368780000}'),o={name:"actions/tools/book-searcher.md"},l=e("",8),p=[l];function r(t,c,i,h,E,y){return a(),n("div",null,p)}const k=s(o,[["render",r]]);export{b as __pageData,k as default};
diff --git a/assets/actions_tools_file-consistency-check.md.7d5c9dfa.js b/assets/actions_tools_file-consistency-check.md.7d5c9dfa.js
new file mode 100644
index 000000000..35b1a094b
--- /dev/null
+++ b/assets/actions_tools_file-consistency-check.md.7d5c9dfa.js
@@ -0,0 +1 @@
+import{_ as t,o as e,c as a,Q as l}from"./chunks/framework.b637c96f.js";const x=JSON.parse('{"title":"各系统下校验文件一致性","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/file-consistency-check.md","filePath":"actions/tools/file-consistency-check.md","lastUpdated":1694368780000}'),s={name:"actions/tools/file-consistency-check.md"},i=l('各系统下校验文件一致性 之前从网上下软件一直没有校验的习惯,直到从某知名mac破解网站上下了个被人恶意投毒的navicat,本文记录下各系统下校验的操作。内容引用自Apache Kafka官方文档。
校验哈希值 Windows Linux Mac SHA-1 (deprecated) certUtil -hashfile file SHA1 sha1sum file shasum -a 1 file SHA-256 certUtil -hashfile file SHA256 sha256sum file shasum -a 256 file SHA-512 certUtil -hashfile file SHA512 sha512sum file shasum -a 512 file MD5 (deprecated) certUtil -hashfile file MD5 md5sum file md5 file
',4),n=[i];function c(d,r,o,h,m,f){return e(),a("div",null,n)}const y=t(s,[["render",c]]);export{x as __pageData,y as default};
diff --git a/assets/actions_tools_file-consistency-check.md.7d5c9dfa.lean.js b/assets/actions_tools_file-consistency-check.md.7d5c9dfa.lean.js
new file mode 100644
index 000000000..d218534af
--- /dev/null
+++ b/assets/actions_tools_file-consistency-check.md.7d5c9dfa.lean.js
@@ -0,0 +1 @@
+import{_ as t,o as e,c as a,Q as l}from"./chunks/framework.b637c96f.js";const x=JSON.parse('{"title":"各系统下校验文件一致性","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/file-consistency-check.md","filePath":"actions/tools/file-consistency-check.md","lastUpdated":1694368780000}'),s={name:"actions/tools/file-consistency-check.md"},i=l("",4),n=[i];function c(d,r,o,h,m,f){return e(),a("div",null,n)}const y=t(s,[["render",c]]);export{x as __pageData,y as default};
diff --git a/assets/actions_tools_git-cmd.md.2cccda47.js b/assets/actions_tools_git-cmd.md.2cccda47.js
new file mode 100644
index 000000000..bdae6e03b
--- /dev/null
+++ b/assets/actions_tools_git-cmd.md.2cccda47.js
@@ -0,0 +1,7 @@
+import{_ as e,o,c as t,Q as c}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"git命令整理","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/git-cmd.md","filePath":"actions/tools/git-cmd.md","lastUpdated":1694368780000}'),a={name:"actions/tools/git-cmd.md"},p=c(`git命令整理 版本控制工具一直用的GIT,之前提交代码都是用IDEA集成的GIT可视化工具,命令行几乎不怎么用,由于接下来项目要整合到微服务平台中,项目代码管理也要迁到Gerrit,idea的集成支持不太好,所以整理下GIT的命令,方便后面使用命令行提交代码。
Remote:远程仓库
+Reporsitory:本地仓库
+WorkSpace:工作区
+Index:暂存区
Remote:远程仓库
+Reporsitory:本地仓库
+WorkSpace:工作区
+Index:暂存区
撤回修改 git commit --amend
:提交完发现漏掉了几个文件没有添加,或者提交信息写错了,此时,可以运行带有 --amend 选项的提交命令来重新提交
git checkout -- <file>
把readme.txt
文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.txt
自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt
已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
git reset [--soft | --mixed | --hard] [HEAD]
使用soft只会移动HEAD到上一个版本,可以理解为撤回上一次commit,暂存区和工作区不受影响 使用mixed在移动HEAD到上一个版本,并且回退暂存区的内容,工作区不受影响 使用hard,除了移动HEAD指针,取消暂存内容,还会覆盖回退工作区内容,属于比较危险的命令,谨慎使用 不加括号中的参数 默认参数为mixed, 如果想回退多个版本可以修改为HEAD^^^或HEAD~3代表回退3个版本,依此类推 关于git checkout
和git reset
建议看下这篇文章,git重置
切换分支 git checkout branch_name
切换分之前要注意本地分支是否有未commit的文件,如果有可以撤销改动,或者commit,再或者使用git stash将当前分支的改动临时保存起来,使当前分支的工作空间和暂存区变干净。然后再进行切换分支;切换回之前的分支,需要恢复被临时保存的改动
暂存和恢复本地修改 git stash -u
恢复本地修改:
1.先查看有多少个临时保存的改动
git stash list
2.再用git stash apply --index stash@{n}
,n为使用git stash list查看到的某个改动的数字
3.再用git stash drop stash@{n}
删除临时保存的改动
如果只有一个临时的stash,那么可以直接git stash apply
即可恢复上次的临时保存记录
创建本地分支 基于本地master分支创建test分支为例:
先切换到master分支:git checkout master
建分支: git branch test
切分支:git checkout test
或者
建分支后切到该分支:git checkout -b test master
以基于某次commit id创建test分支为例:
git checkout -b test 0faceff
其中的0faceff为commit id的前7位
以基于某个tag创建test分支为例
git checkout -b test v0.1.0
v0.1.0为tag的名称
查看分支 git branch
: 只显示本地分支名,当前分支名前有星号
git branch -v
:显示本地分支名,当前分支前有星号,显示commit id
git branch -vv
:显示本地分支名,当前分支名前有星号,显示commit id,显示追踪的远程分支名
git branch -a
:显示所有分支名(包括远程分支)
git branch -r
:查看远程分支名
删除本地分支 普通删除:git branch -d branch_name
强制删除(分支上有修改未合并到其他分支):git branch -D branch_name
更新代码 git pull
或者git fetch
git pull -v --progress "origin"
命令可以显示更详细的信息,git pull命令会fetch所有的远程分支的信息到本地,同时当前本地分支会被合并。
如果本地有修改文件,而且远程仓库也修改了该文件,pull会失败,提示本地的修改会被合并覆盖,此时可以commit本地的修改或者stash本地的修改,再pull。
修改代码 首先使用git checkout branch_name
切换到正确分支,pull,新建或修改代码,再使用git add 文件名把修改或新增的文件添加到暂存区,再执行commit命令提交到本地仓库。
其中:
git add 某个文件
git add 多个文件
(文件名用空格隔开)
git add -u
添加所有修改的文件到暂存区
git add .
添加所有修改和新增的文件到暂存区
git add -A
:添加所有修改,新增和删除的文件到暂存区
git commit 文件名 -m "注释"
:commit某个文件
git commit 文件1 文件2 -m "注释"
commit多个文件,用空格隔开
git commit -m "注释"
commit所有文件
如果是删除文件,可以使用
rm 文件
git add 文件
git commit 文件 -m "注释"
如果是重命名文件或者移动文件,可以使用
git mv 源文件路径 目标文件路径
git commit 文件 -m "注释"
每次编辑前先进行pull操作,避免再push时产生合并冲突 push代码带远程仓库 本地代码从本地branch_name分支推到远端branch_name分支:
git checkout branch_name
git pull
git push origin HEAD:refs/for/branch_name
或者
git checkout branch_name
git pull
git push origin branch_name:refs/for/branch_name
查看信息 git status
显示有变更的文件
git log
显示当前分支的版本历史
git log --stat
显示commit历史,以及每次commit发生变更的文件
git log -S [keyword]
搜索提交历史,根据关键词
git log [tag] HEAD --pretty=format:%s
显示某个commit之后的所有变动,每个commit占据一行
git log [tag] HEAD --grep feature
显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
git log -p [file]
显示指定文件相关的每一次diff
git log -5 --pretty --oneline
显示过去5次提交
git shortlog -sn
显示所有提交过的用户,按提交次数排序
git blame [file]
显示指定文件是什么人在什么时间修改过
git diff
显示暂存区和工作区的代码差异
git diff --cached [file]
显示暂存区和上一个commit的差异
git diff HEAD
显示工作区与当前分支最新commit之间的差异
git diff [first-branch]...[second-branch]
显示两次提交之间的差异
git diff --shortstat "@{0 day ago}"
显示今天你写了多少行代码
git show [commit]
显示某次提交的元数据和内容变化
git show --name-only [commit]
显示某次提交发生变化的文件
git show [commit]:[filename]
显示某次提交时,某个文件的内容
git rebase [branch]
从本地master拉取代码更新当前分支:branch 一般为master
fetch vs pull git fetch是将远程的最新内容拉到本地,用户在检查了以后决定是否合并到本地分支中。 而git pull 则是将远程的最新内容拉下来后直接合并,即:git pull = git fetch + git merge,这样可能会产生冲突,需要手动解决。
`,92),i=[p];function d(s,l,r,n,h,g){return o(),t("div",null,i)}const f=e(a,[["render",d]]);export{u as __pageData,f as default};
diff --git a/assets/actions_tools_git-cmd.md.2cccda47.lean.js b/assets/actions_tools_git-cmd.md.2cccda47.lean.js
new file mode 100644
index 000000000..f9fd9eb40
--- /dev/null
+++ b/assets/actions_tools_git-cmd.md.2cccda47.lean.js
@@ -0,0 +1 @@
+import{_ as e,o,c as t,Q as c}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"git命令整理","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/git-cmd.md","filePath":"actions/tools/git-cmd.md","lastUpdated":1694368780000}'),a={name:"actions/tools/git-cmd.md"},p=c("",92),i=[p];function d(s,l,r,n,h,g){return o(),t("div",null,i)}const f=e(a,[["render",d]]);export{u as __pageData,f as default};
diff --git a/assets/actions_tools_iterm2-oh-my-zsh.md.b5d4f68e.js b/assets/actions_tools_iterm2-oh-my-zsh.md.b5d4f68e.js
new file mode 100644
index 000000000..85026ad79
--- /dev/null
+++ b/assets/actions_tools_iterm2-oh-my-zsh.md.b5d4f68e.js
@@ -0,0 +1 @@
+import{_ as a,o as e,c as t,Q as o}from"./chunks/framework.b637c96f.js";const g=JSON.parse('{"title":"iterm2配合oh-my-zsh配置个性主题终端","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/iterm2-oh-my-zsh.md","filePath":"actions/tools/iterm2-oh-my-zsh.md","lastUpdated":1694368780000}'),r={name:"actions/tools/iterm2-oh-my-zsh.md"},h=o('iterm2配合oh-my-zsh配置个性主题终端 安装iterm2 官网下载:https://iterm2.com/
安装oh my zsh 官网:https://ohmyz.sh/
安装脚本:sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
因为网络原因无法执行这个脚本的可以找gitee上的国内源
更改iterm2的主题颜色为dracula 在iterm2的dracula主题仓库中下载color文件 仓库地址: https://github.com/dracula/iterm.git
打开iterm2导入刚下载的color文件
如图,导入完之后就可以选择导入的dracula主题颜色
安装命令高亮插件 clone代码到本地 git clone https://github.com/zsh-users/zsh-syntax-highlighting ~/.zsh/zsh-syntax-highlighting
修改.zshrc配置 添加配置:source ~/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
效果
安装历史指令提示插件 clone代码到本地 git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
修改.zshrc配置 source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh
',26),s=[h];function i(c,l,n,m,d,u){return e(),t("div",null,s)}const z=a(r,[["render",i]]);export{g as __pageData,z as default};
diff --git a/assets/actions_tools_iterm2-oh-my-zsh.md.b5d4f68e.lean.js b/assets/actions_tools_iterm2-oh-my-zsh.md.b5d4f68e.lean.js
new file mode 100644
index 000000000..783db9069
--- /dev/null
+++ b/assets/actions_tools_iterm2-oh-my-zsh.md.b5d4f68e.lean.js
@@ -0,0 +1 @@
+import{_ as a,o as e,c as t,Q as o}from"./chunks/framework.b637c96f.js";const g=JSON.parse('{"title":"iterm2配合oh-my-zsh配置个性主题终端","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/iterm2-oh-my-zsh.md","filePath":"actions/tools/iterm2-oh-my-zsh.md","lastUpdated":1694368780000}'),r={name:"actions/tools/iterm2-oh-my-zsh.md"},h=o("",26),s=[h];function i(c,l,n,m,d,u){return e(),t("div",null,s)}const z=a(r,[["render",i]]);export{g as __pageData,z as default};
diff --git a/assets/actions_tools_iterm2-ssh-conn-config.md.91112d85.js b/assets/actions_tools_iterm2-ssh-conn-config.md.91112d85.js
new file mode 100644
index 000000000..80d71a13b
--- /dev/null
+++ b/assets/actions_tools_iterm2-ssh-conn-config.md.91112d85.js
@@ -0,0 +1 @@
+import{_ as e,o as a,c as t,Q as o}from"./chunks/framework.b637c96f.js";const x=JSON.parse('{"title":"iterm2配置ssh快速连接","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/iterm2-ssh-conn-config.md","filePath":"actions/tools/iterm2-ssh-conn-config.md","lastUpdated":1694368780000}'),s={name:"actions/tools/iterm2-ssh-conn-config.md"},i=o('iterm2配置ssh快速连接 macos生态的ssh工具有很多,但是试了很多还是感觉很差劲,不如windows生态的mobaxterm和xshell,可惜这两个软件没有mac的版本。不过iterm2作为mac生态下的终端工具代表倒是很简洁方便,但是没有专门的ssh工具那种简易的远程连接配置,需要自己动手折腾一下才可以。
安装iterm2 官网下载,不赘述。
配置profile command+,
打开偏好设置,选择profiles
新建一个profile设置,将command设置从login shell改为command,并输入需要执行的ssh指令
切换到advanced选项卡,选择编辑triggers触发器。新增一个触发器,action选择send text,触发的表达式改为root@xxx.xxx.xxx.xxx's password
,参数改为登陆账户的密码+\\n
,这里注意一定要加\\n代表输入回车,不然就会卡在输入密码那里需要手动回车才能登陆,然后勾选上instant立即触发。
这里触发器的表达式即是输入ssh命令时,服务器给出的需要输入密码的提示文字,所以想配置什么服务器的触发器直接改个登陆名和服务器地址就可以
测试 配置完毕后,可以根据菜单栏的profiles选项卡,选择需要连接的服务器即可
',14),r=[i];function m(c,n,l,p,g,h){return a(),t("div",null,r)}const _=e(s,[["render",m]]);export{x as __pageData,_ as default};
diff --git a/assets/actions_tools_iterm2-ssh-conn-config.md.91112d85.lean.js b/assets/actions_tools_iterm2-ssh-conn-config.md.91112d85.lean.js
new file mode 100644
index 000000000..35fc9cd50
--- /dev/null
+++ b/assets/actions_tools_iterm2-ssh-conn-config.md.91112d85.lean.js
@@ -0,0 +1 @@
+import{_ as e,o as a,c as t,Q as o}from"./chunks/framework.b637c96f.js";const x=JSON.parse('{"title":"iterm2配置ssh快速连接","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/iterm2-ssh-conn-config.md","filePath":"actions/tools/iterm2-ssh-conn-config.md","lastUpdated":1694368780000}'),s={name:"actions/tools/iterm2-ssh-conn-config.md"},i=o("",14),r=[i];function m(c,n,l,p,g,h){return a(),t("div",null,r)}const _=e(s,[["render",m]]);export{x as __pageData,_ as default};
diff --git a/assets/actions_tools_linux-time-machine.md.c1ed2026.js b/assets/actions_tools_linux-time-machine.md.c1ed2026.js
new file mode 100644
index 000000000..a6c917f7a
--- /dev/null
+++ b/assets/actions_tools_linux-time-machine.md.c1ed2026.js
@@ -0,0 +1,5 @@
+import{_ as a,o as e,c as t,Q as n}from"./chunks/framework.b637c96f.js";const _=JSON.parse('{"title":"linux设置macOS时间机器server","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/linux-time-machine.md","filePath":"actions/tools/linux-time-machine.md","lastUpdated":1694368780000}'),s={name:"actions/tools/linux-time-machine.md"},i=n(`linux设置macOS时间机器server 安装需要的包 sudo apt install netatalk avahi-daemon
编辑netatalk配置文件 sudo vim /etc/netatalk/afp.conf
添加Time Machine配置 txt [Time Machine]
+path = /mnt/data/backup/time_machine
+time machine = yes
[Time Machine]
+path = /mnt/data/backup/time_machine
+time machine = yes
创建目录 sudo mkdir -p /mnt/data/backup/time_machine
sudo chown nobody:nogroup /mnt/data/backup/time_machine
sudo chmod 777 /mnt/data/backup/time_machine
重启netatalk服务 sudo systemctl restart netatalk
在mac上进行备份 时间机器中选择磁盘,连接linux server即可。
`,15),o=[i];function c(l,r,d,p,h,m){return e(),t("div",null,o)}const k=a(s,[["render",c]]);export{_ as __pageData,k as default};
diff --git a/assets/actions_tools_linux-time-machine.md.c1ed2026.lean.js b/assets/actions_tools_linux-time-machine.md.c1ed2026.lean.js
new file mode 100644
index 000000000..b64e18a39
--- /dev/null
+++ b/assets/actions_tools_linux-time-machine.md.c1ed2026.lean.js
@@ -0,0 +1 @@
+import{_ as a,o as e,c as t,Q as n}from"./chunks/framework.b637c96f.js";const _=JSON.parse('{"title":"linux设置macOS时间机器server","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/linux-time-machine.md","filePath":"actions/tools/linux-time-machine.md","lastUpdated":1694368780000}'),s={name:"actions/tools/linux-time-machine.md"},i=n("",15),o=[i];function c(l,r,d,p,h,m){return e(),t("div",null,o)}const k=a(s,[["render",c]]);export{_ as __pageData,k as default};
diff --git a/assets/actions_tools_markdown-syntax.md.49464579.js b/assets/actions_tools_markdown-syntax.md.49464579.js
new file mode 100644
index 000000000..3c7def509
--- /dev/null
+++ b/assets/actions_tools_markdown-syntax.md.49464579.js
@@ -0,0 +1,99 @@
+import{_ as s,o as a,c as n,Q as e}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"Markdown基础语法","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/markdown-syntax.md","filePath":"actions/tools/markdown-syntax.md","lastUpdated":1694368780000}'),l={name:"actions/tools/markdown-syntax.md"},p=e(`Markdown基础语法 Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档。
Markdown 语言在 2004 由约翰·格鲁伯(英语:John Gruber)创建。
Markdown 编写的文档可以导出 HTML 、Word、图像、PDF、Epub 等多种格式的文档。
Markdown 编写的文档后缀为 .md, .markdown。
一、标题 示例:
txt # 一级标题
+## 二级标题
+### 三级标题
+#### 四级标题
+##### 五级标题
+###### 六级标题
# 一级标题
+## 二级标题
+### 三级标题
+#### 四级标题
+##### 五级标题
+###### 六级标题
二、字体 示例:
txt **这是加粗的文字**
+*这是倾斜的文字*\`
+***这是斜体加粗的文字***
+~~这是加删除线的文字~~
**这是加粗的文字**
+*这是倾斜的文字*\`
+***这是斜体加粗的文字***
+~~这是加删除线的文字~~
效果: 这是加粗的文字 这是倾斜的文字 \` 这是斜体加粗的文字 这是加删除线的文字
三、引用 只需要在你希望引用的文字前面加上 >
就好,例如:
效果如下:
这是一条引用
引用还可以进行多级嵌套
txt > 这是一条引用
+>> 这是一条引用
+>>> 这是一条引用
> 这是一条引用
+>> 这是一条引用
+>>> 这是一条引用
效果:
这是一条引用
这是一条引用
这是一条引用
四、分割线 三个或者三个以上的 - 或者 * 都可以。
txt ---
+----
+***
+*****
---
+----
+***
+*****
五、图片 语法:
txt ![图片alt](图片地址 ''图片title'')
+
+图片alt就是显示在图片下面的文字,相当于对图片内容的解释。
+图片title是图片的标题,当鼠标移到图片上时显示的内容。title可加可不加
![图片alt](图片地址 ''图片title'')
+
+图片alt就是显示在图片下面的文字,相当于对图片内容的解释。
+图片title是图片的标题,当鼠标移到图片上时显示的内容。title可加可不加
示例:
txt ![示例图片alt](http://io.storyxc.com/images/MegellanicCloud_ZH-CN5132305226_1920x1080.jpg '示例图片title')
![示例图片alt](http://io.storyxc.com/images/MegellanicCloud_ZH-CN5132305226_1920x1080.jpg '示例图片title')
效果:
六、超链接 语法:
txt [超链接名](超链接地址 "超链接title")
+title可加可不加
[超链接名](超链接地址 "超链接title")
+title可加可不加
示例:
txt [故事的博客](https://www.storyxc.com "故事的博客")
[故事的博客](https://www.storyxc.com "故事的博客")
效果 故事的博客
七、列表 无序列表 语法: 无序列表用 - + * 任何一种都可以 示例:
txt - 列表1
++ 列表2
+* 列表3
- 列表1
++ 列表2
+* 列表3
效果
有序列表 语法: 数字加点 示例:
txt 1. 111
+2. 222
+3. 333
1. 111
+2. 222
+3. 333
效果:
111 222 333 列表嵌套
上一级和下一级之间tab即可
八、表格 语法:
txt 表头|表头|表头
+---|:--:|---:
+内容|内容|内容
+内容|内容|内容
+
+第二行分割表头和内容。
+- 有一个就行,为了对齐,多加了几个
+文字默认居左
+-两边加:表示文字居中
+-右边加:表示文字居右
+注:原生的语法两边都要用 | 包起来。此处省略
+
+姓名|技能|排行
+--|:--:|--:
+刘备|哭|大哥
+关羽|打|二哥
+张飞|骂|三弟
表头|表头|表头
+---|:--:|---:
+内容|内容|内容
+内容|内容|内容
+
+第二行分割表头和内容。
+- 有一个就行,为了对齐,多加了几个
+文字默认居左
+-两边加:表示文字居中
+-右边加:表示文字居右
+注:原生的语法两边都要用 | 包起来。此处省略
+
+姓名|技能|排行
+--|:--:|--:
+刘备|哭|大哥
+关羽|打|二哥
+张飞|骂|三弟
效果:
九、代码块 示例:
效果: 这是一行代码
txt (\`\`\`)语言名
+ 代码
+(\`\`\`)
(\`\`\`)语言名
+ 代码
+(\`\`\`)
示例:以java为例
txt (\`\`\`)java
+ public class HelloWorld{
+ public static void main(Stringargs[]){
+ System.out.println("Hello World!");
+ }
+ }
+(\`\`\`)
(\`\`\`)java
+ public class HelloWorld{
+ public static void main(Stringargs[]){
+ System.out.println("Hello World!");
+ }
+ }
+(\`\`\`)
效果
java public class HelloWorld {
+ public static void main (String args []){
+ System.out. println ( "Hello World!" );
+ }
+}
public class HelloWorld {
+ public static void main (String args []){
+ System.out. println ( "Hello World!" );
+ }
+}
`,71),t=[p];function o(c,i,r,d,y,h){return a(),n("div",null,t)}const b=s(l,[["render",o]]);export{u as __pageData,b as default};
diff --git a/assets/actions_tools_markdown-syntax.md.49464579.lean.js b/assets/actions_tools_markdown-syntax.md.49464579.lean.js
new file mode 100644
index 000000000..1c269b10f
--- /dev/null
+++ b/assets/actions_tools_markdown-syntax.md.49464579.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as e}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"Markdown基础语法","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/markdown-syntax.md","filePath":"actions/tools/markdown-syntax.md","lastUpdated":1694368780000}'),l={name:"actions/tools/markdown-syntax.md"},p=e("",71),t=[p];function o(c,i,r,d,y,h){return a(),n("div",null,t)}const b=s(l,[["render",o]]);export{u as __pageData,b as default};
diff --git a/assets/actions_tools_sketch.md.349c9ded.js b/assets/actions_tools_sketch.md.349c9ded.js
new file mode 100644
index 000000000..f3ab2193f
--- /dev/null
+++ b/assets/actions_tools_sketch.md.349c9ded.js
@@ -0,0 +1 @@
+import{_ as e,o,c as a,Q as l}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"Sketch","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/sketch.md","filePath":"actions/tools/sketch.md","lastUpdated":1694368780000}'),c={name:"actions/tools/sketch.md"},i=l('Sketch 快捷键 画布 参考线ctrl+R
缩放z
/option+z
拖动画布space
锁定图层cmd+shift+l
包含框选选择拖动+option
形状工具 以上工具可以按住option
使用扩散的绘制效果
样式工具 矢量工具 文字工具 画板 切片工具 编辑工具 ctrl+cmd+m
蒙版工具
cmd+k
缩放工具
cmd+option+o
轮廓
',19),d=[i];function t(r,h,s,n,u,p){return o(),a("div",null,d)}const k=e(c,[["render",t]]);export{m as __pageData,k as default};
diff --git a/assets/actions_tools_sketch.md.349c9ded.lean.js b/assets/actions_tools_sketch.md.349c9ded.lean.js
new file mode 100644
index 000000000..0c4e399dc
--- /dev/null
+++ b/assets/actions_tools_sketch.md.349c9ded.lean.js
@@ -0,0 +1 @@
+import{_ as e,o,c as a,Q as l}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"Sketch","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/sketch.md","filePath":"actions/tools/sketch.md","lastUpdated":1694368780000}'),c={name:"actions/tools/sketch.md"},i=l("",19),d=[i];function t(r,h,s,n,u,p){return o(),a("div",null,d)}const k=e(c,[["render",t]]);export{m as __pageData,k as default};
diff --git a/assets/actions_tools_typora-picgo-qiniu.md.e45de354.js b/assets/actions_tools_typora-picgo-qiniu.md.e45de354.js
new file mode 100644
index 000000000..3d963fed3
--- /dev/null
+++ b/assets/actions_tools_typora-picgo-qiniu.md.e45de354.js
@@ -0,0 +1 @@
+import{_ as a,o,c as e,Q as t}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"Typora、PicGo、七牛云实现markdown图片自动上传图床","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/typora-picgo-qiniu.md","filePath":"actions/tools/typora-picgo-qiniu.md","lastUpdated":1694368780000}'),i={name:"actions/tools/typora-picgo-qiniu.md"},r=t('Typora、PicGo、七牛云实现markdown图片自动上传图床 背景 由于最近刚把博客迁到VuePress上来,写博客从原来自己博客项目自定义的web端Markdown编辑器换回了原来的Typora ,这个文本编辑工具虽然很好用,但是在markdown中插入图片的时候就会碰到比较烦的问题,可能随便截了个图放在桌面了,在markdown中引入的话还要先把图片放到vuepress项目的静态资源文件夹里面。原来我自己开发的博客编辑器是通过axios调用后台接口把图片传到七牛云图床上去,现在换了博客框架原来的方案不好使了。之前typora也没有这方面的支持。不过我发现typora更新之后也支持了添加图片后的事件触发。
选项还是很多样的,为typora点赞。
这里我还是选择了上传到图床,上传支持PicGo和自定义脚本,本来打算写个python脚本的,后来发现picgo这个应用也很好用,那就直接拿过来用吧。
配置picgo 通过gui配置 1.下载picgo 点击PicGo 进入仓库下载,这里选择windows版的执行程序,下载之后打开
2.申请七牛云账号配置存储空间 没有图床的可以搜一下相关教程,这里不再赘述
3.配置图床信息
选择七牛云图床、或者自己选择其他图床也许,按照自己情况来。
七牛云的AccessKey和SecretKey在七牛云的密钥管理界面 储存空间名就是自己的对象存储里面的具体某个空间名 访问网址如果你有配自定义的cdn就填cdn,如果没有那就是原生的地址,在空间概览页面,不要忘了加上协议http://或者https 😕/ 存储区域指的是华南华北这些地区对应的编号,具体对应值见下图,比如我是华南,存储区域就填z2 设置完成后不要忘记点一下设为默认图床,否则默认不会用七牛云的,其他图床同理
4.回到typora测试一下吧
先验证一下,可以看到成功了。这样再在typora中添加图片就可以看到图片会自动上传到你的图床并修改markdown中的地址了。
通过picgo-core配置 npm安装picgo-core:npm install -g picgo
配置uploader:picgo set uploader
使用uploader:picgo use uploader
配置文件地址为~/.picgo/config.json,可以手动修改
配置完成后在typora中配置自定义命令上传,命令格式 node_path picgo_path upload
例如我的是/opt/homebrew/bin/node /opt/homebrew/bin/picgo upload
验证上传选项,看到返回图片地址即可
',30),p=[r];function c(l,n,s,g,d,h){return o(),e("div",null,p)}const _=a(i,[["render",c]]);export{u as __pageData,_ as default};
diff --git a/assets/actions_tools_typora-picgo-qiniu.md.e45de354.lean.js b/assets/actions_tools_typora-picgo-qiniu.md.e45de354.lean.js
new file mode 100644
index 000000000..b3f9eab22
--- /dev/null
+++ b/assets/actions_tools_typora-picgo-qiniu.md.e45de354.lean.js
@@ -0,0 +1 @@
+import{_ as a,o,c as e,Q as t}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"Typora、PicGo、七牛云实现markdown图片自动上传图床","description":"","frontmatter":{},"headers":[],"relativePath":"actions/tools/typora-picgo-qiniu.md","filePath":"actions/tools/typora-picgo-qiniu.md","lastUpdated":1694368780000}'),i={name:"actions/tools/typora-picgo-qiniu.md"},r=t("",30),p=[r];function c(l,n,s,g,d,h){return o(),e("div",null,p)}const _=a(i,[["render",c]]);export{u as __pageData,_ as default};
diff --git a/assets/app.53da8f68.js b/assets/app.53da8f68.js
new file mode 100644
index 000000000..3184c842c
--- /dev/null
+++ b/assets/app.53da8f68.js
@@ -0,0 +1 @@
+import{s as o,a0 as p,a1 as i,a2 as u,a3 as c,a4 as l,a5 as d,a6 as f,a7 as m,a8 as h,a9 as A,U as g,d as P,u as v,j as y,y as C,aa as w,ab as _,ac as b,ad as E}from"./chunks/framework.b637c96f.js";import{t as R}from"./chunks/theme.0d739576.js";function r(e){if(e.extends){const a=r(e.extends);return{...a,...e,async enhanceApp(t){a.enhanceApp&&await a.enhanceApp(t),e.enhanceApp&&await e.enhanceApp(t)}}}return e}const s=r(R),D=P({name:"VitePressApp",setup(){const{site:e}=v();return y(()=>{C(()=>{document.documentElement.lang=e.value.lang,document.documentElement.dir=e.value.dir})}),w(),_(),b(),s.setup&&s.setup(),()=>E(s.Layout)}});async function j(){const e=S(),a=O();a.provide(i,e);const t=u(e.route);return a.provide(c,t),a.component("Content",l),a.component("ClientOnly",d),Object.defineProperties(a.config.globalProperties,{$frontmatter:{get(){return t.frontmatter.value}},$params:{get(){return t.page.value.params}}}),s.enhanceApp&&await s.enhanceApp({app:a,router:e,siteData:f}),{app:a,router:e,data:t}}function O(){return m(D)}function S(){let e=o,a;return h(t=>{let n=A(t);return n?(e&&(a=n),(e||a===n)&&(n=n.replace(/\.js$/,".lean.js")),o&&(e=!1),g(()=>import(n),[])):null},s.NotFound)}o&&j().then(({app:e,router:a,data:t})=>{a.go().then(()=>{p(a.route,t.site),e.mount("#app")})});export{j as createApp};
diff --git a/assets/chunks/VPAlgoliaSearchBox.377a8f85.js b/assets/chunks/VPAlgoliaSearchBox.377a8f85.js
new file mode 100644
index 000000000..a74f90a00
--- /dev/null
+++ b/assets/chunks/VPAlgoliaSearchBox.377a8f85.js
@@ -0,0 +1,17 @@
+import{d as so,ae as fo,K as mo,j as po,x as vo,o as ho,c as yo}from"./framework.b637c96f.js";import{u as go}from"./theme.0d739576.js";/*! @docsearch/js 3.5.2 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */function un(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),n.push.apply(n,r)}return n}function I(t){for(var e=1;e=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function se(t,e){return function(n){if(Array.isArray(n))return n}(t)||function(n,r){var o=n==null?null:typeof Symbol<"u"&&n[Symbol.iterator]||n["@@iterator"];if(o!=null){var i,a,u=[],c=!0,s=!1;try{for(o=o.call(n);!(c=(i=o.next()).done)&&(u.push(i.value),!r||u.length!==r);c=!0);}catch(l){s=!0,a=l}finally{try{c||o.return==null||o.return()}finally{if(s)throw a}}return u}}(t,e)||yr(t,e)||function(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
+In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function ft(t){return function(e){if(Array.isArray(e))return Lt(e)}(t)||function(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}(t)||yr(t)||function(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
+In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function yr(t,e){if(t){if(typeof t=="string")return Lt(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return n==="Object"&&t.constructor&&(n=t.constructor.name),n==="Map"||n==="Set"?Array.from(t):n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?Lt(t,e):void 0}}function Lt(t,e){(e==null||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n3)for(n=[n],i=3;i0?Ie(v.type,v.props,v.key,null,v.__v):v)!=null){if(v.__=n,v.__b=n.__b+1,(p=b[l])===null||p&&v.key==p.key&&v.type===p.type)b[l]=void 0;else for(m=0;m<_;m++){if((p=b[m])&&v.key==p.key&&v.type===p.type){b[m]=void 0;break}p=null}Yt(t,v,p=p||mt,o,i,a,u,c,s),d=v.__e,(m=v.ref)&&p.ref!=m&&(y||(y=[]),p.ref&&y.push(p.ref,null,v),y.push(m,v.__c||d,v)),d!=null?(h==null&&(h=d),typeof v.type=="function"&&v.__k!=null&&v.__k===p.__k?v.__d=c=wr(v,c,t):c=jr(t,v,p,b,d,c),s||n.type!=="option"?typeof n.type=="function"&&(n.__d=c):t.value=""):c&&p.__e==c&&c.parentNode!=t&&(c=We(p))}for(n.__e=h,l=_;l--;)b[l]!=null&&(typeof n.type=="function"&&b[l].__e!=null&&b[l].__e==n.__d&&(n.__d=We(r,l+1)),Ir(b[l],b[l]));if(y)for(l=0;l3)for(n=[n],i=3;i=n.__.length&&n.__.push({}),n.__[t]}function kr(t){return pe=1,Ar(xr,t)}function Ar(t,e,n){var r=Je(de++,2);return r.t=t,r.__c||(r.__=[n?n(e):xr(void 0,e),function(o){var i=r.t(r.__[0],o);r.__[0]!==i&&(r.__=[i,r.__[1]],r.__c.setState({}))}],r.__c=q),r.__}function Cr(t,e){var n=Je(de++,3);!w.__s&&Gt(n.__H,e)&&(n.__=t,n.__H=e,q.__H.__h.push(n))}function bn(t,e){var n=Je(de++,4);!w.__s&&Gt(n.__H,e)&&(n.__=t,n.__H=e,q.__h.push(n))}function Pt(t,e){var n=Je(de++,7);return Gt(n.__H,e)&&(n.__=t(),n.__H=e,n.__h=t),n.__}function Eo(){Ht.forEach(function(t){if(t.__P)try{t.__H.__h.forEach(ct),t.__H.__h.forEach(Ut),t.__H.__h=[]}catch(e){t.__H.__h=[],w.__e(e,t.__v)}}),Ht=[]}w.__b=function(t){q=null,vn&&vn(t)},w.__r=function(t){dn&&dn(t),de=0;var e=(q=t.__c).__H;e&&(e.__h.forEach(ct),e.__h.forEach(Ut),e.__h=[])},w.diffed=function(t){hn&&hn(t);var e=t.__c;e&&e.__H&&e.__H.__h.length&&(Ht.push(e)!==1&&pn===w.requestAnimationFrame||((pn=w.requestAnimationFrame)||function(n){var r,o=function(){clearTimeout(i),_n&&cancelAnimationFrame(r),setTimeout(n)},i=setTimeout(o,100);_n&&(r=requestAnimationFrame(o))})(Eo)),q=void 0},w.__c=function(t,e){e.some(function(n){try{n.__h.forEach(ct),n.__h=n.__h.filter(function(r){return!r.__||Ut(r)})}catch(r){e.some(function(o){o.__h&&(o.__h=[])}),e=[],w.__e(r,n.__v)}}),yn&&yn(t,e)},w.unmount=function(t){gn&&gn(t);var e=t.__c;if(e&&e.__H)try{e.__H.__.forEach(ct)}catch(n){w.__e(n,e.__v)}};var _n=typeof requestAnimationFrame=="function";function ct(t){var e=q;typeof t.__c=="function"&&t.__c(),q=e}function Ut(t){var e=q;t.__c=t.__(),q=e}function Gt(t,e){return!t||t.length!==e.length||e.some(function(n,r){return n!==t[r]})}function xr(t,e){return typeof e=="function"?e(t):e}function Nr(t,e){for(var n in e)t[n]=e[n];return t}function Ft(t,e){for(var n in t)if(n!=="__source"&&!(n in e))return!0;for(var r in e)if(r!=="__source"&&t[r]!==e[r])return!0;return!1}function Bt(t){this.props=t}(Bt.prototype=new K).isPureReactComponent=!0,Bt.prototype.shouldComponentUpdate=function(t,e){return Ft(this.props,t)||Ft(this.state,e)};var On=w.__b;w.__b=function(t){t.type&&t.type.__f&&t.ref&&(t.props.ref=t.ref,t.ref=null),On&&On(t)};var Po=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911,Sn=function(t,e){return t==null?null:$($(t).map(e))},Io={map:Sn,forEach:Sn,count:function(t){return t?$(t).length:0},only:function(t){var e=$(t);if(e.length!==1)throw"Children.only";return e[0]},toArray:$},Do=w.__e;function ut(){this.__u=0,this.t=null,this.__b=null}function Tr(t){var e=t.__.__c;return e&&e.__e&&e.__e(t)}function je(){this.u=null,this.o=null}w.__e=function(t,e,n){if(t.then){for(var r,o=e;o=o.__;)if((r=o.__c)&&r.__c)return e.__e==null&&(e.__e=n.__e,e.__k=n.__k),r.__c(t,e)}Do(t,e,n)},(ut.prototype=new K).__c=function(t,e){var n=e.__c,r=this;r.t==null&&(r.t=[]),r.t.push(n);var o=Tr(r.__v),i=!1,a=function(){i||(i=!0,n.componentWillUnmount=n.__c,o?o(u):u())};n.__c=n.componentWillUnmount,n.componentWillUnmount=function(){a(),n.__c&&n.__c()};var u=function(){if(!--r.__u){if(r.state.__e){var s=r.state.__e;r.__v.__k[0]=function m(p,v,d){return p&&(p.__v=null,p.__k=p.__k&&p.__k.map(function(h){return m(h,v,d)}),p.__c&&p.__c.__P===v&&(p.__e&&d.insertBefore(p.__e,p.__d),p.__c.__e=!0,p.__c.__P=d)),p}(s,s.__c.__P,s.__c.__O)}var l;for(r.setState({__e:r.__b=null});l=r.t.pop();)l.forceUpdate()}},c=e.__h===!0;r.__u++||c||r.setState({__e:r.__b=r.__v.__k[0]}),t.then(a,a)},ut.prototype.componentWillUnmount=function(){this.t=[]},ut.prototype.render=function(t,e){if(this.__b){if(this.__v.__k){var n=document.createElement("div"),r=this.__v.__k[0].__c;this.__v.__k[0]=function i(a,u,c){return a&&(a.__c&&a.__c.__H&&(a.__c.__H.__.forEach(function(s){typeof s.__c=="function"&&s.__c()}),a.__c.__H=null),(a=Nr({},a)).__c!=null&&(a.__c.__P===c&&(a.__c.__P=u),a.__c=null),a.__k=a.__k&&a.__k.map(function(s){return i(s,u,c)})),a}(this.__b,n,r.__O=r.__P)}this.__b=null}var o=e.__e&&W(X,null,t.fallback);return o&&(o.__h=null),[W(X,null,e.__e?null:t.children),o]};var wn=function(t,e,n){if(++n[1]===n[0]&&t.o.delete(e),t.props.revealOrder&&(t.props.revealOrder[0]!=="t"||!t.o.size))for(n=t.u;n;){for(;n.length>3;)n.pop()();if(n[1]>>1,1),e.i.removeChild(r)}}),Ke(W(ko,{context:e.context},t.__v),e.l)):e.l&&e.componentWillUnmount()}function Rr(t,e){return W(Ao,{__v:t,i:e})}(je.prototype=new K).__e=function(t){var e=this,n=Tr(e.__v),r=e.o.get(t);return r[0]++,function(o){var i=function(){e.props.revealOrder?(r.push(o),wn(e,t,r)):o()};n?n(i):i()}},je.prototype.render=function(t){this.u=null,this.o=new Map;var e=$(t.children);t.revealOrder&&t.revealOrder[0]==="b"&&e.reverse();for(var n=e.length;n--;)this.o.set(e[n],this.u=[1,0,this.u]);return t.children},je.prototype.componentDidUpdate=je.prototype.componentDidMount=function(){var t=this;this.o.forEach(function(e,n){wn(t,n,e)})};var qr=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.element")||60103,Co=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,xo=function(t){return(typeof Symbol<"u"&&Ve(Symbol())=="symbol"?/fil|che|rad/i:/fil|che|ra/i).test(t)};function Lr(t,e,n){return e.__k==null&&(e.textContent=""),Ke(t,e),typeof n=="function"&&n(),t?t.__c:null}K.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(t){Object.defineProperty(K.prototype,t,{configurable:!0,get:function(){return this["UNSAFE_"+t]},set:function(e){Object.defineProperty(this,t,{configurable:!0,writable:!0,value:e})}})});var jn=w.event;function No(){}function To(){return this.cancelBubble}function Ro(){return this.defaultPrevented}w.event=function(t){return jn&&(t=jn(t)),t.persist=No,t.isPropagationStopped=To,t.isDefaultPrevented=Ro,t.nativeEvent=t};var Mr,En={configurable:!0,get:function(){return this.class}},Pn=w.vnode;w.vnode=function(t){var e=t.type,n=t.props,r=n;if(typeof e=="string"){for(var o in r={},n){var i=n[o];o==="value"&&"defaultValue"in n&&i==null||(o==="defaultValue"&&"value"in n&&n.value==null?o="value":o==="download"&&i===!0?i="":/ondoubleclick/i.test(o)?o="ondblclick":/^onchange(textarea|input)/i.test(o+e)&&!xo(n.type)?o="oninput":/^on(Ani|Tra|Tou|BeforeInp)/.test(o)?o=o.toLowerCase():Co.test(o)?o=o.replace(/[A-Z0-9]/,"-$&").toLowerCase():i===null&&(i=void 0),r[o]=i)}e=="select"&&r.multiple&&Array.isArray(r.value)&&(r.value=$(n.children).forEach(function(a){a.props.selected=r.value.indexOf(a.props.value)!=-1})),e=="select"&&r.defaultValue!=null&&(r.value=$(n.children).forEach(function(a){a.props.selected=r.multiple?r.defaultValue.indexOf(a.props.value)!=-1:r.defaultValue==a.props.value})),t.props=r}e&&n.class!=n.className&&(En.enumerable="className"in n,n.className!=null&&(r.class=n.className),Object.defineProperty(r,"className",En)),t.$$typeof=qr,Pn&&Pn(t)};var In=w.__r;w.__r=function(t){In&&In(t),Mr=t.__c};var qo={ReactCurrentDispatcher:{current:{readContext:function(t){return Mr.__n[t.__c].props.value}}}};(typeof performance>"u"?"undefined":Ve(performance))=="object"&&typeof performance.now=="function"&&performance.now.bind(performance);function Dn(t){return!!t&&t.$$typeof===qr}var f={useState:kr,useReducer:Ar,useEffect:Cr,useLayoutEffect:bn,useRef:function(t){return pe=5,Pt(function(){return{current:t}},[])},useImperativeHandle:function(t,e,n){pe=6,bn(function(){typeof t=="function"?t(e()):t&&(t.current=e())},n==null?n:n.concat(t))},useMemo:Pt,useCallback:function(t,e){return pe=8,Pt(function(){return t},e)},useContext:function(t){var e=q.context[t.__c],n=Je(de++,9);return n.__c=t,e?(n.__==null&&(n.__=!0,e.sub(q)),e.props.value):t.__},useDebugValue:function(t,e){w.useDebugValue&&w.useDebugValue(e?e(t):t)},version:"16.8.0",Children:Io,render:Lr,hydrate:function(t,e,n){return Dr(t,e),typeof n=="function"&&n(),t?t.__c:null},unmountComponentAtNode:function(t){return!!t.__k&&(Ke(null,t),!0)},createPortal:Rr,createElement:W,createContext:function(t,e){var n={__c:e="__cC"+br++,__:t,Consumer:function(r,o){return r.children(o)},Provider:function(r){var o,i;return this.getChildContext||(o=[],(i={})[e]=this,this.getChildContext=function(){return i},this.shouldComponentUpdate=function(a){this.props.value!==a.value&&o.some(Mt)},this.sub=function(a){o.push(a);var u=a.componentWillUnmount;a.componentWillUnmount=function(){o.splice(o.indexOf(a),1),u&&u.call(a)}}),r.children}};return n.Provider.__=n.Consumer.contextType=n},createFactory:function(t){return W.bind(null,t)},cloneElement:function(t){return Dn(t)?jo.apply(null,arguments):t},createRef:function(){return{current:null}},Fragment:X,isValidElement:Dn,findDOMNode:function(t){return t&&(t.base||t.nodeType===1&&t)||null},Component:K,PureComponent:Bt,memo:function(t,e){function n(o){var i=this.props.ref,a=i==o.ref;return!a&&i&&(i.call?i(null):i.current=null),e?!e(this.props,o)||!a:Ft(this.props,o)}function r(o){return this.shouldComponentUpdate=n,W(t,o)}return r.displayName="Memo("+(t.displayName||t.name)+")",r.prototype.isReactComponent=!0,r.__f=!0,r},forwardRef:function(t){function e(n,r){var o=Nr({},n);return delete o.ref,t(o,(r=n.ref||r)&&(Ve(r)!="object"||"current"in r)?r:null)}return e.$$typeof=Po,e.render=e,e.prototype.isReactComponent=e.__f=!0,e.displayName="ForwardRef("+(t.displayName||t.name)+")",e},unstable_batchedUpdates:function(t,e){return t(e)},StrictMode:X,Suspense:ut,SuspenseList:je,lazy:function(t){var e,n,r;function o(i){if(e||(e=t()).then(function(a){n=a.default||a},function(a){r=a}),r)throw r;if(!n)throw e;return W(n,i)}return o.displayName="Lazy",o.__f=!0,o},__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:qo};function Lo(){return f.createElement("svg",{width:"15",height:"15",className:"DocSearch-Control-Key-Icon"},f.createElement("path",{d:"M4.505 4.496h2M5.505 5.496v5M8.216 4.496l.055 5.993M10 7.5c.333.333.5.667.5 1v2M12.326 4.5v5.996M8.384 4.496c1.674 0 2.116 0 2.116 1.5s-.442 1.5-2.116 1.5M3.205 9.303c-.09.448-.277 1.21-1.241 1.203C1 10.5.5 9.513.5 8V7c0-1.57.5-2.5 1.464-2.494.964.006 1.134.598 1.24 1.342M12.553 10.5h1.953",strokeWidth:"1.2",stroke:"currentColor",fill:"none",strokeLinecap:"square"}))}function Hr(){return f.createElement("svg",{width:"20",height:"20",className:"DocSearch-Search-Icon",viewBox:"0 0 20 20"},f.createElement("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"}))}var Mo=["translations"];function Vt(){return Vt=Object.assign||function(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=new Array(e);n=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}var Fo=f.forwardRef(function(t,e){var n=t.translations,r=n===void 0?{}:n,o=Uo(t,Mo),i=r.buttonText,a=i===void 0?"Search":i,u=r.buttonAriaLabel,c=u===void 0?"Search":u,s=Ho(kr(null),2),l=s[0],m=s[1];return Cr(function(){typeof navigator<"u"&&(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)?m("⌘"):m("Ctrl"))},[]),f.createElement("button",Vt({type:"button",className:"DocSearch DocSearch-Button","aria-label":c},o,{ref:e}),f.createElement("span",{className:"DocSearch-Button-Container"},f.createElement(Hr,null),f.createElement("span",{className:"DocSearch-Button-Placeholder"},a)),f.createElement("span",{className:"DocSearch-Button-Keys"},l!==null&&f.createElement(f.Fragment,null,f.createElement("kbd",{className:"DocSearch-Button-Key"},l==="Ctrl"?f.createElement(Lo,null):l),f.createElement("kbd",{className:"DocSearch-Button-Key"},"K"))))});function Ur(t,e){var n=void 0;return function(){for(var r=arguments.length,o=new Array(r),i=0;it.length)&&(e=t.length);for(var n=0,r=new Array(e);nt.length)&&(e=t.length);for(var n=0,r=new Array(e);n=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function Nn(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),n.push.apply(n,r)}return n}function ve(t){for(var e=1;e1&&arguments[1]!==void 0?arguments[1]:20,n=[],r=0;r=3||n===2&&r>=4||n===1&&r>=10);function i(a,u,c){if(o&&c!==void 0){var s=c[0].__autocomplete_algoliaCredentials,l={"X-Algolia-Application-Id":s.appId,"X-Algolia-API-Key":s.apiKey};t.apply(void 0,[a].concat(Ge(u),[{headers:l}]))}else t.apply(void 0,[a].concat(Ge(u)))}return{init:function(a,u){t("init",{appId:a,apiKey:u})},setUserToken:function(a){t("setUserToken",a)},clickedObjectIDsAfterSearch:function(){for(var a=arguments.length,u=new Array(a),c=0;c0&&i("clickedObjectIDsAfterSearch",Xe(u),u[0].items)},clickedObjectIDs:function(){for(var a=arguments.length,u=new Array(a),c=0;c 0&&i("clickedObjectIDs",Xe(u),u[0].items)},clickedFilters:function(){for(var a=arguments.length,u=new Array(a),c=0;c 0&&t.apply(void 0,["clickedFilters"].concat(u))},convertedObjectIDsAfterSearch:function(){for(var a=arguments.length,u=new Array(a),c=0;c 0&&i("convertedObjectIDsAfterSearch",Xe(u),u[0].items)},convertedObjectIDs:function(){for(var a=arguments.length,u=new Array(a),c=0;c 0&&i("convertedObjectIDs",Xe(u),u[0].items)},convertedFilters:function(){for(var a=arguments.length,u=new Array(a),c=0;c 0&&t.apply(void 0,["convertedFilters"].concat(u))},viewedObjectIDs:function(){for(var a=arguments.length,u=new Array(a),c=0;c 0&&u.reduce(function(s,l){var m=l.items,p=Br(l,zo);return[].concat(Ge(s),Ge($o(ve(ve({},p),{},{objectIDs:(m==null?void 0:m.map(function(v){return v.objectID}))||p.objectIDs})).map(function(v){return{items:m,payload:v}})))},[]).forEach(function(s){var l=s.items;return i("viewedObjectIDs",[s.payload],l)})},viewedFilters:function(){for(var a=arguments.length,u=new Array(a),c=0;c 0&&t.apply(void 0,["viewedFilters"].concat(u))}}}function Zo(t){var e=t.items.reduce(function(n,r){var o;return n[r.__autocomplete_indexName]=((o=n[r.__autocomplete_indexName])!==null&&o!==void 0?o:[]).concat(r),n},{});return Object.keys(e).map(function(n){return{index:n,items:e[n],algoliaSource:["autocomplete"]}})}function Dt(t){return t.objectID&&t.__autocomplete_indexName&&t.__autocomplete_queryID}function ke(t){return ke=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},ke(t)}function ie(t){return function(e){if(Array.isArray(e))return kt(e)}(t)||function(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}(t)||function(e,n){if(e){if(typeof e=="string")return kt(e,n);var r=Object.prototype.toString.call(e).slice(8,-1);if(r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set")return Array.from(e);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return kt(e,n)}}(t)||function(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
+In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function kt(t,e){(e==null||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n0&&Xo({onItemsChange:r,items:p,insights:u,state:m}))}},0);return{name:"aa.algoliaInsightsPlugin",subscribe:function(l){var m=l.setContext,p=l.onSelect,v=l.onActive;a("addAlgoliaAgent","insights-plugin"),m({algoliaInsightsPlugin:{__algoliaSearchParameters:{clickAnalytics:!0},insights:u}}),p(function(d){var h=d.item,y=d.state,b=d.event;Dt(h)&&o({state:y,event:b,insights:u,item:h,insightsEvents:[G({eventName:"Item Selected"},Cn({item:h,items:c.current}))]})}),v(function(d){var h=d.item,y=d.state,b=d.event;Dt(h)&&i({state:y,event:b,insights:u,item:h,insightsEvents:[G({eventName:"Item Active"},Cn({item:h,items:c.current}))]})})},onStateChange:function(l){var m=l.state;s({state:m})},__autocomplete_pluginOptions:t}}function lt(t,e){var n=e;return{then:function(r,o){return lt(t.then(et(r,n,t),et(o,n,t)),n)},catch:function(r){return lt(t.catch(et(r,n,t)),n)},finally:function(r){return r&&n.onCancelList.push(r),lt(t.finally(et(r&&function(){return n.onCancelList=[],r()},n,t)),n)},cancel:function(){n.isCanceled=!0;var r=n.onCancelList;n.onCancelList=[],r.forEach(function(o){o()})},isCanceled:function(){return n.isCanceled===!0}}}function Rn(t){return lt(t,{isCanceled:!1,onCancelList:[]})}function et(t,e,n){return t?function(r){return e.isCanceled?r:t(r)}:n}function qn(t,e,n,r){if(!n)return null;if(t<0&&(e===null||r!==null&&e===0))return n+t;var o=(e===null?-1:e)+t;return o<=-1||o>=n?r===null?null:0:o}function Ln(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),n.push.apply(n,r)}return n}function Mn(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=new Array(e);n0},reshape:function(i){return i.sources}},t),{},{id:(n=t.id)!==null&&n!==void 0?n:"autocomplete-".concat(Bo++),plugins:o,initialState:ae({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},t.initialState),onStateChange:function(i){var a;(a=t.onStateChange)===null||a===void 0||a.call(t,i),o.forEach(function(u){var c;return(c=u.onStateChange)===null||c===void 0?void 0:c.call(u,i)})},onSubmit:function(i){var a;(a=t.onSubmit)===null||a===void 0||a.call(t,i),o.forEach(function(u){var c;return(c=u.onSubmit)===null||c===void 0?void 0:c.call(u,i)})},onReset:function(i){var a;(a=t.onReset)===null||a===void 0||a.call(t,i),o.forEach(function(u){var c;return(c=u.onReset)===null||c===void 0?void 0:c.call(u,i)})},getSources:function(i){return Promise.all([].concat(ai(o.map(function(a){return a.getSources})),[t.getSources]).filter(Boolean).map(function(a){return function(u,c){var s=[];return Promise.resolve(u(c)).then(function(l){return Promise.all(l.filter(function(m){return!!m}).map(function(m){if(m.sourceId,s.includes(m.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(m.sourceId)," is not unique."));s.push(m.sourceId);var p={getItemInputValue:function(d){return d.state.query},getItemUrl:function(){},onSelect:function(d){(0,d.setIsOpen)(!1)},onActive:vt,onResolve:vt};Object.keys(p).forEach(function(d){p[d].__default=!0});var v=Mn(Mn({},p),m);return Promise.resolve(v)}))})}(a,i)})).then(function(a){return ze(a)}).then(function(a){return a.map(function(u){return ae(ae({},u),{},{onSelect:function(c){u.onSelect(c),e.forEach(function(s){var l;return(l=s.onSelect)===null||l===void 0?void 0:l.call(s,c)})},onActive:function(c){u.onActive(c),e.forEach(function(s){var l;return(l=s.onActive)===null||l===void 0?void 0:l.call(s,c)})},onResolve:function(c){u.onResolve(c),e.forEach(function(s){var l;return(l=s.onResolve)===null||l===void 0?void 0:l.call(s,c)})}})})})},navigator:ae({navigate:function(i){var a=i.itemUrl;r.location.assign(a)},navigateNewTab:function(i){var a=i.itemUrl,u=r.open(a,"_blank","noopener");u==null||u.focus()},navigateNewWindow:function(i){var a=i.itemUrl;r.open(a,"_blank","noopener")}},t.navigator)})}function Te(t){return Te=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Te(t)}function Bn(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),n.push.apply(n,r)}return n}function nt(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=new Array(e);n=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}var Kn,xt,ot,we=null,zn=(Kn=-1,xt=-1,ot=void 0,function(t){var e=++Kn;return Promise.resolve(t).then(function(n){return ot&&e=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function Me(t){return Me=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Me(t)}var bi=["props","refresh","store"],_i=["inputElement","formElement","panelElement"],Oi=["inputElement"],Si=["inputElement","maxLength"],wi=["sourceIndex"],ji=["sourceIndex"],Ei=["item","source","sourceIndex"];function $n(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),n.push.apply(n,r)}return n}function R(t){for(var e=1;e=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function Ii(t){var e=t.props,n=t.refresh,r=t.store,o=ne(t,bi),i=function(a,u){return u!==void 0?"".concat(a,"-").concat(u):a};return{getEnvironmentProps:function(a){var u=a.inputElement,c=a.formElement,s=a.panelElement;function l(m){!r.getState().isOpen&&r.pendingRequests.isEmpty()||m.target===u||[c,s].some(function(p){return v=p,d=m.target,v===d||v.contains(d);var v,d})===!1&&(r.dispatch("blur",null),e.debug||r.pendingRequests.cancelAll())}return R({onTouchStart:l,onMouseDown:l,onTouchMove:function(m){r.getState().isOpen!==!1&&u===e.environment.document.activeElement&&m.target!==u&&u.blur()}},ne(a,_i))},getRootProps:function(a){return R({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?"".concat(e.id,"-list"):void 0,"aria-labelledby":"".concat(e.id,"-label")},a)},getFormProps:function(a){return a.inputElement,R({action:"",noValidate:!0,role:"search",onSubmit:function(u){var c;u.preventDefault(),e.onSubmit(R({event:u,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),(c=a.inputElement)===null||c===void 0||c.blur()},onReset:function(u){var c;u.preventDefault(),e.onReset(R({event:u,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),(c=a.inputElement)===null||c===void 0||c.focus()}},ne(a,Oi))},getLabelProps:function(a){var u=a||{},c=u.sourceIndex,s=ne(u,wi);return R({htmlFor:"".concat(i(e.id,c),"-input"),id:"".concat(i(e.id,c),"-label")},s)},getInputProps:function(a){var u;function c(y){(e.openOnFocus||r.getState().query)&&le(R({event:y,props:e,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var s=a||{},l=(s.inputElement,s.maxLength),m=l===void 0?512:l,p=ne(s,Si),v=fe(r.getState()),d=function(y){return!!(y&&y.match(ni))}(((u=e.environment.navigator)===null||u===void 0?void 0:u.userAgent)||""),h=v!=null&&v.itemUrl&&!d?"go":"search";return R({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&r.getState().activeItemId!==null?"".concat(e.id,"-item-").concat(r.getState().activeItemId):void 0,"aria-controls":r.getState().isOpen?"".concat(e.id,"-list"):void 0,"aria-labelledby":"".concat(e.id,"-label"),value:r.getState().completion||r.getState().query,id:"".concat(e.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:h,spellCheck:"false",autoFocus:e.autoFocus,placeholder:e.placeholder,maxLength:m,type:"search",onChange:function(y){le(R({event:y,props:e,query:y.currentTarget.value.slice(0,m),refresh:n,store:r},o))},onKeyDown:function(y){(function(b){var _=b.event,S=b.props,O=b.refresh,g=b.store,P=gi(b,hi);if(_.key==="ArrowUp"||_.key==="ArrowDown"){var C=function(){var M=S.environment.document.getElementById("".concat(S.id,"-item-").concat(g.getState().activeItemId));M&&(M.scrollIntoViewIfNeeded?M.scrollIntoViewIfNeeded(!1):M.scrollIntoView(!1))},L=function(){var M=fe(g.getState());if(g.getState().activeItemId!==null&&M){var Ot=M.item,St=M.itemInputValue,$e=M.itemUrl,B=M.source;B.onActive(te({event:_,item:Ot,itemInputValue:St,itemUrl:$e,refresh:O,source:B,state:g.getState()},P))}};_.preventDefault(),g.getState().isOpen===!1&&(S.openOnFocus||g.getState().query)?le(te({event:_,props:S,query:g.getState().query,refresh:O,store:g},P)).then(function(){g.dispatch(_.key,{nextActiveItemId:S.defaultActiveItemId}),L(),setTimeout(C,0)}):(g.dispatch(_.key,{}),L(),C())}else if(_.key==="Escape")_.preventDefault(),g.dispatch(_.key,null),g.pendingRequests.cancelAll();else if(_.key==="Tab")g.dispatch("blur",null),g.pendingRequests.cancelAll();else if(_.key==="Enter"){if(g.getState().activeItemId===null||g.getState().collections.every(function(M){return M.items.length===0}))return void(S.debug||g.pendingRequests.cancelAll());_.preventDefault();var x=fe(g.getState()),k=x.item,N=x.itemInputValue,U=x.itemUrl,F=x.source;if(_.metaKey||_.ctrlKey)U!==void 0&&(F.onSelect(te({event:_,item:k,itemInputValue:N,itemUrl:U,refresh:O,source:F,state:g.getState()},P)),S.navigator.navigateNewTab({itemUrl:U,item:k,state:g.getState()}));else if(_.shiftKey)U!==void 0&&(F.onSelect(te({event:_,item:k,itemInputValue:N,itemUrl:U,refresh:O,source:F,state:g.getState()},P)),S.navigator.navigateNewWindow({itemUrl:U,item:k,state:g.getState()}));else if(!_.altKey){if(U!==void 0)return F.onSelect(te({event:_,item:k,itemInputValue:N,itemUrl:U,refresh:O,source:F,state:g.getState()},P)),void S.navigator.navigate({itemUrl:U,item:k,state:g.getState()});le(te({event:_,nextState:{isOpen:!1},props:S,query:N,refresh:O,store:g},P)).then(function(){F.onSelect(te({event:_,item:k,itemInputValue:N,itemUrl:U,refresh:O,source:F,state:g.getState()},P))})}}})(R({event:y,props:e,refresh:n,store:r},o))},onFocus:c,onBlur:vt,onClick:function(y){a.inputElement!==e.environment.document.activeElement||r.getState().isOpen||c(y)}},p)},getPanelProps:function(a){return R({onMouseDown:function(u){u.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},a)},getListProps:function(a){var u=a||{},c=u.sourceIndex,s=ne(u,ji);return R({role:"listbox","aria-labelledby":"".concat(i(e.id,c),"-label"),id:"".concat(i(e.id,c),"-list")},s)},getItemProps:function(a){var u=a.item,c=a.source,s=a.sourceIndex,l=ne(a,Ei);return R({id:"".concat(i(e.id,s),"-item-").concat(u.__autocomplete_id),role:"option","aria-selected":r.getState().activeItemId===u.__autocomplete_id,onMouseMove:function(m){if(u.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",u.__autocomplete_id);var p=fe(r.getState());if(r.getState().activeItemId!==null&&p){var v=p.item,d=p.itemInputValue,h=p.itemUrl,y=p.source;y.onActive(R({event:m,item:v,itemInputValue:d,itemUrl:h,refresh:n,source:y,state:r.getState()},o))}}},onMouseDown:function(m){m.preventDefault()},onClick:function(m){var p=c.getItemInputValue({item:u,state:r.getState()}),v=c.getItemUrl({item:u,state:r.getState()});(v?Promise.resolve():le(R({event:m,nextState:{isOpen:!1},props:e,query:p,refresh:n,store:r},o))).then(function(){c.onSelect(R({event:m,item:u,itemInputValue:p,itemUrl:v,refresh:n,source:c,state:r.getState()},o))})}},l)}}}function He(t){return He=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},He(t)}function Qn(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),n.push.apply(n,r)}return n}function Di(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=new Array(e);n=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function Zi(t){var e=t.translations,n=e===void 0?{}:e,r=Qi(t,Ji),o=n.noResultsText,i=o===void 0?"No results for":o,a=n.suggestedQueryText,u=a===void 0?"Try searching for":a,c=n.reportMissingResultsText,s=c===void 0?"Believe this query should return results?":c,l=n.reportMissingResultsLinkText,m=l===void 0?"Let us know.":l,p=r.state.context.searchSuggestions;return f.createElement("div",{className:"DocSearch-NoResults"},f.createElement("div",{className:"DocSearch-Screen-Icon"},f.createElement(Ki,null)),f.createElement("p",{className:"DocSearch-Title"},i,' "',f.createElement("strong",null,r.state.query),'"'),p&&p.length>0&&f.createElement("div",{className:"DocSearch-NoResults-Prefill-List"},f.createElement("p",{className:"DocSearch-Help"},u,":"),f.createElement("ul",null,p.slice(0,3).reduce(function(v,d){return[].concat($i(v),[f.createElement("li",{key:d},f.createElement("button",{className:"DocSearch-Prefill",key:d,type:"button",onClick:function(){r.setQuery(d.toLowerCase()+" "),r.refresh(),r.inputRef.current.focus()}},d))])},[]))),r.getMissingResultsUrl&&f.createElement("p",{className:"DocSearch-Help"},"".concat(s," "),f.createElement("a",{href:r.getMissingResultsUrl({query:r.state.query}),target:"_blank",rel:"noopener noreferrer"},m)))}var Yi=["hit","attribute","tagName"];function er(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),n.push.apply(n,r)}return n}function tr(t){for(var e=1;e=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function nr(t,e){return e.split(".").reduce(function(n,r){return n!=null&&n[r]?n[r]:null},t)}function ue(t){var e=t.hit,n=t.attribute,r=t.tagName;return W(r===void 0?"span":r,tr(tr({},Xi(t,Yi)),{},{dangerouslySetInnerHTML:{__html:nr(e,"_snippetResult.".concat(n,".value"))||nr(e,n)}}))}function rr(t,e){return function(n){if(Array.isArray(n))return n}(t)||function(n,r){var o=n==null?null:typeof Symbol<"u"&&n[Symbol.iterator]||n["@@iterator"];if(o!=null){var i,a,u=[],c=!0,s=!1;try{for(o=o.call(n);!(c=(i=o.next()).done)&&(u.push(i.value),!r||u.length!==r);c=!0);}catch(l){s=!0,a=l}finally{try{c||o.return==null||o.return()}finally{if(s)throw a}}return u}}(t,e)||function(n,r){if(n){if(typeof n=="string")return or(n,r);var o=Object.prototype.toString.call(n).slice(8,-1);if(o==="Object"&&n.constructor&&(o=n.constructor.name),o==="Map"||o==="Set")return Array.from(n);if(o==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o))return or(n,r)}}(t,e)||function(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
+In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function or(t,e){(e==null||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n|<\/mark>)/g,na=RegExp(zr.source);function Jr(t){var e,n,r=t;if(!r.__docsearch_parent&&!t._highlightResult)return t.hierarchy.lvl0;var o=((r.__docsearch_parent?(e=r.__docsearch_parent)===null||e===void 0||(e=e._highlightResult)===null||e===void 0||(e=e.hierarchy)===null||e===void 0?void 0:e.lvl0:(n=t._highlightResult)===null||n===void 0||(n=n.hierarchy)===null||n===void 0?void 0:n.lvl0)||{}).value;return o&&na.test(o)?o.replace(zr,""):o}function Jt(){return Jt=Object.assign||function(t){for(var e=1;e=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function aa(t){var e=t.translations,n=e===void 0?{}:e,r=ia(t,oa),o=n.recentSearchesTitle,i=o===void 0?"Recent":o,a=n.noRecentSearchesText,u=a===void 0?"No recent searches":a,c=n.saveRecentSearchButtonTitle,s=c===void 0?"Save this search":c,l=n.removeRecentSearchButtonTitle,m=l===void 0?"Remove this search from history":l,p=n.favoriteSearchesTitle,v=p===void 0?"Favorite":p,d=n.removeFavoriteSearchButtonTitle,h=d===void 0?"Remove this search from favorites":d;return r.state.status==="idle"&&r.hasCollections===!1?r.disableUserPersonalization?null:f.createElement("div",{className:"DocSearch-StartScreen"},f.createElement("p",{className:"DocSearch-Help"},u)):r.hasCollections===!1?null:f.createElement("div",{className:"DocSearch-Dropdown-Container"},f.createElement(zt,ht({},r,{title:i,collection:r.state.collections[0],renderIcon:function(){return f.createElement("div",{className:"DocSearch-Hit-icon"},f.createElement(Mi,null))},renderAction:function(y){var b=y.item,_=y.runFavoriteTransition,S=y.runDeleteTransition;return f.createElement(f.Fragment,null,f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:s,type:"submit",onClick:function(O){O.preventDefault(),O.stopPropagation(),_(function(){r.favoriteSearches.add(b),r.recentSearches.remove(b),r.refresh()})}},f.createElement(Xn,null))),f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:m,type:"submit",onClick:function(O){O.preventDefault(),O.stopPropagation(),S(function(){r.recentSearches.remove(b),r.refresh()})}},f.createElement(Kt,null))))}})),f.createElement(zt,ht({},r,{title:v,collection:r.state.collections[1],renderIcon:function(){return f.createElement("div",{className:"DocSearch-Hit-icon"},f.createElement(Xn,null))},renderAction:function(y){var b=y.item,_=y.runDeleteTransition;return f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:h,type:"submit",onClick:function(S){S.preventDefault(),S.stopPropagation(),_(function(){r.favoriteSearches.remove(b),r.refresh()})}},f.createElement(Kt,null)))}})))}var ca=["translations"];function yt(){return yt=Object.assign||function(t){for(var e=1;e=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}var la=f.memo(function(t){var e=t.translations,n=e===void 0?{}:e,r=ua(t,ca);if(r.state.status==="error")return f.createElement(zi,{translations:n==null?void 0:n.errorScreen});var o=r.state.collections.some(function(i){return i.items.length>0});return r.state.query?o===!1?f.createElement(Zi,yt({},r,{translations:n==null?void 0:n.noResultsScreen})):f.createElement(ra,r):f.createElement(aa,yt({},r,{hasCollections:o,translations:n==null?void 0:n.startScreen}))},function(t,e){return e.state.status==="loading"||e.state.status==="stalled"}),sa=["translations"];function gt(){return gt=Object.assign||function(t){for(var e=1;e=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function ma(t){var e=t.translations,n=e===void 0?{}:e,r=fa(t,sa),o=n.resetButtonTitle,i=o===void 0?"Clear the query":o,a=n.resetButtonAriaLabel,u=a===void 0?"Clear the query":a,c=n.cancelButtonText,s=c===void 0?"Cancel":c,l=n.cancelButtonAriaLabel,m=l===void 0?"Cancel":l,p=r.getFormProps({inputElement:r.inputRef.current}).onReset;return f.useEffect(function(){r.autoFocus&&r.inputRef.current&&r.inputRef.current.focus()},[r.autoFocus,r.inputRef]),f.useEffect(function(){r.isFromSelection&&r.inputRef.current&&r.inputRef.current.select()},[r.isFromSelection,r.inputRef]),f.createElement(f.Fragment,null,f.createElement("form",{className:"DocSearch-Form",onSubmit:function(v){v.preventDefault()},onReset:p},f.createElement("label",gt({className:"DocSearch-MagnifierLabel"},r.getLabelProps()),f.createElement(Hr,null)),f.createElement("div",{className:"DocSearch-LoadingIndicator"},f.createElement(Li,null)),f.createElement("input",gt({className:"DocSearch-Input",ref:r.inputRef},r.getInputProps({inputElement:r.inputRef.current,autoFocus:r.autoFocus,maxLength:64}))),f.createElement("button",{type:"reset",title:i,className:"DocSearch-Reset","aria-label":u,hidden:!r.state.query},f.createElement(Kt,null))),f.createElement("button",{className:"DocSearch-Cancel",type:"reset","aria-label":m,onClick:r.onClose},s))}var pa=["_highlightResult","_snippetResult"];function va(t,e){if(t==null)return{};var n,r,o=function(a,u){if(a==null)return{};var c,s,l={},m=Object.keys(a);for(s=0;s=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function da(t){return function(){var e="__TEST_KEY__";try{return localStorage.setItem(e,""),localStorage.removeItem(e),!0}catch{return!1}}()===!1?{setItem:function(){},getItem:function(){return[]}}:{setItem:function(e){return window.localStorage.setItem(t,JSON.stringify(e))},getItem:function(){var e=window.localStorage.getItem(t);return e?JSON.parse(e):[]}}}function cr(t){var e=t.key,n=t.limit,r=n===void 0?5:n,o=da(e),i=o.getItem().slice(0,r);return{add:function(a){var u=a,c=(u._highlightResult,u._snippetResult,va(u,pa)),s=i.findIndex(function(l){return l.objectID===c.objectID});s>-1&&i.splice(s,1),i.unshift(c),i=i.slice(0,r),o.setItem(i)},remove:function(a){i=i.filter(function(u){return u.objectID!==a.objectID}),o.setItem(i)},getAll:function(){return i}}}var ha=["facetName","facetQuery"];function ya(t){var e,n="algoliasearch-client-js-".concat(t.key),r=function(){return e===void 0&&(e=t.localStorage||window.localStorage),e},o=function(){return JSON.parse(r().getItem(n)||"{}")},i=function(u){r().setItem(n,JSON.stringify(u))},a=function(){var u=t.timeToLive?1e3*t.timeToLive:null,c=o(),s=Object.fromEntries(Object.entries(c).filter(function(m){return se(m,2)[1].timestamp!==void 0}));if(i(s),u){var l=Object.fromEntries(Object.entries(s).filter(function(m){var p=se(m,2)[1],v=new Date().getTime();return!(p.timestamp+u2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return Promise.resolve().then(function(){a();var l=JSON.stringify(u);return o()[l]}).then(function(l){return Promise.all([l?l.value:c(),l!==void 0])}).then(function(l){var m=se(l,2),p=m[0],v=m[1];return Promise.all([p,v||s.miss(p)])}).then(function(l){return se(l,1)[0]})},set:function(u,c){return Promise.resolve().then(function(){var s=o();return s[JSON.stringify(u)]={timestamp:new Date().getTime(),value:c},r().setItem(n,JSON.stringify(s)),c})},delete:function(u){return Promise.resolve().then(function(){var c=o();delete c[JSON.stringify(u)],r().setItem(n,JSON.stringify(c))})},clear:function(){return Promise.resolve().then(function(){r().removeItem(n)})}}}function Ee(t){var e=ft(t.caches),n=e.shift();return n===void 0?{get:function(r,o){var i=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return o().then(function(a){return Promise.all([a,i.miss(a)])}).then(function(a){return se(a,1)[0]})},set:function(r,o){return Promise.resolve(o)},delete:function(r){return Promise.resolve()},clear:function(){return Promise.resolve()}}:{get:function(r,o){var i=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return n.get(r,o,i).catch(function(){return Ee({caches:e}).get(r,o,i)})},set:function(r,o){return n.set(r,o).catch(function(){return Ee({caches:e}).set(r,o)})},delete:function(r){return n.delete(r).catch(function(){return Ee({caches:e}).delete(r)})},clear:function(){return n.clear().catch(function(){return Ee({caches:e}).clear()})}}}function Tt(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{serializable:!0},e={};return{get:function(n,r){var o=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}},i=JSON.stringify(n);if(i in e)return Promise.resolve(t.serializable?JSON.parse(e[i]):e[i]);var a=r(),u=o&&o.miss||function(){return Promise.resolve()};return a.then(function(c){return u(c)}).then(function(){return a})},set:function(n,r){return e[JSON.stringify(n)]=t.serializable?JSON.stringify(r):r,Promise.resolve(r)},delete:function(n){return delete e[JSON.stringify(n)],Promise.resolve()},clear:function(){return e={},Promise.resolve()}}}function ga(t){for(var e=t.length-1;e>0;e--){var n=Math.floor(Math.random()*(e+1)),r=t[e];t[e]=t[n],t[n]=r}return t}function $r(t,e){return e&&Object.keys(e).forEach(function(n){t[n]=e[n](t)}),t}function bt(t){for(var e=arguments.length,n=new Array(e>1?e-1:0),r=1;r0?r:void 0,timeout:n.timeout||e,headers:n.headers||{},queryParameters:n.queryParameters||{},cacheable:n.cacheable}}var me={Read:1,Write:2,Any:3},Qr=1,ba=2,Zr=3;function Yr(t){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Qr;return I(I({},t),{},{status:e,lastUpdate:Date.now()})}function Gr(t){return typeof t=="string"?{protocol:"https",url:t,accept:me.Any}:{protocol:t.protocol||"https",url:t.url,accept:t.accept||me.Any}}var $t="GET",_t="POST";function _a(t,e){return Promise.all(e.map(function(n){return t.get(n,function(){return Promise.resolve(Yr(n))})})).then(function(n){var r=n.filter(function(a){return function(u){return u.status===Qr||Date.now()-u.lastUpdate>12e4}(a)}),o=n.filter(function(a){return function(u){return u.status===Zr&&Date.now()-u.lastUpdate<=12e4}(a)}),i=[].concat(ft(r),ft(o));return{getTimeout:function(a,u){return(o.length===0&&a===0?1:o.length+3+a)*u},statelessHosts:i.length>0?i.map(function(a){return Gr(a)}):e}})}function lr(t,e,n,r){var o=[],i=function(p,v){if(!(p.method===$t||p.data===void 0&&v.data===void 0)){var d=Array.isArray(p.data)?p.data:I(I({},p.data),v.data);return JSON.stringify(d)}}(n,r),a=function(p,v){var d=I(I({},p.headers),v.headers),h={};return Object.keys(d).forEach(function(y){var b=d[y];h[y.toLowerCase()]=b}),h}(t,r),u=n.method,c=n.method!==$t?{}:I(I({},n.data),r.data),s=I(I(I({"x-algolia-agent":t.userAgent.value},t.queryParameters),c),r.queryParameters),l=0,m=function p(v,d){var h=v.pop();if(h===void 0)throw{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:sr(o)};var y={data:i,headers:a,method:u,url:Sa(h,n.path,s),connectTimeout:d(l,t.timeouts.connect),responseTimeout:d(l,r.timeout)},b=function(S){var O={request:y,response:S,host:h,triesLeft:v.length};return o.push(O),O},_={onSuccess:function(S){return function(O){try{return JSON.parse(O.content)}catch(g){throw function(P,C){return{name:"DeserializationError",message:P,response:C}}(g.message,O)}}(S)},onRetry:function(S){var O=b(S);return S.isTimedOut&&l++,Promise.all([t.logger.info("Retryable failure",eo(O)),t.hostsCache.set(h,Yr(h,S.isTimedOut?Zr:ba))]).then(function(){return p(v,d)})},onFail:function(S){throw b(S),function(O,g){var P=O.content,C=O.status,L=P;try{L=JSON.parse(P).message}catch{}return function(x,k,N){return{name:"ApiError",message:x,status:k,transporterStackTrace:N}}(L,C,g)}(S,sr(o))}};return t.requester.send(y).then(function(S){return function(O,g){return function(P){var C=P.status;return P.isTimedOut||function(L){var x=L.isTimedOut,k=L.status;return!x&&~~k==0}(P)||~~(C/100)!=2&&~~(C/100)!=4}(O)?g.onRetry(O):~~(O.status/100)==2?g.onSuccess(O):g.onFail(O)}(S,_)})};return _a(t.hostsCache,e).then(function(p){return m(ft(p.statelessHosts).reverse(),p.getTimeout)})}function Oa(t){var e={value:"Algolia for JavaScript (".concat(t,")"),add:function(n){var r="; ".concat(n.segment).concat(n.version!==void 0?" (".concat(n.version,")"):"");return e.value.indexOf(r)===-1&&(e.value="".concat(e.value).concat(r)),e}};return e}function Sa(t,e,n){var r=Xr(n),o="".concat(t.protocol,"://").concat(t.url,"/").concat(e.charAt(0)==="/"?e.substr(1):e);return r.length&&(o+="?".concat(r)),o}function Xr(t){return Object.keys(t).map(function(e){return bt("%s=%s",e,(n=t[e],Object.prototype.toString.call(n)==="[object Object]"||Object.prototype.toString.call(n)==="[object Array]"?JSON.stringify(t[e]):t[e]));var n}).join("&")}function sr(t){return t.map(function(e){return eo(e)})}function eo(t){var e=t.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return I(I({},t),{},{request:I(I({},t.request),{},{headers:I(I({},t.request.headers),e)})})}var wa=function(t){var e=t.appId,n=function(i,a,u){var c={"x-algolia-api-key":u,"x-algolia-application-id":a};return{headers:function(){return i===st.WithinHeaders?c:{}},queryParameters:function(){return i===st.WithinQueryParameters?c:{}}}}(t.authMode!==void 0?t.authMode:st.WithinHeaders,e,t.apiKey),r=function(i){var a=i.hostsCache,u=i.logger,c=i.requester,s=i.requestsCache,l=i.responsesCache,m=i.timeouts,p=i.userAgent,v=i.hosts,d=i.queryParameters,h={hostsCache:a,logger:u,requester:c,requestsCache:s,responsesCache:l,timeouts:m,userAgent:p,headers:i.headers,queryParameters:d,hosts:v.map(function(y){return Gr(y)}),read:function(y,b){var _=ur(b,h.timeouts.read),S=function(){return lr(h,h.hosts.filter(function(g){return(g.accept&me.Read)!=0}),y,_)};if((_.cacheable!==void 0?_.cacheable:y.cacheable)!==!0)return S();var O={request:y,mappedRequestOptions:_,transporter:{queryParameters:h.queryParameters,headers:h.headers}};return h.responsesCache.get(O,function(){return h.requestsCache.get(O,function(){return h.requestsCache.set(O,S()).then(function(g){return Promise.all([h.requestsCache.delete(O),g])},function(g){return Promise.all([h.requestsCache.delete(O),Promise.reject(g)])}).then(function(g){var P=se(g,2);return P[0],P[1]})})},{miss:function(g){return h.responsesCache.set(O,g)}})},write:function(y,b){return lr(h,h.hosts.filter(function(_){return(_.accept&me.Write)!=0}),y,ur(b,h.timeouts.write))}};return h}(I(I({hosts:[{url:"".concat(e,"-dsn.algolia.net"),accept:me.Read},{url:"".concat(e,".algolia.net"),accept:me.Write}].concat(ga([{url:"".concat(e,"-1.algolianet.com")},{url:"".concat(e,"-2.algolianet.com")},{url:"".concat(e,"-3.algolianet.com")}]))},t),{},{headers:I(I(I({},n.headers()),{"content-type":"application/x-www-form-urlencoded"}),t.headers),queryParameters:I(I({},n.queryParameters()),t.queryParameters)})),o={transporter:r,appId:e,addAlgoliaAgent:function(i,a){r.userAgent.add({segment:i,version:a})},clearCache:function(){return Promise.all([r.requestsCache.clear(),r.responsesCache.clear()]).then(function(){})}};return $r(o,t.methods)},ja=function(t){return function(e,n){return e.method===$t?t.transporter.read(e,n):t.transporter.write(e,n)}},to=function(t){return function(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r={transporter:t.transporter,appId:t.appId,indexName:e};return $r(r,n.methods)}},fr=function(t){return function(e,n){var r=e.map(function(o){return I(I({},o),{},{params:Xr(o.params||{})})});return t.transporter.read({method:_t,path:"1/indexes/*/queries",data:{requests:r},cacheable:!0},n)}},mr=function(t){return function(e,n){return Promise.all(e.map(function(r){var o=r.params,i=o.facetName,a=o.facetQuery,u=_o(o,ha);return to(t)(r.indexName,{methods:{searchForFacetValues:no}}).searchForFacetValues(i,a,I(I({},n),u))}))}},Ea=function(t){return function(e,n,r){return t.transporter.read({method:_t,path:bt("1/answers/%s/prediction",t.indexName),data:{query:e,queryLanguages:n},cacheable:!0},r)}},Pa=function(t){return function(e,n){return t.transporter.read({method:_t,path:bt("1/indexes/%s/query",t.indexName),data:{query:e},cacheable:!0},n)}},no=function(t){return function(e,n,r){return t.transporter.read({method:_t,path:bt("1/indexes/%s/facets/%s/query",t.indexName,e),data:{facetQuery:n},cacheable:!0},r)}},Ia=1,Da=2,ka=3;function ro(t,e,n){var r,o={appId:t,apiKey:e,timeouts:{connect:1,read:2,write:30},requester:{send:function(i){return new Promise(function(a){var u=new XMLHttpRequest;u.open(i.method,i.url,!0),Object.keys(i.headers).forEach(function(m){return u.setRequestHeader(m,i.headers[m])});var c,s=function(m,p){return setTimeout(function(){u.abort(),a({status:0,content:p,isTimedOut:!0})},1e3*m)},l=s(i.connectTimeout,"Connection timeout");u.onreadystatechange=function(){u.readyState>u.OPENED&&c===void 0&&(clearTimeout(l),c=s(i.responseTimeout,"Socket timeout"))},u.onerror=function(){u.status===0&&(clearTimeout(l),clearTimeout(c),a({content:u.responseText||"Network request failed",status:u.status,isTimedOut:!1}))},u.onload=function(){clearTimeout(l),clearTimeout(c),a({content:u.responseText,status:u.status,isTimedOut:!1})},u.send(i.data)})}},logger:(r=ka,{debug:function(i,a){return Ia>=r&&console.debug(i,a),Promise.resolve()},info:function(i,a){return Da>=r&&console.info(i,a),Promise.resolve()},error:function(i,a){return console.error(i,a),Promise.resolve()}}),responsesCache:Tt(),requestsCache:Tt({serializable:!1}),hostsCache:Ee({caches:[ya({key:"".concat("4.19.1","-").concat(t)}),Tt()]}),userAgent:Oa("4.19.1").add({segment:"Browser",version:"lite"}),authMode:st.WithinQueryParameters};return wa(I(I(I({},o),n),{},{methods:{search:fr,searchForFacetValues:mr,multipleQueries:fr,multipleSearchForFacetValues:mr,customRequest:ja,initIndex:function(i){return function(a){return to(i)(a,{methods:{search:Pa,searchForFacetValues:no,findAnswers:Ea}})}}}}))}ro.version="4.19.1";var Aa=["footer","searchBox"];function Be(){return Be=Object.assign||function(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=new Array(e);n=0||(l[c]=a[c]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function Ta(t){var e=t.appId,n=t.apiKey,r=t.indexName,o=t.placeholder,i=o===void 0?"Search docs":o,a=t.searchParameters,u=t.maxResultsPerGroup,c=t.onClose,s=c===void 0?ta:c,l=t.transformItems,m=l===void 0?ar:l,p=t.hitComponent,v=p===void 0?qi:p,d=t.resultsFooterComponent,h=d===void 0?function(){return null}:d,y=t.navigator,b=t.initialScrollY,_=b===void 0?0:b,S=t.transformSearchClient,O=S===void 0?ar:S,g=t.disableUserPersonalization,P=g!==void 0&&g,C=t.initialQuery,L=C===void 0?"":C,x=t.translations,k=x===void 0?{}:x,N=t.getMissingResultsUrl,U=t.insights,F=U!==void 0&&U,M=k.footer,Ot=k.searchBox,St=Na(k,Aa),$e=xa(f.useState({query:"",collections:[],completion:null,context:{},isOpen:!1,activeItemId:null,status:"idle"}),2),B=$e[0],oo=$e[1],Xt=f.useRef(null),wt=f.useRef(null),en=f.useRef(null),Qe=f.useRef(null),he=f.useRef(null),Q=f.useRef(10),tn=f.useRef(typeof window<"u"?window.getSelection().toString().slice(0,64):"").current,ee=f.useRef(L||tn).current,nn=function(j,D,T){return f.useMemo(function(){var H=ro(j,D);return H.addAlgoliaAgent("docsearch","3.5.2"),/docsearch.js \(.*\)/.test(H.transporter.userAgent.value)===!1&&H.addAlgoliaAgent("docsearch-react","3.5.2"),T(H)},[j,D,T])}(e,n,O),oe=f.useRef(cr({key:"__DOCSEARCH_FAVORITE_SEARCHES__".concat(r),limit:10})).current,ye=f.useRef(cr({key:"__DOCSEARCH_RECENT_SEARCHES__".concat(r),limit:oe.getAll().length===0?7:4})).current,ge=f.useCallback(function(j){if(!P){var D=j.type==="content"?j.__docsearch_parent:j;D&&oe.getAll().findIndex(function(T){return T.objectID===D.objectID})===-1&&ye.add(D)}},[oe,ye,P]),io=f.useCallback(function(j){if(B.context.algoliaInsightsPlugin&&j.__autocomplete_id){var D=j,T={eventName:"Item Selected",index:D.__autocomplete_indexName,items:[D],positions:[j.__autocomplete_id],queryID:D.__autocomplete_queryID};B.context.algoliaInsightsPlugin.insights.clickedObjectIDsAfterSearch(T)}},[B.context.algoliaInsightsPlugin]),be=f.useMemo(function(){return Ni({id:"docsearch",defaultActiveItemId:0,placeholder:i,openOnFocus:!0,initialState:{query:ee,context:{searchSuggestions:[]}},insights:F,navigator:y,onStateChange:function(j){oo(j.state)},getSources:function(j){var D=j.query,T=j.state,H=j.setContext,Z=j.setStatus;if(!D)return P?[]:[{sourceId:"recentSearches",onSelect:function(A){var V=A.item,_e=A.event;ge(V),at(_e)||s()},getItemUrl:function(A){return A.item.url},getItems:function(){return ye.getAll()}},{sourceId:"favoriteSearches",onSelect:function(A){var V=A.item,_e=A.event;ge(V),at(_e)||s()},getItemUrl:function(A){return A.item.url},getItems:function(){return oe.getAll()}}];var Y=!!F;return nn.search([{query:D,indexName:r,params:Rt({attributesToRetrieve:["hierarchy.lvl0","hierarchy.lvl1","hierarchy.lvl2","hierarchy.lvl3","hierarchy.lvl4","hierarchy.lvl5","hierarchy.lvl6","content","type","url"],attributesToSnippet:["hierarchy.lvl1:".concat(Q.current),"hierarchy.lvl2:".concat(Q.current),"hierarchy.lvl3:".concat(Q.current),"hierarchy.lvl4:".concat(Q.current),"hierarchy.lvl5:".concat(Q.current),"hierarchy.lvl6:".concat(Q.current),"content:".concat(Q.current)],snippetEllipsisText:"…",highlightPreTag:"",highlightPostTag:" ",hitsPerPage:20,clickAnalytics:Y},a)}]).catch(function(A){throw A.name==="RetryError"&&Z("error"),A}).then(function(A){var V=A.results[0],_e=V.hits,uo=V.nbHits,jt=ir(_e,function(Et){return Jr(Et)},u);T.context.searchSuggestions.length0&&(rn(),he.current&&he.current.focus())},[ee,rn]),f.useEffect(function(){function j(){if(wt.current){var D=.01*window.innerHeight;wt.current.style.setProperty("--docsearch-vh","".concat(D,"px"))}}return j(),window.addEventListener("resize",j),function(){window.removeEventListener("resize",j)}},[]),f.createElement("div",Be({ref:Xt},co({"aria-expanded":!0}),{className:["DocSearch","DocSearch-Container",B.status==="stalled"&&"DocSearch-Container--Stalled",B.status==="error"&&"DocSearch-Container--Errored"].filter(Boolean).join(" "),role:"button",tabIndex:0,onMouseDown:function(j){j.target===j.currentTarget&&s()}}),f.createElement("div",{className:"DocSearch-Modal",ref:wt},f.createElement("header",{className:"DocSearch-SearchBar",ref:en},f.createElement(ma,Be({},be,{state:B,autoFocus:ee.length===0,inputRef:he,isFromSelection:!!ee&&ee===tn,translations:Ot,onClose:s}))),f.createElement("div",{className:"DocSearch-Dropdown",ref:Qe},f.createElement(la,Be({},be,{indexName:r,state:B,hitComponent:v,resultsFooterComponent:h,disableUserPersonalization:P,recentSearches:ye,favoriteSearches:oe,inputRef:he,translations:St,getMissingResultsUrl:N,onItemClick:function(j,D){io(j),ge(j),at(D)||s()}}))),f.createElement("footer",{className:"DocSearch-Footer"},f.createElement(Ri,{translations:M}))))}function Qt(){return Qt=Object.assign||function(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=new Array(e);n1&&arguments[1]!==void 0?arguments[1]:window;return typeof e=="string"?n.document.querySelector(e):e}(t.container,t.environment))}const La={id:"docsearch"},Ua=so({__name:"VPAlgoliaSearchBox",props:{algolia:{}},setup(t){const e=t,n=fo(),r=mo(),{site:o,localeIndex:i,lang:a}=go();po(u),vo(i,u);function u(){var v,d;const l={...e.algolia,...(v=e.algolia.locales)==null?void 0:v[i.value]},m=((d=l.searchParameters)==null?void 0:d.facetFilters)??[],p=[...(Array.isArray(m)?m:[m]).filter(h=>!h.startsWith("lang:")),`lang:${a.value}`];c({...l,searchParameters:{...l.searchParameters,facetFilters:p}})}function c(l){const m=Object.assign({},l,{container:"#docsearch",navigator:{navigate({itemUrl:p}){const{pathname:v}=new URL(window.location.origin+p);r.path===v?window.location.assign(window.location.origin+p):n.go(p)}},transformItems(p){return p.map(v=>Object.assign({},v,{url:s(v.url)}))},hitComponent({hit:p,children:v}){return{__v:null,type:"a",ref:void 0,constructor:void 0,key:void 0,props:{href:p.url,children:v}}}});qa(m)}function s(l){const{pathname:m,hash:p}=new URL(l);return m.replace(/\.html$/,o.value.cleanUrls?"":".html")+p}return(l,m)=>(ho(),yo("div",La))}});export{Ua as default};
diff --git a/assets/chunks/framework.b637c96f.js b/assets/chunks/framework.b637c96f.js
new file mode 100644
index 000000000..076c6eac0
--- /dev/null
+++ b/assets/chunks/framework.b637c96f.js
@@ -0,0 +1,2 @@
+function rr(e,t){const n=Object.create(null),r=e.split(",");for(let s=0;s!!n[s.toLowerCase()]:s=>!!n[s]}const te={},dt=[],Re=()=>{},qo=()=>!1,zo=/^on[^a-z]/,Bt=e=>zo.test(e),sr=e=>e.startsWith("onUpdate:"),ie=Object.assign,or=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Yo=Object.prototype.hasOwnProperty,z=(e,t)=>Yo.call(e,t),D=Array.isArray,ht=e=>mn(e)==="[object Map]",Ss=e=>mn(e)==="[object Set]",K=e=>typeof e=="function",se=e=>typeof e=="string",ir=e=>typeof e=="symbol",ee=e=>e!==null&&typeof e=="object",Os=e=>ee(e)&&K(e.then)&&K(e.catch),Rs=Object.prototype.toString,mn=e=>Rs.call(e),Jo=e=>mn(e).slice(8,-1),Ps=e=>mn(e)==="[object Object]",lr=e=>se(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Pt=rr(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),_n=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Xo=/-(\w)/g,Le=_n(e=>e.replace(Xo,(t,n)=>n?n.toUpperCase():"")),Qo=/\B([A-Z])/g,lt=_n(e=>e.replace(Qo,"-$1").toLowerCase()),yn=_n(e=>e.charAt(0).toUpperCase()+e.slice(1)),nn=_n(e=>e?`on${yn(e)}`:""),Nt=(e,t)=>!Object.is(e,t),Mn=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Zo=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Go=e=>{const t=se(e)?Number(e):NaN;return isNaN(t)?e:t};let Mr;const Wn=()=>Mr||(Mr=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function cr(e){if(D(e)){const t={};for(let n=0;n{if(n){const r=n.split(ti);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function ar(e){let t="";if(se(e))t=e;else if(D(e))for(let n=0;nse(e)?e:e==null?"":D(e)||ee(e)&&(e.toString===Rs||!K(e.toString))?JSON.stringify(e,Is,2):String(e),Is=(e,t)=>t&&t.__v_isRef?Is(e,t.value):ht(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[r,s])=>(n[`${r} =>`]=s,n),{})}:Ss(t)?{[`Set(${t.size})`]:[...t.values()]}:ee(t)&&!D(t)&&!Ps(t)?String(t):t;let be;class ii{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=be,!t&&be&&(this.index=(be.scopes||(be.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=be;try{return be=this,t()}finally{be=n}}}on(){be=this}off(){be=this.parent}stop(t){if(this._active){let n,r;for(n=0,r=this.effects.length;n{const t=new Set(e);return t.w=0,t.n=0,t},Ls=e=>(e.w&ze)>0,Ns=e=>(e.n&ze)>0,ai=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let r=0;r{(d==="length"||d>=c)&&l.push(u)})}else switch(n!==void 0&&l.push(i.get(n)),t){case"add":D(e)?lr(n)&&l.push(i.get("length")):(l.push(i.get(st)),ht(e)&&l.push(i.get(qn)));break;case"delete":D(e)||(l.push(i.get(st)),ht(e)&&l.push(i.get(qn)));break;case"set":ht(e)&&l.push(i.get(st));break}if(l.length===1)l[0]&&zn(l[0]);else{const c=[];for(const u of l)u&&c.push(...u);zn(ur(c))}}function zn(e,t){const n=D(e)?e:[...e];for(const r of n)r.computed&&Nr(r);for(const r of n)r.computed||Nr(r)}function Nr(e,t){(e!==Se||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}function fi(e,t){var n;return(n=ln.get(e))==null?void 0:n.get(t)}const di=rr("__proto__,__v_isRef,__isVue"),js=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(ir)),hi=dr(),pi=dr(!1,!0),gi=dr(!0),$r=mi();function mi(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const r=Y(this);for(let o=0,i=this.length;o{e[t]=function(...n){Et();const r=Y(this)[t].apply(this,n);return xt(),r}}),e}function _i(e){const t=Y(this);return me(t,"has",e),t.hasOwnProperty(e)}function dr(e=!1,t=!1){return function(r,s,o){if(s==="__v_isReactive")return!e;if(s==="__v_isReadonly")return e;if(s==="__v_isShallow")return t;if(s==="__v_raw"&&o===(e?t?Mi:Ks:t?ks:Us).get(r))return r;const i=D(r);if(!e){if(i&&z($r,s))return Reflect.get($r,s,o);if(s==="hasOwnProperty")return _i}const l=Reflect.get(r,s,o);return(ir(s)?js.has(s):di(s))||(e||me(r,"get",s),t)?l:ce(l)?i&&lr(s)?l:l.value:ee(l)?e?wn(l):vn(l):l}}const yi=Ds(),bi=Ds(!0);function Ds(e=!1){return function(n,r,s,o){let i=n[r];if(yt(i)&&ce(i)&&!ce(s))return!1;if(!e&&(!cn(s)&&!yt(s)&&(i=Y(i),s=Y(s)),!D(n)&&ce(i)&&!ce(s)))return i.value=s,!0;const l=D(n)&&lr(r)?Number(r)e,bn=e=>Reflect.getPrototypeOf(e);function Vt(e,t,n=!1,r=!1){e=e.__v_raw;const s=Y(e),o=Y(t);n||(t!==o&&me(s,"get",t),me(s,"get",o));const{has:i}=bn(s),l=r?hr:n?mr:$t;if(i.call(s,t))return l(e.get(t));if(i.call(s,o))return l(e.get(o));e!==s&&e.get(t)}function qt(e,t=!1){const n=this.__v_raw,r=Y(n),s=Y(e);return t||(e!==s&&me(r,"has",e),me(r,"has",s)),e===s?n.has(e):n.has(e)||n.has(s)}function zt(e,t=!1){return e=e.__v_raw,!t&&me(Y(e),"iterate",st),Reflect.get(e,"size",e)}function Hr(e){e=Y(e);const t=Y(this);return bn(t).has.call(t,e)||(t.add(e),He(t,"add",e,e)),this}function jr(e,t){t=Y(t);const n=Y(this),{has:r,get:s}=bn(n);let o=r.call(n,e);o||(e=Y(e),o=r.call(n,e));const i=s.call(n,e);return n.set(e,t),o?Nt(t,i)&&He(n,"set",e,t):He(n,"add",e,t),this}function Dr(e){const t=Y(this),{has:n,get:r}=bn(t);let s=n.call(t,e);s||(e=Y(e),s=n.call(t,e)),r&&r.call(t,e);const o=t.delete(e);return s&&He(t,"delete",e,void 0),o}function Br(){const e=Y(this),t=e.size!==0,n=e.clear();return t&&He(e,"clear",void 0,void 0),n}function Yt(e,t){return function(r,s){const o=this,i=o.__v_raw,l=Y(i),c=t?hr:e?mr:$t;return!e&&me(l,"iterate",st),i.forEach((u,d)=>r.call(s,c(u),c(d),o))}}function Jt(e,t,n){return function(...r){const s=this.__v_raw,o=Y(s),i=ht(o),l=e==="entries"||e===Symbol.iterator&&i,c=e==="keys"&&i,u=s[e](...r),d=n?hr:t?mr:$t;return!t&&me(o,"iterate",c?qn:st),{next(){const{value:h,done:y}=u.next();return y?{value:h,done:y}:{value:l?[d(h[0]),d(h[1])]:d(h),done:y}},[Symbol.iterator](){return this}}}}function De(e){return function(...t){return e==="delete"?!1:this}}function Ti(){const e={get(o){return Vt(this,o)},get size(){return zt(this)},has:qt,add:Hr,set:jr,delete:Dr,clear:Br,forEach:Yt(!1,!1)},t={get(o){return Vt(this,o,!1,!0)},get size(){return zt(this)},has:qt,add:Hr,set:jr,delete:Dr,clear:Br,forEach:Yt(!1,!0)},n={get(o){return Vt(this,o,!0)},get size(){return zt(this,!0)},has(o){return qt.call(this,o,!0)},add:De("add"),set:De("set"),delete:De("delete"),clear:De("clear"),forEach:Yt(!0,!1)},r={get(o){return Vt(this,o,!0,!0)},get size(){return zt(this,!0)},has(o){return qt.call(this,o,!0)},add:De("add"),set:De("set"),delete:De("delete"),clear:De("clear"),forEach:Yt(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(o=>{e[o]=Jt(o,!1,!1),n[o]=Jt(o,!0,!1),t[o]=Jt(o,!1,!0),r[o]=Jt(o,!0,!0)}),[e,n,t,r]}const[Ai,Si,Oi,Ri]=Ti();function pr(e,t){const n=t?e?Ri:Oi:e?Si:Ai;return(r,s,o)=>s==="__v_isReactive"?!e:s==="__v_isReadonly"?e:s==="__v_raw"?r:Reflect.get(z(n,s)&&s in r?n:r,s,o)}const Pi={get:pr(!1,!1)},Fi={get:pr(!1,!0)},Ii={get:pr(!0,!1)},Us=new WeakMap,ks=new WeakMap,Ks=new WeakMap,Mi=new WeakMap;function Li(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Ni(e){return e.__v_skip||!Object.isExtensible(e)?0:Li(Jo(e))}function vn(e){return yt(e)?e:gr(e,!1,Bs,Pi,Us)}function $i(e){return gr(e,!1,xi,Fi,ks)}function wn(e){return gr(e,!0,Ei,Ii,Ks)}function gr(e,t,n,r,s){if(!ee(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const o=s.get(e);if(o)return o;const i=Ni(e);if(i===0)return e;const l=new Proxy(e,i===2?r:n);return s.set(e,l),l}function pt(e){return yt(e)?pt(e.__v_raw):!!(e&&e.__v_isReactive)}function yt(e){return!!(e&&e.__v_isReadonly)}function cn(e){return!!(e&&e.__v_isShallow)}function Ws(e){return pt(e)||yt(e)}function Y(e){const t=e&&e.__v_raw;return t?Y(t):e}function Ft(e){return on(e,"__v_skip",!0),e}const $t=e=>ee(e)?vn(e):e,mr=e=>ee(e)?wn(e):e;function _r(e){We&&Se&&(e=Y(e),Hs(e.dep||(e.dep=ur())))}function yr(e,t){e=Y(e);const n=e.dep;n&&zn(n)}function ce(e){return!!(e&&e.__v_isRef===!0)}function fe(e){return qs(e,!1)}function Vs(e){return qs(e,!0)}function qs(e,t){return ce(e)?e:new Hi(e,t)}class Hi{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:Y(t),this._value=n?t:$t(t)}get value(){return _r(this),this._value}set value(t){const n=this.__v_isShallow||cn(t)||yt(t);t=n?t:Y(t),Nt(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:$t(t),yr(this))}}function zs(e){return ce(e)?e.value:e}const ji={get:(e,t,n)=>zs(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const s=e[t];return ce(s)&&!ce(n)?(s.value=n,!0):Reflect.set(e,t,n,r)}};function Ys(e){return pt(e)?e:new Proxy(e,ji)}class Di{constructor(t){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:r}=t(()=>_r(this),()=>yr(this));this._get=n,this._set=r}get value(){return this._get()}set value(t){this._set(t)}}function Bi(e){return new Di(e)}class Ui{constructor(t,n,r){this._object=t,this._key=n,this._defaultValue=r,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return fi(Y(this._object),this._key)}}class ki{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function Ki(e,t,n){return ce(e)?e:K(e)?new ki(e):ee(e)&&arguments.length>1?Wi(e,t,n):fe(e)}function Wi(e,t,n){const r=e[t];return ce(r)?r:new Ui(e,t,n)}class Vi{constructor(t,n,r,s){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this._dirty=!0,this.effect=new fr(t,()=>{this._dirty||(this._dirty=!0,yr(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!s,this.__v_isReadonly=r}get value(){const t=Y(this);return _r(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}function qi(e,t,n=!1){let r,s;const o=K(e);return o?(r=e,s=Re):(r=e.get,s=e.set),new Vi(r,s,o||!s,n)}function Ve(e,t,n,r){let s;try{s=r?e(...r):e()}catch(o){Ut(o,t,n)}return s}function xe(e,t,n,r){if(K(e)){const o=Ve(e,t,n,r);return o&&Os(o)&&o.catch(i=>{Ut(i,t,n)}),o}const s=[];for(let o=0;o>>1;jt(ue[r])Me&&ue.splice(t,1)}function Xi(e){D(e)?gt.push(...e):(!$e||!$e.includes(e,e.allowRecurse?et+1:et))&>.push(e),Xs()}function Ur(e,t=Ht?Me+1:0){for(;tjt(n)-jt(r)),et=0;et<$e.length;et++)$e[et]();$e=null,et=0}}const jt=e=>e.id==null?1/0:e.id,Qi=(e,t)=>{const n=jt(e)-jt(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function Qs(e){Yn=!1,Ht=!0,ue.sort(Qi);const t=Re;try{for(Me=0;Mese(C)?C.trim():C)),h&&(s=n.map(Zo))}let l,c=r[l=nn(t)]||r[l=nn(Le(t))];!c&&o&&(c=r[l=nn(lt(t))]),c&&xe(c,e,6,s);const u=r[l+"Once"];if(u){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,xe(u,e,6,s)}}function Zs(e,t,n=!1){const r=t.emitsCache,s=r.get(e);if(s!==void 0)return s;const o=e.emits;let i={},l=!1;if(!K(e)){const c=u=>{const d=Zs(u,t,!0);d&&(l=!0,ie(i,d))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!o&&!l?(ee(e)&&r.set(e,null),null):(D(o)?o.forEach(c=>i[c]=null):ie(i,o),ee(e)&&r.set(e,i),i)}function xn(e,t){return!e||!Bt(t)?!1:(t=t.slice(2).replace(/Once$/,""),z(e,t[0].toLowerCase()+t.slice(1))||z(e,lt(t))||z(e,t))}let de=null,Tn=null;function un(e){const t=de;return de=e,Tn=e&&e.type.__scopeId||null,t}function va(e){Tn=e}function wa(){Tn=null}function Gi(e,t=de,n){if(!t||e._n)return e;const r=(...s)=>{r._d&&Gr(-1);const o=un(t);let i;try{i=e(...s)}finally{un(o),r._d&&Gr(1)}return i};return r._n=!0,r._c=!0,r._d=!0,r}function Ln(e){const{type:t,vnode:n,proxy:r,withProxy:s,props:o,propsOptions:[i],slots:l,attrs:c,emit:u,render:d,renderCache:h,data:y,setupState:C,ctx:x,inheritAttrs:O}=e;let H,g;const m=un(e);try{if(n.shapeFlag&4){const A=s||r;H=Ae(d.call(A,A,h,o,C,y,x)),g=c}else{const A=t;H=Ae(A.length>1?A(o,{attrs:c,slots:l,emit:u}):A(o,null)),g=t.props?c:el(c)}}catch(A){Lt.length=0,Ut(A,e,1),H=re(ve)}let j=H;if(g&&O!==!1){const A=Object.keys(g),{shapeFlag:B}=j;A.length&&B&7&&(i&&A.some(sr)&&(g=tl(g,i)),j=Ye(j,g))}return n.dirs&&(j=Ye(j),j.dirs=j.dirs?j.dirs.concat(n.dirs):n.dirs),n.transition&&(j.transition=n.transition),H=j,un(m),H}const el=e=>{let t;for(const n in e)(n==="class"||n==="style"||Bt(n))&&((t||(t={}))[n]=e[n]);return t},tl=(e,t)=>{const n={};for(const r in e)(!sr(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function nl(e,t,n){const{props:r,children:s,component:o}=e,{props:i,children:l,patchFlag:c}=t,u=o.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return r?kr(r,i,u):!!i;if(c&8){const d=t.dynamicProps;for(let h=0;he.__isSuspense;function Gs(e,t){t&&t.pendingBranch?D(e)?t.effects.push(...e):t.effects.push(e):Xi(e)}function eo(e,t){return An(e,null,t)}function Ca(e,t){return An(e,null,{flush:"post"})}const Xt={};function qe(e,t,n){return An(e,t,n)}function An(e,t,{immediate:n,deep:r,flush:s,onTrack:o,onTrigger:i}=te){var l;const c=Ms()===((l=le)==null?void 0:l.scope)?le:null;let u,d=!1,h=!1;if(ce(e)?(u=()=>e.value,d=cn(e)):pt(e)?(u=()=>e,r=!0):D(e)?(h=!0,d=e.some(A=>pt(A)||cn(A)),u=()=>e.map(A=>{if(ce(A))return A.value;if(pt(A))return ft(A);if(K(A))return Ve(A,c,2)})):K(e)?t?u=()=>Ve(e,c,2):u=()=>{if(!(c&&c.isUnmounted))return y&&y(),xe(e,c,3,[C])}:u=Re,t&&r){const A=u;u=()=>ft(A())}let y,C=A=>{y=m.onStop=()=>{Ve(A,c,4)}},x;if(wt)if(C=Re,t?n&&xe(t,c,3,[u(),h?[]:void 0,C]):u(),s==="sync"){const A=Zl();x=A.__watcherHandles||(A.__watcherHandles=[])}else return Re;let O=h?new Array(e.length).fill(Xt):Xt;const H=()=>{if(m.active)if(t){const A=m.run();(r||d||(h?A.some((B,q)=>Nt(B,O[q])):Nt(A,O)))&&(y&&y(),xe(t,c,3,[A,O===Xt?void 0:h&&O[0]===Xt?[]:O,C]),O=A)}else m.run()};H.allowRecurse=!!t;let g;s==="sync"?g=H:s==="post"?g=()=>pe(H,c&&c.suspense):(H.pre=!0,c&&(H.id=c.uid),g=()=>En(H));const m=new fr(u,g);t?n?H():O=m.run():s==="post"?pe(m.run.bind(m),c&&c.suspense):m.run();const j=()=>{m.stop(),c&&c.scope&&or(c.scope.effects,m)};return x&&x.push(j),j}function ol(e,t,n){const r=this.proxy,s=se(e)?e.includes(".")?to(r,e):()=>r[e]:e.bind(r,r);let o;K(t)?o=t:(o=t.handler,n=t);const i=le;vt(this);const l=An(s,o.bind(r),n);return i?vt(i):ot(),l}function to(e,t){const n=t.split(".");return()=>{let r=e;for(let s=0;s{ft(n,t)});else if(Ps(e))for(const n in e)ft(e[n],t);return e}function Ie(e,t,n,r){const s=e.dirs,o=t&&t.dirs;for(let i=0;i{e.isMounted=!0}),io(()=>{e.isUnmounting=!0}),e}const we=[Function,Array],no={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:we,onEnter:we,onAfterEnter:we,onEnterCancelled:we,onBeforeLeave:we,onLeave:we,onAfterLeave:we,onLeaveCancelled:we,onBeforeAppear:we,onAppear:we,onAfterAppear:we,onAppearCancelled:we},ll={name:"BaseTransition",props:no,setup(e,{slots:t}){const n=Pn(),r=il();let s;return()=>{const o=t.default&&so(t.default(),!0);if(!o||!o.length)return;let i=o[0];if(o.length>1){for(const O of o)if(O.type!==ve){i=O;break}}const l=Y(e),{mode:c}=l;if(r.isLeaving)return Nn(i);const u=Kr(i);if(!u)return Nn(i);const d=Jn(u,l,r,n);Xn(u,d);const h=n.subTree,y=h&&Kr(h);let C=!1;const{getTransitionKey:x}=u.type;if(x){const O=x();s===void 0?s=O:O!==s&&(s=O,C=!0)}if(y&&y.type!==ve&&(!tt(u,y)||C)){const O=Jn(y,l,r,n);if(Xn(y,O),c==="out-in")return r.isLeaving=!0,O.afterLeave=()=>{r.isLeaving=!1,n.update.active!==!1&&n.update()},Nn(i);c==="in-out"&&u.type!==ve&&(O.delayLeave=(H,g,m)=>{const j=ro(r,y);j[String(y.key)]=y,H._leaveCb=()=>{g(),H._leaveCb=void 0,delete d.delayedLeave},d.delayedLeave=m})}return i}}},cl=ll;function ro(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function Jn(e,t,n,r){const{appear:s,mode:o,persisted:i=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:u,onEnterCancelled:d,onBeforeLeave:h,onLeave:y,onAfterLeave:C,onLeaveCancelled:x,onBeforeAppear:O,onAppear:H,onAfterAppear:g,onAppearCancelled:m}=t,j=String(e.key),A=ro(n,e),B=(_,I)=>{_&&xe(_,r,9,I)},q=(_,I)=>{const M=I[1];B(_,I),D(_)?_.every(J=>J.length<=1)&&M():_.length<=1&&M()},k={mode:o,persisted:i,beforeEnter(_){let I=l;if(!n.isMounted)if(s)I=O||l;else return;_._leaveCb&&_._leaveCb(!0);const M=A[j];M&&tt(e,M)&&M.el._leaveCb&&M.el._leaveCb(),B(I,[_])},enter(_){let I=c,M=u,J=d;if(!n.isMounted)if(s)I=H||c,M=g||u,J=m||d;else return;let P=!1;const W=_._enterCb=L=>{P||(P=!0,L?B(J,[_]):B(M,[_]),k.delayedLeave&&k.delayedLeave(),_._enterCb=void 0)};I?q(I,[_,W]):W()},leave(_,I){const M=String(e.key);if(_._enterCb&&_._enterCb(!0),n.isUnmounting)return I();B(h,[_]);let J=!1;const P=_._leaveCb=W=>{J||(J=!0,I(),W?B(x,[_]):B(C,[_]),_._leaveCb=void 0,A[M]===e&&delete A[M])};A[M]=e,y?q(y,[_,P]):P()},clone(_){return Jn(_,t,n,r)}};return k}function Nn(e){if(kt(e))return e=Ye(e),e.children=null,e}function Kr(e){return kt(e)?e.children?e.children[0]:void 0:e}function Xn(e,t){e.shapeFlag&6&&e.component?Xn(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function so(e,t=!1,n){let r=[],s=0;for(let o=0;o1)for(let o=0;oie({name:e.name},t,{setup:e}))():e}const mt=e=>!!e.type.__asyncLoader;function Ea(e){K(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:r,delay:s=200,timeout:o,suspensible:i=!0,onError:l}=e;let c=null,u,d=0;const h=()=>(d++,c=null,y()),y=()=>{let C;return c||(C=c=t().catch(x=>{if(x=x instanceof Error?x:new Error(String(x)),l)return new Promise((O,H)=>{l(x,()=>O(h()),()=>H(x),d+1)});throw x}).then(x=>C!==c&&c?c:(x&&(x.__esModule||x[Symbol.toStringTag]==="Module")&&(x=x.default),u=x,x)))};return vr({name:"AsyncComponentWrapper",__asyncLoader:y,get __asyncResolved(){return u},setup(){const C=le;if(u)return()=>$n(u,C);const x=m=>{c=null,Ut(m,C,13,!r)};if(i&&C.suspense||wt)return y().then(m=>()=>$n(m,C)).catch(m=>(x(m),()=>r?re(r,{error:m}):null));const O=fe(!1),H=fe(),g=fe(!!s);return s&&setTimeout(()=>{g.value=!1},s),o!=null&&setTimeout(()=>{if(!O.value&&!H.value){const m=new Error(`Async component timed out after ${o}ms.`);x(m),H.value=m}},o),y().then(()=>{O.value=!0,C.parent&&kt(C.parent.vnode)&&En(C.parent.update)}).catch(m=>{x(m),H.value=m}),()=>{if(O.value&&u)return $n(u,C);if(H.value&&r)return re(r,{error:H.value});if(n&&!g.value)return re(n)}}})}function $n(e,t){const{ref:n,props:r,children:s,ce:o}=t.vnode,i=re(e,r,s);return i.ref=n,i.ce=o,delete t.vnode.ce,i}const kt=e=>e.type.__isKeepAlive;function al(e,t){oo(e,"a",t)}function ul(e,t){oo(e,"da",t)}function oo(e,t,n=le){const r=e.__wdc||(e.__wdc=()=>{let s=n;for(;s;){if(s.isDeactivated)return;s=s.parent}return e()});if(Sn(t,r,n),n){let s=n.parent;for(;s&&s.parent;)kt(s.parent.vnode)&&fl(r,t,n,s),s=s.parent}}function fl(e,t,n,r){const s=Sn(t,e,r,!0);On(()=>{or(r[t],s)},n)}function Sn(e,t,n=le,r=!1){if(n){const s=n[e]||(n[e]=[]),o=t.__weh||(t.__weh=(...i)=>{if(n.isUnmounted)return;Et(),vt(n);const l=xe(t,n,e,i);return ot(),xt(),l});return r?s.unshift(o):s.push(o),o}}const je=e=>(t,n=le)=>(!wt||e==="sp")&&Sn(e,(...r)=>t(...r),n),dl=je("bm"),Tt=je("m"),hl=je("bu"),pl=je("u"),io=je("bum"),On=je("um"),gl=je("sp"),ml=je("rtg"),_l=je("rtc");function yl(e,t=le){Sn("ec",e,t)}const wr="components";function xa(e,t){return co(wr,e,!0,t)||e}const lo=Symbol.for("v-ndc");function Ta(e){return se(e)?co(wr,e,!1)||e:e||lo}function co(e,t,n=!0,r=!1){const s=de||le;if(s){const o=s.type;if(e===wr){const l=Jl(o,!1);if(l&&(l===t||l===Le(t)||l===yn(Le(t))))return o}const i=Wr(s[e]||o[e],t)||Wr(s.appContext[e],t);return!i&&r?o:i}}function Wr(e,t){return e&&(e[t]||e[Le(t)]||e[yn(Le(t))])}function Aa(e,t,n,r){let s;const o=n&&n[r];if(D(e)||se(e)){s=new Array(e.length);for(let i=0,l=e.length;it(i,l,void 0,o&&o[l]));else{const i=Object.keys(e);s=new Array(i.length);for(let l=0,c=i.length;lpn(t)?!(t.type===ve||t.type===ge&&!ao(t.children)):!0)?e:null}function Oa(e,t){const n={};for(const r in e)n[t&&/[A-Z]/.test(r)?`on:${r}`:nn(r)]=e[r];return n}const Qn=e=>e?To(e)?Ar(e)||e.proxy:Qn(e.parent):null,It=ie(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Qn(e.parent),$root:e=>Qn(e.root),$emit:e=>e.emit,$options:e=>Cr(e),$forceUpdate:e=>e.f||(e.f=()=>En(e.update)),$nextTick:e=>e.n||(e.n=Cn.bind(e.proxy)),$watch:e=>ol.bind(e)}),Hn=(e,t)=>e!==te&&!e.__isScriptSetup&&z(e,t),bl={get({_:e},t){const{ctx:n,setupState:r,data:s,props:o,accessCache:i,type:l,appContext:c}=e;let u;if(t[0]!=="$"){const C=i[t];if(C!==void 0)switch(C){case 1:return r[t];case 2:return s[t];case 4:return n[t];case 3:return o[t]}else{if(Hn(r,t))return i[t]=1,r[t];if(s!==te&&z(s,t))return i[t]=2,s[t];if((u=e.propsOptions[0])&&z(u,t))return i[t]=3,o[t];if(n!==te&&z(n,t))return i[t]=4,n[t];Zn&&(i[t]=0)}}const d=It[t];let h,y;if(d)return t==="$attrs"&&me(e,"get",t),d(e);if((h=l.__cssModules)&&(h=h[t]))return h;if(n!==te&&z(n,t))return i[t]=4,n[t];if(y=c.config.globalProperties,z(y,t))return y[t]},set({_:e},t,n){const{data:r,setupState:s,ctx:o}=e;return Hn(s,t)?(s[t]=n,!0):r!==te&&z(r,t)?(r[t]=n,!0):z(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:s,propsOptions:o}},i){let l;return!!n[i]||e!==te&&z(e,i)||Hn(t,i)||(l=o[0])&&z(l,i)||z(r,i)||z(It,i)||z(s.config.globalProperties,i)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:z(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Ra(){return vl().slots}function vl(){const e=Pn();return e.setupContext||(e.setupContext=So(e))}function Vr(e){return D(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Zn=!0;function wl(e){const t=Cr(e),n=e.proxy,r=e.ctx;Zn=!1,t.beforeCreate&&qr(t.beforeCreate,e,"bc");const{data:s,computed:o,methods:i,watch:l,provide:c,inject:u,created:d,beforeMount:h,mounted:y,beforeUpdate:C,updated:x,activated:O,deactivated:H,beforeDestroy:g,beforeUnmount:m,destroyed:j,unmounted:A,render:B,renderTracked:q,renderTriggered:k,errorCaptured:_,serverPrefetch:I,expose:M,inheritAttrs:J,components:P,directives:W,filters:L}=t;if(u&&Cl(u,r,null),i)for(const ne in i){const Z=i[ne];K(Z)&&(r[ne]=Z.bind(n))}if(s){const ne=s.call(n,n);ee(ne)&&(e.data=vn(ne))}if(Zn=!0,o)for(const ne in o){const Z=o[ne],Je=K(Z)?Z.bind(n,n):K(Z.get)?Z.get.bind(n,n):Re,Kt=!K(Z)&&K(Z.set)?Z.set.bind(n):Re,Xe=ae({get:Je,set:Kt});Object.defineProperty(r,ne,{enumerable:!0,configurable:!0,get:()=>Xe.value,set:Pe=>Xe.value=Pe})}if(l)for(const ne in l)uo(l[ne],r,n,ne);if(c){const ne=K(c)?c.call(n):c;Reflect.ownKeys(ne).forEach(Z=>{Ol(Z,ne[Z])})}d&&qr(d,e,"c");function X(ne,Z){D(Z)?Z.forEach(Je=>ne(Je.bind(n))):Z&&ne(Z.bind(n))}if(X(dl,h),X(Tt,y),X(hl,C),X(pl,x),X(al,O),X(ul,H),X(yl,_),X(_l,q),X(ml,k),X(io,m),X(On,A),X(gl,I),D(M))if(M.length){const ne=e.exposed||(e.exposed={});M.forEach(Z=>{Object.defineProperty(ne,Z,{get:()=>n[Z],set:Je=>n[Z]=Je})})}else e.exposed||(e.exposed={});B&&e.render===Re&&(e.render=B),J!=null&&(e.inheritAttrs=J),P&&(e.components=P),W&&(e.directives=W)}function Cl(e,t,n=Re){D(e)&&(e=Gn(e));for(const r in e){const s=e[r];let o;ee(s)?"default"in s?o=_t(s.from||r,s.default,!0):o=_t(s.from||r):o=_t(s),ce(o)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>o.value,set:i=>o.value=i}):t[r]=o}}function qr(e,t,n){xe(D(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function uo(e,t,n,r){const s=r.includes(".")?to(n,r):()=>n[r];if(se(e)){const o=t[e];K(o)&&qe(s,o)}else if(K(e))qe(s,e.bind(n));else if(ee(e))if(D(e))e.forEach(o=>uo(o,t,n,r));else{const o=K(e.handler)?e.handler.bind(n):t[e.handler];K(o)&&qe(s,o,e)}}function Cr(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:s,optionsCache:o,config:{optionMergeStrategies:i}}=e.appContext,l=o.get(t);let c;return l?c=l:!s.length&&!n&&!r?c=t:(c={},s.length&&s.forEach(u=>fn(c,u,i,!0)),fn(c,t,i)),ee(t)&&o.set(t,c),c}function fn(e,t,n,r=!1){const{mixins:s,extends:o}=t;o&&fn(e,o,n,!0),s&&s.forEach(i=>fn(e,i,n,!0));for(const i in t)if(!(r&&i==="expose")){const l=El[i]||n&&n[i];e[i]=l?l(e[i],t[i]):t[i]}return e}const El={data:zr,props:Yr,emits:Yr,methods:Rt,computed:Rt,beforeCreate:he,created:he,beforeMount:he,mounted:he,beforeUpdate:he,updated:he,beforeDestroy:he,beforeUnmount:he,destroyed:he,unmounted:he,activated:he,deactivated:he,errorCaptured:he,serverPrefetch:he,components:Rt,directives:Rt,watch:Tl,provide:zr,inject:xl};function zr(e,t){return t?e?function(){return ie(K(e)?e.call(this,this):e,K(t)?t.call(this,this):t)}:t:e}function xl(e,t){return Rt(Gn(e),Gn(t))}function Gn(e){if(D(e)){const t={};for(let n=0;n1)return n&&K(t)?t.call(r&&r.proxy):t}}function Rl(e,t,n,r=!1){const s={},o={};on(o,Rn,1),e.propsDefaults=Object.create(null),ho(e,t,s,o);for(const i in e.propsOptions[0])i in s||(s[i]=void 0);n?e.props=r?s:$i(s):e.type.props?e.props=s:e.props=o,e.attrs=o}function Pl(e,t,n,r){const{props:s,attrs:o,vnode:{patchFlag:i}}=e,l=Y(s),[c]=e.propsOptions;let u=!1;if((r||i>0)&&!(i&16)){if(i&8){const d=e.vnode.dynamicProps;for(let h=0;h{c=!0;const[y,C]=po(h,t,!0);ie(i,y),C&&l.push(...C)};!n&&t.mixins.length&&t.mixins.forEach(d),e.extends&&d(e.extends),e.mixins&&e.mixins.forEach(d)}if(!o&&!c)return ee(e)&&r.set(e,dt),dt;if(D(o))for(let d=0;d-1,C[1]=O<0||x-1||z(C,"default"))&&l.push(h)}}}const u=[i,l];return ee(e)&&r.set(e,u),u}function Jr(e){return e[0]!=="$"}function Xr(e){const t=e&&e.toString().match(/^\s*(function|class) (\w+)/);return t?t[2]:e===null?"null":""}function Qr(e,t){return Xr(e)===Xr(t)}function Zr(e,t){return D(t)?t.findIndex(n=>Qr(n,e)):K(t)&&Qr(t,e)?0:-1}const go=e=>e[0]==="_"||e==="$stable",Er=e=>D(e)?e.map(Ae):[Ae(e)],Fl=(e,t,n)=>{if(t._n)return t;const r=Gi((...s)=>Er(t(...s)),n);return r._c=!1,r},mo=(e,t,n)=>{const r=e._ctx;for(const s in e){if(go(s))continue;const o=e[s];if(K(o))t[s]=Fl(s,o,r);else if(o!=null){const i=Er(o);t[s]=()=>i}}},_o=(e,t)=>{const n=Er(t);e.slots.default=()=>n},Il=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=Y(t),on(t,"_",n)):mo(t,e.slots={})}else e.slots={},t&&_o(e,t);on(e.slots,Rn,1)},Ml=(e,t,n)=>{const{vnode:r,slots:s}=e;let o=!0,i=te;if(r.shapeFlag&32){const l=t._;l?n&&l===1?o=!1:(ie(s,t),!n&&l===1&&delete s._):(o=!t.$stable,mo(t,s)),i=t}else t&&(_o(e,t),i={default:1});if(o)for(const l in s)!go(l)&&!(l in i)&&delete s[l]};function hn(e,t,n,r,s=!1){if(D(e)){e.forEach((y,C)=>hn(y,t&&(D(t)?t[C]:t),n,r,s));return}if(mt(r)&&!s)return;const o=r.shapeFlag&4?Ar(r.component)||r.component.proxy:r.el,i=s?null:o,{i:l,r:c}=e,u=t&&t.r,d=l.refs===te?l.refs={}:l.refs,h=l.setupState;if(u!=null&&u!==c&&(se(u)?(d[u]=null,z(h,u)&&(h[u]=null)):ce(u)&&(u.value=null)),K(c))Ve(c,l,12,[i,d]);else{const y=se(c),C=ce(c);if(y||C){const x=()=>{if(e.f){const O=y?z(h,c)?h[c]:d[c]:c.value;s?D(O)&&or(O,o):D(O)?O.includes(o)||O.push(o):y?(d[c]=[o],z(h,c)&&(h[c]=d[c])):(c.value=[o],e.k&&(d[e.k]=c.value))}else y?(d[c]=i,z(h,c)&&(h[c]=i)):C&&(c.value=i,e.k&&(d[e.k]=i))};i?(x.id=-1,pe(x,n)):x()}}}let Be=!1;const Qt=e=>/svg/.test(e.namespaceURI)&&e.tagName!=="foreignObject",Zt=e=>e.nodeType===8;function Ll(e){const{mt:t,p:n,o:{patchProp:r,createText:s,nextSibling:o,parentNode:i,remove:l,insert:c,createComment:u}}=e,d=(g,m)=>{if(!m.hasChildNodes()){n(null,g,m),an(),m._vnode=g;return}Be=!1,h(m.firstChild,g,null,null,null),an(),m._vnode=g,Be&&console.error("Hydration completed but contains mismatches.")},h=(g,m,j,A,B,q=!1)=>{const k=Zt(g)&&g.data==="[",_=()=>O(g,m,j,A,B,k),{type:I,ref:M,shapeFlag:J,patchFlag:P}=m;let W=g.nodeType;m.el=g,P===-2&&(q=!1,m.dynamicChildren=null);let L=null;switch(I){case bt:W!==3?m.children===""?(c(m.el=s(""),i(g),g),L=g):L=_():(g.data!==m.children&&(Be=!0,g.data=m.children),L=o(g));break;case ve:W!==8||k?L=_():L=o(g);break;case Mt:if(k&&(g=o(g),W=g.nodeType),W===1||W===3){L=g;const _e=!m.children.length;for(let X=0;X{q=q||!!m.dynamicChildren;const{type:k,props:_,patchFlag:I,shapeFlag:M,dirs:J}=m,P=k==="input"&&J||k==="option";if(P||I!==-1){if(J&&Ie(m,null,j,"created"),_)if(P||!q||I&48)for(const L in _)(P&&L.endsWith("value")||Bt(L)&&!Pt(L))&&r(g,L,null,_[L],!1,void 0,j);else _.onClick&&r(g,"onClick",null,_.onClick,!1,void 0,j);let W;if((W=_&&_.onVnodeBeforeMount)&&Ce(W,j,m),J&&Ie(m,null,j,"beforeMount"),((W=_&&_.onVnodeMounted)||J)&&Gs(()=>{W&&Ce(W,j,m),J&&Ie(m,null,j,"mounted")},A),M&16&&!(_&&(_.innerHTML||_.textContent))){let L=C(g.firstChild,m,g,j,A,B,q);for(;L;){Be=!0;const _e=L;L=L.nextSibling,l(_e)}}else M&8&&g.textContent!==m.children&&(Be=!0,g.textContent=m.children)}return g.nextSibling},C=(g,m,j,A,B,q,k)=>{k=k||!!m.dynamicChildren;const _=m.children,I=_.length;for(let M=0;M{const{slotScopeIds:k}=m;k&&(B=B?B.concat(k):k);const _=i(g),I=C(o(g),m,_,j,A,B,q);return I&&Zt(I)&&I.data==="]"?o(m.anchor=I):(Be=!0,c(m.anchor=u("]"),_,I),I)},O=(g,m,j,A,B,q)=>{if(Be=!0,m.el=null,q){const I=H(g);for(;;){const M=o(g);if(M&&M!==I)l(M);else break}}const k=o(g),_=i(g);return l(g),n(null,m,_,k,j,A,Qt(_),B),k},H=g=>{let m=0;for(;g;)if(g=o(g),g&&Zt(g)&&(g.data==="["&&m++,g.data==="]")){if(m===0)return o(g);m--}return g};return[d,h]}const pe=Gs;function Nl(e){return $l(e,Ll)}function $l(e,t){const n=Wn();n.__VUE__=!0;const{insert:r,remove:s,patchProp:o,createElement:i,createText:l,createComment:c,setText:u,setElementText:d,parentNode:h,nextSibling:y,setScopeId:C=Re,insertStaticContent:x}=e,O=(a,f,p,v=null,b=null,T=null,R=!1,E=null,S=!!f.dynamicChildren)=>{if(a===f)return;a&&!tt(a,f)&&(v=Wt(a),Pe(a,b,T,!0),a=null),f.patchFlag===-2&&(S=!1,f.dynamicChildren=null);const{type:w,ref:N,shapeFlag:F}=f;switch(w){case bt:H(a,f,p,v);break;case ve:g(a,f,p,v);break;case Mt:a==null&&m(f,p,v,R);break;case ge:P(a,f,p,v,b,T,R,E,S);break;default:F&1?B(a,f,p,v,b,T,R,E,S):F&6?W(a,f,p,v,b,T,R,E,S):(F&64||F&128)&&w.process(a,f,p,v,b,T,R,E,S,ct)}N!=null&&b&&hn(N,a&&a.ref,T,f||a,!f)},H=(a,f,p,v)=>{if(a==null)r(f.el=l(f.children),p,v);else{const b=f.el=a.el;f.children!==a.children&&u(b,f.children)}},g=(a,f,p,v)=>{a==null?r(f.el=c(f.children||""),p,v):f.el=a.el},m=(a,f,p,v)=>{[a.el,a.anchor]=x(a.children,f,p,v,a.el,a.anchor)},j=({el:a,anchor:f},p,v)=>{let b;for(;a&&a!==f;)b=y(a),r(a,p,v),a=b;r(f,p,v)},A=({el:a,anchor:f})=>{let p;for(;a&&a!==f;)p=y(a),s(a),a=p;s(f)},B=(a,f,p,v,b,T,R,E,S)=>{R=R||f.type==="svg",a==null?q(f,p,v,b,T,R,E,S):I(a,f,b,T,R,E,S)},q=(a,f,p,v,b,T,R,E)=>{let S,w;const{type:N,props:F,shapeFlag:$,transition:U,dirs:V}=a;if(S=a.el=i(a.type,T,F&&F.is,F),$&8?d(S,a.children):$&16&&_(a.children,S,null,v,b,T&&N!=="foreignObject",R,E),V&&Ie(a,null,v,"created"),k(S,a,a.scopeId,R,v),F){for(const Q in F)Q!=="value"&&!Pt(Q)&&o(S,Q,null,F[Q],T,a.children,v,b,Ne);"value"in F&&o(S,"value",null,F.value),(w=F.onVnodeBeforeMount)&&Ce(w,v,a)}V&&Ie(a,null,v,"beforeMount");const G=(!b||b&&!b.pendingBranch)&&U&&!U.persisted;G&&U.beforeEnter(S),r(S,f,p),((w=F&&F.onVnodeMounted)||G||V)&&pe(()=>{w&&Ce(w,v,a),G&&U.enter(S),V&&Ie(a,null,v,"mounted")},b)},k=(a,f,p,v,b)=>{if(p&&C(a,p),v)for(let T=0;T{for(let w=S;w{const E=f.el=a.el;let{patchFlag:S,dynamicChildren:w,dirs:N}=f;S|=a.patchFlag&16;const F=a.props||te,$=f.props||te;let U;p&&Qe(p,!1),(U=$.onVnodeBeforeUpdate)&&Ce(U,p,f,a),N&&Ie(f,a,p,"beforeUpdate"),p&&Qe(p,!0);const V=b&&f.type!=="foreignObject";if(w?M(a.dynamicChildren,w,E,p,v,V,T):R||Z(a,f,E,null,p,v,V,T,!1),S>0){if(S&16)J(E,f,F,$,p,v,b);else if(S&2&&F.class!==$.class&&o(E,"class",null,$.class,b),S&4&&o(E,"style",F.style,$.style,b),S&8){const G=f.dynamicProps;for(let Q=0;Q{U&&Ce(U,p,f,a),N&&Ie(f,a,p,"updated")},v)},M=(a,f,p,v,b,T,R)=>{for(let E=0;E{if(p!==v){if(p!==te)for(const E in p)!Pt(E)&&!(E in v)&&o(a,E,p[E],null,R,f.children,b,T,Ne);for(const E in v){if(Pt(E))continue;const S=v[E],w=p[E];S!==w&&E!=="value"&&o(a,E,w,S,R,f.children,b,T,Ne)}"value"in v&&o(a,"value",p.value,v.value)}},P=(a,f,p,v,b,T,R,E,S)=>{const w=f.el=a?a.el:l(""),N=f.anchor=a?a.anchor:l("");let{patchFlag:F,dynamicChildren:$,slotScopeIds:U}=f;U&&(E=E?E.concat(U):U),a==null?(r(w,p,v),r(N,p,v),_(f.children,p,N,b,T,R,E,S)):F>0&&F&64&&$&&a.dynamicChildren?(M(a.dynamicChildren,$,p,b,T,R,E),(f.key!=null||b&&f===b.subTree)&&yo(a,f,!0)):Z(a,f,p,N,b,T,R,E,S)},W=(a,f,p,v,b,T,R,E,S)=>{f.slotScopeIds=E,a==null?f.shapeFlag&512?b.ctx.activate(f,p,v,R,S):L(f,p,v,b,T,R,S):_e(a,f,S)},L=(a,f,p,v,b,T,R)=>{const E=a.component=Vl(a,v,b);if(kt(a)&&(E.ctx.renderer=ct),ql(E),E.asyncDep){if(b&&b.registerDep(E,X),!a.el){const S=E.subTree=re(ve);g(null,S,f,p)}return}X(E,a,f,p,b,T,R)},_e=(a,f,p)=>{const v=f.component=a.component;if(nl(a,f,p))if(v.asyncDep&&!v.asyncResolved){ne(v,f,p);return}else v.next=f,Ji(v.update),v.update();else f.el=a.el,v.vnode=f},X=(a,f,p,v,b,T,R)=>{const E=()=>{if(a.isMounted){let{next:N,bu:F,u:$,parent:U,vnode:V}=a,G=N,Q;Qe(a,!1),N?(N.el=V.el,ne(a,N,R)):N=V,F&&Mn(F),(Q=N.props&&N.props.onVnodeBeforeUpdate)&&Ce(Q,U,N,V),Qe(a,!0);const oe=Ln(a),Te=a.subTree;a.subTree=oe,O(Te,oe,h(Te.el),Wt(Te),a,b,T),N.el=oe.el,G===null&&rl(a,oe.el),$&&pe($,b),(Q=N.props&&N.props.onVnodeUpdated)&&pe(()=>Ce(Q,U,N,V),b)}else{let N;const{el:F,props:$}=f,{bm:U,m:V,parent:G}=a,Q=mt(f);if(Qe(a,!1),U&&Mn(U),!Q&&(N=$&&$.onVnodeBeforeMount)&&Ce(N,G,f),Qe(a,!0),F&&In){const oe=()=>{a.subTree=Ln(a),In(F,a.subTree,a,b,null)};Q?f.type.__asyncLoader().then(()=>!a.isUnmounted&&oe()):oe()}else{const oe=a.subTree=Ln(a);O(null,oe,p,v,a,b,T),f.el=oe.el}if(V&&pe(V,b),!Q&&(N=$&&$.onVnodeMounted)){const oe=f;pe(()=>Ce(N,G,oe),b)}(f.shapeFlag&256||G&&mt(G.vnode)&&G.vnode.shapeFlag&256)&&a.a&&pe(a.a,b),a.isMounted=!0,f=p=v=null}},S=a.effect=new fr(E,()=>En(w),a.scope),w=a.update=()=>S.run();w.id=a.uid,Qe(a,!0),w()},ne=(a,f,p)=>{f.component=a;const v=a.vnode.props;a.vnode=f,a.next=null,Pl(a,f.props,v,p),Ml(a,f.children,p),Et(),Ur(),xt()},Z=(a,f,p,v,b,T,R,E,S=!1)=>{const w=a&&a.children,N=a?a.shapeFlag:0,F=f.children,{patchFlag:$,shapeFlag:U}=f;if($>0){if($&128){Kt(w,F,p,v,b,T,R,E,S);return}else if($&256){Je(w,F,p,v,b,T,R,E,S);return}}U&8?(N&16&&Ne(w,b,T),F!==w&&d(p,F)):N&16?U&16?Kt(w,F,p,v,b,T,R,E,S):Ne(w,b,T,!0):(N&8&&d(p,""),U&16&&_(F,p,v,b,T,R,E,S))},Je=(a,f,p,v,b,T,R,E,S)=>{a=a||dt,f=f||dt;const w=a.length,N=f.length,F=Math.min(w,N);let $;for($=0;$N?Ne(a,b,T,!0,!1,F):_(f,p,v,b,T,R,E,S,F)},Kt=(a,f,p,v,b,T,R,E,S)=>{let w=0;const N=f.length;let F=a.length-1,$=N-1;for(;w<=F&&w<=$;){const U=a[w],V=f[w]=S?Ke(f[w]):Ae(f[w]);if(tt(U,V))O(U,V,p,null,b,T,R,E,S);else break;w++}for(;w<=F&&w<=$;){const U=a[F],V=f[$]=S?Ke(f[$]):Ae(f[$]);if(tt(U,V))O(U,V,p,null,b,T,R,E,S);else break;F--,$--}if(w>F){if(w<=$){const U=$+1,V=U$)for(;w<=F;)Pe(a[w],b,T,!0),w++;else{const U=w,V=w,G=new Map;for(w=V;w<=$;w++){const ye=f[w]=S?Ke(f[w]):Ae(f[w]);ye.key!=null&&G.set(ye.key,w)}let Q,oe=0;const Te=$-V+1;let at=!1,Pr=0;const At=new Array(Te);for(w=0;w=Te){Pe(ye,b,T,!0);continue}let Fe;if(ye.key!=null)Fe=G.get(ye.key);else for(Q=V;Q<=$;Q++)if(At[Q-V]===0&&tt(ye,f[Q])){Fe=Q;break}Fe===void 0?Pe(ye,b,T,!0):(At[Fe-V]=w+1,Fe>=Pr?Pr=Fe:at=!0,O(ye,f[Fe],p,null,b,T,R,E,S),oe++)}const Fr=at?Hl(At):dt;for(Q=Fr.length-1,w=Te-1;w>=0;w--){const ye=V+w,Fe=f[ye],Ir=ye+1{const{el:T,type:R,transition:E,children:S,shapeFlag:w}=a;if(w&6){Xe(a.component.subTree,f,p,v);return}if(w&128){a.suspense.move(f,p,v);return}if(w&64){R.move(a,f,p,ct);return}if(R===ge){r(T,f,p);for(let F=0;FE.enter(T),b);else{const{leave:F,delayLeave:$,afterLeave:U}=E,V=()=>r(T,f,p),G=()=>{F(T,()=>{V(),U&&U()})};$?$(T,V,G):G()}else r(T,f,p)},Pe=(a,f,p,v=!1,b=!1)=>{const{type:T,props:R,ref:E,children:S,dynamicChildren:w,shapeFlag:N,patchFlag:F,dirs:$}=a;if(E!=null&&hn(E,null,p,a,!0),N&256){f.ctx.deactivate(a);return}const U=N&1&&$,V=!mt(a);let G;if(V&&(G=R&&R.onVnodeBeforeUnmount)&&Ce(G,f,a),N&6)Vo(a.component,p,v);else{if(N&128){a.suspense.unmount(p,v);return}U&&Ie(a,null,f,"beforeUnmount"),N&64?a.type.remove(a,f,p,b,ct,v):w&&(T!==ge||F>0&&F&64)?Ne(w,f,p,!1,!0):(T===ge&&F&384||!b&&N&16)&&Ne(S,f,p),v&&Or(a)}(V&&(G=R&&R.onVnodeUnmounted)||U)&&pe(()=>{G&&Ce(G,f,a),U&&Ie(a,null,f,"unmounted")},p)},Or=a=>{const{type:f,el:p,anchor:v,transition:b}=a;if(f===ge){Wo(p,v);return}if(f===Mt){A(a);return}const T=()=>{s(p),b&&!b.persisted&&b.afterLeave&&b.afterLeave()};if(a.shapeFlag&1&&b&&!b.persisted){const{leave:R,delayLeave:E}=b,S=()=>R(p,T);E?E(a.el,T,S):S()}else T()},Wo=(a,f)=>{let p;for(;a!==f;)p=y(a),s(a),a=p;s(f)},Vo=(a,f,p)=>{const{bum:v,scope:b,update:T,subTree:R,um:E}=a;v&&Mn(v),b.stop(),T&&(T.active=!1,Pe(R,a,f,p)),E&&pe(E,f),pe(()=>{a.isUnmounted=!0},f),f&&f.pendingBranch&&!f.isUnmounted&&a.asyncDep&&!a.asyncResolved&&a.suspenseId===f.pendingId&&(f.deps--,f.deps===0&&f.resolve())},Ne=(a,f,p,v=!1,b=!1,T=0)=>{for(let R=T;Ra.shapeFlag&6?Wt(a.component.subTree):a.shapeFlag&128?a.suspense.next():y(a.anchor||a.el),Rr=(a,f,p)=>{a==null?f._vnode&&Pe(f._vnode,null,null,!0):O(f._vnode||null,a,f,null,null,null,p),Ur(),an(),f._vnode=a},ct={p:O,um:Pe,m:Xe,r:Or,mt:L,mc:_,pc:Z,pbc:M,n:Wt,o:e};let Fn,In;return t&&([Fn,In]=t(ct)),{render:Rr,hydrate:Fn,createApp:Sl(Rr,Fn)}}function Qe({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function yo(e,t,n=!1){const r=e.children,s=t.children;if(D(r)&&D(s))for(let o=0;o>1,e[n[l]]0&&(t[r]=n[o-1]),n[o]=r)}}for(o=n.length,i=n[o-1];o-- >0;)n[o]=i,i=t[i];return n}const jl=e=>e.__isTeleport,ge=Symbol.for("v-fgt"),bt=Symbol.for("v-txt"),ve=Symbol.for("v-cmt"),Mt=Symbol.for("v-stc"),Lt=[];let Oe=null;function bo(e=!1){Lt.push(Oe=e?null:[])}function Dl(){Lt.pop(),Oe=Lt[Lt.length-1]||null}let Dt=1;function Gr(e){Dt+=e}function vo(e){return e.dynamicChildren=Dt>0?Oe||dt:null,Dl(),Dt>0&&Oe&&Oe.push(e),e}function Pa(e,t,n,r,s,o){return vo(Eo(e,t,n,r,s,o,!0))}function wo(e,t,n,r,s){return vo(re(e,t,n,r,s,!0))}function pn(e){return e?e.__v_isVNode===!0:!1}function tt(e,t){return e.type===t.type&&e.key===t.key}const Rn="__vInternal",Co=({key:e})=>e??null,rn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?se(e)||ce(e)||K(e)?{i:de,r:e,k:t,f:!!n}:e:null);function Eo(e,t=null,n=null,r=0,s=null,o=e===ge?0:1,i=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Co(t),ref:t&&rn(t),scopeId:Tn,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:r,dynamicProps:s,dynamicChildren:null,appContext:null,ctx:de};return l?(xr(c,n),o&128&&e.normalize(c)):n&&(c.shapeFlag|=se(n)?8:16),Dt>0&&!i&&Oe&&(c.patchFlag>0||o&6)&&c.patchFlag!==32&&Oe.push(c),c}const re=Bl;function Bl(e,t=null,n=null,r=0,s=null,o=!1){if((!e||e===lo)&&(e=ve),pn(e)){const l=Ye(e,t,!0);return n&&xr(l,n),Dt>0&&!o&&Oe&&(l.shapeFlag&6?Oe[Oe.indexOf(e)]=l:Oe.push(l)),l.patchFlag|=-2,l}if(Xl(e)&&(e=e.__vccOpts),t){t=Ul(t);let{class:l,style:c}=t;l&&!se(l)&&(t.class=ar(l)),ee(c)&&(Ws(c)&&!D(c)&&(c=ie({},c)),t.style=cr(c))}const i=se(e)?1:sl(e)?128:jl(e)?64:ee(e)?4:K(e)?2:0;return Eo(e,t,n,r,s,i,o,!0)}function Ul(e){return e?Ws(e)||Rn in e?ie({},e):e:null}function Ye(e,t,n=!1){const{props:r,ref:s,patchFlag:o,children:i}=e,l=t?kl(r||{},t):r;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&Co(l),ref:t&&t.ref?n&&s?D(s)?s.concat(rn(t)):[s,rn(t)]:rn(t):s,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:i,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==ge?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Ye(e.ssContent),ssFallback:e.ssFallback&&Ye(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function xo(e=" ",t=0){return re(bt,null,e,t)}function Fa(e,t){const n=re(Mt,null,e);return n.staticCount=t,n}function Ia(e="",t=!1){return t?(bo(),wo(ve,null,e)):re(ve,null,e)}function Ae(e){return e==null||typeof e=="boolean"?re(ve):D(e)?re(ge,null,e.slice()):typeof e=="object"?Ke(e):re(bt,null,String(e))}function Ke(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Ye(e)}function xr(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(D(t))n=16;else if(typeof t=="object")if(r&65){const s=t.default;s&&(s._c&&(s._d=!1),xr(e,s()),s._c&&(s._d=!0));return}else{n=32;const s=t._;!s&&!(Rn in t)?t._ctx=de:s===3&&de&&(de.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else K(t)?(t={default:t,_ctx:de},n=32):(t=String(t),r&64?(n=16,t=[xo(t)]):n=8);e.children=t,e.shapeFlag|=n}function kl(...e){const t={};for(let n=0;nle||de;let Tr,ut,es="__VUE_INSTANCE_SETTERS__";(ut=Wn()[es])||(ut=Wn()[es]=[]),ut.push(e=>le=e),Tr=e=>{ut.length>1?ut.forEach(t=>t(e)):ut[0](e)};const vt=e=>{Tr(e),e.scope.on()},ot=()=>{le&&le.scope.off(),Tr(null)};function To(e){return e.vnode.shapeFlag&4}let wt=!1;function ql(e,t=!1){wt=t;const{props:n,children:r}=e.vnode,s=To(e);Rl(e,n,s,t),Il(e,r);const o=s?zl(e,t):void 0;return wt=!1,o}function zl(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Ft(new Proxy(e.ctx,bl));const{setup:r}=n;if(r){const s=e.setupContext=r.length>1?So(e):null;vt(e),Et();const o=Ve(r,e,0,[e.props,s]);if(xt(),ot(),Os(o)){if(o.then(ot,ot),t)return o.then(i=>{ts(e,i,t)}).catch(i=>{Ut(i,e,0)});e.asyncDep=o}else ts(e,o,t)}else Ao(e,t)}function ts(e,t,n){K(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ee(t)&&(e.setupState=Ys(t)),Ao(e,n)}let ns;function Ao(e,t,n){const r=e.type;if(!e.render){if(!t&&ns&&!r.render){const s=r.template||Cr(e).template;if(s){const{isCustomElement:o,compilerOptions:i}=e.appContext.config,{delimiters:l,compilerOptions:c}=r,u=ie(ie({isCustomElement:o,delimiters:l},i),c);r.render=ns(s,u)}}e.render=r.render||Re}vt(e),Et(),wl(e),xt(),ot()}function Yl(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(t,n){return me(e,"get","$attrs"),t[n]}}))}function So(e){const t=n=>{e.exposed=n||{}};return{get attrs(){return Yl(e)},slots:e.slots,emit:e.emit,expose:t}}function Ar(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Ys(Ft(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in It)return It[n](e)},has(t,n){return n in t||n in It}}))}function Jl(e,t=!0){return K(e)?e.displayName||e.name:e.name||t&&e.__name}function Xl(e){return K(e)&&"__vccOpts"in e}const ae=(e,t)=>qi(e,t,wt);function tr(e,t,n){const r=arguments.length;return r===2?ee(t)&&!D(t)?pn(t)?re(e,null,[t]):re(e,t):re(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&pn(n)&&(n=[n]),re(e,t,n))}const Ql=Symbol.for("v-scx"),Zl=()=>_t(Ql),Gl="3.3.4",ec="http://www.w3.org/2000/svg",nt=typeof document<"u"?document:null,rs=nt&&nt.createElement("template"),tc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const s=t?nt.createElementNS(ec,e):nt.createElement(e,n?{is:n}:void 0);return e==="select"&&r&&r.multiple!=null&&s.setAttribute("multiple",r.multiple),s},createText:e=>nt.createTextNode(e),createComment:e=>nt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>nt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,s,o){const i=n?n.previousSibling:t.lastChild;if(s&&(s===o||s.nextSibling))for(;t.insertBefore(s.cloneNode(!0),n),!(s===o||!(s=s.nextSibling)););else{rs.innerHTML=r?`${e} `:e;const l=rs.content;if(r){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};function nc(e,t,n){const r=e._vtc;r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}function rc(e,t,n){const r=e.style,s=se(n);if(n&&!s){if(t&&!se(t))for(const o in t)n[o]==null&&nr(r,o,"");for(const o in n)nr(r,o,n[o])}else{const o=r.display;s?t!==n&&(r.cssText=n):t&&e.removeAttribute("style"),"_vod"in e&&(r.display=o)}}const ss=/\s*!important$/;function nr(e,t,n){if(D(n))n.forEach(r=>nr(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=sc(e,t);ss.test(n)?e.setProperty(lt(r),n.replace(ss,""),"important"):e[r]=n}}const os=["Webkit","Moz","ms"],jn={};function sc(e,t){const n=jn[t];if(n)return n;let r=Le(t);if(r!=="filter"&&r in e)return jn[t]=r;r=yn(r);for(let s=0;sDn||(fc.then(()=>Dn=0),Dn=Date.now());function hc(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;xe(pc(r,n.value),t,5,[r])};return n.value=e,n.attached=dc(),n}function pc(e,t){if(D(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>s=>!s._stopped&&r&&r(s))}else return t}const cs=/^on[a-z]/,gc=(e,t,n,r,s=!1,o,i,l,c)=>{t==="class"?nc(e,r,s):t==="style"?rc(e,n,r):Bt(t)?sr(t)||ac(e,t,n,r,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):mc(e,t,r,s))?ic(e,t,r,o,i,l,c):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),oc(e,t,r,s))};function mc(e,t,n,r){return r?!!(t==="innerHTML"||t==="textContent"||t in e&&cs.test(t)&&K(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||cs.test(t)&&se(n)?!1:t in e}const Ue="transition",St="animation",Oo=(e,{slots:t})=>tr(cl,_c(e),t);Oo.displayName="Transition";const Ro={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};Oo.props=ie({},no,Ro);const Ze=(e,t=[])=>{D(e)?e.forEach(n=>n(...t)):e&&e(...t)},as=e=>e?D(e)?e.some(t=>t.length>1):e.length>1:!1;function _c(e){const t={};for(const P in e)P in Ro||(t[P]=e[P]);if(e.css===!1)return t;const{name:n="v",type:r,duration:s,enterFromClass:o=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=o,appearActiveClass:u=i,appearToClass:d=l,leaveFromClass:h=`${n}-leave-from`,leaveActiveClass:y=`${n}-leave-active`,leaveToClass:C=`${n}-leave-to`}=e,x=yc(s),O=x&&x[0],H=x&&x[1],{onBeforeEnter:g,onEnter:m,onEnterCancelled:j,onLeave:A,onLeaveCancelled:B,onBeforeAppear:q=g,onAppear:k=m,onAppearCancelled:_=j}=t,I=(P,W,L)=>{Ge(P,W?d:l),Ge(P,W?u:i),L&&L()},M=(P,W)=>{P._isLeaving=!1,Ge(P,h),Ge(P,C),Ge(P,y),W&&W()},J=P=>(W,L)=>{const _e=P?k:m,X=()=>I(W,P,L);Ze(_e,[W,X]),us(()=>{Ge(W,P?c:o),ke(W,P?d:l),as(_e)||fs(W,r,O,X)})};return ie(t,{onBeforeEnter(P){Ze(g,[P]),ke(P,o),ke(P,i)},onBeforeAppear(P){Ze(q,[P]),ke(P,c),ke(P,u)},onEnter:J(!1),onAppear:J(!0),onLeave(P,W){P._isLeaving=!0;const L=()=>M(P,W);ke(P,h),wc(),ke(P,y),us(()=>{P._isLeaving&&(Ge(P,h),ke(P,C),as(A)||fs(P,r,H,L))}),Ze(A,[P,L])},onEnterCancelled(P){I(P,!1),Ze(j,[P])},onAppearCancelled(P){I(P,!0),Ze(_,[P])},onLeaveCancelled(P){M(P),Ze(B,[P])}})}function yc(e){if(e==null)return null;if(ee(e))return[Bn(e.enter),Bn(e.leave)];{const t=Bn(e);return[t,t]}}function Bn(e){return Go(e)}function ke(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e._vtc||(e._vtc=new Set)).add(t)}function Ge(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function us(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let bc=0;function fs(e,t,n,r){const s=e._endId=++bc,o=()=>{s===e._endId&&r()};if(n)return setTimeout(o,n);const{type:i,timeout:l,propCount:c}=vc(e,t);if(!i)return r();const u=i+"end";let d=0;const h=()=>{e.removeEventListener(u,y),o()},y=C=>{C.target===e&&++d>=c&&h()};setTimeout(()=>{d(n[x]||"").split(", "),s=r(`${Ue}Delay`),o=r(`${Ue}Duration`),i=ds(s,o),l=r(`${St}Delay`),c=r(`${St}Duration`),u=ds(l,c);let d=null,h=0,y=0;t===Ue?i>0&&(d=Ue,h=i,y=o.length):t===St?u>0&&(d=St,h=u,y=c.length):(h=Math.max(i,u),d=h>0?i>u?Ue:St:null,y=d?d===Ue?o.length:c.length:0);const C=d===Ue&&/\b(transform|all)(,|$)/.test(r(`${Ue}Property`).toString());return{type:d,timeout:h,propCount:y,hasTransform:C}}function ds(e,t){for(;e.lengthhs(n)+hs(e[r])))}function hs(e){return Number(e.slice(0,-1).replace(",","."))*1e3}function wc(){return document.body.offsetHeight}const Cc=["ctrl","shift","alt","meta"],Ec={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Cc.some(n=>e[`${n}Key`]&&!t.includes(n))},Ma=(e,t)=>(n,...r)=>{for(let s=0;sn=>{if(!("key"in n))return;const r=lt(n.key);if(t.some(s=>s===r||xc[s]===r))return e(n)},Tc=ie({patchProp:gc},tc);let Un,ps=!1;function Ac(){return Un=ps?Un:Nl(Tc),ps=!0,Un}const Na=(...e)=>{const t=Ac().createApp(...e),{mount:n}=t;return t.mount=r=>{const s=Sc(r);if(s)return n(s,!0,s instanceof SVGElement)},t};function Sc(e){return se(e)?document.querySelector(e):e}const $a=(e,t)=>{const n=e.__vccOpts||e;for(const[r,s]of t)n[r]=s;return n},Oc="modulepreload",Rc=function(e){return"/"+e},gs={},Ha=function(t,n,r){if(!n||n.length===0)return t();const s=document.getElementsByTagName("link");return Promise.all(n.map(o=>{if(o=Rc(o),o in gs)return;gs[o]=!0;const i=o.endsWith(".css"),l=i?'[rel="stylesheet"]':"";if(!!r)for(let d=s.length-1;d>=0;d--){const h=s[d];if(h.href===o&&(!i||h.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${o}"]${l}`))return;const u=document.createElement("link");if(u.rel=i?"stylesheet":Oc,i||(u.as="script",u.crossOrigin=""),u.href=o,document.head.appendChild(u),i)return new Promise((d,h)=>{u.addEventListener("load",d),u.addEventListener("error",()=>h(new Error(`Unable to preload CSS for ${o}`)))})})).then(()=>t()).catch(o=>{const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=o,window.dispatchEvent(i),!i.defaultPrevented)throw o})},Pc=window.__VP_SITE_DATA__;function Sr(e){return Ms()?(ci(e),!0):!1}function it(e){return typeof e=="function"?e():zs(e)}const Po=typeof window<"u"&&typeof document<"u",Fc=Object.prototype.toString,Ic=e=>Fc.call(e)==="[object Object]",Fo=()=>{},ms=Mc();function Mc(){var e;return Po&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&/iP(ad|hone|od)/.test(window.navigator.userAgent)}function Lc(e,t){function n(...r){return new Promise((s,o)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(s).catch(o)})}return n}const Io=e=>e();function Nc(e=Io){const t=fe(!0);function n(){t.value=!1}function r(){t.value=!0}const s=(...o)=>{t.value&&e(...o)};return{isActive:wn(t),pause:n,resume:r,eventFilter:s}}function Mo(...e){if(e.length!==1)return Ki(...e);const t=e[0];return typeof t=="function"?wn(Bi(()=>({get:t,set:Fo}))):fe(t)}function $c(e,t,n={}){const{eventFilter:r=Io,...s}=n;return qe(e,Lc(r,t),s)}function Hc(e,t,n={}){const{eventFilter:r,...s}=n,{eventFilter:o,pause:i,resume:l,isActive:c}=Nc(r);return{stop:$c(e,t,{...s,eventFilter:o}),pause:i,resume:l,isActive:c}}function jc(e,t=!0){Pn()?Tt(e):t?e():Cn(e)}function Lo(e){var t;const n=it(e);return(t=n==null?void 0:n.$el)!=null?t:n}const Ct=Po?window:void 0;function gn(...e){let t,n,r,s;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,r,s]=e,t=Ct):[t,n,r,s]=e,!t)return Fo;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const o=[],i=()=>{o.forEach(d=>d()),o.length=0},l=(d,h,y,C)=>(d.addEventListener(h,y,C),()=>d.removeEventListener(h,y,C)),c=qe(()=>[Lo(t),it(s)],([d,h])=>{if(i(),!d)return;const y=Ic(h)?{...h}:h;o.push(...n.flatMap(C=>r.map(x=>l(d,C,x,y))))},{immediate:!0,flush:"post"}),u=()=>{c(),i()};return Sr(u),u}function Dc(){const e=fe(!1);return Pn()&&Tt(()=>{e.value=!0}),e}function Bc(e){const t=Dc();return ae(()=>(t.value,!!e()))}function Uc(e,t={}){const{window:n=Ct}=t,r=Bc(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let s;const o=fe(!1),i=u=>{o.value=u.matches},l=()=>{s&&("removeEventListener"in s?s.removeEventListener("change",i):s.removeListener(i))},c=eo(()=>{r.value&&(l(),s=n.matchMedia(it(e)),"addEventListener"in s?s.addEventListener("change",i):s.addListener(i),o.value=s.matches)});return Sr(()=>{c(),l(),s=void 0}),o}const Gt=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},en="__vueuse_ssr_handlers__",kc=Kc();function Kc(){return en in Gt||(Gt[en]=Gt[en]||{}),Gt[en]}function No(e,t){return kc[e]||t}function Wc(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Vc={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},_s="vueuse-storage";function qc(e,t,n,r={}){var s;const{flush:o="pre",deep:i=!0,listenToStorageChanges:l=!0,writeDefaults:c=!0,mergeDefaults:u=!1,shallow:d,window:h=Ct,eventFilter:y,onError:C=_=>{console.error(_)}}=r,x=(d?Vs:fe)(t);if(!n)try{n=No("getDefaultStorage",()=>{var _;return(_=Ct)==null?void 0:_.localStorage})()}catch(_){C(_)}if(!n)return x;const O=it(t),H=Wc(O),g=(s=r.serializer)!=null?s:Vc[H],{pause:m,resume:j}=Hc(x,()=>A(x.value),{flush:o,deep:i,eventFilter:y});return h&&l&&(gn(h,"storage",k),gn(h,_s,q)),k(),x;function A(_){try{if(_==null)n.removeItem(e);else{const I=g.write(_),M=n.getItem(e);M!==I&&(n.setItem(e,I),h&&h.dispatchEvent(new CustomEvent(_s,{detail:{key:e,oldValue:M,newValue:I,storageArea:n}})))}}catch(I){C(I)}}function B(_){const I=_?_.newValue:n.getItem(e);if(I==null)return c&&O!==null&&n.setItem(e,g.write(O)),O;if(!_&&u){const M=g.read(I);return typeof u=="function"?u(M,O):H==="object"&&!Array.isArray(M)?{...O,...M}:M}else return typeof I!="string"?I:g.read(I)}function q(_){k(_.detail)}function k(_){if(!(_&&_.storageArea!==n)){if(_&&_.key==null){x.value=O;return}if(!(_&&_.key!==e)){m();try{(_==null?void 0:_.newValue)!==g.write(x.value)&&(x.value=B(_))}catch(I){C(I)}finally{_?Cn(j):j()}}}}}function zc(e){return Uc("(prefers-color-scheme: dark)",e)}function Yc(e={}){const{selector:t="html",attribute:n="class",initialValue:r="auto",window:s=Ct,storage:o,storageKey:i="vueuse-color-scheme",listenToStorageChanges:l=!0,storageRef:c,emitAuto:u,disableTransition:d=!0}=e,h={auto:"",light:"light",dark:"dark",...e.modes||{}},y=zc({window:s}),C=ae(()=>y.value?"dark":"light"),x=c||(i==null?Mo(r):qc(i,r,o,{window:s,listenToStorageChanges:l})),O=ae(()=>x.value==="auto"?C.value:x.value),H=No("updateHTMLAttrs",(A,B,q)=>{const k=typeof A=="string"?s==null?void 0:s.document.querySelector(A):Lo(A);if(!k)return;let _;if(d){_=s.document.createElement("style");const I="*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}";_.appendChild(document.createTextNode(I)),s.document.head.appendChild(_)}if(B==="class"){const I=q.split(/\s/g);Object.values(h).flatMap(M=>(M||"").split(/\s/g)).filter(Boolean).forEach(M=>{I.includes(M)?k.classList.add(M):k.classList.remove(M)})}else k.setAttribute(B,q);d&&(s.getComputedStyle(_).opacity,document.head.removeChild(_))});function g(A){var B;H(t,n,(B=h[A])!=null?B:A)}function m(A){e.onChanged?e.onChanged(A,g):g(A)}qe(O,m,{flush:"post",immediate:!0}),jc(()=>m(O.value));const j=ae({get(){return u?x.value:O.value},set(A){x.value=A}});try{return Object.assign(j,{store:x,system:C,state:O})}catch{return j}}function Jc(e={}){const{valueDark:t="dark",valueLight:n=""}=e,r=Yc({...e,onChanged:(o,i)=>{var l;e.onChanged?(l=e.onChanged)==null||l.call(e,o==="dark",i,o):i(o)},modes:{dark:t,light:n}});return ae({get(){return r.value==="dark"},set(o){const i=o?"dark":"light";r.system.value===i?r.value="auto":r.value=i}})}function kn(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function $o(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}function ja(e,t=!1){const n=fe(t);let r=null,s;qe(Mo(e),l=>{const c=kn(it(l));if(c){const u=c;s=u.style.overflow,n.value&&(u.style.overflow="hidden")}},{immediate:!0});const o=()=>{const l=kn(it(e));!l||n.value||(ms&&(r=gn(l,"touchmove",c=>{Xc(c)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},i=()=>{const l=kn(it(e));!l||!n.value||(ms&&(r==null||r()),l.style.overflow=s,n.value=!1)};return Sr(i),ae({get(){return n.value},set(l){l?o():i()}})}function Da({window:e=Ct}={}){if(!e)return{x:fe(0),y:fe(0)};const t=fe(e.scrollX),n=fe(e.scrollY);return gn(e,"scroll",()=>{t.value=e.scrollX,n.value=e.scrollY},{capture:!1,passive:!0}),{x:t,y:n}}const Ho=/^[a-z]+:/i,Qc="vitepress-theme-appearance",jo=/#.*$/,Zc=/(index)?\.(md|html)$/,Ee=typeof document<"u",Do={relativePath:"",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function Gc(e,t,n=!1){if(t===void 0)return!1;if(e=ys(`/${e}`),n)return new RegExp(t).test(e);if(ys(t)!==e)return!1;const r=t.match(jo);return r?(Ee?location.hash:"")===r[0]:!0}function ys(e){return decodeURI(e).replace(jo,"").replace(Zc,"")}function ea(e){return Ho.test(e)}function ta(e,t){var r,s,o,i,l,c,u;const n=Object.keys(e.locales).find(d=>d!=="root"&&!ea(d)&&Gc(t,`/${d}/`,!0))||"root";return Object.assign({},e,{localeIndex:n,lang:((r=e.locales[n])==null?void 0:r.lang)??e.lang,dir:((s=e.locales[n])==null?void 0:s.dir)??e.dir,title:((o=e.locales[n])==null?void 0:o.title)??e.title,titleTemplate:((i=e.locales[n])==null?void 0:i.titleTemplate)??e.titleTemplate,description:((l=e.locales[n])==null?void 0:l.description)??e.description,head:Uo(e.head,((c=e.locales[n])==null?void 0:c.head)??[]),themeConfig:{...e.themeConfig,...(u=e.locales[n])==null?void 0:u.themeConfig}})}function Bo(e,t){const n=t.title||e.title,r=t.titleTemplate??e.titleTemplate;if(typeof r=="string"&&r.includes(":title"))return r.replace(/:title/g,n);const s=na(e.title,r);return`${n}${s}`}function na(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function ra(e,t){const[n,r]=t;if(n!=="meta")return!1;const s=Object.entries(r)[0];return s==null?!1:e.some(([o,i])=>o===n&&i[s[0]]===s[1])}function Uo(e,t){return[...e.filter(n=>!ra(t,n)),...t]}const sa=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,oa=/^[a-z]:/i;function bs(e){const t=oa.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(sa,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const ia=Symbol(),rt=Vs(Pc);function Ba(e){const t=ae(()=>ta(rt.value,e.data.relativePath)),n=t.value.appearance?Jc({storageKey:Qc,initialValue:()=>typeof t.value.appearance=="string"?t.value.appearance:"auto",...typeof t.value.appearance=="object"?t.value.appearance:{}}):fe(!1);return{site:t,theme:ae(()=>t.value.themeConfig),page:ae(()=>e.data),frontmatter:ae(()=>e.data.frontmatter),params:ae(()=>e.data.params),lang:ae(()=>t.value.lang),dir:ae(()=>t.value.dir),localeIndex:ae(()=>t.value.localeIndex||"root"),title:ae(()=>Bo(t.value,e.data)),description:ae(()=>e.data.description||t.value.description),isDark:n}}function la(){const e=_t(ia);if(!e)throw new Error("vitepress data not properly injected in app");return e}function ca(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function vs(e){return Ho.test(e)||!e.startsWith("/")?e:ca(rt.value.base,e)}function aa(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),Ee){const n="/";t=bs(t.slice(n.length).replace(/\//g,"_")||"index")+".md";let r=__VP_HASH_MAP__[t.toLowerCase()];if(r||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",r=__VP_HASH_MAP__[t.toLowerCase()]),!r)return null;t=`${n}assets/${t}.${r}.js`}else t=`./${bs(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let sn=[];function Ua(e){sn.push(e),On(()=>{sn=sn.filter(t=>t!==e)})}const ua=Symbol(),ws="http://a.com",fa=()=>({path:"/",component:null,data:Do});function ka(e,t){const n=vn(fa()),r={route:n,go:s};async function s(l=Ee?location.href:"/"){var u,d;if(await((u=r.onBeforeRouteChange)==null?void 0:u.call(r,l))===!1)return;const c=new URL(l,ws);rt.value.cleanUrls||!c.pathname.endsWith("/")&&!c.pathname.endsWith(".html")&&(c.pathname+=".html",l=c.pathname+c.search+c.hash),xs(l),await i(l),await((d=r.onAfterRouteChanged)==null?void 0:d.call(r,l))}let o=null;async function i(l,c=0,u=!1){var y;if(await((y=r.onBeforePageLoad)==null?void 0:y.call(r,l))===!1)return;const d=new URL(l,ws),h=o=d.pathname;try{let C=await e(h);if(!C)throw new Error(`Page not found: ${h}`);if(o===h){o=null;const{default:x,__pageData:O}=C;if(!x)throw new Error(`Invalid route component: ${x}`);n.path=Ee?h:vs(h),n.component=Ft(x),n.data=Ft(O),Ee&&Cn(()=>{let H=rt.value.base+O.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");if(!rt.value.cleanUrls&&!H.endsWith("/")&&(H+=".html"),H!==d.pathname&&(d.pathname=H,l=H+d.search+d.hash,history.replaceState(null,"",l)),d.hash&&!c){let g=null;try{g=document.getElementById(decodeURIComponent(d.hash).slice(1))}catch(m){console.warn(m)}if(g){Cs(g,d.hash);return}}window.scrollTo(0,c)})}}catch(C){if(!/fetch|Page not found/.test(C.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(C),!u)try{const x=await fetch(rt.value.base+"hashmap.json");window.__VP_HASH_MAP__=await x.json(),await i(l,c,!0);return}catch{}o===h&&(o=null,n.path=Ee?h:vs(h),n.component=t?Ft(t):null,n.data=Do)}}return Ee&&(window.addEventListener("click",l=>{if(l.target.closest("button"))return;const u=l.target.closest("a");if(u&&!u.closest(".vp-raw")&&(u instanceof SVGElement||!u.download)){const{target:d}=u,{href:h,origin:y,pathname:C,hash:x,search:O}=new URL(u.href instanceof SVGAnimatedString?u.href.animVal:u.href,u.baseURI),H=window.location,g=C.match(/\.\w+$/);!l.ctrlKey&&!l.shiftKey&&!l.altKey&&!l.metaKey&&!d&&y===H.origin&&!(g&&g[0]!==".html")&&(l.preventDefault(),C===H.pathname&&O===H.search?(x!==H.hash&&(history.pushState(null,"",x),window.dispatchEvent(new Event("hashchange"))),x?Cs(u,x,u.classList.contains("header-anchor")):(xs(h),window.scrollTo(0,0))):s(h))}},{capture:!0}),window.addEventListener("popstate",l=>{i(location.href,l.state&&l.state.scrollPosition||0)}),window.addEventListener("hashchange",l=>{l.preventDefault()})),r}function da(){const e=_t(ua);if(!e)throw new Error("useRouter() is called without provider.");return e}function ko(){return da().route}function Cs(e,t,n=!1){let r=null;try{r=e.classList.contains("header-anchor")?e:document.getElementById(decodeURIComponent(t).slice(1))}catch(s){console.warn(s)}if(r){let u=function(){!n||Math.abs(c-window.scrollY)>window.innerHeight?window.scrollTo(0,c):window.scrollTo({left:0,top:c,behavior:"smooth"})},s=rt.value.scrollOffset,o=0,i=24;if(typeof s=="object"&&"padding"in s&&(i=s.padding,s=s.selector),typeof s=="number")o=s;else if(typeof s=="string")o=Es(s,i);else if(Array.isArray(s))for(const d of s){const h=Es(d,i);if(h){o=h;break}}const l=parseInt(window.getComputedStyle(r).paddingTop,10),c=window.scrollY+r.getBoundingClientRect().top-o+l;requestAnimationFrame(u)}}function Es(e,t){const n=document.querySelector(e);if(!n)return 0;const r=n.getBoundingClientRect().bottom;return r<0?0:r+t}function xs(e){Ee&&e!==location.href&&(history.replaceState({scrollPosition:window.scrollY},document.title),history.pushState(null,"",e))}const Ts=()=>sn.forEach(e=>e()),Ka=vr({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=ko(),{site:n}=la();return()=>tr(e.as,n.value.contentProps??{style:{position:"relative"}},[t.component?tr(t.component,{onVnodeMounted:Ts,onVnodeUpdated:Ts}):"404 Page Not Found"])}}),Wa=vr({setup(e,{slots:t}){const n=fe(!1);return Tt(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function Va(){Ee&&window.addEventListener("click",e=>{var n;const t=e.target;if(t.matches(".vp-code-group input")){const r=(n=t.parentElement)==null?void 0:n.parentElement;if(!r)return;const s=Array.from(r.querySelectorAll("input")).indexOf(t);if(s<0)return;const o=r.querySelector(".blocks");if(!o)return;const i=Array.from(o.children).find(u=>u.classList.contains("active"));if(!i)return;const l=o.children[s];if(!l||i===l)return;i.classList.remove("active"),l.classList.add("active");const c=r==null?void 0:r.querySelector(`label[for="${t.id}"]`);c==null||c.scrollIntoView({block:"nearest"})}})}function qa(){if(Ee){const e=new WeakMap;window.addEventListener("click",t=>{var r;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const s=n.parentElement,o=(r=n.nextElementSibling)==null?void 0:r.nextElementSibling;if(!s||!o)return;const i=/language-(shellscript|shell|bash|sh|zsh)/.test(s.className);let l="";o.querySelectorAll("span.line:not(.diff.remove)").forEach(c=>l+=(c.textContent||"")+`
+`),l=l.slice(0,-1),i&&(l=l.replace(/^ *(\$|>) /gm,"").trim()),ha(l).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const c=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,c)})}})}}async function ha(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const r=document.getSelection(),s=r?r.rangeCount>0&&r.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),s&&(r.removeAllRanges(),r.addRange(s)),n&&n.focus()}}function za(e,t){let n=[],r=!0;const s=o=>{if(r){r=!1;return}n.forEach(i=>document.head.removeChild(i)),n=[],o.forEach(i=>{const l=As(i);document.head.appendChild(l),n.push(l)})};eo(()=>{const o=e.data,i=t.value,l=o&&o.description,c=o&&o.frontmatter.head||[];document.title=Bo(i,o);const u=l||i.description;let d=document.querySelector("meta[name=description]");d?d.setAttribute("content",u):As(["meta",{name:"description",content:u}]),s(Uo(i.head,ga(c)))})}function As([e,t,n]){const r=document.createElement(e);for(const s in t)r.setAttribute(s,t[s]);return n&&(r.innerHTML=n),r}function pa(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function ga(e){return e.filter(t=>!pa(t))}const Kn=new Set,Ko=()=>document.createElement("link"),ma=e=>{const t=Ko();t.rel="prefetch",t.href=e,document.head.appendChild(t)},_a=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let tn;const ya=Ee&&(tn=Ko())&&tn.relList&&tn.relList.supports&&tn.relList.supports("prefetch")?ma:_a;function Ya(){if(!Ee||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const r=()=>{n&&n.disconnect(),n=new IntersectionObserver(o=>{o.forEach(i=>{if(i.isIntersecting){const l=i.target;n.unobserve(l);const{pathname:c}=l;if(!Kn.has(c)){Kn.add(c);const u=aa(c);u&&ya(u)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(o=>{const{hostname:i,pathname:l}=new URL(o.href instanceof SVGAnimatedString?o.href.animVal:o.href,o.baseURI),c=l.match(/\.\w+$/);c&&c[0]!==".html"||o.target!=="_blank"&&i===location.hostname&&(l!==location.pathname?n.observe(o):Kn.add(l))})})};Tt(r);const s=ko();qe(()=>s.path,r),On(()=>{n&&n.disconnect()})}export{Ra as $,Ca as A,pl as B,xa as C,Aa as D,Vs as E,ge as F,Ua as G,re as H,Ta as I,Ho as J,ko as K,kl as L,_t as M,cr as N,Cn as O,Da as P,Fa as Q,wn as R,Ea as S,Oo as T,Ha as U,ja as V,Ol as W,La as X,Oa as Y,Ma as Z,$a as _,xo as a,za as a0,ua as a1,Ba as a2,ia as a3,Ka as a4,Wa as a5,rt as a6,Na as a7,ka as a8,aa as a9,Ya as aa,qa as ab,Va as ac,tr as ad,da as ae,wo as b,Pa as c,vr as d,Ia as e,vs as f,ae as g,fe as h,ea as i,Tt as j,Eo as k,zs as l,wa as m,ar as n,bo as o,va as p,Gc as q,Sa as r,Ee as s,ba as t,la as u,Uc as v,Gi as w,qe as x,eo as y,On as z};
diff --git a/assets/chunks/theme.0d739576.js b/assets/chunks/theme.0d739576.js
new file mode 100644
index 000000000..47b41ae45
--- /dev/null
+++ b/assets/chunks/theme.0d739576.js
@@ -0,0 +1 @@
+import{d as g,o as a,c as i,r as u,n as M,a as H,t as w,_ as m,b as $,w as _,T as ie,e as f,u as Fe,i as Oe,f as ce,g as y,h as L,j as O,k as c,l,p as E,m as D,q as K,s as J,v as ae,x as R,y as ue,z as ee,A as Le,B as Ge,C as j,F as S,D as x,E as de,G as W,H as h,I as U,J as Se,K as te,L as Z,M as se,N as Me,O as Ue,P as Ce,Q as Re,R as je,S as Ke,U as qe,V as Ie,W as Ne,X as We,Y as Ye,Z as Je,$ as Xe}from"./framework.b637c96f.js";const Ze=g({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(s){return(e,t)=>(a(),i("span",{class:M(["VPBadge",e.type])},[u(e.$slots,"default",{},()=>[H(w(e.text),1)],!0)],2))}});const Qe=m(Ze,[["__scopeId","data-v-9613cc9f"]]),et={key:0,class:"VPBackdrop"},tt=g({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(s){return(e,t)=>(a(),$(ie,{name:"fade"},{default:_(()=>[e.show?(a(),i("div",et)):f("",!0)]),_:1}))}});const st=m(tt,[["__scopeId","data-v-c79a1216"]]),P=Fe;function nt(s,e){let t,n=!1;return()=>{t&&clearTimeout(t),n?t=setTimeout(s,e):(s(),(n=!0)&&setTimeout(()=>n=!1,e))}}function le(s){return/^\//.test(s)?s:`/${s}`}function Y(s){if(Oe(s))return s;const{site:e}=P(),{pathname:t,search:n,hash:o}=new URL(s,"http://a.com"),r=t.endsWith("/")||t.endsWith(".html")?s:s.replace(/(?:(^\.+)\/)?.*$/,`$1${t.replace(/(\.md)?$/,e.value.cleanUrls?"":".html")}${n}${o}`);return ce(r)}function X({removeCurrent:s=!0,correspondingLink:e=!1}={}){const{site:t,localeIndex:n,page:o,theme:r}=P(),d=y(()=>{var v,b;return{label:(v=t.value.locales[n.value])==null?void 0:v.label,link:((b=t.value.locales[n.value])==null?void 0:b.link)||(n.value==="root"?"/":`/${n.value}/`)}});return{localeLinks:y(()=>Object.entries(t.value.locales).flatMap(([v,b])=>s&&d.value.label===b.label?[]:{text:b.label,link:ot(b.link||(v==="root"?"/":`/${v}/`),r.value.i18nRouting!==!1&&e,o.value.relativePath.slice(d.value.link.length-1),!t.value.cleanUrls)})),currentLang:d}}function ot(s,e,t,n){return e?s.replace(/\/$/,"")+le(t.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,n?".html":"")):s}const at=s=>(E("data-v-f87ff6e4"),s=s(),D(),s),lt={class:"NotFound"},rt={class:"code"},it={class:"title"},ct=at(()=>c("div",{class:"divider"},null,-1)),ut={class:"quote"},dt={class:"action"},_t=["href","aria-label"],vt=g({__name:"NotFound",setup(s){const{site:e,theme:t}=P(),{localeLinks:n}=X({removeCurrent:!1}),o=L("/");return O(()=>{var d;const r=window.location.pathname.replace(e.value.base,"").replace(/(^.*?\/).*$/,"/$1");n.value.length&&(o.value=((d=n.value.find(({link:p})=>p.startsWith(r)))==null?void 0:d.link)||n.value[0].link)}),(r,d)=>{var p,v,b,N,T;return a(),i("div",lt,[c("p",rt,w(((p=l(t).notFound)==null?void 0:p.code)??"404"),1),c("h1",it,w(((v=l(t).notFound)==null?void 0:v.title)??"PAGE NOT FOUND"),1),ct,c("blockquote",ut,w(((b=l(t).notFound)==null?void 0:b.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),c("div",dt,[c("a",{class:"link",href:l(ce)(o.value),"aria-label":((N=l(t).notFound)==null?void 0:N.linkLabel)??"go to home"},w(((T=l(t).notFound)==null?void 0:T.linkText)??"Take me home"),9,_t)])])}}});const pt=m(vt,[["__scopeId","data-v-f87ff6e4"]]);function Te(s,e){if(Array.isArray(s))return Q(s);if(s==null)return[];e=le(e);const t=Object.keys(s).sort((o,r)=>r.split("/").length-o.split("/").length).find(o=>e.startsWith(le(o))),n=t?s[t]:[];return Array.isArray(n)?Q(n):Q(n.items,n.base)}function ht(s){const e=[];let t=0;for(const n in s){const o=s[n];if(o.items){t=e.push(o);continue}e[t]||e.push({items:[]}),e[t].items.push(o)}return e}function ft(s){const e=[];function t(n){for(const o of n)o.text&&o.link&&e.push({text:o.text,link:o.link,docFooterText:o.docFooterText}),o.items&&t(o.items)}return t(s),e}function re(s,e){return Array.isArray(e)?e.some(t=>re(s,t)):K(s,e.link)?!0:e.items?re(s,e.items):!1}function Q(s,e){return[...s].map(t=>{const n={...t},o=n.base||e;return o&&n.link&&(n.link=o+n.link),n.items&&(n.items=Q(n.items,o)),n})}function F(){const{frontmatter:s,page:e,theme:t}=P(),n=ae("(min-width: 960px)"),o=L(!1),r=y(()=>{const V=t.value.sidebar,k=e.value.relativePath;return V?Te(V,k):[]}),d=L(r.value);R(r,(V,k)=>{JSON.stringify(V)!==JSON.stringify(k)&&(d.value=r.value)});const p=y(()=>s.value.sidebar!==!1&&d.value.length>0&&s.value.layout!=="home"),v=y(()=>b?s.value.aside==null?t.value.aside==="left":s.value.aside==="left":!1),b=y(()=>s.value.layout==="home"?!1:s.value.aside!=null?!!s.value.aside:t.value.aside!==!1),N=y(()=>p.value&&n.value),T=y(()=>p.value?ht(d.value):[]);function A(){o.value=!0}function I(){o.value=!1}function B(){o.value?I():A()}return{isOpen:o,sidebar:d,sidebarGroups:T,hasSidebar:p,hasAside:b,leftAside:v,isSidebarEnabled:N,open:A,close:I,toggle:B}}function mt(s,e){let t;ue(()=>{t=s.value?document.activeElement:void 0}),O(()=>{window.addEventListener("keyup",n)}),ee(()=>{window.removeEventListener("keyup",n)});function n(o){o.key==="Escape"&&s.value&&(e(),t==null||t.focus())}}const Be=L(J?location.hash:"");J&&window.addEventListener("hashchange",()=>{Be.value=location.hash});function gt(s){const{page:e}=P(),t=L(!1),n=y(()=>s.value.collapsed!=null),o=y(()=>!!s.value.link),r=L(!1),d=()=>{r.value=K(e.value.relativePath,s.value.link)};R([e,s,Be],d),O(d);const p=y(()=>r.value?!0:s.value.items?re(e.value.relativePath,s.value.items):!1),v=y(()=>!!(s.value.items&&s.value.items.length));ue(()=>{t.value=!!(n.value&&s.value.collapsed)}),Le(()=>{(r.value||p.value)&&(t.value=!1)});function b(){n.value&&(t.value=!t.value)}return{collapsed:t,collapsible:n,isLink:o,isActiveLink:r,hasActiveLink:p,hasChildren:v,toggle:b}}function $t(){const{hasSidebar:s}=F(),e=ae("(min-width: 960px)"),t=ae("(min-width: 1280px)");return{isAsideEnabled:y(()=>!t.value&&!e.value?!1:s.value?t.value:e.value)}}const kt=71;function _e(s){return typeof s.outline=="object"&&!Array.isArray(s.outline)&&s.outline.label||s.outlineTitle||"On this page"}function ve(s){const e=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(t=>t.id&&t.hasChildNodes()).map(t=>{const n=Number(t.tagName[1]);return{title:bt(t),link:"#"+t.id,level:n}});return yt(e,s)}function bt(s){let e="";for(const t of s.childNodes)if(t.nodeType===1){if(t.classList.contains("VPBadge")||t.classList.contains("header-anchor"))continue;e+=t.textContent}else t.nodeType===3&&(e+=t.textContent);return e.trim()}function yt(s,e){if(e===!1)return[];const t=(typeof e=="object"&&!Array.isArray(e)?e.level:e)||2,[n,o]=typeof t=="number"?[t,t]:t==="deep"?[2,6]:t;s=s.filter(d=>d.level>=n&&d.level<=o);const r=[];e:for(let d=0;d=0;v--){const b=s[v];if(b.level{requestAnimationFrame(r),window.addEventListener("scroll",n)}),Ge(()=>{d(location.hash)}),ee(()=>{window.removeEventListener("scroll",n)});function r(){if(!t.value)return;const p=[].slice.call(s.value.querySelectorAll(".outline-link")),v=[].slice.call(document.querySelectorAll(".content .header-anchor")).filter(I=>p.some(B=>B.hash===I.hash&&I.offsetParent!==null)),b=window.scrollY,N=window.innerHeight,T=document.body.offsetHeight,A=Math.abs(b+N-T)<1;if(v.length&&A){d(v[v.length-1].hash);return}for(let I=0;I{const o=j("VPDocOutlineItem",!0);return a(),i("ul",{class:M(t.root?"root":"nested")},[(a(!0),i(S,null,x(t.headers,({children:r,link:d,title:p})=>(a(),i("li",null,[c("a",{class:"outline-link",href:d,onClick:e,title:p},w(p),9,wt),r!=null&&r.length?(a(),$(o,{key:0,headers:r},null,8,["headers"])):f("",!0)]))),256))],2)}}});const pe=m(Lt,[["__scopeId","data-v-d0ee3533"]]),St=s=>(E("data-v-6ae8e080"),s=s(),D(),s),Mt={class:"content"},Ct={class:"outline-title",role:"heading"},It={"aria-labelledby":"doc-outline-aria-label"},Nt=St(()=>c("span",{class:"visually-hidden",id:"doc-outline-aria-label"}," Table of Contents for current page ",-1)),Tt=g({__name:"VPDocAsideOutline",setup(s){const{frontmatter:e,theme:t}=P(),n=de([]);W(()=>{n.value=ve(e.value.outline??t.value.outline)});const o=L(),r=L();return Pt(o,r),(d,p)=>(a(),i("div",{class:M(["VPDocAsideOutline",{"has-outline":n.value.length>0}]),ref_key:"container",ref:o,role:"navigation"},[c("div",Mt,[c("div",{class:"outline-marker",ref_key:"marker",ref:r},null,512),c("div",Ct,w(l(_e)(l(t))),1),c("nav",It,[Nt,h(pe,{headers:n.value,root:!0},null,8,["headers"])])])],2))}});const Bt=m(Tt,[["__scopeId","data-v-6ae8e080"]]),At={class:"VPDocAsideCarbonAds"},xt=g({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(s){const e=()=>null;return(t,n)=>(a(),i("div",At,[h(l(e),{"carbon-ads":t.carbonAds},null,8,["carbon-ads"])]))}}),Ht=s=>(E("data-v-3f215769"),s=s(),D(),s),zt={class:"VPDocAside"},Et=Ht(()=>c("div",{class:"spacer"},null,-1)),Dt=g({__name:"VPDocAside",setup(s){const{theme:e}=P();return(t,n)=>(a(),i("div",zt,[u(t.$slots,"aside-top",{},void 0,!0),u(t.$slots,"aside-outline-before",{},void 0,!0),h(Bt),u(t.$slots,"aside-outline-after",{},void 0,!0),Et,u(t.$slots,"aside-ads-before",{},void 0,!0),l(e).carbonAds?(a(),$(xt,{key:0,"carbon-ads":l(e).carbonAds},null,8,["carbon-ads"])):f("",!0),u(t.$slots,"aside-ads-after",{},void 0,!0),u(t.$slots,"aside-bottom",{},void 0,!0)]))}});const Ft=m(Dt,[["__scopeId","data-v-3f215769"]]);function Ot(){const{theme:s,page:e}=P();return y(()=>{const{text:t="Edit this page",pattern:n=""}=s.value.editLink||{};let o;return typeof n=="function"?o=n(e.value):o=n.replace(/:path/g,e.value.filePath),{url:o,text:t}})}function Gt(){const{page:s,theme:e,frontmatter:t}=P();return y(()=>{var v,b,N,T,A,I,B,V;const n=Te(e.value.sidebar,s.value.relativePath),o=ft(n),r=o.findIndex(k=>K(s.value.relativePath,k.link)),d=((v=e.value.docFooter)==null?void 0:v.prev)===!1&&!t.value.prev||t.value.prev===!1,p=((b=e.value.docFooter)==null?void 0:b.next)===!1&&!t.value.next||t.value.next===!1;return{prev:d?void 0:{text:(typeof t.value.prev=="string"?t.value.prev:typeof t.value.prev=="object"?t.value.prev.text:void 0)??((N=o[r-1])==null?void 0:N.docFooterText)??((T=o[r-1])==null?void 0:T.text),link:(typeof t.value.prev=="object"?t.value.prev.link:void 0)??((A=o[r-1])==null?void 0:A.link)},next:p?void 0:{text:(typeof t.value.next=="string"?t.value.next:typeof t.value.next=="object"?t.value.next.text:void 0)??((I=o[r+1])==null?void 0:I.docFooterText)??((B=o[r+1])==null?void 0:B.text),link:(typeof t.value.next=="object"?t.value.next.link:void 0)??((V=o[r+1])==null?void 0:V.link)}}})}const Ut={},Rt={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},jt=c("path",{d:"M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"},null,-1),Kt=c("path",{d:"M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"},null,-1),qt=[jt,Kt];function Wt(s,e){return a(),i("svg",Rt,qt)}const Yt=m(Ut,[["render",Wt]]),G=g({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(s){const e=s,t=y(()=>e.tag??(e.href?"a":"span")),n=y(()=>e.href&&Se.test(e.href));return(o,r)=>(a(),$(U(t.value),{class:M(["VPLink",{link:o.href,"vp-external-link-icon":n.value,"no-icon":o.noIcon}]),href:o.href?l(Y)(o.href):void 0,target:o.target??(n.value?"_blank":void 0),rel:o.rel??(n.value?"noreferrer":void 0)},{default:_(()=>[u(o.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),Jt={class:"VPLastUpdated"},Xt=["datetime"],Zt=g({__name:"VPDocFooterLastUpdated",setup(s){const{theme:e,page:t,frontmatter:n}=P(),o=y(()=>new Date(n.value.lastUpdated??t.value.lastUpdated)),r=y(()=>o.value.toISOString()),d=L("");return O(()=>{ue(()=>{var p;d.value=new Intl.DateTimeFormat(void 0,((p=e.value.lastUpdated)==null?void 0:p.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(o.value)})}),(p,v)=>{var b;return a(),i("p",Jt,[H(w(((b=l(e).lastUpdated)==null?void 0:b.text)||l(e).lastUpdatedText||"Last updated")+": ",1),c("time",{datetime:r.value},w(d.value),9,Xt)])}}});const Qt=m(Zt,[["__scopeId","data-v-7de715c0"]]),es={key:0,class:"VPDocFooter"},ts={key:0,class:"edit-info"},ss={key:0,class:"edit-link"},ns={key:1,class:"last-updated"},os={key:1,class:"prev-next"},as={class:"pager"},ls=["href"],rs=["innerHTML"],is=["innerHTML"],cs={class:"pager"},us=["href"],ds=["innerHTML"],_s=["innerHTML"],vs=g({__name:"VPDocFooter",setup(s){const{theme:e,page:t,frontmatter:n}=P(),o=Ot(),r=Gt(),d=y(()=>e.value.editLink&&n.value.editLink!==!1),p=y(()=>t.value.lastUpdated&&n.value.lastUpdated!==!1),v=y(()=>d.value||p.value||r.value.prev||r.value.next);return(b,N)=>{var T,A,I,B,V,k;return v.value?(a(),i("footer",es,[u(b.$slots,"doc-footer-before",{},void 0,!0),d.value||p.value?(a(),i("div",ts,[d.value?(a(),i("div",ss,[h(G,{class:"edit-link-button",href:l(o).url,"no-icon":!0},{default:_(()=>[h(Yt,{class:"edit-link-icon","aria-label":"edit icon"}),H(" "+w(l(o).text),1)]),_:1},8,["href"])])):f("",!0),p.value?(a(),i("div",ns,[h(Qt)])):f("",!0)])):f("",!0),(T=l(r).prev)!=null&&T.link||(A=l(r).next)!=null&&A.link?(a(),i("nav",os,[c("div",as,[(I=l(r).prev)!=null&&I.link?(a(),i("a",{key:0,class:"pager-link prev",href:l(Y)(l(r).prev.link)},[c("span",{class:"desc",innerHTML:((B=l(e).docFooter)==null?void 0:B.prev)||"Previous page"},null,8,rs),c("span",{class:"title",innerHTML:l(r).prev.text},null,8,is)],8,ls)):f("",!0)]),c("div",cs,[(V=l(r).next)!=null&&V.link?(a(),i("a",{key:0,class:"pager-link next",href:l(Y)(l(r).next.link)},[c("span",{class:"desc",innerHTML:((k=l(e).docFooter)==null?void 0:k.next)||"Next page"},null,8,ds),c("span",{class:"title",innerHTML:l(r).next.text},null,8,_s)],8,us)):f("",!0)])])):f("",!0)])):f("",!0)}}});const ps=m(vs,[["__scopeId","data-v-ef5dee53"]]),hs={},fs={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},ms=c("path",{d:"M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"},null,-1),gs=[ms];function $s(s,e){return a(),i("svg",fs,gs)}const he=m(hs,[["render",$s]]),ks={key:0,class:"VPDocOutlineDropdown"},bs={key:0,class:"items"},ys=g({__name:"VPDocOutlineDropdown",setup(s){const{frontmatter:e,theme:t}=P(),n=L(!1);W(()=>{n.value=!1});const o=de([]);return W(()=>{o.value=ve(e.value.outline??t.value.outline)}),(r,d)=>o.value.length>0?(a(),i("div",ks,[c("button",{onClick:d[0]||(d[0]=p=>n.value=!n.value),class:M({open:n.value})},[H(w(l(_e)(l(t)))+" ",1),h(he,{class:"icon"})],2),n.value?(a(),i("div",bs,[h(pe,{headers:o.value},null,8,["headers"])])):f("",!0)])):f("",!0)}});const Ps=m(ys,[["__scopeId","data-v-eadfb36b"]]),Vs=s=>(E("data-v-6b87e69f"),s=s(),D(),s),ws={class:"container"},Ls=Vs(()=>c("div",{class:"aside-curtain"},null,-1)),Ss={class:"aside-container"},Ms={class:"aside-content"},Cs={class:"content"},Is={class:"content-container"},Ns={class:"main"},Ts=g({__name:"VPDoc",setup(s){const{theme:e}=P(),t=te(),{hasSidebar:n,hasAside:o,leftAside:r}=F(),d=y(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(p,v)=>{const b=j("Content");return a(),i("div",{class:M(["VPDoc",{"has-sidebar":l(n),"has-aside":l(o)}])},[u(p.$slots,"doc-top",{},void 0,!0),c("div",ws,[l(o)?(a(),i("div",{key:0,class:M(["aside",{"left-aside":l(r)}])},[Ls,c("div",Ss,[c("div",Ms,[h(Ft,null,{"aside-top":_(()=>[u(p.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":_(()=>[u(p.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":_(()=>[u(p.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":_(()=>[u(p.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":_(()=>[u(p.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":_(()=>[u(p.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):f("",!0),c("div",Cs,[c("div",Is,[u(p.$slots,"doc-before",{},void 0,!0),h(Ps),c("main",Ns,[h(b,{class:M(["vp-doc",[d.value,l(e).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),h(ps,null,{"doc-footer-before":_(()=>[u(p.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),u(p.$slots,"doc-after",{},void 0,!0)])])]),u(p.$slots,"doc-bottom",{},void 0,!0)],2)}}});const Bs=m(Ts,[["__scopeId","data-v-6b87e69f"]]),As=g({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{}},setup(s){const e=s,t=y(()=>e.href&&Se.test(e.href)),n=y(()=>e.tag||e.href?"a":"button");return(o,r)=>(a(),$(U(n.value),{class:M(["VPButton",[o.size,o.theme]]),href:o.href?l(Y)(o.href):void 0,target:t.value?"_blank":void 0,rel:t.value?"noreferrer":void 0},{default:_(()=>[H(w(o.text),1)]),_:1},8,["class","href","target","rel"]))}});const xs=m(As,[["__scopeId","data-v-c1c5efc1"]]),Hs=["src","alt"],zs=g({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(s){return(e,t)=>{const n=j("VPImage",!0);return e.image?(a(),i(S,{key:0},[typeof e.image=="string"||"src"in e.image?(a(),i("img",Z({key:0,class:"VPImage"},typeof e.image=="string"?e.$attrs:{...e.image,...e.$attrs},{src:l(ce)(typeof e.image=="string"?e.image:e.image.src),alt:e.alt??(typeof e.image=="string"?"":e.image.alt||"")}),null,16,Hs)):(a(),i(S,{key:1},[h(n,Z({class:"dark",image:e.image.dark,alt:e.image.alt},e.$attrs),null,16,["image","alt"]),h(n,Z({class:"light",image:e.image.light,alt:e.image.alt},e.$attrs),null,16,["image","alt"])],64))],64)):f("",!0)}}});const fe=m(zs,[["__scopeId","data-v-8426fc1a"]]),Es=s=>(E("data-v-da5d1713"),s=s(),D(),s),Ds={class:"container"},Fs={class:"main"},Os={key:0,class:"name"},Gs=["innerHTML"],Us=["innerHTML"],Rs=["innerHTML"],js={key:0,class:"actions"},Ks={key:0,class:"image"},qs={class:"image-container"},Ws=Es(()=>c("div",{class:"image-bg"},null,-1)),Ys=g({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(s){const e=se("hero-image-slot-exists");return(t,n)=>(a(),i("div",{class:M(["VPHero",{"has-image":t.image||l(e)}])},[c("div",Ds,[c("div",Fs,[u(t.$slots,"home-hero-info",{},()=>[t.name?(a(),i("h1",Os,[c("span",{innerHTML:t.name,class:"clip"},null,8,Gs)])):f("",!0),t.text?(a(),i("p",{key:1,innerHTML:t.text,class:"text"},null,8,Us)):f("",!0),t.tagline?(a(),i("p",{key:2,innerHTML:t.tagline,class:"tagline"},null,8,Rs)):f("",!0)],!0),t.actions?(a(),i("div",js,[(a(!0),i(S,null,x(t.actions,o=>(a(),i("div",{key:o.link,class:"action"},[h(xs,{tag:"a",size:"medium",theme:o.theme,text:o.text,href:o.link},null,8,["theme","text","href"])]))),128))])):f("",!0)]),t.image||l(e)?(a(),i("div",Ks,[c("div",qs,[Ws,u(t.$slots,"home-hero-image",{},()=>[t.image?(a(),$(fe,{key:0,class:"image-src",image:t.image},null,8,["image"])):f("",!0)],!0)])])):f("",!0)])],2))}});const Js=m(Ys,[["__scopeId","data-v-da5d1713"]]),Xs=g({__name:"VPHomeHero",setup(s){const{frontmatter:e}=P();return(t,n)=>l(e).hero?(a(),$(Js,{key:0,class:"VPHomeHero",name:l(e).hero.name,text:l(e).hero.text,tagline:l(e).hero.tagline,image:l(e).hero.image,actions:l(e).hero.actions},{"home-hero-info":_(()=>[u(t.$slots,"home-hero-info")]),"home-hero-image":_(()=>[u(t.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):f("",!0)}}),Zs={},Qs={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},en=c("path",{d:"M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"},null,-1),tn=[en];function sn(s,e){return a(),i("svg",Qs,tn)}const nn=m(Zs,[["render",sn]]),on={class:"box"},an=["innerHTML"],ln=["innerHTML"],rn=["innerHTML"],cn={key:3,class:"link-text"},un={class:"link-text-value"},dn=g({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{}},setup(s){return(e,t)=>(a(),$(G,{class:"VPFeature",href:e.link,rel:e.rel,"no-icon":!0,tag:e.link?"a":"div"},{default:_(()=>[c("article",on,[typeof e.icon=="object"?(a(),$(fe,{key:0,image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])):e.icon?(a(),i("div",{key:1,class:"icon",innerHTML:e.icon},null,8,an)):f("",!0),c("h2",{class:"title",innerHTML:e.title},null,8,ln),e.details?(a(),i("p",{key:2,class:"details",innerHTML:e.details},null,8,rn)):f("",!0),e.linkText?(a(),i("div",cn,[c("p",un,[H(w(e.linkText)+" ",1),h(nn,{class:"link-text-icon"})])])):f("",!0)])]),_:1},8,["href","rel","tag"]))}});const _n=m(dn,[["__scopeId","data-v-3e216711"]]),vn={key:0,class:"VPFeatures"},pn={class:"container"},hn={class:"items"},fn=g({__name:"VPFeatures",props:{features:{}},setup(s){const e=s,t=y(()=>{const n=e.features.length;if(n){if(n===2)return"grid-2";if(n===3)return"grid-3";if(n%3===0)return"grid-6";if(n>3)return"grid-4"}else return});return(n,o)=>n.features?(a(),i("div",vn,[c("div",pn,[c("div",hn,[(a(!0),i(S,null,x(n.features,r=>(a(),i("div",{key:r.title,class:M(["item",[t.value]])},[h(_n,{icon:r.icon,title:r.title,details:r.details,link:r.link,"link-text":r.linkText,rel:r.rel},null,8,["icon","title","details","link","link-text","rel"])],2))),128))])])])):f("",!0)}});const mn=m(fn,[["__scopeId","data-v-39646fad"]]),gn=g({__name:"VPHomeFeatures",setup(s){const{frontmatter:e}=P();return(t,n)=>l(e).features?(a(),$(mn,{key:0,class:"VPHomeFeatures",features:l(e).features},null,8,["features"])):f("",!0)}}),$n={class:"VPHome"},kn=g({__name:"VPHome",setup(s){return(e,t)=>{const n=j("Content");return a(),i("div",$n,[u(e.$slots,"home-hero-before",{},void 0,!0),h(Xs,null,{"home-hero-info":_(()=>[u(e.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":_(()=>[u(e.$slots,"home-hero-image",{},void 0,!0)]),_:3}),u(e.$slots,"home-hero-after",{},void 0,!0),u(e.$slots,"home-features-before",{},void 0,!0),h(gn),u(e.$slots,"home-features-after",{},void 0,!0),h(n)])}}});const bn=m(kn,[["__scopeId","data-v-d82743a8"]]),yn={},Pn={class:"VPPage"};function Vn(s,e){const t=j("Content");return a(),i("div",Pn,[u(s.$slots,"page-top"),h(t),u(s.$slots,"page-bottom")])}const wn=m(yn,[["render",Vn]]),Ln=g({__name:"VPContent",setup(s){const{page:e,frontmatter:t}=P(),{hasSidebar:n}=F();return(o,r)=>(a(),i("div",{class:M(["VPContent",{"has-sidebar":l(n),"is-home":l(t).layout==="home"}]),id:"VPContent"},[l(e).isNotFound?u(o.$slots,"not-found",{key:0},()=>[h(pt)],!0):l(t).layout==="page"?(a(),$(wn,{key:1},{"page-top":_(()=>[u(o.$slots,"page-top",{},void 0,!0)]),"page-bottom":_(()=>[u(o.$slots,"page-bottom",{},void 0,!0)]),_:3})):l(t).layout==="home"?(a(),$(bn,{key:2},{"home-hero-before":_(()=>[u(o.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info":_(()=>[u(o.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":_(()=>[u(o.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":_(()=>[u(o.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":_(()=>[u(o.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":_(()=>[u(o.$slots,"home-features-after",{},void 0,!0)]),_:3})):l(t).layout&&l(t).layout!=="doc"?(a(),$(U(l(t).layout),{key:3})):(a(),$(Bs,{key:4},{"doc-top":_(()=>[u(o.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":_(()=>[u(o.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":_(()=>[u(o.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":_(()=>[u(o.$slots,"doc-before",{},void 0,!0)]),"doc-after":_(()=>[u(o.$slots,"doc-after",{},void 0,!0)]),"aside-top":_(()=>[u(o.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":_(()=>[u(o.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":_(()=>[u(o.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":_(()=>[u(o.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":_(()=>[u(o.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":_(()=>[u(o.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}});const Sn=m(Ln,[["__scopeId","data-v-669faec9"]]),Mn={class:"container"},Cn=["innerHTML"],In=["innerHTML"],Nn=g({__name:"VPFooter",setup(s){const{theme:e,frontmatter:t}=P(),{hasSidebar:n}=F();return(o,r)=>l(e).footer&&l(t).footer!==!1?(a(),i("footer",{key:0,class:M(["VPFooter",{"has-sidebar":l(n)}])},[c("div",Mn,[l(e).footer.message?(a(),i("p",{key:0,class:"message",innerHTML:l(e).footer.message},null,8,Cn)):f("",!0),l(e).footer.copyright?(a(),i("p",{key:1,class:"copyright",innerHTML:l(e).footer.copyright},null,8,In)):f("",!0)])],2)):f("",!0)}});const Tn=m(Nn,[["__scopeId","data-v-e03eb2e1"]]),Bn={class:"header"},An={class:"outline"},xn=g({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(s){const e=s,{theme:t}=P(),n=L(!1),o=L(0),r=L();W(()=>{n.value=!1});function d(){n.value=!n.value,o.value=window.innerHeight+Math.min(window.scrollY-e.navHeight,0)}function p(b){b.target.classList.contains("outline-link")&&(r.value&&(r.value.style.transition="none"),Ue(()=>{n.value=!1}))}function v(){n.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return(b,N)=>(a(),i("div",{class:"VPLocalNavOutlineDropdown",style:Me({"--vp-vh":o.value+"px"})},[b.headers.length>0?(a(),i("button",{key:0,onClick:d,class:M({open:n.value})},[H(w(l(_e)(l(t)))+" ",1),h(he,{class:"icon"})],2)):(a(),i("button",{key:1,onClick:v},w(l(t).returnToTopLabel||"Return to top"),1)),h(ie,{name:"flyout"},{default:_(()=>[n.value?(a(),i("div",{key:0,ref_key:"items",ref:r,class:"items",onClick:p},[c("div",Bn,[c("a",{class:"top-link",href:"#",onClick:v},w(l(t).returnToTopLabel||"Return to top"),1)]),c("div",An,[h(pe,{headers:b.headers},null,8,["headers"])])],512)):f("",!0)]),_:1})],4))}});const Hn=m(xn,[["__scopeId","data-v-1c15a60a"]]),zn={},En={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Dn=c("path",{d:"M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"},null,-1),Fn=c("path",{d:"M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"},null,-1),On=c("path",{d:"M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"},null,-1),Gn=c("path",{d:"M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"},null,-1),Un=[Dn,Fn,On,Gn];function Rn(s,e){return a(),i("svg",En,Un)}const jn=m(zn,[["render",Rn]]),Kn=["aria-expanded"],qn={class:"menu-text"},Wn=g({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(s){const{theme:e,frontmatter:t}=P(),{hasSidebar:n}=F(),{y:o}=Ce(),r=de([]),d=L(0);O(()=>{d.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),W(()=>{r.value=ve(t.value.outline??e.value.outline)});const p=y(()=>r.value.length===0&&!n.value),v=y(()=>({VPLocalNav:!0,fixed:p.value,"reached-top":o.value>=d.value}));return(b,N)=>l(t).layout!=="home"&&(!p.value||l(o)>=d.value)?(a(),i("div",{key:0,class:M(v.value)},[l(n)?(a(),i("button",{key:0,class:"menu","aria-expanded":b.open,"aria-controls":"VPSidebarNav",onClick:N[0]||(N[0]=T=>b.$emit("open-menu"))},[h(jn,{class:"menu-icon"}),c("span",qn,w(l(e).sidebarMenuLabel||"Menu"),1)],8,Kn)):f("",!0),h(Hn,{headers:r.value,navHeight:d.value},null,8,["headers","navHeight"])],2)):f("",!0)}});const Yn=m(Wn,[["__scopeId","data-v-79c8c1df"]]);function Jn(){const s=L(!1);function e(){s.value=!0,window.addEventListener("resize",o)}function t(){s.value=!1,window.removeEventListener("resize",o)}function n(){s.value?t():e()}function o(){window.outerWidth>=768&&t()}const r=te();return R(()=>r.path,t),{isScreenOpen:s,openScreen:e,closeScreen:t,toggleScreen:n}}const Xn={},Zn={class:"VPSwitch",type:"button",role:"switch"},Qn={class:"check"},eo={key:0,class:"icon"};function to(s,e){return a(),i("button",Zn,[c("span",Qn,[s.$slots.default?(a(),i("span",eo,[u(s.$slots,"default",{},void 0,!0)])):f("",!0)])])}const so=m(Xn,[["render",to],["__scopeId","data-v-b1685198"]]),no={},oo={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},ao=c("path",{d:"M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"},null,-1),lo=[ao];function ro(s,e){return a(),i("svg",oo,lo)}const io=m(no,[["render",ro]]),co={},uo={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},_o=Re(' ',9),vo=[_o];function po(s,e){return a(),i("svg",uo,vo)}const ho=m(co,[["render",po]]),fo=g({__name:"VPSwitchAppearance",setup(s){const{isDark:e}=P(),t=se("toggle-appearance",()=>{e.value=!e.value});return(n,o)=>(a(),$(so,{title:"toggle dark mode",class:"VPSwitchAppearance","aria-checked":l(e),onClick:l(t)},{default:_(()=>[h(ho,{class:"sun"}),h(io,{class:"moon"})]),_:1},8,["aria-checked","onClick"]))}});const me=m(fo,[["__scopeId","data-v-ce54a7d1"]]),mo={key:0,class:"VPNavBarAppearance"},go=g({__name:"VPNavBarAppearance",setup(s){const{site:e}=P();return(t,n)=>l(e).appearance?(a(),i("div",mo,[h(me)])):f("",!0)}});const $o=m(go,[["__scopeId","data-v-f6a63727"]]),ge=L();let Ae=!1,oe=0;function ko(s){const e=L(!1);if(J){!Ae&&bo(),oe++;const t=R(ge,n=>{var o,r,d;n===s.el.value||(o=s.el.value)!=null&&o.contains(n)?(e.value=!0,(r=s.onFocus)==null||r.call(s)):(e.value=!1,(d=s.onBlur)==null||d.call(s))});ee(()=>{t(),oe--,oe||yo()})}return je(e)}function bo(){document.addEventListener("focusin",xe),Ae=!0,ge.value=document.activeElement}function yo(){document.removeEventListener("focusin",xe)}function xe(){ge.value=document.activeElement}const Po={},Vo={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},wo=c("path",{d:"M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"},null,-1),Lo=[wo];function So(s,e){return a(),i("svg",Vo,Lo)}const He=m(Po,[["render",So]]),Mo={},Co={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Io=c("circle",{cx:"12",cy:"12",r:"2"},null,-1),No=c("circle",{cx:"19",cy:"12",r:"2"},null,-1),To=c("circle",{cx:"5",cy:"12",r:"2"},null,-1),Bo=[Io,No,To];function Ao(s,e){return a(),i("svg",Co,Bo)}const xo=m(Mo,[["render",Ao]]),Ho={class:"VPMenuLink"},zo=g({__name:"VPMenuLink",props:{item:{}},setup(s){const{page:e}=P();return(t,n)=>(a(),i("div",Ho,[h(G,{class:M({active:l(K)(l(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel},{default:_(()=>[H(w(t.item.text),1)]),_:1},8,["class","href","target","rel"])]))}});const ne=m(zo,[["__scopeId","data-v-43f1e123"]]),Eo={class:"VPMenuGroup"},Do={key:0,class:"title"},Fo=g({__name:"VPMenuGroup",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),i("div",Eo,[e.text?(a(),i("p",Do,w(e.text),1)):f("",!0),(a(!0),i(S,null,x(e.items,n=>(a(),i(S,null,["link"in n?(a(),$(ne,{key:0,item:n},null,8,["item"])):f("",!0)],64))),256))]))}});const Oo=m(Fo,[["__scopeId","data-v-69e747b5"]]),Go={class:"VPMenu"},Uo={key:0,class:"items"},Ro=g({__name:"VPMenu",props:{items:{}},setup(s){return(e,t)=>(a(),i("div",Go,[e.items?(a(),i("div",Uo,[(a(!0),i(S,null,x(e.items,n=>(a(),i(S,{key:n.text},["link"in n?(a(),$(ne,{key:0,item:n},null,8,["item"])):(a(),$(Oo,{key:1,text:n.text,items:n.items},null,8,["text","items"]))],64))),128))])):f("",!0),u(e.$slots,"default",{},void 0,!0)]))}});const jo=m(Ro,[["__scopeId","data-v-e7ea1737"]]),Ko=["aria-expanded","aria-label"],qo={key:0,class:"text"},Wo=["innerHTML"],Yo={class:"menu"},Jo=g({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(s){const e=L(!1),t=L();ko({el:t,onBlur:n});function n(){e.value=!1}return(o,r)=>(a(),i("div",{class:"VPFlyout",ref_key:"el",ref:t,onMouseenter:r[1]||(r[1]=d=>e.value=!0),onMouseleave:r[2]||(r[2]=d=>e.value=!1)},[c("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":e.value,"aria-label":o.label,onClick:r[0]||(r[0]=d=>e.value=!e.value)},[o.button||o.icon?(a(),i("span",qo,[o.icon?(a(),$(U(o.icon),{key:0,class:"option-icon"})):f("",!0),o.button?(a(),i("span",{key:1,innerHTML:o.button},null,8,Wo)):f("",!0),h(He,{class:"text-icon"})])):(a(),$(xo,{key:1,class:"icon"}))],8,Ko),c("div",Yo,[h(jo,{items:o.items},{default:_(()=>[u(o.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}});const $e=m(Jo,[["__scopeId","data-v-9c007e85"]]),Xo={discord:'Discord ',facebook:'Facebook ',github:'GitHub ',instagram:'Instagram ',linkedin:'LinkedIn ',mastodon:'Mastodon ',slack:'Slack ',twitter:'Twitter ',x:'X ',youtube:'YouTube '},Zo=["href","aria-label","innerHTML"],Qo=g({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(s){const e=s,t=y(()=>typeof e.icon=="object"?e.icon.svg:Xo[e.icon]);return(n,o)=>(a(),i("a",{class:"VPSocialLink no-icon",href:n.link,"aria-label":n.ariaLabel??(typeof n.icon=="string"?n.icon:""),target:"_blank",rel:"noopener",innerHTML:t.value},null,8,Zo))}});const ea=m(Qo,[["__scopeId","data-v-f80f8133"]]),ta={class:"VPSocialLinks"},sa=g({__name:"VPSocialLinks",props:{links:{}},setup(s){return(e,t)=>(a(),i("div",ta,[(a(!0),i(S,null,x(e.links,({link:n,icon:o,ariaLabel:r})=>(a(),$(ea,{key:n,icon:o,link:n,ariaLabel:r},null,8,["icon","link","ariaLabel"]))),128))]))}});const ke=m(sa,[["__scopeId","data-v-7bc22406"]]),na={key:0,class:"group translations"},oa={class:"trans-title"},aa={key:1,class:"group"},la={class:"item appearance"},ra={class:"label"},ia={class:"appearance-action"},ca={key:2,class:"group"},ua={class:"item social-links"},da=g({__name:"VPNavBarExtra",setup(s){const{site:e,theme:t}=P(),{localeLinks:n,currentLang:o}=X({correspondingLink:!0}),r=y(()=>n.value.length&&o.value.label||e.value.appearance||t.value.socialLinks);return(d,p)=>r.value?(a(),$($e,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:_(()=>[l(n).length&&l(o).label?(a(),i("div",na,[c("p",oa,w(l(o).label),1),(a(!0),i(S,null,x(l(n),v=>(a(),$(ne,{key:v.link,item:v},null,8,["item"]))),128))])):f("",!0),l(e).appearance?(a(),i("div",aa,[c("div",la,[c("p",ra,w(l(t).darkModeSwitchLabel||"Appearance"),1),c("div",ia,[h(me)])])])):f("",!0),l(t).socialLinks?(a(),i("div",ca,[c("div",ua,[h(ke,{class:"social-links-list",links:l(t).socialLinks},null,8,["links"])])])):f("",!0)]),_:1})):f("",!0)}});const _a=m(da,[["__scopeId","data-v-40855f84"]]),va=s=>(E("data-v-e5dd9c1c"),s=s(),D(),s),pa=["aria-expanded"],ha=va(()=>c("span",{class:"container"},[c("span",{class:"top"}),c("span",{class:"middle"}),c("span",{class:"bottom"})],-1)),fa=[ha],ma=g({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(s){return(e,t)=>(a(),i("button",{type:"button",class:M(["VPNavBarHamburger",{active:e.active}]),"aria-label":"mobile navigation","aria-expanded":e.active,"aria-controls":"VPNavScreen",onClick:t[0]||(t[0]=n=>e.$emit("click"))},fa,10,pa))}});const ga=m(ma,[["__scopeId","data-v-e5dd9c1c"]]),$a=["innerHTML"],ka=g({__name:"VPNavBarMenuLink",props:{item:{}},setup(s){const{page:e}=P();return(t,n)=>(a(),$(G,{class:M({VPNavBarMenuLink:!0,active:l(K)(l(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel,tabindex:"0"},{default:_(()=>[c("span",{innerHTML:t.item.text},null,8,$a)]),_:1},8,["class","href","target","rel"]))}});const ba=m(ka,[["__scopeId","data-v-42ef59de"]]),ya=g({__name:"VPNavBarMenuGroup",props:{item:{}},setup(s){const{page:e}=P();return(t,n)=>(a(),$($e,{class:M({VPNavBarMenuGroup:!0,active:l(K)(l(e).relativePath,t.item.activeMatch,!!t.item.activeMatch)}),button:t.item.text,items:t.item.items},null,8,["class","button","items"]))}}),Pa=s=>(E("data-v-7f418b0f"),s=s(),D(),s),Va={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},wa=Pa(()=>c("span",{id:"main-nav-aria-label",class:"visually-hidden"},"Main Navigation",-1)),La=g({__name:"VPNavBarMenu",setup(s){const{theme:e}=P();return(t,n)=>l(e).nav?(a(),i("nav",Va,[wa,(a(!0),i(S,null,x(l(e).nav,o=>(a(),i(S,{key:o.text},["link"in o?(a(),$(ba,{key:0,item:o},null,8,["item"])):(a(),$(ya,{key:1,item:o},null,8,["item"]))],64))),128))])):f("",!0)}});const Sa=m(La,[["__scopeId","data-v-7f418b0f"]]);const Ma={type:"button",class:"DocSearch DocSearch-Button","aria-label":"Search"},Ca={class:"DocSearch-Button-Container"},Ia=c("svg",{class:"DocSearch-Search-Icon",width:"20",height:"20",viewBox:"0 0 20 20","aria-label":"search icon"},[c("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none","fill-rule":"evenodd","stroke-linecap":"round","stroke-linejoin":"round"})],-1),Na={class:"DocSearch-Button-Placeholder"},Ta=c("span",{class:"DocSearch-Button-Keys"},[c("kbd",{class:"DocSearch-Button-Key"}),c("kbd",{class:"DocSearch-Button-Key"},"K")],-1),we=g({__name:"VPNavBarSearchButton",props:{placeholder:{}},setup(s){return(e,t)=>(a(),i("button",Ma,[c("span",Ca,[Ia,c("span",Na,w(e.placeholder),1)]),Ta]))}});const Ba={id:"local-search"},Aa={key:1,id:"docsearch"},xa=g({__name:"VPNavBarSearch",setup(s){const e=()=>null,t=Ke(()=>qe(()=>import("./VPAlgoliaSearchBox.377a8f85.js"),["assets/chunks/VPAlgoliaSearchBox.377a8f85.js","assets/chunks/framework.b637c96f.js"])),{theme:n,localeIndex:o}=P(),r=L(!1),d=L(!1),p=y(()=>{var k,C,z,q,be,ye,Pe;const V=((k=n.value.search)==null?void 0:k.options)??n.value.algolia;return((be=(q=(z=(C=V==null?void 0:V.locales)==null?void 0:C[o.value])==null?void 0:z.translations)==null?void 0:q.button)==null?void 0:be.buttonText)||((Pe=(ye=V==null?void 0:V.translations)==null?void 0:ye.button)==null?void 0:Pe.buttonText)||"Search"}),v=()=>{const V="VPAlgoliaPreconnect";(window.requestIdleCallback||setTimeout)(()=>{var z;const C=document.createElement("link");C.id=V,C.rel="preconnect",C.href=`https://${(((z=n.value.search)==null?void 0:z.options)??n.value.algolia).appId}-dsn.algolia.net`,C.crossOrigin="",document.head.appendChild(C)})};O(()=>{v();const V=C=>{(C.key.toLowerCase()==="k"&&(C.metaKey||C.ctrlKey)||!T(C)&&C.key==="/")&&(C.preventDefault(),b(),k())},k=()=>{window.removeEventListener("keydown",V)};window.addEventListener("keydown",V),ee(k)});function b(){r.value||(r.value=!0,setTimeout(N,16))}function N(){const V=new Event("keydown");V.key="k",V.metaKey=!0,window.dispatchEvent(V),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||N()},16)}function T(V){const k=V.target,C=k.tagName;return k.isContentEditable||C==="INPUT"||C==="SELECT"||C==="TEXTAREA"}const A=L(!1),I=L("'Meta'");O(()=>{I.value=/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)?"'⌘'":"'Ctrl'"});const B="algolia";return(V,k)=>{var C;return a(),i("div",{class:"VPNavBarSearch",style:Me({"--vp-meta-key":I.value})},[l(B)==="local"?(a(),i(S,{key:0},[A.value?(a(),$(l(e),{key:0,placeholder:p.value,onClose:k[0]||(k[0]=z=>A.value=!1)},null,8,["placeholder"])):f("",!0),c("div",Ba,[h(we,{placeholder:p.value,onClick:k[1]||(k[1]=z=>A.value=!0)},null,8,["placeholder"])])],64)):l(B)==="algolia"?(a(),i(S,{key:1},[r.value?(a(),$(l(t),{key:0,algolia:((C=l(n).search)==null?void 0:C.options)??l(n).algolia,onVnodeBeforeMount:k[2]||(k[2]=z=>d.value=!0)},null,8,["algolia"])):f("",!0),d.value?f("",!0):(a(),i("div",Aa,[h(we,{placeholder:p.value,onClick:b},null,8,["placeholder"])]))],64)):f("",!0)],4)}}});const Ha=g({__name:"VPNavBarSocialLinks",setup(s){const{theme:e}=P();return(t,n)=>l(e).socialLinks?(a(),$(ke,{key:0,class:"VPNavBarSocialLinks",links:l(e).socialLinks},null,8,["links"])):f("",!0)}});const za=m(Ha,[["__scopeId","data-v-0394ad82"]]),Ea=["href"],Da=g({__name:"VPNavBarTitle",setup(s){const{site:e,theme:t}=P(),{hasSidebar:n}=F(),{currentLang:o}=X();return(r,d)=>(a(),i("div",{class:M(["VPNavBarTitle",{"has-sidebar":l(n)}])},[c("a",{class:"title",href:l(t).logoLink??l(Y)(l(o).link)},[u(r.$slots,"nav-bar-title-before",{},void 0,!0),l(t).logo?(a(),$(fe,{key:0,class:"logo",image:l(t).logo},null,8,["image"])):f("",!0),l(t).siteTitle?(a(),i(S,{key:1},[H(w(l(t).siteTitle),1)],64)):l(t).siteTitle===void 0?(a(),i(S,{key:2},[H(w(l(e).title),1)],64)):f("",!0),u(r.$slots,"nav-bar-title-after",{},void 0,!0)],8,Ea)],2))}});const Fa=m(Da,[["__scopeId","data-v-86d1bed8"]]),Oa={},Ga={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Ua=c("path",{d:"M0 0h24v24H0z",fill:"none"},null,-1),Ra=c("path",{d:" M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z ",class:"css-c4d79v"},null,-1),ja=[Ua,Ra];function Ka(s,e){return a(),i("svg",Ga,ja)}const ze=m(Oa,[["render",Ka]]),qa={class:"items"},Wa={class:"title"},Ya=g({__name:"VPNavBarTranslations",setup(s){const{theme:e}=P(),{localeLinks:t,currentLang:n}=X({correspondingLink:!0});return(o,r)=>l(t).length&&l(n).label?(a(),$($e,{key:0,class:"VPNavBarTranslations",icon:ze,label:l(e).langMenuLabel||"Change language"},{default:_(()=>[c("div",qa,[c("p",Wa,w(l(n).label),1),(a(!0),i(S,null,x(l(t),d=>(a(),$(ne,{key:d.link,item:d},null,8,["item"]))),128))])]),_:1},8,["label"])):f("",!0)}});const Ja=m(Ya,[["__scopeId","data-v-74abcbb9"]]),Xa=s=>(E("data-v-a0fd61f4"),s=s(),D(),s),Za={class:"container"},Qa={class:"title"},el={class:"content"},tl=Xa(()=>c("div",{class:"curtain"},null,-1)),sl={class:"content-body"},nl=g({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(s){const{y:e}=Ce(),{hasSidebar:t}=F(),{frontmatter:n}=P(),o=L({});return Le(()=>{o.value={"has-sidebar":t.value,top:n.value.layout==="home"&&e.value===0}}),(r,d)=>(a(),i("div",{class:M(["VPNavBar",o.value])},[c("div",Za,[c("div",Qa,[h(Fa,null,{"nav-bar-title-before":_(()=>[u(r.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":_(()=>[u(r.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),c("div",el,[tl,c("div",sl,[u(r.$slots,"nav-bar-content-before",{},void 0,!0),h(xa,{class:"search"}),h(Sa,{class:"menu"}),h(Ja,{class:"translations"}),h($o,{class:"appearance"}),h(za,{class:"social-links"}),h(_a,{class:"extra"}),u(r.$slots,"nav-bar-content-after",{},void 0,!0),h(ga,{class:"hamburger",active:r.isScreenOpen,onClick:d[0]||(d[0]=p=>r.$emit("toggle-screen"))},null,8,["active"])])])])],2))}});const ol=m(nl,[["__scopeId","data-v-a0fd61f4"]]),al={key:0,class:"VPNavScreenAppearance"},ll={class:"text"},rl=g({__name:"VPNavScreenAppearance",setup(s){const{site:e,theme:t}=P();return(n,o)=>l(e).appearance?(a(),i("div",al,[c("p",ll,w(l(t).darkModeSwitchLabel||"Appearance"),1),h(me)])):f("",!0)}});const il=m(rl,[["__scopeId","data-v-add8f686"]]),cl=g({__name:"VPNavScreenMenuLink",props:{item:{}},setup(s){const e=se("close-screen");return(t,n)=>(a(),$(G,{class:"VPNavScreenMenuLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:l(e)},{default:_(()=>[H(w(t.item.text),1)]),_:1},8,["href","target","rel","onClick"]))}});const ul=m(cl,[["__scopeId","data-v-05f27b2a"]]),dl={},_l={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},vl=c("path",{d:"M18.9,10.9h-6v-6c0-0.6-0.4-1-1-1s-1,0.4-1,1v6h-6c-0.6,0-1,0.4-1,1s0.4,1,1,1h6v6c0,0.6,0.4,1,1,1s1-0.4,1-1v-6h6c0.6,0,1-0.4,1-1S19.5,10.9,18.9,10.9z"},null,-1),pl=[vl];function hl(s,e){return a(),i("svg",_l,pl)}const fl=m(dl,[["render",hl]]),ml=g({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(s){const e=se("close-screen");return(t,n)=>(a(),$(G,{class:"VPNavScreenMenuGroupLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:l(e)},{default:_(()=>[H(w(t.item.text),1)]),_:1},8,["href","target","rel","onClick"]))}});const Ee=m(ml,[["__scopeId","data-v-19976ae1"]]),gl={class:"VPNavScreenMenuGroupSection"},$l={key:0,class:"title"},kl=g({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),i("div",gl,[e.text?(a(),i("p",$l,w(e.text),1)):f("",!0),(a(!0),i(S,null,x(e.items,n=>(a(),$(Ee,{key:n.text,item:n},null,8,["item"]))),128))]))}});const bl=m(kl,[["__scopeId","data-v-8133b170"]]),yl=["aria-controls","aria-expanded"],Pl={class:"button-text"},Vl=["id"],wl={key:1,class:"group"},Ll=g({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(s){const e=s,t=L(!1),n=y(()=>`NavScreenGroup-${e.text.replace(" ","-").toLowerCase()}`);function o(){t.value=!t.value}return(r,d)=>(a(),i("div",{class:M(["VPNavScreenMenuGroup",{open:t.value}])},[c("button",{class:"button","aria-controls":n.value,"aria-expanded":t.value,onClick:o},[c("span",Pl,w(r.text),1),h(fl,{class:"button-icon"})],8,yl),c("div",{id:n.value,class:"items"},[(a(!0),i(S,null,x(r.items,p=>(a(),i(S,{key:p.text},["link"in p?(a(),i("div",{key:p.text,class:"item"},[h(Ee,{item:p},null,8,["item"])])):(a(),i("div",wl,[h(bl,{text:p.text,items:p.items},null,8,["text","items"])]))],64))),128))],8,Vl)],2))}});const Sl=m(Ll,[["__scopeId","data-v-1ecb84e7"]]),Ml={key:0,class:"VPNavScreenMenu"},Cl=g({__name:"VPNavScreenMenu",setup(s){const{theme:e}=P();return(t,n)=>l(e).nav?(a(),i("nav",Ml,[(a(!0),i(S,null,x(l(e).nav,o=>(a(),i(S,{key:o.text},["link"in o?(a(),$(ul,{key:0,item:o},null,8,["item"])):(a(),$(Sl,{key:1,text:o.text||"",items:o.items},null,8,["text","items"]))],64))),128))])):f("",!0)}}),Il=g({__name:"VPNavScreenSocialLinks",setup(s){const{theme:e}=P();return(t,n)=>l(e).socialLinks?(a(),$(ke,{key:0,class:"VPNavScreenSocialLinks",links:l(e).socialLinks},null,8,["links"])):f("",!0)}}),Nl={class:"list"},Tl=g({__name:"VPNavScreenTranslations",setup(s){const{localeLinks:e,currentLang:t}=X({correspondingLink:!0}),n=L(!1);function o(){n.value=!n.value}return(r,d)=>l(e).length&&l(t).label?(a(),i("div",{key:0,class:M(["VPNavScreenTranslations",{open:n.value}])},[c("button",{class:"title",onClick:o},[h(ze,{class:"icon lang"}),H(" "+w(l(t).label)+" ",1),h(He,{class:"icon chevron"})]),c("ul",Nl,[(a(!0),i(S,null,x(l(e),p=>(a(),i("li",{key:p.link,class:"item"},[h(G,{class:"link",href:p.link},{default:_(()=>[H(w(p.text),1)]),_:2},1032,["href"])]))),128))])],2)):f("",!0)}});const Bl=m(Tl,[["__scopeId","data-v-d72aa483"]]),Al={class:"container"},xl=g({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(s){const e=L(null),t=Ie(J?document.body:null);return(n,o)=>(a(),$(ie,{name:"fade",onEnter:o[0]||(o[0]=r=>t.value=!0),onAfterLeave:o[1]||(o[1]=r=>t.value=!1)},{default:_(()=>[n.open?(a(),i("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:e,id:"VPNavScreen"},[c("div",Al,[u(n.$slots,"nav-screen-content-before",{},void 0,!0),h(Cl,{class:"menu"}),h(Bl,{class:"translations"}),h(il,{class:"appearance"}),h(Il,{class:"social-links"}),u(n.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):f("",!0)]),_:3}))}});const Hl=m(xl,[["__scopeId","data-v-cc5739dd"]]),zl={class:"VPNav"},El=g({__name:"VPNav",setup(s){const{isScreenOpen:e,closeScreen:t,toggleScreen:n}=Jn();return Ne("close-screen",t),(o,r)=>(a(),i("header",zl,[h(ol,{"is-screen-open":l(e),onToggleScreen:l(n)},{"nav-bar-title-before":_(()=>[u(o.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":_(()=>[u(o.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":_(()=>[u(o.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":_(()=>[u(o.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),h(Hl,{open:l(e)},{"nav-screen-content-before":_(()=>[u(o.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":_(()=>[u(o.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])]))}});const Dl=m(El,[["__scopeId","data-v-7e5bc4a5"]]),Fl=s=>(E("data-v-e31bd47b"),s=s(),D(),s),Ol=["role","tabindex"],Gl=Fl(()=>c("div",{class:"indicator"},null,-1)),Ul=["onKeydown"],Rl={key:1,class:"items"},jl=g({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(s){const e=s,{collapsed:t,collapsible:n,isLink:o,isActiveLink:r,hasActiveLink:d,hasChildren:p,toggle:v}=gt(y(()=>e.item)),b=y(()=>p.value?"section":"div"),N=y(()=>o.value?"a":"div"),T=y(()=>p.value?e.depth+2===7?"p":`h${e.depth+2}`:"p"),A=y(()=>o.value?void 0:"button"),I=y(()=>[[`level-${e.depth}`],{collapsible:n.value},{collapsed:t.value},{"is-link":o.value},{"is-active":r.value},{"has-active":d.value}]);function B(k){"key"in k&&k.key!=="Enter"||!e.item.link&&v()}function V(){e.item.link&&v()}return(k,C)=>{const z=j("VPSidebarItem",!0);return a(),$(U(b.value),{class:M(["VPSidebarItem",I.value])},{default:_(()=>[k.item.text?(a(),i("div",Z({key:0,class:"item",role:A.value},Ye(k.item.items?{click:B,keydown:B}:{},!0),{tabindex:k.item.items&&0}),[Gl,k.item.link?(a(),$(G,{key:0,tag:N.value,class:"link",href:k.item.link,rel:k.item.rel,target:k.item.target},{default:_(()=>[(a(),$(U(T.value),{class:"text",innerHTML:k.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(a(),$(U(T.value),{key:1,class:"text",innerHTML:k.item.text},null,8,["innerHTML"])),k.item.collapsed!=null?(a(),i("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:V,onKeydown:We(V,["enter"]),tabindex:"0"},[h(he,{class:"caret-icon"})],40,Ul)):f("",!0)],16,Ol)):f("",!0),k.item.items&&k.item.items.length?(a(),i("div",Rl,[k.depth<5?(a(!0),i(S,{key:0},x(k.item.items,q=>(a(),$(z,{key:q.text,item:q,depth:k.depth+1},null,8,["item","depth"]))),128)):f("",!0)])):f("",!0)]),_:1},8,["class"])}}});const Kl=m(jl,[["__scopeId","data-v-e31bd47b"]]),De=s=>(E("data-v-b00e2fdd"),s=s(),D(),s),ql=De(()=>c("div",{class:"curtain"},null,-1)),Wl={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},Yl=De(()=>c("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),Jl=g({__name:"VPSidebar",props:{open:{type:Boolean}},setup(s){const e=s,{sidebarGroups:t,hasSidebar:n}=F(),o=L(null),r=Ie(J?document.body:null);return R([e,o],()=>{var d;e.open?(r.value=!0,(d=o.value)==null||d.focus()):r.value=!1},{immediate:!0,flush:"post"}),(d,p)=>l(n)?(a(),i("aside",{key:0,class:M(["VPSidebar",{open:d.open}]),ref_key:"navEl",ref:o,onClick:p[0]||(p[0]=Je(()=>{},["stop"]))},[ql,c("nav",Wl,[Yl,u(d.$slots,"sidebar-nav-before",{},void 0,!0),(a(!0),i(S,null,x(l(t),v=>(a(),i("div",{key:v.text,class:"group"},[h(Kl,{item:v,depth:0},null,8,["item"])]))),128)),u(d.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):f("",!0)}});const Xl=m(Jl,[["__scopeId","data-v-b00e2fdd"]]),Zl=g({__name:"VPSkipLink",setup(s){const e=te(),t=L();R(()=>e.path,()=>t.value.focus());function n({target:o}){const r=document.getElementById(decodeURIComponent(o.hash).slice(1));if(r){const d=()=>{r.removeAttribute("tabindex"),r.removeEventListener("blur",d)};r.setAttribute("tabindex","-1"),r.addEventListener("blur",d),r.focus(),window.scrollTo(0,0)}}return(o,r)=>(a(),i(S,null,[c("span",{ref_key:"backToTop",ref:t,tabindex:"-1"},null,512),c("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:n}," Skip to content ")],64))}});const Ql=m(Zl,[["__scopeId","data-v-0f60ec36"]]),er=g({__name:"Layout",setup(s){const{isOpen:e,open:t,close:n}=F(),o=te();R(()=>o.path,n),mt(e,n);const{frontmatter:r}=P(),d=Xe(),p=y(()=>!!d["home-hero-image"]);return Ne("hero-image-slot-exists",p),(v,b)=>{const N=j("Content");return l(r).layout!==!1?(a(),i("div",{key:0,class:M(["Layout",l(r).pageClass])},[u(v.$slots,"layout-top",{},void 0,!0),h(Ql),h(st,{class:"backdrop",show:l(e),onClick:l(n)},null,8,["show","onClick"]),l(r).navbar!==!1?(a(),$(Dl,{key:0},{"nav-bar-title-before":_(()=>[u(v.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":_(()=>[u(v.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":_(()=>[u(v.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":_(()=>[u(v.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":_(()=>[u(v.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":_(()=>[u(v.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3})):f("",!0),h(Yn,{open:l(e),onOpenMenu:l(t)},null,8,["open","onOpenMenu"]),h(Xl,{open:l(e)},{"sidebar-nav-before":_(()=>[u(v.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":_(()=>[u(v.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),h(Sn,null,{"page-top":_(()=>[u(v.$slots,"page-top",{},void 0,!0)]),"page-bottom":_(()=>[u(v.$slots,"page-bottom",{},void 0,!0)]),"not-found":_(()=>[u(v.$slots,"not-found",{},void 0,!0)]),"home-hero-before":_(()=>[u(v.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info":_(()=>[u(v.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":_(()=>[u(v.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":_(()=>[u(v.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":_(()=>[u(v.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":_(()=>[u(v.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":_(()=>[u(v.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":_(()=>[u(v.$slots,"doc-before",{},void 0,!0)]),"doc-after":_(()=>[u(v.$slots,"doc-after",{},void 0,!0)]),"doc-top":_(()=>[u(v.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":_(()=>[u(v.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":_(()=>[u(v.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":_(()=>[u(v.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":_(()=>[u(v.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":_(()=>[u(v.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":_(()=>[u(v.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":_(()=>[u(v.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),h(Tn),u(v.$slots,"layout-bottom",{},void 0,!0)],2)):(a(),$(N,{key:1}))}}});const tr=m(er,[["__scopeId","data-v-1919c326"]]);const nr={Layout:tr,enhanceApp:({app:s})=>{s.component("Badge",Qe)}};export{nr as t,P as u};
diff --git a/assets/docker_Docker-compose_docker-compose-syntax.md.0195f5ef.js b/assets/docker_Docker-compose_docker-compose-syntax.md.0195f5ef.js
new file mode 100644
index 000000000..734207f99
--- /dev/null
+++ b/assets/docker_Docker-compose_docker-compose-syntax.md.0195f5ef.js
@@ -0,0 +1,79 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"docker-compose语法","description":"","frontmatter":{},"headers":[],"relativePath":"docker/Docker-compose/docker-compose-syntax.md","filePath":"docker/Docker-compose/docker-compose-syntax.md","lastUpdated":1694368780000}'),p={name:"docker/Docker-compose/docker-compose-syntax.md"},o=l(`docker-compose语法 基础模板 https://docs.docker.com/compose/compose-file/03-compose-file/
yml version : "3.8" # version是compose文件格式版本号 需要和Docker Engine对应 https://docs.docker.com/compose/compose-file/compose-file-v3/
+
+services :
+ service1 :
+ image : image_name:version #指定镜像
+ container_name : service1 #容器名
+ environment : # 指定环境变量
+ - A=1
+ - B=2
+ restart : always #重启策略
+ volumes : #数据卷挂载
+ - /etc/localtime:/etc/localtime:ro
+ ports : #端口映射配置
+ - "6610:6610"
+ - "6611:6611"
+ privileged : true # 将服务容器配置为以提升的权限运行
+ links : #定义到另一个服务中的容器的网络链接,可以在此容器直接用服务名访问另一个容器,links也有服务之间的隐式依赖关系,因此也决定了服务启动的顺序。
+ - service2
+ env_file :
+ - ./a.env
+ - ./b.env
+ devices :
+ - "/dev/ttyUSB0:/dev/ttyUSB0"
+ - "/dev/sda:/dev/xvda:rwm"
+ dns :
+ - 8.8.8.8
+ service2 :
+ build : #构建配置
+ context : . #指定包含Dockerfile的目录或一个git仓库的url
+ dockerfile : webapp.Dockerfile #指定要使用的Dockerfile名称,默认找Dockerfile,和dockerfile_inline参数不能同时使用
+ dockerfile_inline : #直接在compose文件里写Dockerfile指令 和dockerfile参数不能同时使用
+ FROM xxx
+ RUN some command
+ container_name : service2
+ network_mode : "host" #配置网络模式,none(禁用所有容器网络)/host(使用宿主接口)/service:{name}(只能访问指定服务)
+ networks : #指定容器连接的docker网络
+ - netA
+ - netB
+ depends_on : #依赖某个服务,决定了服务的启动和关闭顺序
+ - service3
version : "3.8" # version是compose文件格式版本号 需要和Docker Engine对应 https://docs.docker.com/compose/compose-file/compose-file-v3/
+
+services :
+ service1 :
+ image : image_name:version #指定镜像
+ container_name : service1 #容器名
+ environment : # 指定环境变量
+ - A=1
+ - B=2
+ restart : always #重启策略
+ volumes : #数据卷挂载
+ - /etc/localtime:/etc/localtime:ro
+ ports : #端口映射配置
+ - "6610:6610"
+ - "6611:6611"
+ privileged : true # 将服务容器配置为以提升的权限运行
+ links : #定义到另一个服务中的容器的网络链接,可以在此容器直接用服务名访问另一个容器,links也有服务之间的隐式依赖关系,因此也决定了服务启动的顺序。
+ - service2
+ env_file :
+ - ./a.env
+ - ./b.env
+ devices :
+ - "/dev/ttyUSB0:/dev/ttyUSB0"
+ - "/dev/sda:/dev/xvda:rwm"
+ dns :
+ - 8.8.8.8
+ service2 :
+ build : #构建配置
+ context : . #指定包含Dockerfile的目录或一个git仓库的url
+ dockerfile : webapp.Dockerfile #指定要使用的Dockerfile名称,默认找Dockerfile,和dockerfile_inline参数不能同时使用
+ dockerfile_inline : #直接在compose文件里写Dockerfile指令 和dockerfile参数不能同时使用
+ FROM xxx
+ RUN some command
+ container_name : service2
+ network_mode : "host" #配置网络模式,none(禁用所有容器网络)/host(使用宿主接口)/service:{name}(只能访问指定服务)
+ networks : #指定容器连接的docker网络
+ - netA
+ - netB
+ depends_on : #依赖某个服务,决定了服务的启动和关闭顺序
+ - service3
`,4),e=[o];function c(t,r,E,y,i,d){return n(),a("div",null,e)}const D=s(p,[["render",c]]);export{m as __pageData,D as default};
diff --git a/assets/docker_Docker-compose_docker-compose-syntax.md.0195f5ef.lean.js b/assets/docker_Docker-compose_docker-compose-syntax.md.0195f5ef.lean.js
new file mode 100644
index 000000000..6e9c94c17
--- /dev/null
+++ b/assets/docker_Docker-compose_docker-compose-syntax.md.0195f5ef.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"docker-compose语法","description":"","frontmatter":{},"headers":[],"relativePath":"docker/Docker-compose/docker-compose-syntax.md","filePath":"docker/Docker-compose/docker-compose-syntax.md","lastUpdated":1694368780000}'),p={name:"docker/Docker-compose/docker-compose-syntax.md"},o=l("",4),e=[o];function c(t,r,E,y,i,d){return n(),a("div",null,e)}const D=s(p,[["render",c]]);export{m as __pageData,D as default};
diff --git a/assets/docker_Docker_common-instruction.md.e2127946.js b/assets/docker_Docker_common-instruction.md.e2127946.js
new file mode 100644
index 000000000..02e2334ec
--- /dev/null
+++ b/assets/docker_Docker_common-instruction.md.e2127946.js
@@ -0,0 +1,11 @@
+import{_ as s,o as a,c as o,Q as l}from"./chunks/framework.b637c96f.js";const F=JSON.parse('{"title":"常用指令","description":"","frontmatter":{},"headers":[],"relativePath":"docker/Docker/common-instruction.md","filePath":"docker/Docker/common-instruction.md","lastUpdated":1694368780000}'),e={name:"docker/Docker/common-instruction.md"},n=l(`常用指令 Dockerfile给ubuntu换源 dockerfile RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
macOS运行容器时同步宿主机时间 shell -e TZ=\` ls -la /etc/localtime | cut -d/ -f8-9 \`
-e TZ=\` ls -la /etc/localtime | cut -d/ -f8-9 \`
MacOS中容器访问宿主机 可以用host.docker.internal
来访问宿主机
https://docs.docker.com/desktop/networking/#use-cases-and-workarounds
构建跨平台镜像 docker buildx shell # 创建builder
+docker buildx create --name cross-platform-builder --driver docker-container --use
+# 执行构建
+docker buildx build --platform linux/amd64,linux/arm64 -t 镜像名:tag [-o type=registry | --push] .
+# 查看推送到远程的镜像信息
+docker buildx imagetools inspect 镜像名:tag
# 创建builder
+docker buildx create --name cross-platform-builder --driver docker-container --use
+# 执行构建
+docker buildx build --platform linux/amd64,linux/arm64 -t 镜像名:tag [-o type=registry | --push] .
+# 查看推送到远程的镜像信息
+docker buildx imagetools inspect 镜像名:tag
buildx 实例通过两种方式来执行构建任务,两种执行方式被称为使用不同的「驱动」:
docker
驱动:使用 Docker 服务程序中集成的 BuildKit 库执行构建。docker-container
驱动:启动一个包含 BuildKit 的容器并在容器中执行构建。docker
驱动无法使用一小部分 buildx
的特性(如在一次运行中同时构建多个平台镜像),此外在镜像的默认输出格式上也有所区别:docker
驱动默认将构建结果以 Docker 镜像格式直接输出到 docker
的镜像目录(通常是 /var/lib/overlay2
),之后执行 docker images
命令可以列出所输出的镜像;而 docker container
则需要通过 --output
选项指定输出格式为镜像或其他格式。
docker buildx build
支持丰富的输出行为,通过--output=[PATH,-,type=TYPE[,KEY=VALUE]
选项可以指定构建结果的输出类型和路径等,常用的输出类型有以下几种:
local:构建结果将以文件系统格式写入 dest
指定的本地路径, 如 --output type=local,dest=./output
。 tar:构建结果将在打包后写入 dest
指定的本地路径。 oci:构建结果以 OCI 标准镜像格式写入 dest
指定的本地路径。 docker:构建结果以 Docker 标准镜像格式写入 dest
指定的本地路径或加载到 docker
的镜像库中。同时指定多个目标平台时无法使用该选项。 image:以镜像或者镜像列表输出,并支持 push=true
选项直接推送到远程仓库,同时指定多个目标平台时可使用该选项。 registry:type=image,push=true
的精简表示。 https://waynerv.com/posts/building-multi-architecture-images-with-docker-buildx/
`,17),p=[n];function c(t,r,i,d,y,u){return a(),o("div",null,p)}const h=s(e,[["render",c]]);export{F as __pageData,h as default};
diff --git a/assets/docker_Docker_common-instruction.md.e2127946.lean.js b/assets/docker_Docker_common-instruction.md.e2127946.lean.js
new file mode 100644
index 000000000..568582755
--- /dev/null
+++ b/assets/docker_Docker_common-instruction.md.e2127946.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as o,Q as l}from"./chunks/framework.b637c96f.js";const F=JSON.parse('{"title":"常用指令","description":"","frontmatter":{},"headers":[],"relativePath":"docker/Docker/common-instruction.md","filePath":"docker/Docker/common-instruction.md","lastUpdated":1694368780000}'),e={name:"docker/Docker/common-instruction.md"},n=l("",17),p=[n];function c(t,r,i,d,y,u){return a(),o("div",null,p)}const h=s(e,[["render",c]]);export{F as __pageData,h as default};
diff --git a/assets/docker_Docker_dockerfile-syntax.md.55c6cc9e.js b/assets/docker_Docker_dockerfile-syntax.md.55c6cc9e.js
new file mode 100644
index 000000000..326ca1114
--- /dev/null
+++ b/assets/docker_Docker_dockerfile-syntax.md.55c6cc9e.js
@@ -0,0 +1,101 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const D=JSON.parse('{"title":"Dockerfile语法","description":"","frontmatter":{},"headers":[],"relativePath":"docker/Docker/dockerfile-syntax.md","filePath":"docker/Docker/dockerfile-syntax.md","lastUpdated":1694368780000}'),p={name:"docker/Docker/dockerfile-syntax.md"},o=l(`Dockerfile语法 Docker可以通过读取Dockerfile中的指令自动构建映像。Dockerfile是一个文本文档,其中包含用户在命令行上调用来组装镜像的所有命令。
https://docs.docker.com/engine/reference/builder/
语法 dockerfile # 注释
+INSTRUCTION arguments
# 注释
+INSTRUCTION arguments
指令大小写不敏感,所以使用小写也不影响构建,但习惯上都将指令大写用于区分指令和参数。
Dockerfile必须以FROM
指令开始(特殊情况:ARG指令)。
dockerfile ARG CODE_VERSION=latest
+FROM base:\${CODE_VERSION}
+CMD /code/run-app
ARG CODE_VERSION=latest
+FROM base:\${CODE_VERSION}
+CMD /code/run-app
支持环境变量替换的指令 ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
ONBUILD
dockerfile FROM busybox
+ENV FOO=/bar
+WORKDIR \${FOO} # WORKDIR /bar
+ADD . $FOO # ADD . /bar
+COPY \\$FOO /quux # COPY $FOO /quux
FROM busybox
+ENV FOO=/bar
+WORKDIR \${FOO} # WORKDIR /bar
+ADD . $FOO # ADD . /bar
+COPY \\$FOO /quux # COPY $FOO /quux
基本模板 dockerfile FROM image_name:version as alias1
+# FROM [--platform=<platform>] <image>[:<tag>] [AS <name>] 一个Dockerfile中FROM可以多次出现 用于构建多个镜像或者将一个构建阶段用作另一个构建阶段的依赖项
+MAINTAINER storyxc #维护文档的人,现在用LABEL xxx=xxx代替了
+
+RUN xxx
+# RUN <command> shell格式,默认在linux上使用/bin/sh -c执行,windows上 cmd /S /C执行
+# RUN ['executable', 'param1', 'param2'] exec格式
+
+# Deploy Biliup
+FROM python:3.9 as alias2
+
+ENV TZ=Asia/Shanghai
+# ENV指定环境变量
+
+EXPOSE 19159/tcp
+EXPOSE 19149/udp
+# 注明暴露的端口,只是声明作用,实际没有功能
+
+ADD hom* /mydir/
+# ADD指令用于向镜像内拷贝文件 目录 不仅能复制本机的文件,也能将远程URL的资源复制到镜像中
+# ADD [--chown=<user>:<group>] [--chmod=<perms>] [--checksum=<checksum>] <src>... <dest>
+# ADD [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
+# ADD可以识别压缩格式,把可解压的文件解压为目录,远程URL资源不会被解压
+
+VOLUME /opt
+# 指定创建一个具有指定名称的挂载点 可以使用JSON数组格式 或多个参数纯字符串
+
+COPY --from=alias1 /dir1 /dir2
+# COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
+# COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
+
+WORKDIR /opt
+# WORKDIR指定工作目录,如果没有则会被创建,如果WORKDIR后出现了相对路径,都是相对WORKDIR的
+
+CMD [ "param1" , "param2" ]
+# CMD指令有三种格式
+# CMD ["executable","param1","param2"] exec格式 最常用,不会有变量替换,需要变量替换需要使用shell格式或类似["shell", "-c", "e cho $HOME"]
+# CMD ["param1","param2"] 作为ENTRYPOINT的默认参数,如果是这个用法 那么ENTRYPOINT指令也要用JSON数组的格式书写
+# CMD command param1 param2 shell格式
+# 一个Dockerfile中只能有一个CMD指令,如果写了多个那么只有最后一个生效
+
+ENTRYPOINT [ "biliup" ]
+# ENTRYPOINT ["executable", "param1", "param2"]
+# ENTRYPOINT command param1 param2
FROM image_name:version as alias1
+# FROM [--platform=<platform>] <image>[:<tag>] [AS <name>] 一个Dockerfile中FROM可以多次出现 用于构建多个镜像或者将一个构建阶段用作另一个构建阶段的依赖项
+MAINTAINER storyxc #维护文档的人,现在用LABEL xxx=xxx代替了
+
+RUN xxx
+# RUN <command> shell格式,默认在linux上使用/bin/sh -c执行,windows上 cmd /S /C执行
+# RUN ['executable', 'param1', 'param2'] exec格式
+
+# Deploy Biliup
+FROM python:3.9 as alias2
+
+ENV TZ=Asia/Shanghai
+# ENV指定环境变量
+
+EXPOSE 19159/tcp
+EXPOSE 19149/udp
+# 注明暴露的端口,只是声明作用,实际没有功能
+
+ADD hom* /mydir/
+# ADD指令用于向镜像内拷贝文件 目录 不仅能复制本机的文件,也能将远程URL的资源复制到镜像中
+# ADD [--chown=<user>:<group>] [--chmod=<perms>] [--checksum=<checksum>] <src>... <dest>
+# ADD [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
+# ADD可以识别压缩格式,把可解压的文件解压为目录,远程URL资源不会被解压
+
+VOLUME /opt
+# 指定创建一个具有指定名称的挂载点 可以使用JSON数组格式 或多个参数纯字符串
+
+COPY --from=alias1 /dir1 /dir2
+# COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
+# COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
+
+WORKDIR /opt
+# WORKDIR指定工作目录,如果没有则会被创建,如果WORKDIR后出现了相对路径,都是相对WORKDIR的
+
+CMD [ "param1" , "param2" ]
+# CMD指令有三种格式
+# CMD ["executable","param1","param2"] exec格式 最常用,不会有变量替换,需要变量替换需要使用shell格式或类似["shell", "-c", "e cho $HOME"]
+# CMD ["param1","param2"] 作为ENTRYPOINT的默认参数,如果是这个用法 那么ENTRYPOINT指令也要用JSON数组的格式书写
+# CMD command param1 param2 shell格式
+# 一个Dockerfile中只能有一个CMD指令,如果写了多个那么只有最后一个生效
+
+ENTRYPOINT [ "biliup" ]
+# ENTRYPOINT ["executable", "param1", "param2"]
+# ENTRYPOINT command param1 param2
`,14),e=[o];function t(c,r,i,y,u,E){return a(),n("div",null,e)}const g=s(p,[["render",t]]);export{D as __pageData,g as default};
diff --git a/assets/docker_Docker_dockerfile-syntax.md.55c6cc9e.lean.js b/assets/docker_Docker_dockerfile-syntax.md.55c6cc9e.lean.js
new file mode 100644
index 000000000..ecf7ef059
--- /dev/null
+++ b/assets/docker_Docker_dockerfile-syntax.md.55c6cc9e.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const D=JSON.parse('{"title":"Dockerfile语法","description":"","frontmatter":{},"headers":[],"relativePath":"docker/Docker/dockerfile-syntax.md","filePath":"docker/Docker/dockerfile-syntax.md","lastUpdated":1694368780000}'),p={name:"docker/Docker/dockerfile-syntax.md"},o=l("",14),e=[o];function t(c,r,i,y,u,E){return a(),n("div",null,e)}const g=s(p,[["render",t]]);export{D as __pageData,g as default};
diff --git a/assets/docker_index.md.a98dedbd.js b/assets/docker_index.md.a98dedbd.js
new file mode 100644
index 000000000..4ae8f37e7
--- /dev/null
+++ b/assets/docker_index.md.a98dedbd.js
@@ -0,0 +1 @@
+import{_ as o,o as a,c as n,k as e,a as t}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"Docker","description":"","frontmatter":{},"headers":[],"relativePath":"docker/index.md","filePath":"docker/index.md","lastUpdated":1694368780000}'),r={name:"docker/index.md"},i=e("h1",{id:"docker",tabindex:"-1"},[t("Docker "),e("a",{class:"header-anchor",href:"#docker","aria-label":'Permalink to "Docker"'},"")],-1),c=e("blockquote",null,[e("p",null,"Docker is an open platform for developing, shipping, and running applications. Docker enables you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications. By taking advantage of Docker’s methodologies for shipping, testing, and deploying code quickly, you can significantly reduce the delay between writing code and running it in production.")],-1),s=[i,c];function d(l,p,u,k,f,h){return a(),n("div",null,s)}const y=o(r,[["render",d]]);export{m as __pageData,y as default};
diff --git a/assets/docker_index.md.a98dedbd.lean.js b/assets/docker_index.md.a98dedbd.lean.js
new file mode 100644
index 000000000..4ae8f37e7
--- /dev/null
+++ b/assets/docker_index.md.a98dedbd.lean.js
@@ -0,0 +1 @@
+import{_ as o,o as a,c as n,k as e,a as t}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"Docker","description":"","frontmatter":{},"headers":[],"relativePath":"docker/index.md","filePath":"docker/index.md","lastUpdated":1694368780000}'),r={name:"docker/index.md"},i=e("h1",{id:"docker",tabindex:"-1"},[t("Docker "),e("a",{class:"header-anchor",href:"#docker","aria-label":'Permalink to "Docker"'},"")],-1),c=e("blockquote",null,[e("p",null,"Docker is an open platform for developing, shipping, and running applications. Docker enables you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications. By taking advantage of Docker’s methodologies for shipping, testing, and deploying code quickly, you can significantly reduce the delay between writing code and running it in production.")],-1),s=[i,c];function d(l,p,u,k,f,h){return a(),n("div",null,s)}const y=o(r,[["render",d]]);export{m as __pageData,y as default};
diff --git a/assets/frontend_base_css.md.70afca28.js b/assets/frontend_base_css.md.70afca28.js
new file mode 100644
index 000000000..44a943425
--- /dev/null
+++ b/assets/frontend_base_css.md.70afca28.js
@@ -0,0 +1,391 @@
+import{_ as s,o as a,c as l,Q as n}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"CSS","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/css.md","filePath":"frontend/base/css.md","lastUpdated":1694368780000}'),p={name:"frontend/base/css.md"},o=n(`CSS CSS特性 继承性 层叠性 优先级:继承 < 通配符选择器 < 标签选择器 < 类选择器 < id选择器 < 行内样式 < !important
!important
写在属性值后面,分号前面!important
不能提升继承的优先级,只要是继承优先级最低复合选择器需要根据权重叠加计算(行内样式个数,id选择器个数,类选择器个数,标签选择器个数) 书写顺序:浏览器执行效率更高
浮动 / display 盒子模型 margin border padding 宽高背景色 文字样式 选择器 字体和文本样式 字体样式 字体大小:font-size
取值:数字+px,Google Chrome默认16px 字体粗细:font-weight
关键字:normal/bold/bolder/lighter 数字:100-900的整百数,正常400 加粗700 字体样式:font-style
字体类型:font-family
从左往右查找,如果未安装,则显示下一个,如果都不支持则显示最后字体系列的默认字体,多个字体推荐引号包裹,最后一个字体系列不需要引号 字体类型:属性连写
style weight size family 只能省略前两个,省略相当于设置默认值 如果同时使用单独和连写,要么把单独的写在连写下面,或者写在连写里面 文本样式 背景属性 背景色 背景图 属性:backgroud-image backgroud-image: url('图片链接') url可以省略引号 背景图默认水平和垂直方向平铺 背景平铺 属性:background-repeat 取值:repeat、no-repeat、repeat-x、repeat-y 背景位置 background-position 取值 方位: 水平:left、center、right 垂直:top、center、bottom 数字+px 坐标系原点(0,0),图片左上角 x轴 水平向右(图片向左移动取负数、向右移动取正数) y轴 垂直向下(图片向上移动取负数、向下移动取正数) 方位和坐标可以混用,第一个值为水平,第二个值为垂直 背景属性连写 background: color image repeat position 元素显示模式 元素显示模式 块级元素 显示特点:独占一行,宽度默认是父元素宽度,高度默认由内容撑开,可以设置宽、高 代表标签:div、p、h、ul、li、dl、dt、dd、form、header、nav、footer。。。 行内元素 显示特点:一行可以显示多个,宽度和高度默认由内容撑开,不能设置宽、高 代表标签:a、span、b、u、i、s、strong、ins、em、del。。。 行内块元素 显示特点:一行可以显示多个,可以设置宽、高 代表标签:input、textarea、button、select 特殊:img标签有行内块特点,但是Chrome显示是inline 元素显示模式转换 css span {
+ display : inline-block ;
+}
+
+div {
+ display : inline ;
+}
+
+button {
+ display : block ;
+}
span {
+ display : inline-block ;
+}
+
+div {
+ display : inline ;
+}
+
+button {
+ display : block ;
+}
盒子模型 页面中的每一个标签都可以看作一个“盒子”,通过盒子的视角更方便的进行布局。
每个盒子分别由:内容区域(content)、内边距区域(padding)、边框区域(border)、外边距区域(margin)构成。
内容区域宽高:weight/height 边框:border border:10px solid red; border-top/bottom/left/right 内边距:padding border和padding会撑大盒子,css属性box-sizing: border-box; 可以设置内减模式
外边距:margin
顺序和padding一样
清除默认的内外边距
css * {
+ margin : 0 ;
+ padding : 0 ;
+}
* {
+ margin : 0 ;
+ padding : 0 ;
+}
版心居中
css #main {
+ margin : 0 auto ;
+}
#main {
+ margin : 0 auto ;
+}
顺序: 宽高背景色-> 放内容 -> 调整位置 -> 调整文字细节
外边距问题
折叠现象:垂直布局的块级元素,上下的margin会被合并,取两者的最大值
塌陷现象:互相嵌套的块级元素,子元素的margin-top会作用在父元素上
解决:1.父元素设置border-top或者padding-top(分隔父子元素的margin-top) 2.父元素设置overflow: hidden;
3.转换为行内块元素4.设置浮动 行内元素的内外边距问题:如果想通过margin/padding改变行内元素的垂直 (top/bottom)位置,无法生效
浮动 标准流 标准流:又称文档流,浏览器在渲染显示网页内容时默认采取的一套排版规则,规定了何种方式排列元素 块级元素:从上往下,垂直布局,独占一行 行内元素/行内块元素:从左往右,水平布局,空间不够自动折行 浮动作用 块级元素转行内块时 换行会产生空格
浮动的规则 浮动特点 浮动元素会脱离标准流,在标准流中不占位置 浮动元素比标准流高半个级别,可以覆盖标准流中的元素 浮动找浮动,下一个浮动元素会在上一个浮动元素后面左右浮动 浮动元素有特殊的显示效果 浮动的元素不能通过text-align: center 或者margin: 0 auto居中
清除浮动 定位 作用 可以让元素自由的摆放在网页的任意位置 一般用于盒子之间的层叠情况 常见应用场景 可以解决盒子与盒子之间层叠问题 可以让盒子始终固定带屏幕中的某个位置 使用 设置定位方式
设置偏移值
偏移值分为两个方向,水平和垂直方向各选一个使用即可,水平以left为准,垂直以top为准 选取原则一般是就近原则 元素层级问题 不同布局方式元素的层级关系 不同定位之间的层级关系 相对、绝对、固定默认层级相同 此时默认HTML中写在下面的元素层级更高,会覆盖上面的元素 z-index(配合定位才生效):整数,取值越大,层级越高,显示顺序越靠上,z-index默认为0 定位装饰 垂直对齐方式 属性:vertical-align 取值:baseline(默认,基线对齐)、top(顶部对齐)、middle(中部对齐)、bottom(底部对齐) 浏览器遇到行内和行内块标签当作文字处理,默认文字按照基线对齐
光标类型 属性:cursor 常见取值: default(默认,通常是箭头) pointer(小手,提示可以点击) text(工字型,提示可以选择) move(十字光标,提示可以移动) 边框圆角 属性:border-radius
常见取值:数字+px、百分比
赋值:从左上角开始,顺时针赋值,没有赋值的看对角
正圆:正方形盒子,border-radius: 50%
胶囊按钮:长方形盒子,border-radius: 高度的一半
百分比表示的是圆角半径的大小为盒子较小边长的一半,例如盒子宽200px,高100px,border-radius: 50% 50%; 则这两个50%都是相对于100px的。
溢出部分显示效果 属性:overflow 常见取值: visible 默认值,可见 hidden 隐藏 scroll 始终显示滚动条 auto 自动显示、隐藏滚动条 元素本身隐藏 属性: visibility: hidden(占位隐藏) display: none(不占位隐藏) 元素整体透明度 属性:opacity 取值:0-1之间数字,1完全不透明,0完全透明 特点:opacity会让元素整体透明,包括里面的文字、子元素等 补充 精灵图 CSS精灵图是一种将多个小的背景图片合并到一张大图中的技术,通过CSS的background-position属性控制显示不同的小图片,达到减少HTTP请求、减小页面大小、提高加载速度的效果。
CSS精灵图可以将多个小背景图像合并为一张大图,这样在页面上加载一次大图后,就可以通过background-position属性来控制显示不同的小图像,实现了将多个HTTP请求转化为一次请求,减小了网络延迟和服务器压力。同时,由于减少了HTTP请求和加载的内容大小,减小了页面的带宽消耗,加快了网页的加载速度,提高了用户的体验感受。
在web前端开发中,经常使用CSS精灵图技术,特别是在一些需要大量小icon的场合,如网站菜单栏、按钮、分页等等。
背景图片大小 background连写 backgound:color image repeat position/size
盒子阴影 属性:box-shadow
取值 h-shadow:必须,水平偏移量,允许负值 v-shadow:必须,垂直偏移量,允许负值 blur:可选,模糊度 spread:可选,阴影扩大 color:可选,阴影颜色 inset:可选,改为内部阴影 过渡 属性:transition
作用:让元素样式慢慢变化,常配合hover使用 常见取值 过渡属性:all(所有能过渡的属性都过渡)、具体属性名如width(只有width过渡) 过渡时长: 数字+s 注意点 默认状态和hover状态样式不同才有过渡效果 transition属性需要给过渡的元素本身加 transition属性设置在不同状态中,效果不同 给默认状态设置,鼠标移入移出都有效果 给hover状态设置,只有移入有过渡效果 .box {
width: 200px;
height: 200px;
background-color: pink;
/transition: all 1s /
transition: width 1s, background-color 2s;
}
.box hover {
width: 600px;
background-color: red;
}
字体图标 展示的是图标,实质是文字,用作处理简单的、颜色单一的图片
iconfont
<link rel="stylesheet" href="./iconfont.css"
<span class="iconfont icon-kuaijiezhifu"></span>
平面转换 平面转换(2D转换):改变盒子在平面内的形态,可以使用transform属性实现元素的位移、旋转、缩放等效果
位移 transform: translate(水平移动距离, 垂直移动距离)
取值: 1.像素单位数值 2.百分比(参照为自身盒子尺寸) 只给一个值代表x轴位移,单独设置:tranlateX() / translateY() 旋转 语法:transform: rotate(度数 + deg);
正数:顺时针;负数:逆时针 transform-origin 作用:改变转换的原点,默认的原点是盒子中心点 transform-origin: 原点水平位置 原点垂直位置
取值:方位名词(left、top、right、bottom、center 像素 百分比 多重转换 transform: translate() rotate()
rotate会改变坐标轴向,位移方向会受影响
多重转换如果涉及旋转,旋转往最后写
缩放 scale:改变元素尺寸 语法:transform: scale(x轴缩放倍数, y轴缩放倍数)
一般transform: scale(缩放倍数),等比缩放 scale大于1表示放大,小于1表示缩小 渐变 linear-gradient
空间转换 在空间内位移、旋转、缩放等效果
空间位移 transform: tranlate3d(x,y,z)
单个坐标轴transform: translat[X|Y|Z]
透视 使用perspective(视距)属性实现透视效果,添加给父级元素 ,取值一般800-1200
空间旋转 transform: rotateX()
transform: rotateY()
transform: rotateZ()
transform: rotate3d(x,y,z,角度度数)
:用来设置自定义旋转轴的位置及旋转角度,xyz取值为0-1之间的数字
立体呈现 使用transform-style: perserve-3d
呈现立体图形
空间缩放 transform: scaleX()
transform: scaleY()
transform: scaleY()
transform: scale3d(x,y,z)
动画 使用animation
实现多个状态间的变化过程,动画过程可控(重复播放、最终画面、是否暂停)
实现步骤 定义动画
css @keyframes 动画名称 {
+ from {}
+ to {}
+}
+
+@keyframes 动画名称 {
+ /* 百分比指的是动画总时长的占比 */
+ 0% {}
+ 10% {}
+ 15% {}
+ 100% {}
+}
@keyframes 动画名称 {
+ from {}
+ to {}
+}
+
+@keyframes 动画名称 {
+ /* 百分比指的是动画总时长的占比 */
+ 0% {}
+ 10% {}
+ 15% {}
+ 100% {}
+}
使用动画: animation: 动画名称 动画花费时长
动画属性 animation: 动画名称 动画时长 速度曲线 延迟时间 重复次数 动画方向 执行完毕时状态 播放状态
动画名称和动画时长必须赋值 取值不分先后顺序 如果有2个时间,第一个表示动画时长,第二个表示延迟时间 animation-name: animation-duration animation-timing-function:ease;ease-in;ease-out;ease-in-out;linear;step; animation-delay animation-iteration-count: 1,2,..infinite; animation-direction: normal;reverse;alternate;alternate-reverse; animation-fill-mode: forward;backward;both; animation-play-state: pause;running; animation- 多组动画 animation: 动画1,动画2...,动画n
;
flex布局 是一种浏览器提倡的布局模型 布局网页更简单、灵活 避免了浮动
脱标的问题 组成部分 语法 css display: flex;
display: flex;
主轴对齐方式 主轴整体对齐 justify-content
取值:
left/right start/end flex-start flex-end center space-between space-around space-evenly first/last baseline 主轴单行对齐方式 justify-items:相当于给给每个项设置默认的justify-self值 主轴单个对齐方式 侧轴对齐方式 侧轴多行整体对齐 侧轴单行对齐方式 属性:align-items,相当于给每个项设置默认的align-self值 取值: center stretch: 拉伸,默认值 start/end flex-start/flex-end self-[start/end] first/last baseline 侧轴单个对齐方式 伸缩比 属性:flex
说明: flex是flex-grow
(增长系数)、flex-shrink
(收缩系数)和flex-basis
(初始尺寸)三个属性的简写,第一个无单位数代表flex-grow、第二无单位数代表flex-shrink,带像素单位的是flex-basis的值 修改主轴方向 属性:flex-direction 取值: row column row-reverse column-reverse 弹性项换行 flex-flow flex-flow:flex-direction flex-wrap; 移动适配 rem 相对单位 rem单位是相对HTML标签的字号计算结果 1rem=1HTML字号大小 将网页等分成10份,HTML标签的字号为视口宽度的1/10 px单位数值/基准根字号 媒体查询 css @media 逻辑操作符 媒体类型 and (媒体特性) {
+ 选择器 {
+ CSS 属性
+ }
+}
+
+@media (媒体特性) {
+ 选择器 {
+ CSS 属性
+ }
+}
+
+@media ( min-width : 320 px ) {
+ html {
+ font-size : 32 px ;
+ }
+}
@media 逻辑操作符 媒体类型 and (媒体特性) {
+ 选择器 {
+ CSS 属性
+ }
+}
+
+@media (媒体特性) {
+ 选择器 {
+ CSS 属性
+ }
+}
+
+@media ( min-width : 320 px ) {
+ html {
+ font-size : 32 px ;
+ }
+}
关键词 媒体类型 screen(带屏幕的设备) print(打印预览模式) speech(屏幕阅读模式) all(默认值) 媒体特性 视口宽高:width、height、max-width、max-height、min-width、min-height 屏幕方向:orientation,portrait竖屏,landscape横屏 vw/vh vw = 1/100视口宽度 vh = 1/100视口高度 Less Less(Leaner Style Sheets)是一门向后兼容的CSS 扩展语言,是一个CSS预处理器,扩充了CSS,使CSS具备一定的逻辑性、计算能力。
https://less.bootcss.com
变量 less @width: 10 px ;
+@height: @width + 10 px ;
+
+#header {
+ width : @width;
+ height : @height;
+}
@width: 10 px ;
+@height: @width + 10 px ;
+
+#header {
+ width : @width;
+ height : @height;
+}
编译为
css #header {
+ width : 10 px ;
+ height : 20 px ;
+}
#header {
+ width : 10 px ;
+ height : 20 px ;
+}
混合 less .bordered {
+ border-top : dotted 1 px black ;
+ border-bottom : solid 2 px black ;
+}
+
+#menu a {
+ color : #111 ;
+ .bordered ();
+}
+
+.post a {
+ color : red ;
+ .bordered ();
+}
.bordered {
+ border-top : dotted 1 px black ;
+ border-bottom : solid 2 px black ;
+}
+
+#menu a {
+ color : #111 ;
+ .bordered ();
+}
+
+.post a {
+ color : red ;
+ .bordered ();
+}
.bordered
类所包含的属性就将同时出现在 #menu a
和 .post a
中了。
嵌套 less #header {
+ color : black ;
+ .navigation {
+ font-size : 12 px ;
+ }
+ .logo {
+ width : 300 px ;
+ }
+}
#header {
+ color : black ;
+ .navigation {
+ font-size : 12 px ;
+ }
+ .logo {
+ width : 300 px ;
+ }
+}
css #header {
+ color : black ;
+}
+#header .navigation {
+ font-size : 12 px ;
+}
+#header .logo {
+ width : 300 px ;
+}
#header {
+ color : black ;
+}
+#header .navigation {
+ font-size : 12 px ;
+}
+#header .logo {
+ width : 300 px ;
+}
规则嵌套和冒泡 less .component {
+ width : 300 px ;
+ @media ( min-width : 768 px ) {
+ width : 600 px ;
+ @media ( min-resolution : 192 dpi ) {
+ background-image : url( /img/retina2x.png ) ;
+ }
+ }
+ @media ( min-width : 1280 px ) {
+ width : 800 px ;
+ }
+}
.component {
+ width : 300 px ;
+ @media ( min-width : 768 px ) {
+ width : 600 px ;
+ @media ( min-resolution : 192 dpi ) {
+ background-image : url( /img/retina2x.png ) ;
+ }
+ }
+ @media ( min-width : 1280 px ) {
+ width : 800 px ;
+ }
+}
编译为
css .component {
+ width : 300 px ;
+}
+@media ( min-width : 768 px ) {
+ .component {
+ width : 600 px ;
+ }
+}
+@media ( min-width : 768 px ) and ( min-resolution : 192 dpi ) {
+ .component {
+ background-image : url ( /img/retina2x.png );
+ }
+}
+@media ( min-width : 1280 px ) {
+ .component {
+ width : 800 px ;
+ }
+}
.component {
+ width : 300 px ;
+}
+@media ( min-width : 768 px ) {
+ .component {
+ width : 600 px ;
+ }
+}
+@media ( min-width : 768 px ) and ( min-resolution : 192 dpi ) {
+ .component {
+ background-image : url ( /img/retina2x.png );
+ }
+}
+@media ( min-width : 1280 px ) {
+ .component {
+ width : 800 px ;
+ }
+}
运算 算术运算符 +
、-
、*
、/
可以对任何数字、颜色或变量进行运算。如果可能的话,算术运算符在加、减或比较之前会进行单位换算。计算的结果以最左侧操作数的单位类型为准。如果单位换算无效或失去意义,则忽略单位。无效的单位换算例如:px 到 cm 或 rad 到 % 的转换。
less // 所有操作数被转换成相同的单位
+@conversion-1: 5 cm + 10 mm ; // 结果是 6cm
+@conversion-2: 2 - 3 cm - 5 mm ; // 结果是 -1.5cm
+
+// conversion is impossible
+@incompatible-units: 2 + 5 px - 3 cm ; // 结果是 4px
+
+// example with variables
+@base: 5 % ;
+@filler: @base * 2 ; // 结果是 10%
+@other: @base + @filler; // 结果是 15%
// 所有操作数被转换成相同的单位
+@conversion-1: 5 cm + 10 mm ; // 结果是 6cm
+@conversion-2: 2 - 3 cm - 5 mm ; // 结果是 -1.5cm
+
+// conversion is impossible
+@incompatible-units: 2 + 5 px - 3 cm ; // 结果是 4px
+
+// example with variables
+@base: 5 % ;
+@filler: @base * 2 ; // 结果是 10%
+@other: @base + @filler; // 结果是 15%
乘法和除法不作转换。因为这两种运算在大多数情况下都没有意义,一个长度乘以一个长度就得到一个区域,而 CSS 是不支持指定区域的。Less 将按数字的原样进行操作,并将为计算结果指定明确的单位类型。
less @base: 2 cm * 3 mm ; // 结果是 6cm
@base: 2 cm * 3 mm ; // 结果是 6cm
转义 转义(Escaping)允许你使用任意字符串作为属性或变量值。任何 ~"anything"
或 ~'anything'
形式的内容都将按原样输出,除非 interpolation 。
less @min768: ~ "(min-width: 768px)" ;
+.element {
+ @media @min768 {
+ font-size : 1.2 rem ;
+ }
+}
@min768: ~ "(min-width: 768px)" ;
+.element {
+ @media @min768 {
+ font-size : 1.2 rem ;
+ }
+}
编译为:
less @media ( min-width : 768 px ) {
+ .element {
+ font-size : 1.2 rem ;
+ }
+}
@media ( min-width : 768 px ) {
+ .element {
+ font-size : 1.2 rem ;
+ }
+}
注意,从 Less 3.5 开始,可以简写为:
less @min768: ( min-width : 768 px );
+.element {
+ @media @min768 {
+ font-size : 1.2 rem ;
+ }
+}
@min768: ( min-width : 768 px );
+.element {
+ @media @min768 {
+ font-size : 1.2 rem ;
+ }
+}
函数 Less 内置了多种函数用于转换颜色、处理字符串、算术运算等。这些函数在Less 函数手册 中有详细介绍。
函数的用法非常简单。下面这个例子利用 percentage 函数将 0.5 转换为 50%,将颜色饱和度增加 5%,以及颜色亮度降低 25% 并且色相值增加 8 等用法:
less @base: #f04615 ;
+@width: 0.5 ;
+
+.class {
+ width : percentage (@width); // returns \`50%\`
+ color : saturate (@base, 5 % );
+ background-color : spin ( lighten (@base, 25 % ), 8 );
+}
@base: #f04615 ;
+@width: 0.5 ;
+
+.class {
+ width : percentage (@width); // returns \`50%\`
+ color : saturate (@base, 5 % );
+ background-color : spin ( lighten (@base, 25 % ), 8 );
+}
映射 从 Less 3.5 版本开始,你还可以将混合(mixins)和规则集(rulesets)作为一组值的映射(map)使用。
less #colors () {
+ primary: blue ;
+ secondary: green ;
+}
+
+.button {
+ color : #colors [ primary ];
+ border : 1 px solid #colors [ secondary ];
+}
#colors () {
+ primary: blue ;
+ secondary: green ;
+}
+
+.button {
+ color : #colors [ primary ];
+ border : 1 px solid #colors [ secondary ];
+}
输出符合预期:
css .button {
+ color : blue ;
+ border : 1 px solid green ;
+}
.button {
+ color : blue ;
+ border : 1 px solid green ;
+}
作用域 Less 中的作用域与 CSS 中的作用域非常类似。首先在本地查找变量和混合(mixins),如果找不到,则从“父”级作用域继承。
less @var: red ;
+
+#page {
+ @var: white ;
+ #header {
+ color : @var; // white
+ }
+}
@var: red ;
+
+#page {
+ @var: white ;
+ #header {
+ color : @var; // white
+ }
+}
与 CSS 自定义属性一样,混合(mixin)和变量的定义不必在引用之前事先定义。因此,下面的 Less 代码示例和上面的代码示例是相同的:
less @var: red ;
+
+#page {
+ #header {
+ color : @var; // white
+ }
+ @var: white ;
+}
@var: red ;
+
+#page {
+ #header {
+ color : @var; // white
+ }
+ @var: white ;
+}
导入 css @import "library" ; // library .less
+@import "typo.css" ;
@import "library" ; // library .less
+@import "typo.css" ;
被引入的less文件不会生成单独的css文件
控制Less编译输出 webstorm FileWatcher配置:
npm install -g less aruments:--no-color $FileName$ ../css/$FileNameWithoutExtension$.css
output paths to refresh : ../css/$FileNameWithoutExtension$.css
VS Code EasyLess配置:
out: "../css/" 首行添加注释控制编译的输出情况 // out: ./dir/ // out: ./dir/xxx.css // out: false BootStrap <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
栅格系统 栅格化是指将整个网页的宽度分成若干等份,BootStrap3默认将网页分为12等份 超小屏幕 小屏幕 中等屏幕 大屏幕 响应断点 <768px >=768px >=992px >=1200px 别名 xs sm md lg 容器宽度 100% 750px 970px 1170px 类前缀 col-xs-* col-sm-* col-md-* col-lg-* 列数 12 12 12 12 列间隙 30px 30px 30px 30px
.container
是BootStrap中提供的类名,应用该类名的盒子,默认被指定宽度且居中.container-fluid
是BootStrap中提供的类名,所有应用的盒子,宽度为100%分别用.row
类名和.col
类名定义栅格布局的行和列 TIP
container类自带间距15px row类自带间距-15px bootstrap样式 https://v3.bootcss.com/css/
bootstrap组件 https://v3.bootcss.com/components/
Record 滚动条滑动效果 css html {
+ scroll-behavior : smooth ;
+}
html {
+ scroll-behavior : smooth ;
+}
`,240),e=[o];function t(c,r,i,E,y,d){return a(),l("div",null,e)}const b=s(p,[["render",t]]);export{u as __pageData,b as default};
diff --git a/assets/frontend_base_css.md.70afca28.lean.js b/assets/frontend_base_css.md.70afca28.lean.js
new file mode 100644
index 000000000..a27b07bcb
--- /dev/null
+++ b/assets/frontend_base_css.md.70afca28.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as l,Q as n}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"CSS","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/css.md","filePath":"frontend/base/css.md","lastUpdated":1694368780000}'),p={name:"frontend/base/css.md"},o=n("",240),e=[o];function t(c,r,i,E,y,d){return a(),l("div",null,e)}const b=s(p,[["render",t]]);export{u as __pageData,b as default};
diff --git a/assets/frontend_base_html.md.7eca1008.js b/assets/frontend_base_html.md.7eca1008.js
new file mode 100644
index 000000000..991e7d804
--- /dev/null
+++ b/assets/frontend_base_html.md.7eca1008.js
@@ -0,0 +1 @@
+import{_ as a,o as e,c as t,Q as o}from"./chunks/framework.b637c96f.js";const b=JSON.parse('{"title":"HTML","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/html.md","filePath":"frontend/base/html.md","lastUpdated":1694368780000}'),i={name:"frontend/base/html.md"},l=o('HTML 标签 排版标签 标题:h1-h6
段落:p
换行:br
水平分割线:hr
文本格式化标签 加粗:strong/b
下划线:ins/u
倾斜:em/i
删除线:del/s
媒体标签 图片:img
音频:audio [src|controls|autoplay|loop]
视频:video [src|controls|autoplay|loop]
链接 超链接:a
列表 有序列表:ol > li
无序列表:ul > li
自定义列表:dl > dt > dd
表格 table > tr > td
th:表头
caption:标题
结构标签:thead/tbody/tfoot
合并单元格:rowspan-跨行合并 colspan-跨列合并(不能跨结构标签合并)
表单 input系列:text/password/radio/checkbox/file/submit/reset/button
button
select
textarea
label
语义化标签 没有语义的布局标签:div/span
语义化标签:header/nav/footer/aside/section/article(显示特点和div一致,多了语义)
字符实体 空格: 
骨架结构标签 <!DOCTYPE html>
:声明网页HTML版本<html lang="en"
:标识网页使用的语言,作用:搜索引擎归类+浏览器翻译, zh-CN
/en
<meta>
:元数据标签,元数据是关于文档的数据,例如文档的作者、字符集、关键字和描述等信息,这些信息对于搜索引擎的抓取和用户的浏览很有帮助。 charset:指定文档的字符编码。 name:定义元数据的名称。 content:定义元数据的内容。 http-equiv:提供有关如何处理文档的其他信息。 viewport:指定当前网页的视口(viewport)尺寸和缩放比例,以便浏览器正确渲染页面。 SEO(Search Engine Optimization)标签 <title>
:网页标题<meta name="description">
:网页描述标签<meta name="keywords"
:网页关键词ico图标 <link rel="icon" href="favicon.ico">
',45),r=[l];function n(h,d,c,p,s,u){return e(),t("div",null,r)}const q=a(i,[["render",n]]);export{b as __pageData,q as default};
diff --git a/assets/frontend_base_html.md.7eca1008.lean.js b/assets/frontend_base_html.md.7eca1008.lean.js
new file mode 100644
index 000000000..aff8eed84
--- /dev/null
+++ b/assets/frontend_base_html.md.7eca1008.lean.js
@@ -0,0 +1 @@
+import{_ as a,o as e,c as t,Q as o}from"./chunks/framework.b637c96f.js";const b=JSON.parse('{"title":"HTML","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/html.md","filePath":"frontend/base/html.md","lastUpdated":1694368780000}'),i={name:"frontend/base/html.md"},l=o("",45),r=[l];function n(h,d,c,p,s,u){return e(),t("div",null,r)}const q=a(i,[["render",n]]);export{b as __pageData,q as default};
diff --git a/assets/frontend_base_javascript.md.4c1235ab.js b/assets/frontend_base_javascript.md.4c1235ab.js
new file mode 100644
index 000000000..ac0e10b22
--- /dev/null
+++ b/assets/frontend_base_javascript.md.4c1235ab.js
@@ -0,0 +1,1461 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"JavaScript","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/javascript.md","filePath":"frontend/base/javascript.md","lastUpdated":1694368780000}'),p={name:"frontend/base/javascript.md"},o=l(`JavaScript 组成 ECMAScript:规定了js基础语法,比如变量、分支语句、循环语句、对象等 Web APIs DOM:操作文档,比如对页面元素移动、添加删除等操作 BOM:操作浏览器,比如页面弹窗、检测窗口宽度、存储数据到浏览器等 基本语法 输入 prompt()
输出 console.log()
document.write()
alert()
alert和prompt会跳过页面渲染先被执行
变量 声明 比较旧的JavaScript中使用var声明变量
var的一些问题:
可以先使用,再声明 var声明过的变量可以重复声明 变量提升、全局变量、没有块级作用域 命名 只能下划线、字母、数字、$,且不能数字开头 字母区分大小写 规范 数组 let arr = [1, 2, 3]
/ let arr = new Array(1, 2, 3)
数组有序
取值:数组[下标]
长度:数组.length
修改:arr[下标] = 新值
增加
arr.push() 将一个或多个元素新增到末尾,返回新的数组长度 arr.unshift()将一个或多个元素新增到开头,返回新的数组长度 删除
arr.pop() 删除最后一个元素,并返回该元素的值 arr.shift()删除第一个元素,并返回该元素的值 arr.splice(操作的下标,删除的个数),删除指定元素并返回 常量 数据类型 基本数据类型 number
string
boolean
undefined
null
NaN NaN代表一个计算错误,是一个不正确或未定义的数学操作得到的结果,任何对NaN的操作都会返回NaN
typeof 运算符写法:typeof 变量 函数写法:typeof(变量) 数据类型转换 隐式转换 +
号两边只要有字符串,都会转字符串除了+
,其他算数运算符会把数据转换为数字类型 +
作为正号可以转换数字显式转换 引用数据类型 object
js let obj = {
+ uname: 'abc' ,
+ age: 18 ,
+ gender: '女' ,
+ speak : function ( x ) {
+ console. log ( 'hello' + x)
+ }
+}
let obj = {
+ uname: 'abc' ,
+ age: 18 ,
+ gender: '女' ,
+ speak : function ( x ) {
+ console. log ( 'hello' + x)
+ }
+}
属性名可以用引号,一般省略,除非遇到特殊符号(空格、中横线等) 查看:
修改: 对象.属性 = 新值
新增: 对象.新属性 = 值
删除: delete 对象.属性
对象方法: 对象.方法名()
遍历对象
js for ( let k in obj) {
+ console. log (obj[k]) //k带引号
+}
for ( let k in obj) {
+ console. log (obj[k]) //k带引号
+}
for in遍历数组 是数组下标,但是是字符串
运算符 赋值运算符 一元运算符 比较运算符 <
>
>=
<=
==
: 值是否相等===
:类型和值是否都相等!==
:是否不全等逻辑运算符 流程控制语句 if
除了0,所有的数字都为真 除了'',所有字符串都为真 switch case
数据和值必须满足全等===
js switch (数据) {
+ case 值1:
+ 代码1
+ break
+ case 值2:
+ 代码2
+ break
+ default :
+ 代码n
+}
switch (数据) {
+ case 值1:
+ 代码1
+ break
+ case 值2:
+ 代码2
+ break
+ default :
+ 代码n
+}
三元运算符
循环控制语句 函数 js function 函数名 ( 参数列表 ) {
+ 函数体
+}
function 函数名 ( 参数列表 ) {
+ 函数体
+}
作用域 全局变量 局部变量或块级变量 没有let声明直接赋值的当全局变量看(不提倡) 局部变量 匿名函数 逻辑中断 短路:只存在&&
和||
中,当满足一定条件会让右边代码不执行 &&
:左边为false就短路||
:左边为true就短路 转换boolean "": false
0: false
undefined: false
null: false
NaN: false
"" + 1 = 1
null经过数字转换会变0
undefined经过数字转换会变NaN
Web APIs DOM 获取DOM元素 document.querySelector(CSS选择器):获取匹配的第一个元素 document.querySelectorAll(CSS选择器):获取匹配的多个元素 document.getElementById():通过元素的 id 属性获取一个 DOM 元素 document.getElementsByName():通过元素的 name 属性获取一个类数组的元素集合,该方法返回一个 NodeList 对象 document.getElementsByClassName():方法通过元素的 name 属性获取一个类数组的元素集合,该方法返回一个 NodeList 对象 document.getElementsBytagName():通过元素的标签名获取一个类数组的元素集合,该方法返回一个 NodeList 对象 操作元素内容 事件监听 元素对象.addEventListener('事件类型', 要执行的函数) 元素.on事件:也可以添加事件监听,但会被覆盖,且只能冒泡 不能捕获,addEventListener不会被覆盖,能冒泡 也能捕获。
事件类型 鼠标事件 click mouseenter: 没冒泡,只会在鼠标进入目标元素时触发一次 mouseover:有冒泡,事件在鼠标经过目标元素或任何子元素时会不断触发 mouseleave mousemove: 鼠标移动 焦点事件 键盘事件 文本事件 事件对象 事件对象中有事件触发时的相关信息,例如鼠标点击时的位置,键盘按下时的键位
js btn. addEventListener ( 'click' , function ( e ){
+ console. log (e)
+})
btn. addEventListener ( 'click' , function ( e ){
+ console. log (e)
+})
常用对象属性 type:事件类型 clientX/clientY:光标相对于浏览器可见窗口左上角的位置 offsetX/offsetY:光标相对于当前DOM元素左上角的位置 key:用户按下的键盘的值,现在不提倡使用keyCode 环境对象 指的是函数内部特殊的变量this
,它代表着当前函数运行时所处的环境
函数的调用方式不同,this
的指代对象也不通 this
指向的粗略规则是谁调用指向谁(addEventListener指向绑定的元素,普通函数指向window)回调函数 函数A作为参数传递给函数B,A就被称为回调函数
事件流 事件流指的是事件完整执行过程中的流动路径
事件捕获 DOM的根元素开始去执行对应的事件(从父元素到子元素)
js DOM . addEventListener (事件类型, 函数, 是否使用捕获机制)
DOM . addEventListener (事件类型, 函数, 是否使用捕获机制)
L0事件只有冒泡,没有捕获
事件冒泡 当一个元素的事件被触发时,同样的事件会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡(从子元素到父元素)
阻止事件传播 事件对象.stopPropagation() 阻断事件流动传播,既能阻止冒泡,也能阻止捕获 js btn. addEventListener ( 'click' , function ( e ){
+ e. stopPropagation ()
+})
btn. addEventListener ( 'click' , function ( e ){
+ e. stopPropagation ()
+})
解绑事件 on事件方式
js // 绑定事件
+btn. onClick = function ( e ){
+ console. log (e)
+}
+// 解绑事件
+btn.onClick = null
// 绑定事件
+btn. onClick = function ( e ){
+ console. log (e)
+}
+// 解绑事件
+btn.onClick = null
addEventListener方式
js function fn ( e ){
+ console. log (e)
+}
+//绑定事件
+btn. addEventListener ( 'click' , fn)
+//解绑事件
+btn. removeEventListener ( 'click' , fn)
function fn ( e ){
+ console. log (e)
+}
+//绑定事件
+btn. addEventListener ( 'click' , fn)
+//解绑事件
+btn. removeEventListener ( 'click' , fn)
匿名函数无法解绑
事件委托 事件委托是利用事件流特征解决开发问题的技巧,可以减少事件注册次数,提高程序性能,原理是利用事件冒泡特点,给父元素注册事件,当触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
阻止元素默认行为 e.preventDefault()
其他事件 页面加载事件
页面滚动事件
滚动条在滚动的时候持续触发的事件
window.addEventListener('scroll', function() {})
给window或document添加scroll事件 也可以监听某个元素内部滚动 获取滚动位置
scrollLeft**(可读写)**
scrollTop**(可读写)**
js window. addEventListener ( 'scroll' , function () {
+ const n = document.documentElement.scrollTop
+ console. log (n)
+})
window. addEventListener ( 'scroll' , function () {
+ const n = document.documentElement.scrollTop
+ console. log (n)
+})
document.documentElement返回对象为HTML元素
......滚动到指定坐标
页面尺寸事件
窗口尺寸改变时触发的事件resize
window.addEventListener('resize', function() {})
获取元素可见部分的宽高clientWidth
、clientHeight
元素尺寸位置 获取宽高 offsetWidth和offsetHeight 获取元素自身的宽高,包含padding,border 结果是数值 获取的是可视宽高,如果盒子隐藏,结果是0 获取位置 offsetLeft和offsetTop 获取元素距离自己定位 父级元素的左、上距离,只读属性 获取元素大小及其相对视口的位置 element.getBoundingClientRect()
日期对象 实例化
const date = new Date()
const date = new Date('2023-4-8 08:00:00')
常用方法
getFullYear():四位数年份 getMonth():月份,范围0-11 getDate():获取月份中的每一天 getDay():获取星期,0-6 getHours():小时,0-23 getMinutes():分钟,0-59 getSeconds():秒,0-59 toLocaleString(): yyyy/m/d HH:mm:ss 时间戳
date.getTime()
+new Date()
Date.now()
DOM节点 节点类型 元素节点 属性节点 文本节点 其他(注释、文档类型、CDATA、实体引用、处理指令。。。) 查找节点 父节点 子节点 元素.childNodes:获取所有子节点,包括文本(空格、换行)、注释节点等 元素.children:仅获取元素节点,返回的是一个伪数组 兄弟节点 nextElementSibling:下一个兄弟节点 previousElementSibling:上一个兄弟节点 新增节点 创建节点 const div = document.createElement('div')
追加节点 克隆节点 元素.cloneNode(布尔值) true:克隆时会包含后代节点一起克隆 false:不包含后代节点,默认值 删除节点 BOM 组成 BOM(Browser Object Model)是浏览器对象模型,包含:navigator、location、document、history、screen
window是一个全局对象,document、alert()、console.log()都是window的属性
所有通过var
定义在全局作用域中的变量、函数都会变成window对象的属性和方法 window对象下的属性和方法调用的时候可以省略window 定时器 延时函数 let timer = setTimeout(回调函数, 等待时间ms)
,返回id,setTimeout只执行一次关闭:clearTimeout(timer)
间歇函数 let interval = setInterval(函数, 间隔时间ms)
,返回的是的是一个id数字,不断执行关闭:clearInterval(interval)
事件循环 js是单线程,所有任务需要排队。HTML5提出了Web Worker标准,允许JavaScript脚本创建多个线程。于是JS出现了同步和异步。
同步任务:都在主线程执行,形成执行栈 异步任务:通过回调函数实现,异步任务添加到任务队列中,一般异步任务有以下三种类型 普通事件:click、resize等 资源加载:load、error等 定时器:setTimeout、setInterval等 执行机制 先执行执行栈中的同步任务 异步任务放到任务队列中 执行栈中的所有同步任务执行完毕,系统会按次序读取任务队列中的异步任务,被读取的异步任务结束等待状态,进入执行栈,开始执行 location localtion的数据类型是对象,它拆分保存了URL地址的各个组成部分
location.href
:常用于页面跳转
location.search
:获取地址中携带的参数,符号?
后面的部分
location.hash
:获取地址中的hash值,符号#
后面的部分
location.reload()
:用来刷新当前页面,传入参数true时强制刷新
navigator navigator的数据类型是对象,该对象下记录了浏览器自身的相关信息
navigator.userAgent:检测浏览器版本和平台 history history数据类型是对象,主要管理历史记录,该对象与浏览器地址栏的操作相对应,如前进、后退、历史记录等
history.back() history.forward() history.go(参数): 1->前进一个页面,-1->后退一个页面 本地存储 介绍 数据存储在用户浏览器中,设置、读取方便,刷新页面不会丢失数据,sessionStorage和localStorage约5M
分类 localStorage 可以多窗口(页面)共享(同一浏览器可以共享) 键值对形式存储使用 语法 存储:localStorage.setItem(key, value)
查询:localStorage.getItem(key)
删除:localStorage.removeItem(key)
sessionStorage 生命周期到关闭浏览器窗口截止 在同一个窗口(页面)下数据可以共享 键值对形式存储使用 用法api和localStorage
一致 存储复杂数据类型 把复杂数据类型转成字符串形式存储
数组map和join map 遍历数组处理数据,返回新的数组
js const arr = [ 'red' , 'blue' ]
+const newArr = arr. map ( function ( ele , index ) {
+ return ele + '颜色'
+})
+console. log (newArr) // ['red颜色', 'blue颜色']
const arr = [ 'red' , 'blue' ]
+const newArr = arr. map ( function ( ele , index ) {
+ return ele + '颜色'
+})
+console. log (newArr) // ['red颜色', 'blue颜色']
join 把数组所有元素转换为一个字符串 const newStr = join(字符串)
:元素用指定字符串相连进阶 正则表达式 定义:const reg = /表达式/
判断是否匹配:reg.test(被检测字符串)
,匹配返回true,否则false 查找:reg.exec(被检测字符串)
,找到返回数组,否则为null 元字符 边界符
量词
*
:0或多次+
:1或多次?
:0或1次{n}
:重复n次{n,}
:重复n次或更多{n,m}
:重复n次到m次字符类
[]
:匹配字符集合,匹配任一个都是true[a-zA-Z]
:字母[^a-z]
:[]中的^表示取反.
:除换行之外的任何单个字符\\d
:数字\\D
:所有0-9以外字符,等于[^0-9]
\\w
:任一字母、数字、下划线,相当于[a-zA-Z0-9_]
\\W
:匹配除字母、数字、下划线之外的字符,相当于[^a-zA-Z0-9_]
\\s
:匹配空格(包括制表符、换行符、空格符等),相当于[\\t\\r\\n\\v\\f]
\\S
:匹配非空格,相当于[^\\t\\r\\n\\v\\f]
修饰符 语法:/表达式/修饰符
修饰符:
i:ignore,匹配时,不区分大小写 g:global,匹配所有满足正则的结果 替换 语法:字符串.replace(/正则表达式/, 替换的文本)
,返回替换后的字符串 作用域 局部作用域
全局作用域
作用域链
JS垃圾回收机制
全局变量一般不会回收(关闭页面回收) 一般情况下局部变量的值不再被使用会被自动回收 内存由于某种原因未释放或无法释放会内存泄漏 栈:由操作系统自动分配释放函数的参数值、局部变量等基本数据类型放在栈里 堆:一般由开发分配释放,若开发不释放由垃圾回收机制回收。复杂数据类型放在堆里。 引用计数法(有循环引用问题)
定义“内存不再使用”,看一个对象是否有指向它的引用,没有引用就回收对象 根据记录被引用的次数 被引用一次,就+1,多次引用会累加 如果减少一个引用就-1 如果引用次数是0,则释放内存 标记清除法
将不再使用的对象定义为无法达到的对象 从根部(JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,都是还需要使用的 无法由根部出发触及的对象标记为不再使用,稍后进行回收 闭包
和python中的闭包一样:如果在一个外部函数中定义一个内部函数,内部函数对外部作用域的变量进行引用,外部函数的返回值是内部函数,这样的函数就被认为是闭包(closure)。
变量提升
允许变量在声明之前即被访问(var声明变量) js会在执行之前把当前作用域下var声明的变量提升到当前作用域的最前面,只提升声明,不提升赋值 函数提升
代码执行前会把所有函数声明提升到当前作用域的最前面 只提升声明,不提升调用 函数表达式特殊,必须先声明赋值后调用
函数进阶 动态参数:arguments
,只存在于函数里,伪数组
剩余参数:function getSum(paramA, paramB, ...arr)
,arr是个真数组
展开运算符:...
能将一个数组进行展开
箭头函数 引入箭头函数是为了更简洁的写法,适用于需要匿名函数的地方
js const fn = () => {}
+const fn = x => { console. log (x) }
+const fn = x => console. log (x)
+const fn = x => x * 2
+const fn = ( uname ) => ({ uname: uname }) //返回一个对象
const fn = () => {}
+const fn = x => { console. log (x) }
+const fn = x => console. log (x)
+const fn = x => x * 2
+const fn = ( uname ) => ({ uname: uname }) //返回一个对象
箭头函数的this 箭头函数不会创建自己的this对象,它只会从自己的作用域链的上一层
解构赋值 数组解构 数组结构是将数组的单元值快速批量赋值给一系列变量的简洁语法
const [max, min, avg] = [100, 60, 80]
典型用法:交换两个变量 可以设置默认值 可以用剩余参数防止undefined传递 可以忽略某些值const [a, ,c, d] = [1, 2, 3, 4]
js必须加分号场景:
两个连续的立即执行函数 使用数组 对象解构 对象解构是将对象的属性和方法快速批量赋值给一系列变量的简介语法
对象 创建对象的方式 字面量创建
构造函数
命名以大写字母开头 只能由new
操作符来执行 实例化执行过程 创建新对象 构造函数this指向新对象 执行构造函数代码,修改this,添加属性 返回新对象 实例成员&静态成员 实例成员:构造函数创建的对象为实例对象,实例对象的属性和方法称为实例成员 静态成员:构造函数的属性和方法称为静态成员 静态成员只能由构造函数访问 静态方法中的this指向构造函数 Date.now()、Math.PI、Math.random()
内置构造函数 Object
Object.keys() Object.values() Object.assign(dest, source) Array
String
实例属性、方法:length、split()、substring()、startsWith()、includes()、toUpperCase()、toLowerCase()、indexOf()、endsWith()、replace()、match()... Number
原型Prototype 构造函数通过原型分配的函数是所有对象所共享的。 JavaScript每一个构造函数都有一个prototype
属性,指向另一个对象,所以也称为原型对象 prototype对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存 可以把不变的方法直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法 构造函数和原型对象中的this都指向实例化的对象 constructor属性 每个原型对象里都有个constructor属性,该属性指向该原型对象的构造函数
对象原型 每个对象都有一个属性__proto__
,指向构造函数的prototype对象
__proto__
是JS非标准属性[[prototype]]和__proto__
意义相同 用来表明当前实例对象指向哪个原型对象prototype __proto__
对象原型里也有一个constructor属性,指向创建该实例对象的构造函数原型继承 通过原型可以继承公共属性
js const Person = {
+ eyes: 2 ,
+ nose: 1
+}
+
+function Man () {
+
+}
+Man . prototype = Person
+Man . prototype . constructor = Man
+
+---
+
+const Person = {
+ this.eyes: 2 ,
+ this.nose: 1
+}
+
+function Man () {
+
+}
+Man . prototype = new Person ()
const Person = {
+ eyes: 2 ,
+ nose: 1
+}
+
+function Man () {
+
+}
+Man . prototype = Person
+Man . prototype . constructor = Man
+
+---
+
+const Person = {
+ this.eyes: 2 ,
+ this.nose: 1
+}
+
+function Man () {
+
+}
+Man . prototype = new Person ()
原型链 基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联关系是一种链状的解构,称为原型链
查找规则 当访问一个对象的属性/方法时,首先查找这个对象自身有无该属性 如果没有就查找他的原型(__proto__
指向的prototype对象) 如果还没有就查找原型对象的原型(Object的prototype) 依此类推一直到Object为止(null) __proto__
对象原型的意义就在于为对象成员查找机制提供方向可以使用instanceof
运算符检测构造函数的prototype
属性是否出现在某个实例对象的原型链上 深浅拷贝 深浅拷贝只针对引用数据类型
浅拷贝:如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 拷贝对象:Object.assign() / 展开运算符 {...obj}拷贝对象 拷贝数组:Array.prototype.concat() 或者 [...arr] 深拷贝:拷贝的是对象,不是地址 通过递归实现深拷贝 lodash中的_.cloneDeep()
JSON.stringify() 异常 抛出异常 throw msg throw new Error(msg) 异常捕获 js try {
+
+} catch (err) {
+
+} finally {
+
+}
try {
+
+} catch (err) {
+
+} finally {
+
+}
debugger debugger
this 普通函数 箭头函数 箭头函数中并不存在this 箭头函数会默认绑定外层this的值,所以在箭头函数中this的值和外层的this是一样的 箭头函数中的this引用的就是最近作用域中的this 向外层作用域中,一层一层查找this,直到有this的定义 改变this指向 fun.call(thisArg, arg1, arg2...) thisArg:fun函数运营时指定的this值 arg1,arg2:传递的其他参数 apply(thisArg, [argsArray]) thisArg:fun函数运营时指定的this值 argsArray:传递的值,必须包含在数组里 bind() bind不会调用函数,但能改变函数内部this的指向 fun.bind(thisArg, arg1, arg2...) thisArg:fun函数运营时指定的this值 arg1,arg2:传递的其他参数 返回由指定this值和初始化参数改造的原函数的拷贝 防抖(debounce) 思路 声明一个定时器 每次触发事件都先判断是否有定时器,如果有先清除 如果没有则开启定时器并保存变量 在定时器中调用要执行的函数 js const box = document. querySelector ( '.box' )
+let i = 1
+function mouseMove () {
+ box.innerHTML = i ++
+}
+
+function debounce ( fn , t ) {
+ let timer
+ return function () {
+ if (timer) clearTimeout (timer)
+ timer = setTimeout ( function () {
+ fn ()
+ }, t)
+ }
+}
+
+box. addEventListener ( 'mousemove' , debounce (mouseMove, 500 ))
const box = document. querySelector ( '.box' )
+let i = 1
+function mouseMove () {
+ box.innerHTML = i ++
+}
+
+function debounce ( fn , t ) {
+ let timer
+ return function () {
+ if (timer) clearTimeout (timer)
+ timer = setTimeout ( function () {
+ fn ()
+ }, t)
+ }
+}
+
+box. addEventListener ( 'mousemove' , debounce (mouseMove, 500 ))
节流(throttle) 单位时间内,频繁触发事件,只执行一次 lodash库的_.throttle(fun, 时间)
思路 声明一个定时器 每次触发事件都判断是否有定时器,如果有则不开启新定时器 如果没有定时器则开启定时器并保存变量 定时器里调用执行的函数 定时器里要把上一个定时器清空 js function throttle ( fn , t ) {
+ let timer = null
+ return function () {
+ if ( ! timer) {
+ timer = setTimeout ( function (){
+ fn ()
+ // setTimeout中无法删除定时器,因为定时器还在运作,所以不能用clearTimeout
+ timer = null
+ }, t)
+ }
+ }
+}
+
+box. addEventListener ( 'mousemove' , throttle (mouseMove, 500 ))
function throttle ( fn , t ) {
+ let timer = null
+ return function () {
+ if ( ! timer) {
+ timer = setTimeout ( function (){
+ fn ()
+ // setTimeout中无法删除定时器,因为定时器还在运作,所以不能用clearTimeout
+ timer = null
+ }, t)
+ }
+ }
+}
+
+box. addEventListener ( 'mousemove' , throttle (mouseMove, 500 ))
案例:页面打开,记录上一次的视频播放位置 两个事件 ontimeupdate:事件在视频/音频当前播放位置发生改变时触发 onloadeddata:事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频的下一帧时触发 js video.ontimeupdadte = _. throttle (() => {
+ localStorage. setItem ( 'currentTime' , video.currentTime)
+}, 1000 )
+
+video. onloadeddata = () => {
+ video.currentTime = localStorage. getItem ( 'currentTime' ) || 0
+}
video.ontimeupdadte = _. throttle (() => {
+ localStorage. setItem ( 'currentTime' , video.currentTime)
+}, 1000 )
+
+video. onloadeddata = () => {
+ video.currentTime = localStorage. getItem ( 'currentTime' ) || 0
+}
ES6 Promise 所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise
对象有以下两个特点。
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
基本用法 ES6 规定,Promise
对象是一个构造函数,用来生成Promise
实例。
下面代码创造了一个Promise
实例。
js const promise = new Promise ( function ( resolve , reject ) {
+ // ... some code
+
+ if ( /* 异步操作成功 */ ){
+ resolve (value);
+ } else {
+ reject (error);
+ }
+});
const promise = new Promise ( function ( resolve , reject ) {
+ // ... some code
+
+ if ( /* 异步操作成功 */ ){
+ resolve (value);
+ } else {
+ reject (error);
+ }
+});
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供。
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
js promise. then ( function ( value ) {
+ // success
+}, function ( error ) {
+ // failure
+});
promise. then ( function ( value ) {
+ // success
+}, function ( error ) {
+ // failure
+});
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise
对象传出的值作为参数。
下面是一个Promise
对象的简单例子。
js function timeout ( ms ) {
+ return new Promise (( resolve , reject ) => {
+ setTimeout (resolve, ms, 'done' ); //setTimeout的第三个参数是给第一个函数参数传递的参数,即done会传递给resolve函数作为参数
+ });
+}
+
+timeout ( 100 ). then (( value ) => {
+ console. log (value);
+});
function timeout ( ms ) {
+ return new Promise (( resolve , reject ) => {
+ setTimeout (resolve, ms, 'done' ); //setTimeout的第三个参数是给第一个函数参数传递的参数,即done会传递给resolve函数作为参数
+ });
+}
+
+timeout ( 100 ). then (( value ) => {
+ console. log (value);
+});
上面代码中,timeout
方法返回一个Promise
实例,表示一段时间以后才会发生的结果。过了指定的时间(ms
参数)以后,Promise
实例的状态变为resolved
,就会触发then
方法绑定的回调函数。
Promise 新建后就会立即执行。
js let promise = new Promise ( function ( resolve , reject ) {
+ console. log ( 'Promise' );
+ resolve ();
+});
+
+promise. then ( function () {
+ console. log ( 'resolved.' );
+});
+
+console. log ( 'Hi!' );
+
+// Promise
+// Hi!
+// resolved
let promise = new Promise ( function ( resolve , reject ) {
+ console. log ( 'Promise' );
+ resolve ();
+});
+
+promise. then ( function () {
+ console. log ( 'resolved.' );
+});
+
+console. log ( 'Hi!' );
+
+// Promise
+// Hi!
+// resolved
上面代码中,Promise 新建后立即执行,所以首先输出的是Promise
。然后,then
方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved
最后输出。
下面是异步加载图片的例子。
js function loadImageAsync ( url ) {
+ return new Promise ( function ( resolve , reject ) {
+ const image = new Image ();
+
+ image. onload = function () {
+ resolve (image);
+ };
+
+ image. onerror = function () {
+ reject ( new Error ( 'Could not load image at ' + url));
+ };
+
+ image.src = url;
+ });
+}
function loadImageAsync ( url ) {
+ return new Promise ( function ( resolve , reject ) {
+ const image = new Image ();
+
+ image. onload = function () {
+ resolve (image);
+ };
+
+ image. onerror = function () {
+ reject ( new Error ( 'Could not load image at ' + url));
+ };
+
+ image.src = url;
+ });
+}
上面代码中,使用Promise
包装了一个图片加载的异步操作。如果加载成功,就调用resolve
方法,否则就调用reject
方法。
Generator Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。本章详细介绍 Generator 函数的语法和 API,它的异步编程应用请看《Generator 函数的异步应用》一章。
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”)。
js function* helloWorldGenerator () {
+ yield 'hello' ;
+ yield 'world' ;
+ return 'ending' ;
+}
+
+var hw = helloWorldGenerator ();
function* helloWorldGenerator () {
+ yield 'hello' ;
+ yield 'world' ;
+ return 'ending' ;
+}
+
+var hw = helloWorldGenerator ();
上面代码定义了一个 Generator 函数helloWorldGenerator
,它内部有两个yield
表达式(hello
和world
),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
js hw. next ()
+// { value: 'hello', done: false }
+
+hw. next ()
+// { value: 'world', done: false }
+
+hw. next ()
+// { value: 'ending', done: true }
+
+hw. next ()
+// { value: undefined, done: true }
hw. next ()
+// { value: 'hello', done: false }
+
+hw. next ()
+// { value: 'world', done: false }
+
+hw. next ()
+// { value: 'ending', done: true }
+
+hw. next ()
+// { value: undefined, done: true }
yield 表达式 由于 Generator 函数返回的遍历器对象,只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
遍历器对象的next
方法的运行逻辑如下。
(1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
(4)如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
需要注意的是,yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
js function* gen () {
+ yield 123 + 456 ;
+}
function* gen () {
+ yield 123 + 456 ;
+}
上面代码中,yield
后面的表达式123 + 456
,不会立即求值,只会在next
方法将指针移到这一句时,才会求值。
yield
表达式与return
语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return
语句,但是可以执行多次(或者说多个)yield
表达式。正常函数只能返回一个值,因为只能执行一次return
;Generator 函数可以返回一系列的值,因为可以有任意多个yield
。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。
await 正常情况下,await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
js async function f () {
+ // 等同于
+ // return 123;
+ return await 123 ;
+}
+
+f (). then ( v => console. log (v))
+// 123
async function f () {
+ // 等同于
+ // return 123;
+ return await 123 ;
+}
+
+f (). then ( v => console. log (v))
+// 123
上面代码中,await
命令的参数是数值123
,这时等同于return 123
。
另一种情况是,await
命令后面是一个thenable
对象(即定义了then
方法的对象),那么await
会将其等同于 Promise 对象。
js class Sleep {
+ constructor ( timeout ) {
+ this .timeout = timeout;
+ }
+ then ( resolve , reject ) {
+ const startTime = Date. now ();
+ setTimeout (
+ () => resolve (Date. now () - startTime),
+ this .timeout
+ );
+ }
+}
+
+( async () => {
+ const sleepTime = await new Sleep ( 1000 );
+ console. log (sleepTime);
+})();
+// 1000
class Sleep {
+ constructor ( timeout ) {
+ this .timeout = timeout;
+ }
+ then ( resolve , reject ) {
+ const startTime = Date. now ();
+ setTimeout (
+ () => resolve (Date. now () - startTime),
+ this .timeout
+ );
+ }
+}
+
+( async () => {
+ const sleepTime = await new Sleep ( 1000 );
+ console. log (sleepTime);
+})();
+// 1000
上面代码中,await
命令后面是一个Sleep
对象的实例。这个实例不是 Promise 对象,但是因为定义了then
方法,await
会将其视为Promise
处理。
这个例子还演示了如何实现休眠效果。JavaScript 一直没有休眠的语法,但是借助await
命令就可以让程序停顿指定的时间。下面给出了一个简化的sleep
实现。
js function sleep ( interval ) {
+ return new Promise ( resolve => {
+ setTimeout (resolve, interval);
+ })
+}
+
+// 用法
+async function one2FiveInAsync () {
+ for ( let i = 1 ; i <= 5 ; i ++ ) {
+ console. log (i);
+ await sleep ( 1000 );
+ }
+}
+
+one2FiveInAsync ();
function sleep ( interval ) {
+ return new Promise ( resolve => {
+ setTimeout (resolve, interval);
+ })
+}
+
+// 用法
+async function one2FiveInAsync () {
+ for ( let i = 1 ; i <= 5 ; i ++ ) {
+ console. log (i);
+ await sleep ( 1000 );
+ }
+}
+
+one2FiveInAsync ();
await
命令后面的 Promise 对象如果变为reject
状态,则reject
的参数会被catch
方法的回调函数接收到。
js async function f () {
+ await Promise . reject ( '出错了' );
+}
+
+f ()
+. then ( v => console. log (v))
+. catch ( e => console. log (e))
+// 出错了
async function f () {
+ await Promise . reject ( '出错了' );
+}
+
+f ()
+. then ( v => console. log (v))
+. catch ( e => console. log (e))
+// 出错了
注意,上面代码中,await
语句前面没有return
,但是reject
方法的参数依然传入了catch
方法的回调函数。这里如果在await
前面加上return
,效果是一样的。
任何一个await
语句后面的 Promise 对象变为reject
状态,那么整个async
函数都会中断执行。
js async function f () {
+ await Promise . reject ( '出错了' );
+ await Promise . resolve ( 'hello world' ); // 不会执行
+}
async function f () {
+ await Promise . reject ( '出错了' );
+ await Promise . resolve ( 'hello world' ); // 不会执行
+}
上面代码中,第二个await
语句是不会执行的,因为第一个await
语句状态变成了reject
。
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await
放在try...catch
结构里面,这样不管这个异步操作是否成功,第二个await
都会执行。
js async function f () {
+ try {
+ await Promise . reject ( '出错了' );
+ } catch (e) {
+ }
+ return await Promise . resolve ( 'hello world' );
+}
+
+f ()
+. then ( v => console. log (v))
+// hello world
async function f () {
+ try {
+ await Promise . reject ( '出错了' );
+ } catch (e) {
+ }
+ return await Promise . resolve ( 'hello world' );
+}
+
+f ()
+. then ( v => console. log (v))
+// hello world
另一种方法是await
后面的 Promise 对象再跟一个catch
方法,处理前面可能出现的错误。
js async function f () {
+ await Promise . reject ( '出错了' )
+ . catch ( e => console. log (e));
+ return await Promise . resolve ( 'hello world' );
+}
+
+f ()
+. then ( v => console. log (v))
+// 出错了
+// hello world
async function f () {
+ await Promise . reject ( '出错了' )
+ . catch ( e => console. log (e));
+ return await Promise . resolve ( 'hello world' );
+}
+
+f ()
+. then ( v => console. log (v))
+// 出错了
+// hello world
async async 函数是什么?一句话,它就是 Generator 函数的语法糖。返回值是 Promise 对象 。
Generator 函数,依次读取两个文件。
js const fs = require ( 'fs' );
+
+const readFile = function ( fileName ) {
+ return new Promise ( function ( resolve , reject ) {
+ fs. readFile (fileName, function ( error , data ) {
+ if (error) return reject (error);
+ resolve (data);
+ });
+ });
+};
+
+const gen = function* () {
+ const f1 = yield readFile ( '/etc/fstab' );
+ const f2 = yield readFile ( '/etc/shells' );
+ console. log (f1. toString ());
+ console. log (f2. toString ());
+};
+
+const g = gen ();
+g. next ().value. then ( function ( data ) {
+ g. next (data).value. then ( function ( data ) {
+ g. next (data);
+ });
+});
const fs = require ( 'fs' );
+
+const readFile = function ( fileName ) {
+ return new Promise ( function ( resolve , reject ) {
+ fs. readFile (fileName, function ( error , data ) {
+ if (error) return reject (error);
+ resolve (data);
+ });
+ });
+};
+
+const gen = function* () {
+ const f1 = yield readFile ( '/etc/fstab' );
+ const f2 = yield readFile ( '/etc/shells' );
+ console. log (f1. toString ());
+ console. log (f2. toString ());
+};
+
+const g = gen ();
+g. next ().value. then ( function ( data ) {
+ g. next (data).value. then ( function ( data ) {
+ g. next (data);
+ });
+});
上面代码的函数gen
可以写成async
函数,就是下面这样。
js const asyncReadFile = async function () {
+ const f1 = await readFile ( '/etc/fstab' );
+ const f2 = await readFile ( '/etc/shells' );
+ console. log (f1. toString ());
+ console. log (f2. toString ());
+};
const asyncReadFile = async function () {
+ const f1 = await readFile ( '/etc/fstab' );
+ const f2 = await readFile ( '/etc/shells' );
+ console. log (f1. toString ());
+ console. log (f2. toString ());
+};
一比较就会发现,async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
,仅此而已。
async
函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数一模一样,只要一行。
js asyncReadFile ();
asyncReadFile ();
上面的代码调用了asyncReadFile
函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next
方法,或者用co
模块,才能真正执行,得到最后结果。
(2)更好的语义。
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then
方法指定下一步的操作。
进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
基本用法 async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
下面是一个例子。
js async function getStockPriceByName ( name ) {
+ const symbol = await getStockSymbol (name);
+ const stockPrice = await getStockPrice (symbol);
+ return stockPrice;
+}
+
+getStockPriceByName ( 'goog' ). then ( function ( result ) {
+ console. log (result);
+});
async function getStockPriceByName ( name ) {
+ const symbol = await getStockSymbol (name);
+ const stockPrice = await getStockPrice (symbol);
+ return stockPrice;
+}
+
+getStockPriceByName ( 'goog' ). then ( function ( result ) {
+ console. log (result);
+});
上面代码是一个获取股票报价的函数,函数前面的async
关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise
对象。
下面是另一个例子,指定多少毫秒后输出一个值。
js function timeout ( ms ) {
+ return new Promise (( resolve ) => {
+ setTimeout (resolve, ms);
+ });
+}
+
+async function asyncPrint ( value , ms ) {
+ await timeout (ms);
+ console. log (value);
+}
+
+asyncPrint ( 'hello world' , 50 );
function timeout ( ms ) {
+ return new Promise (( resolve ) => {
+ setTimeout (resolve, ms);
+ });
+}
+
+async function asyncPrint ( value , ms ) {
+ await timeout (ms);
+ console. log (value);
+}
+
+asyncPrint ( 'hello world' , 50 );
上面代码指定 50 毫秒以后,输出hello world
。
由于async
函数返回的是 Promise 对象,可以作为await
命令的参数。所以,上面的例子也可以写成下面的形式。
js async function timeout ( ms ) {
+ await new Promise (( resolve ) => {
+ setTimeout (resolve, ms);
+ });
+}
+
+async function asyncPrint ( value , ms ) {
+ await timeout (ms);
+ console. log (value);
+}
+
+asyncPrint ( 'hello world' , 50 );
async function timeout ( ms ) {
+ await new Promise (( resolve ) => {
+ setTimeout (resolve, ms);
+ });
+}
+
+async function asyncPrint ( value , ms ) {
+ await timeout (ms);
+ console. log (value);
+}
+
+asyncPrint ( 'hello world' , 50 );
async 函数有多种使用形式。
js // 函数声明
+async function foo () {}
+
+// 函数表达式
+const foo = async function () {};
+
+// 对象的方法
+let obj = { async foo () {} };
+obj. foo (). then ( ... )
+
+// Class 的方法
+class Storage {
+ constructor () {
+ this .cachePromise = caches. open ( 'avatars' );
+ }
+
+ async getAvatar ( name ) {
+ const cache = await this .cachePromise;
+ return cache. match ( \`/avatars/\${ name }.jpg\` );
+ }
+}
+
+const storage = new Storage ();
+storage. getAvatar ( 'jake' ). then (…);
+
+// 箭头函数
+const foo = async () => {};
// 函数声明
+async function foo () {}
+
+// 函数表达式
+const foo = async function () {};
+
+// 对象的方法
+let obj = { async foo () {} };
+obj. foo (). then ( ... )
+
+// Class 的方法
+class Storage {
+ constructor () {
+ this .cachePromise = caches. open ( 'avatars' );
+ }
+
+ async getAvatar ( name ) {
+ const cache = await this .cachePromise;
+ return cache. match ( \`/avatars/\${ name }.jpg\` );
+ }
+}
+
+const storage = new Storage ();
+storage. getAvatar ( 'jake' ). then (…);
+
+// 箭头函数
+const foo = async () => {};
语法 返回Promise对象 async
函数返回一个 Promise 对象。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
js async function f () {
+ return 'hello world' ;
+}
+
+f (). then ( v => console. log (v))
+// "hello world"
async function f () {
+ return 'hello world' ;
+}
+
+f (). then ( v => console. log (v))
+// "hello world"
上面代码中,函数f
内部return
命令返回的值,会被then
方法回调函数接收到。
async
函数内部抛出错误,会导致返回的 Promise 对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。
js async function f () {
+ throw new Error ( '出错了' );
+}
+
+f (). then (
+ v => console. log ( 'resolve' , v),
+ e => console. log ( 'reject' , e)
+)
+//reject Error: 出错了
async function f () {
+ throw new Error ( '出错了' );
+}
+
+f (). then (
+ v => console. log ( 'resolve' , v),
+ e => console. log ( 'reject' , e)
+)
+//reject Error: 出错了
Promise对象状态变化 async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
下面是一个例子。
js async function getTitle ( url ) {
+ let response = await fetch (url);
+ let html = await response. text ();
+ return html. match ( / <title>( [\\s\\S] + )< \\/ title> / i )[ 1 ];
+}
+getTitle ( 'https://tc39.github.io/ecma262/' ). then (console.log)
+// "ECMAScript 2017 Language Specification"
async function getTitle ( url ) {
+ let response = await fetch (url);
+ let html = await response. text ();
+ return html. match ( /<title>( [\\s\\S] + )< \\/ title>/ i )[ 1 ];
+}
+getTitle ( 'https://tc39.github.io/ecma262/' ). then (console.log)
+// "ECMAScript 2017 Language Specification"
上面代码中,函数getTitle
内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then
方法里面的console.log
。
错误处理 如果await
后面的异步操作出错,那么等同于async
函数返回的 Promise 对象被reject
。
js async function f () {
+ await new Promise ( function ( resolve , reject ) {
+ throw new Error ( '出错了' );
+ });
+}
+
+f ()
+. then ( v => console. log (v))
+. catch ( e => console. log (e))
+// Error:出错了
async function f () {
+ await new Promise ( function ( resolve , reject ) {
+ throw new Error ( '出错了' );
+ });
+}
+
+f ()
+. then ( v => console. log (v))
+. catch ( e => console. log (e))
+// Error:出错了
上面代码中,async
函数f
执行后,await
后面的 Promise 对象会抛出一个错误对象,导致catch
方法的回调函数被调用,它的参数就是抛出的错误对象。
防止出错的方法,也是将其放在try...catch
代码块之中。
js async function f () {
+ try {
+ await new Promise ( function ( resolve , reject ) {
+ throw new Error ( '出错了' );
+ });
+ } catch (e) {
+ }
+ return await ( 'hello world' );
+}
async function f () {
+ try {
+ await new Promise ( function ( resolve , reject ) {
+ throw new Error ( '出错了' );
+ });
+ } catch (e) {
+ }
+ return await ( 'hello world' );
+}
如果有多个await
命令,可以统一放在try...catch
结构中。
js async function main () {
+ try {
+ const val1 = await firstStep ();
+ const val2 = await secondStep (val1);
+ const val3 = await thirdStep (val1, val2);
+
+ console. log ( 'Final: ' , val3);
+ }
+ catch (err) {
+ console. error (err);
+ }
+}
async function main () {
+ try {
+ const val1 = await firstStep ();
+ const val2 = await secondStep (val1);
+ const val3 = await thirdStep (val1, val2);
+
+ console. log ( 'Final: ' , val3);
+ }
+ catch (err) {
+ console. error (err);
+ }
+}
下面的例子使用try...catch
结构,实现多次重复尝试。
js const superagent = require ( 'superagent' );
+const NUM_RETRIES = 3 ;
+
+async function test () {
+ let i;
+ for (i = 0 ; i < NUM_RETRIES ; ++ i) {
+ try {
+ await superagent. get ( 'http://google.com/this-throws-an-error' );
+ break ;
+ } catch (err) {}
+ }
+ console. log (i); // 3
+}
+
+test ();
const superagent = require ( 'superagent' );
+const NUM_RETRIES = 3 ;
+
+async function test () {
+ let i;
+ for (i = 0 ; i < NUM_RETRIES ; ++ i) {
+ try {
+ await superagent. get ( 'http://google.com/this-throws-an-error' );
+ break ;
+ } catch (err) {}
+ }
+ console. log (i); // 3
+}
+
+test ();
上面代码中,如果await
操作成功,就会使用break
语句退出循环;如果失败,会被catch
语句捕捉,然后进入下一轮循环。
async 函数的实现原理 async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
js async function fn ( args ) {
+ // ...
+}
+
+// 等同于
+
+function fn ( args ) {
+ return spawn ( function* () {
+ // ...
+ });
+}
async function fn ( args ) {
+ // ...
+}
+
+// 等同于
+
+function fn ( args ) {
+ return spawn ( function* () {
+ // ...
+ });
+}
所有的async
函数都可以写成上面的第二种形式,其中的spawn
函数就是自动执行器。
spawn
函数的实现
js function spawn ( genF ) {
+ return new Promise ( function ( resolve , reject ) {
+ const gen = genF ();
+ function step ( nextF ) {
+ let next;
+ try {
+ next = nextF ();
+ } catch (e) {
+ return reject (e);
+ }
+ if (next.done) {
+ return resolve (next.value);
+ }
+ Promise . resolve (next.value). then ( function ( v ) {
+ step ( function () { return gen. next (v); });
+ }, function ( e ) {
+ step ( function () { return gen. throw (e); });
+ });
+ }
+ step ( function () { return gen. next ( undefined ); });
+ });
+}
function spawn ( genF ) {
+ return new Promise ( function ( resolve , reject ) {
+ const gen = genF ();
+ function step ( nextF ) {
+ let next;
+ try {
+ next = nextF ();
+ } catch (e) {
+ return reject (e);
+ }
+ if (next.done) {
+ return resolve (next.value);
+ }
+ Promise . resolve (next.value). then ( function ( v ) {
+ step ( function () { return gen. next (v); });
+ }, function ( e ) {
+ step ( function () { return gen. throw (e); });
+ });
+ }
+ step ( function () { return gen. next ( undefined ); });
+ });
+}
实例:按顺序完成异步操作 实际开发中,经常遇到一组异步操作,需要按照顺序完成。比如,依次远程读取一组 URL,然后按照读取的顺序输出结果。
Promise 的写法如下。
js function logInOrder ( urls ) {
+ // 远程读取所有URL
+ const textPromises = urls. map ( url => {
+ return fetch (url). then ( response => response. text ());
+ });
+
+ // 按次序输出
+ textPromises. reduce (( chain , textPromise ) => {
+ return chain. then (() => textPromise)
+ . then ( text => console. log (text));
+ }, Promise . resolve ());
+}
function logInOrder ( urls ) {
+ // 远程读取所有URL
+ const textPromises = urls. map ( url => {
+ return fetch (url). then ( response => response. text ());
+ });
+
+ // 按次序输出
+ textPromises. reduce (( chain , textPromise ) => {
+ return chain. then (() => textPromise)
+ . then ( text => console. log (text));
+ }, Promise . resolve ());
+}
上面代码使用fetch
方法,同时远程读取一组 URL。每个fetch
操作都返回一个 Promise 对象,放入textPromises
数组。然后,reduce
方法依次处理每个 Promise 对象,然后使用then
,将所有 Promise 对象连起来,因此就可以依次输出结果。
这种写法不太直观,可读性比较差。下面是 async 函数实现。
js async function logInOrder ( urls ) {
+ for ( const url of urls) {
+ const response = await fetch (url);
+ console. log ( await response. text ());
+ }
+}
async function logInOrder ( urls ) {
+ for ( const url of urls) {
+ const response = await fetch (url);
+ console. log ( await response. text ());
+ }
+}
上面代码确实大大简化,问题是所有远程操作都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求。
js async function logInOrder ( urls ) {
+ // 并发读取远程URL
+ const textPromises = urls. map ( async url => {
+ const response = await fetch (url);
+ return response. text ();
+ });
+
+ // 按次序输出
+ for ( const textPromise of textPromises) {
+ console. log ( await textPromise);
+ }
+}
async function logInOrder ( urls ) {
+ // 并发读取远程URL
+ const textPromises = urls. map ( async url => {
+ const response = await fetch (url);
+ return response. text ();
+ });
+
+ // 按次序输出
+ for ( const textPromise of textPromises) {
+ console. log ( await textPromise);
+ }
+}
上面代码中,虽然map
方法的参数是async
函数,但它是并发执行的,因为只有async
函数内部是继发执行,外部不受影响。后面的for..of
循环内部使用了await
,因此实现了按顺序输出。
Class 基本语法 JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。
js function Point ( x , y ) {
+ this .x = x;
+ this .y = y;
+}
+
+Point . prototype . toString = function () {
+ return '(' + this .x + ', ' + this .y + ')' ;
+};
+
+var p = new Point ( 1 , 2 );
function Point ( x , y ) {
+ this .x = x;
+ this .y = y;
+}
+
+Point . prototype . toString = function () {
+ return '(' + this .x + ', ' + this .y + ')' ;
+};
+
+var p = new Point ( 1 , 2 );
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。
基本上,ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class
改写,就是下面这样。
js class Point {
+ constructor ( x , y ) {
+ this .x = x;
+ this .y = y;
+ }
+
+ toString () {
+ return '(' + this .x + ', ' + this .y + ')' ;
+ }
+}
class Point {
+ constructor ( x , y ) {
+ this .x = x;
+ this .y = y;
+ }
+
+ toString () {
+ return '(' + this .x + ', ' + this .y + ')' ;
+ }
+}
上面代码定义了一个“类”,可以看到里面有一个constructor()
方法,这就是构造方法,而this
关键字则代表实例对象。这种新的 Class 写法,本质上与本章开头的 ES5 的构造函数Point
是一致的。
Point
类除了构造方法,还定义了一个toString()
方法。注意,定义toString()
方法的时候,前面不需要加上function
这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。
ES6 的类,完全可以看作构造函数的另一种写法。
js class Point {
+ // ...
+}
+
+typeof Point // "function"
+Point === Point . prototype . constructor // true
class Point {
+ // ...
+}
+
+typeof Point // "function"
+Point === Point . prototype . constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
使用的时候,也是直接对类使用new
命令,跟构造函数的用法完全一致。
js class Bar {
+ doStuff () {
+ console. log ( 'stuff' );
+ }
+}
+
+const b = new Bar ();
+b. doStuff () // "stuff"
class Bar {
+ doStuff () {
+ console. log ( 'stuff' );
+ }
+}
+
+const b = new Bar ();
+b. doStuff () // "stuff"
构造函数的prototype
属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype
属性上面。
js class Point {
+ constructor () {
+ // ...
+ }
+
+ toString () {
+ // ...
+ }
+
+ toValue () {
+ // ...
+ }
+}
+
+// 等同于
+
+Point . prototype = {
+ constructor () {},
+ toString () {},
+ toValue () {},
+};
class Point {
+ constructor () {
+ // ...
+ }
+
+ toString () {
+ // ...
+ }
+
+ toValue () {
+ // ...
+ }
+}
+
+// 等同于
+
+Point . prototype = {
+ constructor () {},
+ toString () {},
+ toValue () {},
+};
上面代码中,constructor()
、toString()
、toValue()
这三个方法,其实都是定义在Point.prototype
上面。
实例属性新写法 ES2022 为类的实例属性,又规定了一种新写法。实例属性现在除了可以定义在constructor()
方法里面的this
上面,也可以定义在类内部的最顶层。
js // 原来的写法
+class IncreasingCounter {
+ constructor () {
+ this ._count = 0 ;
+ }
+ get value () {
+ console. log ( 'Getting the current value!' );
+ return this ._count;
+ }
+ increment () {
+ this ._count ++ ;
+ }
+}
// 原来的写法
+class IncreasingCounter {
+ constructor () {
+ this ._count = 0 ;
+ }
+ get value () {
+ console. log ( 'Getting the current value!' );
+ return this ._count;
+ }
+ increment () {
+ this ._count ++ ;
+ }
+}
上面示例中,实例属性_count
定义在constructor()
方法里面的this
上面。
现在的新写法是,这个属性也可以定义在类的最顶层,其他都不变。
js class IncreasingCounter {
+ _count = 0 ;
+ get value () {
+ console. log ( 'Getting the current value!' );
+ return this ._count;
+ }
+ increment () {
+ this ._count ++ ;
+ }
+}
class IncreasingCounter {
+ _count = 0 ;
+ get value () {
+ console. log ( 'Getting the current value!' );
+ return this ._count;
+ }
+ increment () {
+ this ._count ++ ;
+ }
+}
上面代码中,实例属性_count
与取值函数value()
和increment()
方法,处于同一个层级。这时,不需要在实例属性前面加上this
。
注意,新写法定义的属性是实例对象自身的属性,而不是定义在实例对象的原型上面。
这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。
js class foo {
+ bar = 'hello' ;
+ baz = 'world' ;
+
+ constructor () {
+ // ...
+ }
+}
class foo {
+ bar = 'hello' ;
+ baz = 'world' ;
+
+ constructor () {
+ // ...
+ }
+}
上面的代码,一眼就能看出,foo
类有两个实例属性,一目了然。另外,写起来也比较简洁。
getter和setter 与 ES5 一样,在“类”的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
js class MyClass {
+ constructor () {
+ // ...
+ }
+ get prop () {
+ return 'getter' ;
+ }
+ set prop ( value ) {
+ console. log ( 'setter: ' + value);
+ }
+}
+
+let inst = new MyClass ();
+
+inst.prop = 123 ;
+// setter: 123
+
+inst.prop
+// 'getter'
class MyClass {
+ constructor () {
+ // ...
+ }
+ get prop () {
+ return 'getter' ;
+ }
+ set prop ( value ) {
+ console. log ( 'setter: ' + value);
+ }
+}
+
+let inst = new MyClass ();
+
+inst.prop = 123 ;
+// setter: 123
+
+inst.prop
+// 'getter'
上面代码中,prop
属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。
存值函数和取值函数是设置在属性的 Descriptor 对象上的。
js class CustomHTMLElement {
+ constructor ( element ) {
+ this .element = element;
+ }
+
+ get html () {
+ return this .element.innerHTML;
+ }
+
+ set html ( value ) {
+ this .element.innerHTML = value;
+ }
+}
+
+var descriptor = Object. getOwnPropertyDescriptor (
+ CustomHTMLElement . prototype , "html"
+);
+
+"get" in descriptor // true
+"set" in descriptor // true
class CustomHTMLElement {
+ constructor ( element ) {
+ this .element = element;
+ }
+
+ get html () {
+ return this .element.innerHTML;
+ }
+
+ set html ( value ) {
+ this .element.innerHTML = value;
+ }
+}
+
+var descriptor = Object. getOwnPropertyDescriptor (
+ CustomHTMLElement . prototype , "html"
+);
+
+"get" in descriptor // true
+"set" in descriptor // true
上面代码中,存值函数和取值函数是定义在html
属性的描述对象上面,这与 ES5 完全一致。
属性表达式 类的属性名,可以采用表达式。
js let methodName = 'getArea' ;
+
+class Square {
+ constructor ( length ) {
+ // ...
+ }
+
+ [methodName]() {
+ // ...
+ }
+}
let methodName = 'getArea' ;
+
+class Square {
+ constructor ( length ) {
+ // ...
+ }
+
+ [methodName]() {
+ // ...
+ }
+}
上面代码中,Square
类的方法名getArea
,是从表达式得到的。
Class表达式 与函数一样,类也可以使用表达式的形式定义。
js const MyClass = class Me {
+ getClassName () {
+ return Me.name;
+ }
+};
const MyClass = class Me {
+ getClassName () {
+ return Me.name;
+ }
+};
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是Me
,但是Me
只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass
引用。
静态方法 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
js class Foo {
+ static classMethod () {
+ return 'hello' ;
+ }
+}
+
+Foo. classMethod () // 'hello'
+
+var foo = new Foo ();
+foo. classMethod ()
+// TypeError: foo.classMethod is not a function
class Foo {
+ static classMethod () {
+ return 'hello' ;
+ }
+}
+
+Foo. classMethod () // 'hello'
+
+var foo = new Foo ();
+foo. classMethod ()
+// TypeError: foo.classMethod is not a function
上面代码中,Foo
类的classMethod
方法前有static
关键字,表明该方法是一个静态方法,可以直接在Foo
类上调用(Foo.classMethod()
),而不是在Foo
类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
注意,如果静态方法包含this
关键字,这个this
指的是类,而不是实例。
静态方法可以与非静态方法重名。
父类的静态方法,可以被子类继承。
静态属性 静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
js class Foo {
+}
+
+Foo.prop = 1 ;
+Foo.prop // 1
class Foo {
+}
+
+Foo.prop = 1 ;
+Foo.prop // 1
上面的写法为Foo
类定义了一个静态属性prop
。
私有方法和属性 在属性名之前使用#
表示。
in运算符 Class的继承 Class 可以通过extends
关键字实现继承,让子类继承父类的属性和方法。extends 的写法比 ES5 的原型链继承,要清晰和方便很多。
js class Point {
+}
+
+class ColorPoint extends Point {
+}
class Point {
+}
+
+class ColorPoint extends Point {
+}
在子类的构造函数中,只有调用super()
之后,才可以使用this
关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有super()
方法才能让子类实例继承父类。
父类所有的属性和方法,都会被子类继承,除了私有的属性和方法。
父类的静态属性和静态方法,也会被子类继承。
super
关键字,既可以当作函数使用,也可以当作对象使用。
大多数浏览器的 ES5 实现之中,每一个对象都有__proto__
属性,指向对应的构造函数的prototype
属性。Class 作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链。
(1)子类的__proto__
属性,表示构造函数的继承,总是指向父类。
(2)子类prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。
子类实例的__proto__
属性的__proto__
属性,指向父类实例的__proto__
属性。也就是说,子类的原型的原型,是父类的原型。 Module CommonJS 模块就是对象,输入时必须查找对象属性。
js // CommonJS模块
+let { stat, exists, readfile } = require ( 'fs' );
+
+// 等同于
+let _fs = require ( 'fs' );
+let stat = _fs.stat;
+let exists = _fs.exists;
+let readfile = _fs.readfile;
// CommonJS模块
+let { stat, exists, readfile } = require ( 'fs' );
+
+// 等同于
+let _fs = require ( 'fs' );
+let stat = _fs.stat;
+let exists = _fs.exists;
+let readfile = _fs.readfile;
上面代码的实质是整体加载fs
模块(即加载fs
的所有方法),生成一个对象(_fs
),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export
命令显式指定输出的代码,再通过import
命令输入。
js // ES6模块
+import { stat, exists, readFile } from 'fs' ;
// ES6模块
+import { stat, exists, readFile } from 'fs' ;
上面代码的实质是从fs
模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
严格模式 ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
。
export 模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export
关键字输出该变量。下面是一个 JS 文件,里面使用export
命令输出变量。
js // profile.js
+export var firstName = 'Michael' ;
+export var lastName = 'Jackson' ;
+export var year = 1958 ;
// profile.js
+export var firstName = 'Michael' ;
+export var lastName = 'Jackson' ;
+export var year = 1958 ;
上面代码是profile.js
文件,保存了用户信息。ES6 将其视为一个模块,里面用export
命令对外部输出了三个变量。
export
的写法,除了像上面这样,还有另外一种。
js // profile.js
+var firstName = 'Michael' ;
+var lastName = 'Jackson' ;
+var year = 1958 ;
+
+export { firstName, lastName, year };
// profile.js
+var firstName = 'Michael' ;
+var lastName = 'Jackson' ;
+var year = 1958 ;
+
+export { firstName, lastName, year };
上面代码在export
命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在var
语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。
export
命令除了输出变量,还可以输出函数或类(class)。
js export function multiply ( x , y ) {
+ return x * y;
+};
export function multiply ( x , y ) {
+ return x * y;
+};
上面代码对外输出一个函数multiply
。
通常情况下,export
输出的变量就是本来的名字,但是可以使用as
关键字重命名。
js function v1 () { ... }
+function v2 () { ... }
+
+export {
+ v1 as streamV1,
+ v2 as streamV2,
+ v2 as streamLatestVersion
+};
function v1 () { ... }
+function v2 () { ... }
+
+export {
+ v1 as streamV1,
+ v2 as streamV2,
+ v2 as streamLatestVersion
+};
上面代码使用as
关键字,重命名了函数v1
和v2
的对外接口。重命名后,v2
可以用不同的名字输出两次。
需要特别注意的是,export
命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
import 使用export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过import
命令加载这个模块。
js // main.js
+import { firstName, lastName, year } from './profile.js' ;
+
+function setName ( element ) {
+ element.textContent = firstName + ' ' + lastName;
+}
// main.js
+import { firstName, lastName, year } from './profile.js' ;
+
+function setName ( element ) {
+ element.textContent = firstName + ' ' + lastName;
+}
面代码的import
命令,用于加载profile.js
文件,并从中输入变量。import
命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js
)对外接口的名称相同。
如果想为输入的变量重新取一个名字,import
命令要使用as
关键字,将输入的变量重命名。
js import { lastName as surname } from './profile.js' ;
import { lastName as surname } from './profile.js' ;
import
命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
js import {a} from './xxx.js'
+
+a = {}; // Syntax Error : 'a' is read-only;
import {a} from './xxx.js'
+
+a = {}; // Syntax Error : 'a' is read-only;
上面代码中,脚本加载了变量a
,对其重新赋值就会报错,因为a
是一个只读的接口。但是,如果a
是一个对象,改写a
的属性是允许的。
js import {a} from './xxx.js'
+
+a.foo = 'hello' ; // 合法操作
import {a} from './xxx.js'
+
+a.foo = 'hello' ; // 合法操作
上面代码中,a
的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。
import
后面的from
指定模块文件的位置,可以是相对路径,也可以是绝对路径。如果不带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
js import { myMethod } from 'util' ;
import { myMethod } from 'util' ;
上面代码中,util
是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。
除了指定加载某个输出值,还可以使用整体加载,即用星号(*
)指定一个对象,所有输出值都加载在这个对象上面。
js import * as circle from './circle' ;
+
+console. log ( '圆面积:' + circle. area ( 4 ));
+console. log ( '圆周长:' + circle. circumference ( 14 ));
import * as circle from './circle' ;
+
+console. log ( '圆面积:' + circle. area ( 4 ));
+console. log ( '圆周长:' + circle. circumference ( 14 ));
export default 使用import
命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default
命令,为模块指定默认输出。
js // export-default.js
+export default function () {
+ console. log ( 'foo' );
+}
// export-default.js
+export default function () {
+ console. log ( 'foo' );
+}
上面代码是一个模块文件export-default.js
,它的默认输出是一个函数。
其他模块加载该模块时,import
命令可以为该匿名函数指定任意名字。
js // import-default.js
+import customName from './export-default' ;
+customName (); // 'foo'
// import-default.js
+import customName from './export-default' ;
+customName (); // 'foo'
上面代码的import
命令,可以用任意名称指向export-default.js
输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import
命令后面,不使用大括号。
export default
命令用在非匿名函数前,也是可以的。
js // export-default.js
+export default function foo () {
+ console. log ( 'foo' );
+}
+
+// 或者写成
+
+function foo () {
+ console. log ( 'foo' );
+}
+
+export default foo;
// export-default.js
+export default function foo () {
+ console. log ( 'foo' );
+}
+
+// 或者写成
+
+function foo () {
+ console. log ( 'foo' );
+}
+
+export default foo;
上面代码中,foo
函数的函数名foo
,在模块外部是无效的。加载的时候,视同匿名函数加载。
export和import复合写法 js export { foo, bar } from 'my_module' ;
+
+// 可以简单理解为
+import { foo, bar } from 'my_module' ;
+export { foo, bar };
export { foo, bar } from 'my_module' ;
+
+// 可以简单理解为
+import { foo, bar } from 'my_module' ;
+export { foo, bar };
上面代码中,export
和import
语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo
和bar
实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo
和bar
。
模块的接口改名和整体输出,也可以采用这种写法。
js // 接口改名
+export { foo as myFoo } from 'my_module' ;
+
+// 整体输出
+export * from 'my_module' ;
// 接口改名
+export { foo as myFoo } from 'my_module' ;
+
+// 整体输出
+export * from 'my_module' ;
默认接口的写法如下。
js export { default } from 'foo' ;
export { default } from 'foo' ;
具名接口改为默认接口的写法如下。
js export { es6 as default } from './someModule' ;
+
+// 等同于
+import { es6 } from './someModule' ;
+export default es6;
export { es6 as default } from './someModule' ;
+
+// 等同于
+import { es6 } from './someModule' ;
+export default es6;
同样地,默认接口也可以改名为具名接口。
js export { default as es6 } from './someModule' ;
export { default as es6 } from './someModule' ;
跨模块常量 js // constants.js 模块
+export const A = 1 ;
+export const B = 3 ;
+export const C = 4 ;
+
+// test1.js 模块
+import * as constants from './constants' ;
+console. log (constants. A ); // 1
+console. log (constants. B ); // 3
+
+// test2.js 模块
+import {A, B} from './constants' ;
+console. log ( A ); // 1
+console. log ( B ); // 3
// constants.js 模块
+export const A = 1 ;
+export const B = 3 ;
+export const C = 4 ;
+
+// test1.js 模块
+import * as constants from './constants' ;
+console. log (constants. A ); // 1
+console. log (constants. B ); // 3
+
+// test2.js 模块
+import {A, B} from './constants' ;
+console. log ( A ); // 1
+console. log ( B ); // 3
模块的继承 circleplus
模块,继承了circle
模块。
js // circleplus.js
+
+export * from 'circle' ;
+export var e = 2.71828182846 ;
+export default function ( x ) {
+ return Math. exp (x);
+}
// circleplus.js
+
+export * from 'circle' ;
+export var e = 2.71828182846 ;
+export default function ( x ) {
+ return Math. exp (x);
+}
这时,也可以将circle
的属性或方法,改名后再输出。
js // circleplus.js
+
+export { area as circleArea } from 'circle' ;
// circleplus.js
+
+export { area as circleArea } from 'circle' ;
上面代码表示,只输出circle
模块的area
方法,且将其改名为circleArea
。
加载上面模块的写法如下。
js // main.js
+
+import * as math from 'circleplus' ;
+import exp from 'circleplus' ;
+console. log ( exp (math.e));
// main.js
+
+import * as math from 'circleplus' ;
+import exp from 'circleplus' ;
+console. log ( exp (math.e));
上面代码中的import exp
表示,将circleplus
模块的默认方法加载为exp
方法。
`,514),e=[o];function c(t,r,E,y,i,d){return n(),a("div",null,e)}const h=s(p,[["render",c]]);export{u as __pageData,h as default};
diff --git a/assets/frontend_base_javascript.md.4c1235ab.lean.js b/assets/frontend_base_javascript.md.4c1235ab.lean.js
new file mode 100644
index 000000000..df1a0a828
--- /dev/null
+++ b/assets/frontend_base_javascript.md.4c1235ab.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"JavaScript","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/javascript.md","filePath":"frontend/base/javascript.md","lastUpdated":1694368780000}'),p={name:"frontend/base/javascript.md"},o=l("",514),e=[o];function c(t,r,E,y,i,d){return n(),a("div",null,e)}const h=s(p,[["render",c]]);export{u as __pageData,h as default};
diff --git a/assets/frontend_base_jquery.md.5c027802.js b/assets/frontend_base_jquery.md.5c027802.js
new file mode 100644
index 000000000..1271e2e5a
--- /dev/null
+++ b/assets/frontend_base_jquery.md.5c027802.js
@@ -0,0 +1,115 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"jQuery","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/jquery.md","filePath":"frontend/base/jquery.md","lastUpdated":1694368780000}'),o={name:"frontend/base/jquery.md"},e=l(`jQuery 顶级对象 $
是jQuery的别称,也是jQuery的顶级对象,相当于原生JavaScript肿的window,把元素用$
包装成jQuery对象,就可以调用jQuery的方法。
入口函数 js $ (document). ready ( function () {
+ //do something
+});
+$ ( function () {
+ //do something
+})
$ (document). ready ( function () {
+ //do something
+});
+$ ( function () {
+ //do something
+})
DOM对象和jQuery对象 用原生JS获取的对象是DOM对象 jQuery方法获取的元素是jQuery对象,本质是$
对DOM对象包装后产生的对象(伪数组形式存储) DOM和jQuery对象互相转换 DOM对象转jQuery对象:$(dom对象)
jQuery对象转DOM对象 $('div')[index]
$('div').get(index)
jQuery常用API 选择器 $("选择器")
筛选选择器 $('li:first')
$('li:last')
$('li:eq(2)')
:索引号等于2$('li:odd')
:索引号为奇数$('li:even')
:索引号为偶数 筛选方法 parent() children(selector) find(selector) siblings(selector):查找兄弟节点 不包括本身 nextAll([expr]):查找当前元素之后所有同辈元素 prevtAll([expr]):查找当前之前所有同辈元素 hasClass(class) eq(index) 遍历DOM元素(伪数组形式存储)的过程叫隐式迭代:给匹配到的所有元素进行循环遍历,执行相应的方法,而不用手动进行循环调用
jQuery支持链式编程
样式操作 操作样式:jQuery对象.css(属性, 值)
参数可以是对象形式,设置多组样式:jQuery对象.css({"color": "pink", "font-size": "15px"})
(属性可以不用加引号) 获取样式属性值:jQuery对象.css(属性)
设置类样式: jQuery对象.addClass(className)
jQuery对象.removeClass(className)
jQuery对象.toggleClass(className)
效果 显示隐藏 show([speed, [easing], [fn]]) 参数都可以省略,无动画直接显示 speed:三种预设速度之一的字符串(slow/normal/fast)或表示动画时长的毫秒数值 easing:用来切换指定效果,默认swing
,可用参数linear
fn:回调函数,在动画完成时执行的函数,每个元素执行一次 hide() toggle() 滑动 slideDown() slideUp() slideToggle() 动画队列停止排队:stop(),必须写在动画的前面
jQuery对象.children("ul").stop().slideToggle()
淡入淡出 fadeIn() fadeOut() fadeToggle() fadeTo([[speed], opacity, [easing], [fn]]):修改透明度 自定义动画 animate(params, [speed], [easing],[fn]) params:想要更改的样式属性,以对象形式传递,必传。属性名可以不带引号,如果是复合属性需要采取驼峰命名 属性操作 prop(属性名):获取元素固有属性 prop(属性名, 值):设置元素固有属性 attr(属性名):获取元素自定义属性 attr(属性名, 值):设置元素自定义属性 data():可以在指定元素上存取数据,并不会修改DOM元素结构,页面刷新,存放的数据会被移除,也可以获取h5自定义属性,不用data-开头 文本属性 html():相当于原生js中的innerHTML text():相当于原生js中的innerText val():相当于原生js的value 元素操作 txt - jQuery对象.each(function(index, domElement) { } ):遍历匹配的每一个元素,index是索引号,domElement是DOM元素对象,不是jQuery对象
+- $.each(obj, function(index, domElement) { }):遍历指定对象
+- 创建元素:var li = $("<li> </li>")
+- 添加元素
+ - element.append(li):拼接到最后
+ - element.prepend(li):插入到最前
+ - element.before(li):放到元素之后
+ - element.after(li):放到元素之前
+- 删除元素
+ - element.remove():删除匹配的元素
+ - element.empty():删除匹配元素的子节点
+ - element.html(""):等价于empty()
- jQuery对象.each(function(index, domElement) { } ):遍历匹配的每一个元素,index是索引号,domElement是DOM元素对象,不是jQuery对象
+- $.each(obj, function(index, domElement) { }):遍历指定对象
+- 创建元素:var li = $("<li> </li>")
+- 添加元素
+ - element.append(li):拼接到最后
+ - element.prepend(li):插入到最前
+ - element.before(li):放到元素之后
+ - element.after(li):放到元素之前
+- 删除元素
+ - element.remove():删除匹配的元素
+ - element.empty():删除匹配元素的子节点
+ - element.html(""):等价于empty()
尺寸、位置操作 尺寸 width()/height():只包含宽高 innerWidth()/innerHeight():+padding outerWidth()/outerHeight():+padding、border outerWidth(true)/outerHeight(true):+padding、border、margin 位置 txt - offset():设置或返回被选元素相对于文档(document)的偏移坐标,跟父级没有关系
+ - 有两个属性left、top
+ - 修改传递对象{top: 10, left: 30}
+- position():返回被选元素相对**带有定位父级**偏移坐标,如果父级都没有定位,以文档为准
+- scrollTop()/scrollLeft():被卷去的头部/左侧
+ - 可以传递参数直接跳到指定位置
- offset():设置或返回被选元素相对于文档(document)的偏移坐标,跟父级没有关系
+ - 有两个属性left、top
+ - 修改传递对象{top: 10, left: 30}
+- position():返回被选元素相对**带有定位父级**偏移坐标,如果父级都没有定位,以文档为准
+- scrollTop()/scrollLeft():被卷去的头部/左侧
+ - 可以传递参数直接跳到指定位置
jQuery事件 事件注册 事件处理 on element.on(events, [selector], fn)
events:一个或多个用空格分隔的事件类型,如click、keydown selector:元素的子元素选择器 fn:回调函数,即绑定在元素身上的侦听函数 js $ ( "div" ). on ({
+ mouseenter : function () {
+ $ ( this ). css ( "background" , "skyblue" );
+ },
+ click : function () {
+ $ ( this ). css ( "background" , "red" );
+ }
+});
+
+$ ( "div" ). on ( "mouseenter mouseleave" , function () {
+ $ ( this ). toggleClass ( "current" )
+ }
+);
$ ( "div" ). on ({
+ mouseenter : function () {
+ $ ( this ). css ( "background" , "skyblue" );
+ },
+ click : function () {
+ $ ( this ). css ( "background" , "red" );
+ }
+});
+
+$ ( "div" ). on ( "mouseenter mouseleave" , function () {
+ $ ( this ). toggleClass ( "current" )
+ }
+);
事件委托 事件绑定在父元素上
js $ ( "ul" ). on ( "click" , "li" , function () {
+ alert ( '111' )
+})
$ ( "ul" ). on ( "click" , "li" , function () {
+ alert ( '111' )
+})
on可以给未来元素绑定事件 事件解绑off element.off()
解绑所有element.off(事件1,事件2...)
解绑指定只触发一次的事件one element.one(事件,fn)
自动触发 element.事件() element.trigger(事件) element.triggerHandler(事件):不会触发元素的默认行为 事件对象 事件被触发,就会有事件对象的产生
js element. on (events, [selector], function ( event ) {
+ console. log (event)
+ event. preventDefault (); //阻止默认行为
+ return false ; //阻止默认行为
+ event. stopPropagation (); //阻止冒泡
+})
element. on (events, [selector], function ( event ) {
+ console. log (event)
+ event. preventDefault (); //阻止默认行为
+ return false ; //阻止默认行为
+ event. stopPropagation (); //阻止冒泡
+})
其他方法 拷贝对象 $.extend([deep], target, object1, [objectN])
多库共存 $
统一改为jQuery
新的名称$.noConflict()
/jQuery.noConflict()
jQuery插件 瀑布流 图片懒加载 全屏滚动:fullpage.js Bootstrap组件、插件 jQuery请求 $.get(url, [data], [callback])
js $ ( function () {
+ $ ( '#btn' ). on ( 'click' , function () {
+ $. get ( 'xxx.com/api/getXxx' , 'a=b' , function ( res ) {
+ console. log (res)
+ })
+ })
+})
$ ( function () {
+ $ ( '#btn' ). on ( 'click' , function () {
+ $. get ( 'xxx.com/api/getXxx' , 'a=b' , function ( res ) {
+ console. log (res)
+ })
+ })
+})
$.post(url, [data], [callback])
js $ ( function () {
+ $ ( '#btn' ). on ( 'click' , function () {
+ $. get ( 'xxx.com/api/getXxx' , { "a" : "b" }, function ( res ) {
+ console. log (res)
+ })
+ })
+})
$ ( function () {
+ $ ( '#btn' ). on ( 'click' , function () {
+ $. get ( 'xxx.com/api/getXxx' , { "a" : "b" }, function ( res ) {
+ console. log (res)
+ })
+ })
+})
$.ajax()
js $. ajax ({
+ type: '' ,
+ url: '' ,
+ data: {},
+ success : function ( res ) {}
+})
$. ajax ({
+ type: '' ,
+ url: '' ,
+ data: {},
+ success : function ( res ) {}
+})
`,64),p=[e];function t(c,r,i,y,E,d){return a(),n("div",null,p)}const F=s(o,[["render",t]]);export{h as __pageData,F as default};
diff --git a/assets/frontend_base_jquery.md.5c027802.lean.js b/assets/frontend_base_jquery.md.5c027802.lean.js
new file mode 100644
index 000000000..ad7d43848
--- /dev/null
+++ b/assets/frontend_base_jquery.md.5c027802.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"jQuery","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/jquery.md","filePath":"frontend/base/jquery.md","lastUpdated":1694368780000}'),o={name:"frontend/base/jquery.md"},e=l("",64),p=[e];function t(c,r,i,y,E,d){return a(),n("div",null,p)}const F=s(o,[["render",t]]);export{h as __pageData,F as default};
diff --git a/assets/frontend_base_nodejs.md.b2ea33da.js b/assets/frontend_base_nodejs.md.b2ea33da.js
new file mode 100644
index 000000000..05ebc26fe
--- /dev/null
+++ b/assets/frontend_base_nodejs.md.b2ea33da.js
@@ -0,0 +1,29 @@
+import{_ as s,o as l,c as a,Q as e}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"Node.js","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/nodejs.md","filePath":"frontend/base/nodejs.md","lastUpdated":1694368780000}'),n={name:"frontend/base/nodejs.md"},o=e(`Node.js Node.js简介 Node.js是一个基于Chrome V8引擎的JavaScript运行环境。
浏览器是js的前端运行环境
Node.js是js的后端运行环境
Node.js中的JavaScript运行环境 V8引擎 内置API fs、path、http、JS、querystring。。。 Node.js可以做什么 基于Express可以构建Web应用 基于Electron可以构建跨平台桌面应用 基于restify可以构建API接口项目 读写和操作数据库、创建实用的命令行工具辅助开发。。 基础模块 fs模块 fs.readFile() fs.readFile(path[, options], callback) fs.writeFile() fs.writeFile(file,data[,options], callback) 动态拼接路径问题:__dirname
替代当前文件所处目录 __dirname + '/file/1.txt'
path模块 path.join() path.basename() http模块 创建基本的web服务器 js const http = require ( 'http' )
+
+const server = http. createServer ()
+server. on ( 'request' , ( req , res ) => {
+ console. log ( 'visit' )
+ res. setHeader ( 'Content-Type' , 'text/html; charset=utf-8' )
+ res. end ( '111' )
+})
+
+server. listen ( 80 , () => {
+ console. log ( "http server running at http://127.0.0.1" )
+})
const http = require ( 'http' )
+
+const server = http. createServer ()
+server. on ( 'request' , ( req , res ) => {
+ console. log ( 'visit' )
+ res. setHeader ( 'Content-Type' , 'text/html; charset=utf-8' )
+ res. end ( '111' )
+})
+
+server. listen ( 80 , () => {
+ console. log ( "http server running at http://127.0.0.1" )
+})
模块化 模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,对于整个系统来说,模块是可组合、分解和更换的单元。
模块化优势:
Node.js模块分类 加载模块 const moduleName = require('moduleName')
使用require加载模块时会执行被加载模块的代码
使用自定义模块可以省略.js
模块作用域 在自定义模块中定义的变量、方法等成员只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
好处:防止全局变量污染
向外共享模块作用域中的成员 exports对象 exports
对象是module.exports
的简化写法,两者指向同一个对象
模块化规范 Node.js遵循Common.js的模块化规范
CommonJS规定
每个模块内部,module代表当前模块 module变量是一个对象,它的exports属性是对外的接口 加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载模块 npm和包 npm是Node.js的包管理工具
npm install
npm uninstall
npm install package
npm i package
npm i package@version
包版本的语义化规范
点分十进制,总共三位数字,例如2.14.0
第一位数:大版本
第二位数:功能版本
第三位数:bug修复版本
版本号提升规则:前面版本增长,后面版本号归零
安装包后多出来的文件 node_modules: 存放所有已安装到项目中的包 package-lock.json:记录node_modules目录下每一个包的下载信息,例如包的名字、版本号、下载地址等 包管理配置文件 npm规定,项目根目录必须提供package.json
的包管理配置文件。用来记录与项目有关的配置信息,例如:
项目的名称、版本号、描述 项目中用到了哪些包 哪些包在开发期间会用到 哪些包在开发和部署时都会用到 npm创建package.json命令:npm init -y
运行npm install时,npm会自动把包名、版本记录到package.json中
dependencies节点 package.json
中有一个dependencies节点专门记录npm install
安装了哪些包
devDependencies节点 记录只在开发阶段使用、上线不会用到的包
安装到dev节点中:npm i packageName -D
/ npm install packageName --save-dev
修改镜像 包分类 项目包:被安装到node_modules的都是项目包 全局包:执行npm install时指定-g参数则会安装为全局包 一般为工具性质的包 npm install package -g npm uninstall package -g i5ting_toc: md转换为html工具
i5ting_toc -f md -o
包结构 规范的包结构必须符合:
包必须单独目录 根目录必须包含package.json package.json必须包含name、version、main三个属性,对应包名、版本号、包的入口 模块的加载机制 优先从缓存中加载:模块在第一次被加载后会被缓存,意味着多次调用require()不会导致模块的代码被多次执行 内置模块的加载优先级最高 require()加载自定义模块必需指定./
或../
开头的路径标识符,否则会被当作内置模块或第三方模块,同时如果导入时省略了扩展名,Node.js会按顺序尝试加载以下文件: 按确切文件名加载 补全.js加载 补全.json加载 补全.node记载 加载失败,报错 如果required()的标识符不是内置模块也不是./
开头的路径标识符会被当作第三方模块,会从/node_modules中加载第三方模块 如果没有找到,就移动到上一层进行加载,直到文件系统根目录,找不到报错 目录作为模块标识符的加载顺序 在目录中找package.json的main属性做为require的加载入口 然后找根目录的index.js 都找不到报错 `,64),p=[o];function t(r,c,i,d,E,u){return l(),a("div",null,p)}const m=s(n,[["render",t]]);export{h as __pageData,m as default};
diff --git a/assets/frontend_base_nodejs.md.b2ea33da.lean.js b/assets/frontend_base_nodejs.md.b2ea33da.lean.js
new file mode 100644
index 000000000..35779095a
--- /dev/null
+++ b/assets/frontend_base_nodejs.md.b2ea33da.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as l,c as a,Q as e}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"Node.js","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/nodejs.md","filePath":"frontend/base/nodejs.md","lastUpdated":1694368780000}'),n={name:"frontend/base/nodejs.md"},o=e("",64),p=[o];function t(r,c,i,d,E,u){return l(),a("div",null,p)}const m=s(n,[["render",t]]);export{h as __pageData,m as default};
diff --git a/assets/frontend_base_typescript.md.c2a79275.js b/assets/frontend_base_typescript.md.c2a79275.js
new file mode 100644
index 000000000..8fa405ddb
--- /dev/null
+++ b/assets/frontend_base_typescript.md.c2a79275.js
@@ -0,0 +1,1371 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"TypeScript","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/typescript.md","filePath":"frontend/base/typescript.md","lastUpdated":1694368780000}'),p={name:"frontend/base/typescript.md"},o=l(`TypeScript 语法 原始类型 字符串 布尔 数字 支持十进制、十六进制、二进制、八进制、NaN、Infinity Null和Undefined Any 在编程阶段还不清楚类型的变量指定的一个类型
Void 某种程度上来说,void
类型像是与any
类型相反,它表示没有任何类型。
Null和Undefined TypeScript里,undefined
和null
两者各自有自己的类型分别叫做undefined
和null
。 和 void
相似,它们的本身的类型用处不是很大。
默认情况下null
和undefined
是所有类型的子类型。 就是说你可以把 null
和undefined
赋值给number
类型的变量。
然而,当指定了--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自。 这能避免 很多 常见的问题。
Object Object
:包含所有类型object
:表示非原始类型也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。{}
:同new Object()
,包含所有类型,但无法修改属性、赋值等接口和对象 typescript中定义对象的方式是用interface
,定义一种约束,让数据结构满足约束格式
typescript interface Man extends Person {
+ age ?: number
+ [propName:string] : any
+ readonly id : number
+}
+
+interface Person {
+ name : string
+}
+
+let p : Man = {
+ name: 'zs' ,
+ id: 1 ,
+ age: 18 ,
+ a: 1 ,
+ b: 2
+}
+
+
+interface Fn {
+ ( name : string ) : number []
+}
+
+const fn : Fn = function ( name : string ) {
+ return [ 1 ]
+}
interface Man extends Person {
+ age ?: number
+ [propName:string] : any
+ readonly id : number
+}
+
+interface Person {
+ name : string
+}
+
+let p : Man = {
+ name: 'zs' ,
+ id: 1 ,
+ age: 18 ,
+ a: 1 ,
+ b: 2
+}
+
+
+interface Fn {
+ ( name : string ) : number []
+}
+
+const fn : Fn = function ( name : string ) {
+ return [ 1 ]
+}
对象属性必须和interface
完全一致 重名的interface
会被合并 任意key(索引签名) 可选?
只读readonly
定义函数类型 数组 元素类型后加[]
,例如:number[]
、string[]
数组范型:Array<number>
定义对象数组用interface
二维数组:let arr:number[][] = [[1], [2]]
一把梭:any[]
函数 typescript //返回值
+function add ( a : number , b : number ) : number {
+ return a + b
+}
+//箭头函数和定义返回值
+const add = ( a : number , b : number ) : number => a + b
+
+//默认参数
+function add ( a : number = 10 , b : number = 20 ) {
+ return a + b
+}
+//可选参数
+function add ( a : number = 10 , b ?: number ) : number {
+ return a + b
+}
+//传对象
+interface User {
+ name : string
+ age : nubmer
+}
+
+function add ( user : User ) : User {
+ return user
+}
+console. log ( add ({ name: "111" , age: 18 }))
+
+//ts可以定义this的类型,在js中无法使用,必须是第一个参数定义this的类型(有点类似python里面的self?)
+interface Obj {
+ user : number []
+ add : ( this : Obj , num : number ) => void
+}
+
+let obj : Obj = {
+ user:[ 1 , 2 , 3 ],
+ add ( this : Obj , num : number ) {
+ this .user. push (num)
+ }
+}
+obj. add ( 4 )
+console. log (obj)
+
+//函数重载
+let user : number [] = [ 1 , 2 , 3 ]
+
+
+function findNum () : number []
+function findNum ( id : number ) : number []
+function findNum ( add : number []) : number []
+
+//跟据入参走不同逻辑
+function findNum ( ids ?: number | number []) : number [] {
+ if ( typeof ids == 'number' ) {
+ return user. filter ( v => v == ids)
+ } else if (Array. isArray (ids)) {
+ user. push ( ... ids)
+ return user
+ } else {
+ return user
+ }
+}
+console. log ( findNum ())
//返回值
+function add ( a : number , b : number ) : number {
+ return a + b
+}
+//箭头函数和定义返回值
+const add = ( a : number , b : number ) : number => a + b
+
+//默认参数
+function add ( a : number = 10 , b : number = 20 ) {
+ return a + b
+}
+//可选参数
+function add ( a : number = 10 , b ?: number ) : number {
+ return a + b
+}
+//传对象
+interface User {
+ name : string
+ age : nubmer
+}
+
+function add ( user : User ) : User {
+ return user
+}
+console. log ( add ({ name: "111" , age: 18 }))
+
+//ts可以定义this的类型,在js中无法使用,必须是第一个参数定义this的类型(有点类似python里面的self?)
+interface Obj {
+ user : number []
+ add : ( this : Obj , num : number ) => void
+}
+
+let obj : Obj = {
+ user:[ 1 , 2 , 3 ],
+ add ( this : Obj , num : number ) {
+ this .user. push (num)
+ }
+}
+obj. add ( 4 )
+console. log (obj)
+
+//函数重载
+let user : number [] = [ 1 , 2 , 3 ]
+
+
+function findNum () : number []
+function findNum ( id : number ) : number []
+function findNum ( add : number []) : number []
+
+//跟据入参走不同逻辑
+function findNum ( ids ?: number | number []) : number [] {
+ if ( typeof ids == 'number' ) {
+ return user. filter ( v => v == ids)
+ } else if (Array. isArray (ids)) {
+ user. push ( ... ids)
+ return user
+ } else {
+ return user
+ }
+}
+console. log ( findNum ())
联合类型 typescript let phone : number | string = '123456'
+
+//函数使用联合类型
+let fn = function ( type : number | boolean ) : boolean {
+ return !! type
+}
let phone : number | string = '123456'
+
+//函数使用联合类型
+let fn = function ( type : number | boolean ) : boolean {
+ return !! type
+}
TypeScript中的 !!是一个逻辑非(not)操作符的双重否定形式,它可以用于将一个值转换成对应的布尔值。基本上,!!可以将任何值强制转换为对应的布尔值类型。
例如,使用!!可以将下列值转换为布尔类型的值:
!!true // true
!!1 // true
!!"hello" // true
!!undefined // false
!!null // false
!!0 // false
!!"" // false
交叉类型 typescript interface People {
+ name : string ,
+ age : string
+}
+
+interface Man {
+ sex : number
+}
+
+const p = ( param : People & Man ) : void => {
+ console. log (man)
+}
+
+p ({
+ name: "ikun" ,
+ age: "两年半" ,
+ sex: 1
+})
interface People {
+ name : string ,
+ age : string
+}
+
+interface Man {
+ sex : number
+}
+
+const p = ( param : People & Man ) : void => {
+ console. log (man)
+}
+
+p ({
+ name: "ikun" ,
+ age: "两年半" ,
+ sex: 1
+})
类型断言 尖括号
写法
typescript let someValue : any = "this is a string" ;
+
+let strLength : number = (< string >someValue). length ;
let someValue : any = "this is a string" ;
+
+let strLength : number = (< string >someValue). length ;
as
写法
typescript let someValue : any = "this is a string" ;
+
+let strLength : number = (someValue as string ). length ;
let someValue : any = "this is a string" ;
+
+let strLength : number = (someValue as string ). length ;
使用JSX时只允许as写法
内置对象 Number(1)
Date()
RegExp(/\\w/)
Error('wrong')
XMLHttpRequest
HTML(元素名称)Element / HTMLElement / Element
NodeList
/ NodeListOf<HTMLDivElement | HTMLElement>
Storage
Location
Promise
... Class typescript //class的基本用法 继承 和类型约束 implements
+interface Options {
+ el : string | HTMLElement ;
+}
+interface VueClass {
+ options : Options ;
+ init () : void ;
+}
+interface Vnode {
+ tag : string ;
+ text : string ;
+ children ?: Vnode [];
+}
+//虚拟dom
+class Dom {
+ //创建dom节点
+ createElement ( el : string ) {
+ return document. createElement (el);
+ }
+ //填充文本
+ setText ( el : HTMLElement , text : string | null ) {
+ el.textContent = text;
+ }
+ //渲染函数
+ render ( data : Vnode ) {
+ let root = this . createElement (data.tag);
+ if (data.children && Array. isArray (data.children)) {
+ data.children. forEach (( item ) => {
+ let child = this . render (item);
+ root. appendChild (child);
+ });
+ } else {
+ this . setText (root, data.text === undefined ? "" : data.text);
+ }
+ return root;
+ }
+}
+
+class Vue extends Dom implements VueClass {
+ options : Options ;
+ constructor ( options : Options ) {
+ super ();
+ this .options = options;
+ this . init ();
+ }
+ init () : void {
+ let data : Vnode = {
+ tag: "div" ,
+ text: '111' ,
+ children: [
+ {
+ tag: "section" ,
+ text: "子节点1" ,
+ },
+ {
+ tag: "section" ,
+ text: "子节点2" ,
+ },
+ {
+ tag: "section" ,
+ text: "子节点3" ,
+ }
+ ],
+ };
+ let app =
+ typeof this .options.el == "string"
+ ? document. querySelector ( this .options.el)
+ : this .options.el;
+ app?. appendChild ( this . render (data));
+ }
+}
+
+new Vue ({
+ el: "#app"
+});
//class的基本用法 继承 和类型约束 implements
+interface Options {
+ el : string | HTMLElement ;
+}
+interface VueClass {
+ options : Options ;
+ init () : void ;
+}
+interface Vnode {
+ tag : string ;
+ text : string ;
+ children ?: Vnode [];
+}
+//虚拟dom
+class Dom {
+ //创建dom节点
+ createElement ( el : string ) {
+ return document. createElement (el);
+ }
+ //填充文本
+ setText ( el : HTMLElement , text : string | null ) {
+ el.textContent = text;
+ }
+ //渲染函数
+ render ( data : Vnode ) {
+ let root = this . createElement (data.tag);
+ if (data.children && Array. isArray (data.children)) {
+ data.children. forEach (( item ) => {
+ let child = this . render (item);
+ root. appendChild (child);
+ });
+ } else {
+ this . setText (root, data.text === undefined ? "" : data.text);
+ }
+ return root;
+ }
+}
+
+class Vue extends Dom implements VueClass {
+ options : Options ;
+ constructor ( options : Options ) {
+ super ();
+ this .options = options;
+ this . init ();
+ }
+ init () : void {
+ let data : Vnode = {
+ tag: "div" ,
+ text: '111' ,
+ children: [
+ {
+ tag: "section" ,
+ text: "子节点1" ,
+ },
+ {
+ tag: "section" ,
+ text: "子节点2" ,
+ },
+ {
+ tag: "section" ,
+ text: "子节点3" ,
+ }
+ ],
+ };
+ let app =
+ typeof this .options.el == "string"
+ ? document. querySelector ( this .options.el)
+ : this .options.el;
+ app?. appendChild ( this . render (data));
+ }
+}
+
+new Vue ({
+ el: "#app"
+});
readonly private protected public super() 静态方法 static get set 抽象类 & 抽象方法 abstract className abstract functionName 元组Tuple 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
typescript // Declare a tuple type
+let x : [ string , number ];
+// Initialize it
+x = [ 'hello' , 10 ]; // OK
+// Initialize it incorrectly
+x = [ 10 , 'hello' ]; // Error
// Declare a tuple type
+let x : [ string , number ];
+// Initialize it
+x = [ 'hello' , 10 ]; // OK
+// Initialize it incorrectly
+x = [ 10 , 'hello' ]; // Error
当访问一个已知索引的元素,会得到正确的类型:
ts console. log (x[ 0 ]. substr ( 1 )); // OK
+console. log (x[ 1 ]. substr ( 1 )); // Error, 'number' does not have 'substr'
console. log (x[ 0 ]. substr ( 1 )); // OK
+console. log (x[ 1 ]. substr ( 1 )); // Error, 'number' does not have 'substr'
当访问一个越界的元素,会使用联合类型替代:
ts x[ 3 ] = 'world' ; // OK, 字符串可以赋值给(string | number)类型
+
+console. log (x[ 5 ]. toString ()); // OK, 'string' 和 'number' 都有 toString
+
+x[ 6 ] = true ; // Error, 布尔不是(string | number)类型
x[ 3 ] = 'world' ; // OK, 字符串可以赋值给(string | number)类型
+
+console. log (x[ 5 ]. toString ()); // OK, 'string' 和 'number' 都有 toString
+
+x[ 6 ] = true ; // Error, 布尔不是(string | number)类型
枚举 enum
类型是对JavaScript标准数据类型的一个补充。
ts enum Color { Red , Green , Blue }
+let c : Color = Color.Green;
enum Color { Red , Green , Blue }
+let c : Color = Color.Green;
默认情况下,从0
开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1
开始编号:
ts enum Color { Red = 1 , Green , Blue }
+let c : Color = Color.Green;
enum Color { Red = 1 , Green , Blue }
+let c : Color = Color.Green;
或者,全部都采用手动赋值:
ts enum Color { Red = 1 , Green = 2 , Blue = 4 }
+let c : Color = Color.Green;
enum Color { Red = 1 , Green = 2 , Blue = 4 }
+let c : Color = Color.Green;
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:
ts enum Color { Red = 1 , Green , Blue }
+let colorName : string = Color[ 2 ];
+
+console. log (colorName); // 显示'Green'因为上面代码里它的值是2
enum Color { Red = 1 , Green , Blue }
+let colorName : string = Color[ 2 ];
+
+console. log (colorName); // 显示'Green'因为上面代码里它的值是2
类型推断 TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型。
如果没有指出类型 & 没赋值 会被推断成any类型。
类型别名 typescript type s = string | null
+
+let str : s = 'test'
+
+let str1 = '123'
+type s1 = typeof str1
+
+
+type num = 1 extends number ? 1 : 0
type s = string | null
+
+let str : s = 'test'
+
+let str1 = '123'
+type s1 = typeof str1
+
+
+type num = 1 extends number ? 1 : 0
Never never
类型表示的是那些永不存在的值的类型。 例如, never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never
类型,当它们被永不为真的类型保护所约束时。
never
类型是任何类型的子类型,也可以赋值给任何类型;然而,没有 类型是never
的子类型或可以赋值给never
类型(除了never
本身之外)。 即使 any
也不可以赋值给never
。
typescript type A = string & number //never
+type A = void | number | never //never会被忽略掉
+
+
+type A = '唱' | '跳' | 'rap'
+
+function kun ( value : A ) {
+ switch (value) {
+ case '唱' :
+ break ;
+ case '跳' :
+ break ;
+ case 'rap' :
+ break ;
+ default :
+ const check : never = value;
+ break ;
+ }
+}
type A = string & number //never
+type A = void | number | never //never会被忽略掉
+
+
+type A = '唱' | '跳' | 'rap'
+
+function kun ( value : A ) {
+ switch (value) {
+ case '唱' :
+ break ;
+ case '跳' :
+ break ;
+ case 'rap' :
+ break ;
+ default :
+ const check : never = value;
+ break ;
+ }
+}
Symbol typescript let a1 : symbol = Symbol ( 1 )
+let a2 : symbol = Symbol ( 2 )
+console. log (a1 === a2) // false
+
+//for Symbol 有没有注册过这个key 如果有直接用 没有就创建
+console. log (Symbol. for ( '1' ) === Symbol. for ( '1' )) // true
let a1 : symbol = Symbol ( 1 )
+let a2 : symbol = Symbol ( 2 )
+console. log (a1 === a2) // false
+
+//for Symbol 有没有注册过这个key 如果有直接用 没有就创建
+console. log (Symbol. for ( '1' ) === Symbol. for ( '1' )) // true
生成器 迭代器 typescript function* gen () {
+ yield Promise . resovle ( '111' )
+ yield '1'
+ yield '2'
+}
+const g = gen ()
+console. log (g. next ())
+console. log (g. next ())
+console. log (g. next ())
+console. log (g. next ())
+/*
+{ value: Promise { '111' }, done: false }
+{ value: '1', done: false }
+{ value: '2', done: false }
+{ value: undefined, done: true }
+*/
function* gen () {
+ yield Promise . resovle ( '111' )
+ yield '1'
+ yield '2'
+}
+const g = gen ()
+console. log (g. next ())
+console. log (g. next ())
+console. log (g. next ())
+console. log (g. next ())
+/*
+{ value: Promise { '111' }, done: false }
+{ value: '1', done: false }
+{ value: '2', done: false }
+{ value: undefined, done: true }
+*/
typescript let Set : Set < number > = new Set ([ 1 , 1 , 2 , 3 , 3 , 3 ]) // 1 2 3
+
+let map : Map < any , any > = new Map ()
+let arr = [ 1 , 2 , 3 ]
+map. set (arr, '123' )
+
+function args (){
+ console. log ( arguments ) //伪数组 IArguments
+}
+
+const each = ( value : any ) => {
+ let It : any = value[Symbol.iterator]()
+ let next : any = { done: false }
+ while ( ! next.done) {
+ next = It. next ()
+ if ( ! next.done) {
+ console. log (next.value)
+ }
+ }
+}
+each ([ 1 , 2 , 3 ])
+/*
+1
+2
+3
+*/
+
+
+//迭代器语法糖 for of
+//对象不能用 for of语法
+for ( let value of map) {
+ console. log (value)
+}
+
+//数组解构 底层原理也是调用iterator
+let a = [ 4 , 5 , 6 ]
+let copy = [ ... a]
+console. log (a)
+
+let obj = {
+ max: 5 ,
+ current: 0 ,
+ [Symbol.iterator]() {
+ return {
+ max: this .max,
+ current: this .current,
+ next () {
+ if ( this .current == this .max) {
+ return {
+ value: undefined ,
+ done: true
+ }
+ } else {
+ return {
+ value: this .current ++ ,
+ done: false
+ }
+ }
+ }
+ }
+ }
+}
+
+for ( let value of obj) {
+ console. log (value)
+}
+/*
+0
+1
+2
+3
+4
+*/
let Set : Set < number > = new Set ([ 1 , 1 , 2 , 3 , 3 , 3 ]) // 1 2 3
+
+let map : Map < any , any > = new Map ()
+let arr = [ 1 , 2 , 3 ]
+map. set (arr, '123' )
+
+function args (){
+ console. log ( arguments ) //伪数组 IArguments
+}
+
+const each = ( value : any ) => {
+ let It : any = value[Symbol.iterator]()
+ let next : any = { done: false }
+ while ( ! next.done) {
+ next = It. next ()
+ if ( ! next.done) {
+ console. log (next.value)
+ }
+ }
+}
+each ([ 1 , 2 , 3 ])
+/*
+1
+2
+3
+*/
+
+
+//迭代器语法糖 for of
+//对象不能用 for of语法
+for ( let value of map) {
+ console. log (value)
+}
+
+//数组解构 底层原理也是调用iterator
+let a = [ 4 , 5 , 6 ]
+let copy = [ ... a]
+console. log (a)
+
+let obj = {
+ max: 5 ,
+ current: 0 ,
+ [Symbol.iterator]() {
+ return {
+ max: this .max,
+ current: this .current,
+ next () {
+ if ( this .current == this .max) {
+ return {
+ value: undefined ,
+ done: true
+ }
+ } else {
+ return {
+ value: this .current ++ ,
+ done: false
+ }
+ }
+ }
+ }
+ }
+}
+
+for ( let value of obj) {
+ console. log (value)
+}
+/*
+0
+1
+2
+3
+4
+*/
泛型 typescript function fun < T >( a : T , b : T ) : Array < T > {
+ return [a,b]
+}
+
+type A < T > = string | number | T
+let a : A < boolean > = true
+
+interface Date < T > {
+ msg : T
+}
+let data : Date < number > = {
+ msg: 1
+}
+
+function add < T = number , K = number >( a : T , b : K ) : Array < T | K > {
+ return [a,b]
+}
+add ( false , '1' )
+
+const axios = {
+ get < T >( url : string ) {
+ return new Promise < T >(( resolve , reject ) => {
+ let xhr : XMLHttpRequest = new XMLHttpRequest ()
+ xhr. open ( 'GET' ,url)
+ xhr. onreadystatechange = () => {
+ if (xhr.readyState == 4 && xhr.status == 200 ) {
+ resolve ( JSON . parse (xhr.responseText))
+ }
+ }
+ xhr. send ( null )
+ })
+ }
+}
function fun < T >( a : T , b : T ) : Array < T > {
+ return [a,b]
+}
+
+type A < T > = string | number | T
+let a : A < boolean > = true
+
+interface Date < T > {
+ msg : T
+}
+let data : Date < number > = {
+ msg: 1
+}
+
+function add < T = number , K = number >( a : T , b : K ) : Array < T | K > {
+ return [a,b]
+}
+add ( false , '1' )
+
+const axios = {
+ get < T >( url : string ) {
+ return new Promise < T >(( resolve , reject ) => {
+ let xhr : XMLHttpRequest = new XMLHttpRequest ()
+ xhr. open ( 'GET' ,url)
+ xhr. onreadystatechange = () => {
+ if (xhr.readyState == 4 && xhr.status == 200 ) {
+ resolve ( JSON . parse (xhr.responseText))
+ }
+ }
+ xhr. send ( null )
+ })
+ }
+}
泛型约束 typescript // extends
+interface Len {
+ length : number
+}
+
+function func < T extends Len )(a:T) {
+ console.log(a.length)
+}
+
+let obj = {
+ name : 'test' ,
+ sex : 1
+}
+
+// 约束对象的key
+type key = keyof typeof obj // "name" | "sex"
+
+function ob < T extends object , K extends keyof T >( obj : T , key : K ) {
+
+}
+
+
+interface Data {
+ name : string
+ age : number
+ sex : string
+}
+
+type Options < T extends object > = {
+ //readonly [Key in keyof T]?:T[Key]
+ [ Key in keyof T ] ?: T [ Key ]
+}
+
+type B = Options < Data >
+/*
+type B = {
+ name?: string | undefined;
+ age?: number | undefined;
+ sex?: string | undefined;
+}
+type B = {
+ name?: string | undefined;
+ age?: number | undefined;
+ sex?: string | undefined;
+}
+*/
// extends
+interface Len {
+ length : number
+}
+
+function func < T extends Len )(a:T) {
+ console.log(a.length)
+}
+
+let obj = {
+ name : 'test' ,
+ sex : 1
+}
+
+// 约束对象的key
+type key = keyof typeof obj // "name" | "sex"
+
+function ob < T extends object , K extends keyof T >( obj : T , key : K ) {
+
+}
+
+
+interface Data {
+ name : string
+ age : number
+ sex : string
+}
+
+type Options < T extends object > = {
+ //readonly [Key in keyof T]?:T[Key]
+ [ Key in keyof T ] ?: T [ Key ]
+}
+
+type B = Options < Data >
+/*
+type B = {
+ name?: string | undefined;
+ age?: number | undefined;
+ sex?: string | undefined;
+}
+type B = {
+ name?: string | undefined;
+ age?: number | undefined;
+ sex?: string | undefined;
+}
+*/
tsconfig.json 通过tsc --init
生成
json "compilerOptions" : {
+ "incremental" : true , // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
+ "tsBuildInfoFile" : "./buildFile" , // 增量编译文件的存储位置
+ "diagnostics" : true , // 打印诊断信息
+ "target" : "ES5" , // 目标语言的版本
+ "module" : "CommonJS" , // 生成代码的模板标准
+ "outFile" : "./app.js" , // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
+ "lib" : [ "DOM" , "ES2015" , "ScriptHost" , "ES2019.Array" ], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
+ "allowJS" : true , // 允许编译器编译JS,JSX文件
+ "checkJs" : true , // 允许在JS文件中报错,通常与allowJS一起使用
+ "outDir" : "./dist" , // 指定输出目录
+ "rootDir" : "./" , // 指定输出文件目录(用于输出),用于控制输出目录结构
+ "declaration" : true , // 生成声明文件,开启后会自动生成声明文件
+ "declarationDir" : "./file" , // 指定生成声明文件存放目录
+ "emitDeclarationOnly" : true , // 只生成声明文件,而不会生成js文件
+ "sourceMap" : true , // 生成目标文件的sourceMap文件
+ "inlineSourceMap" : true , // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
+ "declarationMap" : true , // 为声明文件生成sourceMap
+ "typeRoots" : [], // 声明文件目录,默认时node_modules/@types
+ "types" : [], // 加载的声明文件包
+ "removeComments" : true , // 删除注释
+ "noEmit" : true , // 不输出文件,即编译后不会生成任何js文件
+ "noEmitOnError" : true , // 发送错误时不输出任何文件
+ "noEmitHelpers" : true , // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
+ "importHelpers" : true , // 通过tslib引入helper函数,文件必须是模块
+ "downlevelIteration" : true , // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
+ "strict" : true , // 开启所有严格的类型检查
+ "alwaysStrict" : true , // 在代码中注入'use strict'
+ "noImplicitAny" : true , // 不允许隐式的any类型
+ "strictNullChecks" : true , // 不允许把null、undefined赋值给其他类型的变量
+ "strictFunctionTypes" : true , // 不允许函数参数双向协变
+ "strictPropertyInitialization" : true , // 类的实例属性必须初始化
+ "strictBindCallApply" : true , // 严格的bind/call/apply检查
+ "noImplicitThis" : true , // 不允许this有隐式的any类型
+ "noUnusedLocals" : true , // 检查只声明、未使用的局部变量(只提示不报错)
+ "noUnusedParameters" : true , // 检查未使用的函数参数(只提示不报错)
+ "noFallthroughCasesInSwitch" : true , // 防止switch语句贯穿(即如果没有break语句后面不会执行)
+ "noImplicitReturns" : true , //每个分支都会有返回值
+ "esModuleInterop" : true , // 允许export=导出,由import from 导入
+ "allowUmdGlobalAccess" : true , // 允许在模块中全局变量的方式访问umd模块
+ "moduleResolution" : "node" , // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
+ "baseUrl" : "./" , // 解析非相对模块的基地址,默认是当前目录
+ "paths" : { // 路径映射,相对于baseUrl
+ // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
+ "jquery" : [ "node_modules/jquery/dist/jquery.min.js" ]
+ },
+ "rootDirs" : [ "src" , "out" ], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
+ "listEmittedFiles" : true , // 打印输出文件
+ "listFiles" : true // 打印编译的文件(包括引用的声明文件)
+}
+
+// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
+"include" : [
+ "src/**/*"
+],
+// 指定一个排除列表(include的反向操作)
+ "exclude" : [
+ "demo.ts"
+],
+// 指定哪些文件使用该配置(属于手动一个个指定文件)
+ "files" : [
+ "demo.ts"
+]
"compilerOptions" : {
+ "incremental" : true , // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
+ "tsBuildInfoFile" : "./buildFile" , // 增量编译文件的存储位置
+ "diagnostics" : true , // 打印诊断信息
+ "target" : "ES5" , // 目标语言的版本
+ "module" : "CommonJS" , // 生成代码的模板标准
+ "outFile" : "./app.js" , // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
+ "lib" : [ "DOM" , "ES2015" , "ScriptHost" , "ES2019.Array" ], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
+ "allowJS" : true , // 允许编译器编译JS,JSX文件
+ "checkJs" : true , // 允许在JS文件中报错,通常与allowJS一起使用
+ "outDir" : "./dist" , // 指定输出目录
+ "rootDir" : "./" , // 指定输出文件目录(用于输出),用于控制输出目录结构
+ "declaration" : true , // 生成声明文件,开启后会自动生成声明文件
+ "declarationDir" : "./file" , // 指定生成声明文件存放目录
+ "emitDeclarationOnly" : true , // 只生成声明文件,而不会生成js文件
+ "sourceMap" : true , // 生成目标文件的sourceMap文件
+ "inlineSourceMap" : true , // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
+ "declarationMap" : true , // 为声明文件生成sourceMap
+ "typeRoots" : [], // 声明文件目录,默认时node_modules/@types
+ "types" : [], // 加载的声明文件包
+ "removeComments" : true , // 删除注释
+ "noEmit" : true , // 不输出文件,即编译后不会生成任何js文件
+ "noEmitOnError" : true , // 发送错误时不输出任何文件
+ "noEmitHelpers" : true , // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
+ "importHelpers" : true , // 通过tslib引入helper函数,文件必须是模块
+ "downlevelIteration" : true , // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
+ "strict" : true , // 开启所有严格的类型检查
+ "alwaysStrict" : true , // 在代码中注入'use strict'
+ "noImplicitAny" : true , // 不允许隐式的any类型
+ "strictNullChecks" : true , // 不允许把null、undefined赋值给其他类型的变量
+ "strictFunctionTypes" : true , // 不允许函数参数双向协变
+ "strictPropertyInitialization" : true , // 类的实例属性必须初始化
+ "strictBindCallApply" : true , // 严格的bind/call/apply检查
+ "noImplicitThis" : true , // 不允许this有隐式的any类型
+ "noUnusedLocals" : true , // 检查只声明、未使用的局部变量(只提示不报错)
+ "noUnusedParameters" : true , // 检查未使用的函数参数(只提示不报错)
+ "noFallthroughCasesInSwitch" : true , // 防止switch语句贯穿(即如果没有break语句后面不会执行)
+ "noImplicitReturns" : true , //每个分支都会有返回值
+ "esModuleInterop" : true , // 允许export=导出,由import from 导入
+ "allowUmdGlobalAccess" : true , // 允许在模块中全局变量的方式访问umd模块
+ "moduleResolution" : "node" , // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
+ "baseUrl" : "./" , // 解析非相对模块的基地址,默认是当前目录
+ "paths" : { // 路径映射,相对于baseUrl
+ // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
+ "jquery" : [ "node_modules/jquery/dist/jquery.min.js" ]
+ },
+ "rootDirs" : [ "src" , "out" ], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
+ "listEmittedFiles" : true , // 打印输出文件
+ "listFiles" : true // 打印编译的文件(包括引用的声明文件)
+}
+
+// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
+"include" : [
+ "src/**/*"
+],
+// 指定一个排除列表(include的反向操作)
+ "exclude" : [
+ "demo.ts"
+],
+// 指定哪些文件使用该配置(属于手动一个个指定文件)
+ "files" : [
+ "demo.ts"
+]
namespace typescript提供了namespace
避免全局变量污染的问题。
任何包含顶级import或export的文件都被当作一个模块。相反的,如果不带,那么它的内容被视为全局可见的。
命名空间在ts1.5之前叫内部模块
,外部模块
现在简称为模块。 命名空间内的类默认私有 通过export暴露 通过namespace关键字定义 typescript namespace A {
+ export const a = 1
+}
+// 实现:
+"use strict"
+var A ;
+( function ( A ) {
+ A .a = 1 ;
+})( A || A = {});
+
+// 嵌套命名空间
+namespace A {
+ export namespace C {
+ export const D = 5 ;
+ }
+}
+
+console. log ( A . C . D )
+
+//抽离命名空间
+export namespace V {
+ export const a = 1
+}
+
+import {V} from '../index'
+console. log ( V ) // {a:1}
+
+
+//简化命名空间
+namespace A {
+ export namespace C {
+ export const D = 5 ;
+ }
+}
+
+import a = A .C
+console. log (a. D )
+
+//命名空间合并
+namespace A {
+ export const b = 2
+}
+namespace A {
+ export const a = 1
+}
+//等价于
+namespace A {
+ export const b = 2
+ export const a = 1
+}
namespace A {
+ export const a = 1
+}
+// 实现:
+"use strict"
+var A ;
+( function ( A ) {
+ A .a = 1 ;
+})( A || A = {});
+
+// 嵌套命名空间
+namespace A {
+ export namespace C {
+ export const D = 5 ;
+ }
+}
+
+console. log ( A . C . D )
+
+//抽离命名空间
+export namespace V {
+ export const a = 1
+}
+
+import {V} from '../index'
+console. log ( V ) // {a:1}
+
+
+//简化命名空间
+namespace A {
+ export namespace C {
+ export const D = 5 ;
+ }
+}
+
+import a = A .C
+console. log (a. D )
+
+//命名空间合并
+namespace A {
+ export const b = 2
+}
+namespace A {
+ export const a = 1
+}
+//等价于
+namespace A {
+ export const b = 2
+ export const a = 1
+}
三斜线指令 三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。
typescript /// < reference path = "..." />
+/// <reference path="..." />指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。
/// < reference path = "..." />
+/// <reference path="..." />指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。
三斜线引用告诉编译器在编译过程中要引入的额外的文件。
typescript /// < reference types = "..." />
+与 /// <reference path="..." />指令相似,这个指令是用来声明 依赖的; 一个 /// <reference types="..." />指令则声明了对某个包的依赖。
+
+对这些包的名字的解析与在 import语句里对模块名的解析类似。 可以简单地把三斜线类型引用指令当做 import声明的包。
+
+例如,把 /// <reference types="node" />引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
+
+仅当在你需要写一个d.ts文件时才使用这个指令。
+
+对于那些在编译阶段生成的声明文件,编译器会自动地添加 /// <reference types="..." />; 当且仅当结果文件中使用了引用的包里的声明时才会在生成的声明文件里添加/// <reference types="..." />语句。
+
+若要在.ts文件里声明一个对@types包的依赖,使用 -- types命令行选项或在tsconfig.json里指定。
/// < reference types = "..." />
+与 /// <reference path="..." />指令相似,这个指令是用来声明 依赖的; 一个 /// <reference types="..." />指令则声明了对某个包的依赖。
+
+对这些包的名字的解析与在 import语句里对模块名的解析类似。 可以简单地把三斜线类型引用指令当做 import声明的包。
+
+例如,把 /// <reference types="node" />引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
+
+仅当在你需要写一个d.ts文件时才使用这个指令。
+
+对于那些在编译阶段生成的声明文件,编译器会自动地添加 /// <reference types="..." />; 当且仅当结果文件中使用了引用的包里的声明时才会在生成的声明文件里添加/// <reference types="..." />语句。
+
+若要在.ts文件里声明一个对@types包的依赖,使用 -- types命令行选项或在tsconfig.json里指定。
声明文件 使用第三方库时需要引用它的声明文件d.ts
才能获得对应的代码补全、接口提示等功能
typescript npm i @types / xxx
npm i @types / xxx
Mixins混入 除了传统的面向对象继承方式,还流行一种通过可重用组件创建类的方式,就是联合另一个简单类的代码。
typescript // Disposable Mixin
+class Disposable {
+ isDisposed : boolean ;
+ dispose () {
+ this .isDisposed = true ;
+ }
+
+}
+
+// Activatable Mixin
+class Activatable {
+ isActive : boolean ;
+ activate () {
+ this .isActive = true ;
+ }
+ deactivate () {
+ this .isActive = false ;
+ }
+}
+
+class SmartObject implements Disposable , Activatable {
+ constructor () {
+ setInterval (() => console. log ( this .isActive + " : " + this .isDisposed), 500 );
+ }
+
+ interact () {
+ this . activate ();
+ }
+
+ // Disposable
+ isDisposed : boolean = false ;
+ dispose : () => void ;
+ // Activatable
+ isActive : boolean = false ;
+ activate : () => void ;
+ deactivate : () => void ;
+}
+applyMixins (SmartObject, [Disposable, Activatable]);
+
+let smartObj = new SmartObject ();
+setTimeout (() => smartObj. interact (), 1000 );
+
+////////////////////////////////////////
+// In your runtime library somewhere
+////////////////////////////////////////
+
+function applyMixins ( derivedCtor : any , baseCtors : any []) {
+ baseCtors. forEach ( baseCtor => {
+ Object. getOwnPropertyNames ( baseCtor . prototype ). forEach ( name => {
+ derivedCtor . prototype [name] = baseCtor . prototype [name];
+ });
+ });
+}
// Disposable Mixin
+class Disposable {
+ isDisposed : boolean ;
+ dispose () {
+ this .isDisposed = true ;
+ }
+
+}
+
+// Activatable Mixin
+class Activatable {
+ isActive : boolean ;
+ activate () {
+ this .isActive = true ;
+ }
+ deactivate () {
+ this .isActive = false ;
+ }
+}
+
+class SmartObject implements Disposable , Activatable {
+ constructor () {
+ setInterval (() => console. log ( this .isActive + " : " + this .isDisposed), 500 );
+ }
+
+ interact () {
+ this . activate ();
+ }
+
+ // Disposable
+ isDisposed : boolean = false ;
+ dispose : () => void ;
+ // Activatable
+ isActive : boolean = false ;
+ activate : () => void ;
+ deactivate : () => void ;
+}
+applyMixins (SmartObject, [Disposable, Activatable]);
+
+let smartObj = new SmartObject ();
+setTimeout (() => smartObj. interact (), 1000 );
+
+////////////////////////////////////////
+// In your runtime library somewhere
+////////////////////////////////////////
+
+function applyMixins ( derivedCtor : any , baseCtors : any []) {
+ baseCtors. forEach ( baseCtor => {
+ Object. getOwnPropertyNames ( baseCtor . prototype ). forEach ( name => {
+ derivedCtor . prototype [name] = baseCtor . prototype [name];
+ });
+ });
+}
装饰器Decorator 随着TypeScript和ES6里引入了类,在一些场景下我们需要额外的特性来支持标注或修改类及其成员。 装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。
若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json
里启用experimentalDecorators
编译器选项:
命令行 :
shell tsc --target ES5 --experimentalDecorators
tsc --target ES5 --experimentalDecorators
tsconfig.json :
json {
+ "compilerOptions" : {
+ "target" : "ES5" ,
+ "experimentalDecorators" : true
+ }
+}
{
+ "compilerOptions" : {
+ "target" : "ES5" ,
+ "experimentalDecorators" : true
+ }
+}
装饰器 是一种特殊类型的声明,它能够被附加到类声明 ,方法 , 访问符 ,属性 或参数 上。 装饰器使用 @expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
类装饰器 typescript const IKun : ClassDecorator = ( target ) => {
+ console. log (target);
+ target . prototype .name = 'ikun' ;
+ target . prototype . slogan = () => {
+ console. log ( '鸡你太美' );
+ }
+}
+
+@IKun
+class Person {
+
+}
+
+const person = new Person () as any ;
+person. slogan (); // 鸡你太美
const IKun : ClassDecorator = ( target ) => {
+ console. log (target);
+ target . prototype .name = 'ikun' ;
+ target . prototype . slogan = () => {
+ console. log ( '鸡你太美' );
+ }
+}
+
+@IKun
+class Person {
+
+}
+
+const person = new Person () as any ;
+person. slogan (); // 鸡你太美
装饰器工厂 typescript const IKun = ( name : string ) => {
+ const decorator : ClassDecorator = ( target ) => {
+ target . prototype .name = name;
+ target . prototype . slogan = () => {
+ console. log ( '鸡你太美' );
+ }
+ }
+ return decorator
+
+}
+
+@ IKun ( '小黑子' )
+class Person {
+
+}
+
+const person = new Person () as any ;
+console. log (person.name); // 小黑子
+person. slogan (); // 鸡你太美
const IKun = ( name : string ) => {
+ const decorator : ClassDecorator = ( target ) => {
+ target . prototype .name = name;
+ target . prototype . slogan = () => {
+ console. log ( '鸡你太美' );
+ }
+ }
+ return decorator
+
+}
+
+@ IKun ( '小黑子' )
+class Person {
+
+}
+
+const person = new Person () as any ;
+console. log (person.name); // 小黑子
+person. slogan (); // 鸡你太美
方法装饰器 方法装饰器 声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符 上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts
),重载或者任何外部上下文(比如declare
的类)中。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。 成员的名字。 成员的属性描述符 。 如果方法装饰器返回一个值,它会被用作方法的属性描述符 。
typescript const logResult = ( target : any , propertyKey : string , descriptor : PropertyDescriptor ) => {
+ const fn = descriptor.value
+ descriptor. value = function ( ... rest ) {
+ // 使用新的方法来替换原有方法,输出 方法名称 + 输入的参数 实现日志的增强功能
+ const result = fn. apply ( this , rest)
+ console. log (propertyKey + ':' + result)
+ return result
+ }
+}
+
+class Person {
+ name : string = ''
+ age : number = 0
+
+ constructor ( name : string , age : number ) {
+ this .name = name
+ this .age = age
+ }
+
+ @logResult
+ getName () {
+ return this .name
+ }
+
+ @logResult
+ getAge () {
+ return this .age
+ }
+}
+
+const p = new Person ( '张三' , 18 )
+p. getName () // getName:张三
+p. getAge () // getAge:18
const logResult = ( target : any , propertyKey : string , descriptor : PropertyDescriptor ) => {
+ const fn = descriptor.value
+ descriptor. value = function ( ... rest ) {
+ // 使用新的方法来替换原有方法,输出 方法名称 + 输入的参数 实现日志的增强功能
+ const result = fn. apply ( this , rest)
+ console. log (propertyKey + ':' + result)
+ return result
+ }
+}
+
+class Person {
+ name : string = ''
+ age : number = 0
+
+ constructor ( name : string , age : number ) {
+ this .name = name
+ this .age = age
+ }
+
+ @logResult
+ getName () {
+ return this .name
+ }
+
+ @logResult
+ getAge () {
+ return this .age
+ }
+}
+
+const p = new Person ( '张三' , 18 )
+p. getName () // getName:张三
+p. getAge () // getAge:18
typescript import "reflect-metadata" ;
+
+const formatMetadataKey = Symbol ( "format" );
+
+function format ( formatString : string ) {
+ return Reflect. metadata (formatMetadataKey, formatString);
+}
+
+function getFormat ( target : any , propertyKey : string ) {
+ return Reflect. getMetadata (formatMetadataKey, target, propertyKey);
+}
+
+class Greeter {
+ @ format ( "Hello, %s" )
+ greeting : string ;
+
+ constructor ( message : string ) {
+ this .greeting = message;
+ }
+
+ greet () {
+ let formatString = getFormat ( this , "greeting" );
+ return formatString. replace ( "%s" , this .greeting);
+ }
+}
+
+console. log ( new Greeter ( "world" ). greet ()); // "Hello, world"
import "reflect-metadata" ;
+
+const formatMetadataKey = Symbol ( "format" );
+
+function format ( formatString : string ) {
+ return Reflect. metadata (formatMetadataKey, formatString);
+}
+
+function getFormat ( target : any , propertyKey : string ) {
+ return Reflect. getMetadata (formatMetadataKey, target, propertyKey);
+}
+
+class Greeter {
+ @ format ( "Hello, %s" )
+ greeting : string ;
+
+ constructor ( message : string ) {
+ this .greeting = message;
+ }
+
+ greet () {
+ let formatString = getFormat ( this , "greeting" );
+ return formatString. replace ( "%s" , this .greeting);
+ }
+}
+
+console. log ( new Greeter ( "world" ). greet ()); // "Hello, world"
参数装饰器 参数装饰器用于装饰函数参数,参数装饰器接收3个参数:
target
: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象propertyKey
: 方法名。paramIndex
: 参数所在位置的索引。typescript const paramDecorator = ( target : any , propertyKey : string , paramIndex : number ) => {
+ console. log (target, propertyKey, paramIndex)
+}
+
+class Person {
+ name : string = ''
+ age : number = 0
+
+ constructor ( name : string , age : number ) {
+ this .name = name
+ this .age = age
+ }
+
+ setName (@paramDecorator name : string ) {
+ this .name = name
+ }
+}
+
+const p = new Person ( '张三' , 18 ) // Person、setName、0
+p. setName ( '李四' )
const paramDecorator = ( target : any , propertyKey : string , paramIndex : number ) => {
+ console. log (target, propertyKey, paramIndex)
+}
+
+class Person {
+ name : string = ''
+ age : number = 0
+
+ constructor ( name : string , age : number ) {
+ this .name = name
+ this .age = age
+ }
+
+ setName (@paramDecorator name : string ) {
+ this .name = name
+ }
+}
+
+const p = new Person ( '张三' , 18 ) // Person、setName、0
+p. setName ( '李四' )
`,118),e=[o];function t(c,r,y,E,i,F){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{C as __pageData,d as default};
diff --git a/assets/frontend_base_typescript.md.c2a79275.lean.js b/assets/frontend_base_typescript.md.c2a79275.lean.js
new file mode 100644
index 000000000..1c01417f9
--- /dev/null
+++ b/assets/frontend_base_typescript.md.c2a79275.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"TypeScript","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/typescript.md","filePath":"frontend/base/typescript.md","lastUpdated":1694368780000}'),p={name:"frontend/base/typescript.md"},o=l("",118),e=[o];function t(c,r,y,E,i,F){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{C as __pageData,d as default};
diff --git a/assets/frontend_base_webpack.md.606bbc01.js b/assets/frontend_base_webpack.md.606bbc01.js
new file mode 100644
index 000000000..0177dde6f
--- /dev/null
+++ b/assets/frontend_base_webpack.md.606bbc01.js
@@ -0,0 +1,141 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const F=JSON.parse('{"title":"Webpack","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/webpack.md","filePath":"frontend/base/webpack.md","lastUpdated":1694368780000}'),p={name:"frontend/base/webpack.md"},o=l(`Webpack webpack时前端项目工程化的具体解决方案。webpack的主要功能:提供了友好的前端模块化开发支持、代码压缩混淆、处理浏览器端javascript得兼容性、性能优化等强大的功能。
前端工程化 基础使用 默认约定 webpack 4.x和5.x版本有如下默认约定
默认打包入口文件:src -> index.js 默认输出文件路径:dist -> main.js 修改打包默认约定 可以在webpack.config.js中
修改entry节点指定打包入口 修改output节点指定输出 js const path = require ( 'path' )
+
+module . exports = {
+ entry: path. join (__dirname, './src/index.js' ),
+ output: {
+ path: path. join (__dirname, './dist' ),
+ filename: 'js/bundle.js'
+ }
+}
const path = require ( 'path' )
+
+module . exports = {
+ entry: path. join (__dirname, './src/index.js' ),
+ output: {
+ path: path. join (__dirname, './dist' ),
+ filename: 'js/bundle.js'
+ }
+}
插件 webpack-dev-server html-webpack-plugin clean-webpack-plugin devServer节点 js devServer : {
+ open : true ,
+ host : 127.0 . 0.1 ,
+ port : 80
+}
devServer : {
+ open : true ,
+ host : 127.0 . 0.1 ,
+ port : 80
+}
loader 实际开发中,webpack只能打包处理.js模块,其他后缀的模块需要调用loader加载器才能正常打包
loader加载器作用:协助webpack打包处理特定的文件模块,比如:
build打包发布 在package.json的script节点下新增build命令:
json "scripts" : {
+ "dev" : "webpack serve" ,
+ "build" : "webpack --mode production"
+}
"scripts" : {
+ "dev" : "webpack serve" ,
+ "build" : "webpack --mode production"
+}
SourceMap SourceMap是一个信息文件,里面存着位置信息。也就是说SourceMap文件中存储着压缩混淆后的代码所对应的转换前的位置。
出错的时候除错工具直接显示原始代码,而不是转换后的代码,方便后期调试。
开发环境 在webpack.config.js中添加配置,保证运行时报错和源代码行数一致:js module . exports = {
+ devtool: 'eval-source-map'
+}
module . exports = {
+ devtool: 'eval-source-map'
+}
生产环境 省略devltool选项则最终生成文件不包含SourceMap,能够防止源码通过SourceMap暴露。如果只想定位报错具体行数,且不想暴露源码,可以将devtool设置为nosources-source-map
其他 js resolve : {
+ alias : {
+ //@代表源码目录
+ '@' : path. join (__dirname, './src/' )
+ }
+}
resolve : {
+ alias : {
+ //@代表源码目录
+ '@' : path. join (__dirname, './src/' )
+ }
+}
`,38),e=[o];function c(t,r,i,E,y,d){return a(),n("div",null,e)}const h=s(p,[["render",c]]);export{F as __pageData,h as default};
diff --git a/assets/frontend_base_webpack.md.606bbc01.lean.js b/assets/frontend_base_webpack.md.606bbc01.lean.js
new file mode 100644
index 000000000..48d0d7f0f
--- /dev/null
+++ b/assets/frontend_base_webpack.md.606bbc01.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const F=JSON.parse('{"title":"Webpack","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/base/webpack.md","filePath":"frontend/base/webpack.md","lastUpdated":1694368780000}'),p={name:"frontend/base/webpack.md"},o=l("",38),e=[o];function c(t,r,i,E,y,d){return a(),n("div",null,e)}const h=s(p,[["render",c]]);export{F as __pageData,h as default};
diff --git a/assets/frontend_framework_vue.md.c825351a.js b/assets/frontend_framework_vue.md.c825351a.js
new file mode 100644
index 000000000..19925e440
--- /dev/null
+++ b/assets/frontend_framework_vue.md.c825351a.js
@@ -0,0 +1,949 @@
+import{_ as o,o as e,c as t,k as s,a,t as l,Q as p}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"Vue","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/framework/vue.md","filePath":"frontend/framework/vue.md","lastUpdated":1694368780000}'),c={name:"frontend/framework/vue.md"},r=p(`Vue Vue (读音 /vjuː/,类似于 view ) 是一套用于构建用户界面的渐进式框架 。
特性 数据驱动视图:在数据变化时页面会重新渲染 双向数据绑定:DOM元素中的数据和Vue实例中的data保持一致,无论谁被改变,另一方都会更新为相同的数据 MVVM MVVM是Vue实现数据驱动视图和双向数据绑定的原理。MVVM指的是Model、View和ViewModel。
Model:当前页面渲染时依赖的数据源 View:当前页面渲染的DOM结构 ViewModel:Vue的实例,MVVM的核心 ViewModel把Model和View连接在一起,同时监听DOM变化和数据源的变化。
起步 html <! doctype html >
+< html lang = "en" >
+< head >
+ < meta charset = "UTF-8" >
+ < meta name = "viewport"
+ content = "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" >
+ < meta http-equiv = "X-UA-Compatible" content = "ie=edge" >
+ < title >Document</ title >
+</ head >
+< body >
+< div id = "app" >
+ {{ msg }}
+</ div >
+< script src = "https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js" ></ script >
+< script >
+ const vm = new Vue ({
+ el: '#app' ,
+ data: {
+ msg: 'hello world'
+ }
+ })
+</ script >
+</ body >
+</ html >
<! doctype html >
+< html lang = "en" >
+< head >
+ < meta charset = "UTF-8" >
+ < meta name = "viewport"
+ content = "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" >
+ < meta http-equiv = "X-UA-Compatible" content = "ie=edge" >
+ < title >Document</ title >
+</ head >
+< body >
+< div id = "app" >
+ {{ msg }}
+</ div >
+< script src = "https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js" ></ script >
+< script >
+ const vm = new Vue ({
+ el: '#app' ,
+ data: {
+ msg: 'hello world'
+ }
+ })
+</ script >
+</ body >
+</ html >
指令和过滤器 内容渲染 `,12),E=s("li",null,[a("v-test "),s("ul",null,[s("li",null,[s("code",null,"
"),a(":把username值渲染到p标签中")]),s("li",null,[s("code",null,"性别
"),a(":把gender值渲染到p标签中,原有的值会被覆盖")])])],-1),i=s("li",null,"插值表达式(Mustache),专门用来解决v-text会覆盖默认文本内容的问题,不能用在属性上",-1),y=s("li",null,"支持javascript表达式",-1),u=s("li",null,[a("v-html "),s("ul",null,[s("li",null,"把包含HTML标签的字符串渲染为页面的HTML元素")])],-1),d=p(`属性绑定 v-bind:单向绑定 v-bind:属性名
简写为:属性名
支持javascript表达式 事件绑定 v-on 双向绑定 v-model:不操作DOM情况下,快速获取表单数据 修饰符 .nubmer:自动将输入转为数值 .trim:自动过滤输入的首尾空白字符 .lazy:在change时更新,input时不更新 条件渲染 控制DOM的显示与隐藏
v-if 通过添加、移除元素实现 如果刚进入页面不需要被展示,而且后期可能也不需要展示此时v-if性能更好 配套指令:v-else、v-else-if v-show display控制元素显示、隐藏 如果频繁切换显示状态用v-show更好 列表渲染 v-for渲染数组 基于一个数组来循环渲染一个列表结构。v-for指令需要用item in items形式的特殊语法
vue < li v-for = " item in items " >姓名是: {{ item.name }}</ li >
+
+< li v-for = " (item,index) in items " >姓名是: {{ item.name }}</ li >
< li v-for = " item in items " >姓名是: {{ item.name }}</ li >
+
+< li v-for = " (item,index) in items " >姓名是: {{ item.name }}</ li >
items:待循环数组 item:被循环的每一项 index:索引号,从0开始 建议用到v-for指令,要绑定一个:key
属性,而且尽量把id作为key
key的值要是字符串/数字类型 index作为key没有任何意义,因为index没有唯一性(和数据没有绑定关系) 指定key可以提升性能、防止列表状态紊乱 v-for渲染对象 完整语法
vue < li v-for = " (value, key, index) in myObject " >
+ {{ value }} {{ kye }} {{ index }}
+</ li >
< li v-for = " (value, key, index) in myObject " >
+ {{ value }} {{ kye }} {{ index }}
+</ li >
过滤器(vue3已移除) 常用于文本格式化,过滤器可以用在两个地方:插值表达式和v-bind属性绑定,过滤器本质是函数,被定义在vue实例的filters节点下
`,22),h=s("li",null,[s("code",null,'
vue filters: {
+ capitalize(val) {
+ return val.charAt(0).toUppercase() + var.slice(1)
+ }
+}
filters: {
+ capitalize(val) {
+ return val.charAt(0).toUppercase() + var.slice(1)
+ }
+}
私有过滤器和全局过滤器 私有过滤器:定义在vue实例的filters节点下 全局过滤器:使用Vue.filter(filter, (str) => {return xxx})定义 连续调用&传参 vue {{ msg | filterA | filterB(arg1, arg2)}}
{{ msg | filterA | filterB(arg1, arg2)}}
侦听器 watch侦听器语序开发者监视数据的变化,从而针对数据的变化做特定的操作。
侦听器格式 watch定义在vue实例的watch节点下
vue watch: {
+username(newVal, oldVal) {
+ console.log(newVal, oldVal)
+}
+}
watch: {
+username(newVal, oldVal) {
+ console.log(newVal, oldVal)
+}
+}
缺点
无法在刚进入页面时自动触发 如果侦听的是一个对象,对象属性发生变化不会触发侦听器 vue watch: {
+username: {
+ handler(newVal, oldVal) {
+ console.log(newVal, oldVal)
+},
+immediate: true,
+deep: true,
+'info.age'(newVal) {
+ console.log(newVal)
+}
+}
watch: {
+username: {
+ handler(newVal, oldVal) {
+ console.log(newVal, oldVal)
+},
+immediate: true,
+deep: true,
+'info.age'(newVal) {
+ console.log(newVal)
+}
+}
计算属性 通过运算得到的属性值,可以被模版结构或methods方法使用。
计算属性放在vue实例的computed
节点中
vue var vm = new Vue({
+ el: '#app',
+ data: {
+ r: 0, g: 0, b: 0
+ },
+ computed: {
+ //计算属性rgb
+ rgb() {
+ return \`rgb(\${this.r}, \${this.g}, \${this.b})\`
+ }
+ //计算属性 allChecked
+ allChecked: {
+ get() {
+ return this.goodsList.every(item => item.goods_state)
+ },
+ set(newVal) {
+ this.goodsList.forEach(item => item.goods_state = newVal)
+ }
+ }
+ },
+ methods: {
+ show() {
+ console.log(this.rgb)
+ }
+ }
+})
var vm = new Vue({
+ el: '#app',
+ data: {
+ r: 0, g: 0, b: 0
+ },
+ computed: {
+ //计算属性rgb
+ rgb() {
+ return \`rgb(\${this.r}, \${this.g}, \${this.b})\`
+ }
+ //计算属性 allChecked
+ allChecked: {
+ get() {
+ return this.goodsList.every(item => item.goods_state)
+ },
+ set(newVal) {
+ this.goodsList.forEach(item => item.goods_state = newVal)
+ }
+ }
+ },
+ methods: {
+ show() {
+ console.log(this.rgb)
+ }
+ }
+})
vue computed: {
+ allChecked: {
+ get() {
+ return this.goodsList.every(item => item.goods_state)
+ },
+ set(newVal) {
+ this.goodsList.forEach(item => item.goods_state = newVal)
+ }
+ }
+}
computed: {
+ allChecked: {
+ get() {
+ return this.goodsList.every(item => item.goods_state)
+ },
+ set(newVal) {
+ this.goodsList.forEach(item => item.goods_state = newVal)
+ }
+ }
+}
axios 基本语法:
js axios ({
+ method: '请求类型' ,
+ // URL中的query参数
+ params: {
+
+ },
+ // body参数
+ data: {
+
+}
+ url: '请求的URL地址' ,
+}). then (( result ) => {
+ //.then用来指定成功的回调,result是请求成功后的结果
+})
axios ({
+ method: '请求类型' ,
+ // URL中的query参数
+ params: {
+
+ },
+ // body参数
+ data: {
+
+}
+ url: '请求的URL地址' ,
+}). then (( result ) => {
+ //.then用来指定成功的回调,result是请求成功后的结果
+})
结合async和await使用axios
js document. querySelector ( '#btn' ). addEventListener ( 'click' , async function (){
+ // 如果调用方法返回值是Promise实例,则可以在前面添加await,await只能用在被async“修饰”的方法中
+ // 解构赋值的时候使用:进行重命名
+ const { data : res } = await axios ({
+ method: 'POST' ,
+ url: 'xxx' ,
+ data: {
+ name: '111'
+ }
+ })
+ console. log (res.data)
+})
document. querySelector ( '#btn' ). addEventListener ( 'click' , async function (){
+ // 如果调用方法返回值是Promise实例,则可以在前面添加await,await只能用在被async“修饰”的方法中
+ // 解构赋值的时候使用:进行重命名
+ const { data : res } = await axios ({
+ method: 'POST' ,
+ url: 'xxx' ,
+ data: {
+ name: '111'
+ }
+ })
+ console. log (res.data)
+})
axios.get() axios.post() axios.delete() axios.put() vue工程中使用 js // main.js
+
+import Vue from 'docs/frontend/framework/vue'
+import App from './App.vue'
+import axios from 'axios'
+
+Vue.config.productionTip = false
+
+// 缺点:不利于api接口复用
+// 组件实例中直接用\`this.$http\`使用
+// axios.defaults.baseURL = '请求根路径'
+// Vue.prototype.$http = axios
+
+new Vue ({
+ reder : h => h (App)
+}). $mount (#app)
// main.js
+
+import Vue from 'docs/frontend/framework/vue'
+import App from './App.vue'
+import axios from 'axios'
+
+Vue.config.productionTip = false
+
+// 缺点:不利于api接口复用
+// 组件实例中直接用\`this.$http\`使用
+// axios.defaults.baseURL = '请求根路径'
+// Vue.prototype.$http = axios
+
+new Vue ({
+ reder : h => h (App)
+}). $mount (#app)
vue-cli 单页面应用程序(Single Page Application)简称SPA,指的是一个Web网站中只有唯一的一个HTML页面,所有的功能与交互都在这唯一的一个页面内完成。
vue-cli是Vue.js开发的标准工具。简化了基于webpack创建工程化的Vue项目的过程。
安装 npm install -g @vue/cli
创建项目 vue create projectName
vue组件 组件组成 组件后缀名是.vue
,vue组件包括三个组成部分
template vue <!--template是一个虚拟标签,只起到包裹作用,不会被渲染成任何实质性HTML-->
+< template >
+ < div >
+ <!--template中只能有一个根元素-->
+ </ div >
+</ template >
<!--template是一个虚拟标签,只起到包裹作用,不会被渲染成任何实质性HTML-->
+< template >
+ < div >
+ <!--template中只能有一个根元素-->
+ </ div >
+</ template >
script vue < script >
+ export default {
+ name: 'xxx' , // <keep-alive>实现组件缓存功能,调整工具中看到的标签名称
+ // data必须是一个函数
+ data () {
+ return {
+ xx: xx
+ }
+ },
+ methods: {
+ fun () {
+ // 组件中的this代表当前组件的实例对象
+ console. log ( this )
+ this .xx = yy
+ }
+ },
+ watch: {},
+ computed: {}
+ ...
+ }
+</ script >
< script >
+ export default {
+ name: 'xxx' , // <keep-alive>实现组件缓存功能,调整工具中看到的标签名称
+ // data必须是一个函数
+ data () {
+ return {
+ xx: xx
+ }
+ },
+ methods: {
+ fun () {
+ // 组件中的this代表当前组件的实例对象
+ console. log ( this )
+ this .xx = yy
+ }
+ },
+ watch: {},
+ computed: {}
+ ...
+ }
+</ script >
style vue < style lang = "less" > /* 默认lang="css" */
+
+</ style >
< style lang = "less" > /* 默认lang="css" */
+
+</ style >
组件之间的父子关系 组件被封装好后,彼此之间是相互独立的,不存在父子关系。
在使用组件 时,根据彼此的嵌套关系,形成了父子关系,兄弟关系。
组件使用步骤 注册私有子组件 import语法导入需要的组件 vue import A from '@/components/A.vue
import A from '@/components/A.vue
使用components节点注册组件 vue export default {
+ components: {
+ A //注册名称主要用于 以标签形式把注册的组件 渲染和使用到页面结构之中
+ }
+}
export default {
+ components: {
+ A //注册名称主要用于 以标签形式把注册的组件 渲染和使用到页面结构之中
+ }
+}
以标签形式使用注册的组件 vue < div class = "box" >
+ <A></A>
+</ div >
< div class = "box" >
+ <A></A>
+</ div >
注册全局组件 vue // main.js
+
+import Test from '@/components/Test.vue'
+
+Vue.component('MyTest', Test)
// main.js
+
+import Test from '@/components/Test.vue'
+
+Vue.component('MyTest', Test)
组件的props props是组件的自定义属性 ,在封装通用组件的时候,合理的使用props可以极大提高组件的复用性。
props中的数据,可以直接在模板结构中使用 props是只读的 vue < script >
+ export default {
+ props: {
+ initCount: {
+ default: 0 , //默认值
+ type: Number, //规定属性的值类型,如果传递的值不符合,则会报错
+ required: true //必填项
+ }
+ },
+ data () {
+ return {
+ count: this .initCount
+ }
+ }
+ }
+</ script >
< script >
+ export default {
+ props: {
+ initCount: {
+ default: 0 , //默认值
+ type: Number, //规定属性的值类型,如果传递的值不符合,则会报错
+ required: true //必填项
+ }
+ },
+ data () {
+ return {
+ count: this .initCount
+ }
+ }
+ }
+</ script >
组件之间样式冲突 默认情况下,写在组件中的样式会全局生效,原因是:
单页面应用程序中所有的DOM结构都是基于唯一的index.hmtl页面进行呈现的 每个组件中的样式都会影响整个index.html的DOM元素 解决方案 deep样式穿透 /deep/ 选择器
当使用第三方组件库,如果有修改第三方组件库的默认样式需求,需要用到deep
组件的生命周期 生命周期是指一个组件从创建->运行->销毁的整个阶段。
分类 组件创建阶段 beforeCreate:组件的props/data/methods尚未被创建,都处于不可用状态 created:组件的props/data/methods被创建,都处于可用状态,但是组件的模板结构尚未生成 beforeMount:将要把内存中编译好的HTML结果渲染到浏览器中,此时浏览器中还没有当前组件的DOM结构 mounted:已经把内存中编译好的HTML结果渲染到浏览器中,此时浏览器已经包含当前组件DOM结构 组件运行阶段 beforeUpdate:将要根据变化过后、最新的数据重新渲染组件的模版结构 updated:已经根据最新的数据,完成了组件DOM结构的重新渲染 组件销毁阶段 beforeDestroy:将要销毁此组件但还未销毁,组件还处于正常工作状态 destroyed:组件已被销毁,此组件在浏览器中对应的DOM结构已被完全移除 组件数据共享 父组件向子组件共享数据需要使用自定义属性 子向父传值需要使用自定义事件 js //子组件
+methods : {
+ add () {
+ this .count += 1
+ this . $emit ( 'numchange' , this .count)
+ }
+}
//子组件
+methods : {
+ add () {
+ this .count += 1
+ this . $emit ( 'numchange' , this .count)
+ }
+}
js //父组件
+< Son @numchange="getNewCount"></Son>
+
+---
+
+methods: {
+ getNewCount (val) {
+ this.countFromSon = val
+ }
+}
//父组件
+< Son @numchange="getNewCount"></Son>
+
+---
+
+methods: {
+ getNewCount (val) {
+ this.countFromSon = val
+ }
+}
兄弟组件之间数据共享需要使用EventBus vue //A组件
+import bus from './eventBus.js'
+
+methods: {
+ sendMsg()
+ {
+ bus.$emit('share', this.msg)
+ }
+}
+
+//eventBus.js
+import Vue from 'docs/frontend/framework/vue'
+
+export default new Vue()
+
+//兄弟组件B
+import bus from './eventBus.js'
+
+created()
+{
+ bus.$on('share', val => {
+ this.msgFromSibling = val
+ })
+}
//A组件
+import bus from './eventBus.js'
+
+methods: {
+ sendMsg()
+ {
+ bus.$emit('share', this.msg)
+ }
+}
+
+//eventBus.js
+import Vue from 'docs/frontend/framework/vue'
+
+export default new Vue()
+
+//兄弟组件B
+import bus from './eventBus.js'
+
+created()
+{
+ bus.$on('share', val => {
+ this.msgFromSibling = val
+ })
+}
ref引用 ref用来辅助开发者在不依赖jQuery的情况下,获取DOM元素或组件的引用
每个vue组件的实例上,都包含一个$refs
对象,里面存储着对应的DOM元素或组件的引用,默认情况下组件的$refs
指向一个空对象
使用ref引用页面上的DOM元素 vue < div ref = "myDiv" ></ div >
+
+// methods中访问
+this.$refs.myDiv
< div ref = "myDiv" ></ div >
+
+// methods中访问
+this.$refs.myDiv
使用ref引用组件 vue < Son ref = "compSon" ></ Son >
+
+// methods中访问
+this.$refs.compSon.方法
+this.$refs.compSon.属性
< Son ref = "compSon" ></ Son >
+
+// methods中访问
+this.$refs.compSon.方法
+this.$refs.compSon.属性
nextTick(callback) 组件的$nextTick(callback)
方法,会把callback函数会推迟到下一个DOM更新之后执行。
动态组件 动态组件指的是动态切换组件的显示与隐藏。
component标签 的is
属性 vue < template >
+ < component :is = "componentName" ></ component >
+</ template >
+< script >
+ import Left from '@/components/Left.vue'
+ import Right from '@/components/Right.vue'
+
+ export default {
+ data () {
+ return {
+ componentName: "Left"
+ }
+ },
+ components: {
+ Left,
+ Right
+ }
+ }
+</ script >
< template >
+ < component :is = "componentName" ></ component >
+</ template >
+< script >
+ import Left from '@/components/Left.vue'
+ import Right from '@/components/Right.vue'
+
+ export default {
+ data () {
+ return {
+ componentName: "Left"
+ }
+ },
+ components: {
+ Left,
+ Right
+ }
+ }
+</ script >
keep-alive keep-alive
标签能把内部的组件进行缓存,而不是销毁组件
vue < template >
+ < keep-alive >
+ < component :is = "componentName" ></ component >
+ </ keep-alive >
+</ template >
< template >
+ < keep-alive >
+ < component :is = "componentName" ></ component >
+ </ keep-alive >
+</ template >
对应的生命周期函数 被缓存:deactivated生命周期函数 被激活:activated生命周期函数,当组件第一次被创建也会执行 include/exclude属性 vue < template >
+ < keep-alive include = "Left" >
+ < component :is = "componentName" ></ component >
+ </ keep-alive >
+</ template >
< template >
+ < keep-alive include = "Left" >
+ < component :is = "componentName" ></ component >
+ </ keep-alive >
+</ template >
插槽 插槽(Slot)是vue为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部份定义为插槽。
vue <!--Left.vue-->
+< template >
+ < slot name = "default" >
+ 这里可以指定默认内容,会被覆盖
+ </ slot >
+</ template >
<!--Left.vue-->
+< template >
+ < slot name = "default" >
+ 这里可以指定默认内容,会被覆盖
+ </ slot >
+</ template >
渲染Left组件时
vue < template >
+ < Left >
+ <!--此区域必须在组件中声明插槽才会渲染-->
+ <!--默认情况下 会被填充到名为default的插槽内-->
+ < p >
+ 自定义内容
+ </ p >
+ </ Left >
+</ template >
+
+---
+< template >
+ < Left >
+ < template v-slot : default >
+ < p >
+ 自定义内容
+ </ p >
+ </ template >
+ </ Left >
+</ template >
+---
+< template >
+ < Left >
+ < template # default >
+ < p >
+ 自定义内容
+ </ p >
+ </ template >
+ </ Left >
+</ template >
< template >
+ < Left >
+ <!--此区域必须在组件中声明插槽才会渲染-->
+ <!--默认情况下 会被填充到名为default的插槽内-->
+ < p >
+ 自定义内容
+ </ p >
+ </ Left >
+</ template >
+
+---
+< template >
+ < Left >
+ < template v-slot : default >
+ < p >
+ 自定义内容
+ </ p >
+ </ template >
+ </ Left >
+</ template >
+---
+< template >
+ < Left >
+ < template # default >
+ < p >
+ 自定义内容
+ </ p >
+ </ template >
+ </ Left >
+</ template >
slot vue 定义:
+< slot name = "content" msg = "hello world" ></ slot >
+--
+使用:
+< template # content = " scope " >
+ < p >
+ {{ scope.msg }}
+ </ p >
+</ template >
+
+- 作用域插槽解构赋值
+
+ - \`\`\`html
+ < slot name = "content" msg = "hello world" : user = " user " ></ slot >
+
+ ---
+
+ < template # content = " {msg, user} " >
+ < p >
+ {{msg}}
+ </ p >
+ < p >
+ {{user}}
+ </ p >
+ </ template >
定义:
+< slot name = "content" msg = "hello world" ></ slot >
+--
+使用:
+< template # content = " scope " >
+ < p >
+ {{ scope.msg }}
+ </ p >
+</ template >
+
+- 作用域插槽解构赋值
+
+ - \`\`\`html
+ < slot name = "content" msg = "hello world" : user = " user " ></ slot >
+
+ ---
+
+ < template # content = " {msg, user} " >
+ < p >
+ {{msg}}
+ </ p >
+ < p >
+ {{user}}
+ </ p >
+ </ template >
v-slot 只能用在template标签上 使用具名插槽简写形式#default
自定义指令 私有自定义指令 在每个vue组件中,可以在directives节点下声明私有自定义指令
bind函数 vue directives: {
+ color: {
+ bind(el, binding) {
+ el.style.color = binding.value
+ }
+ }
+}
directives: {
+ color: {
+ bind(el, binding) {
+ el.style.color = binding.value
+ }
+ }
+}
update函数 第一次不会触发
在DOM更新的时候就会触发update函数
vue directives: {
+ color: {
+ update(el, binding) {
+ el.style.color = binding.value
+ }
+ }
+}
directives: {
+ color: {
+ update(el, binding) {
+ el.style.color = binding.value
+ }
+ }
+}
函数简写 如果bind和update函数的逻辑完全相同,则对象格式的自定义指令可以简写为
vue directives: {
+ color(el, binding) {
+ el.style.color = binding.value
+ }
+}
directives: {
+ color(el, binding) {
+ el.style.color = binding.value
+ }
+}
全局自定义指令 使用Vue.directive
声明
vue Vue.directive('color', function (el, binding) {
+ el.style.color = binding.value
+})
+---
+ Vue.directive('color', {
+ binding(el, binding) {
+ el.style.color = binding.value
+ },
+ update(el, binding) {
+ el.style.color = binding.value
+ }
+ })
Vue.directive('color', function (el, binding) {
+ el.style.color = binding.value
+})
+---
+ Vue.directive('color', {
+ binding(el, binding) {
+ el.style.color = binding.value
+ },
+ update(el, binding) {
+ el.style.color = binding.value
+ }
+ })
路由(Router) 模式 hash模式
history模式
vue //vue.config.js
+module.exports = {
+ publicPath: process.env.NODE_ENV === 'production'
+ ? '/production-sub-path/'
+ : '/'
+}
//vue.config.js
+module.exports = {
+ publicPath: process.env.NODE_ENV === 'production'
+ ? '/production-sub-path/'
+ : '/'
+}
工作方式 用户点击路由链接 导致了URL地址栏中Hash值发生变化 前端路由监听到了Hash地址变化 前端路由把当前Hash地址对应的组件渲染到浏览器中 vue-router 安装 npm i vue-router@version
创建路由模块 在src目录下,新建router/index.js
模块
vue import Vue from 'docs/frontend/framework/vue'
+import VueRouter from 'vue-router'
+
+// 调用Vue.use()函数,把VueRouter安装为Vue的插件
+Vue.use(VueRouter)
+
+const router = new VueRouter()
+
+export default router
import Vue from 'docs/frontend/framework/vue'
+import VueRouter from 'vue-router'
+
+// 调用Vue.use()函数,把VueRouter安装为Vue的插件
+Vue.use(VueRouter)
+
+const router = new VueRouter()
+
+export default router
导入并挂载路由模块 main.js中挂载路由模块
js //main.js
+import
+
+...
+//import router from '@/router/index.js'
+//简写
+import router from '@/router'
+
+new Vue ({
+ render : h => h (App),
+ //router: router
+ //属性名 属性值一致 可以简写
+ router
+})
//main.js
+import
+
+...
+//import router from '@/router/index.js'
+//简写
+import router from '@/router'
+
+new Vue ({
+ render : h => h (App),
+ //router: router
+ //属性名 属性值一致 可以简写
+ router
+})
声明路由链接和占位符 路由链接:router-link
占位符router-view
,组件在这里展示 vue < template >
+ < div class = "container" >
+ < a href = "#/home" >首页</ a >
+ < a href = "#/movie" >电影</ a >
+ < a href = "#/about" >关于</ a >
+ < hr />
+ 可以用router-link标签代替普通a标签,可以省略#
+ < router-link to = "#/home" >首页</ router-link >
+ < router-link to = "#/movie" >电影</ router-link >
+ < router-link to = "#/about" >关于</ router-link >
+
+ < router-view ></ router-view >
+ </ div >
+</ template >
< template >
+ < div class = "container" >
+ < a href = "#/home" >首页</ a >
+ < a href = "#/movie" >电影</ a >
+ < a href = "#/about" >关于</ a >
+ < hr />
+ 可以用router-link标签代替普通a标签,可以省略#
+ < router-link to = "#/home" >首页</ router-link >
+ < router-link to = "#/movie" >电影</ router-link >
+ < router-link to = "#/about" >关于</ router-link >
+
+ < router-view ></ router-view >
+ </ div >
+</ template >
修改router/index.js
vue import Vue from 'docs/frontend/framework/vue'
+import VueRouter from 'vue-router'
+
+import Home from '@/components/Home.vue'
+import Movie from '@/components/Movie.vue'
+import About from '@/components/About.vue'
+
+// 调用Vue.use()函数,把VueRouter安装为Vue的插件
+Vue.use(VueRouter)
+
+const router = new VueRouter({
+ // routes是一个数组:定义hash地址和组件之间的对应关系
+ routes: [
+ // 路由规则
+ {path: '/', redirect: '/home'}, // 重定向
+ {path: '/home', component: Home},
+ {path: '/movie', component: Movie},
+ {path: '/about', component: About}
+ ]
+})
+
+export default router
import Vue from 'docs/frontend/framework/vue'
+import VueRouter from 'vue-router'
+
+import Home from '@/components/Home.vue'
+import Movie from '@/components/Movie.vue'
+import About from '@/components/About.vue'
+
+// 调用Vue.use()函数,把VueRouter安装为Vue的插件
+Vue.use(VueRouter)
+
+const router = new VueRouter({
+ // routes是一个数组:定义hash地址和组件之间的对应关系
+ routes: [
+ // 路由规则
+ {path: '/', redirect: '/home'}, // 重定向
+ {path: '/home', component: Home},
+ {path: '/movie', component: Movie},
+ {path: '/about', component: About}
+ ]
+})
+
+export default router
嵌套路由 通过路由实现组件的嵌套展示,叫做嵌套路由
模板内容中又有子级路由链接 点击子级路由链接显示子级模板内容 通过children属性声明子路由规则 vue import Vue from 'docs/frontend/framework/vue'
+import VueRouter from 'vue-router'
+
+import Home from '@/components/Home.vue'
+import Movie from '@/components/Movie.vue'
+import About from '@/components/About.vue'
+import Tab1 from '@/components/tabs/Tab1.vue'
+import Tab2 from '@/components/tabs/Tab2.vue'
+
+// 调用Vue.use()函数,把VueRouter安装为Vue的插件
+Vue.use(VueRouter)
+
+const router = new VueRouter({
+ // routes是一个数组:定义hash地址和组件之间的对应关系
+ routes: [
+ // 路由规则
+ {path: '/', redirect: '/home'}, // 重定向
+ {path: '/home', component: Home},
+ {path: '/movie', component: Movie},
+ {
+ path: '/about',
+ component: About,
+ children: [
+ {path: 'tab1', component: Tab1},
+ {path: 'tab2', component: Tab2}
+ ]
+ }
+ ]
+})
+
+export default router
import Vue from 'docs/frontend/framework/vue'
+import VueRouter from 'vue-router'
+
+import Home from '@/components/Home.vue'
+import Movie from '@/components/Movie.vue'
+import About from '@/components/About.vue'
+import Tab1 from '@/components/tabs/Tab1.vue'
+import Tab2 from '@/components/tabs/Tab2.vue'
+
+// 调用Vue.use()函数,把VueRouter安装为Vue的插件
+Vue.use(VueRouter)
+
+const router = new VueRouter({
+ // routes是一个数组:定义hash地址和组件之间的对应关系
+ routes: [
+ // 路由规则
+ {path: '/', redirect: '/home'}, // 重定向
+ {path: '/home', component: Home},
+ {path: '/movie', component: Movie},
+ {
+ path: '/about',
+ component: About,
+ children: [
+ {path: 'tab1', component: Tab1},
+ {path: 'tab2', component: Tab2}
+ ]
+ }
+ ]
+})
+
+export default router
vue const routes = [
+ {
+ path: '/',
+ component: Home,
+ meta: {
+ keepAlive: true,
+ isRecord: true,
+ top: 0
+ }
+ },
+ {
+ path: '/user',
+ component: User
+ }
+]
+
+const router = new VueRouter({
+ routes,
+ scrollBehavior(to, from, savedPosition) {
+ if (savedPosition) {
+ return savedPosition
+ }
+ return {
+ x: 0,
+ y: to.meta.top || 0
+ }
+ }
+})
const routes = [
+ {
+ path: '/',
+ component: Home,
+ meta: {
+ keepAlive: true,
+ isRecord: true,
+ top: 0
+ }
+ },
+ {
+ path: '/user',
+ component: User
+ }
+]
+
+const router = new VueRouter({
+ routes,
+ scrollBehavior(to, from, savedPosition) {
+ if (savedPosition) {
+ return savedPosition
+ }
+ return {
+ x: 0,
+ y: to.meta.top || 0
+ }
+ }
+})
动态路由匹配 把Hash地址中可变的部分定义为参数项,可以提高路由规则的复用性。
使用:
来定义路由的参数项
js { path : '/movie/:id' , component : Movie, props : true }
{ path : '/movie/:id' , component : Movie, props : true }
组件中可以通过$route.params.id
获取路径参数(Path Variable) $route.params.query
获取查询参数
$route.params.fullPath
:包含路径和参数
$route.params.path
:只有路径没有参数
可以在路由规则中添加props
传餐,在组件中定义props直接获取 导航 声明式导航 编程式导航 location.href跳转 vue-router的编程式导航api this.$router.push('hash地址')
:跳转到hash地址,增加一条历史记录this.$router.replace('hash地址')
:跳转到hash地址,并替换掉当前的历史记录this.$router.go(数值n)
:可以在浏览历史中前进或后退 this.$router.back()
this.$router.forward()
导航守卫 可以控制路由的访问权限
全局前置守卫 每次发生路由导航跳转时,都会触发前置守卫,在前置守卫中,可以对每个路由进行访问权限控制。
vue const router = new VueRouter({})
+// 每次路由跳转都会触发回调函数
+router.beforeEach((to, from, next) => {
+ // to:将要访问的路由信息对象
+ // from:将要离开的路由信息对象
+ // next:一个函数,调用next()表示放行,允许这次路由导航
+})
const router = new VueRouter({})
+// 每次路由跳转都会触发回调函数
+router.beforeEach((to, from, next) => {
+ // to:将要访问的路由信息对象
+ // from:将要离开的路由信息对象
+ // next:一个函数,调用next()表示放行,允许这次路由导航
+})
next的三种调用方式 next():直接放行 next('/path'):强制跳转到指定页面 next(false):不允许跳转,强制保留在当前页面 Vue3.0 创建项目 vite 使用:npm init vite-app projectName
create-vue create-vue
创建的项目也是基于vite的构建设置
使用:npm create vue@3
或者npm init vue@latest
crete-vue同样支持vue2:npm create vue@2
/ npm init vue@2
Vite和Webpack的区别(内容来自ChatGPT):
Vite和Webpack是两种常用的前端构建工具。
底层实现不同 Vite使用ES
modules(ESM)作为模块系统管理,而Webpack使用CommonJS来管理模块。这意味着,在使用Webpack打包项目时,所有模块都将被打包到一个或多个bundle.js文件中,而Vite将原始文件作为模块提取和处理,并将其以一种非常高效的方式提供给浏览器。
开发环境下的性能 Vite在开发环境下启动非常快,不需要等待代码打包时间,并且在修改代码时,也可以直接进行热更新,非常适合在开发阶段使用。而Webpack在开发模式下代码打包速度较慢,启动速度也相对较慢,修改代码后也需要较长的时间来重新打包。
生产环境下的性能 在生产环境下,Webpack可以通过代码分割(Code Splitting)和 Tree Shaking来优化代码,减小打包后的文件大小。而Vite在生产环境下目前还不支持代码分割。因此,如果项目需要大量使用Code Splitting和Tree Shaking等技术,使用Webpack可能会更加合适。
生态和可定制性 Webpack具有强大的社区和众多的插件和Loader来处理各种文件和场景,可以根据不同的需求进行高度的定制。而Vite的生态和可定制性方面要弱于Webpack,它的插件数量还比较少。
总的来说,Vite是一种专门为现代浏览器设计的前端构建工具,它在开发环境下性能卓越,但在生产环境方面还有一些局限。而Webpack则是一种更加稳健和灵活的构建工具,它可以用于各种复杂的场景和需求,并且有着更强大的生态和定制能力,但需要进行更多的配置。选择使用哪种工具,应该根据具体项目需求和使用场景来进行选择。
create-vue和vue-cli的区别:
vue-cli基于webpack,而create-vue基于vite 构建一个vue实例 js import {createApp} from 'docs/frontend/framework/vue'
+
+createApp ({
+ data () {
+ return {
+ count: 0
+ }
+ }
+}). mount ( '#app' )
import {createApp} from 'docs/frontend/framework/vue'
+
+createApp ({
+ data () {
+ return {
+ count: 0
+ }
+ }
+}). mount ( '#app' )
feature 响应式
https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html
响应式对象 `,188);function v(n,m,b,F,f,k){return e(),t("div",null,[r,s("ul",null,[E,s("li",null,[a(l()+" ",1),s("ul",null,[i,s("li",null,[s("code",null,"性别 "+l(n.gender)+"
",1)]),y])]),u]),d,s("ul",null,[s("li",null,[s("code",null," "+l(n.message|n.capitalize)+"
",1),a(":调用captitalize过滤器,对message进行格式化")]),h]),g])}const A=o(c,[["render",v]]);export{C as __pageData,A as default};
diff --git a/assets/frontend_framework_vue.md.c825351a.lean.js b/assets/frontend_framework_vue.md.c825351a.lean.js
new file mode 100644
index 000000000..bb1440504
--- /dev/null
+++ b/assets/frontend_framework_vue.md.c825351a.lean.js
@@ -0,0 +1 @@
+import{_ as o,o as e,c as t,k as s,a,t as l,Q as p}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"Vue","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/framework/vue.md","filePath":"frontend/framework/vue.md","lastUpdated":1694368780000}'),c={name:"frontend/framework/vue.md"},r=p("",12),E=s("li",null,[a("v-test "),s("ul",null,[s("li",null,[s("code",null,"
"),a(":把username值渲染到p标签中")]),s("li",null,[s("code",null,"性别
"),a(":把gender值渲染到p标签中,原有的值会被覆盖")])])],-1),i=s("li",null,"插值表达式(Mustache),专门用来解决v-text会覆盖默认文本内容的问题,不能用在属性上",-1),y=s("li",null,"支持javascript表达式",-1),u=s("li",null,[a("v-html "),s("ul",null,[s("li",null,"把包含HTML标签的字符串渲染为页面的HTML元素")])],-1),d=p("",22),h=s("li",null,[s("code",null,'
性别 "+l(n.gender)+"
",1)]),y])]),u]),d,s("ul",null,[s("li",null,[s("code",null," "+l(n.message|n.capitalize)+"
",1),a(":调用captitalize过滤器,对message进行格式化")]),h]),g])}const A=o(c,[["render",v]]);export{C as __pageData,A as default};
diff --git a/assets/frontend_index.md.971a7af8.js b/assets/frontend_index.md.971a7af8.js
new file mode 100644
index 000000000..6cc8bc40f
--- /dev/null
+++ b/assets/frontend_index.md.971a7af8.js
@@ -0,0 +1 @@
+import{_ as t,o as n,c as a,k as e,a as o}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"Frontend","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/index.md","filePath":"frontend/index.md","lastUpdated":1694368780000}'),r={name:"frontend/index.md"},d=e("h1",{id:"frontend",tabindex:"-1"},[o("Frontend "),e("a",{class:"header-anchor",href:"#frontend","aria-label":'Permalink to "Frontend"'},"")],-1),s=e("p",null,"没点前端开发能力自己想写点东西太难了,总不能全写命令行工具吧orz。。。",-1),c=[d,s];function i(_,l,f,p,h,m){return n(),a("div",null,c)}const k=t(r,[["render",i]]);export{u as __pageData,k as default};
diff --git a/assets/frontend_index.md.971a7af8.lean.js b/assets/frontend_index.md.971a7af8.lean.js
new file mode 100644
index 000000000..6cc8bc40f
--- /dev/null
+++ b/assets/frontend_index.md.971a7af8.lean.js
@@ -0,0 +1 @@
+import{_ as t,o as n,c as a,k as e,a as o}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"Frontend","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/index.md","filePath":"frontend/index.md","lastUpdated":1694368780000}'),r={name:"frontend/index.md"},d=e("h1",{id:"frontend",tabindex:"-1"},[o("Frontend "),e("a",{class:"header-anchor",href:"#frontend","aria-label":'Permalink to "Frontend"'},"")],-1),s=e("p",null,"没点前端开发能力自己想写点东西太难了,总不能全写命令行工具吧orz。。。",-1),c=[d,s];function i(_,l,f,p,h,m){return n(),a("div",null,c)}const k=t(r,[["render",i]]);export{u as __pageData,k as default};
diff --git a/assets/frontend_others_elementplus-el-upload.md.90eed779.js b/assets/frontend_others_elementplus-el-upload.md.90eed779.js
new file mode 100644
index 000000000..cc35a3038
--- /dev/null
+++ b/assets/frontend_others_elementplus-el-upload.md.90eed779.js
@@ -0,0 +1,603 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"ElementPlus el-upload源码分析","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/elementplus-el-upload.md","filePath":"frontend/others/elementplus-el-upload.md","lastUpdated":1694368780000}'),p={name:"frontend/others/elementplus-el-upload.md"},o=l(`ElementPlus el-upload源码分析 背景 使用el-upload实现限制上传文件个数、超出数量后后者覆盖前者、自动上传且自行实现上传文件请求,文档写的不清不楚,试了半天都不行,还是看了源码才明白el-upload组件的整个流程。
https://github.com/ElemeFE/element/blob/dev/packages/upload/src/index.vue
https://github.com/ElemeFE/element/blob/dev/packages/upload/src/upload.vue
分析 入口:index.vue的render函数 uploadData定义了参数结构,用于向子组件传参数,props中的几个属性就是el-upload标签的钩子 js // index.vue
+render (h) {
+ let uploadList;
+
+ if ( this .showFileList) {
+ uploadList = (
+ < UploadList
+ disabled = { this .uploadDisabled}
+ listType = { this .listType}
+ files = { this .uploadFiles}
+ on-remove = { this .handleRemove}
+ handlePreview = { this .onPreview}>
+ {
+ ( props ) => {
+ if ( this .$scopedSlots.file) {
+ return this .$scopedSlots. file ({
+ file: props.file
+ });
+ }
+ }
+ }
+ </ UploadList >
+ );
+ }
+
+ const uploadData = {
+ props: {
+ type: this .type,
+ drag: this .drag,
+ action: this .action,
+ multiple: this .multiple,
+ 'before-upload' : this .beforeUpload,
+ 'with-credentials' : this .withCredentials,
+ headers: this .headers,
+ name: this .name,
+ data: this .data,
+ accept: this .accept,
+ fileList: this .uploadFiles,
+ autoUpload: this .autoUpload,
+ listType: this .listType,
+ disabled: this .uploadDisabled,
+ limit: this .limit,
+ 'on-exceed' : this .onExceed,
+ 'on-start' : this .handleStart,
+ 'on-progress' : this .handleProgress,
+ 'on-success' : this .handleSuccess,
+ 'on-error' : this .handleError,
+ 'on-preview' : this .onPreview,
+ 'on-remove' : this .handleRemove,
+ 'http-request' : this .httpRequest
+ },
+ ref: 'upload-inner'
+ };
+
+ const trigger = this .$slots.trigger || this .$slots.default;
+ const uploadComponent = < upload { ... uploadData}>{trigger}</ upload >;
+
+ return (
+ < div >
+ { this .listType === 'picture-card' ? uploadList : '' }
+ {
+ this .$slots.trigger
+ ? [uploadComponent, this .$slots.default]
+ : uploadComponent
+ }
+ { this .$slots.tip}
+ { this .listType !== 'picture-card' ? uploadList : '' }
+ </ div >
+ );
+ }
// index.vue
+render (h) {
+ let uploadList;
+
+ if ( this .showFileList) {
+ uploadList = (
+ < UploadList
+ disabled = { this .uploadDisabled}
+ listType = { this .listType}
+ files = { this .uploadFiles}
+ on-remove = { this .handleRemove}
+ handlePreview = { this .onPreview}>
+ {
+ ( props ) => {
+ if ( this .$scopedSlots.file) {
+ return this .$scopedSlots. file ({
+ file: props.file
+ });
+ }
+ }
+ }
+ </ UploadList >
+ );
+ }
+
+ const uploadData = {
+ props: {
+ type: this .type,
+ drag: this .drag,
+ action: this .action,
+ multiple: this .multiple,
+ 'before-upload' : this .beforeUpload,
+ 'with-credentials' : this .withCredentials,
+ headers: this .headers,
+ name: this .name,
+ data: this .data,
+ accept: this .accept,
+ fileList: this .uploadFiles,
+ autoUpload: this .autoUpload,
+ listType: this .listType,
+ disabled: this .uploadDisabled,
+ limit: this .limit,
+ 'on-exceed' : this .onExceed,
+ 'on-start' : this .handleStart,
+ 'on-progress' : this .handleProgress,
+ 'on-success' : this .handleSuccess,
+ 'on-error' : this .handleError,
+ 'on-preview' : this .onPreview,
+ 'on-remove' : this .handleRemove,
+ 'http-request' : this .httpRequest
+ },
+ ref: 'upload-inner'
+ };
+
+ const trigger = this .$slots.trigger || this .$slots.default;
+ const uploadComponent = < upload { ... uploadData}>{trigger}</ upload >;
+
+ return (
+ < div >
+ { this .listType === 'picture-card' ? uploadList : '' }
+ {
+ this .$slots.trigger
+ ? [uploadComponent, this .$slots.default]
+ : uploadComponent
+ }
+ { this .$slots.tip}
+ { this .listType !== 'picture-card' ? uploadList : '' }
+ </ div >
+ );
+ }
渲染upload子组件,下面是upload组件的render函数 js // upload.vue
+render (h) {
+ let {
+ handleClick,
+ drag,
+ name,
+ handleChange,
+ multiple,
+ accept,
+ listType,
+ uploadFiles,
+ disabled,
+ handleKeydown
+ } = this ;
+ const data = {
+ class: {
+ 'el-upload' : true
+ },
+ on: {
+ click: handleClick,
+ keydown: handleKeydown
+ }
+ };
+ data.class[ \`el-upload--\${ listType }\` ] = true ;
+ return (
+ < div { ... data} tabindex = "0" >
+ {
+ drag
+ ? < upload-dragger disabled = {disabled} on-file = {uploadFiles}>{ this .$slots.default}</ upload-dragger >
+ : this .$slots.default
+ }
+ < input class = "el-upload__input" type = "file" ref = "input" name = {name} on-change = {handleChange} multiple = {multiple} accept = {accept}></ input >
+ </ div >
+ );
+ }
// upload.vue
+render (h) {
+ let {
+ handleClick,
+ drag,
+ name,
+ handleChange,
+ multiple,
+ accept,
+ listType,
+ uploadFiles,
+ disabled,
+ handleKeydown
+ } = this ;
+ const data = {
+ class: {
+ 'el-upload' : true
+ },
+ on: {
+ click: handleClick,
+ keydown: handleKeydown
+ }
+ };
+ data.class[ \`el-upload--\${ listType }\` ] = true ;
+ return (
+ < div { ... data} tabindex = "0" >
+ {
+ drag
+ ? < upload-dragger disabled = {disabled} on-file = {uploadFiles}>{ this .$slots.default}</ upload-dragger >
+ : this .$slots.default
+ }
+ < input class = "el-upload__input" type = "file" ref = "input" name = {name} on-change = {handleChange} multiple = {multiple} accept = {accept}></ input >
+ </ div >
+ );
+ }
主要关注<input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input>
这个jsx代码中绑定了onChange时间,调用函数是handleChange
// upload.vue
+handleChange(ev) {
+ const files = ev.target.files;
+
+ if (!files) return;
+ this.uploadFiles(files);
+}
// upload.vue
+handleChange(ev) {
+ const files = ev.target.files;
+
+ if (!files) return;
+ this.uploadFiles(files);
+}
可以看到handleChange直接调用了uploadFiles函数,而uploadFiles函数就是我们要追溯的内容了
js uploadFiles (files) {
+ if ( this .limit && this .fileList. length + files. length > this .limit) {
+ this .onExceed && this . onExceed (files, this .fileList);
+ return ;
+ }
+
+ let postFiles = Array . prototype .slice. call (files);
+ if ( ! this .multiple) { postFiles = postFiles. slice ( 0 , 1 ); }
+
+ if (postFiles. length === 0 ) { return ; }
+
+ postFiles. forEach ( rawFile => {
+ this . onStart (rawFile);
+ if ( this .autoUpload) this . upload (rawFile);
+ });
+},
+upload (rawFile) {
+ this .$refs.input.value = null ;
+
+ if ( ! this .beforeUpload) {
+ return this . post (rawFile);
+ }
+
+ const before = this . beforeUpload (rawFile);
+ if (before && before.then) {
+ before. then ( processedFile => {
+ const fileType = Object . prototype .toString. call (processedFile);
+
+ if (fileType === '[object File]' || fileType === '[object Blob]' ) {
+ if (fileType === '[object Blob]' ) {
+ processedFile = new File ([processedFile], rawFile.name, {
+ type: rawFile.type
+ });
+ }
+ for ( const p in rawFile) {
+ if (rawFile. hasOwnProperty (p)) {
+ processedFile[p] = rawFile[p];
+ }
+ }
+ this . post (processedFile);
+ } else {
+ this . post (rawFile);
+ }
+ }, () => {
+ this . onRemove ( null , rawFile);
+ });
+ } else if (before !== false ) {
+ this . post (rawFile);
+ } else {
+ this . onRemove ( null , rawFile);
+ }
+}
uploadFiles (files) {
+ if ( this .limit && this .fileList. length + files. length > this .limit) {
+ this .onExceed && this . onExceed (files, this .fileList);
+ return ;
+ }
+
+ let postFiles = Array . prototype .slice. call (files);
+ if ( ! this .multiple) { postFiles = postFiles. slice ( 0 , 1 ); }
+
+ if (postFiles. length === 0 ) { return ; }
+
+ postFiles. forEach ( rawFile => {
+ this . onStart (rawFile);
+ if ( this .autoUpload) this . upload (rawFile);
+ });
+},
+upload (rawFile) {
+ this .$refs.input.value = null ;
+
+ if ( ! this .beforeUpload) {
+ return this . post (rawFile);
+ }
+
+ const before = this . beforeUpload (rawFile);
+ if (before && before.then) {
+ before. then ( processedFile => {
+ const fileType = Object . prototype .toString. call (processedFile);
+
+ if (fileType === '[object File]' || fileType === '[object Blob]' ) {
+ if (fileType === '[object Blob]' ) {
+ processedFile = new File ([processedFile], rawFile.name, {
+ type: rawFile.type
+ });
+ }
+ for ( const p in rawFile) {
+ if (rawFile. hasOwnProperty (p)) {
+ processedFile[p] = rawFile[p];
+ }
+ }
+ this . post (processedFile);
+ } else {
+ this . post (rawFile);
+ }
+ }, () => {
+ this . onRemove ( null , rawFile);
+ });
+ } else if (before !== false ) {
+ this . post (rawFile);
+ } else {
+ this . onRemove ( null , rawFile);
+ }
+}
首先判断是否超过限制,如果没有则会遍历files调用onStart函数,onStart函数是在index.vue中定义的,做了三件事 基于原始file构建新的file对象,基于当前时间增加uid,如果是图片展示模式,还会创建一个url做展示 将新的file对象push进上传文件的数组uploadFiles中 将处理过的uploadFiles传递给on-change钩子函数(文件状态改变,添加文件、上传成功和上传失败时都会被调用) js handleStart (rawFile) {
+ rawFile.uid = Date. now () + this .tempIndex ++ ;
+ let file = {
+ status: 'ready' ,
+ name: rawFile.name,
+ size: rawFile.size,
+ percentage: 0 ,
+ uid: rawFile.uid,
+ raw: rawFile
+ };
+
+ if ( this .listType === 'picture-card' || this .listType === 'picture' ) {
+ try {
+ file.url = URL . createObjectURL (rawFile);
+ } catch (err) {
+ console. error ( '[Element Error][Upload]' , err);
+ return ;
+ }
+ }
+
+ this .uploadFiles. push (file);
+ this . onChange (file, this .uploadFiles);
+}
handleStart (rawFile) {
+ rawFile.uid = Date. now () + this .tempIndex ++ ;
+ let file = {
+ status: 'ready' ,
+ name: rawFile.name,
+ size: rawFile.size,
+ percentage: 0 ,
+ uid: rawFile.uid,
+ raw: rawFile
+ };
+
+ if ( this .listType === 'picture-card' || this .listType === 'picture' ) {
+ try {
+ file.url = URL . createObjectURL (rawFile);
+ } catch (err) {
+ console. error ( '[Element Error][Upload]' , err);
+ return ;
+ }
+ }
+
+ this .uploadFiles. push (file);
+ this . onChange (file, this .uploadFiles);
+}
onStart执行后会判断是否开启了自动上传,如果开启则会调用upload函数 upload中会调用判断是否定义了beforeUpload事件,如果没有直接调用post函数上传 如果定义了beforeUpload则会执行该函数,并根据该方法返回的结果决定如何处理文件 如果 beforeUpload 返回一个 Promise,则等待 Promise 执行完毕,并获取其返回值 processedFile。如果 processedFile 是一个 File 或 Blob 类型,则使用它来替换原始的文件并保留原始文件的属性,最后调用 post 方法上传替换后的文件;否则,仍然使用原始的文件上传。 如果 beforeUpload返回 false,则直接调用 onRemove 方法移除文件。 如果 beforeUpload返回其他非 false 的值,则直接上传原始文件。 js upload (rawFile) {
+ this .$refs.input.value = null ;
+
+ if ( ! this .beforeUpload) {
+ return this . post (rawFile);
+ }
+
+ const before = this . beforeUpload (rawFile);
+ if (before && before.then) {
+ before. then ( processedFile => {
+ const fileType = Object . prototype .toString. call (processedFile);
+
+ if (fileType === '[object File]' || fileType === '[object Blob]' ) {
+ if (fileType === '[object Blob]' ) {
+ processedFile = new File ([processedFile], rawFile.name, {
+ type: rawFile.type
+ });
+ }
+ for ( const p in rawFile) {
+ if (rawFile. hasOwnProperty (p)) {
+ processedFile[p] = rawFile[p];
+ }
+ }
+ this . post (processedFile);
+ } else {
+ this . post (rawFile);
+ }
+ }, () => {
+ this . onRemove ( null , rawFile);
+ });
+ } else if (before !== false ) {
+ this . post (rawFile);
+ } else {
+ this . onRemove ( null , rawFile);
+ }
+}
upload (rawFile) {
+ this .$refs.input.value = null ;
+
+ if ( ! this .beforeUpload) {
+ return this . post (rawFile);
+ }
+
+ const before = this . beforeUpload (rawFile);
+ if (before && before.then) {
+ before. then ( processedFile => {
+ const fileType = Object . prototype .toString. call (processedFile);
+
+ if (fileType === '[object File]' || fileType === '[object Blob]' ) {
+ if (fileType === '[object Blob]' ) {
+ processedFile = new File ([processedFile], rawFile.name, {
+ type: rawFile.type
+ });
+ }
+ for ( const p in rawFile) {
+ if (rawFile. hasOwnProperty (p)) {
+ processedFile[p] = rawFile[p];
+ }
+ }
+ this . post (processedFile);
+ } else {
+ this . post (rawFile);
+ }
+ }, () => {
+ this . onRemove ( null , rawFile);
+ });
+ } else if (before !== false ) {
+ this . post (rawFile);
+ } else {
+ this . onRemove ( null , rawFile);
+ }
+}
post函数中的上传方法是httpRequest(options),我们可以使用el-upload标签的http-request替换成自己的实现 js post (rawFile) {
+ const { uid } = rawFile;
+ const options = {
+ headers: this .headers,
+ withCredentials: this .withCredentials,
+ file: rawFile,
+ data: this .data,
+ filename: this .name,
+ action: this .action,
+ onProgress : e => {
+ this . onProgress (e, rawFile);
+ },
+ onSuccess : res => {
+ this . onSuccess (res, rawFile);
+ delete this .reqs[uid];
+ },
+ onError : err => {
+ this . onError (err, rawFile);
+ delete this .reqs[uid];
+ }
+ };
+ const req = this . httpRequest (options);
+ this .reqs[uid] = req;
+ if (req && req.then) {
+ req. then (options.onSuccess, options.onError);
+ }
+},
+handleClick () {
+ if ( ! this .disabled) {
+ this .$refs.input.value = null ;
+ this .$refs.input. click ();
+ }
+},
+handleKeydown (e) {
+ if (e.target !== e.currentTarget) return ;
+ if (e.keyCode === 13 || e.keyCode === 32 ) {
+ this . handleClick ();
+ }
+}
post (rawFile) {
+ const { uid } = rawFile;
+ const options = {
+ headers: this .headers,
+ withCredentials: this .withCredentials,
+ file: rawFile,
+ data: this .data,
+ filename: this .name,
+ action: this .action,
+ onProgress : e => {
+ this . onProgress (e, rawFile);
+ },
+ onSuccess : res => {
+ this . onSuccess (res, rawFile);
+ delete this .reqs[uid];
+ },
+ onError : err => {
+ this . onError (err, rawFile);
+ delete this .reqs[uid];
+ }
+ };
+ const req = this . httpRequest (options);
+ this .reqs[uid] = req;
+ if (req && req.then) {
+ req. then (options.onSuccess, options.onError);
+ }
+},
+handleClick () {
+ if ( ! this .disabled) {
+ this .$refs.input.value = null ;
+ this .$refs.input. click ();
+ }
+},
+handleKeydown (e) {
+ if (e.target !== e.currentTarget) return ;
+ if (e.keyCode === 13 || e.keyCode === 32 ) {
+ this . handleClick ();
+ }
+}
结果 上面分析的是如果没有超过limit限制的情况,而我要实现的就是在超过限制之后依然能够自动上传。
回归到代码,uploadFiles函数在执行了onExceed钩子之后直接就return了,所以下面一直到上传的流程都需要我们自己去调用
js uploadFiles (files) {
+ if ( this .limit && this .fileList. length + files. length > this .limit) {
+ this .onExceed && this . onExceed (files, this .fileList);
+ return ;
+ }
+ //...
+}
uploadFiles (files) {
+ if ( this .limit && this .fileList. length + files. length > this .limit) {
+ this .onExceed && this . onExceed (files, this .fileList);
+ return ;
+ }
+ //...
+}
在回顾下我的目标:限制上传文件个数为一个、超出数量后后者覆盖前者、自动上传且自行实现上传文件请求
所以:终极目标是执行完onExceed后能调用上传的函数,el-upload暴露给我们的函数是submit()
js // index.vue
+submit () {
+ this .uploadFiles
+ . filter ( file => file.status === 'ready' )
+ . forEach ( file => {
+ this .$refs[ 'upload-inner' ]. upload (file.raw);
+ });
+}
// index.vue
+submit () {
+ this .uploadFiles
+ . filter ( file => file.status === 'ready' )
+ . forEach ( file => {
+ this .$refs[ 'upload-inner' ]. upload (file.raw);
+ });
+}
可以看到这个函数是将uploadFiles中的文件都调用了一次upload,所以我们要在onExceed函数中要做的事:
在函数中把原有文件清除掉,然后替换为最新的文件 el-upload暴露的清除文件的方法: clearFiles js clearFiles () {
+ this .uploadFiles = [];
+}
clearFiles () {
+ this .uploadFiles = [];
+}
把最新的文件push到uploadFiles中,这里就要用到上面分析到的handleStart方法,而且这个方法中还会调用onChange钩子 文件已经push到uploadFiles中,所以只需要调用submit()函数即可 大致代码 vue < template >
+ < el-upload
+ action = "#"
+ ref = "upload"
+ v-model:file-list = "fileList"
+ :limit = "Number(1)"
+ :on-exceed = "handleExceed"
+ :http-request = "handleUpload"
+ list-type = "picture-card"
+ :auto-upload = "true" >
+ </ el-upload >
+</ template >
+
+< script setup lang = "ts" >
+import type { UploadProps, UploadInstance, UploadRawFile } from 'element-plus'
+const upload = ref < UploadInstance >()
+/**
+ * 超过限制后调用的方法
+ * @param files
+ */
+const handleExceed : UploadProps [ 'onExceed' ] = ( files ) => {
+ // 清除所有文件
+ upload.value ! . clearFiles ()
+ const file = files[ 0 ] as UploadRawFile
+ file.uid = genFileId ()
+ /**
+ * 调用handleStart把file push到uploadFiles中 -> handleStart会调用onChange方法
+ */
+ upload.value ! . handleStart (file)
+ upload.value ! . submit ()
+}
+</ script >
< template >
+ < el-upload
+ action = "#"
+ ref = "upload"
+ v-model:file-list = "fileList"
+ :limit = "Number(1)"
+ :on-exceed = "handleExceed"
+ :http-request = "handleUpload"
+ list-type = "picture-card"
+ :auto-upload = "true" >
+ </ el-upload >
+</ template >
+
+< script setup lang = "ts" >
+import type { UploadProps, UploadInstance, UploadRawFile } from 'element-plus'
+const upload = ref < UploadInstance >()
+/**
+ * 超过限制后调用的方法
+ * @param files
+ */
+const handleExceed : UploadProps [ 'onExceed' ] = ( files ) => {
+ // 清除所有文件
+ upload.value ! . clearFiles ()
+ const file = files[ 0 ] as UploadRawFile
+ file.uid = genFileId ()
+ /**
+ * 调用handleStart把file push到uploadFiles中 -> handleStart会调用onChange方法
+ */
+ upload.value ! . handleStart (file)
+ upload.value ! . submit ()
+}
+</ script >
`,30),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const u=s(p,[["render",t]]);export{C as __pageData,u as default};
diff --git a/assets/frontend_others_elementplus-el-upload.md.90eed779.lean.js b/assets/frontend_others_elementplus-el-upload.md.90eed779.lean.js
new file mode 100644
index 000000000..57b316d13
--- /dev/null
+++ b/assets/frontend_others_elementplus-el-upload.md.90eed779.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"ElementPlus el-upload源码分析","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/elementplus-el-upload.md","filePath":"frontend/others/elementplus-el-upload.md","lastUpdated":1694368780000}'),p={name:"frontend/others/elementplus-el-upload.md"},o=l("",30),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const u=s(p,[["render",t]]);export{C as __pageData,u as default};
diff --git a/assets/frontend_others_hackingwithswift-1.md.4aa127cc.js b/assets/frontend_others_hackingwithswift-1.md.4aa127cc.js
new file mode 100644
index 000000000..a6dcd3ca1
--- /dev/null
+++ b/assets/frontend_others_hackingwithswift-1.md.4aa127cc.js
@@ -0,0 +1,853 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"HackingWithSwift-1","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/hackingwithswift-1.md","filePath":"frontend/others/hackingwithswift-1.md","lastUpdated":1694368780000}'),p={name:"frontend/others/hackingwithswift-1.md"},o=l(`HackingWithSwift-1 https://www.youtube.com/playlist?list=PLuoeXyslFTuZRi4q4VT6lZKxYbr7so1Mr
WeSplitApp 核心代码 swift struct ContentView : View {
+ @State private var checkAmount = 0.0
+ @State private var numberOfPeople = 0
+ @State private var tipPercentage = 20
+ @FocusState private var amountIsFocused: Bool
+
+ private var totalPerPerson: Double {
+ let peopleCount = Double (numberOfPeople + 2 )
+ let tipSelection = Double (tipPercentage)
+ return checkAmount * tipSelection / peopleCount / 100
+ }
+
+ let tipPercentages = [ 10 , 15 , 20 , 25 , 0 ]
+
+ var body: some View {
+ NavigationView {
+ Form {
+ Section ( "" ) {
+ TextField ( "Amount" , value : $checkAmount, format : . currency ( code : Locale.current.currencyCode ?? "USD" ))
+ . keyboardType (.decimalPad)
+ . focused ($amountIsFocused)
+
+ Picker ( "Number of people" , selection : $numberOfPeople) {
+ ForEach ( 2 ..< 100 ) {
+ Text ( " \\( $0 ) people" )
+ }
+ }
+ }
+
+ Section {
+ Picker ( "Tip percentage" , selection : $tipPercentage) {
+ ForEach (tipPercentages, id : \\. self ) {
+ Text ( $0 , format : .percent)
+ }
+ }
+ . pickerStyle (.segmented)
+ }header : {
+ Text ( "How much tip do you want to leave" )
+ }
+
+ Section {
+ Text (totalPerPerson, format : . currency ( code : Locale.current.currencyCode ?? "USD" ))
+ }
+ }
+ . navigationTitle ( "WeSplit" )
+ .toolbar {
+ ToolbarItemGroup ( placement : .keyboard) {
+ Spacer ()
+ Button ( "Done" ) {
+ amountIsFocused = false
+ }
+ }
+ }
+ }
+ }
+}
struct ContentView : View {
+ @State private var checkAmount = 0.0
+ @State private var numberOfPeople = 0
+ @State private var tipPercentage = 20
+ @FocusState private var amountIsFocused: Bool
+
+ private var totalPerPerson: Double {
+ let peopleCount = Double (numberOfPeople + 2 )
+ let tipSelection = Double (tipPercentage)
+ return checkAmount * tipSelection / peopleCount / 100
+ }
+
+ let tipPercentages = [ 10 , 15 , 20 , 25 , 0 ]
+
+ var body: some View {
+ NavigationView {
+ Form {
+ Section ( "" ) {
+ TextField ( "Amount" , value : $checkAmount, format : . currency ( code : Locale.current.currencyCode ?? "USD" ))
+ . keyboardType (.decimalPad)
+ . focused ($amountIsFocused)
+
+ Picker ( "Number of people" , selection : $numberOfPeople) {
+ ForEach ( 2 ..< 100 ) {
+ Text ( " \\( $0 ) people" )
+ }
+ }
+ }
+
+ Section {
+ Picker ( "Tip percentage" , selection : $tipPercentage) {
+ ForEach (tipPercentages, id : \\. self ) {
+ Text ( $0 , format : .percent)
+ }
+ }
+ . pickerStyle (.segmented)
+ }header : {
+ Text ( "How much tip do you want to leave" )
+ }
+
+ Section {
+ Text (totalPerPerson, format : . currency ( code : Locale.current.currencyCode ?? "USD" ))
+ }
+ }
+ . navigationTitle ( "WeSplit" )
+ .toolbar {
+ ToolbarItemGroup ( placement : .keyboard) {
+ Spacer ()
+ Button ( "Done" ) {
+ amountIsFocused = false
+ }
+ }
+ }
+ }
+ }
+}
知识点 效果
GuessTheFlag 核心代码 swift struct ContentView : View {
+ @State private var showScores = false
+ @State private var scoreTitle = ""
+
+ @State private var scores = 0
+
+ @State private var countries = [ "Estonia" , "France" , "Germany" , "Ireland" , "Italy" , "Nigeria" ,
+ "Poland" , "Russia" , "Spain" , "UK" , "US" ]. shuffled ()
+ @State var correctAnswer = Int . random ( in : 0 ... 2 )
+
+ var body: some View {
+ ZStack {
+ //AngularGradient(gradient: Gradient(colors: [.red, .yellow, .green,.blue, .purple,.red]), center: .center)
+ RadialGradient ( stops : [
+ . init ( color : Color ( red : 0.1 , green : 0.2 , blue : 0.45 ), location : 0.3 ),
+ . init ( color : Color ( red : 0.76 , green : 0.15 , blue : 0.26 ), location : 0.3 )
+ ], center : .top, startRadius : 200 , endRadius : 700 )
+ . ignoresSafeArea ()
+
+ VStack {
+
+ Spacer ()
+
+ Text ( "Guess the flag" )
+ . font (.largeTitle. weight (.bold))
+ . foregroundColor (.white)
+
+ VStack ( spacing : 15 ) {
+ VStack{
+ Text ( "Tap the flag of" )
+ . foregroundStyle (.secondary)
+ . font (.subheadline. weight (.heavy))
+ Text (countries[correctAnswer])
+ . font (.largeTitle. weight (.semibold))
+ }
+
+ ForEach ( 0 ..< 3 ) {number in
+ Button {
+ flagTapper (number)
+ }label : {
+ Image (countries[number])
+ . renderingMode (.original)
+ . clipShape ( Capsule ())
+ }
+ }
+ }
+ . frame ( maxWidth : . infinity )
+ . padding (.vertical, 20 )
+ . background (.regularMaterial)
+ . clipShape ( RoundedRectangle ( cornerRadius : 20 ))
+
+ Spacer ()
+
+ Text ( "Score \\(scores) " )
+ . foregroundColor (.white)
+ . font (.title. bold ())
+ Spacer ()
+ }
+ . padding ()
+ }
+ . alert (scoreTitle, isPresented : $showScores) {
+ Button ( "Continue" , action : askQuestion)
+ } message : {
+ Text ( "Your score is \\(scores) " )
+ }
+ }
+ func flagTapper ( _ number: Int ) {
+ if number == correctAnswer {
+ scoreTitle = "Correct"
+ scores += 10
+ } else {
+ scoreTitle = "Wrong"
+ }
+ showScores = true
+ }
+
+ func askQuestion () {
+
+ countries. shuffle ()
+ correctAnswer = Int . random ( in : 0 ... 2 )
+ }
+}
struct ContentView : View {
+ @State private var showScores = false
+ @State private var scoreTitle = ""
+
+ @State private var scores = 0
+
+ @State private var countries = [ "Estonia" , "France" , "Germany" , "Ireland" , "Italy" , "Nigeria" ,
+ "Poland" , "Russia" , "Spain" , "UK" , "US" ]. shuffled ()
+ @State var correctAnswer = Int . random ( in : 0 ... 2 )
+
+ var body: some View {
+ ZStack {
+ //AngularGradient(gradient: Gradient(colors: [.red, .yellow, .green,.blue, .purple,.red]), center: .center)
+ RadialGradient ( stops : [
+ . init ( color : Color ( red : 0.1 , green : 0.2 , blue : 0.45 ), location : 0.3 ),
+ . init ( color : Color ( red : 0.76 , green : 0.15 , blue : 0.26 ), location : 0.3 )
+ ], center : .top, startRadius : 200 , endRadius : 700 )
+ . ignoresSafeArea ()
+
+ VStack {
+
+ Spacer ()
+
+ Text ( "Guess the flag" )
+ . font (.largeTitle. weight (.bold))
+ . foregroundColor (.white)
+
+ VStack ( spacing : 15 ) {
+ VStack{
+ Text ( "Tap the flag of" )
+ . foregroundStyle (.secondary)
+ . font (.subheadline. weight (.heavy))
+ Text (countries[correctAnswer])
+ . font (.largeTitle. weight (.semibold))
+ }
+
+ ForEach ( 0 ..< 3 ) {number in
+ Button {
+ flagTapper (number)
+ }label : {
+ Image (countries[number])
+ . renderingMode (.original)
+ . clipShape ( Capsule ())
+ }
+ }
+ }
+ . frame ( maxWidth : . infinity )
+ . padding (.vertical, 20 )
+ . background (.regularMaterial)
+ . clipShape ( RoundedRectangle ( cornerRadius : 20 ))
+
+ Spacer ()
+
+ Text ( "Score \\(scores) " )
+ . foregroundColor (.white)
+ . font (.title. bold ())
+ Spacer ()
+ }
+ . padding ()
+ }
+ . alert (scoreTitle, isPresented : $showScores) {
+ Button ( "Continue" , action : askQuestion)
+ } message : {
+ Text ( "Your score is \\(scores) " )
+ }
+ }
+ func flagTapper ( _ number: Int ) {
+ if number == correctAnswer {
+ scoreTitle = "Correct"
+ scores += 10
+ } else {
+ scoreTitle = "Wrong"
+ }
+ showScores = true
+ }
+
+ func askQuestion () {
+
+ countries. shuffle ()
+ correctAnswer = Int . random ( in : 0 ... 2 )
+ }
+}
知识点 效果
BetterRest 核心代码 swift import CoreML
+import SwiftUI
+
+struct ContentView : View {
+ @State private var wakeUp = defaultWakeTime
+ @State private var sleepAmount = 8.0
+ @State private var coffeeAmount = 1
+
+ @State private var alertTitle = ""
+ @State private var alertMessage = ""
+ @State private var showingAlert = false
+
+ static var defaultWakeTime: Date {
+ var components = DateComponents ()
+ components.hour = 7
+ components.minute = 0
+ return Calendar.current. date ( from : components) ?? Date.now
+ }
+
+ var body: some View {
+ NavigationView {
+ Form {
+ VStack ( alignment : .leading, spacing : 0 ) {
+ Text ( "When do you want to wake up?" )
+ . font (.headline)
+ DatePicker ( "Please enter atime" , selection : $wakeUp, displayedComponents : .hourAndMinute)
+ . labelsHidden ()
+ }
+
+
+ VStack ( alignment : .leading, spacing : 0 ) {
+ Text ( "Desired amount of sleep" )
+ . font (.headline)
+ Stepper ( " \\(sleepAmount. formatted ()) hours" , value : $sleepAmount, in : 4 ... 12 , step : 0.25 )
+ }
+
+
+ VStack ( alignment : .leading, spacing : 0 ) {
+ Text ( "Daily coffee intake" )
+ Stepper (coffeeAmount == 1 ? "1 cup" : " \\(coffeeAmount) cups" , value : $coffeeAmount, in : 0 ... 20 )
+ }
+
+ }
+ . navigationTitle ( "BetterRest" )
+ .toolbar {
+ Button ( "Calculate" , action : calculateBedtime)
+ }
+ . alert (alertTitle, isPresented : $showingAlert) {
+ Button ( "OK" ) {}
+ }message : {
+ Text (alertMessage)
+ }
+ }
+
+ }
+
+ func calculateBedtime () {
+ do {
+ let config = MLModelConfiguration ()
+ let model = try SleepCalculator ( configuration : config)
+ let components = Calendar.current. dateComponents ([.hour, .minute], from : wakeUp)
+ let hour = (components.hour ?? 0 ) * 60 * 60
+ let minute = (components.minute ?? 0 ) * 60
+
+ let prediction = try model. prediction ( wake : Double (hour + minute), estimatedSleep : sleepAmount, coffee : Double (coffeeAmount))
+
+ let sleepTime = wakeUp - prediction.actualSleep
+ alertTitle = "Your ideal bedtime is ..."
+ alertMessage = sleepTime. formatted ( date : .omitted, time : .shortened)
+ } catch {
+ alertTitle = "Error"
+ alertMessage = "Sorry, there was a problem calculating your bedtime."
+ }
+
+ showingAlert = true
+ }
+}
import CoreML
+import SwiftUI
+
+struct ContentView : View {
+ @State private var wakeUp = defaultWakeTime
+ @State private var sleepAmount = 8.0
+ @State private var coffeeAmount = 1
+
+ @State private var alertTitle = ""
+ @State private var alertMessage = ""
+ @State private var showingAlert = false
+
+ static var defaultWakeTime: Date {
+ var components = DateComponents ()
+ components.hour = 7
+ components.minute = 0
+ return Calendar.current. date ( from : components) ?? Date.now
+ }
+
+ var body: some View {
+ NavigationView {
+ Form {
+ VStack ( alignment : .leading, spacing : 0 ) {
+ Text ( "When do you want to wake up?" )
+ . font (.headline)
+ DatePicker ( "Please enter atime" , selection : $wakeUp, displayedComponents : .hourAndMinute)
+ . labelsHidden ()
+ }
+
+
+ VStack ( alignment : .leading, spacing : 0 ) {
+ Text ( "Desired amount of sleep" )
+ . font (.headline)
+ Stepper ( " \\(sleepAmount. formatted ()) hours" , value : $sleepAmount, in : 4 ... 12 , step : 0.25 )
+ }
+
+
+ VStack ( alignment : .leading, spacing : 0 ) {
+ Text ( "Daily coffee intake" )
+ Stepper (coffeeAmount == 1 ? "1 cup" : " \\(coffeeAmount) cups" , value : $coffeeAmount, in : 0 ... 20 )
+ }
+
+ }
+ . navigationTitle ( "BetterRest" )
+ .toolbar {
+ Button ( "Calculate" , action : calculateBedtime)
+ }
+ . alert (alertTitle, isPresented : $showingAlert) {
+ Button ( "OK" ) {}
+ }message : {
+ Text (alertMessage)
+ }
+ }
+
+ }
+
+ func calculateBedtime () {
+ do {
+ let config = MLModelConfiguration ()
+ let model = try SleepCalculator ( configuration : config)
+ let components = Calendar.current. dateComponents ([.hour, .minute], from : wakeUp)
+ let hour = (components.hour ?? 0 ) * 60 * 60
+ let minute = (components.minute ?? 0 ) * 60
+
+ let prediction = try model. prediction ( wake : Double (hour + minute), estimatedSleep : sleepAmount, coffee : Double (coffeeAmount))
+
+ let sleepTime = wakeUp - prediction.actualSleep
+ alertTitle = "Your ideal bedtime is ..."
+ alertMessage = sleepTime. formatted ( date : .omitted, time : .shortened)
+ } catch {
+ alertTitle = "Error"
+ alertMessage = "Sorry, there was a problem calculating your bedtime."
+ }
+
+ showingAlert = true
+ }
+}
知识点 WordScramble 核心代码 swift struct ContentView : View {
+ @State private var usedWords = [ String ]()
+ @State private var rootWord = ""
+ @State private var newWord = ""
+
+ @State private var errorTitle = ""
+ @State private var errorMessage = ""
+ @State private var showingError = false
+
+ var body: some View {
+ NavigationView {
+ List {
+ Section {
+ TextField ( "enter your word" , text : $newWord)
+ . autocapitalization (. none )
+ }
+
+ Section {
+ ForEach (usedWords, id : \\. self ) { word in
+ HStack{
+ Image ( systemName : " \\(word. count ) .circle" )
+ Text (word)
+ }
+ }
+ }
+ }
+ . navigationTitle (rootWord)
+ . onSubmit (addNewWord)
+ . onAppear ( perform : startGame)
+ . alert (errorTitle, isPresented : $showingError) {
+ Button ( "OK" , role : .cancel) {}
+ }message : {
+ Text (errorMessage)
+ }
+ }
+ }
+
+ func addNewWord () {
+ let answer = newWord. lowercased (). trimmingCharacters ( in : .whitespacesAndNewlines)
+ guard answer. count > 0 else { return }
+
+ guard isOriginal ( word : answer) else {
+ wordError ( title : "Word used already" , message : "Be more original" )
+ return
+ }
+
+ guard isPossible ( word : answer) else {
+ wordError ( title : "word not possible" , message : "you can't spell that word from ' \\(rootWord) '" )
+ return
+ }
+
+ guard isReal ( word : answer) else {
+ wordError ( title : "word not recognized" , message : "you can't just make them up, you know!" )
+ return
+ }
+
+
+ withAnimation{
+ usedWords. insert (answer, at : 0 )
+ }
+
+ newWord = ""
+ }
+
+ func startGame () {
+ if let fileURL = Bundle.main. url ( forResource : "start" , withExtension : "txt" ) {
+ if let startWords = try? String ( contentsOf : fileURL) {
+ let allWords = startWords. components ( separatedBy : " \\n " )
+ rootWord = allWords. randomElement () ?? "silkworm"
+ return
+ }
+ }
+ fatalError ( "Could not load start.txt from bundle" )
+ }
+
+ func isOriginal ( word : String ) -> Bool {
+ ! usedWords. contains (word)
+ }
+
+ func isPossible ( word : String ) -> Bool {
+ var tempWord = rootWord
+
+ for letter in word {
+ if let pos = tempWord. firstIndex ( of : letter) {
+ tempWord. remove ( at : pos)
+ } else {
+ return false
+ }
+ }
+ return true
+ }
+
+ func isReal ( word : String ) -> Bool {
+ let checker = UITextChecker ()
+ let range = NSRange ( location : 0 , length : word. utf16 . count )
+ let misspelledRange = checker. rangeOfMisspelledWord ( in : word, range : range, startingAt : 0 , wrap : false , language : "en" )
+
+ return misspelledRange.location == NSNotFound
+ }
+
+ func wordError ( title : String , message : String ) {
+ errorTitle = title
+ errorMessage = message
+ showingError = true
+ }
+}
struct ContentView : View {
+ @State private var usedWords = [ String ]()
+ @State private var rootWord = ""
+ @State private var newWord = ""
+
+ @State private var errorTitle = ""
+ @State private var errorMessage = ""
+ @State private var showingError = false
+
+ var body: some View {
+ NavigationView {
+ List {
+ Section {
+ TextField ( "enter your word" , text : $newWord)
+ . autocapitalization (. none )
+ }
+
+ Section {
+ ForEach (usedWords, id : \\. self ) { word in
+ HStack{
+ Image ( systemName : " \\(word. count ) .circle" )
+ Text (word)
+ }
+ }
+ }
+ }
+ . navigationTitle (rootWord)
+ . onSubmit (addNewWord)
+ . onAppear ( perform : startGame)
+ . alert (errorTitle, isPresented : $showingError) {
+ Button ( "OK" , role : .cancel) {}
+ }message : {
+ Text (errorMessage)
+ }
+ }
+ }
+
+ func addNewWord () {
+ let answer = newWord. lowercased (). trimmingCharacters ( in : .whitespacesAndNewlines)
+ guard answer. count > 0 else { return }
+
+ guard isOriginal ( word : answer) else {
+ wordError ( title : "Word used already" , message : "Be more original" )
+ return
+ }
+
+ guard isPossible ( word : answer) else {
+ wordError ( title : "word not possible" , message : "you can't spell that word from ' \\(rootWord) '" )
+ return
+ }
+
+ guard isReal ( word : answer) else {
+ wordError ( title : "word not recognized" , message : "you can't just make them up, you know!" )
+ return
+ }
+
+
+ withAnimation{
+ usedWords. insert (answer, at : 0 )
+ }
+
+ newWord = ""
+ }
+
+ func startGame () {
+ if let fileURL = Bundle.main. url ( forResource : "start" , withExtension : "txt" ) {
+ if let startWords = try? String ( contentsOf : fileURL) {
+ let allWords = startWords. components ( separatedBy : " \\n " )
+ rootWord = allWords. randomElement () ?? "silkworm"
+ return
+ }
+ }
+ fatalError ( "Could not load start.txt from bundle" )
+ }
+
+ func isOriginal ( word : String ) -> Bool {
+ ! usedWords. contains (word)
+ }
+
+ func isPossible ( word : String ) -> Bool {
+ var tempWord = rootWord
+
+ for letter in word {
+ if let pos = tempWord. firstIndex ( of : letter) {
+ tempWord. remove ( at : pos)
+ } else {
+ return false
+ }
+ }
+ return true
+ }
+
+ func isReal ( word : String ) -> Bool {
+ let checker = UITextChecker ()
+ let range = NSRange ( location : 0 , length : word. utf16 . count )
+ let misspelledRange = checker. rangeOfMisspelledWord ( in : word, range : range, startingAt : 0 , wrap : false , language : "en" )
+
+ return misspelledRange.location == NSNotFound
+ }
+
+ func wordError ( title : String , message : String ) {
+ errorTitle = title
+ errorMessage = message
+ showingError = true
+ }
+}
知识点 效果
iExpense 核心代码 Expenses(ViewModel)
swift class Expenses : ObservableObject {
+ @Published var items = [ExpenseItem]() {
+ didSet {
+ let encoder = JSONEncoder ()
+
+ if let encoded = try? encoder. encode (items) {
+ UserDefaults.standard. set (encoded, forKey : "Items" )
+ }
+ }
+ }
+
+ init () {
+ if let itemArr = UserDefaults.standard. data ( forKey : "Items" ) {
+ let decoder = JSONDecoder ()
+ if let decodedItems = try? decoder. decode ([ExpenseItem]. self , from : itemArr) {
+ items = decodedItems
+ return
+ }
+ }
+ items = []
+ }
+}
class Expenses : ObservableObject {
+ @Published var items = [ExpenseItem]() {
+ didSet {
+ let encoder = JSONEncoder ()
+
+ if let encoded = try? encoder. encode (items) {
+ UserDefaults.standard. set (encoded, forKey : "Items" )
+ }
+ }
+ }
+
+ init () {
+ if let itemArr = UserDefaults.standard. data ( forKey : "Items" ) {
+ let decoder = JSONDecoder ()
+ if let decodedItems = try? decoder. decode ([ExpenseItem]. self , from : itemArr) {
+ items = decodedItems
+ return
+ }
+ }
+ items = []
+ }
+}
ExpenseItem(Model)
swift struct ExpenseItem : Identifiable, Codable {
+ let name: String
+ let type: String
+ let amount: Double
+ let id = UUID ()
+}
struct ExpenseItem : Identifiable, Codable {
+ let name: String
+ let type: String
+ let amount: Double
+ let id = UUID ()
+}
AddView
swift struct AddView : View {
+ @ObservedObject var expenses: Expenses
+
+ @State private var name = ""
+ @State private var type = "Personal"
+ @State private var amount = 0.0
+ @Environment (\\.dismiss) var dismiss
+
+ let types = [ "Business" , "Personal" ]
+
+
+ var body: some View {
+ NavigationView {
+ Form {
+ TextField ( "Name" , text : $name)
+
+ Picker ( "Type" , selection : $type) {
+ ForEach (types, id : \\. self ) {
+ Text ( $0 )
+ }
+ }
+
+ TextField ( "Amount" , value : $amount, format : . currency ( code : Locale.current.currencyCode ?? "CNY" ))
+ . keyboardType (.decimalPad)
+ }
+ . navigationTitle ( "Add new expense" )
+ .toolbar {
+ Button ( "Save" ) {
+ let item = ExpenseItem ( name : name, type : type, amount : amount)
+ expenses.items. append (item)
+ dismiss ()
+ }
+ }
+ }
+ }
+}
struct AddView : View {
+ @ObservedObject var expenses: Expenses
+
+ @State private var name = ""
+ @State private var type = "Personal"
+ @State private var amount = 0.0
+ @Environment (\\.dismiss) var dismiss
+
+ let types = [ "Business" , "Personal" ]
+
+
+ var body: some View {
+ NavigationView {
+ Form {
+ TextField ( "Name" , text : $name)
+
+ Picker ( "Type" , selection : $type) {
+ ForEach (types, id : \\. self ) {
+ Text ( $0 )
+ }
+ }
+
+ TextField ( "Amount" , value : $amount, format : . currency ( code : Locale.current.currencyCode ?? "CNY" ))
+ . keyboardType (.decimalPad)
+ }
+ . navigationTitle ( "Add new expense" )
+ .toolbar {
+ Button ( "Save" ) {
+ let item = ExpenseItem ( name : name, type : type, amount : amount)
+ expenses.items. append (item)
+ dismiss ()
+ }
+ }
+ }
+ }
+}
ContentView
swift struct ContentView : View {
+ @StateObject var expenses = Expenses ()
+ @State private var showingAddExpense = false
+
+ var body: some View {
+ NavigationView {
+ List {
+ ForEach (expenses.items) { item in
+ HStack {
+ VStack ( alignment : .leading) {
+ Text (item.name)
+ . font (.headline)
+ Text (item.type)
+ . font (.subheadline)
+ }
+ Spacer ()
+ Text (item.amount, format : . currency ( code : Locale.current.currencyCode ?? "CNY" ))
+ }
+ }
+ . onDelete ( perform : removeItems)
+ }
+ . navigationTitle ( "iExpense" )
+ .toolbar {
+ Button {
+ showingAddExpense = true
+ } label : {
+ Image ( systemName : "plus" )
+ }
+ }
+ . sheet ( isPresented : $showingAddExpense) {
+ AddView ( expenses : expenses)
+
+ }
+ }
+ }
+
+ func removeItems ( at offsets: IndexSet) {
+ expenses.items. remove ( atOffsets : offsets)
+ }
+}
struct ContentView : View {
+ @StateObject var expenses = Expenses ()
+ @State private var showingAddExpense = false
+
+ var body: some View {
+ NavigationView {
+ List {
+ ForEach (expenses.items) { item in
+ HStack {
+ VStack ( alignment : .leading) {
+ Text (item.name)
+ . font (.headline)
+ Text (item.type)
+ . font (.subheadline)
+ }
+ Spacer ()
+ Text (item.amount, format : . currency ( code : Locale.current.currencyCode ?? "CNY" ))
+ }
+ }
+ . onDelete ( perform : removeItems)
+ }
+ . navigationTitle ( "iExpense" )
+ .toolbar {
+ Button {
+ showingAddExpense = true
+ } label : {
+ Image ( systemName : "plus" )
+ }
+ }
+ . sheet ( isPresented : $showingAddExpense) {
+ AddView ( expenses : expenses)
+
+ }
+ }
+ }
+
+ func removeItems ( at offsets: IndexSet) {
+ expenses.items. remove ( atOffsets : offsets)
+ }
+}
知识点 MVVM
@StateObject、@Published、ObservableObject、@ObservedObject
UserDefaults(存储少量数据,常用于存储Preference)
JSONEncoder、JSONDecoder、Codable
属性观察器(willSet、didSet) 配合 UserDefaults保存数据
onDelete修饰符
sheet修饰符(弹出新页面)
swift . sheet ( isPresented : $showingAddExpense) {
+ AddView ( expenses : expenses)
+}
. sheet ( isPresented : $showingAddExpense) {
+ AddView ( expenses : expenses)
+}
@Environment(.dismiss) var dismiss(@Environment(keyPath) 可以读取系统环境数据,dismiss用于关闭当前展示页面)
效果
`,43),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{u as __pageData,d as default};
diff --git a/assets/frontend_others_hackingwithswift-1.md.4aa127cc.lean.js b/assets/frontend_others_hackingwithswift-1.md.4aa127cc.lean.js
new file mode 100644
index 000000000..956004774
--- /dev/null
+++ b/assets/frontend_others_hackingwithswift-1.md.4aa127cc.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const u=JSON.parse('{"title":"HackingWithSwift-1","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/hackingwithswift-1.md","filePath":"frontend/others/hackingwithswift-1.md","lastUpdated":1694368780000}'),p={name:"frontend/others/hackingwithswift-1.md"},o=l("",43),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{u as __pageData,d as default};
diff --git a/assets/frontend_others_hackingwithswift-2.md.d6de0bd5.js b/assets/frontend_others_hackingwithswift-2.md.d6de0bd5.js
new file mode 100644
index 000000000..a221b7966
--- /dev/null
+++ b/assets/frontend_others_hackingwithswift-2.md.d6de0bd5.js
@@ -0,0 +1,1413 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"HackingWithSwift-2","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/hackingwithswift-2.md","filePath":"frontend/others/hackingwithswift-2.md","lastUpdated":1694368780000}'),p={name:"frontend/others/hackingwithswift-2.md"},o=l(`HackingWithSwift-2 https://www.youtube.com/playlist?list=PLuoeXyslFTuZRi4q4VT6lZKxYbr7so1Mr
Moonshot 核心代码 Bundle-Decodable
swift extension Bundle {
+ func decode < T : Decodable >( _ file: String ) -> T {
+ guard let url = self . url ( forResource : file, withExtension : nil ) else {
+ fatalError ( "Failed to locate \\(file) in bundle" )
+ }
+
+ guard let data = try? Data ( contentsOf : url) else {
+ fatalError ( "Failed to locate \\(file) in bundle" )
+ }
+
+ let decoder = JSONDecoder ()
+ let formatter = DateFormatter ()
+ formatter.dateFormat = "y-MM-dd"
+ decoder.dateDecodingStrategy = . formatted (formatter)
+
+ guard let loaded = try? decoder. decode ( T. self , from : data) else {
+ fatalError ( "Failed to locate \\(file) in bundle" )
+ }
+
+ return loaded
+ }
+}
extension Bundle {
+ func decode < T : Decodable >( _ file: String ) -> T {
+ guard let url = self . url ( forResource : file, withExtension : nil ) else {
+ fatalError ( "Failed to locate \\(file) in bundle" )
+ }
+
+ guard let data = try? Data ( contentsOf : url) else {
+ fatalError ( "Failed to locate \\(file) in bundle" )
+ }
+
+ let decoder = JSONDecoder ()
+ let formatter = DateFormatter ()
+ formatter.dateFormat = "y-MM-dd"
+ decoder.dateDecodingStrategy = . formatted (formatter)
+
+ guard let loaded = try? decoder. decode ( T. self , from : data) else {
+ fatalError ( "Failed to locate \\(file) in bundle" )
+ }
+
+ return loaded
+ }
+}
Color-Theme
swift import SwiftUI
+
+extension ShapeStyle where Self == Color {
+ static var darkBackground: Color {
+ Color ( red : 0.1 , green : 0.1 , blue : 0.2 )
+ }
+
+ static var lightBackgroud: Color {
+ Color ( red : 0.2 , green : 0.2 , blue : 0.3 )
+ }
+}
import SwiftUI
+
+extension ShapeStyle where Self == Color {
+ static var darkBackground: Color {
+ Color ( red : 0.1 , green : 0.1 , blue : 0.2 )
+ }
+
+ static var lightBackgroud: Color {
+ Color ( red : 0.2 , green : 0.2 , blue : 0.3 )
+ }
+}
Model
swift struct Astronaut : Codable, Identifiable {
+ let id: String
+ let name: String
+ let description: String
+}
+
+
+struct Mission : Codable, Identifiable {
+ struct CrewRole : Codable {
+ let name: String
+ let role: String
+ }
+
+ let id: Int
+ let launchDate: Date ?
+ let crew: [CrewRole]
+ let description: String
+
+ var displayName: String {
+ "Apollo \\(id) "
+ }
+
+ var image: String {
+ "apollo \\(id) "
+ }
+
+ var formattedLaunchDate: String {
+ launchDate ? . formatted ( date : .abbreviated, time : .omitted) ?? "N/A"
+ }
+}
struct Astronaut : Codable, Identifiable {
+ let id: String
+ let name: String
+ let description: String
+}
+
+
+struct Mission : Codable, Identifiable {
+ struct CrewRole : Codable {
+ let name: String
+ let role: String
+ }
+
+ let id: Int
+ let launchDate: Date ?
+ let crew: [CrewRole]
+ let description: String
+
+ var displayName: String {
+ "Apollo \\(id) "
+ }
+
+ var image: String {
+ "apollo \\(id) "
+ }
+
+ var formattedLaunchDate: String {
+ launchDate ? . formatted ( date : .abbreviated, time : .omitted) ?? "N/A"
+ }
+}
ContentView
swift struct ContentView : View {
+ let astronauts: [ String : Astronaut] = Bundle.main. decode ( "astronauts.json" )
+ let missions: [Mission] = Bundle.main. decode ( "missions.json" )
+
+ let columns = [
+ GridItem (. adaptive ( minimum : 150 ))
+ ]
+
+ var body: some View {
+ NavigationView {
+ ScrollView {
+ LazyVGrid ( columns : columns) {
+ ForEach (missions) { mission in
+ NavigationLink{
+ MissionView ( mission : mission, astronauts : astronauts)
+ } label : {
+ VStack{
+ Image (mission. image )
+ . resizable ()
+ . scaledToFit ()
+ . frame ( width : 100 , height : 100 )
+ VStack {
+ Text (mission.displayName)
+ . font (.headline)
+ . foregroundColor (.white)
+
+ Text (mission.formattedLaunchDate)
+ . font (.subheadline)
+ . foregroundColor (.white. opacity ( 0.5 ))
+ }
+ . padding (.vertical)
+ . frame ( maxWidth : . infinity )
+ . background (.lightBackgroud)
+ }
+ . clipShape ( RoundedRectangle ( cornerRadius : 10 ))
+ .overlay{
+ RoundedRectangle ( cornerRadius : 10 )
+ . stroke ()
+ }
+ }
+ }
+ }
+ . padding ([.horizontal, .bottom])
+ }
+ . navigationTitle ( "Moonshot" )
+ . background (.darkBackground)
+ . preferredColorScheme (.dark)
+ }
+ }
+}
struct ContentView : View {
+ let astronauts: [ String : Astronaut] = Bundle.main. decode ( "astronauts.json" )
+ let missions: [Mission] = Bundle.main. decode ( "missions.json" )
+
+ let columns = [
+ GridItem (. adaptive ( minimum : 150 ))
+ ]
+
+ var body: some View {
+ NavigationView {
+ ScrollView {
+ LazyVGrid ( columns : columns) {
+ ForEach (missions) { mission in
+ NavigationLink{
+ MissionView ( mission : mission, astronauts : astronauts)
+ } label : {
+ VStack{
+ Image (mission. image )
+ . resizable ()
+ . scaledToFit ()
+ . frame ( width : 100 , height : 100 )
+ VStack {
+ Text (mission.displayName)
+ . font (.headline)
+ . foregroundColor (.white)
+
+ Text (mission.formattedLaunchDate)
+ . font (.subheadline)
+ . foregroundColor (.white. opacity ( 0.5 ))
+ }
+ . padding (.vertical)
+ . frame ( maxWidth : . infinity )
+ . background (.lightBackgroud)
+ }
+ . clipShape ( RoundedRectangle ( cornerRadius : 10 ))
+ .overlay{
+ RoundedRectangle ( cornerRadius : 10 )
+ . stroke ()
+ }
+ }
+ }
+ }
+ . padding ([.horizontal, .bottom])
+ }
+ . navigationTitle ( "Moonshot" )
+ . background (.darkBackground)
+ . preferredColorScheme (.dark)
+ }
+ }
+}
MissionView
swift struct MissionView : View {
+ struct CrewMember {
+ let role: String
+ let astronaut: Astronaut
+ }
+
+
+ let mission: Mission
+
+ let crew: [CrewMember]
+
+ var body: some View {
+ GeometryReader { geometry in
+ ScrollView {
+ VStack {
+ Image (mission. image )
+ . resizable ()
+ . scaledToFit ()
+ . frame ( maxWidth : geometry. size .width * 0.6 )
+ . padding (.top)
+
+ VStack ( alignment : .leading) {
+ Rectangle ()
+ . frame ( height : 2 )
+ . foregroundColor (.lightBackgroud)
+ . padding (.vertical)
+
+ Text ( "Mission Highlights" )
+ . font (.title. bold ())
+ . padding (.bottom, 5 )
+
+ Text (mission. description )
+ Rectangle ()
+ . frame ( height : 2 )
+ . foregroundColor (.lightBackgroud)
+ . padding (.vertical)
+
+ Text ( "Crew" )
+ . font (.title. bold ())
+ . padding (.bottom, 5 )
+ }
+ . padding (.horizontal)
+
+
+
+
+
+ ScrollView (.horizontal, showsIndicators : false ) {
+ HStack {
+ ForEach (crew, id : \\.role) { crewMember in
+ NavigationLink {
+ AstronautView ( astronaut : crewMember.astronaut)
+ }label : {
+ HStack {
+ Image (crewMember.astronaut.id)
+ . resizable ()
+ . frame ( width : 104 , height : 72 )
+ . clipShape ( Capsule ())
+ .overlay{
+ Capsule ()
+ . strokeBorder (.white, lineWidth : 1 )
+ }
+ VStack ( alignment : .leading) {
+ Text (crewMember.astronaut.name)
+ . foregroundColor (.white)
+ . font (.headline)
+
+ Text (crewMember.role)
+ . foregroundColor (.secondary)
+
+ }
+ }
+ }
+ }
+
+ }
+ }
+ . padding (.horizontal)
+ }
+ . padding (.bottom)
+ }
+ . navigationTitle (mission.displayName)
+ . navigationBarTitleDisplayMode (.inline)
+ . background (.darkBackground)
+ }
+
+ }
+
+
+ init ( mission : Mission, astronauts : [ String : Astronaut]) {
+ self .mission = mission
+
+ self .crew = mission.crew. map { member in
+ if let astronaut = astronauts[member.name] {
+ return CrewMember ( role : member.role, astronaut : astronaut)
+ } else {
+ fatalError ( "Missing \\(member. name ) " )
+ }
+ }
+ }
+}
struct MissionView : View {
+ struct CrewMember {
+ let role: String
+ let astronaut: Astronaut
+ }
+
+
+ let mission: Mission
+
+ let crew: [CrewMember]
+
+ var body: some View {
+ GeometryReader { geometry in
+ ScrollView {
+ VStack {
+ Image (mission. image )
+ . resizable ()
+ . scaledToFit ()
+ . frame ( maxWidth : geometry. size .width * 0.6 )
+ . padding (.top)
+
+ VStack ( alignment : .leading) {
+ Rectangle ()
+ . frame ( height : 2 )
+ . foregroundColor (.lightBackgroud)
+ . padding (.vertical)
+
+ Text ( "Mission Highlights" )
+ . font (.title. bold ())
+ . padding (.bottom, 5 )
+
+ Text (mission. description )
+ Rectangle ()
+ . frame ( height : 2 )
+ . foregroundColor (.lightBackgroud)
+ . padding (.vertical)
+
+ Text ( "Crew" )
+ . font (.title. bold ())
+ . padding (.bottom, 5 )
+ }
+ . padding (.horizontal)
+
+
+
+
+
+ ScrollView (.horizontal, showsIndicators : false ) {
+ HStack {
+ ForEach (crew, id : \\.role) { crewMember in
+ NavigationLink {
+ AstronautView ( astronaut : crewMember.astronaut)
+ }label : {
+ HStack {
+ Image (crewMember.astronaut.id)
+ . resizable ()
+ . frame ( width : 104 , height : 72 )
+ . clipShape ( Capsule ())
+ .overlay{
+ Capsule ()
+ . strokeBorder (.white, lineWidth : 1 )
+ }
+ VStack ( alignment : .leading) {
+ Text (crewMember.astronaut.name)
+ . foregroundColor (.white)
+ . font (.headline)
+
+ Text (crewMember.role)
+ . foregroundColor (.secondary)
+
+ }
+ }
+ }
+ }
+
+ }
+ }
+ . padding (.horizontal)
+ }
+ . padding (.bottom)
+ }
+ . navigationTitle (mission.displayName)
+ . navigationBarTitleDisplayMode (.inline)
+ . background (.darkBackground)
+ }
+
+ }
+
+
+ init ( mission : Mission, astronauts : [ String : Astronaut]) {
+ self .mission = mission
+
+ self .crew = mission.crew. map { member in
+ if let astronaut = astronauts[member.name] {
+ return CrewMember ( role : member.role, astronaut : astronaut)
+ } else {
+ fatalError ( "Missing \\(member. name ) " )
+ }
+ }
+ }
+}
AstronautView
swift struct AstronautView : View {
+ let astronaut: Astronaut
+
+ var body: some View {
+ ScrollView {
+ VStack {
+ Image (astronaut.id)
+ . resizable ()
+ . scaledToFit ()
+
+
+ Text (astronaut. description )
+ . padding ()
+
+
+ }
+
+ }
+ . background (.darkBackground)
+ . navigationTitle (astronaut.name)
+ . navigationBarTitleDisplayMode (.inline)
+ }
+}
struct AstronautView : View {
+ let astronaut: Astronaut
+
+ var body: some View {
+ ScrollView {
+ VStack {
+ Image (astronaut.id)
+ . resizable ()
+ . scaledToFit ()
+
+
+ Text (astronaut. description )
+ . padding ()
+
+
+ }
+
+ }
+ . background (.darkBackground)
+ . navigationTitle (astronaut.name)
+ . navigationBarTitleDisplayMode (.inline)
+ }
+}
知识点 extension扩展Bubble读取静态资源文件、扩展Color用于设置背景色 LazyVGrid设置自适应的网格视图 .preferredColorScheme(.dark)设置首选项颜色 GeometryReader,可以获取父View的坐标、尺寸等 Image调整大小- resizable(), scaleToFit(),frame() 效果
CupcakeCorner 核心代码 Order
swift class Order : ObservableObject, Codable {
+
+ enum CodingKeys : CodingKey {
+ case type, quantity, extraFrosting, addSprinkles, name, streetAddress, city, zip
+ }
+
+ static let types = [ "Vanilla" , "Strawberry" , "Chocolate" , "Rainbow" ]
+
+ @Published var type = 0
+ @Published var quantity = 3
+ @Published var specialRequestEnabled = false {
+ didSet {
+ if specialRequestEnabled == false {
+ extraFrosting = false
+ addSprinkles = false
+ }
+ }
+ }
+ @Published var extraFrosting = false
+ @Published var addSprinkles = false
+
+
+ @Published var name = ""
+ @Published var streetAddress = ""
+ @Published var city = ""
+ @Published var zip = ""
+
+
+ var hasValidAddress: Bool {
+ if name. isEmpty || streetAddress. isEmpty || city. isEmpty || zip. isEmpty {
+ return false
+ }
+ return true
+ }
+
+
+ var cost: Double {
+ // $2 per cake
+ var cost = Double (quantity) * 2
+
+ // complicated cakes cost more
+ cost += Double (type) / 2
+
+ // $1/cake for extra frosting
+ if extraFrosting {
+ cost += Double (quantity)
+ }
+
+ // $0.5/cake for sprinkles
+ if addSprinkles {
+ cost += Double (quantity) / 2
+ }
+
+ return cost
+ }
+
+ init () {}
+
+
+ func encode ( to encoder: Encoder) throws {
+ var container = encoder. container ( keyedBy : CodingKeys. self )
+
+ try container. encode (type, forKey : .type)
+ try container. encode (quantity, forKey : .quantity)
+ try container. encode (extraFrosting, forKey : .extraFrosting)
+ try container. encode (addSprinkles, forKey : .addSprinkles)
+ try container. encode (name, forKey : .name)
+ try container. encode (streetAddress, forKey : .streetAddress)
+ try container. encode (city, forKey : .city)
+ try container. encode (zip, forKey : .zip)
+ }
+
+ required init ( from decoder: Decoder) throws {
+ let container = try decoder. container ( keyedBy : CodingKeys. self )
+
+ type = try container. decode ( Int . self , forKey : .type)
+ quantity = try container. decode ( Int . self , forKey : .quantity)
+ extraFrosting = try container. decode ( Bool . self , forKey : .extraFrosting)
+ addSprinkles = try container. decode ( Bool . self , forKey : .addSprinkles)
+ name = try container. decode ( String . self , forKey : .name)
+ city = try container. decode ( String . self , forKey : .city)
+ streetAddress = try container. decode ( String . self , forKey : .streetAddress)
+ zip = try container. decode ( String . self , forKey : .zip)
+ }
+}
class Order : ObservableObject, Codable {
+
+ enum CodingKeys : CodingKey {
+ case type, quantity, extraFrosting, addSprinkles, name, streetAddress, city, zip
+ }
+
+ static let types = [ "Vanilla" , "Strawberry" , "Chocolate" , "Rainbow" ]
+
+ @Published var type = 0
+ @Published var quantity = 3
+ @Published var specialRequestEnabled = false {
+ didSet {
+ if specialRequestEnabled == false {
+ extraFrosting = false
+ addSprinkles = false
+ }
+ }
+ }
+ @Published var extraFrosting = false
+ @Published var addSprinkles = false
+
+
+ @Published var name = ""
+ @Published var streetAddress = ""
+ @Published var city = ""
+ @Published var zip = ""
+
+
+ var hasValidAddress: Bool {
+ if name. isEmpty || streetAddress. isEmpty || city. isEmpty || zip. isEmpty {
+ return false
+ }
+ return true
+ }
+
+
+ var cost: Double {
+ // $2 per cake
+ var cost = Double (quantity) * 2
+
+ // complicated cakes cost more
+ cost += Double (type) / 2
+
+ // $1/cake for extra frosting
+ if extraFrosting {
+ cost += Double (quantity)
+ }
+
+ // $0.5/cake for sprinkles
+ if addSprinkles {
+ cost += Double (quantity) / 2
+ }
+
+ return cost
+ }
+
+ init () {}
+
+
+ func encode ( to encoder: Encoder) throws {
+ var container = encoder. container ( keyedBy : CodingKeys. self )
+
+ try container. encode (type, forKey : .type)
+ try container. encode (quantity, forKey : .quantity)
+ try container. encode (extraFrosting, forKey : .extraFrosting)
+ try container. encode (addSprinkles, forKey : .addSprinkles)
+ try container. encode (name, forKey : .name)
+ try container. encode (streetAddress, forKey : .streetAddress)
+ try container. encode (city, forKey : .city)
+ try container. encode (zip, forKey : .zip)
+ }
+
+ required init ( from decoder: Decoder) throws {
+ let container = try decoder. container ( keyedBy : CodingKeys. self )
+
+ type = try container. decode ( Int . self , forKey : .type)
+ quantity = try container. decode ( Int . self , forKey : .quantity)
+ extraFrosting = try container. decode ( Bool . self , forKey : .extraFrosting)
+ addSprinkles = try container. decode ( Bool . self , forKey : .addSprinkles)
+ name = try container. decode ( String . self , forKey : .name)
+ city = try container. decode ( String . self , forKey : .city)
+ streetAddress = try container. decode ( String . self , forKey : .streetAddress)
+ zip = try container. decode ( String . self , forKey : .zip)
+ }
+}
ContentView
swift struct ContentView : View {
+ @StateObject var order = Order ()
+
+ var body: some View {
+ NavigationView{
+ Form {
+ Section {
+ Picker ( "Select your cake type" , selection : $order.type) {
+ ForEach (Order.types. indices , id : \\. self ) {
+ Text (Order.types[ $0 ])
+ }
+ }
+ Stepper ( "Number of cakes: \\(order. quantity ) " , value : $order.quantity, in : 3 ... 20 )
+ }
+
+
+ Section {
+ Toggle ( "Any special requests?" , isOn : $order.specialRequestEnabled. animation ())
+
+ if order.specialRequestEnabled {
+ Toggle ( "Add extra frosting" , isOn : $order.extraFrosting)
+ Toggle ( "Add extra sprinkles" , isOn : $order.addSprinkles)
+ }
+ }
+
+ Section {
+ NavigationLink {
+ AddressView ( order : order)
+ } label : {
+ Text ( "Deliver details" )
+ }
+ }
+ }
+ . navigationTitle ( "Cupcake Corner" )
+ }
+ }
+}
struct ContentView : View {
+ @StateObject var order = Order ()
+
+ var body: some View {
+ NavigationView{
+ Form {
+ Section {
+ Picker ( "Select your cake type" , selection : $order.type) {
+ ForEach (Order.types. indices , id : \\. self ) {
+ Text (Order.types[ $0 ])
+ }
+ }
+ Stepper ( "Number of cakes: \\(order. quantity ) " , value : $order.quantity, in : 3 ... 20 )
+ }
+
+
+ Section {
+ Toggle ( "Any special requests?" , isOn : $order.specialRequestEnabled. animation ())
+
+ if order.specialRequestEnabled {
+ Toggle ( "Add extra frosting" , isOn : $order.extraFrosting)
+ Toggle ( "Add extra sprinkles" , isOn : $order.addSprinkles)
+ }
+ }
+
+ Section {
+ NavigationLink {
+ AddressView ( order : order)
+ } label : {
+ Text ( "Deliver details" )
+ }
+ }
+ }
+ . navigationTitle ( "Cupcake Corner" )
+ }
+ }
+}
AddressView
swift struct AddressView : View {
+ @ObservedObject var order: Order
+ var body: some View {
+ Form {
+ Section {
+ TextField ( "name" , text : $order.name)
+ TextField ( "Street address" , text : $order.streetAddress)
+ TextField ( "City" , text : $order.city)
+ TextField ( "Zip" , text : $order.zip)
+ }
+
+ Section {
+ NavigationLink {
+ CheckoutView ( order : order)
+ } label : {
+ Text ( "Check out" )
+ }
+ }
+ . disabled ( ! order.hasValidAddress)
+ }
+ . navigationTitle ( "Delivery details" )
+ . navigationBarTitleDisplayMode (.inline)
+ }
+}
struct AddressView : View {
+ @ObservedObject var order: Order
+ var body: some View {
+ Form {
+ Section {
+ TextField ( "name" , text : $order.name)
+ TextField ( "Street address" , text : $order.streetAddress)
+ TextField ( "City" , text : $order.city)
+ TextField ( "Zip" , text : $order.zip)
+ }
+
+ Section {
+ NavigationLink {
+ CheckoutView ( order : order)
+ } label : {
+ Text ( "Check out" )
+ }
+ }
+ . disabled ( ! order.hasValidAddress)
+ }
+ . navigationTitle ( "Delivery details" )
+ . navigationBarTitleDisplayMode (.inline)
+ }
+}
CheckoutView
swift struct CheckoutView : View {
+ @ObservedObject var order: Order
+ @State private var confirmationMessage = ""
+ @State private var showingConfirmation = false
+
+
+ var body: some View {
+ ScrollView {
+
+ VStack {
+ AsyncImage ( url : URL ( string : "https://hws.dev/img/cupcakes@3x.jpg" ), scale : 3 ) { image in
+ image
+ . resizable ()
+ . scaledToFit ()
+ } placeholder : {
+ ProgressView ()
+ }
+
+
+ Text ( "Your total is \\(order. cost , format : . currency ( code : "USD" )) " )
+ . font (.title)
+
+ Button ( "Place Order" ) {
+ Task {
+ await placeOrder ()
+ }
+ }
+ . padding ()
+
+ }
+ }
+ . navigationTitle ( "Check out" )
+ . navigationBarTitleDisplayMode (.inline)
+ . alert ( "Thank you!" , isPresented : $showingConfirmation) {
+ Button ( "OK" ) {}
+ } message : {
+ Text (confirmationMessage)
+ }
+ }
+
+ func placeOrder () async {
+ guard let encoded = try? JSONEncoder (). encode (order) else {
+ print ( "Failed to encode order" )
+ return
+ }
+
+ let url = URL ( string : "https://reqres.in/api/cupcakes" ) !
+ var request = URLRequest ( url : url)
+ request. setValue ( "application/json" , forHTTPHeaderField : "Content-Type" )
+ request.httpMethod = "POST"
+
+ do {
+ let (data, _ ) = try await URLSession.shared. upload ( for : request, from : encoded)
+ let decodedOrder = try JSONDecoder (). decode (Order. self , from : data)
+ confirmationMessage = "Your order for \\(decodedOrder. quantity ) x \\(Order. types [decodedOrder. type ]. lowercased ()) cupcakes is on its way !"
+ showingConfirmation = true
+ } catch {
+ print ( "Check out failed ..." )
+ }
+
+ }
+}
struct CheckoutView : View {
+ @ObservedObject var order: Order
+ @State private var confirmationMessage = ""
+ @State private var showingConfirmation = false
+
+
+ var body: some View {
+ ScrollView {
+
+ VStack {
+ AsyncImage ( url : URL ( string : "https://hws.dev/img/cupcakes@3x.jpg" ), scale : 3 ) { image in
+ image
+ . resizable ()
+ . scaledToFit ()
+ } placeholder : {
+ ProgressView ()
+ }
+
+
+ Text ( "Your total is \\(order. cost , format : . currency ( code : "USD" )) " )
+ . font (.title)
+
+ Button ( "Place Order" ) {
+ Task {
+ await placeOrder ()
+ }
+ }
+ . padding ()
+
+ }
+ }
+ . navigationTitle ( "Check out" )
+ . navigationBarTitleDisplayMode (.inline)
+ . alert ( "Thank you!" , isPresented : $showingConfirmation) {
+ Button ( "OK" ) {}
+ } message : {
+ Text (confirmationMessage)
+ }
+ }
+
+ func placeOrder () async {
+ guard let encoded = try? JSONEncoder (). encode (order) else {
+ print ( "Failed to encode order" )
+ return
+ }
+
+ let url = URL ( string : "https://reqres.in/api/cupcakes" ) !
+ var request = URLRequest ( url : url)
+ request. setValue ( "application/json" , forHTTPHeaderField : "Content-Type" )
+ request.httpMethod = "POST"
+
+ do {
+ let (data, _ ) = try await URLSession.shared. upload ( for : request, from : encoded)
+ let decodedOrder = try JSONDecoder (). decode (Order. self , from : data)
+ confirmationMessage = "Your order for \\(decodedOrder. quantity ) x \\(Order. types [decodedOrder. type ]. lowercased ()) cupcakes is on its way !"
+ showingConfirmation = true
+ } catch {
+ print ( "Check out failed ..." )
+ }
+
+ }
+}
知识点 Codable协议无法处理被@Published等属性包装器修饰的属性,需要额外编码来手动实现Codable协议
Form表单校验使用.disabled()
修饰符
异步加载图像AsyncImage
,这个View不能像普通Image一样直接使用.resizable()
调整大小,需要特殊处理
swift AsyncImage ( url : URL ( string : "https://hws.dev/img/cupcakes@3x.jpg" ), scale : 3 ) { image in
+ image
+ . resizable ()
+ . scaledToFit ()
+ } placeholder : {
+ ProgressView () //加载中loading view
+ }
AsyncImage ( url : URL ( string : "https://hws.dev/img/cupcakes@3x.jpg" ), scale : 3 ) { image in
+ image
+ . resizable ()
+ . scaledToFit ()
+ } placeholder : {
+ ProgressView () //加载中loading view
+ }
Button的action使用异步方法时需要使用Task
swift Button ( "Place Order" ) {
+ Task {
+ await placeOrder ()
+ }
+}
Button ( "Place Order" ) {
+ Task {
+ await placeOrder ()
+ }
+}
使用URLRequest、URLSession发送http请求
异步方法,async,await关键字
效果
Bookworm 核心代码 BookwormApp
swift @main
+struct BookwormApp : App {
+
+ @StateObject private var dataController = DataController ()
+
+ var body: some Scene {
+ WindowGroup {
+ ContentView ()
+ . environment (\\.managedObjectContext, dataController.container.viewContext )
+ }
+ }
+}
@main
+struct BookwormApp : App {
+
+ @StateObject private var dataController = DataController ()
+
+ var body: some Scene {
+ WindowGroup {
+ ContentView ()
+ . environment (\\.managedObjectContext, dataController.container.viewContext )
+ }
+ }
+}
ContentView
swift struct ContentView : View {
+ @Environment (\\.managedObjectContext) var moc
+ @FetchRequest (sortDescriptors : [ SortDescriptor (\\.title, order : .forward), SortDescriptor (\\.author, order : .forward)]) var books: FetchedResults<Book>
+
+ @State private var showingAddScreen = false
+
+ var body: some View {
+ NavigationView {
+ List {
+ ForEach (books) { book in
+ NavigationLink {
+ DetailView ( book : book)
+ } label : {
+ HStack {
+ EmojiRatingView ( rating : book.rating)
+ . font (.largeTitle)
+ VStack ( alignment : .leading) {
+ Text (book.title ?? "Unknown title" )
+ . font (.headline)
+ Text (book.author ?? "Unknown author" )
+ . foregroundColor (.secondary)
+ }
+ }
+ }
+ }
+ . onDelete ( perform : deleteBooks)
+ }
+ . navigationTitle ( "Bookworm" )
+ .toolbar {
+ ToolbarItem ( placement : .navigationBarLeading) {
+ EditButton ()
+ }
+
+ ToolbarItem ( placement : .navigationBarTrailing) {
+ Button {
+ showingAddScreen. toggle ()
+ } label : {
+ Label ( "Add book" , systemImage : "plus" )
+ }
+ }
+ }
+ . sheet ( isPresented : $showingAddScreen) {
+ AddBookView ()
+ }
+ }
+ }
+
+ func deleteBooks ( at offsets: IndexSet) {
+ for offset in offsets {
+ let book = books[offset]
+ moc. delete (book)
+ }
+ try? moc. save ()
+ }
+}
struct ContentView : View {
+ @Environment (\\.managedObjectContext) var moc
+ @FetchRequest (sortDescriptors : [ SortDescriptor (\\.title, order : .forward), SortDescriptor (\\.author, order : .forward)]) var books: FetchedResults<Book>
+
+ @State private var showingAddScreen = false
+
+ var body: some View {
+ NavigationView {
+ List {
+ ForEach (books) { book in
+ NavigationLink {
+ DetailView ( book : book)
+ } label : {
+ HStack {
+ EmojiRatingView ( rating : book.rating)
+ . font (.largeTitle)
+ VStack ( alignment : .leading) {
+ Text (book.title ?? "Unknown title" )
+ . font (.headline)
+ Text (book.author ?? "Unknown author" )
+ . foregroundColor (.secondary)
+ }
+ }
+ }
+ }
+ . onDelete ( perform : deleteBooks)
+ }
+ . navigationTitle ( "Bookworm" )
+ .toolbar {
+ ToolbarItem ( placement : .navigationBarLeading) {
+ EditButton ()
+ }
+
+ ToolbarItem ( placement : .navigationBarTrailing) {
+ Button {
+ showingAddScreen. toggle ()
+ } label : {
+ Label ( "Add book" , systemImage : "plus" )
+ }
+ }
+ }
+ . sheet ( isPresented : $showingAddScreen) {
+ AddBookView ()
+ }
+ }
+ }
+
+ func deleteBooks ( at offsets: IndexSet) {
+ for offset in offsets {
+ let book = books[offset]
+ moc. delete (book)
+ }
+ try? moc. save ()
+ }
+}
DataController
swift import CoreData
+
+class DataController : ObservableObject {
+ let container = NSPersistentContainer ( name : "Bookworm" )
+
+
+ init () {
+ container.loadPersistentStores { description, error in
+ if let error = error {
+ print ( "Core data failed to load: \\(error. localizedDescription ) " )
+ }
+
+ }
+ }
+}
import CoreData
+
+class DataController : ObservableObject {
+ let container = NSPersistentContainer ( name : "Bookworm" )
+
+
+ init () {
+ container.loadPersistentStores { description, error in
+ if let error = error {
+ print ( "Core data failed to load: \\(error. localizedDescription ) " )
+ }
+
+ }
+ }
+}
AddBookView
swift struct AddBookView : View {
+
+ @Environment (\\.managedObjectContext) var moc
+ @Environment (\\.dismiss) var dismiss
+ @State private var title = ""
+ @State private var author = ""
+ @State private var rating = 3
+ @State private var genre = ""
+ @State private var review = ""
+
+ let genres = [ "Fantasy" , "Horror" , "Kids" , "Mystery" , "Poetry" , "Romance" , "Thriller" ]
+
+ var body: some View {
+ NavigationView {
+ Form {
+ Section {
+ TextField ( "Name of book" , text : $title)
+ TextField ( "Author's name" , text : $author)
+
+ Picker ( "Genre" , selection : $genre) {
+ ForEach (genres, id : \\. self ) {
+ Text ( $0 )
+ }
+ }
+ }
+
+ Section {
+ TextEditor ( text : $review)
+
+ RatingView ( rating : $rating)
+ } header : {
+ Text ( "Write a review" )
+ }
+
+ Section {
+ Button ( "Save" ) {
+ let book = Book ( context : moc)
+ book.id = UUID ()
+ book.title = self .title
+ book.author = self .author
+ book.genre = self .genre
+ book.review = self .review
+ book.rating = Int16 ( self .rating)
+ try? moc. save ()
+ dismiss ()
+ }
+ }
+ }
+ . navigationTitle ( "Add book" )
+ }
+ }
+}
struct AddBookView : View {
+
+ @Environment (\\.managedObjectContext) var moc
+ @Environment (\\.dismiss) var dismiss
+ @State private var title = ""
+ @State private var author = ""
+ @State private var rating = 3
+ @State private var genre = ""
+ @State private var review = ""
+
+ let genres = [ "Fantasy" , "Horror" , "Kids" , "Mystery" , "Poetry" , "Romance" , "Thriller" ]
+
+ var body: some View {
+ NavigationView {
+ Form {
+ Section {
+ TextField ( "Name of book" , text : $title)
+ TextField ( "Author's name" , text : $author)
+
+ Picker ( "Genre" , selection : $genre) {
+ ForEach (genres, id : \\. self ) {
+ Text ( $0 )
+ }
+ }
+ }
+
+ Section {
+ TextEditor ( text : $review)
+
+ RatingView ( rating : $rating)
+ } header : {
+ Text ( "Write a review" )
+ }
+
+ Section {
+ Button ( "Save" ) {
+ let book = Book ( context : moc)
+ book.id = UUID ()
+ book.title = self .title
+ book.author = self .author
+ book.genre = self .genre
+ book.review = self .review
+ book.rating = Int16 ( self .rating)
+ try? moc. save ()
+ dismiss ()
+ }
+ }
+ }
+ . navigationTitle ( "Add book" )
+ }
+ }
+}
RatingView
swift struct RatingView : View {
+ @Binding var rating: Int
+
+ var label = ""
+ var maxmiumRating = 5
+ var offImage: Image ?
+ var onImage = Image ( systemName : "star.fill" )
+ var offColor = Color.gray
+ var onColor = Color.yellow
+
+
+ var body: some View {
+ HStack {
+ if label. isEmpty == false {
+ Text (label)
+ }
+
+ ForEach ( 1 ..< maxmiumRating + 1 , id : \\. self ) { number in
+ image ( for : number)
+ . foregroundColor (number > rating ? offColor : onColor)
+ .onTapGesture {
+ rating = number
+ }
+ }
+ }
+ }
+
+ func image ( for number: Int ) -> Image {
+ if number > rating {
+ return offImage ?? onImage
+ } else {
+ return onImage
+ }
+ }
+}
struct RatingView : View {
+ @Binding var rating: Int
+
+ var label = ""
+ var maxmiumRating = 5
+ var offImage: Image ?
+ var onImage = Image ( systemName : "star.fill" )
+ var offColor = Color.gray
+ var onColor = Color.yellow
+
+
+ var body: some View {
+ HStack {
+ if label. isEmpty == false {
+ Text (label)
+ }
+
+ ForEach ( 1 ..< maxmiumRating + 1 , id : \\. self ) { number in
+ image ( for : number)
+ . foregroundColor (number > rating ? offColor : onColor)
+ .onTapGesture {
+ rating = number
+ }
+ }
+ }
+ }
+
+ func image ( for number: Int ) -> Image {
+ if number > rating {
+ return offImage ?? onImage
+ } else {
+ return onImage
+ }
+ }
+}
EmojiRatingView
swift struct EmojiRatingView : View {
+ let rating: Int16
+
+ var body: some View {
+ switch rating {
+ case 1 :
+ return Text ( "☹️" )
+ case 2 :
+ return Text ( "😞" )
+ case 3 :
+ return Text ( "😊" )
+ case 4 :
+ return Text ( "😍" )
+ default:
+ return Text ( "🤩" )
+ }
+ }
+}
struct EmojiRatingView : View {
+ let rating: Int16
+
+ var body: some View {
+ switch rating {
+ case 1 :
+ return Text ( "☹️" )
+ case 2 :
+ return Text ( "😞" )
+ case 3 :
+ return Text ( "😊" )
+ case 4 :
+ return Text ( "😍" )
+ default:
+ return Text ( "🤩" )
+ }
+ }
+}
DetailView
swift struct DetailView : View {
+ let book: Book
+ @Environment (\\.managedObjectContext) var moc
+ @Environment (\\.dismiss) var dismiss
+ @State private var showingAlert = false
+
+ var body: some View {
+ ScrollView {
+ ZStack ( alignment : .bottomTrailing) {
+ Image (book.genre ?? "Fantasy" )
+ . resizable ()
+ . scaledToFit ()
+
+ Text (book.genre ? . uppercased () ?? "FANTASY" )
+ . font (.caption)
+ . fontWeight (.black)
+ . padding ( 8 )
+ . foregroundColor (.white)
+ . background (.black. opacity ( 0.75 ))
+ . clipShape ( Capsule ())
+ . offset ( x : -5 , y : -5 )
+ }
+
+ Text (book.author ?? "Unknown author" )
+ . font (.title)
+ . foregroundColor (.secondary)
+
+ Text (book.review ?? "No review" )
+ . padding ()
+
+ RatingView ( rating : . constant ( Int (book.rating)))
+ }
+ . navigationTitle (book.title ?? "Unknown book" )
+ . navigationBarTitleDisplayMode (.inline)
+ . alert ( "Delete this book?" , isPresented : $showingAlert) {
+ Button ( "OK" , role : .destructive, action : deleteBook)
+ Button ( "Cancle" , role : .cancel) { }
+ } message : {
+ Text ( "Are you sure?" )
+ }
+ .toolbar {
+ Button {
+ showingAlert = true
+ } label : {
+ Label ( "Delete this book" , systemImage : "trash" )
+ }
+ }
+
+ }
+
+
+ func deleteBook () {
+ moc. delete (book)
+
+ try? moc. save ()
+
+ dismiss ()
+
+ }
+}
struct DetailView : View {
+ let book: Book
+ @Environment (\\.managedObjectContext) var moc
+ @Environment (\\.dismiss) var dismiss
+ @State private var showingAlert = false
+
+ var body: some View {
+ ScrollView {
+ ZStack ( alignment : .bottomTrailing) {
+ Image (book.genre ?? "Fantasy" )
+ . resizable ()
+ . scaledToFit ()
+
+ Text (book.genre ? . uppercased () ?? "FANTASY" )
+ . font (.caption)
+ . fontWeight (.black)
+ . padding ( 8 )
+ . foregroundColor (.white)
+ . background (.black. opacity ( 0.75 ))
+ . clipShape ( Capsule ())
+ . offset ( x : -5 , y : -5 )
+ }
+
+ Text (book.author ?? "Unknown author" )
+ . font (.title)
+ . foregroundColor (.secondary)
+
+ Text (book.review ?? "No review" )
+ . padding ()
+
+ RatingView ( rating : . constant ( Int (book.rating)))
+ }
+ . navigationTitle (book.title ?? "Unknown book" )
+ . navigationBarTitleDisplayMode (.inline)
+ . alert ( "Delete this book?" , isPresented : $showingAlert) {
+ Button ( "OK" , role : .destructive, action : deleteBook)
+ Button ( "Cancle" , role : .cancel) { }
+ } message : {
+ Text ( "Are you sure?" )
+ }
+ .toolbar {
+ Button {
+ showingAlert = true
+ } label : {
+ Label ( "Delete this book" , systemImage : "trash" )
+ }
+ }
+
+ }
+
+
+ func deleteBook () {
+ moc. delete (book)
+
+ try? moc. save ()
+
+ dismiss ()
+
+ }
+}
Bookworm.xcdatamodeld
知识点 CoreData相关概念,.xcdatamodeld文件、NSPersistentContainer、@Environment(.managedObjectContext)、context.save()、context.delete(xxx)、FetchRequest TextEditor 通用RatingView 效果
CoreData Conditional saving of NSManagedObjectContex swift @Environment (\\.managedObjectContext) var moc
+
+var body: some View {
+ Button ( "Save" ) {
+ if moc.hasChanges {
+ try? moc. save ()
+ }
+ }
+}
@Environment (\\.managedObjectContext) var moc
+
+var body: some View {
+ Button ( "Save" ) {
+ if moc.hasChanges {
+ try? moc. save ()
+ }
+ }
+}
swift struct FilteredList < T : NSManagedOBject , Context : View >: View {
+ @FetchRequest var fetchRequest: FetchedResults<T>
+
+ var body: some View {
+ List (fetchRequest, id :\\. self ) { item in
+ self . content (item)
+ }
+ }
+
+ init ( filterKey : String , filterValue : String , @ ViewBuilder content: @escaping (T) -> Content) {
+ _fetchRequest = FetchRequest < T > ( sortDescriptors : [], predicate : NSPredicate ( format : "%K BEGINSWITH %@" , filterKey, filterValue))
+ self .content = content
+ }
+}
struct FilteredList < T : NSManagedOBject , Context : View >: View {
+ @FetchRequest var fetchRequest: FetchedResults<T>
+
+ var body: some View {
+ List (fetchRequest, id :\\. self ) { item in
+ self . content (item)
+ }
+ }
+
+ init ( filterKey : String , filterValue : String , @ ViewBuilder content: @escaping (T) -> Content) {
+ _fetchRequest = FetchRequest < T > ( sortDescriptors : [], predicate : NSPredicate ( format : "%K BEGINSWITH %@" , filterKey, filterValue))
+ self .content = content
+ }
+}
`,67),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const u=s(p,[["render",t]]);export{d as __pageData,u as default};
diff --git a/assets/frontend_others_hackingwithswift-2.md.d6de0bd5.lean.js b/assets/frontend_others_hackingwithswift-2.md.d6de0bd5.lean.js
new file mode 100644
index 000000000..a7dfdd7a4
--- /dev/null
+++ b/assets/frontend_others_hackingwithswift-2.md.d6de0bd5.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"HackingWithSwift-2","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/hackingwithswift-2.md","filePath":"frontend/others/hackingwithswift-2.md","lastUpdated":1694368780000}'),p={name:"frontend/others/hackingwithswift-2.md"},o=l("",67),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const u=s(p,[["render",t]]);export{d as __pageData,u as default};
diff --git a/assets/frontend_others_swift-syntax.md.fe62a728.js b/assets/frontend_others_swift-syntax.md.fe62a728.js
new file mode 100644
index 000000000..2a663a6c4
--- /dev/null
+++ b/assets/frontend_others_swift-syntax.md.fe62a728.js
@@ -0,0 +1,305 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"swift语法","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/swift-syntax.md","filePath":"frontend/others/swift-syntax.md","lastUpdated":1694368780000}'),p={name:"frontend/others/swift-syntax.md"},o=l(`swift语法 英文原版:https://docs.swift.org/swift-book/
中文版:https://swiftgg.gitbook.io/swift/
可选类型 *可选类型(optionals)*来处理值可能缺失的情况。可选类型实际也是一个枚举:
swift enum Optional < T > {
+ case none
+ case some (T)
+}
enum Optional < T > {
+ case none
+ case some (T)
+}
nil 如果声明一个可选常量或者变量但是没有赋值,它们会自动被设置为nil
:
swift var hello: String ? var hello: Optional < String > = . none
+var hello: String ? = "hello" var hello: Optional < String > = . some ( "hello" )
+var hello: String ? = nil var hello: Optional < String > = . none
var hello: String ? var hello: Optional < String > = . none
+var hello: String ? = "hello" var hello: Optional < String > = . some ( "hello" )
+var hello: String ? = nil var hello: Optional < String > = . none
swift var surveyAnswer: String ?
+// surveyAnswer 会被自动设置为 nil -> var surveyAnswer: String? = nil
var surveyAnswer: String ?
+// surveyAnswer 会被自动设置为 nil -> var surveyAnswer: String? = nil
强制解析 swift let hello: String ? = ...
+print (hello ! )
let hello: String ? = ...
+print (hello ! )
等价于
swift switch hello {
+ case . none : //raise an exception(crash)
+ case . some ( let data) : print (data)
+}
switch hello {
+ case . none : //raise an exception(crash)
+ case . some ( let data) : print (data)
+}
可选绑定 强制解析可能会导致异常,可以使用if let
来安全的获取可选类型中的值
swift if let safehello = hello {
+ print (safehello)
+} else {
+ //do something else
+}
if let safehello = hello {
+ print (safehello)
+} else {
+ //do something else
+}
等价于
swift switch hello {
+ case . none : //do something else
+ case . some ( let data) : print (data)
+}
switch hello {
+ case . none : //do something else
+ case . some ( let data) : print (data)
+}
使用可选绑定时后面不能用&&
,可以用,
隔开语句
空合运算符(Nil Coalescing Operator) 空合运算符 (a ?? b
)将对可选类型 a
进行空判断,如果 a
包含一个值就进行解包,否则就返回一个默认值 b
。表达式 a
必须是 Optional 类型。默认值 b
的类型必须要和 a
存储值的类型保持一致。
空合运算符是对以下代码的简短表达方法:
swift a != nil ? a ! : b
a != nil ? a ! : b
例子:
swift let x: String ? = ...
+let y = x ?? z
let x: String ? = ...
+let y = x ?? z
实际等价于上:
swift switch a {
+ case . none : y = z
+ case . some ( let data) : y = data
+}
switch a {
+ case . none : y = z
+ case . some ( let data) : y = data
+}
可选链 可选链式调用 是一种可以在当前值可能为 nil
的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是 nil
,那么调用将返回 nil
。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为 nil
,整个调用链都会失败,即返回 nil
。
swift let x: String = ...
+let y = x ? . foo () ? .bar ? .z
let x: String = ...
+let y = x ? . foo () ? .bar ? .z
等价于
swift switch x {
+ case . none : y = nil
+ case . some ( let xval) :
+ switch xval. foo () {
+ case . none : y = nil
+ case . some ( let xfooval) :
+ switch xfooval.bar {
+ case . none : y = nil
+ case . some ( let xfbval) : y = xfbval.z
+ }
+ }
+}
switch x {
+ case . none : y = nil
+ case . some ( let xval) :
+ switch xval. foo () {
+ case . none : y = nil
+ case . some ( let xfooval) :
+ switch xfooval.bar {
+ case . none : y = nil
+ case . some ( let xfbval) : y = xfbval.z
+ }
+ }
+}
闭包 闭包表达式 swift { (parameters) -> return_type in
+ statements
+}
{ (parameters) -> return_type in
+ statements
+}
尾随闭包(Trailing Closures) 尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,不用写出它的参数标签
例如:
swift ForEach (modelData.categories. keys . sorted (), id : \\. self ) { key in
+ CategoryRow ( categoryName : key, items : modelData.categories[key] ! )
+}
ForEach (modelData.categories. keys . sorted (), id : \\. self ) { key in
+ CategoryRow ( categoryName : key, items : modelData.categories[key] ! )
+}
多重尾随闭包(multiple trailing closure) swift struct SignInView : View {
+ var body: some View {
+ Button {
+ showingProfile. toggle ()
+ } label : {
+ Label ( "User Profile" , systemImage : "person.crop.circle" )
+ }
+ }
+}
struct SignInView : View {
+ var body: some View {
+ Button {
+ showingProfile. toggle ()
+ } label : {
+ Label ( "User Profile" , systemImage : "person.crop.circle" )
+ }
+ }
+}
方法 在实例方法中修改值类型 结构体和枚举是值类型 。默认情况下,值类型的属性不能在它的实例方法中被修改。
如果确实需要在某个特定的方法中修改结构体或者枚举的属性,可以为这个方法选择 可变(mutating)
swift struct Point {
+ var x = 0.0 , y = 0.0
+ mutating func moveBy ( x deltaX: Double , y deltaY: Double ) {
+ x += deltaX
+ y += deltaY
+ }
+}
+var somePoint = Point ( x : 1.0 , y : 1.0 )
+somePoint. moveBy ( x : 2.0 , y : 3.0 )
+print ( "The point is now at ( \\(somePoint. x ) , \\(somePoint. y ) )" )
+// 打印“The point is now at (3.0, 4.0)”
struct Point {
+ var x = 0.0 , y = 0.0
+ mutating func moveBy ( x deltaX: Double , y deltaY: Double ) {
+ x += deltaX
+ y += deltaY
+ }
+}
+var somePoint = Point ( x : 1.0 , y : 1.0 )
+somePoint. moveBy ( x : 2.0 , y : 3.0 )
+print ( "The point is now at ( \\(somePoint. x ) , \\(somePoint. y ) )" )
+// 打印“The point is now at (3.0, 4.0)”
属性 计算属性 计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
swift struct Point {
+ var x = 0.0 , y = 0.0
+}
+struct Size {
+ var width = 0.0 , height = 0.0
+}
+struct Rect {
+ var origin = Point ()
+ var size = Size ()
+ var center: Point {
+ get {
+ let centerX = origin.x + (size.width / 2 )
+ let centerY = origin.y + (size.height / 2 )
+ return Point ( x : centerX, y : centerY)
+ }
+ set (newCenter) {
+ origin.x = newCenter.x - (size.width / 2 )
+ origin.y = newCenter.y - (size.height / 2 )
+ }
+ }
+}
+var square = Rect ( origin : Point ( x : 0.0 , y : 0.0 ),
+ size : Size ( width : 10.0 , height : 10.0 ))
+let initialSquareCenter = square.center
+square.center = Point ( x : 15.0 , y : 15.0 )
+print ( "square.origin is now at ( \\(square. origin . x ) , \\(square. origin . y ) )" )
+// 打印“square.origin is now at (10.0, 10.0)”
struct Point {
+ var x = 0.0 , y = 0.0
+}
+struct Size {
+ var width = 0.0 , height = 0.0
+}
+struct Rect {
+ var origin = Point ()
+ var size = Size ()
+ var center: Point {
+ get {
+ let centerX = origin.x + (size.width / 2 )
+ let centerY = origin.y + (size.height / 2 )
+ return Point ( x : centerX, y : centerY)
+ }
+ set (newCenter) {
+ origin.x = newCenter.x - (size.width / 2 )
+ origin.y = newCenter.y - (size.height / 2 )
+ }
+ }
+}
+var square = Rect ( origin : Point ( x : 0.0 , y : 0.0 ),
+ size : Size ( width : 10.0 , height : 10.0 ))
+let initialSquareCenter = square.center
+square.center = Point ( x : 15.0 , y : 15.0 )
+print ( "square.origin is now at ( \\(square. origin . x ) , \\(square. origin . y ) )" )
+// 打印“square.origin is now at (10.0, 10.0)”
简化 Setter 声明 如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue
。下面是使用了简化 setter 声明的 Rect
结构体代码:
swift struct AlternativeRect {
+ var origin = Point ()
+ var size = Size ()
+ var center: Point {
+ get {
+ let centerX = origin.x + (size.width / 2 )
+ let centerY = origin.y + (size.height / 2 )
+ return Point ( x : centerX, y : centerY)
+ }
+ set {
+ origin.x = newValue.x - (size.width / 2 )
+ origin.y = newValue.y - (size.height / 2 )
+ }
+ }
+}
struct AlternativeRect {
+ var origin = Point ()
+ var size = Size ()
+ var center: Point {
+ get {
+ let centerX = origin.x + (size.width / 2 )
+ let centerY = origin.y + (size.height / 2 )
+ return Point ( x : centerX, y : centerY)
+ }
+ set {
+ origin.x = newValue.x - (size.width / 2 )
+ origin.y = newValue.y - (size.height / 2 )
+ }
+ }
+}
简化 Getter 声明 如果整个 getter 是单一表达式,getter 会隐式地返回这个表达式结果。下面是另一个版本的 Rect
结构体,用到了简化的 getter 和 setter 声明:
swift struct CompactRect {
+ var origin = Point ()
+ var size = Size ()
+ var center: Point {
+ get {
+ Point ( x : origin.x + (size.width / 2 ),
+ y : origin.y + (size.height / 2 ))
+ }
+ set {
+ origin.x = newValue.x - (size.width / 2 )
+ origin.y = newValue.y - (size.height / 2 )
+ }
+ }
+}
struct CompactRect {
+ var origin = Point ()
+ var size = Size ()
+ var center: Point {
+ get {
+ Point ( x : origin.x + (size.width / 2 ),
+ y : origin.y + (size.height / 2 ))
+ }
+ set {
+ origin.x = newValue.x - (size.width / 2 )
+ origin.y = newValue.y - (size.height / 2 )
+ }
+ }
+}
在 getter 中忽略 return
与在函数中忽略 return
的规则相同.
只读计算属性 只有 getter 没有 setter 的计算属性叫只读计算属性 。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
注意
必须使用 var
关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let
关键字只用来声明常量属性,表示初始化后再也无法修改的值。
只读计算属性的声明可以去掉 get
关键字和花括号:
swift struct Cuboid {
+ var width = 0.0 , height = 0.0 , depth = 0.0
+ var volume: Double {
+ return width * height * depth
+ }
+}
+let fourByFiveByTwo = Cuboid ( width : 4.0 , height : 5.0 , depth : 2.0 )
+print ( "the volume of fourByFiveByTwo is \\(fourByFiveByTwo. volume ) " )
+// 打印“the volume of fourByFiveByTwo is 40.0”
struct Cuboid {
+ var width = 0.0 , height = 0.0 , depth = 0.0
+ var volume: Double {
+ return width * height * depth
+ }
+}
+let fourByFiveByTwo = Cuboid ( width : 4.0 , height : 5.0 , depth : 2.0 )
+print ( "the volume of fourByFiveByTwo is \\(fourByFiveByTwo. volume ) " )
+// 打印“the volume of fourByFiveByTwo is 40.0”
属性观察器 属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
可以在以下位置添加属性观察器:
可以为属性添加其中一个或两个观察器:
willSet
在新的值被设置之前调用didSet
在新的值被设置之后调用willSet
观察器会将新的属性值作为常量参数传入,在 willSet
的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 newValue
表示。
同样,didSet
观察器会将旧的属性值作为参数传入,可以为该参数指定一个名称或者使用默认参数名 oldValue
。如果在 didSet
方法中再次对该属性赋值,那么新值会覆盖旧的值。
在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的 willSet
和 didSet
观察器。而在父类初始化方法调用之前,给子类的属性赋值时不会调用子类属性的观察器。
swift class StepCounter {
+ var totalSteps: Int = 0 {
+ willSet (newTotalSteps) {
+ print ( "将 totalSteps 的值设置为 \\(newTotalSteps) " )
+ }
+ didSet {
+ if totalSteps > oldValue {
+ print ( "增加了 \\(totalSteps - oldValue) 步" )
+ }
+ }
+ }
+}
+let stepCounter = StepCounter ()
+stepCounter.totalSteps = 200
+// 将 totalSteps 的值设置为 200
+// 增加了 200 步
+stepCounter.totalSteps = 360
+// 将 totalSteps 的值设置为 360
+// 增加了 160 步
+stepCounter.totalSteps = 896
+// 将 totalSteps 的值设置为 896
+// 增加了 536 步
class StepCounter {
+ var totalSteps: Int = 0 {
+ willSet (newTotalSteps) {
+ print ( "将 totalSteps 的值设置为 \\(newTotalSteps) " )
+ }
+ didSet {
+ if totalSteps > oldValue {
+ print ( "增加了 \\(totalSteps - oldValue) 步" )
+ }
+ }
+ }
+}
+let stepCounter = StepCounter ()
+stepCounter.totalSteps = 200
+// 将 totalSteps 的值设置为 200
+// 增加了 200 步
+stepCounter.totalSteps = 360
+// 将 totalSteps 的值设置为 360
+// 增加了 160 步
+stepCounter.totalSteps = 896
+// 将 totalSteps 的值设置为 896
+// 增加了 536 步
protocol sort of a "stripped-down" struct/class
有函数和变量,但是没有具体实现,类似interface, 当实现一个协议时,必须实现协议中所有的函数和变量
使用protocol来限制entension:
swift extension Array where Element: Hashable { ... }
extension Array where Element: Hashable { ... }
使用protocol来限制函数:
swift init ( data : Data) where Data: Collection, Data.Element: Identifiable
init ( data : Data) where Data: Collection, Data.Element: Identifiable
protocol extension 可以通过extension给protocol的func或var添加默认的实现
swift struct Tesla : Vehicle {
+ //...
+}
+
+extension Vehicle {
+ fun registerWithDMV () { // actual implementation }
+}
struct Tesla : Vehicle {
+ //...
+}
+
+extension Vehicle {
+ fun registerWithDMV () { // actual implementation }
+}
swift protocol View {
+ var body: some View
+}
protocol View {
+ var body: some View
+}
swift extension View {
+ func foregroundColor ( _ color: Color) -> some View { /* implementation */ }
+ func font ( _ font: Font ? ) -> some View { /* implementation */ }
+ ...
+}
extension View {
+ func foregroundColor ( _ color: Color) -> some View { /* implementation */ }
+ func font ( _ font: Font ? ) -> some View { /* implementation */ }
+ ...
+}
generics + protocols swift protocol Identifiable {
+ associatedtype ID
+ var id: ID { get }
+}
protocol Identifiable {
+ associatedtype ID
+ var id: ID { get }
+}
`,86),e=[o];function t(c,r,y,E,i,F){return a(),n("div",null,e)}const C=s(p,[["render",t]]);export{h as __pageData,C as default};
diff --git a/assets/frontend_others_swift-syntax.md.fe62a728.lean.js b/assets/frontend_others_swift-syntax.md.fe62a728.lean.js
new file mode 100644
index 000000000..98bb8b2c2
--- /dev/null
+++ b/assets/frontend_others_swift-syntax.md.fe62a728.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const h=JSON.parse('{"title":"swift语法","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/swift-syntax.md","filePath":"frontend/others/swift-syntax.md","lastUpdated":1694368780000}'),p={name:"frontend/others/swift-syntax.md"},o=l("",86),e=[o];function t(c,r,y,E,i,F){return a(),n("div",null,e)}const C=s(p,[["render",t]]);export{h as __pageData,C as default};
diff --git a/assets/frontend_others_swiftui-syntax.md.e9ffe4fb.js b/assets/frontend_others_swiftui-syntax.md.e9ffe4fb.js
new file mode 100644
index 000000000..dd91e5de0
--- /dev/null
+++ b/assets/frontend_others_swiftui-syntax.md.e9ffe4fb.js
@@ -0,0 +1,319 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"SwiftUI入门","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/swiftui-syntax.md","filePath":"frontend/others/swiftui-syntax.md","lastUpdated":1694368780000}'),p={name:"frontend/others/swiftui-syntax.md"},o=l(`SwiftUI入门 MVVM MVVM是一种架构设计范式,把数据和视图分离开,Model和View必须通过ViewModel通信。
Model 数据模型,负责数据和逻辑的处理,独立于UI界面,数据流(data flows)在映射到视图中的过程是只读 的
View 渲染UI界面,展示Model数据,声明式(为UI声明的方法,在任何时候做它们应做的事情)、无状态的(不需要关心任何状态变化)、响应式的(跟随Model数据变化重新渲染)。
ViewModel 执行解释工作(interpreter),绑定View和Model。ViewModel关注Model中的变化(notices changes),然后把Model的数据变更发布出去(publishes changed),订阅了(subsrcbes)某个发布(publication)的View会进行rebuild。
ViewModel没有指向View的指针,不直接与View对话,如果View订阅了某个发布,就会询问ViewModel怎么适应变化,这个过程不会涉及Model,因为ViewModel的作用就是解释Model的变化。
MVVM的Processes Intent MVVM有一个对应的关联架构,是Model-View-Intent。如果用户意图(intent )做一些操作,那么这些Intent就要进行View到Model这个反向传递过程。而swiftUI还没有进行这个设计,所以我们用下面一系列操作来处理Intent:
View Calls Intent function 视图调用方法ViewModel modifies the Model 视图模型修改模型Model changes 模型改动变化ViewModel notices changes and publishes 模型关注到变化并发布View whitch subscribes Reflect the Model 订阅变化的视图进行模型映射对比MVVM的映射过程,多了ViewModel处理View操作,并且修改Model这两个操作。
https://www.jianshu.com/p/c14c70c0c9f7
Layout HStack and VStack stacks划分提供给自身的空间,然后把空间分配给内部的视图。优先给least flexible
的子视图分配空间。
Example of inflexible view : Image,Image视图需要一个固定尺寸 Another example(slightly more flexible): Text,需要一个完全适合内部文本的尺寸 Example of a very flexible View: RoundedRectangle,总是使用所有可用的空间 在给一个视图它需要的空间后,这块空间从可用空间中被移除,然后stack继续给下一个least flexible
的视图分配空间。very flexible views
最后会平分空间。
在子视图选择了它们的尺寸后,stack会调整自己的size来适应它们,如果有very flexible
的子视图,那么这个stack也会变得very flexible
.layoutPriority(Double) 可以使用.layoutPriority(Double)
改变获取空间的优先级,默认值为0。.layoutPriority(Double)
的优先级要比least flexible
更高。
alignment why .leading instead of .left?Stacks会根据语言环境判断对齐方式,例如有些语言(阿拉伯语)的文本是从右向左的。
LazyHStack and LazyVStack 不会build不可见的视图内容,通常用在ScrollView中
占据所有可用空间,子视图大小根据滚动轴调整
really smart VStacks
.backgroup 修饰符 Text("hello").backgroup(Rectangle().foregroundColor(.red))
,效果类似ZStack(Text在上),但是区别是这个例子中最终的View大小是由Text决定的
.overlay 修饰符 swift Image ( systemName : "folder" )
+ . font (. system ( size : 55 , weight : .thin))
+ . overlay ( Text ( "❤️" ), alignment : .bottom)
Image ( systemName : "folder" )
+ . font (. system ( size : 55 , weight : .thin))
+ . overlay ( Text ( "❤️" ), alignment : .bottom)
视图的大小由Image决定,Text会堆叠在Image上,底部对齐
Modifiers 所有修饰符都会返回一个View
Example swift HStack{
+ ForEach (viewModel.cards) { card in
+ CardView ( card : card). aspectRatio ( 2 / 3 , contentMode : .fit)
+ }
+}
+. foregroundColor (.orange)
+. padding ( 10 )
HStack{
+ ForEach (viewModel.cards) { card in
+ CardView ( card : card). aspectRatio ( 2 / 3 , contentMode : .fit)
+ }
+}
+. foregroundColor (.orange)
+. padding ( 10 )
首先被提供空间的是.padding(10)
然后内边距10的空间会提供给.foregroudColor
最后所有空间被提供给HStack 然后空间被平均分给.aspectRatio
每个.aspectRatio
会设置宽度,然后遵循2/3的长宽比设置高度,或者在HStack高度不足时,占据所有高度,然后按2/3设置宽度。 .aspectRatio
把所有空间提供给CardViewSpacer(minLength: CGFloat) 总是占据提供给他的所有空间,不绘制任何东西.
Divider() 分割线,在HStack中绘制垂直的线,VStack中是水平线。
@ViewBuilder @ViewBuilder是一个参数属性,作用于构造视图的闭包参数上,允许闭包提供多个子视图。
swift @ViewBuilder
+func front ( of card: Card) -> some View {
+ let shape = RoundedRectangle ( cornerRadius : 20 )
+ shape
+ shape. stroke ()
+ Text (card.content)
+}
@ViewBuilder
+func front ( of card: Card) -> some View {
+ let shape = RoundedRectangle ( cornerRadius : 20 )
+ shape
+ shape. stroke ()
+ Text (card.content)
+}
Property Wrapper swift @propertyWrapper
+struct Converter1 {
+ let from: String
+ let to: String
+ let rate: Double
+
+ var value: Double
+ var wrappedValue: String {
+ get {
+ " \\(from)\\(value) "
+ }
+ set {
+ value = Double (newValue) ?? -1
+ }
+ }
+
+ var projectedValue: String {
+ return " \\(to)\\(value * rate) "
+ }
+
+ init ( initialValue : String ,
+ from : String ,
+ to : String ,
+ rate : Double
+ ) {
+ self .rate = rate
+ self . value = 0
+ self .from = from
+ self .to = to
+ self .wrappedValue = initialValue
+ }
+
+
+}
+
+struct TestWraper {
+ @State var myname = ""
+ @Converter1 (initialValue : "100" , from : "USD" , to : "CNY" , rate : 6.88 )
+ var usd_cny
+
+ @Converter1 (initialValue : "100" , from : "CNY" , to : "EUR" , rate : 0.13 )
+ var cny_eur
+
+ func test1 (){
+ print ( " \\(usd_cny) = \\($usd_cny) " )
+ print ( " \\(cny_eur) = \\($cny_eur) " )
+ }
+ /*
+ USD100.0=CNY688.0
+ CNY100.0=EUR13.0
+ */
+}
@propertyWrapper
+struct Converter1 {
+ let from: String
+ let to: String
+ let rate: Double
+
+ var value: Double
+ var wrappedValue: String {
+ get {
+ " \\(from)\\(value) "
+ }
+ set {
+ value = Double (newValue) ?? -1
+ }
+ }
+
+ var projectedValue: String {
+ return " \\(to)\\(value * rate) "
+ }
+
+ init ( initialValue : String ,
+ from : String ,
+ to : String ,
+ rate : Double
+ ) {
+ self .rate = rate
+ self . value = 0
+ self .from = from
+ self .to = to
+ self .wrappedValue = initialValue
+ }
+
+
+}
+
+struct TestWraper {
+ @State var myname = ""
+ @Converter1 (initialValue : "100" , from : "USD" , to : "CNY" , rate : 6.88 )
+ var usd_cny
+
+ @Converter1 (initialValue : "100" , from : "CNY" , to : "EUR" , rate : 0.13 )
+ var cny_eur
+
+ func test1 (){
+ print ( " \\(usd_cny) = \\($usd_cny) " )
+ print ( " \\(cny_eur) = \\($cny_eur) " )
+ }
+ /*
+ USD100.0=CNY688.0
+ CNY100.0=EUR13.0
+ */
+}
属性包装器必须有一个包装值,名为wrappedValue
的计算属性 预计值为projectedValue
,访问预计值的方式为.$属性名
,projectedValue
是只读的。 Property Wrapper使用限制 protocol中无法使用 通过wrapper包装的实例属性不能在extension中声明 不能在enum中声明 class中通过wrapper包装的属性无法被另外一个属性通过override覆盖 通过wrapper包装的实例属性不能用lazy
、@NSCopying
、@NSManaged
、weak
或unowned
修饰 @State don't worry,之所以这样是因为View应该是stateless的,只负责渲染model,不需要自身具有什么状态属性。但是极少数情况下View也是需要状态的(it turns out there are a few rare times when a View needs some state),但这种状态存储总是暂时的(always temporary),所有持久化的状态都存在Model中。
例如:进入编辑模式,需要提前收集数据来为用户修改数据的intent作准备,需要暂时展示其他的View(编辑页面)来收集数据,编辑完后需要一个动画效果来关闭这个编辑页面,所以需要一个"编辑模式状态"的属性来标记何时该关闭。
上述场景中可以使用@State
来标记这个临时状态存储变量
swift @State private var somethingTemporary: SomeType //someType can be any struct
@State private var somethingTemporary: SomeType //someType can be any struct
这个临时状态变量是private修饰的,是因为只有当前View能访问这个变量。@State
变量的变化会导致这个View的body重新渲染。这和@ObservedObject
类似,但是@State
作用的是一个随机的数据(值语义),而@ObservedObject
作用在ViewModel上(对象语义)。
@ObservedObject 多个视图数据共享和更新时,需要一个数据模型的概念,即多视图的状态可以根据Data-Model
进行更新,这种场景下@State就不再适用了。
ObservableObject
协议定义了一个数据模型的数据发生变化时发布通知的能力
@ObservedObject
这个属性包装器包装的属性可以监听到数据的变化,也可以利用它去更新数据。
@Published
这个属性包装器包装的属性,都会被转化为一个publisher(Combine框架的概念),当值发生变化时,会通知系统,然后系统再去更新画面
@StateObject 和@ObservedObject类似,也是修饰对象语义,和@ObservedObject的区别在于,实例是否被创建其的View所持有,其生命周期是否完全可控,@StateObject修饰的属性的生命周期由创建该对象的对象维护(这一点又类似@State)
swift class DataSource : ObservableObject {
+ @Published var counter = 0
+}
+
+struct Counter : View {
+ @ObservedObject var dataSource = DataSource ()
+
+ var body: some View {
+ VStack {
+ Button ( "Increment counter" ) {
+ dataSource.counter += 1
+ }
+
+ Text ( "Count is \\(dataSource. counter ) " )
+ }
+ }
+}
+
+struct ItemList : View {
+ @State private var items = [ "hello" , "world" ]
+
+ var body: some View {
+ VStack {
+ Button ( "Append item to list" ) {
+ items. append ( "test" )
+ }
+
+ List (items, id : \\. self ) { name in
+ Text (name)
+ }
+
+ Counter ()
+ }
+ }
+}
class DataSource : ObservableObject {
+ @Published var counter = 0
+}
+
+struct Counter : View {
+ @ObservedObject var dataSource = DataSource ()
+
+ var body: some View {
+ VStack {
+ Button ( "Increment counter" ) {
+ dataSource.counter += 1
+ }
+
+ Text ( "Count is \\(dataSource. counter ) " )
+ }
+ }
+}
+
+struct ItemList : View {
+ @State private var items = [ "hello" , "world" ]
+
+ var body: some View {
+ VStack {
+ Button ( "Append item to list" ) {
+ items. append ( "test" )
+ }
+
+ List (items, id : \\. self ) { name in
+ Text (name)
+ }
+
+ Counter ()
+ }
+ }
+}
在这个例子中,每次点击Append item to list
Button,counter都会被重置,这是因为每次重新渲染,DataSource()都会被重新创建。解决这个问题有两个方法:
在ItemList中创建DataSource,并把DataSource传递给Counter 把@ObservedObject替换为@StateObject 将DataSource标记为@StateObject意味着DataSource被实例化后会保存在Counter的外部,当Counter重新渲染时,会直接用这个值。
@EnvironmentObject 使用@ObservedObject
可以在视图间共享数据、刷新画面,但是必须为需要的视图进行引用的传递。如果视图的层级较多,且各个View和子View使用同一个数据模型,那么@ObservedObject
的传递将会变得笨重且易出错。
SwiftUI提供了另一种选择,@EnvironmentObject
就是把数据模型引用保存到了一个共同的环境变量中,environment
是一个共通的存储区域,保存了app的信息和Views,当然也可以保存自定义数据,包括对observable object的引用。
@Environment 和@State
类似,App也可以响应iOS系统过来的state变化,例如语言环境、字体大小、暗黑模式切换等,为了及时响应这些变化,app可以使用@Environment(KeyPath)
来进行获取实时的信息。
Combine框架 @Published
属性包装器和ObservableObject
的实现定义在Combine
框架中。Combine框架中定义了一些协议和数据类型,可以让我们处理数据,当一个代码数据发生变化,可以应用这个框架来通知另外一处代码有新数据可以使用。
这样就会出现两个类型的任务,一个是发布者(publisher),一个是订阅者(subscriber)。发布者决定了数据和错误信息的产生并发给订阅者,订阅者会接受这些信息。
在SwiftUI中,被@Published
修饰的属性,会被自动转化为Publisher,ObservableObject
协议的实现中,定义了被@Published
修饰的属性作为发布者,在属性的值发生变化的时候,发布者将通知订阅者。@ObservedObject
和@EnvironmentObject
修饰的属性,扮演订阅者的角色。
Just发布者和Subscribers.Sink swift import Combine
+import Foundation
+
+let myPublisher = Just ( "55" )
+
+let mySubscriber = Subscribers.Sink < String , Never > ( receiveCompletion : { completion in
+ if completion == .finished {
+ print ( "111" )
+ } else {
+ print ( "222" )
+ }
+
+}, receiveValue : { value in
+ print (value)
+})
+
+myPublisher. subscribe (mySubscriber)
import Combine
+import Foundation
+
+let myPublisher = Just ( "55" )
+
+let mySubscriber = Subscribers.Sink < String , Never > ( receiveCompletion : { completion in
+ if completion == .finished {
+ print ( "111" )
+ } else {
+ print ( "222" )
+ }
+
+}, receiveValue : { value in
+ print (value)
+})
+
+myPublisher. subscribe (mySubscriber)
数据的转换 中间发布者
Publishers.Map
Publishers.Filter
...
或Just().操作符
Subjects Combine还有一种发布者叫Subjects,实现了Subject协议,可以调用send方法发送数据
PassthroughSubject() CurrentValueSubject(value) swift import Combine
+import Foundation
+
+enum MyErrors : Error {
+ case wrongValue
+}
+
+let myPublisher = PassthroughSubject < String , MyErrors > ()
+//let myPublisher = CurrentValueSubject<String, MyErrors>("100")
+
+let mySubscriber = myPublisher. filter ({
+ return $0 . count < 5
+}). sink ( receiveCompletion : {completion in
+ if completion == . failure (MyErrors.wrongValue) {
+ print ( "MyErrors.wrongValue" )
+ } else {
+ print (completion)
+ }
+
+}, receiveValue : { value in
+ print ( "value: \\(value) " )
+})
+
+
+myPublisher. send ( "h" )
import Combine
+import Foundation
+
+enum MyErrors : Error {
+ case wrongValue
+}
+
+let myPublisher = PassthroughSubject < String , MyErrors > ()
+//let myPublisher = CurrentValueSubject<String, MyErrors>("100")
+
+let mySubscriber = myPublisher. filter ({
+ return $0 . count < 5
+}). sink ( receiveCompletion : {completion in
+ if completion == . failure (MyErrors.wrongValue) {
+ print ( "MyErrors.wrongValue" )
+ } else {
+ print (completion)
+ }
+
+}, receiveValue : { value in
+ print ( "value: \\(value) " )
+})
+
+
+myPublisher. send ( "h" )
.onReceive SwiftUI中,View协议有一个修饰符.onReceive(Publisher, perform: Closure)
把任何View转换成一个订阅者,来接受来自发布者的数据,SwiftUI使UI组件和Combine结合带来了扩展可能。
swift import SwiftUI
+
+class ContentViewData : ObservableObject {
+ @Published var counter: Int = 0
+ let timePublisher = Timer. publish ( every : 2 , on : .main, in : .common). autoconnect ()
+}
+
+struct ContentView : View {
+ @ObservedObject var contentData = ContentViewData ()
+
+ var body: some View {
+ Text ( "hello, world! \\( self . contentData . counter ) " )
+ . onReceive (contentData.timePublisher, perform : { value in
+ self .contentData.counter += 1
+ if self .contentData.counter > 20 {
+ self .contentData.timePublisher.upstream. connect (). cancel ()
+ print ( "stop" )
+ }
+ })
+ }
+}
import SwiftUI
+
+class ContentViewData : ObservableObject {
+ @Published var counter: Int = 0
+ let timePublisher = Timer. publish ( every : 2 , on : .main, in : .common). autoconnect ()
+}
+
+struct ContentView : View {
+ @ObservedObject var contentData = ContentViewData ()
+
+ var body: some View {
+ Text ( "hello, world! \\( self . contentData . counter ) " )
+ . onReceive (contentData.timePublisher, perform : { value in
+ self .contentData.counter += 1
+ if self .contentData.counter > 20 {
+ self .contentData.timePublisher.upstream. connect (). cancel ()
+ print ( "stop" )
+ }
+ })
+ }
+}
`,93),e=[o];function t(c,r,E,y,i,u){return a(),n("div",null,e)}const C=s(p,[["render",t]]);export{d as __pageData,C as default};
diff --git a/assets/frontend_others_swiftui-syntax.md.e9ffe4fb.lean.js b/assets/frontend_others_swiftui-syntax.md.e9ffe4fb.lean.js
new file mode 100644
index 000000000..6b6dcb909
--- /dev/null
+++ b/assets/frontend_others_swiftui-syntax.md.e9ffe4fb.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"SwiftUI入门","description":"","frontmatter":{},"headers":[],"relativePath":"frontend/others/swiftui-syntax.md","filePath":"frontend/others/swiftui-syntax.md","lastUpdated":1694368780000}'),p={name:"frontend/others/swiftui-syntax.md"},o=l("",93),e=[o];function t(c,r,E,y,i,u){return a(),n("div",null,e)}const C=s(p,[["render",t]]);export{d as __pageData,C as default};
diff --git a/assets/golang_base_go-template.md.498dda04.js b/assets/golang_base_go-template.md.498dda04.js
new file mode 100644
index 000000000..49b6126d9
--- /dev/null
+++ b/assets/golang_base_go-template.md.498dda04.js
@@ -0,0 +1,391 @@
+import{_ as s,o as n,c as a,Q as p}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"Go Template","description":"","frontmatter":{},"headers":[],"relativePath":"golang/base/go-template.md","filePath":"golang/base/go-template.md","lastUpdated":1694368780000}'),l={name:"golang/base/go-template.md"},o=p(`Go Template Go Template是一种用于生成文本输出的模板引擎,它是Go语言标准库中内置的一部分。Go Template使用简单而强大的语法来描述要生成的最终文本的结构和内容。
Go Template的语法是基于文本插值的思想,通过在模板文件中插入占位符和控制指令来控制输出的结果。模板可以包含静态文本和动态值,并且可以使用控制指令来迭代、条件判断和执行其他逻辑操作。
示例 html <!--test.html-->
+<! DOCTYPE html >
+< html >
+< head >
+ < meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" >
+ < title >Go Web</ title >
+</ head >
+< body >
+{{ . }}
+</ body >
+</ html >
<!--test.html-->
+<! DOCTYPE html >
+< html >
+< head >
+ < meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" >
+ < title >Go Web</ title >
+</ head >
+< body >
+{{ . }}
+</ body >
+</ html >
go package main
+
+import (
+ " html/template "
+ " net/http "
+)
+
+func tmpl (w http.ResponseWriter, r * http.Request) {
+ t1, err := template. ParseFiles ( "test.html" )
+ if err != nil {
+ panic (err)
+ }
+ t1. Execute (w, "hello world" )
+}
+
+func main () {
+ server := http.Server{
+ Addr: "127.0.0.1:8080" ,
+ }
+ http. HandleFunc ( "/" , tmpl)
+ server. ListenAndServe ()
+}
package main
+
+import (
+ " html/template "
+ " net/http "
+)
+
+func tmpl (w http.ResponseWriter, r * http.Request) {
+ t1, err := template. ParseFiles ( "test.html" )
+ if err != nil {
+ panic (err)
+ }
+ t1. Execute (w, "hello world" )
+}
+
+func main () {
+ server := http.Server{
+ Addr: "127.0.0.1:8080" ,
+ }
+ http. HandleFunc ( "/" , tmpl)
+ server. ListenAndServe ()
+}
.
和作用域 在写template的时候,会经常用到"."。
在template中,点"."代表当前作用域的当前对象 。它类似于java/c++的this关键字,类似于perl/python的self。
go type Person struct {
+ Name string
+ Age int
+}
+
+func main (){
+ p := Person{ "leo" , 23 }
+ tmpl, _ := template. New ( "test" ). Parse ( "Name: {{.Name}}, Age: {{.Age}}" )
+ _ = tmpl. Execute (os.Stdout, p)
+}
+// Name: leo, Age: 23
type Person struct {
+ Name string
+ Age int
+}
+
+func main (){
+ p := Person{ "leo" , 23 }
+ tmpl, _ := template. New ( "test" ). Parse ( "Name: {{.Name}}, Age: {{.Age}}" )
+ _ = tmpl. Execute (os.Stdout, p)
+}
+// Name: leo, Age: 23
但是并非只有一个顶级作用域,range、with、if等内置action都有自己的本地作用域。
go package main
+
+import (
+ " os "
+ " text/template "
+)
+
+type Friend struct {
+ Fname string
+}
+type Person struct {
+ UserName string
+ Emails [] string
+ Friends [] * Friend
+}
+
+func main () {
+ f1 := Friend{Fname: "xiaofang" }
+ f2 := Friend{Fname: "wugui" }
+ t := template. New ( "test" )
+ t = template. Must (t. Parse (
+ \`hello {{.UserName}}!
+{{ range .Emails }}
+an email {{ . }}
+{{- end }}
+{{ with .Friends }}
+{{- range . }}
+my friend name is {{.Fname}}
+{{- end }}
+{{ end }}\` ))
+ p := Person{UserName: "test" ,
+ Emails: [] string { "a1@qq.com" , "a2@gmail.com" },
+ Friends: [] * Friend{ & f1, & f2}}
+ t. Execute (os.Stdout, p)
+}
package main
+
+import (
+ " os "
+ " text/template "
+)
+
+type Friend struct {
+ Fname string
+}
+type Person struct {
+ UserName string
+ Emails [] string
+ Friends [] * Friend
+}
+
+func main () {
+ f1 := Friend{Fname: "xiaofang" }
+ f2 := Friend{Fname: "wugui" }
+ t := template. New ( "test" )
+ t = template. Must (t. Parse (
+ \`hello {{.UserName}}!
+{{ range .Emails }}
+an email {{ . }}
+{{- end }}
+{{ with .Friends }}
+{{- range . }}
+my friend name is {{.Fname}}
+{{- end }}
+{{ end }}\` ))
+ p := Person{UserName: "test" ,
+ Emails: [] string { "a1@qq.com" , "a2@gmail.com" },
+ Friends: [] * Friend{ & f1, & f2}}
+ t. Execute (os.Stdout, p)
+}
输出:
hello test!
+
+an email a1@qq.com
+an email a2@gmail.com
+
+my friend name is xiaofang
+my friend name is wugui
hello test!
+
+an email a1@qq.com
+an email a2@gmail.com
+
+my friend name is xiaofang
+my friend name is wugui
去除空白 template引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以, 对于要解析的内容,不要随意缩进、随意换行 。
go //可以在\`{{</span>\`符号的后面加上短横线并保留一个或多个空格"- "来去除它前面的空白(包括换行符、制表符、空格等)
+//,即\`{{- xxxx\`。
+//在\`}}\`的前面加上一个或多个空格以及一个短横线"-"来去除它后面的空白,即\`xxxx -}}\`。
+
+{{ 23 }} < {{ 45 }} -> 23 < 45
+{{ 23 }} < {{ - 45 }} -> 23 < 45
+{{ 23 - }} < {{ 45 }} -> 23 < 45
+{{ 23 - }} < {{ - 45 }} -> 23 < 45
//可以在\`{{</span>\`符号的后面加上短横线并保留一个或多个空格"- "来去除它前面的空白(包括换行符、制表符、空格等)
+//,即\`{{- xxxx\`。
+//在\`}}\`的前面加上一个或多个空格以及一个短横线"-"来去除它后面的空白,即\`xxxx -}}\`。
+
+{{ 23 }} < {{ 45 }} -> 23 < 45
+{{ 23 }} < {{ - 45 }} -> 23 < 45
+{{ 23 - }} < {{ 45 }} -> 23 < 45
+{{ 23 - }} < {{ - 45 }} -> 23 < 45
上面的例子
go t. Parse (
+\`hello {{.UserName}}!
+{{ range .Emails }}
+an email {{ . }}
+{{- end }}
+{{ with .Friends }}
+{{- range . }}
+my friend name is {{.Fname}}
+{{- end }}
+{{ end }}\` )
t. Parse (
+\`hello {{.UserName}}!
+{{ range .Emails }}
+an email {{ . }}
+{{- end }}
+{{ with .Friends }}
+{{- range . }}
+my friend name is {{.Fname}}
+{{- end }}
+{{ end }}\` )
注意,上面没有进行缩进。因为缩进的制表符或空格在替换的时候会保留。
注释 注释方式:
go {{ /* a comment */ }}
{{ /* a comment */ }}
注释后的内容不会被引擎进行替换。但需要注意,注释行在替换的时候也会占用行,所以应该去除前缀和后缀空白,否则会多一空行。
go {{ - /* a comment without prefix/suffix space */ }}
+{{ /* a comment without prefix/suffix space */ - }}
+{{ - /* a comment without prefix/suffix space */ - }}
{{ - /* a comment without prefix/suffix space */ }}
+{{ /* a comment without prefix/suffix space */ - }}
+{{ - /* a comment without prefix/suffix space */ - }}
注意,应该只去除前缀或后缀空白,不要同时都去除,否则会破坏原有的格式。
管道pipeline pipeline是指产生数据的操作。
可以使用管道符号|
链接多个命令,用法和unix下的管道类似:|
前面的命令将运算结果(或返回值)传递给后一个命令的最后一个位置。
例如:
go {{.}} | printf " %s\\n " "abcd"
{{.}} | printf " %s\\n " "abcd"
命令可以有超过1个的返回值,这时第二个返回值必须为err类型。
需要注意的是,并非只有使用了|
才是pipeline。Go template中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。这使得某些操作可以作为另一些操作内部的表达式先运行得到结果,就像是Unix下的命令替换一样。
例如,下面的(len "output")
是pipeline,它整体先运行。
go {{println (len "output" )}}
{{println (len "output" )}}
下面是Pipeline的几种示例,它们都输出"output"
:
go {{ \`"output"\` }}
+{{printf " %q " "output" }}
+{{ "output" | printf " %q " }}
+{{printf " %q " (print "out" "put" )}}
+{{ "put" | printf " %s%s " "out" | printf " %q " }}
+{{ "output" | printf " %s " | printf " %q " }}
{{ \`"output"\` }}
+{{printf " %q " "output" }}
+{{ "output" | printf " %q " }}
+{{printf " %q " (print "out" "put" )}}
+{{ "put" | printf " %s%s " "out" | printf " %q " }}
+{{ "output" | printf " %s " | printf " %q " }}
变量 可以在template中定义变量:
go // 未定义过的变量
+$ var : = pipeline
+
+// 已定义过的变量
+$ var = pipeline
// 未定义过的变量
+$ var : = pipeline
+
+// 已定义过的变量
+$ var = pipeline
go {{ - $how_long := (len "output" )}}
+{{ - println $how_long}} // 输出6
{{ - $how_long := (len "output" )}}
+{{ - println $how_long}} // 输出6
go tx := template. Must (template. New ( "hh" ). Parse (
+\`{{range $x := . -}}
+{{$y := 333}}
+{{- if (gt $x 33)}}{{println $x $y ($z := 444)}}{{- end}}
+{{- end}}
+\` ))
+s := [] int { 11 , 22 , 33 , 44 , 55 }
+_ = tx. Execute (os.Stdout, s)
+//44 333 444
+//55 333 444
tx := template. Must (template. New ( "hh" ). Parse (
+\`{{range $x := . -}}
+{{$y := 333}}
+{{- if (gt $x 33)}}{{println $x $y ($z := 444)}}{{- end}}
+{{- end}}
+\` ))
+s := [] int { 11 , 22 , 33 , 44 , 55 }
+_ = tx. Execute (os.Stdout, s)
+//44 333 444
+//55 333 444
上面的示例中,使用range迭代slice,每个元素都被赋值给变量$x
,每次迭代过程中,都新设置一个变量$y
,在内层嵌套的if结构中,可以使用这个两个外层的变量。在if的条件表达式中,使用了一个内置的比较函数gt,如果$x
大于33,则为true。在println的参数中还定义了一个$z
,之所以能定义,是因为($z := 444)
的过程是一个Pipeline,可以先运行。
需要注意三点:
变量有作用域,只要出现end,则当前层次的作用域结束。内层可以访问外层变量,但外层不能访问内层变量 。有一个特殊变量$
,它代表模板的最顶级作用域对象(通俗地理解,是以模板为全局作用域的全局变量),在Execute() 执行的时候进行赋值,且一直不变 。例如上面的示例中,$ = [11 22 33 44 55]
。再例如,define定义了一个模板t1,则t1中的$
作用域只属于这个t1。变量不可在模板之间继承 。普通变量可能比较容易理解,但对于特殊变量"."和"$",比较容易搞混。见下面的例子。条件判断 go {{ if pipeline}} T1 {{end}}
+{{ if pipeline}} T1 {{ else }} T0 {{end}}
+{{ if pipeline}} T1 {{ else if pipeline}} T0 {{end}}
+{{ if pipeline}} T1 {{ else }}{{ if pipeline}} T0 {{end}}{{end}}
{{ if pipeline}} T1 {{end}}
+{{ if pipeline}} T1 {{ else }} T0 {{end}}
+{{ if pipeline}} T1 {{ else if pipeline}} T0 {{end}}
+{{ if pipeline}} T1 {{ else }}{{ if pipeline}} T0 {{end}}{{end}}
需要注意的是,pipeline为false的情况是各种数据对象的0值:数值0,指针或接口是nil,数组、slice、map或string则是len为0。
range...end迭代 有两种迭代表达式
go {{ range pipeline}} T1 {{end}}
+{{ range pipeline}} T1 {{ else }} T0 {{end}}
{{ range pipeline}} T1 {{end}}
+{{ range pipeline}} T1 {{ else }} T0 {{end}}
range可以迭代slice、数组、map或channel。迭代的时候,会设置"."为当前正在迭代的元素。
对于第一个表达式,当迭代对象的值为0值时,则range直接跳过,就像if一样。对于第二个表达式,则在迭代到0值时执行else语句。
go tx := template. Must (template. New ( "hh" ). Parse (
+\`{{range $x := . -}}
+{{println $x}}
+{{- end}}
+\` ))
+s := [] int { 11 , 22 , 33 , 44 , 55 }
+_ = tx. Execute (os.Stdout, s)
tx := template. Must (template. New ( "hh" ). Parse (
+\`{{range $x := . -}}
+{{println $x}}
+{{- end}}
+\` ))
+s := [] int { 11 , 22 , 33 , 44 , 55 }
+_ = tx. Execute (os.Stdout, s)
需注意的是,range的参数部分是pipeline,所以在迭代的过程中是可以进行赋值的。但有两种赋值情况:
go {{ range $value := .}}
+{{ range $key,$value := .}}
{{ range $value := .}}
+{{ range $key,$value := .}}
如果range中只赋值给一个变量,则这个变量是当前正在迭代元素的值。如果赋值给两个变量,则第一个变量是索引值( map/slice是数值,map是key),第二个变量是当前正在迭代元素的值。
with...end with用来设置"."的值 。两种格式:
go {{with pipeline}} T1 {{end}}
+{{with pipeline}} T1 {{ else }} T0 {{end}}
{{with pipeline}} T1 {{end}}
+{{with pipeline}} T1 {{ else }} T0 {{end}}
对于第一种格式,当pipeline不为0值的时候,点"." 设置为pipeline运算的值,否则跳过。对于第二种格式,当pipeline为0值时,执行else语句块,否则"."设置为pipeline运算的值,并执行T1。
go {{with "xx" }}{{println .}}{{end}}
{{with "xx" }}{{println .}}{{end}}
上面将输出xx
,因为"."已经设置为"xx"。
内置函数和自定义函数 template定义了一些内置函数,也支持自定义函数
go and
+ 返回第一个为空的参数或最后一个参数。可以有任意多个参数。
+ and x y等价于if x then y else x
+
+not
+ 布尔取反。只能一个参数。
+
+or
+ 返回第一个不为空的参数或最后一个参数。可以有任意多个参数。
+ "or x y" 等价于 "if x then x else y" 。
+
+print
+printf
+println
+ 分别等价于fmt包中的Sprint、Sprintf、Sprintln
+
+len
+ 返回参数的length。
+
+index
+ 对可索引对象进行索引取值。第一个参数是索引对象,后面的参数是索引位。
+ "index x 1 2 3" 代表的是x[ 1 ][ 2 ][ 3 ]。
+ 可索引对象包括map、slice、array。
+
+call
+ 显式调用函数。第一个参数必须是函数类型,且不是template中的函数,而是外部函数。
+ 例如一个struct中的某个字段是func类型的。
+ "call .X.Y 1 2" 表示调用dot.X. Y ( 1 , 2 ),Y必须是func类型,函数参数是1和2。
+ 函数必须只能有一个或2个返回值,如果有第二个返回值,则必须为error类型。
and
+ 返回第一个为空的参数或最后一个参数。可以有任意多个参数。
+ and x y等价于if x then y else x
+
+not
+ 布尔取反。只能一个参数。
+
+or
+ 返回第一个不为空的参数或最后一个参数。可以有任意多个参数。
+ "or x y" 等价于 "if x then x else y" 。
+
+print
+printf
+println
+ 分别等价于fmt包中的Sprint、Sprintf、Sprintln
+
+len
+ 返回参数的length。
+
+index
+ 对可索引对象进行索引取值。第一个参数是索引对象,后面的参数是索引位。
+ "index x 1 2 3" 代表的是x[ 1 ][ 2 ][ 3 ]。
+ 可索引对象包括map、slice、array。
+
+call
+ 显式调用函数。第一个参数必须是函数类型,且不是template中的函数,而是外部函数。
+ 例如一个struct中的某个字段是func类型的。
+ "call .X.Y 1 2" 表示调用dot.X. Y ( 1 , 2 ),Y必须是func类型,函数参数是1和2。
+ 函数必须只能有一个或2个返回值,如果有第二个返回值,则必须为error类型。
go eq arg1 arg2:
+ arg1 == arg2时为true
+ne arg1 arg2:
+ arg1 != arg2时为true
+lt arg1 arg2:
+ arg1 < arg2时为true
+le arg1 arg2:
+ arg1 <= arg2时为true
+gt arg1 arg2:
+ arg1 > arg2时为true
+ge arg1 arg2:
+ arg1 >= arg2时为true
eq arg1 arg2:
+ arg1 == arg2时为true
+ne arg1 arg2:
+ arg1 != arg2时为true
+lt arg1 arg2:
+ arg1 < arg2时为true
+le arg1 arg2:
+ arg1 <= arg2时为true
+gt arg1 arg2:
+ arg1 > arg2时为true
+ge arg1 arg2:
+ arg1 >= arg2时为true
对于eq函数,支持多个参数,它们都和第一个参数arg1进行比较。它等价于:
go eq arg1 arg2 arg3 arg4 ...
+arg1 == arg2 || arg1 == arg3 || arg1 == arg4
eq arg1 arg2 arg3 arg4 ...
+arg1 == arg2 || arg1 == arg3 || arg1 == arg4
嵌套template:define和template define可以直接在待解析内容中定义一个模板,这个模板会加入到common结构组中,并关联到关联名称上。
定义了模板之后,可以使用template这个action来执行模板。template有两种格式:
go {{template "name" }}
+{{template "name" pipeline}}
{{template "name" }}
+{{template "name" pipeline}}
第一种是直接执行名为name的template,点设置为nil。第二种是点"."设置为pipeline的值,并执行名为name的template。可以将template看作是函数:
go template ( "name)
+template(" name ",pipeline)
template ( "name)
+template(" name ",pipeline)
go func main () {
+ t1 := template. New ( "test1" )
+ tmpl, _ := t1. Parse (
+\`{{- define "T1"}}ONE {{println .}}{{end}}
+{{- define "T2"}}TWO {{println .}}{{end}}
+{{- define "T3"}}{{template "T1"}}{{template "T2" "haha"}}{{end}}
+{{- template "T3" -}}
+\` )
+ _ = tmpl. Execute (os.Stdout, "hello world" )
+}
+//ONE <nil>
+//TWO haha
func main () {
+ t1 := template. New ( "test1" )
+ tmpl, _ := t1. Parse (
+\`{{- define "T1"}}ONE {{println .}}{{end}}
+{{- define "T2"}}TWO {{println .}}{{end}}
+{{- define "T3"}}{{template "T1"}}{{template "T2" "haha"}}{{end}}
+{{- template "T3" -}}
+\` )
+ _ = tmpl. Execute (os.Stdout, "hello world" )
+}
+//ONE <nil>
+//TWO haha
block块 go {{block "name" pipeline}} T1 {{end}}
+ A block is shorthand for defining a template
+ {{define "name" }} T1 {{end}}
+ and then executing it in place
+ {{template "name" pipeline}}
+ The typical use is to define a set of root templates that are
+ then customized by redefining the block templates within.
{{block "name" pipeline}} T1 {{end}}
+ A block is shorthand for defining a template
+ {{define "name" }} T1 {{end}}
+ and then executing it in place
+ {{template "name" pipeline}}
+ The typical use is to define a set of root templates that are
+ then customized by redefining the block templates within.
根据官方文档的解释:block等价于define定义一个名为name的模板,并在"有需要"的地方执行这个模板,执行时将"."设置为pipeline的值。
但应该注意,**block的第一个动作是执行名为name的模板,如果不存在,则在此处自动定义这个模板,并执行这个临时定义的模板。换句话说,block可以认为是设置一个默认模板 **。
例如:
go {{block "T1" .}} one {{end}}
{{block "T1" .}} one {{end}}
它首先找到T1模板,如果T1存在,则执行找到的T1,如果没找到T1,则临时定义一个,并执行它。
不转义 上下文感知的自动转义能让程序更加安全,比如防止XSS攻击(例如在表单中输入带有<script>...</script>
的内容并提交,会使得用户提交的这部分script被执行)。
如果确实不想转义,可以进行类型转换。
go type CSS
+type HTML
+type JS
+type URL
type CSS
+type HTML
+type JS
+type URL
go func process (w http.ResponseWriter, r * http.Request) {
+ t, _ := template. ParseFiles ( "tmpl.html" )
+ t. Execute (w, template. HTML (r. FormValue ( "comment" )))
+}
func process (w http.ResponseWriter, r * http.Request) {
+ t, _ := template. ParseFiles ( "tmpl.html" )
+ t. Execute (w, template. HTML (r. FormValue ( "comment" )))
+}
`,88),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const g=s(l,[["render",t]]);export{d as __pageData,g as default};
diff --git a/assets/golang_base_go-template.md.498dda04.lean.js b/assets/golang_base_go-template.md.498dda04.lean.js
new file mode 100644
index 000000000..72d2377f4
--- /dev/null
+++ b/assets/golang_base_go-template.md.498dda04.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as p}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"Go Template","description":"","frontmatter":{},"headers":[],"relativePath":"golang/base/go-template.md","filePath":"golang/base/go-template.md","lastUpdated":1694368780000}'),l={name:"golang/base/go-template.md"},o=p("",88),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const g=s(l,[["render",t]]);export{d as __pageData,g as default};
diff --git a/assets/golang_base_golang-syntax.md.4fddf075.js b/assets/golang_base_golang-syntax.md.4fddf075.js
new file mode 100644
index 000000000..b992b6403
--- /dev/null
+++ b/assets/golang_base_golang-syntax.md.4fddf075.js
@@ -0,0 +1,5003 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"Golang基础语法","description":"","frontmatter":{},"headers":[],"relativePath":"golang/base/golang-syntax.md","filePath":"golang/base/golang-syntax.md","lastUpdated":1694368780000}'),p={name:"golang/base/golang-syntax.md"},o=l(`Golang基础语法 第一个Go程序 go package main
+
+import " fmt "
+
+func main () {
+ fmt. Println ( "Hello, World!" )
+}
package main
+
+import " fmt "
+
+func main () {
+ fmt. Println ( "Hello, World!" )
+}
关键字 package
:定义当前源码文件所属的包。import
:导入其他包。func
:定义函数。var
:声明变量。const
:声明常量。type
:定义类型。struct
:定义结构体。interface
:定义接口。map
:定义映射类型。range
:用于循环迭代。select
:用于通道操作。defer
:延迟执行。go
:启动一个新的 goroutine。chan
:定义通道类型。default
:select 语句中的默认情况。fallthrough
:在 switch 语句中贯穿到下一个 case。if
:条件语句。else
:if 语句中的默认情况。switch
:多分支条件语句。case
:switch 语句中的分支情况。for
:循环语句。break
:跳出循环或 switch 语句。continue
:结束当前循环,开始下一次循环。return
:返回函数结果。panic
:抛出异常。变量与常量 变量 声明变量不赋值 go package main
+
+import " fmt "
+
+func main () {
+ var a int
+ fmt. Println ( "a = " , a)
+ fmt. Printf ( "a的类型是 %T\\n " , a)
+}
+// a = 0
+// a的类型是int
package main
+
+import " fmt "
+
+func main () {
+ var a int
+ fmt. Println ( "a = " , a)
+ fmt. Printf ( "a的类型是 %T\\n " , a)
+}
+// a = 0
+// a的类型是int
整型、浮点型变量的默认值为0和0.0 字符串变量的默认值为空字符串 布尔类型变量默认为false 切片、函数、指针变量的默认为nil 声明变量并初始化 go package main
+
+import " fmt "
+
+func main () {
+ var a int = 10
+ fmt. Println ( "a =" , a)
+ fmt. Printf ( "a的类型是 %T\\n " , a)
+
+ var b string = "hello"
+ fmt. Println ( "b =" , b)
+ fmt. Printf ( "b的类型是 %T\\n " , b)
+}
+// a = 10
+// a的类型是int
+// b = hello
+// b的类型是string
package main
+
+import " fmt "
+
+func main () {
+ var a int = 10
+ fmt. Println ( "a =" , a)
+ fmt. Printf ( "a的类型是 %T\\n " , a)
+
+ var b string = "hello"
+ fmt. Println ( "b =" , b)
+ fmt. Printf ( "b的类型是 %T\\n " , b)
+}
+// a = 10
+// a的类型是int
+// b = hello
+// b的类型是string
声明变量省略类型 go package main
+
+import " fmt "
+
+func main () {
+ var a = 10
+ fmt. Println ( "a =" , a)
+ fmt. Printf ( "a的类型是 %T\\n " , a)
+
+ var b = "hello"
+ fmt. Println ( "b =" , b)
+ fmt. Printf ( "b的类型是 %T\\n " , b)
+}
+// a = 10
+// a的类型是int
+// b = hello
+// b的类型是string
package main
+
+import " fmt "
+
+func main () {
+ var a = 10
+ fmt. Println ( "a =" , a)
+ fmt. Printf ( "a的类型是 %T\\n " , a)
+
+ var b = "hello"
+ fmt. Println ( "b =" , b)
+ fmt. Printf ( "b的类型是 %T\\n " , b)
+}
+// a = 10
+// a的类型是int
+// b = hello
+// b的类型是string
短声明(只能在函数内) go package main
+
+import " fmt "
+
+func main () {
+ c := "1"
+ fmt. Printf ( "c = %s , c的类型是 %T\\n " , c, c)
+}
+// c = 1, c的类型是string
package main
+
+import " fmt "
+
+func main () {
+ c := "1"
+ fmt. Printf ( "c = %s , c的类型是 %T\\n " , c, c)
+}
+// c = 1, c的类型是string
多变量声明 go package main
+
+func main (){
+ var xx, yy int = 100 , 200
+ var kk, wx = 300 , "666
+ var (
+ nn int = 100
+ mm bool = true
+ )
+}
package main
+
+func main (){
+ var xx, yy int = 100 , 200
+ var kk, wx = 300 , "666
+ var (
+ nn int = 100
+ mm bool = true
+ )
+}
匿名变量 匿名变量:下划线_
,本身就是一个特殊的标识符。可以像其他标识符那样用于变量的声明,任何类型都可以赋值给它,但任何赋值给这个标识符的值都将被抛弃 ,因此这些值不能在后续的代码中使用。
变量交换 和其他静态类型语言不同,可以直接交换变量(python也有这个语法)
go package main
+
+import " fmt "
+
+func main () {
+ var (
+ a = 100
+ b = 200
+ )
+
+ a, b = b, a
+
+ fmt. Println (a, b)
+}
+// 200 100
package main
+
+import " fmt "
+
+func main () {
+ var (
+ a = 100
+ b = 200
+ )
+
+ a, b = b, a
+
+ fmt. Println (a, b)
+}
+// 200 100
常量 go package main
+
+import " fmt "
+
+func main (){
+ // 常量(只读属性)
+ const length int = 10
+ // length = 100 // 常量是不允许被修改的
+ fmt. Println ( "length = " , length)
+}
package main
+
+import " fmt "
+
+func main (){
+ // 常量(只读属性)
+ const length int = 10
+ // length = 100 // 常量是不允许被修改的
+ fmt. Println ( "length = " , length)
+}
使用常量定义枚举类型 go package main
+
+import " fmt "
+
+// const来定义枚举类型
+const (
+ BEIJING = 0
+ SHANGHAI = 1
+ SHENZHEN = 2
+)
+
+func main () {
+ fmt. Println ( "BEIJING = " , BEIJING) // 0
+ fmt. Println ( "SHANGHAI = " , SHANGHAI) // 1
+ fmt. Println ( "SHENZHEN = " , SHENZHEN) // 2
+}
package main
+
+import " fmt "
+
+// const来定义枚举类型
+const (
+ BEIJING = 0
+ SHANGHAI = 1
+ SHENZHEN = 2
+)
+
+func main () {
+ fmt. Println ( "BEIJING = " , BEIJING) // 0
+ fmt. Println ( "SHANGHAI = " , SHANGHAI) // 1
+ fmt. Println ( "SHENZHEN = " , SHENZHEN) // 2
+}
iota常量计数器 iota
是一个常量生成器,用于生成一组相关的枚举值。iota
可以与 const
关键字一起使用,在定义一组枚举时,用来生成连续的值。const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)
go // iota 初始值为 0,每当出现一个新的常量声明时,它的值就会自动加 1,因此 Monday 的值为 1,Tuesday 的值为 2,以此类推。
+const (
+ Sunday = iota // 0
+ Monday // 1
+ Tuesday // 2
+ Wednesday // 3
+ Thursday // 4
+ Friday // 5
+ Saturday // 6
+)
+
+// 在下面的例子中,B 被显式赋值为 3.14,因此接下来的 C 的值为 iota + 1,即 2,而 D 的值也是 iota + 1,所以它的值为 3。
+const (
+ A = iota // 0
+ B = 3.14 // 3.14
+ C = iota // 2
+ D // 3
+)
// iota 初始值为 0,每当出现一个新的常量声明时,它的值就会自动加 1,因此 Monday 的值为 1,Tuesday 的值为 2,以此类推。
+const (
+ Sunday = iota // 0
+ Monday // 1
+ Tuesday // 2
+ Wednesday // 3
+ Thursday // 4
+ Friday // 5
+ Saturday // 6
+)
+
+// 在下面的例子中,B 被显式赋值为 3.14,因此接下来的 C 的值为 iota + 1,即 2,而 D 的值也是 iota + 1,所以它的值为 3。
+const (
+ A = iota // 0
+ B = 3.14 // 3.14
+ C = iota // 2
+ D // 3
+)
go package main
+
+import " fmt "
+
+// 定义递增的步长
+const (
+ BEIJING = iota * 10
+ SHANGHAI
+ SHENZHEN
+)
+
+func main () {
+ fmt. Println ( "BEIJING = " , BEIJING) // 0
+ fmt. Println ( "SHANGHAI = " , SHANGHAI) // 10
+ fmt. Println ( "SHENZHEN = " , SHENZHEN) // 20
+}
package main
+
+import " fmt "
+
+// 定义递增的步长
+const (
+ BEIJING = iota * 10
+ SHANGHAI
+ SHENZHEN
+)
+
+func main () {
+ fmt. Println ( "BEIJING = " , BEIJING) // 0
+ fmt. Println ( "SHANGHAI = " , SHANGHAI) // 10
+ fmt. Println ( "SHENZHEN = " , SHENZHEN) // 20
+}
基本数据类型 整型 int8:有符号 8 位整数类型,取值范围为 -128 到 127。 uint8(或 byte):无符号 8 位整数类型,取值范围为 0 到 255。 int16:有符号 16 位整数类型,取值范围为 -32768 到 32767。 uint16:无符号 16 位整数类型,取值范围为 0 到 65535。 int32(或 rune):有符号 32 位整数类型,取值范围为 -2147483648 到 2147483647。 uint32:无符号 32 位整数类型,取值范围为 0 到 4294967295。 int64:有符号 64 位整数类型,取值范围为 -9223372036854775808 到 9223372036854775807。 uint64:无符号 64 位整数类型,取值范围为 0 到 18446744073709551615。 go package main
+
+import (
+ " fmt "
+ " math "
+ " unsafe "
+)
+
+// 有符号整型
+func Integer () {
+ var num8 int8 = 127
+ var num16 int16 = 32767
+ var num32 int32 = math.MaxInt32
+ var num64 int64 = math.MaxInt64
+ var num int = math.MaxInt
+ fmt. Printf ( "num8的类型是 %T , num8的大小 %d , num8是 %d\\n " ,
+ num8, unsafe. Sizeof (num8), num8)
+ fmt. Printf ( "num16的类型是 %T , num16的大小 %d , num16是 %d\\n " ,
+ num16, unsafe. Sizeof (num16), num16)
+ fmt. Printf ( "num32的类型是 %T , num32的大小 %d , num32是 %d\\n " ,
+ num32, unsafe. Sizeof (num32), num32)
+ fmt. Printf ( "num64的类型是 %T , num64的大小 %d , num64是 %d\\n " ,
+ num64, unsafe. Sizeof (num64), num64)
+ fmt. Printf ( "num的类型是 %T , num的大小 %d , num是 %d\\n " ,
+ num, unsafe. Sizeof (num), num)
+}
+
+// 无符号整型
+func unsignedInteger () {
+ var num8 uint8 = 128
+ var num16 uint16 = 32768
+ var num32 uint32 = math.MaxUint32
+ var num64 uint64 = math.MaxUint64
+ var num uint = math.MaxUint
+ fmt. Printf ( "num8的类型是 %T , num8的大小 %d , num8是 %d\\n " ,
+ num8, unsafe. Sizeof (num8), num8)
+ fmt. Printf ( "num16的类型是 %T , num16的大小 %d , num16是 %d\\n " ,
+ num16, unsafe. Sizeof (num16), num16)
+ fmt. Printf ( "num32的类型是 %T , num32的大小 %d , num32是 %d\\n " ,
+ num32, unsafe. Sizeof (num32), num32)
+ fmt. Printf ( "num64的类型是 %T , num64的大小 %d , num64是 %d\\n " ,
+ num64, unsafe. Sizeof (num64), num64)
+ fmt. Printf ( "num的类型是 %T , num的大小 %d , num是 %d\\n " ,
+ num, unsafe. Sizeof (num), num)
+}
+
+func main () {
+ Integer ()
+ println ( "---------------------------------------" )
+ unsignedInteger ()
+}
package main
+
+import (
+ " fmt "
+ " math "
+ " unsafe "
+)
+
+// 有符号整型
+func Integer () {
+ var num8 int8 = 127
+ var num16 int16 = 32767
+ var num32 int32 = math.MaxInt32
+ var num64 int64 = math.MaxInt64
+ var num int = math.MaxInt
+ fmt. Printf ( "num8的类型是 %T , num8的大小 %d , num8是 %d\\n " ,
+ num8, unsafe. Sizeof (num8), num8)
+ fmt. Printf ( "num16的类型是 %T , num16的大小 %d , num16是 %d\\n " ,
+ num16, unsafe. Sizeof (num16), num16)
+ fmt. Printf ( "num32的类型是 %T , num32的大小 %d , num32是 %d\\n " ,
+ num32, unsafe. Sizeof (num32), num32)
+ fmt. Printf ( "num64的类型是 %T , num64的大小 %d , num64是 %d\\n " ,
+ num64, unsafe. Sizeof (num64), num64)
+ fmt. Printf ( "num的类型是 %T , num的大小 %d , num是 %d\\n " ,
+ num, unsafe. Sizeof (num), num)
+}
+
+// 无符号整型
+func unsignedInteger () {
+ var num8 uint8 = 128
+ var num16 uint16 = 32768
+ var num32 uint32 = math.MaxUint32
+ var num64 uint64 = math.MaxUint64
+ var num uint = math.MaxUint
+ fmt. Printf ( "num8的类型是 %T , num8的大小 %d , num8是 %d\\n " ,
+ num8, unsafe. Sizeof (num8), num8)
+ fmt. Printf ( "num16的类型是 %T , num16的大小 %d , num16是 %d\\n " ,
+ num16, unsafe. Sizeof (num16), num16)
+ fmt. Printf ( "num32的类型是 %T , num32的大小 %d , num32是 %d\\n " ,
+ num32, unsafe. Sizeof (num32), num32)
+ fmt. Printf ( "num64的类型是 %T , num64的大小 %d , num64是 %d\\n " ,
+ num64, unsafe. Sizeof (num64), num64)
+ fmt. Printf ( "num的类型是 %T , num的大小 %d , num是 %d\\n " ,
+ num, unsafe. Sizeof (num), num)
+}
+
+func main () {
+ Integer ()
+ println ( "---------------------------------------" )
+ unsignedInteger ()
+}
TIP
除非对整型的大小有特定的需求,否则你通常应该使用 int
表示整型宽度,在 32
位系统下是 32
位,而在 64
位系统下是 64
位。表示范围:在 32
位系统下是 -2147483648
~ 2147483647
,而在 64
位系统是 -9223372036854775808
~ 9223372036854775807
。 对于 int8
, int16
等这些类型后面有跟一个数值的类型来说,它们能表示的数值个数是固定的。所以,在有的时候:例如在二进制传输、读写文件的结构描述(为了保持文件的结构不会受到不同编译目标平台字节长度的影响)等情况下,使用更加精确的 int32
和 int64
是更好的。 浮点型 Go 语言中的浮点数默认为 float64
类型,如果需要使用 float32
类型,需要显式声明。
go package main
+
+import (
+ " fmt "
+ " math "
+)
+
+func showFloat () {
+ var num1 float32 = math.MaxFloat32
+ var num2 float64 = math.MaxFloat64
+ fmt. Printf ( "num1的类型是 %T ,num1是 %g\\n " , num1, num1)
+ fmt. Printf ( "num2的类型是 %T ,num1是 %g\\n " , num2, num2)
+}
+
+func main () {
+ showFloat ()
+}
+//num1的类型是float32,num1是3.4028235e+38
+//num2的类型是float64,num1是1.7976931348623157e+308
package main
+
+import (
+ " fmt "
+ " math "
+)
+
+func showFloat () {
+ var num1 float32 = math.MaxFloat32
+ var num2 float64 = math.MaxFloat64
+ fmt. Printf ( "num1的类型是 %T ,num1是 %g\\n " , num1, num1)
+ fmt. Printf ( "num2的类型是 %T ,num1是 %g\\n " , num2, num2)
+}
+
+func main () {
+ showFloat ()
+}
+//num1的类型是float32,num1是3.4028235e+38
+//num2的类型是float64,num1是1.7976931348623157e+308
字符 字符串中的每一个元素叫作“字符”,定义字符时使用单引号。Go 语言的字符有两种。
byte
类型,占用1个字节,表示 UTF-8 字符串的单个字节的值,表示的是 ASCII 码表中的一个字符,uint8 的别名类型rune
类型,占用4个字节,表示单个 unicode 字符,int32 的别名类型go package main
+
+import (
+ " fmt "
+ " unsafe "
+)
+
+func showChar () {
+ var x byte = 65
+ var y uint8 = 65
+ z := ' A '
+ fmt. Printf ( "x = %c\\n " , x) // x = A
+ fmt. Printf ( "y = %c\\n " , y) // y = A
+ fmt. Printf ( "z = %c\\n " , z) // z = A
+
+}
+
+func sizeOfChar () {
+ var x byte = 65
+ fmt. Printf ( "x = %c\\n " , x)
+ fmt. Printf ( "x 占用 %d 个字节 \\n " , unsafe. Sizeof (x))
+
+ var y rune = ' A '
+ fmt. Printf ( "y = %c\\n " , y)
+ fmt. Printf ( "y 占用 %d 个字节 \\n " , unsafe. Sizeof (y))
+}
+
+func main () {
+ showChar ()
+ sizeOfChar ()
+}
package main
+
+import (
+ " fmt "
+ " unsafe "
+)
+
+func showChar () {
+ var x byte = 65
+ var y uint8 = 65
+ z := ' A '
+ fmt. Printf ( "x = %c\\n " , x) // x = A
+ fmt. Printf ( "y = %c\\n " , y) // y = A
+ fmt. Printf ( "z = %c\\n " , z) // z = A
+
+}
+
+func sizeOfChar () {
+ var x byte = 65
+ fmt. Printf ( "x = %c\\n " , x)
+ fmt. Printf ( "x 占用 %d 个字节 \\n " , unsafe. Sizeof (x))
+
+ var y rune = ' A '
+ fmt. Printf ( "y = %c\\n " , y)
+ fmt. Printf ( "y 占用 %d 个字节 \\n " , unsafe. Sizeof (y))
+}
+
+func main () {
+ showChar ()
+ sizeOfChar ()
+}
字符串 字符串在Go语言中是基本数据类型。
go var study string // 定义名为str的字符串类型变量
+study = "《123》" // 将变量赋值
+study2 := "《789》" // 以自动推断方式初始化
var study string // 定义名为str的字符串类型变量
+study = "《123》" // 将变量赋值
+study2 := "《789》" // 以自动推断方式初始化
定义多行字符串的方法如下。
双引号书写字符串被称为字符串字面量(string literal),这种字面量不能跨行。 多行字符串需要使用反引号“\`”,多用于内嵌源码和内嵌数据。 在反引号中的所有代码不会被编译器识别,而只是作为字符串的一部分。 go package main
+import " fmt "
+
+func main () {
+ var s1 string
+ s1 = \`
+ study := 'Go语言'
+ fmt.Println(study)
+ \`
+ fmt. Println (s1)
+}
package main
+import " fmt "
+
+func main () {
+ var s1 string
+ s1 = \`
+ study := 'Go语言'
+ fmt.Println(study)
+ \`
+ fmt. Println (s1)
+}
布尔 go func showBool (){
+ a := true
+ b := false
+ fmt. Println ( "a=" , a)
+ fmt. Println ( "b=" , b)
+ fmt. Println ( "true && false = " , a && b)
+ fmt. Println ( "true || false = " , a || b)
+}
+
+func main () {
+ showBool ()
+}
func showBool (){
+ a := true
+ b := false
+ fmt. Println ( "a=" , a)
+ fmt. Println ( "b=" , b)
+ fmt. Println ( "true && false = " , a && b)
+ fmt. Println ( "true || false = " , a || b)
+}
+
+func main () {
+ showBool ()
+}
复数 类 型 字 节 数 说 明 complex64 8 64 位的复数型,由 float32 类型的实部和虚部联合表示 complex128 16 128 位的复数型,由 float64 类型的实部和虚部联合表示
go func showComplex () {
+ // 内置的 complex 函数用于构建复数
+ var x complex64 = complex ( 1 , 2 )
+ var y complex128 = complex ( 3 , 4 )
+ var z complex128 = complex ( 5 , 6 )
+ fmt. Println ( "x = " , x)
+ fmt. Println ( "y = " , y)
+ fmt. Println ( "z = " , z)
+
+ // 内建的 real 和 imag 函数分别返回复数的实部和虚部
+ fmt. Println ( "real(x) = " , real (x))
+ fmt. Println ( "imag(x) = " , imag (x))
+ fmt. Println ( "y * z = " , y * z)
+}
+
+func main () {
+ showComplex ()
+}
func showComplex () {
+ // 内置的 complex 函数用于构建复数
+ var x complex64 = complex ( 1 , 2 )
+ var y complex128 = complex ( 3 , 4 )
+ var z complex128 = complex ( 5 , 6 )
+ fmt. Println ( "x = " , x)
+ fmt. Println ( "y = " , y)
+ fmt. Println ( "z = " , z)
+
+ // 内建的 real 和 imag 函数分别返回复数的实部和虚部
+ fmt. Println ( "real(x) = " , real (x))
+ fmt. Println ( "imag(x) = " , imag (x))
+ fmt. Println ( "y * z = " , y * z)
+}
+
+func main () {
+ showComplex ()
+}
TIP
同样可以用自然方式表示复数
go x := 1 + 2 i
+y := 3 + 4 i
+z := 5 + 6 i
x := 1 + 2 i
+y := 3 + 4 i
+z := 5 + 6 i
fmt格式化输出 格式 含义 %% 一个%字面量 %b 一个二进制整数值(基数为 2),或者是一个(高级的)用科学计数法表示的指数为 2 的浮点数 %c 字符型。可以把输入的数字按照 ASCII 码相应转换为对应的字符 %d 一个十进制数值(基数为 10) %f 以标准记数法表示的浮点数或者复数值 %o 一个以八进制表示的数字(基数为 8) %p 以十六进制(基数为 16)表示的一个值的地址,前缀为 0x,字母使用小写的 a-f 表示 %q 使用 Go 语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字 %s 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\\0‘结尾,这个’\\0’即空字符) %t 以 true 或者 false 输出的布尔值 %T 使用 Go 语法输出的值的类型 %x 以十六进制表示的整型值(基数为十六),数字 a-f 使用小写表示 %X 以十六进制表示的整型值(基数为十六),数字 A-F 使用小写表示
fmt.Print: 将指定的内容打印到标准输出,不换行。
fmt.Println: 将指定的内容打印到标准输出,并在末尾添加换行符。
fmt.Printf: 根据格式字符串将指定的内容格式化后打印到标准输出。
fmt.Sprintf: 根据格式字符串将指定的内容格式化后返回一个格式化的字符串。
fmt.Scan: 从标准输入读取内容,并将其存储到指定的变量中。
fmt.Scanln: 从标准输入按空格分隔读取内容,并将其存储到指定的变量中,遇到换行符停止。
fmt.Scanf: 根据格式字符串从标准输入读取内容,并将其按指定的格式存储到指定的变量中。
fmt.Errorf: 根据格式字符串创建一个新的错误。
运算符 算数运算符 +
、-
、*
、/
、%
、++
、--
关系运算符 ==
、!=
、>
、<
、>=
、<=
逻辑运算符 &&
、||
、!
位运算符 &
、|
、^
、<<
、>>
容器类型 数组 Go 中的数组是值类型而不是引用类型。当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。如果对新变量进行更改,不会影响原始数组。
go func arrByValue () {
+ arr := [ ... ] string { "123" , "456" , "789" }
+ copy := arr
+ copy[ 0 ] = "Golang"
+ fmt. Println (arr)
+ fmt. Println (copy)
+}
func arrByValue () {
+ arr := [ ... ] string { "123" , "456" , "789" }
+ copy := arr
+ copy[ 0 ] = "Golang"
+ fmt. Println (arr)
+ fmt. Println (copy)
+}
声明 var variable_name [SIZE]variable_type
初始化 var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
go var balance = [ ... ] float32 { 1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
+或
+balance := [ ... ] float32 { 1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
var balance = [ ... ] float32 { 1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
+或
+balance := [ ... ] float32 { 1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
go // 将索引为 1 和 3 的元素初始化
+balance := [ 5 ] float32 { 1 : 2.0 , 3 : 7.0 }
// 将索引为 1 和 3 的元素初始化
+balance := [ 5 ] float32 { 1 : 2.0 , 3 : 7.0 }
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
go balance[ 4 ] = 50.0
balance[ 4 ] = 50.0
访问 数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:
go var salary float32 = balance[ 9 ]
var salary float32 = balance[ 9 ]
数组长度 len(arr)
数组遍历 使用for range循环
go func showArr () {
+ arr := [ ... ] string { "123" , "456" , "789" }
+ for index, value := range arr {
+ fmt. Printf ( "arr[ %d ]= %s\\n " , index, value)
+ }
+
+ for _, value := range arr {
+ fmt. Printf ( "value= %s\\n " , value)
+ }
+}
func showArr () {
+ arr := [ ... ] string { "123" , "456" , "789" }
+ for index, value := range arr {
+ fmt. Printf ( "arr[ %d ]= %s\\n " , index, value)
+ }
+
+ for _, value := range arr {
+ fmt. Printf ( "value= %s\\n " , value)
+ }
+}
切片Slice Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
定义切片 go var identifier [] type
var identifier [] type
切片不需要说明长度。
或使用 make() 函数来创建切片:
go var slice1 [] type = make ([]type, len)
+
+也可以简写为
+
+slice1 := make ([]type, len)
var slice1 [] type = make ([]type, len)
+
+也可以简写为
+
+slice1 := make ([]type, len)
也可以指定容量,其中 capacity 为可选参数。
go make ([]T, length, capacity)
make ([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。
切片初始化 直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3 ,其 cap=len=3 。
go s := [] int { 1 , 2 , 3 }
s := [] int { 1 , 2 , 3 }
初始化切片 s ,是数组 arr 的引用。
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
go s := arr[startIndex:endIndex]
s := arr[startIndex:endIndex]
默认 endIndex 时将表示一直到arr的最后一个元素。
go s := arr[startIndex:]
s := arr[startIndex:]
默认 startIndex 时将表示从 arr 的第一个元素开始。
go s := arr[:endIndex]
s := arr[:endIndex]
通过切片 s 初始化切片 s1。
go s1 := s[startIndex:endIndex]
s1 := s[startIndex:endIndex]
通过内置函数 make() 初始化切片s ,[]int 标识为其元素类型为 int 的切片。
go s := make ([] int ,len,cap)
s := make ([] int ,len,cap)
make([]T, length, capacity)
用于创建一个指定类型 T
、长度为 length
、容量为 capacity
的切片。其中,length
表示切片的实际长度,而 capacity
则表示切片底层数组的容量。
切片的容量可以理解为底层数组能够容纳的元素数量。当切片的容量不足以容纳新添加的元素时,Go 会自动将底层数组扩展一倍,并将原有的元素复制到新的数组中。因此,在预先分配足够容量的情况下,可以避免频繁的内存分配和数据复制操作,提高代码的性能。
需要注意的是,capacity
参数不能小于 length
参数。如果 capacity
小于 length
,则会抛出一个运行时异常。
由于 slice 是引用类型,所以你不对它进行赋值的话,它的默认值是 nil
go var numList [] int
+fmt. Println (numList == nil ) // true
var numList [] int
+fmt. Println (numList == nil ) // true
切片之间不能比较,因此我们不能使用 ==
操作符来判断两个 slice 是否含有全部相等元素。特别注意,如果你需要测试一个 slice 是否是空的,使用 len(s) == 0
来判断,而不应该用 s == nil
来判断。 切片的长度和容量 一个 slice 由三个部分构成:指针 、 长度 和 容量 。指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。长度对应 slice 中元素的数目;长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。简单的讲,容量就是从创建切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数。
内置的 len
和 cap
函数分别返回 slice 的长度和容量。
go s := make ([] string , 3 , 5 )
+fmt. Println ( len (s)) // 3
+fmt. Println ( cap (s)) // 5
s := make ([] string , 3 , 5 )
+fmt. Println ( len (s)) // 3
+fmt. Println ( cap (s)) // 5
切片元素修改 切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。
go func modifySlice () {
+ var arr = [ ... ] string { "123" , "456" , "789" }
+ s := arr[:] //[0:len(arr)]
+ fmt. Println (arr)
+ fmt. Println (s)
+
+ s[ 0 ] = "Go语言"
+ fmt. Println (arr)
+ fmt. Println (s)
+}
func modifySlice () {
+ var arr = [ ... ] string { "123" , "456" , "789" }
+ s := arr[:] //[0:len(arr)]
+ fmt. Println (arr)
+ fmt. Println (s)
+
+ s[ 0 ] = "Go语言"
+ fmt. Println (arr)
+ fmt. Println (s)
+}
这里的 arr[:]
没有填入起始值和结束值,默认就是 0
和 len(arr)
。
追加切片元素 使用 append
可以将新元素追加到切片上。append
函数的定义是 func append(slice []Type, elems ...Type) []Type
。其中 elems ...Type
在函数定义中表示该函数接受参数 elems
的个数是可变的。这些类型的函数被称为可变参数。
go func appendSliceData () {
+ s := [] string { "123" }
+ fmt. Println (s)
+ fmt. Println ( cap (s))
+
+ s = append (s, "567" )
+ fmt. Println (s)
+ fmt. Println ( cap (s))
+
+ s = append (s, "789" , "0" )
+ fmt. Println (s)
+ fmt. Println ( cap (s))
+
+ s = append (s, [] string { "1" , "2" } ... )
+ fmt. Println (s)
+ fmt. Println ( cap (s))
+}
func appendSliceData () {
+ s := [] string { "123" }
+ fmt. Println (s)
+ fmt. Println ( cap (s))
+
+ s = append (s, "567" )
+ fmt. Println (s)
+ fmt. Println ( cap (s))
+
+ s = append (s, "789" , "0" )
+ fmt. Println (s)
+ fmt. Println ( cap (s))
+
+ s = append (s, [] string { "1" , "2" } ... )
+ fmt. Println (s)
+ fmt. Println ( cap (s))
+}
当新的元素被添加到切片时,如果容量不足,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回新的引用。现在新切片的容量是旧切片的两倍。
多维切片 类似于数组,切片也可以有多个维度。
go func mSlice () {
+ numList := [][] string {
+ { "1" , "123" },
+ { "2" , "456" },
+ { "3" , "789" },
+ }
+ fmt. Println (numList)
+}
func mSlice () {
+ numList := [][] string {
+ { "1" , "123" },
+ { "2" , "456" },
+ { "3" , "789" },
+ }
+ fmt. Println (numList)
+}
Map 在 Go 语言中,map 是散列表(哈希表)的引用。它是一个拥有键值对元素的无序集合 ,在这个集合中,键是唯一的,可以通过键来获取、更新或移除操作。无论这个散列表有多大,这些操作基本上是通过常量时间完成的。所有可比较的类型,如 整型
,字符串
等,都可以作为 key
。
创建Map 使用 make
函数传入键和值的类型,可以创建 map 。具体语法为 make(map[KeyType]ValueType)
。
go // 创建一个键类型为 string 值类型为 int 名为 scores 的 map
+scores := make ( map [ string ] int )
+steps := make ( map [ string ] string )
// 创建一个键类型为 string 值类型为 int 名为 scores 的 map
+scores := make ( map [ string ] int )
+steps := make ( map [ string ] string )
字面量创建:
go var steps2 map [ string ]string = map [ string ] string {
+ "第一步" : "123" ,
+ "第二步" : "456" ,
+ "第三步" : "789" ,
+}
+fmt. Println (steps2)
var steps2 map [ string ]string = map [ string ] string {
+ "第一步" : "123" ,
+ "第二步" : "456" ,
+ "第三步" : "789" ,
+}
+fmt. Println (steps2)
go steps3 := map [ string ] string {
+ "第一步" : "123" ,
+ "第二步" : "456" ,
+ "第三步" : "789" ,
+}
+fmt. Println (steps3)
steps3 := map [ string ] string {
+ "第一步" : "123" ,
+ "第二步" : "456" ,
+ "第三步" : "789" ,
+}
+fmt. Println (steps3)
Map操作 map是引用类型 当 map
被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。
GO func mapByReference () {
+ steps4 := map [ string ] string {
+ "第一步" : "123" ,
+ "第二步" : "456" ,
+ "第三步" : "789" ,
+ }
+ fmt. Println ( "steps4: " , steps4)
+ // steps4: map[第一步:123 第三步:789 第二步:456]
+ newSteps4 := steps4
+ newSteps4[ "第一步" ] = "123-222"
+ newSteps4[ "第二步" ] = "456-222"
+ newSteps4[ "第三步" ] = "789-222"
+ fmt. Println ( "steps4: " , steps4)
+ // steps4: map[第一步:123-222 第三步:789-222 第二步:456-222]
+ fmt. Println ( "newSteps4: " , newSteps4)
+ // newSteps4: map[第一步:123-222 第三步:789-222 第二步:456-222]
+}
func mapByReference () {
+ steps4 := map [ string ] string {
+ "第一步" : "123" ,
+ "第二步" : "456" ,
+ "第三步" : "789" ,
+ }
+ fmt. Println ( "steps4: " , steps4)
+ // steps4: map[第一步:123 第三步:789 第二步:456]
+ newSteps4 := steps4
+ newSteps4[ "第一步" ] = "123-222"
+ newSteps4[ "第二步" ] = "456-222"
+ newSteps4[ "第三步" ] = "789-222"
+ fmt. Println ( "steps4: " , steps4)
+ // steps4: map[第一步:123-222 第三步:789-222 第二步:456-222]
+ fmt. Println ( "newSteps4: " , newSteps4)
+ // newSteps4: map[第一步:123-222 第三步:789-222 第二步:456-222]
+}
当 map
作为函数参数传递时也会发生同样的情况。
流程控制语句 条件语句 go if 条件1 {
+ 逻辑代码1
+} else if 条件2 {
+ 逻辑代码2
+} else if 条件 ... {
+ 逻辑代码 ...
+} else {
+ 逻辑代码 else
+}
if 条件1 {
+ 逻辑代码1
+} else if 条件2 {
+ 逻辑代码2
+} else if 条件 ... {
+ 逻辑代码 ...
+} else {
+ 逻辑代码 else
+}
go score := 88
+if score >= 90 {
+ fmt. Println ( "成绩等级为A" )
+} else if score >= 80 {
+ fmt. Println ( "成绩等级为B" )
+} else if score >= 70 {
+ fmt. Println ( "成绩等级为C" )
+} else if score >= 60 {
+ fmt. Println ( "成绩等级为D" )
+} else {
+ fmt. Println ( "成绩等级为E 成绩不及格" )
+}
score := 88
+if score >= 90 {
+ fmt. Println ( "成绩等级为A" )
+} else if score >= 80 {
+ fmt. Println ( "成绩等级为B" )
+} else if score >= 70 {
+ fmt. Println ( "成绩等级为C" )
+} else if score >= 60 {
+ fmt. Println ( "成绩等级为D" )
+} else {
+ fmt. Println ( "成绩等级为E 成绩不及格" )
+}
if
还有另外一种写法,它包含一个 statement
可选语句部分,该可选语句在条件判断之前运行。它的语法是:
go if statement; condition {
+}
+
+if score := 88 ; score >= 60 {
+ fmt. Println ( "成绩及格" )
+}
if statement; condition {
+}
+
+if score := 88 ; score >= 60 {
+ fmt. Println ( "成绩及格" )
+}
switch case go switch 表达式 {
+ case 表达式值1:
+ 业务逻辑代码1
+ case 表达式值2:
+ 业务逻辑代码2
+ case 表达式值3:
+ 业务逻辑代码3
+ case 表达式值 ... :
+ 业务逻辑代码 ...
+ default :
+ 业务逻辑代码
+}
switch 表达式 {
+ case 表达式值1:
+ 业务逻辑代码1
+ case 表达式值2:
+ 业务逻辑代码2
+ case 表达式值3:
+ 业务逻辑代码3
+ case 表达式值 ... :
+ 业务逻辑代码 ...
+ default :
+ 业务逻辑代码
+}
go grade := "B"
+switch grade {
+case "A" :
+ fmt. Println ( "Your score is between 90 and 100." )
+case "B" :
+ fmt. Println ( "Your score is between 80 and 90." )
+case "C" :
+ fmt. Println ( "Your score is between 70 and 80." )
+case "D" :
+ fmt. Println ( "Your score is between 60 and 70." )
+default :
+ fmt. Println ( "Your score is below 60." )
+}
grade := "B"
+switch grade {
+case "A" :
+ fmt. Println ( "Your score is between 90 and 100." )
+case "B" :
+ fmt. Println ( "Your score is between 80 and 90." )
+case "C" :
+ fmt. Println ( "Your score is between 70 and 80." )
+case "D" :
+ fmt. Println ( "Your score is between 60 and 70." )
+default :
+ fmt. Println ( "Your score is below 60." )
+}
一个 case 多个条件
go month := 5
+switch month {
+case 1 , 3 , 5 , 7 , 8 , 10 , 12 :
+ fmt. Println ( "该月份有 31 天" )
+case 4 , 6 , 9 , 11 :
+ fmt. Println ( "该月份有 30 天" )
+case 2 :
+ fmt. Println ( "该月份闰年为 29 天,非闰年为 28 天" )
+default :
+ fmt. Println ( "输入有误!" )
+}
month := 5
+switch month {
+case 1 , 3 , 5 , 7 , 8 , 10 , 12 :
+ fmt. Println ( "该月份有 31 天" )
+case 4 , 6 , 9 , 11 :
+ fmt. Println ( "该月份有 30 天" )
+case 2 :
+ fmt. Println ( "该月份闰年为 29 天,非闰年为 28 天" )
+default :
+ fmt. Println ( "输入有误!" )
+}
switch
还有另外一种写法,它包含一个 statement
可选语句部分,该可选语句在表达式之前运行。它的语法是:
go switch statement; expression {
+}
+
+
+switch month := 5 ; month {
+case 1 , 3 , 5 , 7 , 8 , 10 , 12 :
+ fmt. Println ( "该月份有 31 天" )
+case 4 , 6 , 9 , 11 :
+ fmt. Println ( "该月份有 30 天" )
+case 2 :
+ fmt. Println ( "该月份闰年为 29 天,非闰年为 28 天" )
+default :
+ fmt. Println ( "输入有误!" )
+}
switch statement; expression {
+}
+
+
+switch month := 5 ; month {
+case 1 , 3 , 5 , 7 , 8 , 10 , 12 :
+ fmt. Println ( "该月份有 31 天" )
+case 4 , 6 , 9 , 11 :
+ fmt. Println ( "该月份有 30 天" )
+case 2 :
+ fmt. Println ( "该月份闰年为 29 天,非闰年为 28 天" )
+default :
+ fmt. Println ( "输入有误!" )
+}
这里 month
变量的作用域就仅限于这个 switch
内。
switch 后可接函数
switch
后面可以接一个函数,只要保证 case
后的值类型与函数的返回值一致即可。
go package main
+
+import " fmt "
+
+func getResult (args ...int ) bool {
+ for _, v := range args {
+ if v < 60 {
+ return false
+ }
+ }
+ return true
+}
+
+func main () {
+ chinese := 88
+ math := 90
+ english := 95
+
+ switch getResult (chinese, math, english) {
+ case true :
+ fmt. Println ( "考试通过" )
+ case false :
+ fmt. Println ( "考试未通过" )
+ }
+}
package main
+
+import " fmt "
+
+func getResult (args ...int ) bool {
+ for _, v := range args {
+ if v < 60 {
+ return false
+ }
+ }
+ return true
+}
+
+func main () {
+ chinese := 88
+ math := 90
+ english := 95
+
+ switch getResult (chinese, math, english) {
+ case true :
+ fmt. Println ( "考试通过" )
+ case false :
+ fmt. Println ( "考试未通过" )
+ }
+}
无表达式的 switch
switch
后面的表达式是可选的。如果省略该表达式,则表示这个 switch
语句等同于 switch true
,并且每个 case
表达式都被认定为有效,相应的代码块也会被执行。
go score := 88
+switch {
+case score >= 90 && score <= 100 :
+ fmt. Println ( "grade A" )
+case score >= 80 && score < 90 :
+ fmt. Println ( "grade B" )
+case score >= 70 && score < 80 :
+ fmt. Println ( "grade C" )
+case score >= 60 && score < 70 :
+ fmt. Println ( "grade D" )
+case score < 60 :
+ fmt. Println ( "grade E" )
+}
score := 88
+switch {
+case score >= 90 && score <= 100 :
+ fmt. Println ( "grade A" )
+case score >= 80 && score < 90 :
+ fmt. Println ( "grade B" )
+case score >= 70 && score < 80 :
+ fmt. Println ( "grade C" )
+case score >= 60 && score < 70 :
+ fmt. Println ( "grade D" )
+case score < 60 :
+ fmt. Println ( "grade E" )
+}
该 switch-case
语句相当于 if-elseif-else
语句。
fallthrough 语句
正常情况下 switch-case
语句在执行时只要有一个 case
满足条件,就会直接退出 switch-case
,如果一个都没有满足,才会执行 default
的代码块。不同于其他语言需要在每个 case
中添加 break
语句才能退出。使用 fallthrough
语句可以在已经执行完成的 case
之后,把控制权转移到下一个 case
的执行代码中。fallthrough
只能穿透一层,不管你有没有匹配上,都要退出了。fallthrough
语句是 case
子句的最后一个语句。如果它出现在了 case
语句的中间,编译会不通过。
go s := "123"
+switch {
+case s == "123" :
+ fmt. Println ( "123" )
+ fallthrough
+case s == "456" :
+ fmt. Println ( "456" )
+case s != "789" :
+ fmt. Println ( "789" )
+}
s := "123"
+switch {
+case s == "123" :
+ fmt. Println ( "123" )
+ fallthrough
+case s == "456" :
+ fmt. Println ( "456" )
+case s != "789" :
+ fmt. Println ( "789" )
+}
循环语句 循环语句 可以用来重复执行某一段代码。在 C 语言中,循环语句有 for
、 while
和 do while
三种循环。但在 Go 中只有 for
一种循环语句。下面是 for
循环语句的四种基本模型:
go // for 接三个表达式
+for initialisation; condition; post {
+ code
+}
+
+// for 接一个条件表达式
+for condition {
+ code
+}
+
+// for 接一个 range 表达式
+for range_expression {
+ code
+}
+
+// for 不接表达式
+for {
+ code
+}
// for 接三个表达式
+for initialisation; condition; post {
+ code
+}
+
+// for 接一个条件表达式
+for condition {
+ code
+}
+
+// for 接一个 range 表达式
+for range_expression {
+ code
+}
+
+// for 不接表达式
+for {
+ code
+}
接一个条件表达式
go num := 0
+for num < 4 {
+ fmt. Println (num)
+ num ++
+}
num := 0
+for num < 4 {
+ fmt. Println (num)
+ num ++
+}
接三个表达式
for
后面接的这三个表达式,各有各的用途:
第一个表达式(initialisation
):初始化控制变量,在整个循环生命周期内,只执行一次; 第二个表达式(condition
):设置循环控制条件,该表达式值为 true
时循环,值为 false
时结束循环; 第三个表达式(post
):每次循环完都会执行此表达式,可以利用其让控制变量增量或减量。 这三个表达式,使用 ;
分隔。
go for num := 0 ; num < 4 ; num ++ {
+ fmt. Println (num)
+}
for num := 0 ; num < 4 ; num ++ {
+ fmt. Println (num)
+}
接一个 range 表达式
go str := "Golang"
+for index, value := range str{
+ fmt. Printf ( "index %d , value %c\\n " , index, value)
+}
str := "Golang"
+for index, value := range str{
+ fmt. Printf ( "index %d , value %c\\n " , index, value)
+}
不接表达式
for
后面不接表达式就相当于无限循环,当然,可以使用 break
语句退出循环
go // 第一种写法
+for {
+ code
+}
+// 第二种写法
+for ;; {
+ code
+}
// 第一种写法
+for {
+ code
+}
+// 第二种写法
+for ;; {
+ code
+}
break 语句
break
语句用于终止 for
循环,之后程序将执行在 for
循环后的代码。上面的例子已经演示了 break
语句的使用。
continue 语句
continue
语句用来跳出 for
循环中的当前循环。在 continue
语句后的所有的 for
循环语句都不会在本次循环中执行,执行完 continue
语句后将会继续执行一下次循环。下面的程序会打印出 10
以内的奇数。
defer延迟调用 含有 defer
语句的函数,会在该函数将要返回之前,调用另一个函数。简单点说就是 defer
语句后面跟着的函数会延迟到当前函数执行完后再执行。
go package main
+
+import " fmt "
+
+func bookPrint () {
+ fmt. Println ( "123" )
+}
+
+func main () {
+ defer bookPrint ()
+ fmt. Println ( "main函数..." )
+}
package main
+
+import " fmt "
+
+func bookPrint () {
+ fmt. Println ( "123" )
+}
+
+func main () {
+ defer bookPrint ()
+ fmt. Println ( "main函数..." )
+}
首先,执行 main
函数,因为 bookPrint()
函数前有 defer
关键字,所以会在执行完 main
函数后再执行 bookPrint()
函数,所以先打印出 main函数...
,再执行 bookPrint()
函数打印 123
。
即时求值的变量快照
使用 defer
只是延时调用函数,传递给函数里的变量,不应该受到后续程序的影响。
go str := "123"
+defer fmt. Println (str)
+str = "456"
+fmt. Println (str)
+// 456
+// 123
str := "123"
+defer fmt. Println (str)
+str = "456"
+fmt. Println (str)
+// 456
+// 123
延迟方法
defer
不仅能够延迟函数的执行,也能延迟方法的执行。
go package main
+
+import " fmt "
+
+type Book struct {
+ bookName, authorName string
+}
+
+func (b Book) printName () {
+ fmt. Printf ( " %s %s " , b.bookName, b.authorName)
+}
+
+func main () {
+ book := Book{ "123" , "456" }
+ defer book. printName ()
+ fmt. Printf ( "main... " )
+}
+// main... 123 456
package main
+
+import " fmt "
+
+type Book struct {
+ bookName, authorName string
+}
+
+func (b Book) printName () {
+ fmt. Printf ( " %s %s " , b.bookName, b.authorName)
+}
+
+func main () {
+ book := Book{ "123" , "456" }
+ defer book. printName ()
+ fmt. Printf ( "main... " )
+}
+// main... 123 456
defer 栈
当一个函数内多次调用 defer
时,Go 会把 defer
调用放入到一个栈中,随后按照 后进先出 的顺序执行。
go package main
+
+import " fmt "
+
+func main () {
+ defer fmt. Printf ( "123" )
+ defer fmt. Printf ( "456" )
+ defer fmt. Printf ( "789" )
+ fmt. Printf ( "main..." )
+}
+//main...789456123
package main
+
+import " fmt "
+
+func main () {
+ defer fmt. Printf ( "123" )
+ defer fmt. Printf ( "456" )
+ defer fmt. Printf ( "789" )
+ fmt. Printf ( "main..." )
+}
+//main...789456123
defer 在 return 后调用
go package main
+
+import " fmt "
+
+var s string = "123"
+
+func showLesson () string {
+ defer func () {
+ s = "456"
+ }()
+ fmt. Println ( "showLesson: s =" , s)
+ return s
+}
+
+func main () {
+ lesson := showLesson ()
+ fmt. Println ( "main: s =" , s)
+ fmt. Println ( "main: lesson =" , lesson)
+}
+//showLesson: s = 123
+//main: s = 456
+//main: lesson = 123
package main
+
+import " fmt "
+
+var s string = "123"
+
+func showLesson () string {
+ defer func () {
+ s = "456"
+ }()
+ fmt. Println ( "showLesson: s =" , s)
+ return s
+}
+
+func main () {
+ lesson := showLesson ()
+ fmt. Println ( "main: s =" , s)
+ fmt. Println ( "main: lesson =" , lesson)
+}
+//showLesson: s = 123
+//main: s = 456
+//main: lesson = 123
Go 中 defer 和 return 执行的先后顺序 多个defer的执行顺序为“后进先出”; defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。 如果函数的返回值是无名的(不带命名返回值),则go语言会在执行return的时候会执行一个类似创建一个临时变量作为保存return值的动作,而有名返回值的函数,由于返回值在函数定义的时候已经将该变量进行定义,在执行return的时候会先执行返回值保存操作,而后续的defer函数会改变这个返回值(虽然defer是在return之后执行的,但是由于使用的函数定义的变量,所以执行defer操作后对该变量的修改会影响到return的值
defer 可以使代码更简洁
如果没有使用 defer
,当在一个操作资源的函数里调用多个 return
时,每次都得释放资源,你可能这样写代码:
go func f () {
+ r := getResource () //0,获取资源
+ ......
+ if ... {
+ r. release () //1,释放资源
+ return
+ }
+ ......
+ if ... {
+ r. release () //2,释放资源
+ return
+ }
+ ......
+ if ... {
+ r. release () //3,释放资源
+ return
+ }
+ ......
+ r. release () //4,释放资源
+ return
+}
func f () {
+ r := getResource () //0,获取资源
+ ......
+ if ... {
+ r. release () //1,释放资源
+ return
+ }
+ ......
+ if ... {
+ r. release () //2,释放资源
+ return
+ }
+ ......
+ if ... {
+ r. release () //3,释放资源
+ return
+ }
+ ......
+ r. release () //4,释放资源
+ return
+}
有了 defer
之后,可以简洁地写成下面这样:
go func f () {
+ r := getResource () //0,获取资源
+
+ defer r. release () //1,释放资源
+ ......
+ if ... {
+ ...
+ return
+ }
+ ......
+ if ... {
+ ...
+ return
+ }
+ ......
+ if ... {
+ ...
+ return
+ }
+ ......
+ return
+}
func f () {
+ r := getResource () //0,获取资源
+
+ defer r. release () //1,释放资源
+ ......
+ if ... {
+ ...
+ return
+ }
+ ......
+ if ... {
+ ...
+ return
+ }
+ ......
+ if ... {
+ ...
+ return
+ }
+ ......
+ return
+}
goto无条件跳转 在 Go 语言中保留 goto
。goto
后面接的是标签,表示下一步要执行哪里的代码。
go package main
+
+import " fmt "
+
+func main () {
+ fmt. Println ( "123" )
+ goto label
+ fmt. Println ( "456" )
+label:
+ fmt. Println ( "789" )
+}
+//123
+//789
package main
+
+import " fmt "
+
+func main () {
+ fmt. Println ( "123" )
+ goto label
+ fmt. Println ( "456" )
+label:
+ fmt. Println ( "789" )
+}
+//123
+//789
指针 一个指针变量指向了一个值的内存地址。
类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
go var var_name * var - type
var var_name * var - type
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:
go var ip *int /* 指向整型*/
+var fp *float32 /* 指向浮点型 */
var ip *int /* 指向整型*/
+var fp *float32 /* 指向浮点型 */
操作符 操作符如果在赋值操作值的左边,指该指针指向的变量;* 操作符如果在赋值操作符的右边,指从一个指针变量中取得变量值,又称指针的解引用。 如何使用指针 指针使用流程:
定义指针变量。 为指针变量赋值。 访问指针变量中指向地址的值。 在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。 go package main
+
+import " fmt "
+
+func main () {
+ var a int= 20 /* 声明实际变量 */
+ var ip *int /* 声明指针变量 */
+
+ ip = & a /* 指针变量的存储地址 */
+
+ fmt. Printf ( "a 变量的地址是: %x\\n " , & a )
+
+ /* 指针变量的存储地址 */
+ fmt. Printf ( "ip 变量储存的指针地址: %x\\n " , ip )
+
+ /* 使用指针访问值 */
+ fmt. Printf ( "*ip 变量的值: %d\\n " , * ip )
+}
+//a 变量的地址是: 20818a220
+//ip 变量储存的指针地址: 20818a220
+//*ip 变量的值: 20
package main
+
+import " fmt "
+
+func main () {
+ var a int= 20 /* 声明实际变量 */
+ var ip *int /* 声明指针变量 */
+
+ ip = & a /* 指针变量的存储地址 */
+
+ fmt. Printf ( "a 变量的地址是: %x\\n " , & a )
+
+ /* 指针变量的存储地址 */
+ fmt. Printf ( "ip 变量储存的指针地址: %x\\n " , ip )
+
+ /* 使用指针访问值 */
+ fmt. Printf ( "*ip 变量的值: %d\\n " , * ip )
+}
+//a 变量的地址是: 20818a220
+//ip 变量储存的指针地址: 20818a220
+//*ip 变量的值: 20
空指针 当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
go package main
+
+import " fmt "
+
+func main () {
+ var ptr *int
+
+ fmt. Printf ( "ptr 的值为 : %x ** \\n **" , ptr )
+}
+//ptr 的值为 : 0
package main
+
+import " fmt "
+
+func main () {
+ var ptr *int
+
+ fmt. Printf ( "ptr 的值为 : %x ** \\n **" , ptr )
+}
+//ptr 的值为 : 0
空指针判断
go if (ptr != nil ) /* ptr 不是空指针 */
+if (ptr == nil ) /* ptr 是空指针 */
if (ptr != nil ) /* ptr 不是空指针 */
+if (ptr == nil ) /* ptr 是空指针 */
函数传递指针函数 在函数中对指针参数所做的修改,在函数返回后会保存相应的修改。
go package main
+
+import (
+ " fmt "
+)
+
+func changeByPointer (value *int ) {
+ * value = 200
+}
+
+func main () {
+ x3 := 99
+ p3 := & x3
+ fmt. Println ( "执行changeByPointer函数之前p3是" , * p3)
+ changeByPointer (p3)
+ fmt. Println ( "执行changeByPointer函数之后p3是" , * p3)
+}
+//执行changeByPointer函数之前p3是 99
+//执行changeByPointer函数之后p3是 200
package main
+
+import (
+ " fmt "
+)
+
+func changeByPointer (value *int ) {
+ * value = 200
+}
+
+func main () {
+ x3 := 99
+ p3 := & x3
+ fmt. Println ( "执行changeByPointer函数之前p3是" , * p3)
+ changeByPointer (p3)
+ fmt. Println ( "执行changeByPointer函数之后p3是" , * p3)
+}
+//执行changeByPointer函数之前p3是 99
+//执行changeByPointer函数之后p3是 200
指针与切片 切片与指针一样是引用类型,如果我们想通过一个函数改变一个数组的值,可以将该数组的切片当作参数传给函数,也可以将这个数组的指针当作参数传给函数。但 Go 中建议使用第一种方法,即将该数组的切片当作参数传给函数,因为这么写更加简洁易读。
go package main
+
+import " fmt "
+
+// 使用切片
+func changeSlice (value [] int ) {
+ value[ 0 ] = 200
+}
+
+// 使用数组指针
+func changeArray (value * [ 3 ] int ) {
+ ( * value)[ 0 ] = 200
+}
+
+func main () {
+ x := [ 3 ] int { 10 , 20 , 30 }
+ changeSlice (x[:])
+ fmt. Println (x) // [200 20 30]
+
+ y := [ 3 ] int { 100 , 200 , 300 }
+ changeArray ( & y)
+ fmt. Println (y) // [200 200 300]
+}
package main
+
+import " fmt "
+
+// 使用切片
+func changeSlice (value [] int ) {
+ value[ 0 ] = 200
+}
+
+// 使用数组指针
+func changeArray (value * [ 3 ] int ) {
+ ( * value)[ 0 ] = 200
+}
+
+func main () {
+ x := [ 3 ] int { 10 , 20 , 30 }
+ changeSlice (x[:])
+ fmt. Println (x) // [200 20 30]
+
+ y := [ 3 ] int { 100 , 200 , 300 }
+ changeArray ( & y)
+ fmt. Println (y) // [200 200 300]
+}
结构体 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。Go中没有class
的概念,只有struct
结构体,所以也没有继承。
声明 go type struct_name struct {
+ attribute_name1 attribute_type
+ attribute_name2 attribute_type
+ ...
+}
+
+type Lesson struct {
+ name string //名称
+ target string //学习目标
+ spend int //学习花费时间
+}
+//可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑
+type Lesson2 struct {
+ name, target string
+ spend int
+}
type struct_name struct {
+ attribute_name1 attribute_type
+ attribute_name2 attribute_type
+ ...
+}
+
+type Lesson struct {
+ name string //名称
+ target string //学习目标
+ spend int //学习花费时间
+}
+//可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑
+type Lesson2 struct {
+ name, target string
+ spend int
+}
上面的结构体称为命名结构体Named Structure
。声明结构体时也可以不用声明新类型,这种结构体类型称为匿名结构体Anonymous Structure
go var Lesson3 struct {
+ name, target string
+ spend int
+}
var Lesson3 struct {
+ name, target string
+ spend int
+}
创建命名结构体 go package main
+
+import " fmt "
+
+type Lesson struct {
+ name, target string
+ spend int
+}
+
+func main () {
+ // 使用字段名创建结构体
+ lesson1 := Lesson{
+ name: "Golang" ,
+ target: "学习Go语言,并完成一个单体服务" ,
+ spend: 5 ,
+ }
+ // 不使用字段名创建结构体,按字段声明顺序初始化
+ lesson2 := Lesson{ "Golang" , "学习Go语言,并完成一个单体服务" , 5 }
+
+ fmt. Println ( "lesson1 " , lesson1)
+ fmt. Println ( "lesson2 " , lesson2)
+}
package main
+
+import " fmt "
+
+type Lesson struct {
+ name, target string
+ spend int
+}
+
+func main () {
+ // 使用字段名创建结构体
+ lesson1 := Lesson{
+ name: "Golang" ,
+ target: "学习Go语言,并完成一个单体服务" ,
+ spend: 5 ,
+ }
+ // 不使用字段名创建结构体,按字段声明顺序初始化
+ lesson2 := Lesson{ "Golang" , "学习Go语言,并完成一个单体服务" , 5 }
+
+ fmt. Println ( "lesson1 " , lesson1)
+ fmt. Println ( "lesson2 " , lesson2)
+}
结构体标签 Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
go \`key1:"value1" key2:"value2"\`
\`key1:"value1" key2:"value2"\`
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
例如我们为Student结构体的每个字段定义json序列化时使用的Tag:
go //Student 学生
+type Student struct {
+ ID int \`json:"id"\` //通过指定tag实现json序列化该字段时的key
+ Gender string //json序列化是默认使用字段名作为key
+ name string //私有不能被json包访问
+}
+
+func main () {
+ s1 := Student{
+ ID: 1 ,
+ Gender: "女" ,
+ name: "pprof" ,
+ }
+ data, err := json. Marshal (s1)
+ if err != nil {
+ fmt. Println ( "json marshal failed!" )
+ return
+ }
+ fmt. Printf ( "json str: %s\\n " , data) //json str:{"id":1,"Gender":"女"}
+}
//Student 学生
+type Student struct {
+ ID int \`json:"id"\` //通过指定tag实现json序列化该字段时的key
+ Gender string //json序列化是默认使用字段名作为key
+ name string //私有不能被json包访问
+}
+
+func main () {
+ s1 := Student{
+ ID: 1 ,
+ Gender: "女" ,
+ name: "pprof" ,
+ }
+ data, err := json. Marshal (s1)
+ if err != nil {
+ fmt. Println ( "json marshal failed!" )
+ return
+ }
+ fmt. Printf ( "json str: %s\\n " , data) //json str:{"id":1,"Gender":"女"}
+}
创建匿名结构体 go package main
+
+import " fmt "
+
+func main () {
+ // 创建匿名结构体变量
+ lesson3 := struct {
+ name, target string
+ spend int
+ }{
+ name: "Go语言" ,
+ target: "掌握GO语言" ,
+ spend: 3 ,
+ }
+
+ fmt. Println ( "lesson3 " , lesson3)
+}
package main
+
+import " fmt "
+
+func main () {
+ // 创建匿名结构体变量
+ lesson3 := struct {
+ name, target string
+ spend int
+ }{
+ name: "Go语言" ,
+ target: "掌握GO语言" ,
+ spend: 3 ,
+ }
+
+ fmt. Println ( "lesson3 " , lesson3)
+}
结构体的零值(Zero Value) 当定义好的结构体没有被显式初始化时,结构体的字段将会默认赋为相应类型的零值。
go package main
+
+import " fmt "
+
+type Lesson struct {
+ name, target string
+ spend int
+}
+
+func main () {
+ // 不初始化结构体
+ var lesson4 = Lesson{}
+
+ fmt. Println ( "lesson4 " , lesson4)
+}
+//lesson4 { 0}
package main
+
+import " fmt "
+
+type Lesson struct {
+ name, target string
+ spend int
+}
+
+func main () {
+ // 不初始化结构体
+ var lesson4 = Lesson{}
+
+ fmt. Println ( "lesson4 " , lesson4)
+}
+//lesson4 { 0}
访问结构体字段 使用.
点操作符访问:lesson.name
使用.
也可用与给结构体字段赋值:lesson.name = "test"
指向结构体的指针 go package main
+
+import " fmt "
+
+type Lesson struct {
+ name, target string
+ spend int
+}
+
+func main () {
+ lesson8 := & Lesson{ "Go语言" , "Go语言微服务" , 50 }
+ fmt. Println ( "lesson8 name: " , ( * lesson8).name)
+ fmt. Println ( "lesson8 name: " , lesson8.name)
+}
package main
+
+import " fmt "
+
+type Lesson struct {
+ name, target string
+ spend int
+}
+
+func main () {
+ lesson8 := & Lesson{ "Go语言" , "Go语言微服务" , 50 }
+ fmt. Println ( "lesson8 name: " , ( * lesson8).name)
+ fmt. Println ( "lesson8 name: " , lesson8.name)
+}
lesson8
是一个指向结构体 Lesson
的指针,用 (*lesson8).name
访问 lesson8
的 name
字段, lesson8.name
代替 (*lesson8).name
的解引用访问。
匿名字段 在创建结构体时,字段可以只有类型没有字段名,这种字段称为 匿名字段(Anonymous Field) 。
go package main
+
+import " fmt "
+
+type Lesson4 struct {
+ string
+ int
+}
+
+func main () {
+ lesson9 := Lesson4{ "Golang" , 50 }
+ fmt. Println ( "lesson9 " , lesson9)
+ fmt. Println ( "lesson9 string: " , lesson9. string )
+ fmt. Println ( "lesson9 int: " , lesson9. int )
+}
package main
+
+import " fmt "
+
+type Lesson4 struct {
+ string
+ int
+}
+
+func main () {
+ lesson9 := Lesson4{ "Golang" , 50 }
+ fmt. Println ( "lesson9 " , lesson9)
+ fmt. Println ( "lesson9 string: " , lesson9. string )
+ fmt. Println ( "lesson9 int: " , lesson9. int )
+}
上面的程序结构体定义了两个匿名字段,虽然这两个字段没有字段名,但匿名字段的名称默认就是它的类型。所以上面的结构体 Lesoon4
有两个名为 string
和 int
的字段。
嵌套结构体 结构体的字段也可能是另外一个结构体,这样的结构体称为 嵌套结构体(Nested Structs)
go package main
+
+import " fmt "
+
+type Author struct {
+ name string
+ wx string
+}
+
+type Lesson5 struct {
+ name,target string
+ spend int
+ author Author
+}
+
+func main () {
+ lesson10 := Lesson5{
+ name: "Go语言" ,
+ spend: 50 ,
+ }
+ lesson10.author = Author{
+ name: "golang" ,
+ wx: "666" ,
+ }
+ fmt. Println ( "lesson10 name:" , lesson10.name)
+ fmt. Println ( "lesson10 spend:" , lesson10.spend)
+ fmt. Println ( "lesson10 author name:" , lesson10.author.name)
+ fmt. Println ( "lesson10 author wx:" , lesson10.author.wx)
+}
package main
+
+import " fmt "
+
+type Author struct {
+ name string
+ wx string
+}
+
+type Lesson5 struct {
+ name,target string
+ spend int
+ author Author
+}
+
+func main () {
+ lesson10 := Lesson5{
+ name: "Go语言" ,
+ spend: 50 ,
+ }
+ lesson10.author = Author{
+ name: "golang" ,
+ wx: "666" ,
+ }
+ fmt. Println ( "lesson10 name:" , lesson10.name)
+ fmt. Println ( "lesson10 spend:" , lesson10.spend)
+ fmt. Println ( "lesson10 author name:" , lesson10.author.name)
+ fmt. Println ( "lesson10 author wx:" , lesson10.author.wx)
+}
上面的程序 Lesson5
结构体有一个字段 author
,而且它的类型也是一个结构体 Author
。
提升字段 结构体中如果有匿名的结构体类型字段,则该匿名结构体里的字段就称为 提升字段(Promoted Fields) 。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。就像刚刚上面的程序,如果我们把 Lesson
结构体中的字段 author
直接用匿名字段 Author
代替, Author
结构体的字段例如 name
就不用像上面那样使用 lesson10.author.wx
访问,而是使用 lesson10.wx
就能访问 Author
结构体中的 wx
字段。现在结构体 Author
有 name
、 wx
两个字段,访问字段就像在 Lesson
里直接声明的一样,因此我们称之为提升字段。
go package main
+
+import " fmt "
+
+type Author struct {
+ name string
+ wx string
+}
+
+type Lesson6 struct {
+ name,target string
+ spend int
+ Author
+}
+
+func main () {
+ lesson10 := Lesson6{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ }
+ lesson10.Author = Author{
+ name: "golang" ,
+ wx: "666" ,
+ }
+ fmt. Println ( "lesson10 name:" , lesson10.name)
+ fmt. Println ( "lesson10 target:" , lesson10.target)
+ fmt. Println ( "lesson10 author wx:" , lesson10.wx)
+}
+//lesson10 name: Go语言
+//lesson10 target: 掌握Go语言
+//lesson10 author wx: 666
package main
+
+import " fmt "
+
+type Author struct {
+ name string
+ wx string
+}
+
+type Lesson6 struct {
+ name,target string
+ spend int
+ Author
+}
+
+func main () {
+ lesson10 := Lesson6{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ }
+ lesson10.Author = Author{
+ name: "golang" ,
+ wx: "666" ,
+ }
+ fmt. Println ( "lesson10 name:" , lesson10.name)
+ fmt. Println ( "lesson10 target:" , lesson10.target)
+ fmt. Println ( "lesson10 author wx:" , lesson10.wx)
+}
+//lesson10 name: Go语言
+//lesson10 target: 掌握Go语言
+//lesson10 author wx: 666
结构体比较 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 ==
或 !=
运算符进行比较。可以通过==运算符或 DeeplyEqual()函数比较两个结构相同的类型并包含相同的字段值。因此下面两个比较的表达式是等价的:
go package main
+
+import " fmt "
+
+type Lesson struct {
+ name,target string
+ spend int
+}
+
+func main () {
+ lesson11 := Lesson{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ }
+ lesson12 := Lesson{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ }
+ fmt. Println (lesson11.name == lesson12.name && lesson11.target == lesson12.target) // true
+ fmt. Println (lesson11 == lesson12) // true
+}
package main
+
+import " fmt "
+
+type Lesson struct {
+ name,target string
+ spend int
+}
+
+func main () {
+ lesson11 := Lesson{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ }
+ lesson12 := Lesson{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ }
+ fmt. Println (lesson11.name == lesson12.name && lesson11.target == lesson12.target) // true
+ fmt. Println (lesson11 == lesson12) // true
+}
给结构体定义方法 在 Go 中无法在结构体内部定义方法
go package main
+
+import " fmt "
+
+// Lesson 定义一个名为 Lesson 的结构体
+type Lesson struct {
+ name, target string
+ spend int
+}
+
+// ShowLessonInfo 定义一个与 Lesson 的绑定的方法
+func (l Lesson) ShowLessonInfo () {
+ fmt. Println ( "name:" , l.name)
+ fmt. Println ( "target:" , l.target)
+}
+
+func main () {
+ lesson13 := Lesson{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ }
+ lesson13. ShowLessonInfo ()
+}
package main
+
+import " fmt "
+
+// Lesson 定义一个名为 Lesson 的结构体
+type Lesson struct {
+ name, target string
+ spend int
+}
+
+// ShowLessonInfo 定义一个与 Lesson 的绑定的方法
+func (l Lesson) ShowLessonInfo () {
+ fmt. Println ( "name:" , l.name)
+ fmt. Println ( "target:" , l.target)
+}
+
+func main () {
+ lesson13 := Lesson{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ }
+ lesson13. ShowLessonInfo ()
+}
上面的程序中定义了一个与结构体 Lesson
绑定的方法 ShowLessonInfo()
,其中 ShowLessonInfo
是方法名, (l Lesson)
表示将此方法与 Lesson
的实例绑定,这在 Go 语言中称为接收者,而 l
表示实例本身,相当于 Python 中的 self
,在方法内可以使用 实例本身.属性名称
来访问实例属性。
方法的参数传递方式 如果绑定结构体的方法中要改变实例的属性时,必须使用指针作为方法的接收者。
go package main
+
+import " fmt "
+
+// Lesson 定义一个名为 Lesson 的结构体
+type Lesson struct {
+ name,target string
+ spend int
+}
+
+// ShowLessonInfo 定义一个与 Lesson 的绑定的方法
+func (l Lesson) ShowLessonInfo () {
+ fmt. Println ( "name:" , l.name)
+ fmt. Println ( "target:" , l.target)
+}
+
+// AddTime 定义一个与 Lesson 的绑定的方法,使 spend 值加 n
+func (l * Lesson) AddTime (n int ) {
+ l.spend = l.spend + n
+}
+
+func main () {
+ lesson13 := Lesson{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ spend: 50 ,
+ }
+ fmt. Println ( "添加add方法前" )
+ lesson13. ShowLessonInfo ()
+ lesson13. AddTime ( 5 )
+ fmt. Println ( "添加add方法后" )
+ lesson13. ShowLessonInfo ()
+}
package main
+
+import " fmt "
+
+// Lesson 定义一个名为 Lesson 的结构体
+type Lesson struct {
+ name,target string
+ spend int
+}
+
+// ShowLessonInfo 定义一个与 Lesson 的绑定的方法
+func (l Lesson) ShowLessonInfo () {
+ fmt. Println ( "name:" , l.name)
+ fmt. Println ( "target:" , l.target)
+}
+
+// AddTime 定义一个与 Lesson 的绑定的方法,使 spend 值加 n
+func (l * Lesson) AddTime (n int ) {
+ l.spend = l.spend + n
+}
+
+func main () {
+ lesson13 := Lesson{
+ name: "Go语言" ,
+ target: "掌握Go语言" ,
+ spend: 50 ,
+ }
+ fmt. Println ( "添加add方法前" )
+ lesson13. ShowLessonInfo ()
+ lesson13. AddTime ( 5 )
+ fmt. Println ( "添加add方法后" )
+ lesson13. ShowLessonInfo ()
+}
函数 函数 是基于功能或逻辑进行封装的可复用的代码结构。将一段功能复杂、很长的一段代码封装成多个代码片段(即函数),有助于提高代码可读性和可维护性。由于 Go 语言是编译型语言,所以函数编写的顺序是无关紧要的。
声明 go func function_name (parameter_list) (result_list) {
+ //函数体
+}
func function_name (parameter_list) (result_list) {
+ //函数体
+}
可变参数 多个类型一致的参数 在参数类型前面加 ...
表示一个切片,用来接收调用者传入的参数。注意,如果该函数下有其他类型的参数,这些其他参数必须放在参数列表的前面,切片必须放在最后。
go package main
+
+import " fmt "
+
+func show (args ... string ) int {
+ sum := 0
+ for _, item := range args {
+ fmt. Println (item)
+ sum += 1
+ }
+ return sum
+}
+
+func main () {
+ fmt. Println ( show ( "1" , "2" , "3" ))
+}
package main
+
+import " fmt "
+
+func show (args ... string ) int {
+ sum := 0
+ for _, item := range args {
+ fmt. Println (item)
+ sum += 1
+ }
+ return sum
+}
+
+func main () {
+ fmt. Println ( show ( "1" , "2" , "3" ))
+}
多个类型不一致的参数 如果传多个参数的类型都不一样,可以指定类型为 ...interface{}
,然后再遍历。
go package main
+
+import " fmt "
+
+func PrintType (args ...interface {}) {
+ for _, arg := range args {
+ switch arg.(type) {
+ case int :
+ fmt. Println (arg, "type is int." )
+ case string :
+ fmt. Println (arg, "type is string." )
+ case float64 :
+ fmt. Println (arg, "type is float64." )
+ default :
+ fmt. Println (arg, "is an unknown type." )
+ }
+ }
+}
+
+func main () {
+ PrintType ( 57 , 3.14 , "123" )
+}
package main
+
+import " fmt "
+
+func PrintType (args ...interface {}) {
+ for _, arg := range args {
+ switch arg.(type) {
+ case int :
+ fmt. Println (arg, "type is int." )
+ case string :
+ fmt. Println (arg, "type is string." )
+ case float64 :
+ fmt. Println (arg, "type is float64." )
+ default :
+ fmt. Println (arg, "is an unknown type." )
+ }
+ }
+}
+
+func main () {
+ PrintType ( 57 , 3.14 , "123" )
+}
解序列 使用 ...
可以用来解序列
函数的返回值 当函数没有返回值时,函数体可以使用 return
语句返回。在 Go 中一个函数可以返回多个值。
go package main
+
+import " fmt "
+
+func showBookInfo (bookName, authorName string ) ( string , error ) {
+ if bookName == "" {
+ return "" , errors. New ( "图书名称为空" )
+ }
+ if authorName == "" {
+ return "" , errors. New ( "作者名称为空" )
+ }
+ return bookName + ",作者:" + authorName, nil
+}
+
+func main () {
+ bookInfo, err := showBookInfo ( "123" , "45" )
+ fmt. Printf ( "bookInfo = %s , err = %v " , bookInfo, err)
+}
package main
+
+import " fmt "
+
+func showBookInfo (bookName, authorName string ) ( string , error ) {
+ if bookName == "" {
+ return "" , errors. New ( "图书名称为空" )
+ }
+ if authorName == "" {
+ return "" , errors. New ( "作者名称为空" )
+ }
+ return bookName + ",作者:" + authorName, nil
+}
+
+func main () {
+ bookInfo, err := showBookInfo ( "123" , "45" )
+ fmt. Printf ( "bookInfo = %s , err = %v " , bookInfo, err)
+}
返回带有变量名的值
go func showBookInfo2 (bookName, authorName string ) (info string , err error ) {
+ info = ""
+ if bookName == "" {
+ err = errors. New ( "图书名称为空" )
+ return
+ }
+ if authorName == "" {
+ err = errors. New ( "作者名称为空" )
+ return
+ }
+ // 不使用 := 因为已经在返回值那里声明了
+ info = bookName + ",作者:" + authorName
+ // 直接返回即可
+ return
+}
func showBookInfo2 (bookName, authorName string ) (info string , err error ) {
+ info = ""
+ if bookName == "" {
+ err = errors. New ( "图书名称为空" )
+ return
+ }
+ if authorName == "" {
+ err = errors. New ( "作者名称为空" )
+ return
+ }
+ // 不使用 := 因为已经在返回值那里声明了
+ info = bookName + ",作者:" + authorName
+ // 直接返回即可
+ return
+}
匿名函数 go func (parameter_list) (result_list) {
+ body
+}
func (parameter_list) (result_list) {
+ body
+}
内部方法与外部方法 在 Go 语言中,函数名通过首字母大小写实现控制对方法的访问权限。
当方法的首字母为 大写 时,这个方法对于 所有包 都是 Public ,其他包可以随意调用。 当方法的首字母为 小写 时,这个方法是 Private ,其他包是无法访问的。 方法 方法 其实就是一个函数,在 func
这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的。
go func (t Type) methodName (parameterList) returnList{
+}
func (t Type) methodName (parameterList) returnList{
+}
实例绑定 go package main
+
+import " fmt "
+
+// Lesson 定义一个名为 Lesson 的结构体
+type Lesson struct {
+ Name string
+ Target string
+}
+
+// PrintInfo 定义一个与 Lesson 的绑定的方法
+func (lesson Lesson) PrintInfo () {
+ fmt. Println ( "name:" , lesson.Name)
+ fmt. Println ( "target:" , lesson.Target)
+}
+
+
+func main () {
+ l := Lesson{
+ Name: "Go语言" ,
+ Target: "掌握Go语言" ,
+ }
+ l. PrintInfo ()
+}
package main
+
+import " fmt "
+
+// Lesson 定义一个名为 Lesson 的结构体
+type Lesson struct {
+ Name string
+ Target string
+}
+
+// PrintInfo 定义一个与 Lesson 的绑定的方法
+func (lesson Lesson) PrintInfo () {
+ fmt. Println ( "name:" , lesson.Name)
+ fmt. Println ( "target:" , lesson.Target)
+}
+
+
+func main () {
+ l := Lesson{
+ Name: "Go语言" ,
+ Target: "掌握Go语言" ,
+ }
+ l. PrintInfo ()
+}
也可以把上面程序的方法改成一个函数
go package main
+
+import " fmt "
+
+type Lesson struct {
+ Name string
+ Target string
+}
+
+func PrintInfo (lesson Lesson) {
+ fmt. Println ( "name:" , lesson.Name)
+ fmt. Println ( "target:" , lesson.Target)
+}
+
+func main () {
+ lesson := Lesson{
+ Name: "Go语言" ,
+ Target: "掌握Go语言" ,
+ }
+ PrintInfo (lesson)
+}
package main
+
+import " fmt "
+
+type Lesson struct {
+ Name string
+ Target string
+}
+
+func PrintInfo (lesson Lesson) {
+ fmt. Println ( "name:" , lesson.Name)
+ fmt. Println ( "target:" , lesson.Target)
+}
+
+func main () {
+ lesson := Lesson{
+ Name: "Go语言" ,
+ Target: "掌握Go语言" ,
+ }
+ PrintInfo (lesson)
+}
运行这个程序,也同样会输出上面一样的答案,那么我们为什么还要用方法呢?因为在 Go 中,相同的名字的方法可以定义在不同的类型上,而相同名字的函数是不被允许的。如果你在上面这个程序添加一个同名函数,就会报错。但是在不同的结构体上面定义同名的方法就是可行的。
go package main
+
+import " fmt "
+
+type Lesson struct {
+ Name string
+ Target string
+}
+
+func (lesson Lesson) PrintInfo () {
+ fmt. Println ( "Lesson name:" , lesson.Name)
+ fmt. Println ( "Lesson target:" , lesson.Target)
+}
+
+type Author struct {
+ Name string
+}
+
+func (author Author) PrintInfo () {
+ fmt. Println ( "author name:" , author.Name)
+}
+
+func main () {
+ lesson := Lesson{
+ Name: "Go语言" ,
+ Target: "掌握Go语言" ,
+ }
+ lesson. PrintInfo ()
+ author := Author{ "Google" }
+ author. PrintInfo ()
+}
package main
+
+import " fmt "
+
+type Lesson struct {
+ Name string
+ Target string
+}
+
+func (lesson Lesson) PrintInfo () {
+ fmt. Println ( "Lesson name:" , lesson.Name)
+ fmt. Println ( "Lesson target:" , lesson.Target)
+}
+
+type Author struct {
+ Name string
+}
+
+func (author Author) PrintInfo () {
+ fmt. Println ( "author name:" , author.Name)
+}
+
+func main () {
+ lesson := Lesson{
+ Name: "Go语言" ,
+ Target: "掌握Go语言" ,
+ }
+ lesson. PrintInfo ()
+ author := Author{ "Google" }
+ author. PrintInfo ()
+}
指针接收器与值接收器 值接收器和指针接收器之间的区别在于,在指针接收器的方法内部的改变对于调用者是可见的,然而值接收器的方法内部的改变对于调用者是不可见的,所以若要改变实例的属性时,必须使用指针作为方法的接收者。
go package main
+
+import " fmt "
+
+// Lesson 定义一个名为 Lesson 的结构体
+type Lesson struct {
+ Name string
+ Target string
+ SpendTime int
+}
+
+// PrintInfo 定义一个与 Lesson 的绑定的方法
+func (lesson Lesson) PrintInfo () {
+ fmt. Println ( "name:" , lesson.Name)
+ fmt. Println ( "target:" , lesson.Target)
+ fmt. Println ( "spendTime:" , lesson.SpendTime)
+}
+
+func (lesson Lesson) ChangeLessonName (name string ) {
+ lesson.Name = name
+}
+
+func (lesson * Lesson) AddSpendTime (n int ) {
+ lesson.SpendTime = lesson.SpendTime + n
+}
+
+func main () {
+ lesson := Lesson{
+ Name: "Go语言" ,
+ Target: "掌握Go语言" ,
+ SpendTime: 1 ,
+ }
+ fmt. Println ( "before change" )
+ lesson. PrintInfo ()
+
+ fmt. Println ( "after change" )
+ lesson. AddSpendTime ( 2 )
+ lesson. ChangeLessonName ( "Go语言123" )
+ lesson. PrintInfo ()
+}
package main
+
+import " fmt "
+
+// Lesson 定义一个名为 Lesson 的结构体
+type Lesson struct {
+ Name string
+ Target string
+ SpendTime int
+}
+
+// PrintInfo 定义一个与 Lesson 的绑定的方法
+func (lesson Lesson) PrintInfo () {
+ fmt. Println ( "name:" , lesson.Name)
+ fmt. Println ( "target:" , lesson.Target)
+ fmt. Println ( "spendTime:" , lesson.SpendTime)
+}
+
+func (lesson Lesson) ChangeLessonName (name string ) {
+ lesson.Name = name
+}
+
+func (lesson * Lesson) AddSpendTime (n int ) {
+ lesson.SpendTime = lesson.SpendTime + n
+}
+
+func main () {
+ lesson := Lesson{
+ Name: "Go语言" ,
+ Target: "掌握Go语言" ,
+ SpendTime: 1 ,
+ }
+ fmt. Println ( "before change" )
+ lesson. PrintInfo ()
+
+ fmt. Println ( "after change" )
+ lesson. AddSpendTime ( 2 )
+ lesson. ChangeLessonName ( "Go语言123" )
+ lesson. PrintInfo ()
+}
在上面的程序中, AddSpendTime
使用指针接收器最终能改变实例的 SpendTime
值,然而使用值接收器的 ChangeLessonName
最终没有改变实例 Name
的值。
在方法中使用值接收器 与 在函数中使用值参数 当一个函数有一个值参数,它只能接受一个值参数。当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
go package main
+
+import " fmt "
+
+type Lesson struct {
+ Name string
+}
+
+func (lesson Lesson) PrintInfo () {
+ fmt. Println (lesson.Name)
+}
+
+func PrintInfo (lesson Lesson) {
+ fmt. Println (lesson.Name)
+}
+
+func main () {
+ lesson := Lesson{ "Go语言" }
+ PrintInfo (lesson)
+ lesson. PrintInfo ()
+
+ bPtr := & lesson
+ //PrintInfo(bPtr) // error
+ bPtr. PrintInfo ()
+}
package main
+
+import " fmt "
+
+type Lesson struct {
+ Name string
+}
+
+func (lesson Lesson) PrintInfo () {
+ fmt. Println (lesson.Name)
+}
+
+func PrintInfo (lesson Lesson) {
+ fmt. Println (lesson.Name)
+}
+
+func main () {
+ lesson := Lesson{ "Go语言" }
+ PrintInfo (lesson)
+ lesson. PrintInfo ()
+
+ bPtr := & lesson
+ //PrintInfo(bPtr) // error
+ bPtr. PrintInfo ()
+}
在上面的程序中,使用值参数 PrintInfo(lesson)
来调用这个函数是合法的,使用值接收器来调用 lesson.PrintInfo()
也是合法的。
然后在程序中我们创建了一个指向 Lesson
的指针 bPtr
,通过使用指针接收器来调用 bPtr.PrintInfo()
是合法的,但使用值参数调用 PrintInfo(bPtr)
是非法的。
在非结构体上的方法 go package main
+
+import " fmt "
+
+type myInt int
+
+func (a myInt) add (b myInt) myInt {
+ return a + b
+}
+
+func main () {
+ var x myInt = 50
+ var y myInt = 7
+ fmt. Println (x. add (y)) // 57
+}
package main
+
+import " fmt "
+
+type myInt int
+
+func (a myInt) add (b myInt) myInt {
+ return a + b
+}
+
+func main () {
+ var x myInt = 50
+ var y myInt = 7
+ fmt. Println (x. add (y)) // 57
+}
接口 在 Go 语言中, 接口 就是方法签名(Method Signature)的集合。在面向对象的领域里,接口定义一个对象的行为,接口只指定了对象应该做什么,至于如何实现这个行为,则由对象本身去确定。当一个类型实现了接口中的所有方法,我们称它实现了该接口。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
定义 go type interface_name interface {
+ method ()
+}
type interface_name interface {
+ method ()
+}
接口实现 go package main
+
+import " fmt "
+
+type Study interface {
+ learn ()
+}
+
+type Student struct {
+ name string
+ book string
+}
+
+func (s Student) learn () {
+ fmt. Printf ( " %s 在读 %s " , s.name, s.book)
+}
+
+func main () {
+ student1 := Student{
+ name: "张三" ,
+ book: "《Go语言》" ,
+ }
+ student1. learn ()
+}
package main
+
+import " fmt "
+
+type Study interface {
+ learn ()
+}
+
+type Student struct {
+ name string
+ book string
+}
+
+func (s Student) learn () {
+ fmt. Printf ( " %s 在读 %s " , s.name, s.book)
+}
+
+func main () {
+ student1 := Student{
+ name: "张三" ,
+ book: "《Go语言》" ,
+ }
+ student1. learn ()
+}
上面的程序定义了一个名为 Study
的接口,接口中有未实现的方法 learn()
,这里还定义了名为 Student
的结构体,其绑定了方法 learn()
,也就隐式实现了 Study
接口,实现的内容是打印语句。
接口实现多态 go package main
+
+import " fmt "
+
+type Study interface {
+ learn ()
+}
+type Student struct {
+ name, book string
+}
+
+func (s Student) learn () {
+ fmt. Printf ( " %s 在读 %s " , s.name, s.book)
+}
+
+type Worker struct {
+ name string
+ book string
+ by string
+}
+
+func (w * Worker) learn () {
+ fmt. Printf ( " %s 在读 %s ,通过方式 %s " , w.name, w.book, w.by)
+}
+
+func main () {
+ var s1 Study
+ var s2 Study
+
+ student2 := Student{
+ name: "李四" ,
+ book: "《Go语言》" ,
+ }
+ s1 = student2
+ s1. learn ()
+
+ student3 := Student{
+ name: "王五" ,
+ book: "Go语言1" ,
+ }
+ s1 = & student3
+ s1. learn ()
+
+ worker1 := Worker{
+ name: "老王" ,
+ book: "Go语言2" ,
+ by: "视频" ,
+ }
+ // s2 = worker1 // error
+ s2 = & worker1
+ s2. learn ()
+}
package main
+
+import " fmt "
+
+type Study interface {
+ learn ()
+}
+type Student struct {
+ name, book string
+}
+
+func (s Student) learn () {
+ fmt. Printf ( " %s 在读 %s " , s.name, s.book)
+}
+
+type Worker struct {
+ name string
+ book string
+ by string
+}
+
+func (w * Worker) learn () {
+ fmt. Printf ( " %s 在读 %s ,通过方式 %s " , w.name, w.book, w.by)
+}
+
+func main () {
+ var s1 Study
+ var s2 Study
+
+ student2 := Student{
+ name: "李四" ,
+ book: "《Go语言》" ,
+ }
+ s1 = student2
+ s1. learn ()
+
+ student3 := Student{
+ name: "王五" ,
+ book: "Go语言1" ,
+ }
+ s1 = & student3
+ s1. learn ()
+
+ worker1 := Worker{
+ name: "老王" ,
+ book: "Go语言2" ,
+ by: "视频" ,
+ }
+ // s2 = worker1 // error
+ s2 = & worker1
+ s2. learn ()
+}
接口的内部表示 可以把接口的内部看做 (type, value)
。type
是接口底层的具体类型(Concrete Type),而 value
是具体类型的值。
go package main
+
+import " fmt "
+
+type Study interface {
+ learn ()
+}
+type Student struct {
+ name, book string
+}
+
+func (s Student) learn () {
+ fmt. Printf ( " %s 在读 %s " , s.name, s.book)
+}
+func ShowInterface (s Study) {
+ fmt. Printf ( "接口类型: %T\\n 接口值: %v\\n " , s, s)
+}
+
+func main () {
+ var s Study
+ student2 := Student{
+ name: "李四" ,
+ book: "《Go语言》" ,
+ }
+ s = student2
+ ShowInterface (s)
+ s. learn ()
+}
+//接口类型: main.Student
+//接口值: {李四 《Go语言》}
+//李四 在读 《Go语言》
package main
+
+import " fmt "
+
+type Study interface {
+ learn ()
+}
+type Student struct {
+ name, book string
+}
+
+func (s Student) learn () {
+ fmt. Printf ( " %s 在读 %s " , s.name, s.book)
+}
+func ShowInterface (s Study) {
+ fmt. Printf ( "接口类型: %T\\n 接口值: %v\\n " , s, s)
+}
+
+func main () {
+ var s Study
+ student2 := Student{
+ name: "李四" ,
+ book: "《Go语言》" ,
+ }
+ s = student2
+ ShowInterface (s)
+ s. learn ()
+}
+//接口类型: main.Student
+//接口值: {李四 《Go语言》}
+//李四 在读 《Go语言》
空接口 空接口 是特殊形式的接口类型,没有定义任何方法的接口就称为空接口,可以说所有类型都至少实现了空接口,空接口表示为 interface{}
。例如,我们之前的写过的空接口参数函数,可以接受任何类型的参数:
go package main
+
+import " fmt "
+
+func ShowType (i interface {}) {
+ fmt. Printf ( "类型: %T , 值: %v\\n " , i, i)
+}
+
+func main () {
+ str := "Go语言"
+ ShowType (str)
+ num := 3.14
+ ShowType (num)
+}
package main
+
+import " fmt "
+
+func ShowType (i interface {}) {
+ fmt. Printf ( "类型: %T , 值: %v\\n " , i, i)
+}
+
+func main () {
+ str := "Go语言"
+ ShowType (str)
+ num := 3.14
+ ShowType (num)
+}
通过上面的例子不难发现接口都有两个属性,一个是值,而另一个是类型。对于空接口来说,这两个属性都为 nil
go package main
+
+import " fmt "
+
+func main () {
+ var i interface {}
+ fmt. Printf ( "Type: %T , Value: %v " , i, i)
+ // Type: <nil>, Value: <nil>
+}
package main
+
+import " fmt "
+
+func main () {
+ var i interface {}
+ fmt. Printf ( "Type: %T , Value: %v " , i, i)
+ // Type: <nil>, Value: <nil>
+}
除了上面讲到的使用空接口作为函数参数的用法,空接口还有以下两种用法。
直接使用 interface{}
作为类型声明一个实例,这个实例就能承载任何类型的值:
go package main
+
+import " fmt "
+
+func main () {
+ var i interface {}
+
+ i = "Go语言"
+ fmt. Println (i) // Let's go
+
+ i = 3.14
+ fmt. Println (i) // 3.14
+}
package main
+
+import " fmt "
+
+func main () {
+ var i interface {}
+
+ i = "Go语言"
+ fmt. Println (i) // Let's go
+
+ i = 3.14
+ fmt. Println (i) // 3.14
+}
我们也可以定义一个接收任何类型的 array
、 slice
、 map
、 strcut
。例如:
go package main
+
+import " fmt "
+
+func main () {
+ x := make ([] interface {}, 3 )
+ x[ 0 ] = "Go"
+ x[ 1 ] = 3.14
+ x[ 2 ] = [] int { 1 , 2 , 3 }
+ for _, value := range x {
+ fmt. Println (value)
+ }
+}
package main
+
+import " fmt "
+
+func main () {
+ x := make ([] interface {}, 3 )
+ x[ 0 ] = "Go"
+ x[ 1 ] = 3.14
+ x[ 2 ] = [] int { 1 , 2 , 3 }
+ for _, value := range x {
+ fmt. Println (value)
+ }
+}
空接口可以承载任何值,但是空接口类型的对象是不能赋值给另一个固定类型对象的。
go package main
+
+func main () {
+ var num = 1
+ var i interface {} = num
+ var str string = i // error
+}
package main
+
+func main () {
+ var num = 1
+ var i interface {} = num
+ var str string = i // error
+}
当空接口承载数组和切片后,该对象无法再进行切片。
go package main
+
+import " fmt "
+
+func main () {
+ var s = [] int { 1 , 2 , 3 }
+
+ var i interface {} = s
+
+ var s2 = i[ 1 : 2 ] // error
+ fmt. Println (s2)
+}
package main
+
+import " fmt "
+
+func main () {
+ var s = [] int { 1 , 2 , 3 }
+
+ var i interface {} = s
+
+ var s2 = i[ 1 : 2 ] // error
+ fmt. Println (s2)
+}
类型断言 类型断言用于提取接口的底层值(Underlying Value)。使用 interface.(Type)
可以获取接口的底层值,其中接口 interface
的具体类型是 Type
go package main
+
+import " fmt "
+
+func assert (i interface {}) {
+ value, ok := i.( int )
+ fmt. Println (value, ok)
+}
+
+func main () {
+ var x interface {} = 3
+ assert (x)
+ var y interface {} = "Go语言"
+ assert (y)
+}
package main
+
+import " fmt "
+
+func assert (i interface {}) {
+ value, ok := i.( int )
+ fmt. Println (value, ok)
+}
+
+func main () {
+ var x interface {} = 3
+ assert (x)
+ var y interface {} = "Go语言"
+ assert (y)
+}
第一次调用 assert(x)
输出 3 true
,表示将整数 3 转换为 int
类型成功。
第二次调用 assert(y)
输出 0 false
,表示将字符串 "Go语言" 转换为 int
类型失败,因为该字符串无法转换为整数。
类型选择 go package main
+
+import " fmt "
+
+func getTypeValue (i interface {}) {
+ switch i.(type) {
+ case int :
+ fmt. Printf ( "Type: int, Value: %d\\n " , i.( int ))
+ case string :
+ fmt. Printf ( "Type: string, Value: %s\\n " , i.( string ))
+ default :
+ fmt. Printf ( "Unknown type \\n " )
+ }
+}
+
+func main () {
+ getTypeValue ( 300 )
+ getTypeValue ( "Go语言" )
+ getTypeValue ( true )
+}
package main
+
+import " fmt "
+
+func getTypeValue (i interface {}) {
+ switch i.(type) {
+ case int :
+ fmt. Printf ( "Type: int, Value: %d\\n " , i.( int ))
+ case string :
+ fmt. Printf ( "Type: string, Value: %s\\n " , i.( string ))
+ default :
+ fmt. Printf ( "Unknown type \\n " )
+ }
+}
+
+func main () {
+ getTypeValue ( 300 )
+ getTypeValue ( "Go语言" )
+ getTypeValue ( true )
+}
实现多个接口 类型或者结构体可以实现多个接口
接口的嵌套 虽然在 Go 中没有继承机制,但可以通过接口的嵌套实现类似功能。
go package main
+
+import " fmt "
+
+// 定义一个简单的读取器接口
+type Reader interface {
+ Read () string
+}
+
+// 定义一个简单的写入器接口
+type Writer interface {
+ Write (data string )
+}
+
+// 定义一个复合接口,嵌套了Reader和Writer接口
+type ReadWriter interface {
+ Reader
+ Writer
+}
+
+// 实现Reader接口
+type MyReader struct {}
+
+func (r MyReader) Read () string {
+ return "Data read from MyReader"
+}
+
+// 实现Writer接口
+type MyWriter struct {}
+
+func (w MyWriter) Write (data string ) {
+ fmt. Println ( "Writing data:" , data)
+}
+
+// 实现ReadWriter接口
+type MyReadWriter struct {
+ MyReader
+ MyWriter
+}
+
+// 使用ReadWriter接口作为参数进行函数调用
+func ProcessData (rw ReadWriter) {
+ data := rw. Read ()
+ rw. Write (data + " modified" )
+}
+
+func main () {
+ // 创建MyReadWriter实例
+ myRW := MyReadWriter{}
+
+ // 调用ProcessData函数,传入myRW作为参数
+ ProcessData (myRW)
+}
package main
+
+import " fmt "
+
+// 定义一个简单的读取器接口
+type Reader interface {
+ Read () string
+}
+
+// 定义一个简单的写入器接口
+type Writer interface {
+ Write (data string )
+}
+
+// 定义一个复合接口,嵌套了Reader和Writer接口
+type ReadWriter interface {
+ Reader
+ Writer
+}
+
+// 实现Reader接口
+type MyReader struct {}
+
+func (r MyReader) Read () string {
+ return "Data read from MyReader"
+}
+
+// 实现Writer接口
+type MyWriter struct {}
+
+func (w MyWriter) Write (data string ) {
+ fmt. Println ( "Writing data:" , data)
+}
+
+// 实现ReadWriter接口
+type MyReadWriter struct {
+ MyReader
+ MyWriter
+}
+
+// 使用ReadWriter接口作为参数进行函数调用
+func ProcessData (rw ReadWriter) {
+ data := rw. Read ()
+ rw. Write (data + " modified" )
+}
+
+func main () {
+ // 创建MyReadWriter实例
+ myRW := MyReadWriter{}
+
+ // 调用ProcessData函数,传入myRW作为参数
+ ProcessData (myRW)
+}
定义了三个接口:Reader
、Writer
和ReadWriter
。然后,我们实现了这些接口的具体类型:MyReader
、MyWriter
和MyReadWriter
。
MyReadWriter
结构体通过嵌套MyReader
和MyWriter
,同时实现了Reader
和Writer
接口。这样,MyReadWriter
可以以ReadWriter
类型的方式使用。
在main
函数中,我们创建了一个MyReadWriter
实例myRW
,然后将其作为参数传递给ProcessData
函数。ProcessData
函数接收一个ReadWriter
类型的参数,并调用其中的方法。
通过接口嵌套,我们可以更灵活地组织和复用代码
包 包(package) 用于组织 Go 源代码,提供了更好的可重用性与可读性.可以用 go list std
命令查看标准包,标准库为大多数的程序提供了必要的基础组件。
创建包 先创建一个 book
文件夹,位于该目录下创建一个 book.go
源文件,里面实现自定义的数学加法函数。函数名的首字母要大写。
go // Package book
+package book
+
+func ShowBookInfo (bookName, authorName string ) ( string , error ) {
+ if bookName == "" {
+ return "" , errors. New ( "图书名称为空" )
+ }
+ if authorName == "" {
+ return "" , errors. New ( "作者名称为空" )
+ }
+ return bookName + ",作者:" + authorName, nil
+}
// Package book
+package book
+
+func ShowBookInfo (bookName, authorName string ) ( string , error ) {
+ if bookName == "" {
+ return "" , errors. New ( "图书名称为空" )
+ }
+ if authorName == "" {
+ return "" , errors. New ( "作者名称为空" )
+ }
+ return bookName + ",作者:" + authorName, nil
+}
导入包 使用包之前我们需要导入包,在 GoLand 中会帮你自动导入所需要的包。导入包的语法为 import path
,其中 path
可以是相对于工作区文件夹的相对路径,也可以是绝对路径。
go package main
+
+import (
+ " fmt "
+ " learn/book "
+)
+
+func main () {
+ bookName := "《Go语言》"
+ author := "Golang"
+ bookInfo, _ := book. ShowBookInfo (bookName, author)
+ fmt. Println ( "bookInfo = " , bookInfo)
+}
package main
+
+import (
+ " fmt "
+ " learn/book "
+)
+
+func main () {
+ bookName := "《Go语言》"
+ author := "Golang"
+ bookInfo, _ := book. ShowBookInfo (bookName, author)
+ fmt. Println ( "bookInfo = " , bookInfo)
+}
使用别名 go import (
+ " crypto/rand "
+ mrand " math/rand " // 将名称替换为 mrand 避免冲突
+)
import (
+ " crypto/rand "
+ mrand " math/rand " // 将名称替换为 mrand 避免冲突
+)
使用点操作 go import . " fmt "
+
+func main () {
+ Println ( "hello, world" )
+}
import . " fmt "
+
+func main () {
+ Println ( "hello, world" )
+}
对于一些使用高频的包,例如 fmt
包,每次调用打印函数时都要使用 fmt.Println()
进行调用,很不方便。可以在导入包的时,使用 import . package_path
语法。打印就不用加 fmt
了。
包的初始化 每个包都允许有一个或多个 init
函数, init
函数不应该有任何返回值类型和参数,在代码中也不能显式调用它,当这个包被导入时,就会执行这个包的 init
函数,做初始化任务, init
函数优先于 main
函数执行。该函数形式如下:
go func init () {
+}
func init () {
+}
包的初始化顺序:首先初始化 包级别(Package Level) 的变量,紧接着调用 init
函数。包可以有多个 init
函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。如果一个包导入了另一个包,会先初始化被导入的包。尽管一个包可能会被导入多次,但是它只会被初始化一次。
包的匿名导入 导入一个没有使用的包编译会报错。但有时候我们只是想执行包里的 init
函数来执行一些初始化任务,可以使用匿名导入的方法,使用 空白标识符(Blank Identifier) :
go import _ " fmt "
import _ " fmt "
协程 Go 语言的 协程(Groutine) 是与其他函数或方法一起并发运行的工作方式。协程可以看作是轻量级线程。与线程相比,创建一个协程的成本很小。因此在 Go 应用中,常常会看到会有很多协程并发地运行。
启动一个 go 协程 调用函数或者方法时,如果在前面加上关键字 go
,就可以让一个新的 Go 协程并发地运行。
go // 定义一个函数
+func functionName (parameterList) {
+ code
+}
+
+// 执行一个函数
+functionName (parameterList)
+
+// 开启一个协程执行这个函数
+go functionName (parameterList)
// 定义一个函数
+func functionName (parameterList) {
+ code
+}
+
+// 执行一个函数
+functionName (parameterList)
+
+// 开启一个协程执行这个函数
+go functionName (parameterList)
go package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func PrintInfo () {
+ fmt. Println ( "Go语言" )
+}
+
+func main () {
+ // 开启一个协程执行 PrintInfo 函数
+ go PrintInfo ()
+ // 使主协程休眠 1 秒
+ time. Sleep ( 1 * time.Second)
+ // 打印 main
+ fmt. Println ( "main" )
+}
package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func PrintInfo () {
+ fmt. Println ( "Go语言" )
+}
+
+func main () {
+ // 开启一个协程执行 PrintInfo 函数
+ go PrintInfo ()
+ // 使主协程休眠 1 秒
+ time. Sleep ( 1 * time.Second)
+ // 打印 main
+ fmt. Println ( "main" )
+}
PrintInfo()
函数与 main()
函数会并发执行,主函数运行在一个特殊的协程上,这个协程称之为 主协程(Main Goroutine) 。
启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值。如果 Go 主协程终止,则程序终止,于是其他 Go 协程也会终止。为了让新的协程能继续运行,在 main()
函数添加了 time.Sleep(1 * time.Second)
使主协程休眠 1 秒
启动多个 Go 协程 go package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func PrintNum (num int ) {
+ for i := 0 ; i < 3 ; i ++ {
+ fmt. Println (num)
+ // 避免观察不到并发效果 加个休眠
+ time. Sleep ( 100 * time.Millisecond)
+ }
+}
+
+func main () {
+ // 开启 1 号协程
+ go PrintNum ( 1 )
+ // 开启 2 号协程
+ go PrintNum ( 2 )
+ // 使主协程休眠 1 秒
+ time. Sleep (time.Second)
+}
package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func PrintNum (num int ) {
+ for i := 0 ; i < 3 ; i ++ {
+ fmt. Println (num)
+ // 避免观察不到并发效果 加个休眠
+ time. Sleep ( 100 * time.Millisecond)
+ }
+}
+
+func main () {
+ // 开启 1 号协程
+ go PrintNum ( 1 )
+ // 开启 2 号协程
+ go PrintNum ( 2 )
+ // 使主协程休眠 1 秒
+ time. Sleep (time.Second)
+}
通道 通道(channel) ,就是一个管道,可以想像成 Go 协程之间通信的管道。它是一种队列式的数据结构,遵循先入先出的规则。
通道的声明 每个通道都只能传递一种数据类型的数据,在你声明的时候,我们要指定通道的类型。chan Type
表示 Type
类型的通道。通道的零值为 nil
。
go var channel_name chan channel_types
+
+var ch chan string
var channel_name chan channel_types
+
+var ch chan string
通道的初始化 声明完通道后,通道的值为 nil
,我们不能直接使用,必须先使用 make
函数对通道进行初始化操作。
go ch = make ( chan channel_type)
+
+ch = make ( chan string )
ch = make ( chan channel_type)
+
+ch = make ( chan string )
这样,我们就已经定义好了一个 string
类型的通道 nameChan
。当然,也可以使用简短声明语句一次性定义一个通道:
go ch := make ( chan string )
ch := make ( chan string )
使用通道发送和接收数据 发送数据:
go // 把 data 数据发送到 channel_name 通道中
+// 即把 data 数据写入到 channel_name 通道中
+channel_name <- data
// 把 data 数据发送到 channel_name 通道中
+// 即把 data 数据写入到 channel_name 通道中
+channel_name <- data
接收数据:
go // 从 channel_name 通道中接收数据到 value
+// 即从 channel_name 通道中读取数据到 value
+value := <- channel_name
// 从 channel_name 通道中接收数据到 value
+// 即从 channel_name 通道中读取数据到 value
+value := <- channel_name
通道旁的箭头方向指定了是发送数据还是接收数据。箭头指向通道,代表数据写入到通道中;箭头往通道指向外,代表从通道读数据出去。
go package main
+
+import (
+ " fmt "
+)
+
+func PrintChan (c chan string ) {
+ // 往通道传入数据
+ c <- "学习Go语言"
+}
+
+func main () {
+ // 创建一个通道
+ ch := make ( chan string )
+ // 打印 "学习课程:"
+ fmt. Println ( "学习课程:" )
+ // 开启协程
+ go PrintChan (ch)
+ // 从通道接收数据
+ rec := <- ch
+ // 打印从通道接收到的数据
+ fmt. Println (rec)
+}
package main
+
+import (
+ " fmt "
+)
+
+func PrintChan (c chan string ) {
+ // 往通道传入数据
+ c <- "学习Go语言"
+}
+
+func main () {
+ // 创建一个通道
+ ch := make ( chan string )
+ // 打印 "学习课程:"
+ fmt. Println ( "学习课程:" )
+ // 开启协程
+ go PrintChan (ch)
+ // 从通道接收数据
+ rec := <- ch
+ // 打印从通道接收到的数据
+ fmt. Println (rec)
+}
Tips : 发送与接收默认是阻塞的
从上面的例子我们知道,如果从通道接收数据没接收完主协程是不会继续执行下去的。当把数据发送到通道时,会在发送数据的语句处发生阻塞,直到有其它协程从通道读取到数据,才会解除阻塞。与此类似,当读取通道的数据时,如果没有其它的协程把数据写入到这个通道,那么读取过程就会一直阻塞着。 通道的关闭 go close (channel_name)
close (channel_name)
这里要注意,对于一个已经关闭的通道如果再次关闭会导致报错,我们可以在接收数据时,判断通道是否已经关闭,从通道读取数据返回的第二个值表示通道是否没被关闭,如果已经关闭,返回值为 false
;如果还未关闭,返回值为 true
。
go value, ok := <- channel_name
value, ok := <- channel_name
通道的容量与长度 make
函数是可以接收两个参数的,同理,创建通道可以传入第二个参数——容量。
当容量为 0
时,说明通道中不能存放数据,在发送数据时,必须要求立马有人接收,否则会报错。此时的通道称之为无缓冲通道。 当容量为 1
时,说明通道只能缓存一个数据,若通道中已有一个数据,此时再往里发送数据,会造成程序阻塞。利用这点可以利用通道来做锁。 当容量大于 1
时,通道中可以存放多个数据,可以用于多个协程之间的通信管道,共享资源。 既然通道有容量和长度,那么我们可以通过 cap
函数和 len
函数获取通道的容量和长度。
go package main
+
+import (
+ " fmt "
+)
+
+func main () {
+ // 创建一个通道
+ c := make ( chan int , 3 )
+ fmt. Println ( "初始化后:" )
+ fmt. Println ( "cap =" , cap (c))
+ fmt. Println ( "len =" , len (c))
+ c <- 1
+ c <- 2
+ fmt. Println ( "传入两个数后:" )
+ fmt. Println ( "cap =" , cap (c))
+ fmt. Println ( "len =" , len (c))
+ <- c
+ fmt. Println ( "取出一个数后:" )
+ fmt. Println ( "cap =" , cap (c))
+ fmt. Println ( "len =" , len (c))
+}
package main
+
+import (
+ " fmt "
+)
+
+func main () {
+ // 创建一个通道
+ c := make ( chan int , 3 )
+ fmt. Println ( "初始化后:" )
+ fmt. Println ( "cap =" , cap (c))
+ fmt. Println ( "len =" , len (c))
+ c <- 1
+ c <- 2
+ fmt. Println ( "传入两个数后:" )
+ fmt. Println ( "cap =" , cap (c))
+ fmt. Println ( "len =" , len (c))
+ <- c
+ fmt. Println ( "取出一个数后:" )
+ fmt. Println ( "cap =" , cap (c))
+ fmt. Println ( "len =" , len (c))
+}
缓冲通道与无缓冲通道 按照是否可缓冲数据可分为:缓冲通道 与 无缓冲通道 。
无缓冲通道在通道里无法存储数据,接收端必须先于发送端准备好,以确保你发送完数据后,有人立马接收数据,否则发送端就会造成阻塞,原因很简单,通道中无法存储数据。也就是说发送端和接收端是同步运行的。
go c := make ( chan int )
+// 或者
+c := make ( chan int , 0 )
c := make ( chan int )
+// 或者
+c := make ( chan int , 0 )
缓冲通道允许通道里存储一个或多个数据,设置缓冲区后,发送端和接收端可以处于异步的状态。
go c := make ( chan int , 3 )
c := make ( chan int , 3 )
双向通道 到目前为止,上面定义的都是双向通道,既可以发送数据也可以接收数据。例如:
go package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func main () {
+ // 创建一个通道
+ c := make ( chan int )
+
+ // 发送数据
+ go func () {
+ fmt. Println ( "send: 1" )
+ c <- 1
+ }()
+
+ // 接收数据
+ go func () {
+ n := <- c
+ fmt. Println ( "receive:" , n)
+ }()
+
+ // 主协程休眠
+ time. Sleep (time.Millisecond)
+}
package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func main () {
+ // 创建一个通道
+ c := make ( chan int )
+
+ // 发送数据
+ go func () {
+ fmt. Println ( "send: 1" )
+ c <- 1
+ }()
+
+ // 接收数据
+ go func () {
+ n := <- c
+ fmt. Println ( "receive:" , n)
+ }()
+
+ // 主协程休眠
+ time. Sleep (time.Millisecond)
+}
单向通道 单向通道只能发送或者接收数据。所以可以具体细分为只读通道和只写通道。
<-chan
表示只读通道:
chan<-
表示只写通道:
go package main
+
+import (
+ " fmt "
+ " time "
+)
+
+// Sender 只写通道类型
+type Sender = chan<- string
+
+// Receiver 只读通道类型
+type Receiver = <-chan string
+
+func main () {
+ // 创建一个双向通道
+ var ch = make ( chan string )
+
+ // 开启一个协程
+ go func () {
+ // 只能写通道
+ var sender Sender = ch
+ fmt. Println ( "即将学习:" )
+ sender <- "Go语言"
+ }()
+
+ // 开启一个协程
+ go func () {
+ // 只能读通道
+ var receiver Receiver = ch
+ message := <- receiver
+ fmt. Println ( "开始学习: " , message)
+ }()
+
+ time. Sleep (time.Millisecond)
+}
package main
+
+import (
+ " fmt "
+ " time "
+)
+
+// Sender 只写通道类型
+type Sender = chan<- string
+
+// Receiver 只读通道类型
+type Receiver = <-chan string
+
+func main () {
+ // 创建一个双向通道
+ var ch = make ( chan string )
+
+ // 开启一个协程
+ go func () {
+ // 只能写通道
+ var sender Sender = ch
+ fmt. Println ( "即将学习:" )
+ sender <- "Go语言"
+ }()
+
+ // 开启一个协程
+ go func () {
+ // 只能读通道
+ var receiver Receiver = ch
+ message := <- receiver
+ fmt. Println ( "开始学习: " , message)
+ }()
+
+ time. Sleep (time.Millisecond)
+}
遍历通道 使用 for range
循环可以遍历通道,但在遍历时要确保通道是处于关闭状态,否则循环会被阻塞。
go package main
+
+import (
+ " fmt "
+)
+
+func loopPrint (c chan int ) {
+ for i := 0 ; i < 10 ; i ++ {
+ c <- i
+ }
+ // 记得要关闭通道
+ // 否则主协程遍历完不会结束,而会阻塞
+ close (c)
+}
+
+func main () {
+ // 创建一个通道
+ var ch2 = make ( chan int , 5 )
+ go loopPrint (ch2)
+ for v := range ch2 {
+ fmt. Println (v)
+ }
+}
package main
+
+import (
+ " fmt "
+)
+
+func loopPrint (c chan int ) {
+ for i := 0 ; i < 10 ; i ++ {
+ c <- i
+ }
+ // 记得要关闭通道
+ // 否则主协程遍历完不会结束,而会阻塞
+ close (c)
+}
+
+func main () {
+ // 创建一个通道
+ var ch2 = make ( chan int , 5 )
+ go loopPrint (ch2)
+ for v := range ch2 {
+ fmt. Println (v)
+ }
+}
用通道做锁 上面讲过,当通道容量为 1
时,说明通道只能缓存一个数据,若通道中已有一个数据,此时再往里发送数据,会造成程序阻塞。例如:
go package main
+
+import (
+ " fmt "
+ " time "
+)
+
+// 由于 x = x+1 不是原子操作
+// 所以应避免多个协程对 x 进行操作
+// 使用容量为 1 的通道可以达到锁的效果
+func increment (ch chan bool , x *int ) {
+ ch <- true
+ * x = * x + 1
+ <- ch
+}
+
+func main () {
+ ch3 := make ( chan bool , 1 )
+ var x int
+ for i := 0 ; i < 10000 ; i ++ {
+ go increment (ch3, & x)
+ }
+ time. Sleep (time.Millisecond)
+ fmt. Println ( "x =" , x)
+}
package main
+
+import (
+ " fmt "
+ " time "
+)
+
+// 由于 x = x+1 不是原子操作
+// 所以应避免多个协程对 x 进行操作
+// 使用容量为 1 的通道可以达到锁的效果
+func increment (ch chan bool , x *int ) {
+ ch <- true
+ * x = * x + 1
+ <- ch
+}
+
+func main () {
+ ch3 := make ( chan bool , 1 )
+ var x int
+ for i := 0 ; i < 10000 ; i ++ {
+ go increment (ch3, & x)
+ }
+ time. Sleep (time.Millisecond)
+ fmt. Println ( "x =" , x)
+}
死锁 当协程给一个通道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic
,形成死锁。同理,当有协程等着从一个通道接收数据时,我们期望其他的 Go 协程会向该通道写入数据,要不然程序也会触发 panic
。
go package main
+
+func main () {
+ ch := make ( chan bool )
+ ch <- true
+}
+//fatal error: all goroutines are asleep - deadlock!
package main
+
+func main () {
+ ch := make ( chan bool )
+ ch <- true
+}
+//fatal error: all goroutines are asleep - deadlock!
go package main
+
+import " fmt "
+
+func main () {
+ ch := make ( chan bool )
+ ch <- true
+ fmt. Println ( <- ch)
+}
+//fatal error: all goroutines are asleep - deadlock!
+//使用 make 函数创建通道时默认不传递第二个参数,通道中不能存放数据,在发送数据时,必须要求立马有人接收,即该通道为无缓冲通道。所以在接收者没有准备好前,发送操作会被阻塞。
package main
+
+import " fmt "
+
+func main () {
+ ch := make ( chan bool )
+ ch <- true
+ fmt. Println ( <- ch)
+}
+//fatal error: all goroutines are asleep - deadlock!
+//使用 make 函数创建通道时默认不传递第二个参数,通道中不能存放数据,在发送数据时,必须要求立马有人接收,即该通道为无缓冲通道。所以在接收者没有准备好前,发送操作会被阻塞。
go package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func funcRecieve (c chan bool ) {
+ fmt. Println ( <- c)
+}
+func main () {
+ ch4 := make ( chan bool )
+ go funcRecieve (ch4)
+ ch4 <- true
+ time. Sleep (time.Millisecond)
+}
+
+
+// 或
+
+package main
+
+import " fmt "
+
+func main () {
+ ch6 := make ( chan bool , 1 )
+ ch6 <- true
+ ch6 <- false
+ fmt. Println ( <- ch6)
+}
package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func funcRecieve (c chan bool ) {
+ fmt. Println ( <- c)
+}
+func main () {
+ ch4 := make ( chan bool )
+ go funcRecieve (ch4)
+ ch4 <- true
+ time. Sleep (time.Millisecond)
+}
+
+
+// 或
+
+package main
+
+import " fmt "
+
+func main () {
+ ch6 := make ( chan bool , 1 )
+ ch6 <- true
+ ch6 <- false
+ fmt. Println ( <- ch6)
+}
WaitGroup 在实际开发中我们并不能保证每个协程执行的时间,如果需要等待多个协程,全部结束任务后,再执行某个业务逻辑。下面我们介绍处理这种情况的方式。
WaitGroup
有几个方法:
Add
:初始值为 0
,这里直接传入子协程的数量,你传入的值会往计数器上加。Done
:当某个子协程完成后,可调用此方法,会从计数器上减一,即子协程的数量减一,通常使用 defer
来调用。Wait
:阻塞当前协程,直到实例里的计数器归零。使用信道 信道可以实现多个协程间的通信,于是乎我们可以定义一个信道,在任务执行完成后,往信道中写入 true
,然后在主协程中获取到 true
,就可以认为子协程已经执行完毕。
go package main
+
+import " fmt "
+
+func main () {
+ isDone := make ( chan bool )
+ go func () {
+ for i := 0 ; i < 5 ; i ++ {
+ fmt. Println (i)
+ }
+ isDone <- true
+ }()
+ <- isDone
+}
package main
+
+import " fmt "
+
+func main () {
+ isDone := make ( chan bool )
+ go func () {
+ for i := 0 ; i < 5 ; i ++ {
+ fmt. Println (i)
+ }
+ isDone <- true
+ }()
+ <- isDone
+}
运行上面的程序,主协程就会等待创建的协程执行完毕后退出。
使用 WaitGroup 使用上面的信道方法,虽然可行,但在你程序中使用很多协程的话,你的代码就会看起来很复杂,这里就要介绍一种更好的方法,那就是使用 sync
包中提供的 WaitGroup 类型。WaitGroup
用于等待一批 Go 协程执行结束。程序控制会一直阻塞,直到这些协程全部执行完毕。当然 WaitGroup
也可以用于实现工作池。
WaitGroup
实例化后就能使用:
go var name sync.WaitGroup
var name sync.WaitGroup
go package main
+
+import (
+ " fmt "
+ " sync "
+)
+
+func task (taskNum int , wg * sync.WaitGroup) {
+ // 延迟调用 执行完子协程计数器减一
+ defer wg. Done ()
+ // 输出任务号
+ for i := 0 ; i < 3 ; i ++ {
+ fmt. Printf ( "task %d : %d\\n " , taskNum, i)
+ }
+}
+
+func main () {
+ // 实例化 sync.WaitGroup
+ var waitGroup sync.WaitGroup
+ // 传入子协程的数量
+ waitGroup. Add ( 3 )
+ // 开启一个子协程 协程 1 以及 实例 waitGroup
+ go task ( 1 , & waitGroup)
+ // 开启一个子协程 协程 2 以及 实例 waitGroup
+ go task ( 2 , & waitGroup)
+ // 开启一个子协程 协程 3 以及 实例 waitGroup
+ go task ( 3 , & waitGroup)
+ // 实例 waitGroup 阻塞当前协程 等待所有子协程执行完
+ waitGroup. Wait ()
+}
package main
+
+import (
+ " fmt "
+ " sync "
+)
+
+func task (taskNum int , wg * sync.WaitGroup) {
+ // 延迟调用 执行完子协程计数器减一
+ defer wg. Done ()
+ // 输出任务号
+ for i := 0 ; i < 3 ; i ++ {
+ fmt. Printf ( "task %d : %d\\n " , taskNum, i)
+ }
+}
+
+func main () {
+ // 实例化 sync.WaitGroup
+ var waitGroup sync.WaitGroup
+ // 传入子协程的数量
+ waitGroup. Add ( 3 )
+ // 开启一个子协程 协程 1 以及 实例 waitGroup
+ go task ( 1 , & waitGroup)
+ // 开启一个子协程 协程 2 以及 实例 waitGroup
+ go task ( 2 , & waitGroup)
+ // 开启一个子协程 协程 3 以及 实例 waitGroup
+ go task ( 3 , & waitGroup)
+ // 实例 waitGroup 阻塞当前协程 等待所有子协程执行完
+ waitGroup. Wait ()
+}
Select select 语句用在多个发送/接收通道操作中进行选择。
select
语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个通道操作准备完毕, select
会随机地选取其中之一执行。 select
语法如下:
go select {
+ case expression1:
+ code
+ case expression2:
+ code
+ default :
+ code
+}
select {
+ case expression1:
+ code
+ case expression2:
+ code
+ default :
+ code
+}
go package main
+
+import " fmt "
+
+func main () {
+ // 创建3个通道
+ ch1 := make ( chan string , 1 )
+ ch2 := make ( chan string , 1 )
+ ch3 := make ( chan string , 1 )
+ // 往通道 1 发送数据
+ ch1 <- "Go语言1"
+ // 往通道 2 发送数据
+ ch2 <- "Go语言2"
+ // 往通道 3 发送数据
+ ch3 <- "Go语言3"
+
+ select {
+ // 如果从通道 1 收到数据
+ case message1 := <- ch1:
+ fmt. Println ( "ch1 received:" , message1)
+ // 如果从通道 2 收到数据
+ case message2 := <- ch2:
+ fmt. Println ( "ch2 received:" , message2)
+ // 如果从通道 3 收到数据
+ case message3 := <- ch3:
+ fmt. Println ( "ch3 received:" , message3)
+ // 默认输出
+ default :
+ fmt. Println ( "No data received." )
+ }
+}
package main
+
+import " fmt "
+
+func main () {
+ // 创建3个通道
+ ch1 := make ( chan string , 1 )
+ ch2 := make ( chan string , 1 )
+ ch3 := make ( chan string , 1 )
+ // 往通道 1 发送数据
+ ch1 <- "Go语言1"
+ // 往通道 2 发送数据
+ ch2 <- "Go语言2"
+ // 往通道 3 发送数据
+ ch3 <- "Go语言3"
+
+ select {
+ // 如果从通道 1 收到数据
+ case message1 := <- ch1:
+ fmt. Println ( "ch1 received:" , message1)
+ // 如果从通道 2 收到数据
+ case message2 := <- ch2:
+ fmt. Println ( "ch2 received:" , message2)
+ // 如果从通道 3 收到数据
+ case message3 := <- ch3:
+ fmt. Println ( "ch3 received:" , message3)
+ // 默认输出
+ default :
+ fmt. Println ( "No data received." )
+ }
+}
在执行 select
语句时,如果有机会的话会运行所有表达式,只要其中一个通道接收到数据,那么就会执行对应的 case
代码,然后退出。
select 的应用 每个任务执行的时间不同,使用 select
语句等待相应的通道发出响应。select
会选择首先响应先完成的 task,而忽略其它的响应。使用这种方法,我们可以做多个 task,并给用户返回最快的 task 结果。
go package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func task1 (ch chan string ) {
+ time. Sleep ( 5 * time.Second)
+ ch <- "Go语言1"
+}
+
+func task2 (ch chan string ) {
+ time. Sleep ( 7 * time.Second)
+ ch <- "Go语言2"
+}
+
+func task3 (ch chan string ) {
+ time. Sleep ( 2 * time.Second)
+ ch <- "Go语言3"
+}
+
+func main () {
+ // 创建三个通道
+ ch1 := make ( chan string )
+ ch2 := make ( chan string )
+ ch3 := make ( chan string )
+ go task1 (ch1)
+ go task2 (ch2)
+ go task3 (ch3)
+
+ select {
+ // 如果从通道 1 收到数据
+ case message1 := <- ch1:
+ fmt. Println ( "ch1 received:" , message1)
+ // 如果从通道 2 收到数据
+ case message2 := <- ch2:
+ fmt. Println ( "ch2 received:" , message2)
+ // 如果从通道 3 收到数据
+ case message3 := <- ch3:
+ fmt. Println ( "ch3 received:" , message3)
+ }
+}
package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func task1 (ch chan string ) {
+ time. Sleep ( 5 * time.Second)
+ ch <- "Go语言1"
+}
+
+func task2 (ch chan string ) {
+ time. Sleep ( 7 * time.Second)
+ ch <- "Go语言2"
+}
+
+func task3 (ch chan string ) {
+ time. Sleep ( 2 * time.Second)
+ ch <- "Go语言3"
+}
+
+func main () {
+ // 创建三个通道
+ ch1 := make ( chan string )
+ ch2 := make ( chan string )
+ ch3 := make ( chan string )
+ go task1 (ch1)
+ go task2 (ch2)
+ go task3 (ch3)
+
+ select {
+ // 如果从通道 1 收到数据
+ case message1 := <- ch1:
+ fmt. Println ( "ch1 received:" , message1)
+ // 如果从通道 2 收到数据
+ case message2 := <- ch2:
+ fmt. Println ( "ch2 received:" , message2)
+ // 如果从通道 3 收到数据
+ case message3 := <- ch3:
+ fmt. Println ( "ch3 received:" , message3)
+ }
+}
上面的程序会发现,没有 default
分支,因为如果加了该默认分支,如果还没从通道接收到数据, select
语句就会直接执行 default
分支然后退出,而不是被阻塞。
造成死锁 如果没有 default
分支, select
就会阻塞,如果一直没有命中其中的某个 case
最后会造成死锁。
go package main
+
+import (
+ " fmt "
+)
+
+func main () {
+ // 创建两个通道
+ ch1 := make ( chan string , 1 )
+ ch2 := make ( chan string , 1 )
+ ch3 := make ( chan string , 1 )
+
+ select {
+ // 如果从通道 1 收到数据
+ case message1 := <- ch1:
+ fmt. Println ( "ch1 received:" , message1)
+ // 如果从通道 2 收到数据
+ case message2 := <- ch2:
+ fmt. Println ( "ch2 received:" , message2)
+ // 如果从通道 3 收到数据
+ case message3 := <- ch3:
+ fmt. Println ( "ch3 received:" , message3)
+ }
+}
+//fatal error: all goroutines are asleep - deadlock!
package main
+
+import (
+ " fmt "
+)
+
+func main () {
+ // 创建两个通道
+ ch1 := make ( chan string , 1 )
+ ch2 := make ( chan string , 1 )
+ ch3 := make ( chan string , 1 )
+
+ select {
+ // 如果从通道 1 收到数据
+ case message1 := <- ch1:
+ fmt. Println ( "ch1 received:" , message1)
+ // 如果从通道 2 收到数据
+ case message2 := <- ch2:
+ fmt. Println ( "ch2 received:" , message2)
+ // 如果从通道 3 收到数据
+ case message3 := <- ch3:
+ fmt. Println ( "ch3 received:" , message3)
+ }
+}
+//fatal error: all goroutines are asleep - deadlock!
运行上面的程序会造成死锁。解决该问题的方法是写好 default
分支。
还有另一种情况会导致死锁的发生,那就是使用空 select
:
go package main
+
+func main () {
+ select {}
+}
package main
+
+func main () {
+ select {}
+}
运行上面的程序会抛出 panic
。
Tips:
switch-case
里面的 case
是顺序执行的,但在 select
里并不是顺序执行的。在上面的第一个例子就可以看出,当 select
由多个 case
准备就绪时,将会随机地选取其中之一去执行。select超时处理 当 case
里的通道始终没有接收到数据时,而且也没有 default
语句时, select
整体就会阻塞,但是有时我们并不希望 select
一直阻塞下去,这时候就可以手动设置一个超时时间。
go package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func makeTimeout (ch chan bool , t int ) {
+ time. Sleep (time.Second * time. Duration (t))
+ ch <- true
+}
+
+func main () {
+ c1 := make ( chan string , 1 )
+ c2 := make ( chan string , 1 )
+ c3 := make ( chan string , 1 )
+ timeout := make ( chan bool , 1 )
+
+ go makeTimeout (timeout, 2 )
+
+ select {
+ case msg1 := <- c1:
+ fmt. Println ( "c1 received: " , msg1)
+ case msg2 := <- c2:
+ fmt. Println ( "c2 received: " , msg2)
+ case msg3 := <- c3:
+ fmt. Println ( "c3 received: " , msg3)
+ case <- timeout:
+ fmt. Println ( "Timeout, exit." )
+ }
+}
package main
+
+import (
+ " fmt "
+ " time "
+)
+
+func makeTimeout (ch chan bool , t int ) {
+ time. Sleep (time.Second * time. Duration (t))
+ ch <- true
+}
+
+func main () {
+ c1 := make ( chan string , 1 )
+ c2 := make ( chan string , 1 )
+ c3 := make ( chan string , 1 )
+ timeout := make ( chan bool , 1 )
+
+ go makeTimeout (timeout, 2 )
+
+ select {
+ case msg1 := <- c1:
+ fmt. Println ( "c1 received: " , msg1)
+ case msg2 := <- c2:
+ fmt. Println ( "c2 received: " , msg2)
+ case msg3 := <- c3:
+ fmt. Println ( "c3 received: " , msg3)
+ case <- timeout:
+ fmt. Println ( "Timeout, exit." )
+ }
+}
读取/写入数据 select
里的 case
表达式只能对通道进行操作,不管你是往通道写入数据,还是从通道读出数据。
go package main
+
+import (
+ " fmt "
+)
+
+func main () {
+ c1 := make ( chan string , 2 )
+
+ c1 <- "Go语言1"
+ select {
+ case c1 <- "Go语言2" :
+ fmt. Println ( "c1 received: " , <- c1)
+ fmt. Println ( "c1 received: " , <- c1)
+ default :
+ fmt. Println ( "channel blocking" )
+ }
+}
+//c1 received: Go语言1
+//c1 received: Go语言2
package main
+
+import (
+ " fmt "
+)
+
+func main () {
+ c1 := make ( chan string , 2 )
+
+ c1 <- "Go语言1"
+ select {
+ case c1 <- "Go语言2" :
+ fmt. Println ( "c1 received: " , <- c1)
+ fmt. Println ( "c1 received: " , <- c1)
+ default :
+ fmt. Println ( "channel blocking" )
+ }
+}
+//c1 received: Go语言1
+//c1 received: Go语言2
线程同步 Go 语言中,经常会遇到并发的问题,当然我们会优先考虑使用通道,同时 Go 语言也给出了传统的解决方式 Mutex(互斥锁) 和 RWMutex(读写锁) 来处理竞争条件。
go type Bank struct {
+ balance int
+}
+
+func (b * Bank) Deposit (amount int ) {
+ b.balance += amount
+}
+
+func (b * Bank) Balance () int {
+ return b.balance
+}
+
+func main () {
+ b := & Bank{}
+
+ b. Deposit ( 1000 )
+ b. Deposit ( 1000 )
+ b. Deposit ( 1000 )
+
+ fmt. Println (b. Balance ()) //3000
+}
type Bank struct {
+ balance int
+}
+
+func (b * Bank) Deposit (amount int ) {
+ b.balance += amount
+}
+
+func (b * Bank) Balance () int {
+ return b.balance
+}
+
+func main () {
+ b := & Bank{}
+
+ b. Deposit ( 1000 )
+ b. Deposit ( 1000 )
+ b. Deposit ( 1000 )
+
+ fmt. Println (b. Balance ()) //3000
+}
临界区 当程序并发地运行时,多个 Go 协程不应该同时访问那些修改共享资源的代码。这些修改共享资源的代码称为临界区 。
go package main
+
+import (
+ " fmt "
+ " sync "
+)
+
+type Bank struct {
+ balance int
+}
+
+func (b * Bank) Deposit (amount int ) {
+ b.balance += amount
+}
+
+func (b * Bank) Balance () int {
+ return b.balance
+}
+func main () {
+ var wg sync.WaitGroup
+ b := & Bank{}
+
+ n := 1000
+ wg. Add (n)
+ for i := 1 ; i <= n; i ++ {
+ go func () {
+ b. Deposit ( 1000 )
+ wg. Done ()
+ }()
+ }
+ wg. Wait ()
+ fmt. Println (b. Balance ()) //972000,962000,941000
+}
package main
+
+import (
+ " fmt "
+ " sync "
+)
+
+type Bank struct {
+ balance int
+}
+
+func (b * Bank) Deposit (amount int ) {
+ b.balance += amount
+}
+
+func (b * Bank) Balance () int {
+ return b.balance
+}
+func main () {
+ var wg sync.WaitGroup
+ b := & Bank{}
+
+ n := 1000
+ wg. Add (n)
+ for i := 1 ; i <= n; i ++ {
+ go func () {
+ b. Deposit ( 1000 )
+ wg. Done ()
+ }()
+ }
+ wg. Wait ()
+ fmt. Println (b. Balance ()) //972000,962000,941000
+}
举一个简单的例子,当前变量的值增加 b.balance += amount
当然,对于只有一个协程的程序来说,上面的代码没有任何问题。但是,如果有多个协程并发运行时,就会发生错误,这种情况就称之为数据竞争(data race)。使用下面的互斥锁 Mutex
就能避免这种情况的发生。
互斥锁 Mutex 互斥锁(Mutex,mutual exclusion) 用于提供一种 加锁机制(Locking Mechanism) ,可确保在某时刻只有一个协程在临界区运行,以防止出现竞争。也是为了来保护一个资源不会因为并发操作而引起冲突导致数据不准确。
Mutex
有两个方法,分别是 Lock()
和 Unlock()
,即对应的加锁和解锁。在 Lock()
和 Unlock()
之间的代码,都只能由一个协程执行,就能避免竞争条件。
如果有一个协程已经持有了锁(Lock) ,当其他协程试图获得该锁时,这些协程会被阻塞,直到Mutex
解除锁定。
go package main
+
+import (
+ " fmt "
+ " sync "
+)
+
+type BankV2 struct {
+ balance int
+ m sync.Mutex
+}
+
+func (b * BankV2) Deposit (amount int ) {
+ b.m. Lock ()
+ b.balance += amount
+ b.m. Unlock ()
+}
+
+func (b * BankV2) Balance () int {
+ return b.balance
+}
+
+func main () {
+ var wg sync.WaitGroup
+ b := & BankV2{}
+
+ n := 1000
+ wg. Add (n)
+ for i := 1 ; i <= n; i ++ {
+ go func () {
+ b. Deposit ( 1000 )
+ wg. Done ()
+ }()
+ }
+ wg. Wait ()
+ fmt. Println (b. Balance ()) //1000000
+}
package main
+
+import (
+ " fmt "
+ " sync "
+)
+
+type BankV2 struct {
+ balance int
+ m sync.Mutex
+}
+
+func (b * BankV2) Deposit (amount int ) {
+ b.m. Lock ()
+ b.balance += amount
+ b.m. Unlock ()
+}
+
+func (b * BankV2) Balance () int {
+ return b.balance
+}
+
+func main () {
+ var wg sync.WaitGroup
+ b := & BankV2{}
+
+ n := 1000
+ wg. Add (n)
+ for i := 1 ; i <= n; i ++ {
+ go func () {
+ b. Deposit ( 1000 )
+ wg. Done ()
+ }()
+ }
+ wg. Wait ()
+ fmt. Println (b. Balance ()) //1000000
+}
要注意同一协程里不要在尚未解锁时再次加锁,也不要对已经解锁的锁再次解锁。
读写锁 RWMutex sync.RWMutex
类型实现读写互斥锁,适用于读多写少的场景,它规定了当有人还在读取数据(即读锁占用)时,不允许有人更新这个数据(即写锁会阻塞);为了保证程序的效率,多个人(协程)读取数据(拥有读锁)时,互不影响不会造成阻塞,它不会像 Mutex
那样只允许有一个人(协程)读取同一个数据。读锁与读锁兼容,读锁与写锁互斥,写锁与写锁互斥。
可以同时申请多个读锁; 有读锁时申请写锁将阻塞,有写锁时申请读锁将阻塞; 只要有写锁,后续申请读锁和写锁都将阻塞。 定义一个 RWMuteux
读写锁:
go var rwMutex sync.RWMutex
var rwMutex sync.RWMutex
RWMutex
里提供了两种锁,每种锁分别对应两个方法,为了避免死锁,两个方法应成对出现,必要时请使用 defer
。
读锁:调用 RLock
方法开启锁,调用 RUnlock
释放锁; 写锁:调用 Lock
方法开启锁,调用 Unlock
释放锁。 go package main
+
+import (
+ " fmt "
+ " sync "
+ " time "
+)
+
+type BankV3 struct {
+ balance int
+ rwMutex sync.RWMutex // read write lock
+}
+
+func (b * BankV3) Deposit (amount int ) {
+ b.rwMutex. Lock () // write lock
+ b.balance += amount
+ b.rwMutex. Unlock () // wirte unlock
+}
+
+func (b * BankV3) Balance () (balance int ) {
+ b.rwMutex. RLock () // read lock
+ balance = b.balance
+ b.rwMutex. RUnlock () // read unlock
+ return
+}
+
+func main () {
+ var wg sync.WaitGroup
+ b := & BankV3{}
+
+ n := 1000
+ wg. Add (n)
+ for i := 1 ; i <= n; i ++ {
+ go func () {
+ b. Deposit ( 1000 )
+ wg. Done ()
+ }()
+ }
+ wg. Wait ()
+ fmt. Println (b. Balance ())
+}
package main
+
+import (
+ " fmt "
+ " sync "
+ " time "
+)
+
+type BankV3 struct {
+ balance int
+ rwMutex sync.RWMutex // read write lock
+}
+
+func (b * BankV3) Deposit (amount int ) {
+ b.rwMutex. Lock () // write lock
+ b.balance += amount
+ b.rwMutex. Unlock () // wirte unlock
+}
+
+func (b * BankV3) Balance () (balance int ) {
+ b.rwMutex. RLock () // read lock
+ balance = b.balance
+ b.rwMutex. RUnlock () // read unlock
+ return
+}
+
+func main () {
+ var wg sync.WaitGroup
+ b := & BankV3{}
+
+ n := 1000
+ wg. Add (n)
+ for i := 1 ; i <= n; i ++ {
+ go func () {
+ b. Deposit ( 1000 )
+ wg. Done ()
+ }()
+ }
+ wg. Wait ()
+ fmt. Println (b. Balance ())
+}
条件变量 sync.Cond Cond 实现了一个条件变量,在 Locker 的基础上增加的一个消息通知的功能,保存了一个通知列表,用来唤醒一个或所有因等待条件变量而阻塞的 Go 程,以此来实现多个 Go 程间的同步。
错误与异常 错误 内建错误 在 Go 中, 错误 使用内建的 error
类型表示。error
类型是一个接口类型,它的定义如下:
go type error interface {
+ Error () string
+}
type error interface {
+ Error () string
+}
error
有了一个签名为 Error() string
的方法。所有实现该接口的类型都可以当作一个错误类型。Error()
方法给出了错误的描述。fmt.Println
在打印错误时,会在内部调用 Error() string
方法来得到该错误的描述。
go package main
+
+import (
+ " fmt "
+ " os "
+)
+
+func main () {
+ // 尝试打开文件
+ file, err := os. Open ( "/a.txt" )
+ // 如果打开文件时发生错误 返回一个不等于 nil 的错误
+ if err != nil {
+ fmt. Println (err)
+ return
+ }
+ // 如果打开文件成功 返回一个文件句柄 和 一个值为 nil 的错误
+ fmt. Println (file. Name (), "opened successfully" )
+}
+// open /a.txt: no such file or directory
package main
+
+import (
+ " fmt "
+ " os "
+)
+
+func main () {
+ // 尝试打开文件
+ file, err := os. Open ( "/a.txt" )
+ // 如果打开文件时发生错误 返回一个不等于 nil 的错误
+ if err != nil {
+ fmt. Println (err)
+ return
+ }
+ // 如果打开文件成功 返回一个文件句柄 和 一个值为 nil 的错误
+ fmt. Println (file. Name (), "opened successfully" )
+}
+// open /a.txt: no such file or directory
自定义错误 使用 errors
包中的 New
函数可以创建自定义错误。下面是 errors
包中 New
函数的实现代码:
go package errors
+
+func New (text string ) error {
+ return & errorString{text}
+}
+
+type errorString struct {
+ s string
+}
+
+func (e * errorString) Error () string {
+ return e.s
+}
package errors
+
+func New (text string ) error {
+ return & errorString{text}
+}
+
+type errorString struct {
+ s string
+}
+
+func (e * errorString) Error () string {
+ return e.s
+}
errorString
是一个结构体类型,只有一个字符串字段 s
。它使用了 errorString
指针接受者,来实现 error
接口的 Error() string
方法。New
函数有一个字符串参数,通过这个参数创建了 errorString
类型的变量,并返回了它的地址。于是它就创建并返回了一个新的错误。
下面是一个简单的自定义错误例子,该例子创建了一个计算矩形面积的函数,当矩形的长和宽两者有一个为负数时,就会返回一个错误:
go package main
+
+import (
+ " errors "
+ " fmt "
+)
+
+func area (a, b int ) ( int , error ) {
+ if a < 0 || b < 0 {
+ return 0 , errors. New ( "计算错误, 长度或宽度,不能小于0." )
+ }
+ return a * b, nil
+}
+func main () {
+ a := 100
+ b := - 10
+ r, err := area (a, b)
+ if err != nil {
+ fmt. Println (err)
+ return
+ }
+ fmt. Println ( "Area =" , r)
+}
package main
+
+import (
+ " errors "
+ " fmt "
+)
+
+func area (a, b int ) ( int , error ) {
+ if a < 0 || b < 0 {
+ return 0 , errors. New ( "计算错误, 长度或宽度,不能小于0." )
+ }
+ return a * b, nil
+}
+func main () {
+ a := 100
+ b := - 10
+ r, err := area (a, b)
+ if err != nil {
+ fmt. Println (err)
+ return
+ }
+ fmt. Println ( "Area =" , r)
+}
给错误添加更多信息 上面的程序能报出我们自定义的错误,但是没有具体说明是哪个数据出了问题,所以下面就来改进一下这个程序,我们使用 fmt
包中的 Errorf
函数,规定错误格式,并返回一个符合该错误的字符串。
go package main
+
+import (
+ " fmt "
+)
+
+func area (a, b int ) ( int , error ) {
+ if a < 0 || b < 0 {
+ return 0 , fmt. Errorf ( "计算错误, 长度 %d 或宽度 %d ,不能小于0" , a, b)
+ }
+ return a * b, nil
+}
+func main () {
+ a := 100
+ b := - 10
+ area, err := area (a, b)
+ if err != nil {
+ fmt. Println (err)
+ return
+ }
+ fmt. Println ( "Area =" , area)
+}
package main
+
+import (
+ " fmt "
+)
+
+func area (a, b int ) ( int , error ) {
+ if a < 0 || b < 0 {
+ return 0 , fmt. Errorf ( "计算错误, 长度 %d 或宽度 %d ,不能小于0" , a, b)
+ }
+ return a * b, nil
+}
+func main () {
+ a := 100
+ b := - 10
+ area, err := area (a, b)
+ if err != nil {
+ fmt. Println (err)
+ return
+ }
+ fmt. Println ( "Area =" , area)
+}
给错误添加更多信息还可以 使用结构体类型和字段 实现。下面还是通过改进上面的程序来讲解这种方法的实现:
首先创建一个表示错误的结构体类型,一般错误类型名称都是以 Error
结尾,上面的错误是由于面积计算中长度或宽度错误导致的,所以这里把结构体命名为 areaError
:
go package main
+
+import (
+ " fmt "
+)
+
+type areaError struct {
+ // 错误信息
+ err string
+ // 错误有关的长度
+ length int
+ // 错误有关的宽度
+ width int
+}
+
+// 使用指针接收者 *areaError 实现了 error 接口的 Error() string 方法
+func (e * areaError) Error () string {
+ // 打印长度和宽度以及错误的描述
+ return fmt. Sprintf ( "length %d , width %d : %s " , e.length, e.width, e.err)
+}
+
+func rectangleArea (a, b int ) ( int , error ) {
+ if a < 0 || b < 0 {
+ return 0 , & areaError{ "length or width is negative" , a, b}
+ }
+ return a * b, nil
+}
+func main () {
+ a := 100
+ b := - 10
+ area, err := rectangleArea (a, b)
+ // 检查了错误是否为 nil
+ if err != nil {
+ // 断言 *areaError 类型
+ if err, ok := err.( * areaError); ok {
+ // 如果错误是 *areaError 类型
+ // 用 err.length 和 err.width 来获取错误的长度和宽度 打印出自定义错误的消息
+ fmt. Printf ( "length %d or width %d is less than zero" , err.length, err.width)
+ return
+ }
+ fmt. Println (err)
+ return
+ }
+ fmt. Println ( "Area =" , area)
+}
package main
+
+import (
+ " fmt "
+)
+
+type areaError struct {
+ // 错误信息
+ err string
+ // 错误有关的长度
+ length int
+ // 错误有关的宽度
+ width int
+}
+
+// 使用指针接收者 *areaError 实现了 error 接口的 Error() string 方法
+func (e * areaError) Error () string {
+ // 打印长度和宽度以及错误的描述
+ return fmt. Sprintf ( "length %d , width %d : %s " , e.length, e.width, e.err)
+}
+
+func rectangleArea (a, b int ) ( int , error ) {
+ if a < 0 || b < 0 {
+ return 0 , & areaError{ "length or width is negative" , a, b}
+ }
+ return a * b, nil
+}
+func main () {
+ a := 100
+ b := - 10
+ area, err := rectangleArea (a, b)
+ // 检查了错误是否为 nil
+ if err != nil {
+ // 断言 *areaError 类型
+ if err, ok := err.( * areaError); ok {
+ // 如果错误是 *areaError 类型
+ // 用 err.length 和 err.width 来获取错误的长度和宽度 打印出自定义错误的消息
+ fmt. Printf ( "length %d or width %d is less than zero" , err.length, err.width)
+ return
+ }
+ fmt. Println (err)
+ return
+ }
+ fmt. Println ( "Area =" , area)
+}
还可以使用 结构体类型的方法 来给错误添加更多信息。下面我们继续完善上面的程序,让程序更加精确的定位是长度引发的错误还是宽度引发的错误。
go package main
+
+import (
+ " fmt "
+)
+
+type areaError struct {
+ // 错误信息
+ err string
+ // 长度
+ length int
+ // 宽度
+ width int
+}
+
+// 使用指针接收者 *areaError 实现了 error 接口的 Error() string 方法
+func (e * areaError) Error () string {
+ return e.err
+}
+
+// 长度为负数返回 true
+func (e * areaError) lengthNegative () bool {
+ return e.length < 0
+}
+
+// 宽度为负数返回 true
+func (e * areaError) widthNegative () bool {
+ return e.width < 0
+}
+
+func area (length, width int ) ( int , error ) {
+ err := ""
+ if length < 0 {
+ err += "length is less than zero"
+ }
+ if width < 0 {
+ if err == "" {
+ err = "width is less than zero"
+ } else {
+ err += " and width is less than zero"
+ }
+ }
+ if err != "" {
+ return 0 , & areaError{err, length, width}
+ }
+ return length * width, nil
+}
+
+func main () {
+ length := 100
+ width := - 10
+ area, err := area (length, width)
+ // 检查了错误是否为 nil
+ if err != nil {
+ // 断言 *areaError 类型
+ if err, ok := err.( * areaError); ok {
+ // 如果错误是 *areaError 类型
+ // 如果长度为负数 打印错误长度具体值
+ if err. lengthNegative () {
+ fmt. Printf ( "error: 长度 %d 小于0 \\n " , err.length)
+ }
+ // 如果宽度为负数 打印错误宽度具体值
+ if err. widthNegative () {
+ fmt. Printf ( "error: 宽度 %d 小于0 \\n " , err.width)
+ }
+ return
+ }
+ fmt. Println (err)
+ return
+ }
+ fmt. Println ( "Area =" , area)
+}
package main
+
+import (
+ " fmt "
+)
+
+type areaError struct {
+ // 错误信息
+ err string
+ // 长度
+ length int
+ // 宽度
+ width int
+}
+
+// 使用指针接收者 *areaError 实现了 error 接口的 Error() string 方法
+func (e * areaError) Error () string {
+ return e.err
+}
+
+// 长度为负数返回 true
+func (e * areaError) lengthNegative () bool {
+ return e.length < 0
+}
+
+// 宽度为负数返回 true
+func (e * areaError) widthNegative () bool {
+ return e.width < 0
+}
+
+func area (length, width int ) ( int , error ) {
+ err := ""
+ if length < 0 {
+ err += "length is less than zero"
+ }
+ if width < 0 {
+ if err == "" {
+ err = "width is less than zero"
+ } else {
+ err += " and width is less than zero"
+ }
+ }
+ if err != "" {
+ return 0 , & areaError{err, length, width}
+ }
+ return length * width, nil
+}
+
+func main () {
+ length := 100
+ width := - 10
+ area, err := area (length, width)
+ // 检查了错误是否为 nil
+ if err != nil {
+ // 断言 *areaError 类型
+ if err, ok := err.( * areaError); ok {
+ // 如果错误是 *areaError 类型
+ // 如果长度为负数 打印错误长度具体值
+ if err. lengthNegative () {
+ fmt. Printf ( "error: 长度 %d 小于0 \\n " , err.length)
+ }
+ // 如果宽度为负数 打印错误宽度具体值
+ if err. widthNegative () {
+ fmt. Printf ( "error: 宽度 %d 小于0 \\n " , err.width)
+ }
+ return
+ }
+ fmt. Println (err)
+ return
+ }
+ fmt. Println ( "Area =" , area)
+}
异常 错误和异常是两个不同的概念,非常容易混淆。错误指的是可能出现问题的地方出现了问题;而异常指的是不应该出现问题的地方出现了问题。
panic 在有些情况,当程序发生异常时,无法继续运行。在这种情况下,我们会使用 panic
来终止程序。当函数发生 panic
时,它会终止运行,在执行完所有的延迟函数后,程序返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic
信息,接着打印出堆栈跟踪,最后程序终止。
我们应该尽可能地使用错误,而不是使用 panic
和 recover
。只有当程序不能继续运行的时候,才应该使用 panic
和 recover
机制。
panic
有两个合理的用例:
发生了一个不能恢复的错误,此时程序不能继续运行。一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 panic
,因为如果不能绑定端口,啥也做不了。 发生了一个编程上的错误。假如我们有一个接收指针参数的方法,而其他人使用 nil
作为参数调用了它。在这种情况下,我们可以使用 panic
,因为这是一个编程错误:用 nil
参数调用了一个只能接收合法指针的方法。 go func panic (v interface {})
func panic (v interface {})
go package main
+
+func main () {
+ panic ( "panic error" )
+}
package main
+
+func main () {
+ panic ( "panic error" )
+}
发生 panic 时的 defer 上面已经提到了,当函数发生 panic
时,它会终止运行,在执行完所有的延迟函数后,程序返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic
信息,接着打印出堆栈跟踪,最后程序终止。
go package main
+
+import " fmt "
+
+func myTest () {
+ defer fmt. Println ( "defer myTest" )
+ panic ( "panic myTest" )
+}
+func main () {
+ defer fmt. Println ( "defer main" )
+ myTest ()
+}
+// defer myTest
+// defer main
+// panic: panic myTest
package main
+
+import " fmt "
+
+func myTest () {
+ defer fmt. Println ( "defer myTest" )
+ panic ( "panic myTest" )
+}
+func main () {
+ defer fmt. Println ( "defer main" )
+ myTest ()
+}
+// defer myTest
+// defer main
+// panic: panic myTest
recover recover
是一个内建函数,用于重新获得 panic
协程的控制。下面是内建函数 recover
的签名:
go func recover () interface {}
func recover () interface {}
recover
必须在 defer
函数中才能生效,在其他作用域下,它是不工作的。在延迟函数内调用 recover
,可以取到 panic
的错误信息,并且停止 panic
续发事件,程序运行恢复正常。
go package main
+
+import " fmt "
+
+func outOfArray (x int ) {
+ defer func () {
+ // recover() 可以将捕获到的 panic 信息打印
+ if err := recover (); err != nil {
+ fmt. Println (err)
+ }
+ }()
+ var array [ 5 ] int
+ array[x] = 1
+}
+func main () {
+ // 故意制造数组越界 触发 panic
+ outOfArray ( 20 )
+ // 如果能执行到这句 说明 panic 被捕获了
+ // 后续的程序能继续运行
+ fmt. Println ( "main..." )
+}
+// runtime error: index out of range [20] with length 5
+// main...
package main
+
+import " fmt "
+
+func outOfArray (x int ) {
+ defer func () {
+ // recover() 可以将捕获到的 panic 信息打印
+ if err := recover (); err != nil {
+ fmt. Println (err)
+ }
+ }()
+ var array [ 5 ] int
+ array[x] = 1
+}
+func main () {
+ // 故意制造数组越界 触发 panic
+ outOfArray ( 20 )
+ // 如果能执行到这句 说明 panic 被捕获了
+ // 后续的程序能继续运行
+ fmt. Println ( "main..." )
+}
+// runtime error: index out of range [20] with length 5
+// main...
虽然该程序触发了 panic
,但由于我们使用了 recover()
捕获了 panic
异常,并输出 panic
信息,即使 panic
会导致整个程序退出,但在退出前,有 defer
延迟函数,还是得执行完 defer
。然后程序还会继续执行下去
只有在相同的协程中调用 recover
才管用, recover
不能恢复一个不同协程的 panic
。
make 和 new new函数 内置函数 new
分配内存。该函数只接受一个参数,该参数是一个任意类型(包括自定义类型),而不是值,返回指向该类型新分配零值的指针。
go // The new built-in function allocates memory. The first argument is a type,
+// not a value, and the value returned is a pointer to a newly
+// allocated zero value of that type.
+func new (Type) * Type
// The new built-in function allocates memory. The first argument is a type,
+// not a value, and the value returned is a pointer to a newly
+// allocated zero value of that type.
+func new (Type) * Type
使用 new
函数首先会分配内存,并设置类型零值,最后返回指向该类型新分配零值的指针。
go package main
+
+import (
+ " fmt "
+)
+
+func main () {
+ num := new ( int )
+ // 打印出类型的值
+ fmt. Println ( * num) // 0
+}
package main
+
+import (
+ " fmt "
+)
+
+func main () {
+ num := new ( int )
+ // 打印出类型的值
+ fmt. Println ( * num) // 0
+}
make函数 内置函数 make
只能分配和初始化类型为 slice
、 map
或 chan
的对象。与 new
一样,第一个参数是类型,而不是值。与 new
不同, make
的返回类型与其参数的类型相同,而不是指向它的指针。结果取决于类型:
slice
:size 指定长度。切片的容量等于其长度。可提供第三个参数以指定不同的容量;它不能小于长度。map
:为空映射分配足够的空间来容纳指定数量的元素。可以省略大小,在这种情况下,分配一个小的起始大小。chan
:使用指定的缓冲区容量初始化通道的缓冲区。如果为零,或者忽略了大小,则通道是无缓冲的。go func make (t Type, size ... IntegerType) Type
func make (t Type, size ... IntegerType) Type
使用make函数必须初始化
go // slice
+a := make ([] int , 2 , 10 )
+
+// map
+b := make ( map [ string ] int )
+
+// chan
+c := make ( chan int , 10 )
// slice
+a := make ([] int , 2 , 10 )
+
+// map
+b := make ( map [ string ] int )
+
+// chan
+c := make ( chan int , 10 )
new 和 make 的区别 new
:为所有的类型分配内存,并初始化为零值,返回指针。
make
:只能为 slice
、 map
、 chan
分配内存,并初始化,返回的是类型。
反射 reflect 包 Go 语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法,而不需要在编译时就知道这些变量的具体类型。这种机制被称为 反射 。
在 Go 中 reflect
包实现了运行时反射。reflect
包会帮助识别 interface{}
变量的底层具体类型和具体值。
reflect.Type reflect.Type
表示 interface{}
的具体类型。reflect.TypeOf()
方法返回 reflect.Type
go package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectType (x interface {}) {
+ obj := reflect. TypeOf (x)
+ fmt. Println (obj)
+}
+
+func main () {
+ var a int64 = 123
+ reflectType (a)
+ var b string = "Go语言"
+ reflectType (b)
+}
package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectType (x interface {}) {
+ obj := reflect. TypeOf (x)
+ fmt. Println (obj)
+}
+
+func main () {
+ var a int64 = 123
+ reflectType (a)
+ var b string = "Go语言"
+ reflectType (b)
+}
reflect.Value reflect.Value
表示 interface{}
的具体值。reflect.ValueOf()
方法返回 reflect.Value
go package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectType (x interface {}) {
+ typeX := reflect. TypeOf (x)
+ valueX := reflect. ValueOf (x)
+ fmt. Println (typeX)
+ fmt. Println (valueX)
+}
+
+func main () {
+ var a int64 = 123
+ reflectType (a)
+ var b string = "Go语言"
+ reflectType (b)
+}
package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectType (x interface {}) {
+ typeX := reflect. TypeOf (x)
+ valueX := reflect. ValueOf (x)
+ fmt. Println (typeX)
+ fmt. Println (valueX)
+}
+
+func main () {
+ var a int64 = 123
+ reflectType (a)
+ var b string = "Go语言"
+ reflectType (b)
+}
relfect.Kind relfect.Kind
表示的是种类。在使用反射时,需要理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。
Go 语言程序中的类型(Type)指的是系统原生数据类型,如 int
、 string
、 bool
、 float32
等类型,以及使用 type
关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{}
定义结构体时,A
就是 struct{}
的类型。
种类(Kind)指的是对象归属的品种,在 reflect
包中有如下定义:
go // A Kind represents the specific kind of type that a Type represents.
+// The zero Kind is not a valid kind.
+type Kind uint
+
+const (
+ Invalid Kind = iota
+ Bool
+ Int
+ Int8
+ Int16
+ Int32
+ Int64
+ Uint
+ Uint8
+ Uint16
+ Uint32
+ Uint64
+ Uintptr
+ Float32
+ Float64
+ Complex64
+ Complex128
+ Array
+ Chan
+ Func
+ Interface
+ Map
+ Ptr
+ Slice
+ String
+ Struct
+ UnsafePointer
+)
// A Kind represents the specific kind of type that a Type represents.
+// The zero Kind is not a valid kind.
+type Kind uint
+
+const (
+ Invalid Kind = iota
+ Bool
+ Int
+ Int8
+ Int16
+ Int32
+ Int64
+ Uint
+ Uint8
+ Uint16
+ Uint32
+ Uint64
+ Uintptr
+ Float32
+ Float64
+ Complex64
+ Complex128
+ Array
+ Chan
+ Func
+ Interface
+ Map
+ Ptr
+ Slice
+ String
+ Struct
+ UnsafePointer
+)
go package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectType (x interface {}) {
+ typeX := reflect. TypeOf (x)
+ fmt. Println (typeX. Kind ()) // struct
+ fmt. Println (typeX) // main.book
+}
+
+type book struct {
+}
+
+func main () {
+ var b book
+ reflectType (b)
+}
package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectType (x interface {}) {
+ typeX := reflect. TypeOf (x)
+ fmt. Println (typeX. Kind ()) // struct
+ fmt. Println (typeX) // main.book
+}
+
+type book struct {
+}
+
+func main () {
+ var b book
+ reflectType (b)
+}
relfect.NumField() relfect.NumField()
方法返回结构体中字段的数量。
go package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectNumField (x interface {}) {
+ // 检查 x 的类别是 struct
+ if reflect. ValueOf (x). Kind () == reflect.Struct {
+ v := reflect. ValueOf (x)
+ fmt. Println ( "Number of fields" , v. NumField ())
+ }
+}
+
+type book struct {
+ name string
+ spend int
+}
+
+func main () {
+ var b book
+ reflectNumField (b)
+}
package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectNumField (x interface {}) {
+ // 检查 x 的类别是 struct
+ if reflect. ValueOf (x). Kind () == reflect.Struct {
+ v := reflect. ValueOf (x)
+ fmt. Println ( "Number of fields" , v. NumField ())
+ }
+}
+
+type book struct {
+ name string
+ spend int
+}
+
+func main () {
+ var b book
+ reflectNumField (b)
+}
relfect.Field() relfect.Field(i int)
方法返回字段 i
的 reflect.Value
go package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectNumField (x interface {}) {
+ // 检查 x 的类别是 struct
+ if reflect. ValueOf (x). Kind () == reflect.Struct {
+ v := reflect. ValueOf (x)
+ fmt. Println ( "Number of fields" , v. NumField ())
+ for i := 0 ; i < v. NumField (); i ++ {
+ fmt. Printf ( "Field: %d type: %T value: %v\\n " , i, v. Field (i), v. Field (i))
+ }
+ }
+}
+
+type book struct {
+ name string
+ spend int
+}
+
+func main () {
+ var b = book{ "Go语言" , 8 }
+ reflectNumField (a)
+}
+// Number of fields 2
+// Field:0 type:reflect.Value value:Go语言
+// Field:1 type:reflect.Value value:8
package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func reflectNumField (x interface {}) {
+ // 检查 x 的类别是 struct
+ if reflect. ValueOf (x). Kind () == reflect.Struct {
+ v := reflect. ValueOf (x)
+ fmt. Println ( "Number of fields" , v. NumField ())
+ for i := 0 ; i < v. NumField (); i ++ {
+ fmt. Printf ( "Field: %d type: %T value: %v\\n " , i, v. Field (i), v. Field (i))
+ }
+ }
+}
+
+type book struct {
+ name string
+ spend int
+}
+
+func main () {
+ var b = book{ "Go语言" , 8 }
+ reflectNumField (a)
+}
+// Number of fields 2
+// Field:0 type:reflect.Value value:Go语言
+// Field:1 type:reflect.Value value:8
反射的三大定律 一个接口变量,实际上都是由一 pair
对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。也就是说在真实世界(反射前环境)里,type 和 value 是合并在一起组成接口变量的。
而在反射的世界(反射后的环境)里,type 和 data 却是分开的,他们分别由 reflect.Type
和 reflect.Value
来表现。
Go语言反射三定律:
Reflection goes from interface value to reflection object. Reflection goes from reflection object to interface value. To modify a reflection object, the value must be settable. go package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func main () {
+ var a interface {} = 3.14
+
+ fmt. Printf ( "接口变量的类型为 %T ,值为 %v\\n " , a, a)
+
+ t := reflect. TypeOf (a)
+ v := reflect. ValueOf (a)
+
+ // 反射第一定律
+ fmt. Printf ( "从接口变量到反射对象:Type对象类型为 %T\\n " , t)
+ fmt. Printf ( "从接口变量到反射对象:Value对象类型为 %T\\n " , v)
+
+ // 反射第二定律
+ i := v. Interface ()
+ fmt. Printf ( "从反射对象到接口变量:对象类型为 %T ,值为 %v\\n " , i, i)
+ // 使用类型断言进行转换
+ x := v. Interface ().( float64 )
+ fmt. Printf ( "x 类型为 %T ,值为 %v\\n " , x, x)
+}
package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func main () {
+ var a interface {} = 3.14
+
+ fmt. Printf ( "接口变量的类型为 %T ,值为 %v\\n " , a, a)
+
+ t := reflect. TypeOf (a)
+ v := reflect. ValueOf (a)
+
+ // 反射第一定律
+ fmt. Printf ( "从接口变量到反射对象:Type对象类型为 %T\\n " , t)
+ fmt. Printf ( "从接口变量到反射对象:Value对象类型为 %T\\n " , v)
+
+ // 反射第二定律
+ i := v. Interface ()
+ fmt. Printf ( "从反射对象到接口变量:对象类型为 %T ,值为 %v\\n " , i, i)
+ // 使用类型断言进行转换
+ x := v. Interface ().( float64 )
+ fmt. Printf ( "x 类型为 %T ,值为 %v\\n " , x, x)
+}
go package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func main () {
+ var a float64 = 3.14
+ v := reflect. ValueOf (a)
+ fmt. Println ( "是否可写:" , v. CanSet ())
+}
+---
+package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func main () {
+ var a float64 = 3.14
+ v := reflect. ValueOf ( & a)
+ fmt. Println ( "是否可写:" , v. CanSet ())
+}
+---
+package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func main () {
+ var a float64 = 3.14
+ v := reflect. ValueOf ( & a). Elem ()
+ fmt. Println ( "是否可写:" , v. CanSet ())
+
+ v. SetFloat ( 2 )
+ fmt. Println (v)
+}
package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func main () {
+ var a float64 = 3.14
+ v := reflect. ValueOf (a)
+ fmt. Println ( "是否可写:" , v. CanSet ())
+}
+---
+package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func main () {
+ var a float64 = 3.14
+ v := reflect. ValueOf ( & a)
+ fmt. Println ( "是否可写:" , v. CanSet ())
+}
+---
+package main
+
+import (
+ " fmt "
+ " reflect "
+)
+
+func main () {
+ var a float64 = 3.14
+ v := reflect. ValueOf ( & a). Elem ()
+ fmt. Println ( "是否可写:" , v. CanSet ())
+
+ v. SetFloat ( 2 )
+ fmt. Println (v)
+}
`,592),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const C=s(p,[["render",t]]);export{d as __pageData,C as default};
diff --git a/assets/golang_base_golang-syntax.md.4fddf075.lean.js b/assets/golang_base_golang-syntax.md.4fddf075.lean.js
new file mode 100644
index 000000000..fdbf76dcb
--- /dev/null
+++ b/assets/golang_base_golang-syntax.md.4fddf075.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"Golang基础语法","description":"","frontmatter":{},"headers":[],"relativePath":"golang/base/golang-syntax.md","filePath":"golang/base/golang-syntax.md","lastUpdated":1694368780000}'),p={name:"golang/base/golang-syntax.md"},o=l("",592),e=[o];function t(c,r,E,y,i,F){return n(),a("div",null,e)}const C=s(p,[["render",t]]);export{d as __pageData,C as default};
diff --git a/assets/golang_cli_cobra.md.4b826d1d.js b/assets/golang_cli_cobra.md.4b826d1d.js
new file mode 100644
index 000000000..07de35d70
--- /dev/null
+++ b/assets/golang_cli_cobra.md.4b826d1d.js
@@ -0,0 +1,369 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"Cobra","description":"","frontmatter":{},"headers":[],"relativePath":"golang/cli/cobra.md","filePath":"golang/cli/cobra.md","lastUpdated":1694368780000}'),p={name:"golang/cli/cobra.md"},o=l(`Cobra Cobra是一个能够快速构建cli工具的库,相比于之前用过的Python的argparser模块,Cobra更加强大、灵活,还有自动生成文档等功能。
https://github.com/spf13/cobra/blob/main/site/content/user_guide.md
安装cobra依赖 go get -u github.com/spf13/cobra@latest
安装cobra-cli工具 go install github.com/spf13/cobra-cli@latest
cobra-cli会被安装到GOPATH的bin目录
使用cobra-cli初始化项目 shell cd cobra-learn
+cobra-cli init
+// Your Cobra application is ready at
+// /Users/story/Developer/go/src/cobra-learn
cd cobra-learn
+cobra-cli init
+// Your Cobra application is ready at
+// /Users/story/Developer/go/src/cobra-learn
生成的目录结构:
shell ├── LICENSE
+├── cmd
+│ └── root.go
+├── go.mod
+├── go.sum
+└── main.go
├── LICENSE
+├── cmd
+│ └── root.go
+├── go.mod
+├── go.sum
+└── main.go
go // main.go
+package main
+
+import " cobra-learn/cmd "
+
+func main () {
+ cmd. Execute ()
+}
// main.go
+package main
+
+import " cobra-learn/cmd "
+
+func main () {
+ cmd. Execute ()
+}
go // root.go
+package cmd
+
+import (
+ " os "
+
+ " github.com/spf13/cobra "
+)
+
+
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = & cobra.Command{
+ Use: "cobra-learn" ,
+ Short: "A brief description of your application" ,
+ Long: \`A longer description that spans multiple lines and likely contains
+examples and usage of using your application. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.\` ,
+ // Uncomment the following line if your bare application
+ // has an action associated with it:
+ // Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute () {
+ err := rootCmd. Execute ()
+ if err != nil {
+ os. Exit ( 1 )
+ }
+}
+
+func init () {
+ // Here you will define your flags and configuration settings.
+ // Cobra supports persistent flags, which, if defined here,
+ // will be global for your application.
+
+ // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra-learn.yaml)")
+
+ // Cobra also supports local flags, which will only run
+ // when this action is called directly.
+ rootCmd. Flags (). BoolP ( "toggle" , "t" , false , "Help message for toggle" )
+}
// root.go
+package cmd
+
+import (
+ " os "
+
+ " github.com/spf13/cobra "
+)
+
+
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = & cobra.Command{
+ Use: "cobra-learn" ,
+ Short: "A brief description of your application" ,
+ Long: \`A longer description that spans multiple lines and likely contains
+examples and usage of using your application. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.\` ,
+ // Uncomment the following line if your bare application
+ // has an action associated with it:
+ // Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute () {
+ err := rootCmd. Execute ()
+ if err != nil {
+ os. Exit ( 1 )
+ }
+}
+
+func init () {
+ // Here you will define your flags and configuration settings.
+ // Cobra supports persistent flags, which, if defined here,
+ // will be global for your application.
+
+ // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra-learn.yaml)")
+
+ // Cobra also supports local flags, which will only run
+ // when this action is called directly.
+ rootCmd. Flags (). BoolP ( "toggle" , "t" , false , "Help message for toggle" )
+}
执行命令go run main.go
会输出定义的详细描述
shell ➜ cobra-learn go run main.go
+A longer description that spans multiple lines and likely contains
+examples and usage of using your application. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.
➜ cobra-learn go run main.go
+A longer description that spans multiple lines and likely contains
+examples and usage of using your application. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.
给命令添加子命令 shell ➜ cobra-learn cobra-cli add version
+version created at /Users/story/Developer/go/src/cobra-learn
➜ cobra-learn cobra-cli add version
+version created at /Users/story/Developer/go/src/cobra-learn
目录结构:
shell ├── LICENSE
+├── cmd
+│ ├── root.go
+│ └── version.go
+├── go.mod
+├── go.sum
+└── main.go
├── LICENSE
+├── cmd
+│ ├── root.go
+│ └── version.go
+├── go.mod
+├── go.sum
+└── main.go
go // version.go
+package cmd
+
+import (
+ " fmt "
+
+ " github.com/spf13/cobra "
+)
+
+// versionCmd represents the version command
+var versionCmd = & cobra.Command{
+ Use: "version" ,
+ Short: "A brief description of your command" ,
+ Long: \`A longer description that spans multiple lines and likely contains examples
+and usage of using your command. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.\` ,
+ Run: func (cmd * cobra.Command, args [] string ) {
+ fmt. Println ( "version called" )
+ },
+}
+
+func init () {
+ rootCmd. AddCommand (versionCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // versionCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
// version.go
+package cmd
+
+import (
+ " fmt "
+
+ " github.com/spf13/cobra "
+)
+
+// versionCmd represents the version command
+var versionCmd = & cobra.Command{
+ Use: "version" ,
+ Short: "A brief description of your command" ,
+ Long: \`A longer description that spans multiple lines and likely contains examples
+and usage of using your command. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.\` ,
+ Run: func (cmd * cobra.Command, args [] string ) {
+ fmt. Println ( "version called" )
+ },
+}
+
+func init () {
+ rootCmd. AddCommand (versionCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // versionCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
执行go build
编译项目,会在项目根目录生成二进制文件cobra-learn
执行该命令:
shell ➜ cobra-learn ./cobra-learn
+A longer description that spans multiple lines and likely contains
+examples and usage of using your application. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.
+
+Usage:
+ cobra-learn [command]
+
+Available Commands:
+ completion Generate the autocompletion script for the specified shell
+ help Help about any command
+ version A brief description of your command
+
+Flags:
+ -h, --help help for cobra-learn
+ -t, --toggle Help message for toggle
+
+Use "cobra-learn [command] --help" for more information about a command.
➜ cobra-learn ./cobra-learn
+A longer description that spans multiple lines and likely contains
+examples and usage of using your application. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.
+
+Usage:
+ cobra-learn [command]
+
+Available Commands:
+ completion Generate the autocompletion script for the specified shell
+ help Help about any command
+ version A brief description of your command
+
+Flags:
+ -h, --help help for cobra-learn
+ -t, --toggle Help message for toggle
+
+Use "cobra-learn [command] --help" for more information about a command.
执行cobra-learn version
:
shell ➜ cobra-learn ./cobra-learn version
+version called
➜ cobra-learn ./cobra-learn version
+version called
可以看到调用命令执行的就是Run
属性对应的函数
给命令增加flag go func init () {
+ rootCmd. AddCommand (versionCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // versionCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ versionCmd. Flags (). StringP ( "ver" , "v" , "1.0" , "版本号" )
+}
func init () {
+ rootCmd. AddCommand (versionCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // versionCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ versionCmd. Flags (). StringP ( "ver" , "v" , "1.0" , "版本号" )
+}
shell ➜ cobra-learn go build
+➜ cobra-learn ./cobra-learn version
+Usage:
+ cobra-learn version [flags]
+
+Flags:
+ -h, --help help for version
+ -v, --ver string 版本号 (default "1.0" )
➜ cobra-learn go build
+➜ cobra-learn ./cobra-learn version
+Usage:
+ cobra-learn version [flags]
+
+Flags:
+ -h, --help help for version
+ -v, --ver string 版本号 (default "1.0" )
在Run函数中获取flag
go Run: func (cmd * cobra.Command, args [] string ) {
+ ver, _ := cmd. Flags (). GetString ( "ver" )
+ fmt. Println (ver)
+}
Run: func (cmd * cobra.Command, args [] string ) {
+ ver, _ := cmd. Flags (). GetString ( "ver" )
+ fmt. Println (ver)
+}
shell ➜ cobra-learn go build
+➜ cobra-learn ./cobra-learn version --ver 123
+123 # 使用name
+➜ cobra-learn ./cobra-learn version -v 1234
+1234 # 使用shorthand
+➜ cobra-learn ./cobra-learn version
+1.0 # 不带flag 使用默认值
➜ cobra-learn go build
+➜ cobra-learn ./cobra-learn version --ver 123
+123 # 使用name
+➜ cobra-learn ./cobra-learn version -v 1234
+1234 # 使用shorthand
+➜ cobra-learn ./cobra-learn version
+1.0 # 不带flag 使用默认值
修改命令配置 自定义usage输出 可以看到上面输出的./cobra-learn version
的uage信息是默认的
shell Usage:
+ cobra-learn version [flags]
Usage:
+ cobra-learn version [flags]
我们可以通过SetUsageTemplate
或SetUsageFunc
自定义这一内容:
go func init () {
+ rootCmd. AddCommand (versionCmd)
+ rootCmd. AddCommand (versionCmd)
+ versionCmd. SetUsageTemplate (
+ \`Usage: story version [options] <ver>\` + " \\n " +
+ \`版本号\` + " \\n " +
+ \`Options:\` + " \\n " +
+ \` -h, --help help for version\` + " \\n " ,
+ )
+}
func init () {
+ rootCmd. AddCommand (versionCmd)
+ rootCmd. AddCommand (versionCmd)
+ versionCmd. SetUsageTemplate (
+ \`Usage: story version [options] <ver>\` + " \\n " +
+ \`版本号\` + " \\n " +
+ \`Options:\` + " \\n " +
+ \` -h, --help help for version\` + " \\n " ,
+ )
+}
shell ➜ cobra-learn go build
+➜ cobra-learn ./cobra-learn version
+Error: accepts 1 arg ( s ) , received 0
+Usage: story version [options] < ver >
+版本号
+Options:
+ -h, --help help for version
➜ cobra-learn go build
+➜ cobra-learn ./cobra-learn version
+Error: accepts 1 arg ( s ) , received 0
+Usage: story version [options] < ver >
+版本号
+Options:
+ -h, --help help for version
go func init () {
+ rootCmd. AddCommand (versionCmd)
+ versionCmd. SetUsageFunc ( func (cmd * cobra.Command) error {
+ fmt. Println ( "Usage: story version" )
+ return nil
+ })
+}
func init () {
+ rootCmd. AddCommand (versionCmd)
+ versionCmd. SetUsageFunc ( func (cmd * cobra.Command) error {
+ fmt. Println ( "Usage: story version" )
+ return nil
+ })
+}
go ➜ cobra - learn go build
+➜ cobra - learn . / cobra - learn version
+Usage: story version
➜ cobra - learn go build
+➜ cobra - learn . / cobra - learn version
+Usage: story version
限制arg参数 例:Args: cobra.ExactArgs(1)
执行不带参数时:
shell ➜ cobra-learn ./cobra-learn version
+Error: accepts 1 arg ( s ) , received 0 # 提示需要提供一个参数
+Usage: story version
➜ cobra-learn ./cobra-learn version
+Error: accepts 1 arg ( s ) , received 0 # 提示需要提供一个参数
+Usage: story version
`,48),e=[o];function c(t,r,E,y,i,F){return a(),n("div",null,e)}const u=s(p,[["render",c]]);export{C as __pageData,u as default};
diff --git a/assets/golang_cli_cobra.md.4b826d1d.lean.js b/assets/golang_cli_cobra.md.4b826d1d.lean.js
new file mode 100644
index 000000000..dc8ce6948
--- /dev/null
+++ b/assets/golang_cli_cobra.md.4b826d1d.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,Q as l}from"./chunks/framework.b637c96f.js";const C=JSON.parse('{"title":"Cobra","description":"","frontmatter":{},"headers":[],"relativePath":"golang/cli/cobra.md","filePath":"golang/cli/cobra.md","lastUpdated":1694368780000}'),p={name:"golang/cli/cobra.md"},o=l("",48),e=[o];function c(t,r,E,y,i,F){return a(),n("div",null,e)}const u=s(p,[["render",c]]);export{C as __pageData,u as default};
diff --git a/assets/golang_index.md.d3c28484.js b/assets/golang_index.md.d3c28484.js
new file mode 100644
index 000000000..05d5a9298
--- /dev/null
+++ b/assets/golang_index.md.d3c28484.js
@@ -0,0 +1 @@
+import{_ as a,o as n,c as l,k as e,a as t}from"./chunks/framework.b637c96f.js";const f=JSON.parse('{"title":"Golang","description":"","frontmatter":{},"headers":[],"relativePath":"golang/index.md","filePath":"golang/index.md","lastUpdated":1694368780000}'),o={name:"golang/index.md"},s=e("h1",{id:"golang",tabindex:"-1"},[t("Golang "),e("a",{class:"header-anchor",href:"#golang","aria-label":'Permalink to "Golang"'},"")],-1),i=e("ul",null,[e("li",null,"基础"),e("li",null,"Web"),e("li",null,"Cli"),e("li",null,"工具")],-1),r=[s,i];function d(c,_,g,p,h,u){return n(),l("div",null,r)}const x=a(o,[["render",d]]);export{f as __pageData,x as default};
diff --git a/assets/golang_index.md.d3c28484.lean.js b/assets/golang_index.md.d3c28484.lean.js
new file mode 100644
index 000000000..05d5a9298
--- /dev/null
+++ b/assets/golang_index.md.d3c28484.lean.js
@@ -0,0 +1 @@
+import{_ as a,o as n,c as l,k as e,a as t}from"./chunks/framework.b637c96f.js";const f=JSON.parse('{"title":"Golang","description":"","frontmatter":{},"headers":[],"relativePath":"golang/index.md","filePath":"golang/index.md","lastUpdated":1694368780000}'),o={name:"golang/index.md"},s=e("h1",{id:"golang",tabindex:"-1"},[t("Golang "),e("a",{class:"header-anchor",href:"#golang","aria-label":'Permalink to "Golang"'},"")],-1),i=e("ul",null,[e("li",null,"基础"),e("li",null,"Web"),e("li",null,"Cli"),e("li",null,"工具")],-1),r=[s,i];function d(c,_,g,p,h,u){return n(),l("div",null,r)}const x=a(o,[["render",d]]);export{f as __pageData,x as default};
diff --git a/assets/golang_tools_redis-cleaner.md.40f7c6d6.js b/assets/golang_tools_redis-cleaner.md.40f7c6d6.js
new file mode 100644
index 000000000..c1327cb06
--- /dev/null
+++ b/assets/golang_tools_redis-cleaner.md.40f7c6d6.js
@@ -0,0 +1,143 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"Redis-cleaner","description":"","frontmatter":{},"headers":[],"relativePath":"golang/tools/redis-cleaner.md","filePath":"golang/tools/redis-cleaner.md","lastUpdated":1694368780000}'),p={name:"golang/tools/redis-cleaner.md"},o=l(`Redis-cleaner go package main
+
+import (
+ " context "
+ " fmt "
+ " github.com/redis/go-redis/v9 "
+ " log "
+)
+
+func main () {
+ ctx := context. Background ()
+ // 创建Redis客户端
+ client := redis. NewClient ( & redis.Options{
+ Addr: "localhost:6379" ,
+ DB: 1 ,
+ })
+
+ // 定义匹配模式和批量处理大小
+ matchPattern := "*"
+ batchSize := 1000
+
+ // 设置游标初始值和删除计数器
+ startCursor := uint64 ( 0 )
+ keysDeleted := 0
+ memSaved := 0
+
+ for {
+ // 扫描Redis中的key
+ keys, cursor, err := client. Scan (ctx, startCursor, matchPattern, int64 (batchSize)). Result ()
+
+ if err != nil {
+ log. Fatal (err)
+ }
+
+ // 检查每个key的过期时间并删除符合条件的键
+ for _, key := range keys {
+ ttl, err := client. TTL (ctx, key). Result ()
+ if err != nil {
+ log. Fatal (err)
+ }
+
+ // 如果过期时间大于15年,则删除该键
+ if ttl. Hours () > 24 * 365 * 10 {
+ mem, err := client. MemoryUsage (ctx, key). Result ()
+ if err != nil {
+ log. Fatal (err)
+ }
+ err = client. Del (ctx, key). Err ()
+ if err != nil {
+ log. Fatal (err)
+ }
+ memSaved += int (mem)
+ keysDeleted ++
+ }
+ }
+
+ // 如果游标为0,则表示已完成遍历
+ if cursor == 0 {
+ break
+ }
+ startCursor = cursor
+ }
+
+ fmt. Printf ( "已删除 %d 个过期时间大于10年的键 \\n " , keysDeleted)
+ fmt. Printf ( "已释放 %d MB内存 \\n " , memSaved / 1024 / 1024 )
+
+ // 关闭Redis客户端连接
+ err := client. Close ()
+ if err != nil {
+ log. Fatal (err)
+ }
+}
package main
+
+import (
+ " context "
+ " fmt "
+ " github.com/redis/go-redis/v9 "
+ " log "
+)
+
+func main () {
+ ctx := context. Background ()
+ // 创建Redis客户端
+ client := redis. NewClient ( & redis.Options{
+ Addr: "localhost:6379" ,
+ DB: 1 ,
+ })
+
+ // 定义匹配模式和批量处理大小
+ matchPattern := "*"
+ batchSize := 1000
+
+ // 设置游标初始值和删除计数器
+ startCursor := uint64 ( 0 )
+ keysDeleted := 0
+ memSaved := 0
+
+ for {
+ // 扫描Redis中的key
+ keys, cursor, err := client. Scan (ctx, startCursor, matchPattern, int64 (batchSize)). Result ()
+
+ if err != nil {
+ log. Fatal (err)
+ }
+
+ // 检查每个key的过期时间并删除符合条件的键
+ for _, key := range keys {
+ ttl, err := client. TTL (ctx, key). Result ()
+ if err != nil {
+ log. Fatal (err)
+ }
+
+ // 如果过期时间大于15年,则删除该键
+ if ttl. Hours () > 24 * 365 * 10 {
+ mem, err := client. MemoryUsage (ctx, key). Result ()
+ if err != nil {
+ log. Fatal (err)
+ }
+ err = client. Del (ctx, key). Err ()
+ if err != nil {
+ log. Fatal (err)
+ }
+ memSaved += int (mem)
+ keysDeleted ++
+ }
+ }
+
+ // 如果游标为0,则表示已完成遍历
+ if cursor == 0 {
+ break
+ }
+ startCursor = cursor
+ }
+
+ fmt. Printf ( "已删除 %d 个过期时间大于10年的键 \\n " , keysDeleted)
+ fmt. Printf ( "已释放 %d MB内存 \\n " , memSaved / 1024 / 1024 )
+
+ // 关闭Redis客户端连接
+ err := client. Close ()
+ if err != nil {
+ log. Fatal (err)
+ }
+}
`,2),t=[o];function e(c,r,E,y,i,F){return n(),a("div",null,t)}const u=s(p,[["render",e]]);export{d as __pageData,u as default};
diff --git a/assets/golang_tools_redis-cleaner.md.40f7c6d6.lean.js b/assets/golang_tools_redis-cleaner.md.40f7c6d6.lean.js
new file mode 100644
index 000000000..20897e055
--- /dev/null
+++ b/assets/golang_tools_redis-cleaner.md.40f7c6d6.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const d=JSON.parse('{"title":"Redis-cleaner","description":"","frontmatter":{},"headers":[],"relativePath":"golang/tools/redis-cleaner.md","filePath":"golang/tools/redis-cleaner.md","lastUpdated":1694368780000}'),p={name:"golang/tools/redis-cleaner.md"},o=l("",2),t=[o];function e(c,r,E,y,i,F){return n(),a("div",null,t)}const u=s(p,[["render",e]]);export{d as __pageData,u as default};
diff --git a/assets/golang_web_gin.md.11770ac9.js b/assets/golang_web_gin.md.11770ac9.js
new file mode 100644
index 000000000..9f68dc3aa
--- /dev/null
+++ b/assets/golang_web_gin.md.11770ac9.js
@@ -0,0 +1,491 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const g=JSON.parse('{"title":"Gin","description":"","frontmatter":{},"headers":[],"relativePath":"golang/web/gin.md","filePath":"golang/web/gin.md","lastUpdated":1694368780000}'),p={name:"golang/web/gin.md"},o=l(`Gin Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
Gin安装和基本使用 go get -u github.com/gin-gonic/gin
go package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " github.com/thinkerou/favicon "
+)
+
+func main () {
+ // 创建服务
+ ginServer := gin. Default ()
+ ginServer. Use (favicon. New ( "./favicon.ico" ))
+ ginServer. GET ( "/" , func (c * gin.Context) {
+ c. String ( 200 , "Hello World!" )
+ })
+ ginServer. POST ( "/post" , func (c * gin.Context) {
+ c. JSON ( 200 , gin.H{
+ "message" : "POST Data" ,
+ })
+ })
+
+ _ = ginServer. Run ( ":8080" )
+
+}
package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " github.com/thinkerou/favicon "
+)
+
+func main () {
+ // 创建服务
+ ginServer := gin. Default ()
+ ginServer. Use (favicon. New ( "./favicon.ico" ))
+ ginServer. GET ( "/" , func (c * gin.Context) {
+ c. String ( 200 , "Hello World!" )
+ })
+ ginServer. POST ( "/post" , func (c * gin.Context) {
+ c. JSON ( 200 , gin.H{
+ "message" : "POST Data" ,
+ })
+ })
+
+ _ = ginServer. Run ( ":8080" )
+
+}
返回一个静态页 go package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " github.com/thinkerou/favicon "
+)
+
+func main () {
+ // 创建服务
+ ginServer := gin. Default ()
+ // 设置favicon
+ ginServer. Use (favicon. New ( "./favicon.ico" ))
+
+ // 加载静态页
+ ginServer. LoadHTMLGlob ( "templates/*" )
+ // 响应页面给前端
+ ginServer. GET ( "/index" , func (context * gin.Context) {
+ context. HTML ( 200 , "index.html" , gin.H{
+ "title" : "Main website" ,
+ })
+ })
+ _ = ginServer. Run ( ":8080" )
+}
package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " github.com/thinkerou/favicon "
+)
+
+func main () {
+ // 创建服务
+ ginServer := gin. Default ()
+ // 设置favicon
+ ginServer. Use (favicon. New ( "./favicon.ico" ))
+
+ // 加载静态页
+ ginServer. LoadHTMLGlob ( "templates/*" )
+ // 响应页面给前端
+ ginServer. GET ( "/index" , func (context * gin.Context) {
+ context. HTML ( 200 , "index.html" , gin.H{
+ "title" : "Main website" ,
+ })
+ })
+ _ = ginServer. Run ( ":8080" )
+}
访问localhost:8080
或localhost:8080/index
加载资源文件 go package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " github.com/thinkerou/favicon "
+)
+
+func main () {
+ // 创建服务
+ ginServer := gin. Default ()
+ // 设置favicon
+ ginServer. Use (favicon. New ( "./favicon.ico" ))
+
+ // 加载静态页
+ ginServer. LoadHTMLGlob ( "templates/*" )
+ // 响应页面给前端
+ ginServer. GET ( "/index" , func (context * gin.Context) {
+ context. HTML ( 200 , "index.html" , gin.H{
+ "title" : "Main website" ,
+ })
+ })
+ _ = ginServer. Run ( ":8080" )
+}
package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " github.com/thinkerou/favicon "
+)
+
+func main () {
+ // 创建服务
+ ginServer := gin. Default ()
+ // 设置favicon
+ ginServer. Use (favicon. New ( "./favicon.ico" ))
+
+ // 加载静态页
+ ginServer. LoadHTMLGlob ( "templates/*" )
+ // 响应页面给前端
+ ginServer. GET ( "/index" , func (context * gin.Context) {
+ context. HTML ( 200 , "index.html" , gin.H{
+ "title" : "Main website" ,
+ })
+ })
+ _ = ginServer. Run ( ":8080" )
+}
Restful API Query参数 go package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " net/http "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+
+ ginServer. GET ( "/user/info" , func (context * gin.Context) {
+ userId := context. Query ( "userId" )
+ userName := context. Query ( "userName" )
+ context. JSON (http.StatusOK, gin.H{
+ "userId" : userId,
+ "userName" : userName,
+ })
+ })
+
+ _ = ginServer. Run ( ":8080" )
+}
package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " net/http "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+
+ ginServer. GET ( "/user/info" , func (context * gin.Context) {
+ userId := context. Query ( "userId" )
+ userName := context. Query ( "userName" )
+ context. JSON (http.StatusOK, gin.H{
+ "userId" : userId,
+ "userName" : userName,
+ })
+ })
+
+ _ = ginServer. Run ( ":8080" )
+}
curl -X GET 'http://localhost:8080/user/info?userId=123&userName=小明'
{"userId":"123","userName":"小明"}
Body参数 go package main
+
+import (
+ " encoding/json "
+ " github.com/gin-gonic/gin "
+ " net/http "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+ ginServer. POST ( "/user" , func (context * gin.Context) {
+ // request body []byte, err
+ body, _ := context. GetRawData ()
+ // 包装为map类型
+ var m map [ string ] interface {}
+ _ = json. Unmarshal (body, & m)
+
+ context. JSON (http.StatusOK, m)
+ })
+
+ _ = ginServer. Run ( ":8080" )
+}
package main
+
+import (
+ " encoding/json "
+ " github.com/gin-gonic/gin "
+ " net/http "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+ ginServer. POST ( "/user" , func (context * gin.Context) {
+ // request body []byte, err
+ body, _ := context. GetRawData ()
+ // 包装为map类型
+ var m map [ string ] interface {}
+ _ = json. Unmarshal (body, & m)
+
+ context. JSON (http.StatusOK, m)
+ })
+
+ _ = ginServer. Run ( ":8080" )
+}
curl -X POST 'http://127.0.0.1:8080/user' --header 'Content-Type: application/json' --data '{"userName": "张三"}'
表单参数 html <! DOCTYPE html >
+< html lang = "en" >
+< head >
+ < meta charset = "UTF-8" >
+ < title >Title</ title >
+ < link rel = "stylesheet" href = "/static/base.css" >
+</ head >
+< body >
+< h1 >Hello World!</ h1 >
+
+< form action = "/user/add" method = "post" >
+ < input type = "text" name = "username" >
+ < input type = "password" name = "password" >
+
+ < button type = "submit" >提交</ button >
+</ form >
+< script src = "/static/base.js" ></ script >
+</ body >
+</ html >
<! DOCTYPE html >
+< html lang = "en" >
+< head >
+ < meta charset = "UTF-8" >
+ < title >Title</ title >
+ < link rel = "stylesheet" href = "/static/base.css" >
+</ head >
+< body >
+< h1 >Hello World!</ h1 >
+
+< form action = "/user/add" method = "post" >
+ < input type = "text" name = "username" >
+ < input type = "password" name = "password" >
+
+ < button type = "submit" >提交</ button >
+</ form >
+< script src = "/static/base.js" ></ script >
+</ body >
+</ html >
go package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " net/http "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+ ginServer. LoadHTMLGlob ( "templates/*" )
+
+ ginServer. GET ( "/index" , func (context * gin.Context) {
+ context. HTML (http.StatusOK, "index.html" , gin.H{
+ "title" : "Hello World" ,
+ })
+ })
+
+ ginServer. POST ( "/user/add" , func (context * gin.Context) {
+ username := context. PostForm ( "username" )
+ password := context. PostForm ( "password" )
+
+ context. JSON (http.StatusOK, gin.H{
+ "msg" : "success" ,
+ "data" : gin.H{
+ "username" : username,
+ "password" : password,
+ },
+ })
+ })
+
+ _ = ginServer. Run ( ":8080" )
+}
package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " net/http "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+ ginServer. LoadHTMLGlob ( "templates/*" )
+
+ ginServer. GET ( "/index" , func (context * gin.Context) {
+ context. HTML (http.StatusOK, "index.html" , gin.H{
+ "title" : "Hello World" ,
+ })
+ })
+
+ ginServer. POST ( "/user/add" , func (context * gin.Context) {
+ username := context. PostForm ( "username" )
+ password := context. PostForm ( "password" )
+
+ context. JSON (http.StatusOK, gin.H{
+ "msg" : "success" ,
+ "data" : gin.H{
+ "username" : username,
+ "password" : password,
+ },
+ })
+ })
+
+ _ = ginServer. Run ( ":8080" )
+}
路由 go package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " net/http "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+ ginServer. LoadHTMLGlob ( "templates/*" )
+ // 重定向到首页
+ ginServer. GET ( "/" , func (context * gin.Context) {
+ context. Redirect (http.StatusMovedPermanently, "/index" )
+ })
+
+ ginServer. GET ( "/index" , func (context * gin.Context) {
+ context. HTML (http.StatusOK, "index.html" , gin.H{
+ "title" : "Hello World" ,
+ })
+ })
+ // 404页面
+ ginServer. NoRoute ( func (context * gin.Context) {
+ context. HTML (http.StatusNotFound, "404.html" , gin.H{
+ "title" : "404" ,
+ })
+ })
+
+ _ = ginServer. Run ( ":8080" )
+}
package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " net/http "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+ ginServer. LoadHTMLGlob ( "templates/*" )
+ // 重定向到首页
+ ginServer. GET ( "/" , func (context * gin.Context) {
+ context. Redirect (http.StatusMovedPermanently, "/index" )
+ })
+
+ ginServer. GET ( "/index" , func (context * gin.Context) {
+ context. HTML (http.StatusOK, "index.html" , gin.H{
+ "title" : "Hello World" ,
+ })
+ })
+ // 404页面
+ ginServer. NoRoute ( func (context * gin.Context) {
+ context. HTML (http.StatusNotFound, "404.html" , gin.H{
+ "title" : "404" ,
+ })
+ })
+
+ _ = ginServer. Run ( ":8080" )
+}
路由组 go package main
+
+import (
+ " github.com/gin-gonic/gin "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+ userGroup := ginServer. Group ( "/user" )
+ {
+ userGroup. GET ( "/get" , func (c * gin.Context) {
+ c. JSON ( 200 , gin.H{
+ "message" : "get user" ,
+ })
+ })
+ userGroup. POST ( "/post" , func (c * gin.Context) {
+ c. JSON ( 200 , gin.H{
+ "message" : "post user" ,
+ })
+ })
+ }
+
+ _ = ginServer. Run ( ":8080" )
+}
package main
+
+import (
+ " github.com/gin-gonic/gin "
+)
+
+func main () {
+ ginServer := gin. Default ()
+
+ userGroup := ginServer. Group ( "/user" )
+ {
+ userGroup. GET ( "/get" , func (c * gin.Context) {
+ c. JSON ( 200 , gin.H{
+ "message" : "get user" ,
+ })
+ })
+ userGroup. POST ( "/post" , func (c * gin.Context) {
+ c. JSON ( 200 , gin.H{
+ "message" : "post user" ,
+ })
+ })
+ }
+
+ _ = ginServer. Run ( ":8080" )
+}
自定义中间件 拦截器 go package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " log "
+)
+
+func myHandler () gin.HandlerFunc {
+ return func (context * gin.Context) {
+ // do something
+ context. Set ( "name" , "zhangsan" )
+ context. Next () // 放行
+ // context.Abort() 阻止
+ }
+}
+
+func main () {
+ ginServer := gin. Default ()
+
+ userGroup := ginServer. Group ( "/user" )
+ {
+ userGroup. GET ( "/get" , myHandler (), func (c * gin.Context) {
+ // 获取拦截器里设置的值
+ name := c. MustGet ( "name" ).( string )
+ log. Println (name)
+ c. JSON ( 200 , gin.H{
+ "message" : "get user" ,
+ })
+ })
+ }
+
+ _ = ginServer. Run ( ":8080" )
+}
+// 2023/07/01 21:36:53 zhangsan
package main
+
+import (
+ " github.com/gin-gonic/gin "
+ " log "
+)
+
+func myHandler () gin.HandlerFunc {
+ return func (context * gin.Context) {
+ // do something
+ context. Set ( "name" , "zhangsan" )
+ context. Next () // 放行
+ // context.Abort() 阻止
+ }
+}
+
+func main () {
+ ginServer := gin. Default ()
+
+ userGroup := ginServer. Group ( "/user" )
+ {
+ userGroup. GET ( "/get" , myHandler (), func (c * gin.Context) {
+ // 获取拦截器里设置的值
+ name := c. MustGet ( "name" ).( string )
+ log. Println (name)
+ c. JSON ( 200 , gin.H{
+ "message" : "get user" ,
+ })
+ })
+ }
+
+ _ = ginServer. Run ( ":8080" )
+}
+// 2023/07/01 21:36:53 zhangsan
`,29),t=[o];function e(c,r,E,y,i,u){return n(),a("div",null,t)}const q=s(p,[["render",e]]);export{g as __pageData,q as default};
diff --git a/assets/golang_web_gin.md.11770ac9.lean.js b/assets/golang_web_gin.md.11770ac9.lean.js
new file mode 100644
index 000000000..b9fe30b5e
--- /dev/null
+++ b/assets/golang_web_gin.md.11770ac9.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.b637c96f.js";const g=JSON.parse('{"title":"Gin","description":"","frontmatter":{},"headers":[],"relativePath":"golang/web/gin.md","filePath":"golang/web/gin.md","lastUpdated":1694368780000}'),p={name:"golang/web/gin.md"},o=l("",29),t=[o];function e(c,r,E,y,i,u){return n(),a("div",null,t)}const q=s(p,[["render",e]]);export{g as __pageData,q as default};
diff --git a/assets/index.md.b912301d.js b/assets/index.md.b912301d.js
new file mode 100644
index 000000000..8743d8d7d
--- /dev/null
+++ b/assets/index.md.b912301d.js
@@ -0,0 +1 @@
+import{_ as t,o as e,c as a}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"故事","text":"Document","tagline":"🚀 热爱,是所有的理由和答案","actions":[{"theme":"brand","text":"进入博客 →","link":"/java/"},{"theme":"alt","text":"GitHub","link":"https://github.com/storyxc"}],"image":{"src":"/logo.png","alt":"Story"}},"features":[{"title":"⌨️ Coding","details":"编程让我解构世界。"},{"title":"⚽️ Football","details":"白云偶尔会遮住蓝天,但蓝天永远在白云之上。"},{"title":"🎸 Guitar","details":"还记得,年少时的梦吗?"}]},"headers":[],"relativePath":"index.md","filePath":"index.md","lastUpdated":1694368780000}'),n={name:"index.md"};function i(o,s,l,r,d,c){return e(),a("div")}const p=t(n,[["render",i]]);export{m as __pageData,p as default};
diff --git a/assets/index.md.b912301d.lean.js b/assets/index.md.b912301d.lean.js
new file mode 100644
index 000000000..8743d8d7d
--- /dev/null
+++ b/assets/index.md.b912301d.lean.js
@@ -0,0 +1 @@
+import{_ as t,o as e,c as a}from"./chunks/framework.b637c96f.js";const m=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"故事","text":"Document","tagline":"🚀 热爱,是所有的理由和答案","actions":[{"theme":"brand","text":"进入博客 →","link":"/java/"},{"theme":"alt","text":"GitHub","link":"https://github.com/storyxc"}],"image":{"src":"/logo.png","alt":"Story"}},"features":[{"title":"⌨️ Coding","details":"编程让我解构世界。"},{"title":"⚽️ Football","details":"白云偶尔会遮住蓝天,但蓝天永远在白云之上。"},{"title":"🎸 Guitar","details":"还记得,年少时的梦吗?"}]},"headers":[],"relativePath":"index.md","filePath":"index.md","lastUpdated":1694368780000}'),n={name:"index.md"};function i(o,s,l,r,d,c){return e(),a("div")}const p=t(n,[["render",i]]);export{m as __pageData,p as default};
diff --git a/assets/inter-italic-cyrillic-ext.33bd5a8e.woff2 b/assets/inter-italic-cyrillic-ext.33bd5a8e.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..2a687296748f6b8bc8076cd11bde49cd27e4442b
GIT binary patch
literal 28332
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
HNCp4^t1hY;
literal 0
HcmV?d00001
diff --git a/assets/inter-italic-cyrillic.ea42a392.woff2 b/assets/inter-italic-cyrillic.ea42a392.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..f64035158d7e4c01654e3f23dcd6e8299928a28c
GIT binary patch
literal 17824
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