diff --git a/code/algorithm/front-end/add.js b/code/algorithm/front-end/add.js index 758611697..8bfe96d1a 100644 --- a/code/algorithm/front-end/add.js +++ b/code/algorithm/front-end/add.js @@ -24,6 +24,5 @@ function add() { const str = add(1, 6)(2)(3) console.log(str) -// console.log(String(add(1,6)(2)(3)) ) -// console.log(add(1)(2)(3)) -// console.log(add(1)(2,3,4)) +console.log(add(1)(2)(3)) +console.log(add(1)(2, 3, 4)) diff --git a/code/docker-cluster/nginx-docker-compose.yaml b/code/docker-cluster/nginx-docker-compose.yaml index e69de29bb..dadcf0835 100644 --- a/code/docker-cluster/nginx-docker-compose.yaml +++ b/code/docker-cluster/nginx-docker-compose.yaml @@ -0,0 +1 @@ +# nginx \ No newline at end of file diff --git a/code/docker-cluster/redis.docker-compose.yaml b/code/docker-cluster/redis.docker-compose.yaml index e69de29bb..a3415d909 100644 --- a/code/docker-cluster/redis.docker-compose.yaml +++ b/code/docker-cluster/redis.docker-compose.yaml @@ -0,0 +1 @@ +# redis \ No newline at end of file diff --git a/code/node/dayjs/demo-1.js b/code/node/dayjs/demo-1.js index 44cacc3ef..360a857c5 100644 --- a/code/node/dayjs/demo-1.js +++ b/code/node/dayjs/demo-1.js @@ -31,12 +31,10 @@ console.log(dayjs(1318781876406)) console.log(dayjs.unix(1318781876)) console.log(dayjs.unix(1318781876.721)) - // Date对象 const date = new Date(2022, 8, 18) console.log(dayjs(date)) - // 对象复制 const a = dayjs() const b = a.clone() @@ -47,7 +45,6 @@ const c = dayjs() const d = dayjs(c) console.log(c, d) - // 校验是否为日期 console.log(dayjs('2022-01-33').isValid()) // true, parsed to 2022-02-02 diff --git a/code/node/dayjs/demo-2.js b/code/node/dayjs/demo-2.js index 748841771..f0fed6235 100644 --- a/code/node/dayjs/demo-2.js +++ b/code/node/dayjs/demo-2.js @@ -36,7 +36,6 @@ console.log(dayjs().month(0)) console.log(dayjs().year()) console.log(dayjs().year(2023)) - // get方法获取 console.log(dayjs().get('year')) console.log(dayjs().get('month')) diff --git a/code/node/dayjs/demo-3.js b/code/node/dayjs/demo-3.js index 1780ba3f5..e8b629b6b 100644 --- a/code/node/dayjs/demo-3.js +++ b/code/node/dayjs/demo-3.js @@ -14,7 +14,6 @@ console.log(dayjs().add(10, 'hour')) console.log(dayjs().subtract(7, 'year')) console.log(dayjs().subtract(2, 'month')) - // 设置到一个时间的开始 console.log(dayjs().startOf('year')) // 设置到一个时间的末尾 diff --git a/code/node/dayjs/demo-4.js b/code/node/dayjs/demo-4.js index 8ed132731..23154dfe8 100644 --- a/code/node/dayjs/demo-4.js +++ b/code/node/dayjs/demo-4.js @@ -21,6 +21,5 @@ console.log(dayjs('2022-01-25').unix()) // 获取当前月份包含的天数 console.log(dayjs('2019-01-25').daysInMonth()) - // 原生Date对象 console.log(dayjs('2019-01-25').toDate()) diff --git a/code/node/dayjs/demo-5.js b/code/node/dayjs/demo-5.js index 3c8104ddc..d5cff1b05 100644 --- a/code/node/dayjs/demo-5.js +++ b/code/node/dayjs/demo-5.js @@ -20,7 +20,6 @@ console.log(dayjs().isAfter(dayjs('2021-01-01'))) // 比较年份 console.log(dayjs().isAfter('2024-01-01', 'year')) - // 是否为dayjs对象 console.log(dayjs.isDayjs(dayjs())) console.log(dayjs.isDayjs(new Date())) diff --git a/code/node/lodash/demo-string.js b/code/node/lodash/demo-string.js index 958f2edb2..af80494e7 100644 --- a/code/node/lodash/demo-string.js +++ b/code/node/lodash/demo-string.js @@ -3,8 +3,7 @@ * @private */ - -const _ = require('lodash') +// const _ = require('lodash') // camelCase: 小驼峰 // kebabCase: 转换为-连接,例如:Foo Bar ----> foo-bar diff --git a/code/redis/cluster/docker-compose.yaml b/code/redis/cluster/docker-compose.yaml index a7c351689..b107c941e 100644 --- a/code/redis/cluster/docker-compose.yaml +++ b/code/redis/cluster/docker-compose.yaml @@ -4,7 +4,6 @@ ## - docker-compose up -d ## 默认密码:123456 ## -## version: '2' services: diff --git a/docs/.vuepress/config/constant.config.ts b/docs/.vuepress/config/constant.config.ts index e7d849a1e..914ded54a 100644 --- a/docs/.vuepress/config/constant.config.ts +++ b/docs/.vuepress/config/constant.config.ts @@ -4,7 +4,6 @@ Copyrights ©2015-${new Date().getFullYear()} 妍荣姑娘网络工作室 // 代码组织:142VIP // Theme By vuepress-theme-hope | - export const NotFoundMsgList = [ "徒留我孤单在湖面成双。", "就让回忆永远停在那里。", diff --git a/docs/.vuepress/config/navbar.ts b/docs/.vuepress/config/navbar.ts index 7da386c79..2723bdd27 100644 --- a/docs/.vuepress/config/navbar.ts +++ b/docs/.vuepress/config/navbar.ts @@ -1,50 +1,69 @@ export default [ - { - text: "首页", - link: "/", - }, - { - text: "SOLO算法", - link: "/manuscripts/solo-algorithm", - }, - { - text: "Battle面试官", - link: "/manuscripts/battle-interview", - }, - { - text: "前端", - link: "/manuscripts/front-end", - }, - - { - text: "Node后端", - link: "/manuscripts/server-end", - }, - { - text: '微服务', - link: "/manuscripts/microservice", - }, - { - text: "开发技巧", - link: "/manuscripts/develop-skill", - }, - { - text: "读书整理", - link: "/manuscripts/read-books", - }, - // { - // text: "网站", - // children:[ - // "/manuscripts/other/公众号文章.md", - // "/manuscripts/other/技术文档.md", - // { - // text:"RoadMap", - // link:"https://142vip-cn.feishu.cn/share/base/view/shrcnxtFKV2JfBZbqFh0GUxzTOg" - // } - // ], - // }, - { - text: "其他", - link: "/manuscripts/other", - }, + { + text: "首页", + link: "/", + }, + { + text: "SOLO算法", + link: "/manuscripts/solo-algorithm", + }, + { + text: "Battle面试官", + link: "/manuscripts/battle-interview", + }, + { + text: "前端", + link: "/manuscripts/front-end", + }, + { + text: "Node后端", + link: "/manuscripts/server-end", + }, + { + text: '微服务', + link: "/manuscripts/microservice", + }, + { + text: "开发技巧", + link: "/manuscripts/develop-skill", + }, + { + text: "读书整理", + link: "/manuscripts/read-books", + }, + { + text: "了解更多", + children: [ + { + text: '变更记录', + link: '/manuscripts/changelog.md' + }, + { + text: '网站动态', + link: '/manuscripts/big-event-history.md' + }, + { + text: "自媒体", + children: [ + { + text: "公众号文章", + link: "/manuscripts/wechat-list.md" + }, + ] + }, + { + text: "外链", + children: [ + { + text: '常用网站', + link: '/manuscripts/frequent-site-link.md' + }, + { + text: "RoadMap计划", + link: "https://142vip-cn.feishu.cn/share/base/view/shrcnxtFKV2JfBZbqFh0GUxzTOg" + } + ] + }, + ], + }, ]; diff --git a/docs/.vuepress/config/sidebar.ts b/docs/.vuepress/config/sidebar.ts index 0c74c2373..5b4dc36a0 100644 --- a/docs/.vuepress/config/sidebar.ts +++ b/docs/.vuepress/config/sidebar.ts @@ -3,8 +3,6 @@ import {serverEndSidebar} from "../../manuscripts/server-end/server-end.sidebar" import {eggSidebar} from "../../manuscripts/server-end/framework/egg/egg.sidebar"; import {developSkillSidebar} from "../../manuscripts/develop-skill/develop-skill.sidebar"; import {soloAlgorithmSidebar} from "../../manuscripts/solo-algorithm/solo-algorithm.sidebar"; -import {esStandardSidebar} from "../../manuscripts/read-books/cs-books/es-standard/es-standard-sidebar"; -import {OtherSidebar} from "../../manuscripts/other/other.sidebar"; import {jobChanceSidebar} from "../../manuscripts/job-chance/job-chance.sidebar"; import {readBooksSidebar} from "../../manuscripts/read-books/read-books.sidebar"; import {microserviceSidebar} from "../../manuscripts/microservice/microservice.sidebar"; @@ -17,8 +15,6 @@ export default { "/manuscripts/develop-skill": developSkillSidebar, "/manuscripts/solo-algorithm": soloAlgorithmSidebar, "/manuscripts/read-books": readBooksSidebar, - "/manuscripts/read-books/cs-books/es-standard": esStandardSidebar, - "/manuscripts/other": OtherSidebar, "/manuscripts/job-chance": jobChanceSidebar, "/manuscripts/microservice": microserviceSidebar, "/manuscripts/battle-interview": battleInterviewSidebar, diff --git a/docs/.vuepress/config/theme.config.ts b/docs/.vuepress/config/theme.config.ts index cedb6eeba..66c8b22bc 100644 --- a/docs/.vuepress/config/theme.config.ts +++ b/docs/.vuepress/config/theme.config.ts @@ -9,161 +9,161 @@ import {langConfig} from "./lang.config"; * 参考主题:https://theme-hope.vuejs.press/zh/config/intro.html#%E9%85%8D%E7%BD%AE%E6%A6%82%E5%BF%B5 */ export default { - theme: hopeTheme({ - locales: langConfig, - navbarIcon: false, - darkmode: "toggle", - // 支持全屏 - fullscreen: true, - // 纯净模式 - // pure: true, - print: false, // 打印按钮 - hostname: 'https://142vip.cn', - author: { - name: '微信公众号:储凡', - email: 'fairy_vip@2925.com', - url: 'https://www.142vip.cn' - }, - favicon: "/favicon.ico", - // logo: "/assets/408_logo.png", - navbar: navbar, - // 导航栏布局 - navbarLayout: { - start: ["Brand"], - center: ["Links"], - end: ["Language", "Search", "Repo", "Outlook",] - }, - sidebar: sidebar, + theme: hopeTheme({ + locales: langConfig, + navbarIcon: false, + darkmode: "toggle", + // 支持全屏 + fullscreen: true, + // 纯净模式 + // pure: true, + print: false, // 打印按钮 + hostname: 'https://142vip.cn', + author: { + name: '微信公众号:储凡', + email: 'fairy_vip@2925.com', + url: 'https://www.142vip.cn' + }, + favicon: "/favicon.ico", + // logo: "/assets/408_logo.png", + navbar: navbar, + // 导航栏布局 + navbarLayout: { + start: ["Brand"], + center: ["Links"], + end: ["Search", "Language", "Repo", "Outlook",] + }, + sidebar: sidebar, - pageInfo: ["Author", "Original", "Date", "Category", "Tag", "ReadingTime"], - // 主题布局选项 - docsRepo: "https://github.com/142vip/JavaScriptCollection", - docsDir: "docs", - docsBranch: "master", - repo: "https://github.com/142vip/JavaScriptCollection.git", - // logoDark: "/assets/408_logo.png", - // logo: "/assets/408_logo.png", + pageInfo: ["Author", "Original", "Date", "Category", "Tag", "ReadingTime"], + // 主题布局选项 + docsRepo: "https://github.com/142vip/JavaScriptCollection", + docsDir: "docs", + docsBranch: "master", + repo: "https://github.com/142vip/JavaScriptCollection.git", + // logoDark: "/assets/408_logo.png", + // logo: "/assets/408_logo.png", - // 博客配置 - // blog: { - // name: '测试', - // avatar: '', - // description: '', - // intro: '', - // roundAvatar: true, - // timeline: "时间轴的顶部文字", - // // articleInfo: "", - // medias: { - // "BiliBili": "https://space.bilibili.com/350937042?spm_id_from=333.1007.0.0" - // } - // }, - // 设置页脚 - displayFooter: true, - footer: FOOTER_HTML_INFO, - // copyright: false, + // 博客配置 + // blog: { + // name: '测试', + // avatar: '', + // description: '', + // intro: '', + // roundAvatar: true, + // timeline: "时间轴的顶部文字", + // // articleInfo: "", + // medias: { + // "BiliBili": "https://space.bilibili.com/350937042?spm_id_from=333.1007.0.0" + // } + // }, + // 设置页脚 + displayFooter: true, + footer: FOOTER_HTML_INFO, + // copyright: false, - // 主题色选择器 - // themeColor: { - // blue: "#2196f3", - // red: "#f26d6d", - // green: "#3eaf7c", - // orange: "#fb9b5f", - // }, + // 主题色选择器 + themeColor: { + blue: "#2196f3", + red: "#f26d6d", + green: "#3eaf7c", + orange: "#fb9b5f", + }, - plugins: { - readingTime: { - wordPerMinute: 200 - }, - copyright: false, - // 开启博客功能 - blog: false, - // 代码块 - mdEnhance: { - codetabs: true, - tasklist: true, // 支持任务列表 - // 启用 figure - figure: true, - // 启用图片懒加载 - imgLazyload: true, - // 启用图片标记 - imgMark: true, - // 启用图片大小 - imgSize: true, - playground: { - presets: ["ts", "vue"], - }, - revealjs: ["highlight", "math", "search", "notes", "zoom"], - stylize: [ - { - matcher: "Recommended", - replacer: ({tag}) => { - if (tag === "em") - return { - tag: "Badge", - attrs: {type: "tip"}, - content: "Recommended", - }; - }, - }, - ], - sub: true, - sup: true, - tabs: true, - vPre: true, - vuePlayground: true, - // 文件导入 - include: true, - // 容器 - container: true, - // mermaid - mermaid: true, - // 自定义对齐 - align: true, - }, - copyCode: { - showInMobile: true - }, - // 不自动生成readme目录 - autoCatalog: false, - // 参考:https://theme-hope.vuejs.press/zh/guide/markdown/components.html - components: { - components: [ - "AudioPlayer", - "Badge", - "BiliBili", - "CodePen", - "PDF", - "Replit", - "StackBlitz", - "VideoPlayer", - "YouTube", - "Share", - "XiGua" - ], - rootComponents: { - // 公告 参考:https://plugin-components.vuejs.press/zh/guide/notice.html - // notice: [ - // { - // path: "/", - // title: "在线浏览", - // content: "网站无法访问时,建议通过科学上网访问备用网络", - // actions: [ - // { - // text: "尝鲜版", - // link: "https://142vip.github.io/JavaScriptCollection", - // type: "default", - // }, - // { - // text: "稳定版", - // link: "https://142vip.cn/JavaScriptCollection", - // type: "primary", - // }, - // ], - // fullscreen: false, - // }, - // ], - }, + plugins: { + readingTime: { + wordPerMinute: 200 + }, + copyright: false, + // 开启博客功能 + blog: false, + // 代码块 + mdEnhance: { + codetabs: true, + tasklist: true, // 支持任务列表 + // 启用 figure + figure: true, + // 启用图片懒加载 + imgLazyload: true, + // 启用图片标记 + imgMark: true, + // 启用图片大小 + imgSize: true, + playground: { + presets: ["ts", "vue"], + }, + revealjs: ["highlight", "math", "search", "notes", "zoom"], + stylize: [ + { + matcher: "Recommended", + replacer: ({tag}) => { + if (tag === "em") + return { + tag: "Badge", + attrs: {type: "tip"}, + content: "Recommended", + }; }, - } - }) + }, + ], + sub: true, + sup: true, + tabs: true, + vPre: true, + vuePlayground: true, + // 文件导入 + include: true, + // 容器 + container: true, + // mermaid + mermaid: true, + // 自定义对齐 + align: true, + }, + copyCode: { + showInMobile: true + }, + // 不自动生成readme目录 + autoCatalog: false, + // 参考:https://theme-hope.vuejs.press/zh/guide/markdown/components.html + components: { + components: [ + "AudioPlayer", + "Badge", + "BiliBili", + "CodePen", + "PDF", + "Replit", + "StackBlitz", + "VideoPlayer", + "YouTube", + "Share", + "XiGua" + ], + rootComponents: { + // 公告 参考:https://plugin-components.vuejs.press/zh/guide/notice.html + // notice: [ + // { + // path: "/", + // title: "在线浏览", + // content: "网站无法访问时,建议通过科学上网访问备用网络", + // actions: [ + // { + // text: "尝鲜版", + // link: "https://142vip.github.io/JavaScriptCollection", + // type: "default", + // }, + // { + // text: "稳定版", + // link: "https://142vip.cn/JavaScriptCollection", + // type: "primary", + // }, + // ], + // fullscreen: false, + // }, + // ], + }, + }, + } + }) } \ No newline at end of file diff --git a/docs/.vuepress/styles/index.scss b/docs/.vuepress/styles/index.scss index 371aed36c..89f490f91 100644 --- a/docs/.vuepress/styles/index.scss +++ b/docs/.vuepress/styles/index.scss @@ -106,10 +106,6 @@ --medium-zoom-bg-color: var(--c-bg); } -// plugin-nprogress -#nprogress { - --nprogress-color: var(--c-brand); -} // plugin-pwa-popup .pwa-popup { diff --git "a/docs/manuscripts/battle-interview/problems/\345\211\215\347\253\257\345\270\270\350\200\203.md" "b/docs/manuscripts/battle-interview/problems/\345\211\215\347\253\257\345\270\270\350\200\203.md" index 151fdc9d8..c03065841 100644 --- "a/docs/manuscripts/battle-interview/problems/\345\211\215\347\253\257\345\270\270\350\200\203.md" +++ "b/docs/manuscripts/battle-interview/problems/\345\211\215\347\253\257\345\270\270\350\200\203.md" @@ -9,7 +9,7 @@ -```javascript +```js /** * 非递归实现快速排序 * @param {*} data @@ -90,7 +90,7 @@ console.log(quickSort([1, 8, 9, 2], 0, 3)) 综合代码整理如下,直接记忆: -```javascript +```js function rightBinarySearch(data, target) { if (!data.length) { return -1 @@ -166,7 +166,7 @@ function leftBinarySearch(data, target) { -```javascript +```js function binarySearch(data,target){ if (!data.length) { return -1 diff --git a/docs/manuscripts/other/big-event-history.md b/docs/manuscripts/big-event-history.md similarity index 77% rename from docs/manuscripts/other/big-event-history.md rename to docs/manuscripts/big-event-history.md index 67c5da38e..c8c01bd98 100644 --- a/docs/manuscripts/other/big-event-history.md +++ b/docs/manuscripts/big-event-history.md @@ -1,6 +1,6 @@ --- title: 大事记 -permalink: /manuscripts/other/big-event-history.html +permalink: /manuscripts/big-event-history.html --- # 大事记 diff --git a/docs/manuscripts/other/changelog.md b/docs/manuscripts/changelog.md similarity index 82% rename from docs/manuscripts/other/changelog.md rename to docs/manuscripts/changelog.md index 4870c3d5b..c1581500f 100644 --- a/docs/manuscripts/other/changelog.md +++ b/docs/manuscripts/changelog.md @@ -5,7 +5,7 @@ 该文件包含 `408CSFamily` 仓库和网站所有显着更改。 - + ## 更多 diff --git a/docs/manuscripts/develop-skill/code-manager/CI-CD.md b/docs/manuscripts/develop-skill/code-manager/CI-CD.md index a1845b5e8..6067fed62 100644 --- a/docs/manuscripts/develop-skill/code-manager/CI-CD.md +++ b/docs/manuscripts/develop-skill/code-manager/CI-CD.md @@ -1 +1,6 @@ +--- +title: 持续集成交付 +permalink: /manuscripts/develop-skill/code-manager/ci-cd.html +--- + # 持续集成交付 \ No newline at end of file diff --git a/docs/manuscripts/develop-skill/code-manager/git.md b/docs/manuscripts/develop-skill/code-manager/git.md index a72d9100c..349f12814 100644 --- a/docs/manuscripts/develop-skill/code-manager/git.md +++ b/docs/manuscripts/develop-skill/code-manager/git.md @@ -1,3 +1,7 @@ +--- +title: Git的使用 +permalink: /manuscripts/develop-skill/code-manager/git.html +--- # Git的使用 diff --git a/docs/manuscripts/develop-skill/code-manager/github-actions.md b/docs/manuscripts/develop-skill/code-manager/github-actions.md index 418701dc7..d3ed7038c 100644 --- a/docs/manuscripts/develop-skill/code-manager/github-actions.md +++ b/docs/manuscripts/develop-skill/code-manager/github-actions.md @@ -1,2 +1,6 @@ +--- +title: GitHub Action +permalink: /manuscripts/develop-skill/code-manager/github-actions.html +--- # GitHub Action \ No newline at end of file diff --git a/docs/manuscripts/develop-skill/code-manager/github-ci.md b/docs/manuscripts/develop-skill/code-manager/github-ci.md index 7bd149694..b88fa0e21 100644 --- a/docs/manuscripts/develop-skill/code-manager/github-ci.md +++ b/docs/manuscripts/develop-skill/code-manager/github-ci.md @@ -1,3 +1,8 @@ +--- +title: Github-CI +permalink: /manuscripts/develop-skill/code-manager/github-ci.html +--- + # Github-CI 当提交代码后需要做的事情: diff --git a/docs/manuscripts/develop-skill/code-manager/github.md b/docs/manuscripts/develop-skill/code-manager/github.md index 389b872f8..fb29c0476 100644 --- a/docs/manuscripts/develop-skill/code-manager/github.md +++ b/docs/manuscripts/develop-skill/code-manager/github.md @@ -1,3 +1,7 @@ +--- +title: 托管平台 +permalink: /manuscripts/develop-skill/code-manager/code-platform.html +--- # 托管平台 diff --git a/docs/manuscripts/develop-skill/code-manager/jenkins.md b/docs/manuscripts/develop-skill/code-manager/jenkins.md index 6127565e4..61d4b6c73 100644 --- a/docs/manuscripts/develop-skill/code-manager/jenkins.md +++ b/docs/manuscripts/develop-skill/code-manager/jenkins.md @@ -1,3 +1,7 @@ +--- +title: Jenkins +permalink: /manuscripts/develop-skill/code-manager/jenkins.html +--- # Jenkins diff --git a/docs/manuscripts/develop-skill/code-style/eslint.md b/docs/manuscripts/develop-skill/code-style/eslint.md index 082e5f9e3..b43f0124d 100644 --- a/docs/manuscripts/develop-skill/code-style/eslint.md +++ b/docs/manuscripts/develop-skill/code-style/eslint.md @@ -1,8 +1,10 @@ --- title: Eslint +permalink: /manuscripts/develop-skill/code-style/eslint.html --- +# Eslint -### 基础使用 +## 基础使用 - 配置eslint - 生产环境安装eslint @@ -47,7 +49,7 @@ pnpm i eslint -D ``` -### 参考资料 +## 参考资料 - diff --git a/docs/manuscripts/develop-skill/code-style/prettier.md b/docs/manuscripts/develop-skill/code-style/prettier.md index e69de29bb..42607f565 100644 --- a/docs/manuscripts/develop-skill/code-style/prettier.md +++ b/docs/manuscripts/develop-skill/code-style/prettier.md @@ -0,0 +1,7 @@ +--- +title: Prettier +permalink: /manuscripts/develop-skill/code-style/prettier.html +--- +# Prettier + +## 基础使用 diff --git a/docs/manuscripts/develop-skill/develop-skill.sidebar.ts b/docs/manuscripts/develop-skill/develop-skill.sidebar.ts index 7e04632f6..f4056aaaa 100644 --- a/docs/manuscripts/develop-skill/develop-skill.sidebar.ts +++ b/docs/manuscripts/develop-skill/develop-skill.sidebar.ts @@ -1,7 +1,7 @@ export const developSkillSidebar = [ { text: '软件安装', - link: 'software-install/readme.md' + link: 'software-install.md' }, { text: 'npm&pnpm&yarn', diff --git a/docs/manuscripts/develop-skill/monorepo.md b/docs/manuscripts/develop-skill/monorepo.md index bc75bc700..ba74c40ec 100644 --- a/docs/manuscripts/develop-skill/monorepo.md +++ b/docs/manuscripts/develop-skill/monorepo.md @@ -1,3 +1,8 @@ +--- +title: Monorepo +permalink: /manuscripts/develop-skill/monorepo.html +--- + # Monorepo - pnpm @@ -5,7 +10,7 @@ ## pnpm -## turboRepo +## TurboRepo `turbo`支持在`yarn`和`npm`上使用,但在使用`pnpm`目前支持: @@ -17,5 +22,6 @@ -参考资料 + +## 参考资料 - \ No newline at end of file diff --git a/docs/manuscripts/develop-skill/package-manager.md b/docs/manuscripts/develop-skill/package-manager.md index fc38ec18c..e2c5fe23a 100644 --- a/docs/manuscripts/develop-skill/package-manager.md +++ b/docs/manuscripts/develop-skill/package-manager.md @@ -1,3 +1,8 @@ +--- +title: 依赖管理 +permalink: /manuscripts/develop-skill/package-manager.html +--- + # 依赖管理 ## PNPM @@ -299,5 +304,6 @@ pnpm add @3.0.0 - `--global, -g`:将软件包安装都全局环境中。 +## 参考资料 -参考:https://blog.csdn.net/tangxiujiang/article/details/119977698 \ No newline at end of file +- \ No newline at end of file diff --git a/docs/manuscripts/develop-skill/readme.md b/docs/manuscripts/develop-skill/readme.md index 023d4c90d..296edb950 100644 --- a/docs/manuscripts/develop-skill/readme.md +++ b/docs/manuscripts/develop-skill/readme.md @@ -1,3 +1,2 @@ - -# 开发技巧 +# 开发技巧 \ No newline at end of file diff --git a/docs/manuscripts/develop-skill/software-install/readme.md b/docs/manuscripts/develop-skill/software-install.md similarity index 66% rename from docs/manuscripts/develop-skill/software-install/readme.md rename to docs/manuscripts/develop-skill/software-install.md index 8b80efe67..d2f209356 100644 --- a/docs/manuscripts/develop-skill/software-install/readme.md +++ b/docs/manuscripts/develop-skill/software-install.md @@ -1,12 +1,15 @@ --- title: 软件安装 +permalink: /manuscripts/develop-skill/software-install.html --- -### IDEA +## IDEA +在官网下载官方依赖,选择符合系统的版本,然后安装 -### uPic + +## uPic 简洁的 Mac 图床客户端 uPic diff --git a/docs/manuscripts/develop-skill/software-install/upic.md b/docs/manuscripts/develop-skill/software-install/upic.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/manuscripts/front-end/base-begin/javascript.md b/docs/manuscripts/front-end/base-begin/javascript.md index d81299446..88f2cd0b9 100644 --- a/docs/manuscripts/front-end/base-begin/javascript.md +++ b/docs/manuscripts/front-end/base-begin/javascript.md @@ -53,7 +53,7 @@ title: JavaScript相关 > ES6新增了类的概念,可以使用class关键字来声明一个类,然后用类来实例化对象 -```javascript +```js // 定义类 class Star{ @@ -79,7 +79,7 @@ star.say(); > 子类可以继承父类的属性和方法 -```javascript +```js // 定义父类 class Father{ @@ -137,7 +137,7 @@ class出现之前,创建对象的方式: - new Object(); - 自定义构造函数 -```javascript +```js // 利用new Object() 创建对象 let obj=new Object(); @@ -186,7 +186,7 @@ prototype本身就是一个对象,这个对象的所有属性和方法,都 **因此,我们可以把那些不变的方法,直接定义在prototype对象上面,这样所有的对象的实例就可以共享这些方法** -```javascript +```js // 方法挂在构造函数的原型对象 creatObj.prototype.test=function(){ @@ -218,7 +218,7 @@ creatObj.prototype.test=function(){ - 1. 先查实例化对象是否有对应方法。如果有就直接调用 - 2. 没有则根据对象原型_proto_的存在,去查找构造函数原型对象prototype上查找对应方法 -```javascript +```js // 实例化对象中的_proto对象原型 和 构造函数中的原型对象prototype是等价的 输出结构为:true; console.log(obj._proto_===creatObj.prototype) @@ -233,7 +233,7 @@ console.log(obj._proto_===creatObj.prototype) **用来记录该对象引用那个构造函数,可以让原型对象重新指向原来的构造函数** -```javascript +```js creatObj.prototype={ // 如果修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数 @@ -289,7 +289,7 @@ creatObj.prototype={ ### call() -```javascript +```js // 定义函数 function fn(x,y){ @@ -313,7 +313,7 @@ let result={ ### 借用构造函数继承属性 -```javascript +```js // 父构造函数 function Father(name,age){ this.name=name; @@ -375,7 +375,7 @@ some()和forEach()的区别: ### 对象方法 -```javascript +```js // 定义对象总新属性或修改原有的属性 // - obj: 必须 ,目标对象 // - prop: 必须,需定义或修改的属性的名字 @@ -408,7 +408,7 @@ Object.defineProperty(obj,'name',{ - 函数表达式(匿名函数) - 利用new Function('参数一','参数二','函数体') 【利用Function构造函数】 -```javascript +```js // 命名函数 function fn(){ @@ -435,7 +435,7 @@ let fn=new Function('a','b','return a+b') - 立即执行函数 -```javascript +```js // 普通函数 function fn(){ @@ -501,7 +501,7 @@ setInterval(function(){ - bind() - apply() -```javascript +```js // call() :1. 调用函数fn.call() 2.改变this指向 let obj={ @@ -532,7 +532,7 @@ fun.apply(thisArg,[argsArray]); **apply()参数传数组** -```javascript +```js // 求数组中的最大值 let arr=[1,4,23,78,25]; @@ -551,7 +551,7 @@ fun.bind(thisArg,arg1,arg2) - arg1,arg2: 传递的其他参数 - 返回由指定的this值和初始化参数改造的**原函数拷贝** -```javascript +```js let obj={ name:'xxx' @@ -604,14 +604,14 @@ f(); - 提高编译器效率,增加运行速度 - 禁用在ECMAScript的未来版本中,可能会定义的一些语法,为未来新版本的JavaScript做好铺垫。例如:保留字/关键字 super class export等不能做变量名 -```javascript +```js // 严格模式 'use strict' ``` -**在严格模式下,全局作用域中函数的this指向的是undefined,而不是window对象** +**在严格模式下,全局作用域中函数的this指向的是undefined,而不是window对象** @@ -621,7 +621,7 @@ f(); 函数本身也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。例如:回调函数 -```javascript +```js // 高阶函数示例 function fn(callback){ callback&&callback(); @@ -647,7 +647,7 @@ fn(); -```javascript +```js function fn(){ var num=10; @@ -668,7 +668,7 @@ fn(); > 外面的作用域可以访问函数内部的局部变量 -```javascript +```js function fn(){ var num=10; @@ -702,7 +702,7 @@ fun1() ### 浅拷贝 -```javascript +```js let obj={ name:'xxxx', @@ -751,7 +751,7 @@ Object.assign(target, ...sources) -```javascript +```js let obj={ @@ -814,7 +814,7 @@ console.log(obj_test,obj) - 不存在变量提升(先定义再使用) - 具有暂时性死区【let声明,会和当前的块级进行绑定】 -```javascript +```js var num=10; if(true){ // 此时输出会出现无定义,只会在块级里面查找num是否定义,不会查找外面 @@ -827,7 +827,7 @@ if(true){ ### let面试题 -```javascript +```js var arr=[]; for(var i=0;i<2;i++){ @@ -843,7 +843,7 @@ arr[1](); // 输出2 ``` -```javascript +```js let arr=[]; // 每次循环let都会产生块级作用域 @@ -867,7 +867,7 @@ arr[1](); // 输出1 - 声明常量时,必须赋初始值 - 常量赋值后,内存地址值不可以改 -```javascript +```js const PI=3.14 PI=100 // 出错 @@ -897,7 +897,7 @@ arr=[12,13] > 允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构 -```javascript +```js // 数组解构 let [a,b,c]=[1,2,3] @@ -923,7 +923,7 @@ console.log(my_name,my_age); // leo 20 > 新增的定义函数的方式 -```javascript +```js ()=>{} const fn=()=>{ console.log(123) @@ -939,7 +939,7 @@ const sum = a=>a **箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this** -```javascript +```js const obj={name:'leo'} function fn(){ @@ -962,7 +962,7 @@ resFn(); ### 箭头函数面试题 -```javascript +```js let obj={ age:20, @@ -984,7 +984,7 @@ obj.say(); ### 剩余参数 -```javascript +```js function sum(first,...args){ console.log(first); console.log(args); @@ -998,7 +998,7 @@ const result=(...args)=>{ ### Array扩展方法 -```javascript +```js // ## 扩展运算符合并数组 ## let arr1=[1,2,3]; @@ -1042,7 +1042,7 @@ let arr3=Array.from(arrayLike,item=>{ 表示某个数组是否包含给定的值,返回布尔值 -```javascript +```js [1,2,3].includes(2); // true [1,2,3].includes(4); // false @@ -1055,7 +1055,7 @@ let arr3=Array.from(arrayLike,item=>{ - endsWith(): 表示参数字符串是否在原字符串的尾部,返回布尔值 -```javascript +```js let str = 'hello world!'; str.startsWith('Hello'); // true @@ -1075,7 +1075,7 @@ repeat()方法表示将原字符串重复n次,返回一个新的字符串; **Set本身是一个构造函数,用来生成Set数据结构** -```javascript +```js // 定义set集合 const set=new Set(); // 可以接收数组作为参数,用来初始化 @@ -1097,7 +1097,7 @@ arr=[...temp] - clear(): 清除所有成员,没有返回值 -```javascript +```js let set =new Set(); set.add(1).add(2).add(3); @@ -1112,7 +1112,7 @@ set.clear(); Set结构的实例与数组是一样的,也拥有forEach()方法,用于对每个成员执行某种操作,没有返回值 -```javascript +```js let set=new Set(); @@ -1146,7 +1146,7 @@ join,就是把数组转换成字符串,然后给他规定个连接字符, 书写格式:join(" "),括号里面写字符串 ("要加引号"), -```javascript +```js var arr = [1,2,3]; console.log(arr.join());     // 1,2,3 @@ -1166,7 +1166,7 @@ pop():移除数组最后一项,返回移除的那个值,减少数组的len 书写格式:arr.pop( ) -```javascript +```js var arr = ["Lily","lucy","Tom"]; var count = arr.push("Jack","Sean"); console.log(count);           // 5 @@ -1186,7 +1186,7 @@ unshift():将参数添加到原数组开头,并返回数组的长度 。 书写格式:arr.shift(" "),括号里面写内容 ("字符串要加引号"), -```javascript +```js var arr = ["Lily","lucy","Tom"]; var count = arr.unshift("Jack","Sean"); @@ -1228,7 +1228,7 @@ reverse():反转数组项的顺序。 书写格式:arr.reverse( ) -```javascript +```js var arr = [13, 24, 51, 3]; console.log(arr.reverse());         //[3, 51, 24, 13] @@ -1241,7 +1241,7 @@ console.log(arr);               //[3, 51, 24, 13]( concat() :将参数添加到原数组中。这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat()方法传递参数的情况下,它只是复制当前数组并返回副本。 书写格式:arr.concat(),括号里面写内容 ("字符串要加引号"), -```javascript +```js var arr = [1,3,5,7]; var arrCopy = arr.concat(9,[11,13]); @@ -1255,7 +1255,7 @@ console.log(arr);               // [1, 3, 5, 7](原 slice():返回从原数组中指定开始下标到结束下标之间的项组成的新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下, slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。 书写格式:arr.slice( 1 , 3 ) -```javascript +```js var arr = [1,3,5,7,9,11]; var arrCopy = arr.slice(1); @@ -1296,7 +1296,7 @@ splice():删除、插入和替换。 书写格式:arr.splice( 2,0,4,6 ) -```javascript +```js var arr = [1,3,5,7,9,11]; var arrRemoved = arr.splice(0,2); @@ -1322,7 +1322,7 @@ lastIndexOf:接收两个参数:要查找的项和(可选的)表示查找 书写格式:arr.lastIndexOf( 5,4 ) -```javascript +```js var arr = [1,3,5,7,7,5,3,1]; console.log(arr.indexOf(5));       //2 console.log(arr.lastIndexOf(5));     //5 @@ -1337,7 +1337,7 @@ console.log(arr.indexOf("5"));      //-1 forEach():对数组进行遍历循环,对数组中的每一项运行给定函数。这个方法没有返回值。参数都是function类型,默认有传参,参数分别为:遍历的数组内容;第对应的数组索引,数组本身。 书写格式:arr.forEach() -```javascript +```js var arr = [1, 2, 3, 4, 5]; arr.forEach(function(x, index, a){ @@ -1358,7 +1358,7 @@ map():指“映射”,对数组中的每一项运行给定函数,返回每 书写格式:arr.map() -```javascript +```js var arr = [1, 2, 3, 4, 5]; var arr2 = arr.map(function(item){ @@ -1374,7 +1374,7 @@ filter():“过滤”功能,数组中的每一项运行给定函数,返回 书写格式:arr.filter() -```javascript +```js var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var arr2 = arr.filter(function(x, index) { return index % 3 === 0 || x >= 8; @@ -1406,7 +1406,7 @@ some():判断数组中是否存在满足条件的项,只要有一项满足 书写格式:arr.some() -```javascript +```js var arr = [1, 2, 3, 4, 5]; var arr2 = arr.some(function(x) { return x < 3; diff --git a/docs/manuscripts/front-end/front-end.sidebar.ts b/docs/manuscripts/front-end/front-end.sidebar.ts index caa45a2d8..47708ccf4 100644 --- a/docs/manuscripts/front-end/front-end.sidebar.ts +++ b/docs/manuscripts/front-end/front-end.sidebar.ts @@ -30,7 +30,7 @@ export const frontEndSidebar = [ }, { text: 'JQuery', - link: 'jquery' + link: 'jquery.md' } ] }, @@ -41,28 +41,28 @@ export const frontEndSidebar = [ children: [ { text: 'Ant-Design-Vue', - link: 'ant-design-vue' + link: 'ant-design-vue.md' }, { text: 'Element-UI', - link: 'element-ui' + link: 'element-ui.md' }, { text: 'IView-Design', - link: 'iview-design' + link: 'iview-design.md' }, { text: 'VAnt-UI', - link: 'vant-ui' + link: 'vant-ui.md' }, { text: 'Layer-UI', - link: 'layer-ui' + link: 'layer-ui.md' }, { text: 'Bootstrap', - link: 'bootstrap' + link: 'bootstrap.md' } ] }, diff --git a/docs/manuscripts/front-end/front-framework/jquery.md b/docs/manuscripts/front-end/front-framework/jquery.md index 345b6c13e..7eddb588e 100644 --- a/docs/manuscripts/front-end/front-framework/jquery.md +++ b/docs/manuscripts/front-end/front-framework/jquery.md @@ -1,3 +1,7 @@ +--- +title: JQuery +permalink: /manuscripts/front-end/front-framework/jquery.html +--- # JQuery > 不推荐学,直接看官方文档,操作api方法即可 diff --git a/docs/manuscripts/front-end/front-framework/nuxt.md b/docs/manuscripts/front-end/front-framework/nuxt.md index b8225aa3d..eca42c48c 100644 --- a/docs/manuscripts/front-end/front-framework/nuxt.md +++ b/docs/manuscripts/front-end/front-framework/nuxt.md @@ -1 +1,5 @@ +--- +title: Nuxt.js +permalink: /manuscripts/front-end/front-framework/nuxt.html +--- # Nuxt.js \ No newline at end of file diff --git a/docs/manuscripts/front-end/front-framework/vue.md b/docs/manuscripts/front-end/front-framework/vue.md index 7ca331276..dbf8f80f0 100644 --- a/docs/manuscripts/front-end/front-framework/vue.md +++ b/docs/manuscripts/front-end/front-framework/vue.md @@ -1 +1,5 @@ -## Vue框架 \ No newline at end of file +--- +title: Vue框架 +permalink: /manuscripts/front-end/front-framework/vue.html +--- +# Vue框架 \ No newline at end of file diff --git a/docs/manuscripts/front-end/front-framework/vuepress.md b/docs/manuscripts/front-end/front-framework/vuepress.md index 7600abf83..3943e6bac 100644 --- a/docs/manuscripts/front-end/front-framework/vuepress.md +++ b/docs/manuscripts/front-end/front-framework/vuepress.md @@ -1 +1,5 @@ +--- +title: Vuepress +permalink: /manuscripts/front-end/front-framework/vuepress.html +--- # Vuepress \ No newline at end of file diff --git "a/docs/manuscripts/front-end/server-deploy/SSR\344\274\230\345\214\226.md" "b/docs/manuscripts/front-end/server-deploy/SSR\344\274\230\345\214\226.md" index e69de29bb..862a7ad5a 100644 --- "a/docs/manuscripts/front-end/server-deploy/SSR\344\274\230\345\214\226.md" +++ "b/docs/manuscripts/front-end/server-deploy/SSR\344\274\230\345\214\226.md" @@ -0,0 +1,6 @@ +--- +title: SSR优化 +permalink: /manuscripts/front-end/server-deploy/ssr-improve.html +--- + +# SSR优化 \ No newline at end of file diff --git "a/docs/manuscripts/front-end/server-deploy/\346\234\215\345\212\241\347\253\257\346\270\262\346\237\223.md" "b/docs/manuscripts/front-end/server-deploy/\346\234\215\345\212\241\347\253\257\346\270\262\346\237\223.md" index d4e5ee74d..85784c273 100644 --- "a/docs/manuscripts/front-end/server-deploy/\346\234\215\345\212\241\347\253\257\346\270\262\346\237\223.md" +++ "b/docs/manuscripts/front-end/server-deploy/\346\234\215\345\212\241\347\253\257\346\270\262\346\237\223.md" @@ -1,6 +1,8 @@ --- -title: 前后端分离 +title: 服务端渲染 +permalink: /manuscripts/front-end/server-deploy/ssr.html --- +# 服务端渲染 > 前端借助nginx \ No newline at end of file diff --git "a/docs/manuscripts/front-end/server-deploy/\351\235\231\346\200\201\346\226\207\344\273\266\351\203\250\347\275\262.md" "b/docs/manuscripts/front-end/server-deploy/\351\235\231\346\200\201\346\226\207\344\273\266\351\203\250\347\275\262.md" index 0409debad..9009807dd 100644 --- "a/docs/manuscripts/front-end/server-deploy/\351\235\231\346\200\201\346\226\207\344\273\266\351\203\250\347\275\262.md" +++ "b/docs/manuscripts/front-end/server-deploy/\351\235\231\346\200\201\346\226\207\344\273\266\351\203\250\347\275\262.md" @@ -1,4 +1,7 @@ - +--- +title: 静态文件部署 +permalink: /manuscripts/front-end/server-deploy/static-deploy.html +--- # 静态文件部署 目前,前端静态资源部署主要分为前端独立部署和后端混合部署两种方式;就node技术栈而言,更加推荐前端独立部署,这样便于在微服务场景下进行问题定位 diff --git a/docs/manuscripts/front-end/ui-framework/Readme.md b/docs/manuscripts/front-end/ui-framework/Readme.md deleted file mode 100644 index 6ebaf239c..000000000 --- a/docs/manuscripts/front-end/ui-framework/Readme.md +++ /dev/null @@ -1,3 +0,0 @@ - - -## UI框架 \ No newline at end of file diff --git a/docs/manuscripts/front-end/ui-framework/ant-design-vue.md b/docs/manuscripts/front-end/ui-framework/ant-design-vue.md index 50a7ced8c..fec7ba5bf 100644 --- a/docs/manuscripts/front-end/ui-framework/ant-design-vue.md +++ b/docs/manuscripts/front-end/ui-framework/ant-design-vue.md @@ -1,6 +1,8 @@ --- title: Ant-Design-Vue +permalink: /manuscripts/front-end/ui-framework/ant-design-vue.html --- +# Ant-Design-Vue diff --git a/docs/manuscripts/front-end/ui-framework/bootstrap.md b/docs/manuscripts/front-end/ui-framework/bootstrap.md new file mode 100644 index 000000000..6f420b832 --- /dev/null +++ b/docs/manuscripts/front-end/ui-framework/bootstrap.md @@ -0,0 +1,6 @@ +--- +title: Bootstrap框架 +permalink: /manuscripts/front-end/ui-framework/bootstrap.html +--- + +# Bootstrap框架 \ No newline at end of file diff --git a/docs/manuscripts/front-end/ui-framework/element-ui.md b/docs/manuscripts/front-end/ui-framework/element-ui.md index a31e0a8ac..4b0f87ae4 100644 --- a/docs/manuscripts/front-end/ui-framework/element-ui.md +++ b/docs/manuscripts/front-end/ui-framework/element-ui.md @@ -1,3 +1,6 @@ --- title: Element-UI +permalink: /manuscripts/front-end/ui-framework/element.html --- + +# Element-UI diff --git a/docs/manuscripts/microservice/apollo.md b/docs/manuscripts/microservice/apollo.md new file mode 100644 index 000000000..98256af00 --- /dev/null +++ b/docs/manuscripts/microservice/apollo.md @@ -0,0 +1,7 @@ +--- +title: Apollo +permalink: /manuscripts/microservice/apollo.html +--- + + +# Apollo \ No newline at end of file diff --git a/docs/manuscripts/microservice/consul.md b/docs/manuscripts/microservice/consul.md new file mode 100644 index 000000000..307e2eef6 --- /dev/null +++ b/docs/manuscripts/microservice/consul.md @@ -0,0 +1,7 @@ +--- +title: Consul +permalink: /manuscripts/microservice/consul.html +--- + + +# Consul \ No newline at end of file diff --git a/docs/manuscripts/microservice/docker-compose.md b/docs/manuscripts/microservice/docker-compose.md index 12688c44c..0f91948e5 100644 --- a/docs/manuscripts/microservice/docker-compose.md +++ b/docs/manuscripts/microservice/docker-compose.md @@ -1 +1,37 @@ -# Docker-Compose \ No newline at end of file +--- +title: Docker-Compose +permalink: /manuscripts/microservice/docker-compose.html +--- + +# Docker-Compose + + +## 基本介绍 + +## 安装 + +```bash +## 文件下载 可能下载有点慢 +curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-uname -s-uname -m -o /usr/local/bin/docker-compose + +## 【推荐】下载慢,可以尝试: +curl -L https://get.daocloud.io/docker/compose/releases/download/1.24.1/docker-compose-uname -s-uname -m > /usr/local/bin/docker-compose + +## 配置docker-compose执行权限 +chmod +x /usr/local/bin/docker-compose + +## 查看版本 +docker-compose version +安装完成后,就可以查看docker-compose版本信息 +[root@172-16-203-143 ~]# docker-compose version +docker-compose version 1.24.1, build 4667896b +docker-py version: 3.7.3 +CPython version: 3.6.8 +OpenSSL version: OpenSSL 1.1.0j 20 Nov 2018 +另外,docker-compose的操作和docker非常类似,可以简单记忆: +## 服务启动,默认Dockerfile -d:后台启动,不加为前台启动 +docker-compose up -d + +## 服务down 相当于kill +docker-compose down +``` \ No newline at end of file diff --git a/docs/manuscripts/microservice/docker.md b/docs/manuscripts/microservice/docker.md index dc0428544..802415f76 100644 --- a/docs/manuscripts/microservice/docker.md +++ b/docs/manuscripts/microservice/docker.md @@ -1,9 +1,116 @@ +--- +title: Docker +permalink: /manuscripts/microservice/docker.html +--- + # Docker +```mindmap +root(Docker) + 简单介绍 + 镜像命令 + 容器命令 + 数据卷 +``` + +Docker是一个开源的应用容器引擎,它是基于Go语言并遵从Apache2.0协议开源。是一个应用打包、分发、部署的工具,可以把它理解为一个轻量的虚拟机。 + +Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的linux机器上,也可以实现虚拟化。 +通过容器可以实现方便快速并且与平台解耦的自动化部署方式,无论你部署时的环境如何,容器中的应用程序都会运行在同一种环境下。并且它是完全使用沙箱机制,相互之间是隔离的,更重要的是容器性能开销极低。 + + +Docker思想: + +- 集装箱 +- 标准化: ① 运输方式 ② 存储方式 ③ API 接口 +- 隔离 -### 常用命令 +## 安装 + +### Mac + +使用Mac中的`Homebrew`直接安装即可 -基于linux系统使用 + +```bash +brew install --cask docker +``` + + +### Linux +> 以CentOS系统为例 + +#### 使用Yum安装 + +- 检查系统版本 + +```bash +uname -r +``` + +- 移除旧Docker版本 + +```bash +$ sudo yum remove docker \ + docker-client \ + docker-client-latest \ + docker-common \ + docker-latest \ + docker-latest-logrotate \ + docker-logrotate \ + docker-selinux \ + docker-engine-selinux \ + docker-engine +``` + +- 安装docker-ce + +使用yum下载,可以先对yum更新 + +```bash + +## 软件更新 +yum update + +## 安装docker社区版 +sudo yum -y install docker-ce +``` + +#### 使用官方脚本安装 + +```bash +## 下载脚本 +curl -fsSL get.docker.com -o get-docker.sh + +## 执行安装 +sh get-docker.sh --mirror Aliyun +``` + + +当docker安装完成后,建议设置开机启动启动服务 + +```bash +# 开启docker服务开机自启动命令 +systemctl enable docker.service + +# 关闭docker服务开机自启动命令 +systemctl disable docker.service +``` + + + +## 卸载 + +```bash +# 卸载docker-ce +sudo yum remove docker-ce + +# 删除docker文件数据 +sudo rm -rf /var/lib/docker +``` + + +## 启动|停止服务 ```bash ## 启动docker @@ -34,7 +141,7 @@ docker create/log --help ``` -### 镜像命令 +## 镜像命令 ```bash ## 查看本地所有镜像 @@ -85,7 +192,7 @@ docker image prune > 虚悬镜像名字很高大上,实际就是指:镜像没有仓库名或没有标签 -### 容器命令 +## 容器命令 ```bash ## 运行容器 @@ -116,7 +223,6 @@ docker rm 容器ID docker rm -f ${docker ps -a -q} docker ps -a -q | xargs docker rm - ## 查看容器日志 docker logs xxxx @@ -125,24 +231,21 @@ docker top xxxx ## 查看容器详细信息 docker inspect 容器ID - - - ``` -#### 容器两种退出方式 +### 容器退出 -##### exit命令 +- exit命令 run命令进入容器,通过exit命令退出后,容器停止运行 -##### ctrl+p+q 命令 +- ctrl+p+q 命令 run命令进入容器,通过`ctrl+p+q`退出,容器不停止 -#### 容器两种进入方法 +### 进入容器 ```bash ## exec命令进入容器 @@ -162,7 +265,7 @@ attach命令和exec命令的执行区别: -#### 启动后台守护容器 +### 后台守护容器 > 在大部分场景下,希望docker的服务在后台运行的,可以通过`-d`指定容器的后台运行模式 @@ -178,23 +281,58 @@ docker run -it xxxx #### 文件相关 -> export和import命令可以参考:https://blog.csdn.net/clj198606061111/article/details/50450793 -#### 拷贝cp命令 -docker cp 容器ID : 容器中文件路径 当前主机待保存的路径 +### 文件拷贝 + +通过docker cp指令能够将文件复制到容器中,也可以将容器中的文件复制出来 + +```bash +## 复制到容器中 +docker cp 当前主机待保存的路径 容器ID:容器中文件路径 + +## 将容器中的文件复制到容器外 +docker cp 容器ID:容器中文件路径 当前主机待保存的路径 + +``` + + + +### 导入|导出 + +> 参考:https://blog.csdn.net/clj198606061111/article/details/50450793 + #### 导出export命令 -docker export 容器ID > 文件名.tar +> 可以自定义容器导出后文件的格式 + + +命令格式: docker export 容器ID > 文件名.tar + + + +```bash +## 例如: +docker export xxx xxx.tar +docker export -o "xxx.tar" xxx + +``` #### 导入import命令 -cat 文件名.tar | docker import -镜像用户/镜像名:镜像版本号 +> 将本地保存的容器快照导入为镜像 + +命令格式: docker import -镜像用户/镜像名:镜像版本号 + +```bash +# 例如: +docker import web.tar web:v1 +``` -## docker容器数据卷 +## 容器数据卷 注意开启文件权限,避免权限不够出现错误 @@ -225,7 +363,7 @@ cat 文件名.tar | docker import -镜像用户/镜像名:镜像版本号 命令格式: `-v xxx容器文件路径:宿主机文件路径` -### 容器卷读写规则 +### 读写规则 - ro: 只读 - rw:可读可写 @@ -234,6 +372,14 @@ cat 文件名.tar | docker import -镜像用户/镜像名:镜像版本号 +## 实战操作 + + +## 参考资料 + +- + + diff --git a/docs/manuscripts/microservice/elk/elasticsearch.md b/docs/manuscripts/microservice/elk/elasticsearch.md index 8754f91f4..c6d28c32d 100644 --- a/docs/manuscripts/microservice/elk/elasticsearch.md +++ b/docs/manuscripts/microservice/elk/elasticsearch.md @@ -1,2 +1,6 @@ +--- +title: Elasticsearch +permalink: /manuscripts/microservice/elk/elasticsearch.html +--- # Elasticsearch \ No newline at end of file diff --git a/docs/manuscripts/microservice/elk/filebeat.md b/docs/manuscripts/microservice/elk/filebeat.md index 8540c21ea..2eb3024e1 100644 --- a/docs/manuscripts/microservice/elk/filebeat.md +++ b/docs/manuscripts/microservice/elk/filebeat.md @@ -1 +1,6 @@ +--- +title: Filebeat +permalink: /manuscripts/microservice/elk/filebeat.html +--- + # Filebeat \ No newline at end of file diff --git a/docs/manuscripts/microservice/elk/kibana.md b/docs/manuscripts/microservice/elk/kibana.md index e4db1a60e..13de4e06e 100644 --- a/docs/manuscripts/microservice/elk/kibana.md +++ b/docs/manuscripts/microservice/elk/kibana.md @@ -1,2 +1,6 @@ +--- +title: Kibana +permalink: /manuscripts/microservice/elk/kibana.html +--- # Kibana \ No newline at end of file diff --git a/docs/manuscripts/microservice/elk/logstash.md b/docs/manuscripts/microservice/elk/logstash.md index 6c5a0573a..6b64928ff 100644 --- a/docs/manuscripts/microservice/elk/logstash.md +++ b/docs/manuscripts/microservice/elk/logstash.md @@ -1 +1,6 @@ +--- +title: Logstash +permalink: /manuscripts/microservice/elk/logstash.html +--- + # Logstash \ No newline at end of file diff --git a/docs/manuscripts/microservice/gateway.md b/docs/manuscripts/microservice/gateway.md new file mode 100644 index 000000000..bb13caf2a --- /dev/null +++ b/docs/manuscripts/microservice/gateway.md @@ -0,0 +1,6 @@ +--- +title: 网关 +permalink: /manuscripts/microservice/gateway.html +--- + +# 网关 \ No newline at end of file diff --git a/docs/manuscripts/microservice/grpc.md b/docs/manuscripts/microservice/grpc.md index fe1fa2cd0..bed3527ba 100644 --- a/docs/manuscripts/microservice/grpc.md +++ b/docs/manuscripts/microservice/grpc.md @@ -1,7 +1,9 @@ -# gRPC +--- +title: gRPC +permalink: /manuscripts/microservice/grpc.html +--- -- 文档: -- 仓库: +# gRPC **gRPC 是一种可扩展、松耦合且类型安全的解决方案,与传统的基于 REST/HTTP 的通信相比,它实现了更高效的进程间通信。允许你像本地方法调用一样调用、调试分布式应用程序** @@ -19,12 +21,9 @@ gRPC 是一个现代开源高性能远程过程调用 (RPC) 框架,可以在 ![](images/grpc-core.png) +## 核心概念 - - -### 核心概念 - -#### 服务定义 +### 服务定义 与许多 RPC 系统一样,gRPC 是基于定义服务的思想,指定可以使用其参数和返回类型远程调用的方法。 @@ -91,9 +90,9 @@ rpc BidiHello(stream HelloRequest) returns (stream HelloResponse); 在响应从服务器到达之前阻塞的同步 RPC 调用最接近于 RPC 所追求的过程调用的抽象。**另一方面,网络本质上是异步的,在许多情况下,能够在不阻塞当前线程的情况下启动 RPC 是很有用的**。 -### RPC 生命周期 +## RPC 生命周期 -#### 一元 RPC +### 一元 RPC 首先考虑最简单的 RPC 类型,其中客户端发送单个请求并返回单个响应。 - 一旦客户端调用了存根方法,服务器就会收到通知,RPC 已被调用,其中包含客户端的元数据 、方法名称和指定的截止日期(如果适用)。 @@ -101,19 +100,19 @@ rpc BidiHello(stream HelloRequest) returns (stream HelloResponse); - 一旦服务器收到客户端的请求消息,它就会执行创建和填充响应所需的任何工作。然后将响应(如果成功)连同状态详细信息(状态代码和可选的状态消息)和可选的尾随元数据一起返回给客户端。 - 如果响应状态为OK,则客户端得到响应,从而完成客户端的调用。 -#### 服务器流式 RPC +### 服务器流式 RPC 服务器流式 RPC 类似于一元 RPC,不同之处在于服务器返回消息流以响应客户端的请求。发送完所有消息后,服务器的状态详细信息(状态代码和可选的状态消息)和可选的尾随元数据将发送到客户端。这样就完成了服务器端的处理。客户端在收到服务器的所有消息后完成。 -#### 客户端流式 RPC +### 客户端流式 RPC 客户端流式 RPC 类似于一元 RPC,不同之处在于客户端向服务器发送消息流而不是单个消息。服务器响应一条消息(连同其状态详细信息和可选的尾随元数据),通常但不一定在它收到所有客户端消息之后。 -#### 双向流式 RPC +### 双向流式 RPC 在双向流式 RPC 中,调用由客户端调用方法和服务器接收客户端元数据、方法名称和截止日期发起。服务器可以选择发回其初始元数据或等待客户端开始流式传输消息。 客户端和服务器端流处理是特定于应用程序的。由于两个流是独立的,因此客户端和服务器可以按任意顺序读写消息。例如,服务器可以等到收到客户端的所有消息后再写入消息,或者服务器和客户端可以玩“乒乓球”——服务器收到请求,然后发回响应,然后客户端发送基于响应的另一个请求,等等。 -### 请求流程 +## 请求流程 - 客户端(gRPC Stub)调用A方法,发起RPC调用 @@ -126,7 +125,7 @@ rpc BidiHello(stream HelloRequest) returns (stream HelloResponse); - 客户端接受到服务端响应,解码请求体。回调被调用的A方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果 -### 使用场景 +## 使用场景 建议: @@ -142,7 +141,7 @@ rpc BidiHello(stream HelloRequest) returns (stream HelloResponse); - 进程间通信 -### 优势 +## 优势 - **实现的进程间通信方式高效。** gRPC 不使用 JSON 或 XML 等文本格式,而是使用基于二进制协议的protocol buffer与 gRPC 服务、客户端进行通信。此外,gRPC 是在 HTTP/2 之上实现的protocol buffer,这使得进程间通信更快。 @@ -158,7 +157,14 @@ rpc BidiHello(stream HelloRequest) returns (stream HelloResponse); - **与云原生生态系统高度集成。** gRPC 是 CNCF 的一部分,大多数现代框架和技术都为 gRPC 提供了开箱即用的原生支持。 -### 不足 +## 不足 - **可能不适合面向外部的服务。** 当你想将应用程序或服务提供给外部客户端使用时,gRPC 可能不是最合适的协议,因为大多数外部使用者对 gRPC 还很陌生。而且,gRPC 服务的协议驱动、强类型化特性可能会降低你向外部提供的服务的灵活性,因为外部使用者可以控制的东西要少得多。 - **生态系统相对较小。** 与传统的 REST/HTTP 协议相比,gRPC 生态系统仍然相对较小。浏览器和移动应用程序对 gRPC 的支持仍处于初级阶段。 + + + +## 参考资料 + +- 文档: +- 仓库: diff --git a/docs/manuscripts/microservice/istio.md b/docs/manuscripts/microservice/istio.md index e69de29bb..e0294b554 100644 --- a/docs/manuscripts/microservice/istio.md +++ b/docs/manuscripts/microservice/istio.md @@ -0,0 +1,6 @@ +--- +title: Istio +permalink: /manuscripts/microservice/istio.html +--- + +# Istio \ No newline at end of file diff --git a/docs/manuscripts/microservice/k8s.md b/docs/manuscripts/microservice/k8s.md index e69de29bb..474761cc5 100644 --- a/docs/manuscripts/microservice/k8s.md +++ b/docs/manuscripts/microservice/k8s.md @@ -0,0 +1,4 @@ +--- +title: k8s +permalink: /manuscripts/microservice/k8s.html +--- \ No newline at end of file diff --git a/docs/manuscripts/microservice/microservice.sidebar.ts b/docs/manuscripts/microservice/microservice.sidebar.ts index 5dd7d2673..a9fa49634 100644 --- a/docs/manuscripts/microservice/microservice.sidebar.ts +++ b/docs/manuscripts/microservice/microservice.sidebar.ts @@ -32,7 +32,7 @@ export const microserviceSidebar = [ }, { text: '网关', - link: 'kong' + link: 'gateway.md' }, { text: 'ELK', diff --git a/docs/manuscripts/microservice/mq/kafka.md b/docs/manuscripts/microservice/mq/kafka.md index 96fba0ab7..b37f57365 100644 --- a/docs/manuscripts/microservice/mq/kafka.md +++ b/docs/manuscripts/microservice/mq/kafka.md @@ -1 +1,6 @@ +--- +title: Kafka +permalink: /manuscripts/microservice/mq/kafka.html +--- + # Kafka \ No newline at end of file diff --git a/docs/manuscripts/microservice/mq/rabbitmq.md b/docs/manuscripts/microservice/mq/rabbitmq.md index 63b005eb8..9a73a8f65 100644 --- a/docs/manuscripts/microservice/mq/rabbitmq.md +++ b/docs/manuscripts/microservice/mq/rabbitmq.md @@ -1 +1,6 @@ +--- +title: RabbitMQ +permalink: /manuscripts/microservice/mq/rabbitmq.html +--- + # RabbitMQ diff --git a/docs/manuscripts/microservice/mq/rocketmq.md b/docs/manuscripts/microservice/mq/rocketmq.md index 4f4a64fa4..481d70e16 100644 --- a/docs/manuscripts/microservice/mq/rocketmq.md +++ b/docs/manuscripts/microservice/mq/rocketmq.md @@ -1 +1,6 @@ +--- +title: RocketMQ +permalink: /manuscripts/microservice/mq/rocketmq.html +--- + # RocketMQ \ No newline at end of file diff --git a/docs/manuscripts/microservice/nacos.md b/docs/manuscripts/microservice/nacos.md new file mode 100644 index 000000000..ba8d84632 --- /dev/null +++ b/docs/manuscripts/microservice/nacos.md @@ -0,0 +1,5 @@ +--- +title: Nacos +permalink: /manuscripts/microservice/nacos.html +--- +# Nacos \ No newline at end of file diff --git a/docs/manuscripts/microservice/node-grpc.md b/docs/manuscripts/microservice/node-grpc.md index ee32dbd47..33446b1fd 100644 --- a/docs/manuscripts/microservice/node-grpc.md +++ b/docs/manuscripts/microservice/node-grpc.md @@ -1,12 +1,20 @@ +--- +title: Node-gRPC +permalink: /manuscripts/microservice/node-grpc.html +--- # Node下gRPC的简单使用 -# @142vip/egg-grpc-client +## @142vip/egg-grpc-client - 模块地址: ## @142vip/egg-grpc-server -- 模块地址: \ No newline at end of file +- 模块地址: + + + +## 参考资料 \ No newline at end of file diff --git a/docs/manuscripts/other/other.sidebar.ts b/docs/manuscripts/other/other.sidebar.ts deleted file mode 100644 index 54f9efaa5..000000000 --- a/docs/manuscripts/other/other.sidebar.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const OtherSidebar = [ - { - text: '公众号文章', - link: 'wechat-list.md' - }, - { - text: '常用网站', - link: 'frequent-site-link.md' - }, - { - text: '变更记录', - link: 'changelog.md' - }, - { - text: '网站动态', - link: 'big-event-history.md' - } -] diff --git a/docs/manuscripts/other/readme.md b/docs/manuscripts/other/readme.md deleted file mode 100644 index 940b98573..000000000 --- a/docs/manuscripts/other/readme.md +++ /dev/null @@ -1 +0,0 @@ -# 其他相关 \ No newline at end of file diff --git "a/docs/manuscripts/other/\345\205\254\344\274\227\345\217\267\346\226\207\347\253\240.md" "b/docs/manuscripts/other/\345\205\254\344\274\227\345\217\267\346\226\207\347\253\240.md" deleted file mode 100644 index c2e1deaba..000000000 --- "a/docs/manuscripts/other/\345\205\254\344\274\227\345\217\267\346\226\207\347\253\240.md" +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: 公众号文章 -permalink: /manuscripts/other/wechat-list.html ---- - -# 公众号文章 - diff --git "a/docs/manuscripts/read-books/cs-books/ES6\346\240\207\345\207\206\345\205\245\351\227\250.md" "b/docs/manuscripts/read-books/cs-books/ES6\346\240\207\345\207\206\345\205\245\351\227\250.md" new file mode 100644 index 000000000..856a2268b --- /dev/null +++ "b/docs/manuscripts/read-books/cs-books/ES6\346\240\207\345\207\206\345\205\245\351\227\250.md" @@ -0,0 +1,4973 @@ + +# ES6标准入门 + +## 简介 + +### ECMAScript VS JavaScript +>前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。 + + +### ECMAScript2015 VS ES6 + +>ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言” + +### Babel转码器 + +Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。 + +```js +// 转码前的箭头函数 +input.map(item=>item+1) + +// 转码后 +input.map(function (item){ + return item+1 +}) + +``` + +#### 安装Babel + +```bash +## 本地安装 +npm install --save-dev @babel/core +``` + +#### 配置文件.babelrc + +Babel 的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。 + +```json +{ + "presets": [], + "plugins": [] +} + +``` + +presets字段设定转码规则 + +```bash +# 最新转码规则 +$ npm install --save-dev @babel/preset-env + +# react 转码规则 +$ npm install --save-dev @babel/preset-react +``` +下载完成后,可以将规则键入到`.babelrc`文件中 + +```json +{ + "presets": [ + "@babel/env", + "@babel/preset-react" + ], + "plugins": [] +} +``` + +## let和const命令 + + +### let命令 +>ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,**只在let命令所在的代码块内有效。** + +```js + +// 函数内部定义变量 +function test(){ + let a=10 + var b=1 +} + +console.log(a) //输出报错,let块级作用域 + +console.log(b) // 输出1 + +``` +#### 不存在变量提升 + +> var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined + +let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。 + +```js +// var +console.log(test) // 输出undefined +var test=2 + +// let +console.log(err) // 输出ReferenceError错误 +let err=1 +``` + +#### 暂时性死区 + +>只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 + +ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,**从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错**。 + +> 代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。 + +```js +// 在let命令声明变量tmp之前,都属于变量tmp的“死区”。 +if (true) { + // TDZ开始 + tmp = 'abc'; // ReferenceError + console.log(tmp); // ReferenceError + + let tmp; // TDZ结束 + console.log(tmp); // undefined + + tmp = 123; + console.log(tmp); // 123 +} + +``` + +**ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。** + +总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。 + +#### 不允许重复声明 + +> let不允许在相同作用域内,重复声明同一个变量。 + +```js +// 报错 +function func() { + let a = 10; + var a = 1; +} + +// 报错 +function func() { + let a = 10; + let a = 1; +} + +``` + +当然这样写是不报错的,但不建议 +```js + +function func(arg) { + { + let arg; + } +} +func() // 不报错 +``` + +### 块级作用域 + +ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。 + +```js +// 循环结束后,变量i并没有消失,泄露成了全局变量。 +var s = 'hello'; + +for (var i = 0; i < s.length; i++) { + console.log(s[i]); +} + +console.log(i); // 5 +``` + +#### ES6的块级作用域 + +let为 JavaScript 新增了块级作用域。 + +ES6 允许块级作用域的任意嵌套。 + +```js +// 报错情况 +{ + { + { + let instance='test' + } + // 此时并没有变量名instance,输出会报错 + console.log(instance) + } +} + +// 正常情况 +{ + { + let instance='test' + { + // 与上面的instance互不影响 + let instance='test' + } + } +} +``` + +块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。 + +```js +// IIFE 写法 +(function () { + var tmp = ...; + ... +}()); + +// 块级作用域写法 +{ + let tmp = ...; + ... +} + +``` + +#### 块级作用域和函数声明 + +ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 + +```js +// 按照 ES5 的规定以下情况都是非法的。 +// 情况一 +if (true) { + function f(){ + + } +} + +// 情况二 +try { + function f(){ + + } +} catch(e) { + // ... +} +``` + +ES6 引入了块级作用域,明确允许在块级作用域之中声明函数 + +> ES6 规定,块级作用域之中,**函数声明语句的行为类似于let,在块级作用域之外不可引用。** + +```js +function f(){ + console.log('outside') +} + +(function(){ + if(false){ + // 重复声明函数f + function f(){ + console.log('inside') + } + } +}) + +// 运行会得到“inside”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。 +// ES5 环境 +function f() { + console.log('outside') + } + +(function () { + function f() { + console.log('inside') + } + if (false) { + + } + f(); +}()); +``` + +ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢? + +```js +// 浏览器的 ES6 环境 +function f() { + console.log('outside') +} + +(function () { + if (false) { + // 重复声明一次函数f + function f() { + console.log('inside') + } + } + f(); +}()); +// Uncaught TypeError: f is not a function + +``` +上面的代码在 ES6 浏览器中,都会报错。 + +原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。 + +允许在块级作用域内声明函数。 +函数声明类似于var,即会提升到全局作用域或函数作用域的头部。 +同时,函数声明还会提升到所在的块级作用域的头部。 +注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。 + +根据这三条规则,浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。上面的例子实际运行的代码如下。 + +```js +// 浏览器的 ES6 环境 +function f(){ + console.log('outside') +} +(function () { + var f = undefined; + if (false) { + function f(){ + console.log('inside') + } + } + // 执行函数 + f(); +}()); +// Uncaught TypeError: f is not a function +``` +考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。 + +```js +// 块级作用域内部的函数声明语句,建议不要使用 +{ + let a = 'secret'; + function f(){ + return a; + } +} + +// 块级作用域内部,优先使用函数表达式 +{ + let a = 'secret'; + let f = function(){ + return a; + }; +} +``` +另外,还有一个需要注意的地方。ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。 + +```js +// 第一种写法,报错 +if (true) let x = 1; + +// 第二种写法,不报错 +if (true) { + let x = 1; +} + +``` +上面代码中,第一种写法没有大括号,所以不存在块级作用域,而let只能出现在当前作用域的顶层,所以报错。第二种写法有大括号,所以块级作用域成立。 + +函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。 + +```js +// 不报错 +'use strict'; +if (true) { + function f(){ + + } +} + +// 报错 +'use strict'; +if (true) + function f(){} + +``` + + +### const命令 + +**const声明一个只读的常量。一旦声明,常量的值就不能改变。** + + +```js + +const test=2323 + +console.log(test) // 输出:2323 + +// 重新赋值会报错:Assignment to constant variable + +test=4567 + +``` +**const声明的变量不得改变值**,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。 + +```js + +// 只声明、不赋值会报错 +const test; + +``` +**const的作用域与let命令相同:只在声明所在的块级作用域内有效。** + +```js +if(true){ + const max=5 +} + +// 输出报错:max is not defined +console.log(max) + +``` + +const命令声明的常量也是不提升的,同样存在暂时性死区,**只能在声明的位置后面使用** + +```js +// 存在暂时性死区 +if(true){ + // 调用报错 + console.log(max) + const max=34; +} + +``` + +**const声明的常量,也与let一样不可重复声明。** + +```js +var student='tom' +let gender='girl' + +// 已声明的变量,重复声明会报错 +const message='go go go' + +const gender='boy' + +``` + + +**const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。** + +对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心 + +```js +// 定义对象 +const student={} + +// 添加属性 +student.age=18 + +// 正常输出 18 +console.log(student.age) + +// 此时指针地址发生了变化,报错 +student={} + +``` + +> 常量student储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把student指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性 + + +对象值确保不变,可以考虑使用`Object.freeze()`函数将其冻结 + +```js + +const student=Object.freeze({}) + +// 常规模式: 赋值不起作用 +// 严格模式: 报错 +student.age=18 + +``` + +当然,出了冻结对象本身,对象可能存在的属性也需要冻结 + +```js +// 冻结对象和属性 +function objectConstant(obj){ + // 冻结对象 + Object.freeze(obj) + // 冻结属性 + Object.keys(obj).forEach((key,index)=>{ + // 属性值为对象 + if(typeof obj[key]==='object'){ + // 递归调用冻结方法 + constantize(obj[key]) + } + }) +} +``` + +### ES6声明变量的6中方法 + +- var定义 +- function命令 +- let +- const +- import +- class + +### globalThis 对象 + + +JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。 + +- 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。 + +- 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。 + +- 在Node 里面,顶层对象是global,但其他环境都不支持。 + +同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。 + +- 全局环境中,this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined。 +- 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。 +- 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。 + + + +**很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。** +```js +// 方法一 +(typeof window !== 'undefined' + ? window + : (typeof process === 'object' && + typeof require === 'function' && + typeof global === 'object') + ? global + : this); + +// 方法二 +var getGlobal = function () { + if (typeof self !== 'undefined') { + return self; + } + if (typeof window !== 'undefined') { + return window; + } + if (typeof global !== 'undefined') { + return global; + } + throw new Error('unable to locate global object'); +}; +``` + +## 解构赋值 + +### 数组 + +#### 基本用法 + +>ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。 + +```js +// 变量赋值 +let a=1; +let b=2; +let c=3; + +// ES6中可以从数组中提取值,按照对应位置,对变量赋值。: + +let [a,b,c]=[1,2,3] + +``` + +本质上,这种写法属于“**模式匹配**”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。 + +```js +// 嵌套解构 +let [foo, [[bar], baz]] = [1, [[2], 3]]; +foo // 1 +bar // 2 +baz // 3 + +let [ , , third] = ["foo", "bar", "baz"]; +console.log(third) // "baz" + + +let [x, , y] = [1, 2, 3]; +console.log(x) // 1 +console.log(y) // 3 + +let [head, ...tail] = [1, 2, 3, 4]; +console.log(head) // 1 +console.log(tail) // [2, 3, 4] + +let [x, y, ...z] = ['a']; +console.log(x) // "a" +console.log(y) // undefined +console.log(z) // [] + +``` +**解构不成功,变量值等于undefined** + + +```js +// 不完全解构, 只匹配部分 +let [x, y] = [1, 2, 3]; +x // 1 +y // 2 + +let [a, [b], d] = [1, [2, 3], 4]; +a // 1 +b // 2 +d // 4 + +``` + +如果等号的右边不是数组(正确的说:不属于可以遍历的结构),就会报错 + +```js +// 解构时会报错 +let [foo] = 1; +let [foo] = false; +let [foo] = NaN; +let [foo] = undefined; +let [foo] = null; +let [foo] = {}; + +// 因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式), +// 要么本身就不具备 Iterator 接口(最后一个表达式)。 +``` + +Set结构的数据明显存在递归迭代、遍历的接口,也是可以使用数组的解构赋值的 + +```js +let [x, y, z] = new Set(['a', 'b', 'c']); +x // "a" +``` + +**只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值** + + +#### 默认值 + +解构赋值允许指定默认值。 + +```js +let [foo = true] = []; +foo // true + +let [x, y = 'b'] = ['a']; // x='a', y='b' +let [x, y = 'b'] = ['a', undefined]; // x='a', y='b' + +``` +**ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。** + +```js +// undefined情况 +let [x = 1] = [undefined]; +x // 1 + +// null情况 +let [x = 1] = [null]; +x // null +``` + +**如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。** + +```js + +// 定义函数 +function f() { + console.log('aaa'); +} + +// 解构赋值 +let [x = f()] = [1]; + +``` +此时x明显可以拿到值,所以函数f()是不会执行的。 + +```js + +let x; + +// 数组[1]中的第一个元素,不严格等于undefined的时候,才会解构成功 +if([1][0]===undefined){ + x=f(); +}else{ + x=[1][0] +} + +``` + +默认值可以引用解构赋值的其他变量,但该变量必须已经声明 + +```js +let [x = 1, y = x] = []; // x=1; y=1 +let [x = 1, y = x] = [2]; // x=2; y=2 +let [x = 1, y = x] = [1, 2]; // x=1; y=2 + +// 变量y没有声明 +let [x = y, y = 1] = []; // ReferenceError: y is not defined + +``` + + +### 对象 + +同样,解构赋值可以适用数组,也可以适用于对象 + +```js +let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; +foo // "aaa" +bar // "bbb" +``` + +**数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。** + +```js +let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; +foo // "aaa" +bar // "bbb" + +// 变量没有对应的同名属性,导致取不到值,最后等于undefined。 +let { baz } = { foo: 'aaa', bar: 'bbb' }; +baz // undefined + +``` + +如果变量名与属性名不一致 + +```js +let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; +baz // "aaa" + +let obj = { first: 'hello', last: 'world' }; +let { first: f, last: l } = obj; +f // 'hello' +l // 'world' +``` + +```js +// 对象的解构赋值是下面形式的简写 +let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' }; + +// 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者 + +// 前者为:匹配的模式,后者为变量 +``` + +**与数组一样,解构也可以用于嵌套结构的对象** + +```js +let obj = { + p: [ + 'Hello', + { y: 'World' } + ] +}; + +let { p: [x, { y }] } = obj; +x // "Hello" +y // "World" + +``` +这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。 + +```js +let obj = { + p: [ + 'Hello', + { y: 'World' } + ] +}; + +// 此时p作为了变量进行赋值 +let { p, p: [x, { y }] } = obj; +x // "Hello" +y // "World" +p // ["Hello", {y: "World"}] + +``` + +#### 默认值 + +同样,对象的解构也是可以指定默认值的 + +```js +var {x = 3} = {}; +x // 3 + +var {x, y = 5} = {x: 1}; +x // 1 +y // 5 + +var {x: y = 3} = {}; +y // 3 + +var {x: y = 3} = {x: 5}; +y // 5 + +var { message: msg = 'Something went wrong' } = {}; +msg // "Something went wrong" + +``` + +**和数组的解构赋值一样,默认值生效的条件是,对象的属性值严格等于undefined** + +```js + +let {x = 3} ={x:undefined} +x //3 + +// 属性x等于null,因为null与undefined不严格相等,所以是个有效的赋值,导致默认值3不会生效。 +let {x = 3} = {x: null}; +x // null + +``` + +需要注意: + +- 如果要将一个已经声明的变量用于解构赋值,必须非常小心。 + +```js + +// 错误的写法 +let x; +{x} = {x: 1}; +// SyntaxError: syntax error + +// JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。 + +// 正确的写法 +let x; +({x} = {x: 1}); +x //1 +``` + +- 解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。 + +```js +// 表达式虽然毫无意义,但是语法是合法的,可以执行 +({} = [true, false]); +({} = 'abc'); +({} = []); +``` + + +- 由于**数组本质是特殊的对象**,因此可以对数组进行对象属性的解构 + +```js +// 注意将数组理解为特殊的对象 +let arr = [1, 2, 3]; +let {0 : first, [arr.length - 1] : last} = arr; +first // 1 +last // 3 +``` + + + +### 字符串 + +**字符串也可以解构赋值**。这是因为此时,字符串被转换成了一个类似数组的对象。 + +```js +const [a, b, c, d, e] = 'hello'; +a // "h" +b // "e" +c // "l" +d // "l" +e // "o" + +``` + +类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。 + +```js +// length长度属性 +let {length : len} = 'hello'; +len // 5 +``` + + +### 数值和布尔值 + +**解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。** + +```js +// 数值和布尔值的包装对象都有toString属性 +let {toString: s} = 123; +s === Number.prototype.toString // true + +let {toString: s} = true; +s === Boolean.prototype.toString // true + +``` + +解构赋值的规则是,**只要等号右边的值不是对象或数组,就先将其转为对象**。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。 + +```js + +// undefined和null无法转为对象 +let { prop: x } = undefined; // TypeError +let { prop: y } = null; // TypeError + +``` + +### 函数参数 + +函数也是可以使用解构赋值的 + +```js +function add([x, y]){ + return x + y; +} + +add([1, 2]); // 3 + +``` +函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。 + +```js +[[1, 2], [3, 4]].map(([a, b]) => a + b); +// [ 3, 7 ] + +// undefined就会触发函数参数的默认值。 +[1, undefined, 3].map((x = 'yes') => x); +// [ 1, 'yes', 3 ] + +``` + +#### 圆括号问题 + +**解构赋值虽然很方便,但是解析起来并不容易**对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。 + +由此带来的问题是,如果模式中出现圆括号怎么处理。ES6 的规则是,**只要有可能导致解构的歧义,就不得使用圆括号**。 + +但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。 + +##### 不能使用圆括号的情况 + +- 变量声明语句 + +```js +// 全部报错 都是变量声明语句,模式不能使用圆括号。 +let [(a)] = [1]; + +let {x: (c)} = {}; +let ({x: c}) = {}; +let {(x: c)} = {}; +let {(x): c} = {}; + +let { o: ({ p: p }) } = { o: { p: 2 } }; + +``` + +- 函数参数 + +```js +// 函数参数也属于变量声明,因此不能带有圆括号。 +// 报错 +function f([(z)]) { return z; } +// 报错 +function f([z,(x)]) { return x; } + + +``` + +- 赋值语句的模式 + +```js +// 全部报错 整个模式都放在圆括号之中 +({ p: a }) = { p: 42 }; +([a]) = [5]; + +// 报错 一部分模式放在圆括号之中 +[({ p: a }), { x: c }] = [{}, {}]; + +``` + + +##### 可以使用圆括号的情况 + +**赋值语句的非模式部分,可以使用圆括号** + +```js +// 都是赋值语句,而不是声明语句 +// 圆括号都不属于模式的一部分 +[(b)] = [3]; // 正确 +({ p: (d) } = {}); // 正确 +[(parseInt.prop)] = [3]; // 正确 + +``` + +### 实际用途 + +- 交换变量的值 + +> 这里简单易读,语义非常清晰 + +```js +let x=1; +let y=2; + +// 两值交换 +[x,y]=[y,x] + +``` + +- 从函数返回多个值 + +> 函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回 + +```js +// 返回一个数组 +function example() { + return [1, 2, 3]; +} + +// 解构 +let [a, b, c] = example(); + +// 返回一个对象 +function example() { + return { + foo: 1, + bar: 2 + }; +} +// 解构 +let { foo, bar } = example(); + +``` + +- 函数参数的定义 + +> 解构赋值可以方便地将一组参数与变量名对应起来。 + +```js +// 参数是一组有次序的值 +function f([x, y, z]) { + ... +} +// 调用 +f([1, 2, 3]); + +// 参数是一组无次序的值 +function f({x, y, z}) { + ... +} + +// 调用 +f({z: 3, y: 2, x: 1}); + +``` + +- 提取 JSON 数据 + +> 解构赋值对提取 JSON 对象中的数据,尤其有用。 + +```js +// 定义数据 +let jsonData = { + id: 42, + status: "OK", + data: [867, 5309] +}; + +// 解构 +let { id, status, data: number } = jsonData; + +console.log(id, status, number); +// 42, "OK", [867, 5309] + +``` + +- 函数参数的默认值 + +```js +jQuery.ajax = function (url, { + async = true, + beforeSend = function () {}, + cache = true, + complete = function () {}, + crossDomain = false, + global = true, + // ... more config +} = {}) { + // ... do stuff +}; + +``` + +避免了在函数体内部再写`var foo = config.foo || 'default foo'`;这样的语句。 + +- 遍历 Map 结构 + +> 任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。 + +```js + +const map = new Map(); +map.set('first', 'hello'); +map.set('second', 'world'); + +for (let [key, value] of map) { + console.log(key + " is " + value); +} +// first is hello +// second is world + +// 获取键名 +for (let [key] of map) { + // ... +} + +// 获取键值 注意此处的逗号 +for (let [,value] of map) { + // ... +} + +``` + + +- 输入模块的指定方法 + +> 加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。 + +```js + +// CommonJs写法 +const { SourceMapConsumer, SourceNode } = require("source-map"); + +``` + + +## 字符串 + + +### 遍历器接口 + +ES6 为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历。 + +```js +// of遍历 依次输出 +for (let codePoint of 'foo') { + console.log(codePoint) +} + +``` + +### 模板字符串 + +传统的 JavaScript 语言,输出模板通常采用`+`拼接 + +```js +// jquery 输出模板 +$('#result').append( + 'There are ' + basket.count + ' ' + + 'items in your basket, ' + + '' + basket.onSale + + ' are on sale!' +); +``` +非常明显,写法相当繁琐且不方便,我最开始写的时候,真的'和"傻傻分不清楚,总觉得多了一个或者少了一个; + +```js +// ES6模板字符串 +$('#result').append(` + There are ${basket.count} items + in your basket, ${basket.onSale} + are on sale! +`); + +``` +**模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。** + +```js +// 普通字符串 +`In JavaScript '\n' is a line-feed.` + +// 多行字符串 +`In JavaScript this is + not legal.` + +console.log(`string text line 1 +string text line 2`); + +// 字符串中嵌入变量 +let name = "Bob", time = "today"; +`Hello ${name}, how are you ${time}?` + +``` +都是用反引号表示。**如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。** + +```js +let greeting = `\`Yo\` World!`; +``` + +如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。模板字符串的空格和换行,都是被保留的,如果不想要这个换行,可以使用trim方法消除它。 +- trim() 方法用于删除字符串的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等。 + +- trim() 方法不会改变原始字符串。 + +- trim() 方法不适用于 null, undefined, Number 类型。 + +**模板字符串中嵌入变量,需要将变量名写在${}之中。** + + +```js +// 方式比较 +function authorize(user, action) { + if (!user.hasPrivilege(action)) { + throw new Error( + // 传统写法: + // 'User ' + // + user.name + // + ' is not authorized to do ' + // + action + // + '.' + + // ES6模板语法 + `User ${user.name} is not authorized to do ${action}.`); + } +} + +``` + +**大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。** + + +```js +let x = 1; +let y = 2; + +`${x} + ${y} = ${x + y}` +// "1 + 2 = 3" + +`${x} + ${y * 2} = ${x + y * 2}` +// "1 + 4 = 5" + +// 定义对象 +let obj = {x: 1, y: 2}; +// 运算 +`${obj.x + obj.y}` +// "3" + +``` + +在模板字符串中也是可以调用函数的: + +```js +function fn() { + return "Hello World"; +} + +// 调用函数 +`foo ${fn()} bar` +// foo Hello World bar + +``` + +**如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。** + +```js +// 变量place没有声明,报错 +let msg = `Hello, ${place}`; + +``` + +由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串,将会原样输出。 + +```js +// 输出:"Hello World" +`Hello ${'World'}` +``` + +如果需要引用模板字符串本身,在需要时执行,可以写成函数。 + +```js +// 函数定义,箭头函数 +let func = (name) => `Hello ${name}!`; + +// 执行 +func('Jack') +// "Hello Jack!" +``` +模板字符串写成了一个函数的返回值。执行这个函数,就相当于执行这个模板字符串了。 + + + + + +### 新增方法 + +- String.fromCodePoint() +- **String.raw()** +- codePointAt() +- normalize() +- **includes()、startsWith()、endsWith()** +- **repeat()** +- **padStart()、padEnd()** +- **trimStart()、trimEnd()** +- **matchAll()** +- **replaceAll()** + +### String.fromCodePoint() + +ES5 提供`String.fromCharCode()`方法,用于从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于`0xFFFF`的字符 + +**ES6 提供了String.fromCodePoint()方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode()方法的不足。在作用上,正好与下面的codePointAt()方法相反。** + +```js + +String.fromCodePoint(0x20BB7) +// 输出: "𠮷" +String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' +// 输出: true + +``` +**如果String.fromCodePoint方法有多个参数,则它们会被合并成一个字符串返回。** + +注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。 + + +### String.raw() + +raw方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。 + +```js +String.raw`Hi\n${2+3}!` +// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!" + +String.raw`Hi\u000A!`; +// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!" +``` +**如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义** + +```js +String.raw`Hi\\n` +// 返回 "Hi\\\\n" + +String.raw`Hi\\n` === "Hi\\\\n" // true + +``` + + +- String.raw()方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。 + +- String.raw()本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组,对应模板字符串解析后的值。 + +```js +// `foo${1 + 2}bar` +// 等同于 +String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar" + +``` + +**String.raw()方法的第一个参数是一个对象,它的raw属性等同于原始的模板字符串解析后得到的数组。** + +作为函数,String.raw()的代码实现: + +```js +// 定义函数,绑定到raw属性上 +String.raw = function (strings, ...values) { + let output = ''; + let index; + for (index = 0; index < values.length; index++) { + output += strings.raw[index] + values[index]; + } + + // 递归 + output += strings.raw[index] + return output; +} + +``` + +### codePointAt() + +JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为它们是两个字符。 + +```js +let s = "𠮷"; + +s.length // 2 +s.charAt(0) // '' +s.charAt(1) // '' +s.charCodeAt(0) // 55362 +s.charCodeAt(1) // 57271 + +``` + +ES6 提供了codePointAt()方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。 + +```js +let s = '𠮷a'; + +s.codePointAt(0) // 134071 +s.codePointAt(1) // 57271 + +s.codePointAt(2) // 97 + +``` + +**codePointAt()方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。** + +```js +function is32Bit(c) { + return c.codePointAt(0) > 0xFFFF; +} + +is32Bit("𠮷") // true +is32Bit("a") // false +``` + +### normalize() + +ES6 提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。 + +```js +'\u01D1'.normalize() === '\u004F\u030C'.normalize() +// true + +``` + +normalize方法可以接受一个参数来指定normalize的方式,参数的四个可选值如下。 + +- NFC,默认参数,表示“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。 +- NFD,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。 +- NFKC,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文。) +- NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。 + + + +### includes(), startsWith(), endsWith() + +传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中; + +ES6 又提供了三种新方法: + +- includes() 返回布尔值,表示是否找到了参数字符串。 + +- startsWith() 返回布尔值,表示参数字符串是否在原字符串的头部。 + +- endsWith() 返回布尔值,表示参数字符串是否在原字符串的尾部。 + +例如: + +```js + +let s = 'Hello world!'; + +s.startsWith('Hello') // true +s.endsWith('!') // true +s.includes('o') // true + +``` + +**这三个方法都支持第二个参数,表示开始搜索的位置。** + +```js +let s = 'Hello world!'; + +s.startsWith('world', 6) // true +s.endsWith('Hello', 5) // true +s.includes('Hello', 6) // false + +``` + +**使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。** + + +### repeat() + +repeat方法返回一个新字符串,表示将原字符串重复n次。 + +```js + +'x'.repeat(3) // "xxx" +'hello'.repeat(2) // "hellohello" +'na'.repeat(0) // "" + +``` +- 参数如果是小数,会被取整(向下取整) + +```js +'test'.repeat(2.9) // "testtest" +``` + +- 如果repeat的参数是负数或者Infinity,会报错。 + +```js +// Infinity 无穷 +'na'.repeat(Infinity) +// RangeError +'na'.repeat(-1) +// RangeError +``` + +- **如果参数是 0 到-1 之间的小数,则等同于 0**,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。 + +```js +'na'.repeat(-0.9) // "" + +// 参数NaN等同于 0。 +'na'.repeat(NaN) // "" + +``` + +- 如果repeat的参数是字符串,则会先转换成数字。 + +```js + +'na'.repeat('na') // "" +'na'.repeat('3') // "nanana" + +``` + +### padStart()、padEnd() + +ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。 +- padStart()用于头部补全 +- padEnd()用于尾部补全 + +```js +// 头部补齐 +'x'.padStart(5, 'ab') // 'ababx' +'x'.padStart(4, 'ab') // 'abax' + +// 尾部补齐 +'x'.padEnd(5, 'ab') // 'xabab' +'x'.padEnd(4, 'ab') // 'xaba' + +``` + +padStart()和padEnd()一共接受两个参数: +- 第一个参数是字符串补全生效的最大长度 + +- 第二个参数是用来补全的字符串。 + +在实际使用过程中,会存在如下情况: + +- **如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串** + +```js +'xxx'.padStart(2, 'ab') // 'xxx' +'xxx'.padEnd(2, 'ab') // 'xxx + +``` + +- **如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。** + +```js +'abc'.padStart(10, '0123456789') +// '0123456abc' +``` + +- **如果省略第二个参数,默认使用空格补全长度。** + +```js + +'x'.padStart(4) // ' x' +'x'.padEnd(4) // 'x ' +``` + + +**padStart()的常见用途是为数值补全指定位数** ,下面代码生成 10 位的数值字符串。 + +```js +'1'.padStart(10, '0') // "0000000001" +'12'.padStart(10, '0') // "0000000012" +'123456'.padStart(10, '0') // "0000123456" +``` + +另一个用途是提示字符串格式。 + +```js +'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" +'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12 + +``` + +### trimStart()、trimEnd() + +ES2019 对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致。 +- trimStart()消除字符串头部的空格 +- trimEnd()消除尾部的空格 + +它们返回的都是新字符串,不会修改原始字符串。 + +```js +// 定义 +const s = ' abc '; + +s.trim() // "abc" +s.trimStart() // "abc " +s.trimEnd() // " abc + +// 原始字符串不变 +console.log(s) // " abc " + +``` + +**除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。** + +浏览器还部署了额外的两个方法: + +- trimLeft()是trimStart()的别名 +- trimRight()是trimEnd()的别名 + + +### matchAll() + +matchAll()方法返回一个正则表达式在当前字符串的**所有匹配** + + +### replaceAll() + +**字符串的实例方法replace()只能替换第一个匹配。** + +```js +'aabbcc'.replace('b', '_') +// 'aa_bcc + +``` + +如果要替换所有的匹配,不得不使用正则表达式的g修饰符。 + +```js +// 全部匹配 +'aabbcc'.replace(/b/g, '_') +// 'aa__cc' +``` + +正则表达式毕竟不是那么方便和直观,**ES2021 引入了replaceAll()方法**,可以一次性替换所有匹配。 + +```js + +// 全局匹配 +'aabbcc'.replaceAll('b','_') +// 'aa__cc' + +``` +用法与replace()相同,返回一个新字符串,不会改变原字符串。 + +```js +String.prototype.replaceAll(searchValue, replacement) + +``` +**searchValue是搜索模式,可以是一个字符串,也可以是一个全局的正则表达式(带有g修饰符)。如果searchValue是一个不带有g修饰符的正则表达式,replaceAll()会报错。与replace()不同。** + +```js +// 不报错 +'aabbcc'.replace(/b/, '_') + +// /b/不带有g修饰符,会导致replaceAll()报错。 +'aabbcc'.replaceAll(/b/, '_') +``` + +**replaceAll()的第二个参数replacement是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。** + +- $&:匹配的子字符串。 +- $`:匹配结果前面的文本。 +- $':匹配结果后面的文本。 +- $n:匹配成功的第n组内容,n是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。 +- $$:指代美元符号$。 + +```js +// $& 表示匹配的字符串,即`b`本身 +// 所以返回结果与原字符串一致 +'abbc'.replaceAll('b', '$&') +// 'abbc' + +// $` 表示匹配结果之前的字符串 +// 对于第一个`b`,$` 指代`a` +// 对于第二个`b`,$` 指代`ab` +'abbc'.replaceAll('b', '$`') +// 'aaabc' + +// $' 表示匹配结果之后的字符串 +// 对于第一个`b`,$' 指代`bc` +// 对于第二个`b`,$' 指代`c` +'abbc'.replaceAll('b', `$'`) +// 'abccc' + +// $1 表示正则表达式的第一个组匹配,指代`ab` +// $2 表示正则表达式的第二个组匹配,指代`bc` +'abbc'.replaceAll(/(ab)(bc)/g, '$2$1') +// 'bcab' + +// $$ 指代 $ +'abc'.replaceAll('b', '$$') +// 'a$c' + +``` + +**replaceAll()的第二个参数replacement也可以是一个函数,该函数的返回值将替换掉第一个参数searchValue匹配的文本。** + +```js +// 第二个参数是一个函数,该函数的返回值会替换掉所有b的匹配。 +'aabbcc'.replaceAll('b', () => '_') +// 'aa__cc' + +``` + +**这个替换函数可以接受多个参数** + +- 第一个参数是捕捉到的匹配内容 +- 第二个参数捕捉到是组匹配(有多少个组匹配,就有多少个对应的参数) +- 最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置,最后一个参数是原字符串。 + +```js +const str = '123abc456'; +const regex = /(\d+)([a-z]+)(\d+)/g; + +function replacer(match, p1, p2, p3, offset, string) { + return [p1, p2, p3].join(' - '); +} + +str.replaceAll(regex, replacer) +// 123 - abc - 456 + + +``` +上面例子中,正则表达式有三个组匹配,所以replacer()函数的第一个参数match是捕捉到的匹配内容(即字符串123abc456),后面三个参数p1、p2、p3则依次为三个组匹配。 + + + + +## 数值 + + +### Number.isFinite() VS Number.isNaN() + +ES6 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法 + + +`Number.isFinite()`用来检查一个数值是否为有限的(`finite`),即不是Infinity。 + + +```js +Number.isFinite(15); // true +Number.isFinite(0.8); // true +Number.isFinite(NaN); // false +Number.isFinite(Infinity); // false +Number.isFinite(-Infinity); // false +Number.isFinite('foo'); // false +Number.isFinite('15'); // false +Number.isFinite(true); // false +``` + +**如果参数类型不是数值,Number.isFinite一律返回false** + +`Number.isNaN()`用来检查一个值是否为`NaN`(Not A Number)。 + +```js +Number.isNaN(NaN) // true +Number.isNaN(15) // false +Number.isNaN('15') // false +Number.isNaN(true) // false +Number.isNaN(9/NaN) // true +Number.isNaN('true' / 0) // true +Number.isNaN('true' / 'true') // true +``` + +如果参数类型不是`NaN`,`Number.isNaN`一律返回`false`。 + +#### 重要区别 + +> 与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。 + + +```js + +isFinite(25) // true +isFinite("25") // true +Number.isFinite(25) // true +Number.isFinite("25") // false + +isNaN(NaN) // true +isNaN("NaN") // true +Number.isNaN(NaN) // true +Number.isNaN("NaN") // false +Number.isNaN(1) // false + +``` + + +### Number.parseInt() VS Number.parseFloat() + +ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。 + +```js +// ES5的写法 +parseInt('12.34') // 12 +parseFloat('123.45#') // 123.45 + +// ES6的写法 +Number.parseInt('12.34') // 12 +Number.parseFloat('123.45#') // 123.45 +``` + + +**逐步减少全局性方法,使得语言逐步模块化。** + +```js +Number.parseInt === parseInt // true +Number.parseFloat === parseFloat // true +``` + +### Number.isInteger() + +`Number.isInteger()`用来判断一个数值是否为整数。 + +```js +Number.isInteger(25) // true +Number.isInteger(25.1) // false + +// 整数和浮点数采用的是同样的储存方法 +Number.isInteger(25) // true +Number.isInteger(25.0) // true +``` + +**JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。** + +如果参数不是数值,`Number.isInteger`返回`false`。 + +```js +Number.isInteger() // false +Number.isInteger(null) // false +Number.isInteger('15') // false +Number.isInteger(true) // false +``` + +### Math 对象的扩展 + +#### Math.trunc() + +`Math.trunc()`方法用于去除一个数的小数部分,返回整数部分 + +```js +Math.trunc(4.1) // 4 +Math.trunc(4.9) // 4 +Math.trunc(-4.1) // -4 +Math.trunc(-4.9) // -4 +Math.trunc(-0.1234) // -0 +``` + +对于非数值,`Math.trunc`内部使用`Number`方法将其先转为数值 + +```js +Math.trunc('123.456') // 123 +Math.trunc(true) //1 +Math.trunc(false) // 0 +Math.trunc(null) // 0 +``` + +对于空值和无法截取整数的值,返回`NaN`。 + +```js +Math.trunc(NaN); // NaN +Math.trunc('foo'); // NaN +Math.trunc(); // NaN +Math.trunc(undefined) // NaN +``` + +`Math.trunc()`的类似实现: + +```js +Math.trunc = Math.trunc || function(x) { + return x < 0 ? Math.ceil(x) : Math.floor(x); +}; + +``` + + +#### Math.sign() + +Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。 + +- 参数为正数,返回+1; +- 参数为负数,返回-1; +- 参数为 0,返回0; +- 参数为-0,返回-0; +- 其他值,返回NaN。 + + +如果参数是非数值,会自动转为数值。对于那些无法转为数值的值,会返回NaN。 + +```js +Math.sign('') // 0 +Math.sign(true) // +1 +Math.sign(false) // 0 +Math.sign(null) // 0 +Math.sign('9') // +1 +Math.sign('foo') // NaN +Math.sign() // NaN +Math.sign(undefined) // NaN +``` + +Math.sign()的类似实现: + +```js +// 判断正数、负数、还是零 +Math.sign = Math.sign || function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; +}; +``` + + +#### Math.cbrt() + +Math.cbrt()方法用于计算一个数的立方根。 + +```js +Math.cbrt(-1) // -1 +Math.cbrt(0) // 0 +Math.cbrt(1) // 1 +Math.cbrt(2) // 1.2599210498948732 +``` + +对于非数值,Math.cbrt()方法内部也是先使用Number()方法将其转为数值。 + +```js +Math.cbrt('8') // 2 +Math.cbrt('hello') // NaN +``` + +`Math.cbrt()`类似实现: + +```js +// 计算一个数的立方根 +Math.cbrt = Math.cbrt || function(x) { + var y = Math.pow(Math.abs(x), 1/3); + return x < 0 ? -y : y; +}; +``` + + +#### Math.hypot() +Math.hypot方法返回所有参数的平方和的平方根。 + +```js + +// 3 的平方加上 4 的平方,等于 5 的平方。 +Math.hypot(3, 4); // 5 +Math.hypot(3, 4, 5); // 7.0710678118654755 +Math.hypot(); // 0 +Math.hypot(NaN); // NaN +Math.hypot(3, 4, 'foo'); // NaN +Math.hypot(3, 4, '5'); // 7.0710678118654755 +Math.hypot(-3); // 3 +``` + +如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。 + + +#### 指数运算符 +ES2016 新增了一个指数运算符(**)。 + +```js +2 ** 2 // 4 +2 ** 3 // 8 +``` + +**这个运算符是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。** + +```js + +// 首先计算的是第二个指数运算符,而不是第一个 +// 相当于 2 ** (3 ** 2) +2 ** 3 ** 2 +// 512 +``` + +指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。 + +```js +let a = 1.5; +a **= 2; +// 等同于 a = a * a; + +let b = 4; +b **= 3; +// 等同于 b = b * b * b; +``` + +## 函数 + +### 参数的默认值 + +ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 + +```js +function log(x, y) { + y = y || 'World'; + console.log(x, y); +} + +log('Hello') // Hello World +log('Hello', 'China') // Hello China +log('Hello', '') // Hello World + +// ES6中可以 +function log(x, y = 'World') { + console.log(x, y); +} + +log('Hello') // Hello World +log('Hello', 'China') // Hello China +log('Hello', '') // Hello + +``` + +通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。 + +```js + +if (typeof y === 'undefined') { + y = 'World'; +} + +``` + +**参数变量是默认声明的,不能用let或const再次声明,否则会报错。** + + +使用参数默认值时,函数不能有同名参数 + +```js +// 不报错 +function test(x, x, y) { + // ... +} + +// 函数同名报错 +function test(x, x, y = 1) { + // ... +} +``` + +另外,参数默认值不是传值的,而是每次都重新计算默认值表达式的值。**也就是说,参数默认值是惰性求值的。** + + +```js +let x = 99; +function add(p = x + 1) { + console.log(p); +} + +add() // 100 + +// 修改变量值 +x = 100; +add() // 101 +``` + +注意:默认p不是等于100 + +#### 与解构赋值默认值结合使用 + +参数默认值可以与解构赋值的默认值,结合起来使用。 + +```js +function add({x, y = 5}) { + console.log(x, y); +} + +add({}) // undefined 5 +add({x: 1}) // 1 5 +add({x: 1, y: 2}) // 1 2 +add() // TypeError: Cannot read property 'x' of undefined +``` + +如果函数`add`调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。 + +```js + +// 提供默认值进行解构 +function add({x, y = 5} = {}) { + console.log(x, y); +} + +add() // undefined 5 + +``` + + +#### 参数默认值的位置 + +通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。**如果非尾部的参数设置默认值,实际上这个参数是没法省略的。** + +```js +// 存在默认值不是尾参数 +function test(x = 1, y) { + return [x, y]; +} + +test() // [1, undefined] +test(2) // [2, undefined] +test(, 1) // 报错 +test(undefined, 1) // [1, 1] + +``` + +显式输入`undefined`,配合解构的原理,可以省略有默认值的参数 + +**如果传入`undefined`,将触发该参数等于默认值,`null`则没有触发默认值。** + +```js +function test(x = 5, y = 6) { + console.log(x, y); +} + +test(undefined, null) +// 5 null +``` + +### length 属性 + +指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。 + +```js + +(function (a) {}).length // 1 +(function (a = 5) {}).length // 0 +(function (a, b, c = 5) {}).length // 2 + +``` + +`length`属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了 `3` 个参数,其中有一个参数c指定了默认值,因此`length`属性等于`3`减去`1`,最后得到`2` + +```js +(function(...args) {}).length // 0 +``` + +`length`属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。 + +**如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。** + +```js +(function (a = 0, b, c) {}).length // 0 +(function (a, b = 1, c) {}).length // 1 +``` + +#### 作用域 + +一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。**这种语法行为,在不设置参数默认值时,是不会出现的。** + +```js +var x = 1; + +function add(x, y = x) { + // 默认值变量x指向第一个参数x,而不是全局变量x + console.log(y); +} + +add(2) // 2 +``` + +上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。 + +```js +let x = 1; + +function f(y = x) { + let x = 2; + console.log(y); +} + +f() // 1 +``` + +上面代码中,函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。 + +```js + +// 此时全局变量x不存在,就会报错。 +function f(y = x) { + let x = 2; + console.log(y); +} + +f() // ReferenceError: x is not defined +``` + +上面代码中,参数`x = x`形成一个单独作用域。实际执行的是`let x = x`,由于暂时性死区的原因,这行代码会报错`x未定义`。 + +**如果参数的默认值是一个函数,该函数的作用域也遵守这个规则** + +```js +let foo = 'outer'; + +function bar(func = () => foo) { + let foo = 'inner'; + console.log(func()); +} + +bar(); // outer +``` + +上面代码中,函数`bar`的参数`func`的默认值是一个匿名函数,返回值为变量`foo`。函数参数形成的单独作用域里面,并没有定义变量`foo`,所以`foo`指向外层的全局变量`foo`,因此输出`outer`。 + +```js + +function bar(func = () => foo) { + let foo = 'inner'; + console.log(func()); +} + +bar() // ReferenceError: foo is not defined + +``` + +上面代码中,匿名函数里面的`foo`指向函数外层,但是函数外层并没有声明变量`foo`,所以就报错了。 + + +```js +var x = 1; +function foo(x, y = function() { x = 2; }) { + var x = 3; + y(); + console.log(x); +} + +foo() // 3 +x // 1 + +``` + +上面代码中,函数`foo`的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量`y`,`y`的默认值是一个匿名函数。这个匿名函数内部的变量`x`,指向同一个作用域的第一个参数x。函数`foo`内部又声明了一个内部变量`x`,该变量与第一个参数`x`由于不是同一个作用域,所以不是同一个变量,因此执行`y`后,内部变量`x`和外部全局变量x的值都没变。 + +```js +var x = 1; +function foo(x, y = function() { x = 2; }) { + x = 3; + y(); + console.log(x); +} + +foo() // 2 +x // 1 +``` + +如果将`var x = 3`的`var`去除,函数`foo`的内部变量`x`就指向第一个参数`x`,与匿名函数内部的`x`是一致的,所以最后输出的就是`2`,而外层的全局变量`x`依然不受影响 + +### rest 参数 + +`ES6` 引入 `rest` 参数(形式为`...`变量名),用于获取函数的多余参数,这样就不需要使用`arguments`对象了。`rest`参数搭配的变量是一个数组,该变量将多余的参数放入数组中。 + +```js +// 利用 rest 参数,可以向该函数传入任意数目的参数。 +function add(...values) { + let sum = 0; + + for (var val of values) { + sum += val; + } + + return sum; +} + +add(2, 5, 3) // 10 +``` + +rest 参数代替arguments变量 + + ```js + // arguments变量的写法 +function sortNumbers() { + return Array.prototype.slice.call(arguments).sort(); +} + +// rest参数的写法 +const sortNumbers = (...numbers) => numbers.sort(); + ``` +**rest 参数的写法更自然也更简洁。** + +`arguments`对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用`Array.prototype.slice.call`先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。 + +```js + +function push(array, ...items) { + items.forEach(function(item) { + array.push(item); + console.log(item); + }); +} + +var a = []; +push(a, 1, 2, 3) + +``` +**注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。** + +```js +(function(a) {}).length // 1 +(function(...a) {}).length // 0 +(function(a, ...b) {}).length // 1 +``` +**函数的length属性,不包括 rest 参数。** + +### 严格模式 + +从 ES5 开始,函数内部可以设定为严格模式。 + +```js +function doSomething(a, b) { + 'use strict'; + // code ES5中是被允许的 +} +``` + +ES2016 做了一点修改,**ES2016中规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。** + +```js + +// 报错 +function doSomething(a, b = a) { + 'use strict'; + // code +} + +// 报错 +const doSomething = function ({a, b}) { + 'use strict'; + // code +}; + +// 报错 +const doSomething = (...a) => { + 'use strict'; + // code +}; + +const obj = { + // 报错 + doSomething({a, b}) { + 'use strict'; + // code + } +``` + +函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。 + +### name 属性 + +函数的name属性,返回该函数的函数名。 + +```js +// 函数 +function test(){ + // ...code +} + +test.name // “test” + +``` + +ES6 对这个属性的行为做出了一些修改,如果将一个匿名函数赋值给一个变量: + +- ES5 的name属性,会返回空字符串 + +- ES6 的name属性会返回实际的函数名。 + +```js +// 匿名函数 +var f = function () {}; + +// ES5 +f.name // "" + +// ES6 +f.name // "f" +``` + +如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。 + +```js +const bar = function test() {}; + +// ES5 +bar.name // "test" + +// ES6 +bar.name // "test" +``` + +**Function构造函数返回的函数实例,name属性的值为anonymous。** + +```js +(new Function).name // "anonymous" +``` + +bind返回的函数,name属性值会加上bound前缀。 + +```js +// 定义函数 +function foo() { + +}; + +foo.bind({}).name // "bound foo" + +(function(){}).bind({}).name // "bound " +``` + +### 箭头函数 + +ES6 允许使用“箭头”(=>)定义函数。 + +```js +var f = v => v; + +// 等同于 +var f = function (v) { + return v; +}; + +``` +如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。 + +```js +var f = () => 5; + +// 等同于 +var f = function () { + return 5 +}; + +var sum = (num1, num2) => num1 + num2; +// 等同于 +var sum = function(num1, num2) { + return num1 + num2; +}; +``` + +由于**大括号被解释为代码块**,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。 + +```js +// 报错 +let getItem = id => { id: id, name: "tom" }; + +// 不报错 +let getItem = id => ({ id: id, name: "tom" }); + +``` + +下面是一种特殊情况,虽然可以运行,但会得到错误的结果。 + +```js +let foo = () => { a: 1 }; +foo() // undefined +``` + +原始意图是返回一个对象`{ a: 1 }`,但是由于引擎认为大括号是代码块,所以执行了一行语句`a: 1`。这时,`a`可以被解释为语句的标签,因此实际执行的语句是`1`;,然后函数就结束了,没有返回值。 + +如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。 + +```js + +// void运算符 +let fn = () => void doesNotReturn(); +``` + +**`void` 是一元运算符,它可以出现在任意类型的操作数之前执行操作数,却忽略操作数的返回值,返回一个 `undefined`** + +箭头函数可以与变量解构结合使用。 + +```js +const full = ({ first, last }) => first + ' ' + last; + +// 等同于 +function full(person) { + return person.first + ' ' + person.last; +} + +// 模式字符串 +const full= ({first,last})=>`${first}${last}` +``` + +**箭头函数的一个用处是简化回调函数。** + +```js +// 正常函数写法 +[1,2,3].map(function (x) { + return x * x; +}); + +// 箭头函数写法 +[1,2,3].map(x => x * x); + + +// 正常函数写法 +var result = values.sort(function (a, b) { + return a - b; +}); + +// 箭头函数写法 +var result = values.sort((a, b) => a - b); + + +// rest 参数与箭头函数结合 +const numbers = (...nums) => nums; + +numbers(1, 2, 3, 4, 5) +// [1,2,3,4,5] + +const headAndTail = (head, ...tail) => [head, tail]; + +headAndTail(1, 2, 3, 4, 5) +// [1,[2,3,4,5]] + +``` + + + + +箭头函数使用需要注意: + +- 函数体内的`this`对象,就是定义时所在的对象,而不是使用时所在的对象。 + +- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 + +- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 + +- 不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数。 + + +**`this`对象的指向是可变的,但是在箭头函数中,this对象的指向是固定的。** + +```js +function foo() { + setTimeout(() => { + console.log('id:', this.id); + }, 100); +} + +var id = 21; + +// call()函数修改this指向 +foo.call({ id: 42 }); +// id: 42 +``` + +`setTimeout()`的参数是一个箭头函数,这个箭头函数的定义生效是在`foo`函数生成时,而它的真正执行要等到 `100` 毫秒后。如果是普通函数,执行时`this`应该指向全局对象`window`,这时应该输出`21`。但是,**箭头函数导致this总是指向函数定义生效时所在的对象**(本例是`{id: 42}`),所以打印出来的是`42`。 + +**箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。** + +```js +function Timer() { + this.s1 = 0; + this.s2 = 0; + // 箭头函数 + setInterval(() => this.s1++, 1000); + // 普通函数 + setInterval(function () { + this.s2++; + }, 1000); +} + +var timer = new Timer(); + +setTimeout(() => console.log('s1: ', timer.s1), 3100); +setTimeout(() => console.log('s2: ', timer.s2), 3100); +// s1: 3 +// s2: 0 + +``` + +`Timer`函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的`this`绑定定义时所在的作用域(即`Timer`函数),后者的`this`指向运行时所在的作用域(即全局对象)。所以,`3100` 毫秒之后,`timer.s1`被更新了 `3` 次,而`timer.s2`一次都没更新。 + + +**箭头函数可以让this指向固定化,这种特性很有利于封装回调函数** + +```js +var handler = { + id: '123456', + + init: function() { + document.addEventListener('click', + event => this.doSomething(event.type), false); + }, + + doSomething: function(type) { + console.log('Handling ' + type + ' for ' + this.id); + } +}; +``` + +`init`方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。 + +**`this`指向的固定化,并不是因为箭头函数内部有绑定`this`的机制,实际原因是箭头函数根本没有自己的`this`,导致内部的`this`就是外层代码块的`this`。正是因为它没有`this`,所以也就不能用作构造函数。** + +```js +function foo() { + return () => { + return () => { + return () => { + console.log('id:', this.id); + }; + }; + }; +} + +var f = foo.call({id: 1}); + +var t1 = f.call({id: 2})()(); // id: 1 +var t2 = f().call({id: 3})(); // id: 1 +var t3 = f()().call({id: 4}); // id: 1 + +``` + +只有一个`this`,就是函数`foo`的`this`,所以`t1`、`t2`、`t3`都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的`this`,它们的this其实都是最外层`foo`函数的`this`。 + +除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量: +- arguments +- super +- new.target + +```js +function foo() { + setTimeout(() => { + console.log('args:', arguments); + }, 100); +} + +foo(2, 4, 6, 8) +// args: [2, 4, 6, 8] +``` + +上面代码中,箭头函数内部的变量`arguments`,其实是函数`foo`的`arguments`变量。 + + +由于箭头函数没有自己的`this`,所以当然也就不能用`call()`、`apply()`、`bind()`这些方法去改变`this`的指向。 + +```js +// 箭头函数没有自己的this +// bind方法无效,内部的this指向外部的this。 + +(function() { + return [ + (() => this.x).bind({ x: 'inner' })() + ]; +}).call({ x: 'outer' }); +// ['outer'] +``` + +### Function.prototype.toString() + +`ES2019` 对函数实例的`toString()`方法做出了修改 + +`toString()`方法返回函数代码本身,以前会省略注释和空格。 + + +```js +// 定义函数【注意注释】 +function /* foo comment */ foo () {} + +foo.toString() +// function foo() {} + +``` + +函数`foo`的原始代码包含注释,函数名`foo`和圆括号之间有空格,但是`toString()`方法都把它们省略了。 + + + +**修改后的`toString()`方法,明确要求返回一模一样的原始代码。** + + +```js +function /* foo comment */ foo () {} + +foo.toString() +// "function /* foo comment */ foo () {}" +``` + +### catch 命令的参数省略 + +JavaScript 语言的`try...catch`结构,以前明确要求`catch`命令后面必须跟参数,接受try代码块抛出的错误对象。 + +```js +try { + // ... +} catch (err) { + // 处理错误 +} +``` + +catch命令后面带有参数err。 + + + +但是,很多时候,catch代码块可能用不到这个参数。但是,为了保证语法正确,还是必须写。**ES2019 做出了改变,允许catch语句省略参数。** + + +```js + +try { + // ... +} catch { + // ... +} +``` + + +## 数组 + +### 扩展运算符 + +扩展运算符(`spread`)是三个点(`...`)。它好比 `rest` 参数的逆运算,**将一个数组转为用逗号分隔的参数序列**。 + +```js +console.log(...[1, 2, 3]) +// 1 2 3 + +console.log(1, ...[2, 3, 4], 5) +// 1 2 3 4 5 +``` + +主要用于函数调用。 + + +```js +// 将数组转化为逗号分隔的参数序列 +function push(array, ...items) { + array.push(...items); +} + +function add(x, y) { + return x + y; +} + +const numbers = [4, 38]; +add(...numbers) // 42 + +``` + +扩展运算符与正常的函数参数可以结合使用,非常灵活。 + +```js +function test(v, w, x, y, z) { } +const args = [0, 1]; + +// 调用 +test(-1, ...args, 2, ...[3]); +``` + + +扩展运算符后面还可以放置表达式。 + +```js + +// 结合三目运算 +const arr = [ + ...(x > 0 ? ['a'] : []), + 'b', +]; +``` + +**如果扩展运算符后面是一个空数组,则不产生任何效果。** + +```js +[...[], 1] +// [1] +``` + +**只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。** + + +```text + +// 扩展运算符所在的括号不是函数调用。 + +(...[1, 2]) +// Uncaught SyntaxError: Unexpected number + +console.log((...[1, 2])) +// Uncaught SyntaxError: Unexpected number + + +// 正常函数调用情况 +console.log(...[1, 2]) +// 1 2 + +``` + + +#### 替代函数的 apply 方法 + +由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。 + + +```js +// ES5 的写法 +function test(x, y, z) { + // ... +} +// 实际调用 +var args = [0, 1, 2]; +test.apply(null, args); + + +// ES6的写法 +function test(x, y, z) { + // ... +} +// 实际调用 +let args = [0, 1, 2]; +test(...args); + + + +// 应用Math.max方法的简化应用 + +// ES5 的写法 +Math.max.apply(null, [14, 3, 77]) + +// ES6 的写法 +Math.max(...[14, 3, 77]) + +// 等同于 +Math.max(14, 3, 77); +``` + +由于 JavaScript 不提供求数组最大元素的函数,所以只能**套用`Math.max`函数,将数组转为一个参数序列,然后求最大值。** 有了扩展运算符以后,就可以直接用`Math.max`了。 + + + +```js +// ES5的 写法 +var arr1 = [0, 1, 2]; +var arr2 = [3, 4, 5]; +Array.prototype.push.apply(arr1, arr2); + +// ES6 的写法 +let arr1 = [0, 1, 2]; +let arr2 = [3, 4, 5]; +arr1.push(...arr2); + + +// ES5 +new (Date.bind.apply(Date, [null, 2015, 1, 1])) +// ES6 +new Date(...[2015, 1, 1]); +``` + +#### 扩展运算符的应用 + +- 复制数组 +- 合并数组 +- 与解构赋值结合 +- 字符串 +- 实现了 Iterator 接口的对象 +- Map 和 Set 结构,Generator 函数 + + +##### 复制数组 + +**数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组** + + +```js +const a1 = [1, 2]; +const a2 = a1; + + +a2[0] = 2; +a1 // [2, 2] +``` +a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。 + +```js +// ES5 只能用变通方法来复制数组。 +const a1 = [1, 2]; +const a2 = a1.concat(); + +a2[0] = 2; +a1 // [1, 2] + +``` +a1会返回原数组的克隆,再修改a2就不会对a1产生影响。 + +```js +// 扩展运算符提供了复制数组的简便写法【都是克隆】。 +const a1 = [1, 2]; +// 写法一 +const a2 = [...a1]; +// 写法二 +const [...a2] = a1; +``` + +##### 合并数组 + +扩展运算符提供了数组合并的新写法。 + + +```js +const arr1 = ['a', 'b']; +const arr2 = ['c']; +const arr3 = ['d', 'e']; + +// ES5 的合并数组 +arr1.concat(arr2, arr3); +// [ 'a', 'b', 'c', 'd', 'e' ] + +// ES6 的合并数组 +[...arr1, ...arr2, ...arr3] +// [ 'a', 'b', 'c', 'd', 'e' ] +``` + +不过,这两种方法都是浅拷贝,使用的时候需要注意。 + +```js +const a1 = [{ foo: 1 }]; +const a2 = [{ bar: 2 }]; + +const a3 = a1.concat(a2); +const a4 = [...a1, ...a2]; + +a3[0] === a1[0] // true +a4[0] === a1[0] // true +``` +a3和a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。**如果修改了引用指向的值,会同步反映到新数组。** + +#### 与解构赋值结合 + +扩展运算符可以与解构赋值结合起来,用于生成数组。 + +```text +// ES5 +a = list[0], rest = list.slice(1) +// ES6 +[a, ...rest] = list + +const [first, ...rest] = [1, 2, 3, 4, 5]; +first // 1 +rest // [2, 3, 4, 5] + +const [first, ...rest] = []; +first // undefined +rest // [] + +const [first, ...rest] = ["foo"]; +first // "foo" +rest // [] +``` + +如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。 + +```text +const [...butLast, last] = [1, 2, 3, 4, 5]; +// 报错 + +const [first, ...middle, last] = [1, 2, 3, 4, 5]; +// 报错 +``` + +#### 字符串 + +扩展运算符还可以将字符串转为真正的数组。 + +```js +[...'hello'] +// [ "h", "e", "l", "l", "o" ] +``` + + +#### 实现了 Iterator 接口的对象 + +任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组 + +```js +let nodeList = document.querySelectorAll('div'); +let array = [...nodeList]; +``` + +`querySelectorAll`方法返回的是一个`NodeList`对象。**它不是数组,而是一个类似数组的对象**。这时,扩展运算符可以将其转为真正的数组,原因就在于`NodeList`对象实现了`Iterator` 。 + + +```js +// arrayLike是一个类似数组的对象,但是没有部署 Iterator 接口 +let arrayLike = { + '0': 'a', + '1': 'b', + '2': 'c', + length: 3 +}; + +// TypeError: Cannot spread non-iterable object. +let arr = [...arrayLike]; + +``` + +对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。 + + +#### Map 和 Set 结构,Generator 函数 + +**扩展运算符内部调用的是数据结构的 Iterator 接口**,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。 + + +```js +let map = new Map([ + [1, 'one'], + [2, 'two'], + [3, 'three'], +]); + +let arr = [...map.keys()]; // [1, 2, 3] +``` + +**Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。** + +```js +const go = function*(){ + yield 1; + yield 2; + yield 3; +}; + +[...go()] // [1, 2, 3] +``` + +如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。 + +```js +const obj = {a: 1, b: 2}; +// TypeError: Cannot spread non-iterable object +let arr = [...obj]; +``` + +### Array.from() + +`Array.from`方法用于将两类对象转为真正的数组: + +- 类似数组的对象(array-like object) +- 可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map) + + +```js +let arrayLike = { + '0': 'a', + '1': 'b', + '2': 'c', + length: 3 +}; + +// ES5的写法 +var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] + +// ES6的写法 +let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'] +``` + +实际应用中,常见的类似数组的对象是 `DOM` 操作返回的 `NodeList` 集合,以及函数内部的`arguments`对象。`Array.from`都可以将它们转为真正的数组 + + +```js +// NodeList对象 +let ps = document.querySelectorAll('p'); +Array.from(ps).filter(p => { + return p.textContent.length > 100; +}); + +// arguments对象 +function foo() { + // 转化成数组 + var args = Array.from(arguments); + // ... +} + +``` + +**只要是部署了 `Iterator` 接口的数据结构,`Array.from`都能将其转为数组。** + +```js +// 字符串和 Set 结构都具有 Iterator 接口 +Array.from('hello') +// ['h', 'e', 'l', 'l', 'o'] + +let namesSet = new Set(['a', 'b']) +Array.from(namesSet) // ['a', 'b'] +``` + +**如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组。** + +```js +Array.from([1, 2, 3]) +// [1, 2, 3] +``` + +**扩展运算符(...)也可以将某些数据结构转为数组。** + +```js +// arguments对象 +function foo() { + // 扩展运算符,效果和Array.from一样 + const args = [...arguments]; +} + +``` + +**`Array.from`方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有`length`属性。因此,任何有`length`属性的对象,都可以通过`Array.from`方法转为数组,而此时扩展运算符就无法转换。** + +```js +Array.from({ length: 3 }); +// [ undefined, undefined, undefined ] +``` + +`Array.from`返回了一个具有三个成员的数组,每个位置的值都是`undefined`。扩展运算符转换不了这个对象 + + +对于还没有部署该方法的浏览器,可以用`Array.prototype.slice`方法替代。 + +```js + +// 兼容存在Array.from情况 +const toArray = (() => + Array.from ? Array.from : obj => [].slice.call(obj) +)(); +``` + +**`Array.from`还可以接受第二个参数,作用类似于数组的`map`方法,用来对每个元素进行处理,将处理后的值放入返回的数组。** + +```js +Array.from(arrayLike, x => x * x); +// 等同于 +Array.from(arrayLike).map(x => x * x); + +Array.from([1, 2, 3], (x) => x * x) +// [1, 4, 9] +``` + + +`Array.from()`可以将各种值转为真正的数组,并且还提供`map`功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。 + +```js +Array.from({ length: 2 }, () => 'jack') +// ['jack', 'jack'] +``` + +上面代码中,Array.from的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。 + +`Array.from()`的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种 `Unicode` 字符,可以避免 JavaScript 将大于`\uFFFF`的 `Unicode` 字符,算作两个字符的 `bug`。 + +```js +function countSymbols(string) { + return Array.from(string).length; +} + +``` + +### Array.of() + +**`Array.of()`方法用于将一组值,转换为数组。** + +```js +Array.of(3, 11, 8) // [3,11,8] +Array.of(3) // [3] +Array.of(3).length // 1 +``` + +弥补数组构造函数`Array()`的不足。因为参数个数的不同,会导致`Array()`的行为有差异。 + +```js +Array() // [] +Array(3) // [, , ,] +Array(3, 11, 8) // [3, 11, 8] +``` +`Array()`方法没有参数、一个参数、三个参数时,返回的结果都不一样。 +- 只有当参数个数不少于 2 个时,`Array()`才会返回由参数组成的新数组。 +- 参数只有一个正整数时,实际上是指定数组的长度。 + + +`Array.of()`基本上可以用来替代`Array()`或`new Array()`,并且不存在由于参数不同而导致的重载,行为非常统一。 + +```js +Array.of() // [] +Array.of(undefined) // [undefined] +Array.of(1) // [1] +Array.of(1, 2) // [1, 2] +``` + +**`Array.of()`总是返回参数值组成的数组。如果没有参数,就返回一个空数组。** + + +`Array.of()`方法可以用下面的代码模拟实现。 + +```js +function ArrayOf(){ + // arguments 参数数组 + return [].slice.call(arguments); +} +``` + +### copyWithin() + +数组实例的`copyWithin()`方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。 + +```js +Array.prototype.copyWithin(target, start = 0, end = this.length) +``` + +接受三个参数: + +- target(必需):从该位置开始替换数据。如果为负值,表示倒数。 +- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。 +- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。 + +```text +[1, 2, 3, 4, 5].copyWithin(0, 3) +// [4, 5, 3, 4, 5] + +// 将3号位复制到0号位 +[1, 2, 3, 4, 5].copyWithin(0, 3, 4) +// [4, 2, 3, 4, 5] + +// -2相当于3号位,-1相当于4号位 +[1, 2, 3, 4, 5].copyWithin(0, -2, -1) +// [4, 2, 3, 4, 5] + +// 将3号位复制到0号位 +[].copyWithin.call({length: 5, 3: 1}, 0, 3) +// {0: 1, 3: 1, length: 5} + +// 将2号位到数组结束,复制到0号位 +let i32a = new Int32Array([1, 2, 3, 4, 5]); +i32a.copyWithin(0, 2); +// Int32Array [3, 4, 5, 4, 5] + +// 对于没有部署 TypedArray 的 copyWithin 方法的平台 +// 需要采用下面的写法 +[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4); +// Int32Array [4, 2, 3, 4, 5] +``` + +### find() VS findIndex() + +数组实例的`find`方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为`true`的成员,然后返回该成员。如果没有符合条件的成员,则返回`undefined`。 + +```js +[1, 4, -5, 10].find((n) => n < 0) +// -5 + +[1, 5, 10, 15].find(function(value, index, arr) { + return value > 9; +}) // 10 +``` + +**find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。** + +数组实例的findIndex方法的用法与find方法非常类似,**返回第一个符合条件的数组成员的位置**,如果所有成员都不符合条件,则返回`-1`。 + +```js +// 返回第一个符合条件的数组成员的位置 +[1, 5, 10, 15].findIndex(function(value, index, arr) { + return value > 9; +}) // 2 +``` + +两个方法都可以接受第二个参数,用来绑定回调函数的this对象。 + +```js +// 回调函数中的this对象指向person对象。 +function f(v){ + return v > this.age; +} +let person = {name: 'John', age: 20}; +[10, 12, 26, 15].find(f, person); // 26 +``` + +另外,两个方法都可以发现NaN,弥补了数组的indexOf方法的不足 + +```js +[NaN].indexOf(NaN) +// -1 + +[NaN].findIndex(y => Object.is(NaN, y)) +// 0 +``` + +**indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。** + + +### fill() + +`fill`方法使用给定值,填充一个数组。 + +```js +['a', 'b', 'c'].fill(7) +// [7, 7, 7] + +new Array(3).fill(7) +// [7, 7, 7] + +``` + +数组中已有的元素,会被全部抹去。 + + + +```js +// fill方法还可以接受第二个和第三个参数 +// startIndex指定填充的起始位置 +// endIndex指定填充的结束位置 +fill(value,startIndex,endIndex) + +['a', 'b', 'c'].fill(7, 1, 2) +// ['a', 7, 'c'] +``` + +**如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。** + +```js +let arr = new Array(3).fill({name: "Mike"}); +arr[0].name = "Ben"; +arr +// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}] + +let arr = new Array(3).fill([]); +arr[0].push(5); +arr +// [[5], [5], [5]] +``` + +### entries()、keys() 、 values() + +ES6 提供三个新的方法,用于遍历数组 + +- `entries()` 对键值对的遍历。 +- `keys()` 对键名的遍历 +- `values()` 对键值的遍历 + +都返回一个遍历器对象【Iterator】,可以用`for...of`循环进行遍历 + +```js +for (let index of ['a', 'b'].keys()) { + console.log(index); +} +// 0 +// 1 + +for (let elem of ['a', 'b'].values()) { + console.log(elem); +} +// 'a' +// 'b' + +for (let [index, elem] of ['a', 'b'].entries()) { + console.log(index, elem); +} +// 0 "a" +// 1 "b" +``` + +如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。 + +```js +let letter = ['a', 'b', 'c']; +let entries = letter.entries(); +console.log(entries.next().value); // [0, 'a'] +console.log(entries.next().value); // [1, 'b'] +console.log(entries.next().value); // [2, 'c'] +``` + +### includes() + +`Array.prototype.includes`方法返回一个布尔值,表示某个数组是否包含给定的值. + + +```js +[1, 2, 3].includes(2) // true +[1, 2, 3].includes(4) // false +[1, 2, NaN].includes(NaN) // true +``` + +与字符串的includes方法类似。ES2016 引入了该方法。 + +```js + +[1, 2, 3].includes(3, 3); // false +[1, 2, 3].includes(3, -1); // true + +``` + +**第二个参数表示搜索的起始位置,默认为0**。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。 + + +通常使用数组的`indexOf`法,也能检查是否包含某个值。 + +```js +if (arr.indexOf(el) !== -1) { + // ... +} +``` + +`indexOf`方法有两个缺点: + +- 不够语义化,它的含义是找到参数值的第一个出现位置,要去比较是否不等于-1,表达起来不够直观 +- 内部使用严格相等运算符(`===`)进行判断,这会导致对NaN的误判。 + + +```js + +// indexof存在NaN误判 +[NaN].indexOf(NaN) +// -1 + +// includes正常 +[NaN].includes(NaN) +// true +``` + + +类似功能替代方案: + +```js +const contains = (() => + Array.prototype.includes + ? (arr, value) => arr.includes(value) + : (arr, value) => arr.some(el => el === value) +)(); +contains(['foo', 'bar'], 'baz'); // => false +``` + + +`Map` 和 `Set` 数据结构有一个`has`方法,需要注意与`includes`区分。 + +- **Map 结构的has方法,是用来查找键名的**,比如Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)。 +- **Set 结构的has方法,是用来查找值的**,比如Set.prototype.has(value)、WeakSet.prototype.has(value)。 + +### flat()、flatMap() + +数组的成员有时还是数组,`Array.prototype.flat()`用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。 + +```js + +// flat()方法将子数组的成员取出来,添加在原来的位置。 +[1, 2, [3, 4]].flat() +// [1, 2, 3, 4] +``` + +**flat()默认只会“拉平”一层**,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。 + + +```js + +// 默认拉平一层 +[1, 2, [3, [4, 5]]].flat() +// [1, 2, 3, [4, 5]] + +// 拉平嵌套两层得嵌套数组 +[1, 2, [3, [4, 5]]].flat(2) +// [1, 2, 3, 4, 5] + +``` + +如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。 + +```js +// 不管有多少层嵌套,都要转成一维数组 +[1, [2, [3]]].flat(Infinity) +// [1, 2, 3] + +``` + +如果原数组有空位,flat()方法会跳过空位。 + +```js +[1, 2, , 4, 5].flat() +// [1, 2, 4, 5] +``` + +**flatMap()方法对原数组的每个成员执行一个函数**(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。 + +```js +// 相当于 [[2, 4], [3, 6], [4, 8]].flat() +[2, 3, 4].flatMap((x) => [x, x * 2]) +// [2, 4, 3, 6, 4, 8] +``` + +`flatMap()`只能展开一层数组 + + +```js +// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat() +[1, 2, 3, 4].flatMap(x => [[x * 2]]) +// [[2], [4], [6], [8]] +``` +上面代码中,遍历函数返回的是一个双层的数组,但是默认只能展开一层,因此flatMap()返回的还是一个嵌套数组。 + +**`flatMap()`方法的参数是一个遍历函数,可以接受三个参数** + +- 当前数组成员 +- 当前数组成员的位置(从零开始) +- 原数组 + +```text + +arr.flatMap(function callback(currentValue[, index[, array]]) { + // ... +}[, thisArg]) +``` + +**`flatMap()`方法还可以有第二个参数,用来绑定遍历函数里面的`this`。** + +### 数组的空位 + + +**数组的空位指,数组的某一个位置没有任何值**。比如,Array构造函数返回的数组都是空位。 + +```js +// 返回具有 3 个空位的数组。 +Array(3) // [, , ,] +``` + + +空位不是`undefined`,一个位置的值等于`undefined`,依然是有值的。**空位是没有任何值**,in运算符可以说明这一点。 + +```js +// 数组的 0 号位置是有值的 +0 in [undefined, undefined, undefined] // true + +// 数组的 0 号位置没有值 +0 in [, , ,] // false +``` + +ES5和ES6中空位的区别比较可以参考: +https://es6.ruanyifeng.com/#docs/array#%E6%95%B0%E7%BB%84%E7%9A%84%E7%A9%BA%E4%BD%8D + + +### 扩展运算符 + +扩展运算符(spread)是三个点(...),可以将数组转为用逗号分隔的参数序列 +```js +console.log(...[1,2,3]) +// 输出 1 2 3 + +console.log(1,...[2,3,4],5) +// 输出 1 2 3 4 5 + +``` + +可以用在函数调用这样的场景下 + +```js + +function push(arr, ...items){ + // 数组中添加元素 + arr.push(...items) +} + +function add(x,y){ + return x+y; +} + +// 定于参数 +const num=[4,22] + +// 调用 + +add(...num) + +// 输出26 + +``` + +从上面的代码例子中可以看出,arr.push(...items)和add(...num)都是函数的调用,也都可以使用扩展运算符,**将数组变为参数序列** + + +```js +// 表达式 +const arr=[ + ...(x>0?['a']:[]), + 'b' +] + +// 如上,扩展运算符是空数组,则不产生任何效果 +console.log([...[],1]) +// [1] + +``` + + +### 替代数组的apply()方法 + +> apply()方法可以将数组转为函数的参数 + +```js + +// ES5 +function f(x,y,z){ + // ... +} + +const arg=[0,1,2] + +// 利用apply方法 + +f.apply(null,args) + +// 而ES6中可以 + +f(...arg) + +``` + +类似的也可以 + +```js +// ES5 + +Math.max.apply(null,[1,2,3]) + +// ES6 +Math.max(...[1,2,3]) + +// 上面个两个等价于 + +Math.max(1,2,3) + +``` + +类似也可以实现元素添加数组到尾部 + +```js +const arr1=[0,1,2] +const arr2=[3,4,5] + +// ES5 (apply()劫持属性) +Array.prototype.push.apply(arr1,arr2) + +// 特别注意:Array的原型链上的push方法不能直接使用数组,需要用apply方法劫持变通 + +// ES6 +arr1.push(...arr2) +``` + +### 简单应用 + +#### 合并数组 + +```text +let arr1=['a','b'] +let arr2=['c'] +let arr3=['d','e'] +// ES5 +[1,2].concat(more) +// eg +arr1.concat(arr2,arr3) +// 输出 ['a','b','c','d','e'] + +// ES6 +[1,2,...more] +// eg: +[...arr1,...arr2,...arr3] +// 输出 ['a','b','c','d','e'] + +``` + +#### 解构赋值 + +> 与解构赋值的结合,可以帮助生成数组 + +```js +// ES5 +const a=list[0] +const rest=list.slice(1) + +// ES6 +[a,...rest]=list + +const [first,...rest]=[1,2,3,4,5] + +first // 1 +rest // [2,3,4,5] +``` + +#### 函数的返回值 + +> 在Javascript中,函数只能返回一个值,如果需要返回多个值,就通过返回对象或者数组来实现,拓展运算符提供了相对应的变通方法 + +```js +const fields=readDateFields(database); + +// 间数据构造传入构造函数Date(),获取新值 +const d=new Date(...fields) + +``` + +#### 字符串 +```js +[..."hello"] +// ['h','e','l','l','o'] +``` + + + + + +## 对象 + +> 对象(object)是 JavaScript 最重要的数据结构 + + +### 属性的简洁表示法 + +ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法 + +```js +// 属性名就是变量名, 属性值就是变量值 +const foo = 'bar'; +const baz = {foo}; +baz // {foo: "bar"} + +// 等同于 +const baz = {foo: foo}; +``` + +除了属性简写,方法也可以简写。 + +```js +function test(x, y) { + return {x, y}; +} + +// 等同于 +function test(x, y) { + return {x: x, y: y}; +} + +test(1, 2) // Object {x: 1, y: 2} + + +const fc = { + method() { + return "Hello!"; + } +}; + +// 等同于 +const fc = { + method: function() { + return "Hello!"; + } +}; +``` + + +CommonJS 模块输出一组变量,就非常合适使用简洁写法。 + +```js +let ms = {}; + +function getItem (key) { + return key in ms ? ms[key] : null; +} + +function setItem (key, value) { + ms[key] = value; +} + +function clear () { + ms = {}; +} + +module.exports = { getItem, setItem, clear }; +// 等同于 +module.exports = { + getItem: getItem, + setItem: setItem, + clear: clear +}; +``` + +属性的赋值器(setter)和取值器(getter),事实上也是采用这种写法。 + +```js +const cart = { + // 属性 + _wheels: 4, + + // 取值器 + get wheels () { + return this._wheels; + }, + // 赋值器 + set wheels (value) { + if (value < this._wheels) { + throw new Error('数值太小了!'); + } + this._wheels = value; + } +} +``` + +**注意,简写的对象方法不能用作构造函数,会报错** + +```js +const obj = { + test() { + this.foo = 'bar'; + } +}; +new obj.test() // 报错 +``` + +上面代码中,`test`是一个简写的对象方法,所以`obj.test`不能当作构造函数使用。 + + +### 属性名表达式 + +JavaScript 定义对象属性的两种方法 + +```js +// 方法一:直接用标识符作为属性名 +obj.foo = true; + +// 方法二:用表达式作为属性名 +obj['a' + 'bc'] = 123; + +``` + +如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。 + +```js +var obj = { + foo: true, + abc: 123 +}; +``` + +ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。 + +```js +let propKey = 'foo'; + +let obj = { + [propKey]: true, + ['a' + 'bc']: 123 +}; +``` + +**表达式还可以用于定义方法名。** + +```js +let obj = { + ['h' + 'ello']() { + return 'hi'; + } +}; + +obj.hello() // hi + +``` + +注意,属性名表达式与简洁表示法,不能同时使用,会报错。 + +```js +// 报错 +const foo = 'bar'; +const bar = 'abc'; +const baz = { [foo] }; + +// 正确 +const foo = 'bar'; +const baz = { [foo]: 'abc'}; + +``` +注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串`[object Object]` + + +```js +const keyA = {a: 1}; +const keyB = {b: 2}; + +const myObject = { + [keyA]: 'valueA', + [keyB]: 'valueB' +}; + +myObject // Object {[object Object]: "valueB"} +``` + + +### 方法的 name 属性 + +函数的`name`属性,返回函数名。对象方法也是函数,因此也有`name`属性。 + +```js +const person = { + sayName() { + console.log('hello!'); + }, +}; + +// 方法的name属性返回函数名(即方法名) +person.sayName.name // "sayName" +``` + + +如果对象的方法使用了取值函数(`getter`)和存值函数(`setter`),则name属性不是在该方法上面,而是该方法的属性的描述对象的`get`和`set`属性上面,返回值是方法名前加上`get`和`set` + +```js +const obj = { + get foo() {}, + set foo(x) {} +}; + +obj.foo.name +// TypeError: Cannot read property 'name' of undefined + + + +const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo'); + +descriptor.get.name // "get foo" +descriptor.set.name // "set foo" +``` + +**`Object.getOwnPropertyDescriptor`方法,返回某个对象属性的描述对象( `descriptor` )。** + +有两种特殊情况: + +- `bind`方法创造的函数,`name`属性返回`bound`加上原函数的名字 + +- `Function`构造函数创造的函数,`name`属性返回`anonymous`。 + +```js +(new Function()).name // "anonymous" + +var doSomething = function() { + // ... +}; +doSomething.bind().name // "bound doSomething" +``` + +如果对象的方法是一个 `Symbol` 值,那么`name`属性返回的是这个 `Symbol` 值的描述。 + +```js +const key1 = Symbol('description'); +const key2 = Symbol(); +let obj = { + [key1]() {}, + [key2]() {}, +}; +obj[key1].name // "[description]" + +// 没有Symbol描述,为空字符串 +obj[key2].name // "" + +``` + + +### 属性的可枚举性和遍历 + + +#### 可枚举性 + +对象的每个属性都有一个描述对象(`Descriptor`),用来控制该属性的行为。`Object.getOwnPropertyDescriptor`方法可以获取该属性的描述对象。 + +```js +let obj = { foo: 123 }; +Object.getOwnPropertyDescriptor(obj, 'foo') +// { +// value: 123, +// writable: true, +// enumerable: true, +// configurable: true +// } + +``` + +描述对象的`enumerable`属性,称为“可枚举性”,**如果该属性为`false`,就表示某些操作会忽略当前属性。** + +目前,有四个操作会忽略`enumerable`为`false`的属性 + +- `for...in循环`:只遍历对象自身的和继承的可枚举的属性。 +- `Object.keys()`:返回对象自身的所有可枚举的属性的键名。 +- `JSON.stringify()`:只串行化对象自身的可枚举的属性。 +- `Object.assign()`: 忽略`enumerable`为`false`的属性,只拷贝对象自身的可枚举的属性。【ES6新增】 + + + +**只有`for...in`会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。** + +> 实际上,引入“可枚举”(`enumerable`)这个概念的最初目的,就是让某些属性可以规避掉for...in操作,不然所有内部属性和方法都会被遍历到。 + + + +```js +Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable +// false + +Object.getOwnPropertyDescriptor([], 'length').enumerable +// false +``` + +对象原型的toString方法,以及数组的length属性,就通过“可枚举性”,从而避免被for...in遍历到。 + + +**ES6 规定,所有 Class 的原型的方法都是不可枚举的。** + +```js +Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable +// false +``` + +总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,只关心对象自身的属性。所以,**尽量不要用`for...in`循环,而用`Object.keys()`代替。** + + +#### 属性的遍历 + +ES6 一共有 5 种方法可以遍历对象的属性 + +- `for...in` +- `Object.keys(obj)` +- `Object.getOwnPropertyNames(Obj)` +- `Object.getOwnPropertySymbols(obj)` +- `Reflect.ownKeys(obj)` + +##### for...in + +`for...in`循环遍历对象自身的和继承的可枚举属性(不含 `Symbol`属性)。 + + +##### Object.keys(obj) + +`Object.keys`返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。 + +##### Object.getOwnPropertyNames(obj) + +`Object.getOwnPropertyNames`返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。 + +##### Object.getOwnPropertySymbols(obj) + +`Object.getOwnPropertySymbols`返回一个数组,包含对象自身的所有 `Symbol` 属性的键名。 + + +##### Reflect.ownKeys(obj) + +`Reflect.ownKeys`返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 `Symbol` 或字符串,也不管是否可枚举。 + + +**以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。** + +- 首先遍历所有数值键,按照数值升序排列。 +- 其次遍历所有字符串键,按照加入时间升序排列。 +- 最后遍历所有 `Symbol` 键,按照加入时间升序排列。 + + +```js + +// 排序规则:首先是数值属性2和10,其次是字符串属性b和a,最后是 Symbol 属性。 +Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) +// ['2', '10', 'b', 'a', Symbol()] +``` + +### super 关键字 + +`this`关键字总是指向函数所在的当前对象,`ES6` 新增了另一个类似的关键字`super`,**指向当前对象的原型对象**。 + +```js + +const proto = { + foo: 'hello' +}; + +const obj = { + foo: 'world', + find() { + return super.foo; + } +}; + + +Object.setPrototypeOf(obj, proto); + +// 对象obj.find()方法之中,通过super.foo引用了原型对象proto的foo属性。 +obj.find() // "hello" + +``` + +注意:Object.setPrototypeOf(),为现有对象设置原型,返回一个新对象,接收两个参数: + +- 第一个是现有对象 +- 第二是原型对象。 + + + +**注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。** + +```js +// 报错 +const obj = { + foo: super.foo +} + +// 报错 +const obj = { + foo: () => super.foo +} + +// 报错 +const obj = { + foo: function () { + return super.foo + } +} +``` + +上面三种`super`的用法都会报错,因为对于 `JavaScript` 引擎来说,这里的super都没有用在对象的方法之中。第一种写法是`super`用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给`foo`属性。目前,只有对象方法的简写法可以让 `JavaScript`引擎确认,定义的是对象的方法。 + +`JavaScript` 引擎内部,`super.foo`等同于`Object.getPrototypeOf(this).foo(属性)`或`Object.getPrototypeOf(this).foo.call(this)`(方法)。 + +```js +const proto = { + x: 'hello', + foo() { + console.log(this.x); + }, +}; + +const obj = { + x: 'world', + foo() { + super.foo(); + } +} + +Object.setPrototypeOf(obj, proto); + +obj.foo() // "world" +``` + +上面代码中,`super.foo`指向原型对象`proto`的`foo`方法,但是绑定的`this`却还是当前对象`obj`,因此输出的就是`world`。 + + +### 链判断运算符 + +如果读取对象内部的某个属性,往往需要判断一下该对象是否存在 + +```js +// 错误的写法 +const firstName = message.body.user.firstName; + +// 正确的写法 +const firstName = (message + && message.body + && message.body.user + && message.body.user.firstName) || 'default'; +``` +上面例子中,`firstName`属性在对象的第四层,所以需要判断四次,每一层是否有值。 + + +**三元运算符?:也常用于判断对象是否存在。** + +```js +const fooInput = myForm.querySelector('input[name=foo]') +const fooValue = fooInput ? fooInput.value : undefined +``` +这样的层层判断非常麻烦,ES2020 引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 +```js +const firstName = message?.body?.user?.firstName || 'default'; +const fooValue = myForm.querySelector('input[name=foo]')?.value +``` + +上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null`或`undefined`。如果是的,就不再往下运算,而是返回`undefined`。 + + +```js +// 判断对象方法是否存在,如果存在就立即执行 +iterator.return?.() +``` + +`iterator.return`如果有定义,就会调用该方法,否则`iterator.return`直接返回`undefined`,不再执行`?.`后面的部分。 + +```js +if (myForm.checkValidity?.() === false) { + // 表单校验失败 + return; +} +``` + +链判断运算符有三种用法: + +- `obj?.prop` : 对象属性 +- `obj?.[expr]` : 同上 +- `func?.(...args)` : 函数或对象方法的调用 + +```js +a?.b +// 等同于 +a == null ? undefined : a.b + +a?.[x] +// 等同于 +a == null ? undefined : a[x] + +a?.b() +// 等同于 +a == null ? undefined : a.b() + +a?.() +// 等同于 +a == null ? undefined : a() +``` + +特别注意后两种形式,如果`a?.b()`里面的`a.b`不是函数,不可调用,那么`a?.b()`是会报错的。`a?.()`也是如此,如果`a`不是`null`或`undefined`,但也不是函数,那么`a?.()`会报错。 + +使用链判断运算符,有几个注意点: + +- 短路机制 +- delete运算符 +- 括号的影响 +- 报错场合 +- 右侧不得为十进制数值 + +#### 短路机制 + +`?.`运算符相当于一种短路机制,只要不满足条件,就不再往下执行。**链判断运算符一旦为真,右侧的表达式就不再求值。** + + +#### delete 运算符 + +```js +delete a?.b +// 等同于 +a == null ? undefined : delete a.b +``` + +如果a是`undefined`或`null`,会直接返回`undefined`,而不会进行`delete运算`。 + +#### 括号的影响 + +如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。 + +```js +(a?.b).c +// 等价于 +(a == null ? undefined : a.b).c +``` + +**一般来说,使用`?.`运算符的场合,不应该使用圆括号。** + +#### 报错场合 + +以下写法是禁止的,会报错。 + +```js +// 构造函数 +new a?.() +new a?.b() + +// 链判断运算符的右侧有模板字符串 +a?.`{b}` +a?.b`{c}` + +// 链判断运算符的左侧是 super +super?.() +super?.foo + +// 链运算符用于赋值运算符左侧 +a?.b = c +``` + + +#### 右侧不得为十进制数值 + +为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照`三元运算符`进行处理,**也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。** + + +### Null 判断运算符 + +读取对象属性的时候,如果某个属性的值是`null`或`undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。 + +```js +const headerText = response.settings.headerText || 'Hello, world!'; +const animationDuration = response.settings.animationDuration || 300; +const showSplashScreen = response.settings.showSplashScreen || true; +``` + +开发者的原意是,只要属性的值为null或undefined,默认值就会生效,**但是属性的值如果为空字符串或false或0,默认值也会生效。** + +为了避免这种情况,ES2020 引入了一个新的 `Null` 判断运算符`??`。 + +行为类似`||`,但是**只有运算符左侧的值为`null`或`undefined`时,才会返回右侧的值。** + +这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值。 + +```js +const animationDuration = response.settings?.animationDuration ?? 300; +``` + +这个运算符很适合判断函数参数是否赋值。 + +```js +function Component(props) { + const enable = props.enabled ?? true; + // … +} + +``` + + +## 对象新增方法 + +* `Object.is()` +* `Object.assign()` +* `Object.getOwnPropertyDescriptors()` +* `__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()` +* `Object.keys(),Object.values(),Object.entries()` +* `Object.fromEntries()` + +### `Object.is()` + +ES5 比较两个值是否相等,只有两个运算符:相等运算符(`==`)和严格相等运算符(`===`) + +* 相等运算符(`==`)会自动转换数据类型 +* 严格相等运算符(`===`)的NaN不等于自身 + +> ES6 提出“Same-value equality”(同值相等)算法,**在所有环境中,只要两个值是一样的,它们就应该相等。** + +**`Object.is()`用来比较两个值是否严格相等,与严格比较运算符(`===`)的行为基本一致。** + +```js +Object.is('foo', 'foo') +// true +Object.is({}, {}) +// false +``` + +不同之处只有两个: + +* `Object.is()`的`+0`不等于`-0` +* `Object.is()`的`NaN`等于自身 + +```js ++0 === -0 //true +NaN === NaN // false + +Object.is(+0, -0) // false +Object.is(NaN, NaN) // true +``` + +类似功能实现: + +```js + +// 实现 Object.is()功能 +Object.defineProperty(Object, 'is', { + value: function(x, y) { + if (x === y) { + // 针对+0 不等于 -0的情况 + return x !== 0 || 1 / x === 1 / y; + } + // 针对NaN的情况 + return x !== x && y !== y; + }, + configurable: true, + enumerable: false, + writable: true +}); +``` + +### `Object.assign()` + +`Object.assign()`方法用于对象的合并,将源对象(`source`)的所有可枚举属性,复制到目标对象(`target`)。 + +```js +const target = { a: 1 }; + +const source1 = { b: 2 }; +const source2 = { c: 3 }; + +Object.assign(target, source1, source2); +target // {a:1, b:2, c:3} +``` + +**`Object.assign()`方法的第一个参数是目标对象,后面的参数都是源对象。** + +注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。 + +```js +const target = { a: 1, b: 1 }; + +const source1 = { b: 2, c: 2 }; +const source2 = { c: 3 }; + +Object.assign(target, source1, source2); +target // {a:1, b:2, c:3} +``` + +如果只有一个参数,Object.assign()会直接返回该参数。 + +```js +const obj = {a: 1}; +Object.assign(obj) === obj // true +``` + +如果该参数不是对象,则会先转成对象,然后返回。 + +```js +typeof Object.assign(2) // "object" +``` + +**由于`undefined`和`null`无法转成对象,所以如果它们作为参数,就会报错。** + +```js +Object.assign(undefined) // 报错 +Object.assign(null) // 报错 +``` + +如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果`undefined`和`null`不在首参数,就不会报错。 + +```js +let obj = {a: 1}; +Object.assign(obj, undefined) === obj // true +Object.assign(obj, null) === obj // true +``` + +**其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。** + +除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。 + +```js +const v1 = 'abc'; +const v2 = true; +const v3 = 10; + +const obj = Object.assign({}, v1, v2, v3); +console.log(obj); // { "0": "a", "1": "b", "2": "c" } +``` + +只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。**因为只有字符串的包装对象,会产生可枚举属性。** + +```js +Object(true) // {[[PrimitiveValue]]: true} +Object(10) // {[[PrimitiveValue]]: 10} +Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"} + +``` + +`布尔值`、`数值`、`字符串`分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性`[[PrimitiveValue]]`上面,这个属性是不会被`Object.assign()`拷贝的。只有字符串的包装对象,会产生`可枚举的实义属性`,那些属性则会被拷贝。 + +Object.assign()拷贝的属性是有限制的 + +* 只拷贝源对象的自身属性 +* 不拷贝继承属性 +* 不拷贝不可枚举的属性(enumerable: false)。 + +```js + +Object.assign({b: 'c'}, + Object.defineProperty({}, 'invisible', { + enumerable: false, + value: 'hello' + }) +) + +// Object.assign()要拷贝的对象只有一个不可枚举属性invisible,这个属性没有被拷贝进去。 +// { b: 'c' } +``` + +属性名为 Symbol 值的属性,也会被Object.assign()拷贝。 + +```js +Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) +// { a: 'b', Symbol(c): 'd' } +``` + +#### 需要注意 + +* 浅拷贝 + +`Object.assign()`方法实行的是浅拷贝,而不是深拷贝。 +**如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个`对象的引用`。** + +```js +const obj1 = {a: {b: 1}}; +const obj2 = Object.assign({}, obj1); + +// obj1.a.b的任何变化,都会反映到obj2.a.b上面。 +obj1.a.b = 2; +obj2.a.b // 2 +``` + +* 同名属性的替换 + +对于这种嵌套的对象,一旦遇到同名属性,`Object.assign()`的处理方法是替换,而不是添加。 + +一些函数库提供`Object.assign()`的定制版本(比如 `Lodash` 的`_.defaultsDeep()`方法),可以得到深拷贝的合并。 + +* 数组的处理 + +`Object.assign()`可以用来处理数组,但是会把数组视为对象。 + +```js + +Object.assign([1, 2, 3], [4, 5]) +// [4, 5, 3] + +``` + +上面代码中,`Object.assign()`把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。 + +* 取值函数的处理 + +`Object.assign()`只能进行值的复制,如果要复制的值是一个取值函数,那么**将求值后再复制**。 + +```js +const source = { + get foo() { return 1 } +}; +const target = {}; + +Object.assign(target, source) +// { foo: 1 } +``` + +上面代码中,`source`对象的`foo`属性是一个取值函数,`Object.assign()`不会复制这个取值函数,只会拿到值以后,将这个值复制过去。 + +#### 常见用途 + +* 为对象添加属性 + +```js +// 将x属性和y属性添加到Point类的对象实例。 +class Point { + constructor(x, y) { + Object.assign(this, {x, y}); + } +} +``` + +* 为对象添加方法 + +```js +Object.assign(SomeClass.prototype, { + someMethod(arg1, arg2) { + ··· + }, + anotherMethod() { + ··· + } +}); + +// 等同于下面的写法 +SomeClass.prototype.someMethod = function (arg1, arg2) { + ··· +}; +SomeClass.prototype.anotherMethod = function () { + ··· +}; +``` + +* 克隆对象 + +```js +function clone(origin) { + return Object.assign({}, origin); +} +``` + +**采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值**。 + +如果想要保持继承链,可以采用下面的代码。 + +```js +function clone(origin) { + let originProto = Object.getPrototypeOf(origin); + + return Object.assign(Object.create(originProto), origin); +} +``` + +[Object.create()和new object()和{}的区别](https://www.cnblogs.com/leijee/p/7490822.html) + +* 合并多个对象 + +```js +// 将多个对象合并到某个对象 +const merge = + (target, ...sources) => Object.assign(target, ...sources); + +// 对一个空对象合并,合并后返回一个新对象 +const merge = + (...sources) => Object.assign({}, ...sources); +``` + +* 为属性指定默认值 + +```js +const DEFAULTS = { + logLevel: 0, + outputFormat: 'html' +}; + +function processContent(options) { + options = Object.assign({}, DEFAULTS, options); + console.log(options); + // ... +} +``` + +上面代码中 + +* `DEFAULTS`对象是默认值 +* `options`对象是用户提供的参数。 + +`Object.assign()`方法将`DEFAULTS`和`options`合并成一个新对象,如果两者有同名属性,则`options`的属性值会覆盖`DEFAULTS`的属性值。 + +**注意,由于存在浅拷贝的问题,`DEFAULTS`对象和`options`对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,`DEFAULTS`对象的该属性很可能不起作用。** + +### Object.getOwnPropertyDescriptors() + +`ES5`的`Object.getOwnPropertyDescriptor()`方法会返回某个对象属性的描述对象(`descriptor`) + +`ES2017` 引入了`Object.getOwnPropertyDescriptors()`方法,返回指定对象所有自身属性(非继承属性)的描述对象。 + +```js + +// 相关实现 +function getOwnPropertyDescriptors(obj) { + const result = {}; + for (let key of Reflect.ownKeys(obj)) { + result[key] = Object.getOwnPropertyDescriptor(obj, key); + } + return result; +} +``` + +`getOwnPropertyDescriptors`该方法的引入目的,主要是为了解决`Object.assign()`无法正确拷贝`get`属性和`set`属性的问题。 + +```js +const source = { + set foo(value) { + console.log(value); + } +}; + +const target1 = {}; +Object.assign(target1, source); + +Object.getOwnPropertyDescriptor(target1, 'foo') +// { value: undefined, +// writable: true, +// enumerable: true, +// configurable: true } +``` + +上面代码中,`source`对象的`foo`属性的值是一个赋值函数,`Object.assign`方法将这个属性拷贝给`target1`对象,结果该属性的值变成了`undefined`。 +**这是因为`Object.assign`方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。** + +这时,`Object.getOwnPropertyDescriptors()`方法配合`Object.defineProperties()`方法,就可以实现正确拷贝。 + +```js +const source = { + set foo(value) { + console.log(value); + } +}; + +const target2 = {}; +Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source)); +Object.getOwnPropertyDescriptor(target2, 'foo') +// { get: undefined, +// set: [Function: set foo], +// enumerable: true, +// configurable: true } + + +// 抽象成函数 +const shallowMerge = (target, source) => Object.defineProperties( + target,Object.getOwnPropertyDescriptors(source) +); +``` + +### \_\_proto\_\_属性,Object.setPrototypeOf(),Object.getPrototypeOf() + +> JavaScript 语言的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作方法 + +#### \_\_proto\_\_属性 + +`__proto__`属性(前后各两个下划线),用来读取或设置当前对象的原型对象(`prototype`)。目前,所有浏览器(包括 `IE11`)都部署了这个属性。 + +```js +// es5 的写法 +const obj = { + method: function() { ... } +}; +obj.__proto__ = someOtherObj; + +// es6 的写法 +var obj = Object.create(someOtherObj); +obj.method = function() { ... }; +``` + +可以使用 + +* `Object.setPrototypeOf()`(写操作) +* `Object.getPrototypeOf()`(读操作) +* `Object.create()`(生成操作) + +代替实现。 + +实现上,`__proto__`调用的是`Object.prototype.__proto__` + +```js + +Object.defineProperty(Object.prototype, '__proto__', { + get() { + let _thisObj = Object(this); + return Object.getPrototypeOf(_thisObj); + }, + set(proto) { + if (this === undefined || this === null) { + throw new TypeError(); + } + if (!isObject(this)) { + return undefined; + } + if (!isObject(proto)) { + return undefined; + } + let status = Reflect.setPrototypeOf(this, proto); + if (!status) { + throw new TypeError(); + } + }, +}); + +function isObject(value) { + return Object(value) === value; +} + +``` + +如果一个对象本身部署了`__proto__`属性,该属性的值就是对象的原型。 + +```js +Object.getPrototypeOf({ __proto__: null }) +// null +``` + +#### Object.setPrototypeOf() + +`Object.setPrototypeOf`方法的作用与`__proto__`相同,用来设置一个对象的原型对象(`prototype`),返回参数对象本身,是 ES6 正式推荐的设置原型对象的方法。 + +```js +// 格式 +Object.setPrototypeOf(object, prototype) + +// 用法 +const o = Object.setPrototypeOf({}, null); + +// 等同于 +function setPrototypeOf(obj, proto) { + obj.__proto__ = proto; + return obj; +} +``` + +很经典的例子: + +```js +let proto = {}; +let obj = { x: 10 }; +Object.setPrototypeOf(obj, proto); + +proto.y = 20; +proto.z = 40; + +obj.x // 10 +obj.y // 20 +obj.z // 40 +``` + +将`proto`对象设为`obj`对象的原型,所以从`obj`对象可以读取`proto`对象的属性。 + +**如果第一个参数不是对象,会自动转为对象**。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。 + +```js +Object.setPrototypeOf(1, {}) === 1 // true +Object.setPrototypeOf('foo', {}) === 'foo' // true +Object.setPrototypeOf(true, {}) === true // true +``` + +由于`undefined`和`null`无法转为对象,所以如果第一个参数是`undefined`或`null`,就会报错。 + +```js +Object.setPrototypeOf(undefined, {}) +// TypeError: Object.setPrototypeOf called on null or undefined + +Object.setPrototypeOf(null, {}) +// TypeError: Object.setPrototypeOf called on null or undefined +``` + +#### Object.getPrototypeOf() + +与`Object.setPrototypeOf`方法配套,用于读取一个对象的原型对象。 + +```js +// 基本使用 +Object.getPrototypeOf(obj); + +// 原型设置和获取 +function User() { + // ... +} + +const user = new User(); + +Object.getPrototypeOf(user) === User.prototype +// true + +Object.setPrototypeOf(user, Object.prototype); +Object.getPrototypeOf(user) === User.prototype +// false +``` + +如果参数不是对象,会被自动转为对象。 + +```js +// 等同于 Object.getPrototypeOf(Number(1)) +Object.getPrototypeOf(1) +// Number {[[PrimitiveValue]]: 0} + +// 等同于 Object.getPrototypeOf(String('foo')) +Object.getPrototypeOf('foo') +// String {length: 0, [[PrimitiveValue]]: ""} + +// 等同于 Object.getPrototypeOf(Boolean(true)) +Object.getPrototypeOf(true) +// Boolean {[[PrimitiveValue]]: false} + +Object.getPrototypeOf(1) === Number.prototype // true +Object.getPrototypeOf('foo') === String.prototype // true +Object.getPrototypeOf(true) === Boolean.prototype // true +``` + +如果参数是`undefined`或`null`,它们无法转为对象,所以会报错。 + +```js +Object.getPrototypeOf(null) +// TypeError: Cannot convert undefined or null to object + +Object.getPrototypeOf(undefined) +// TypeError: Cannot convert undefined or null to object +``` + +### Object.keys(),Object.values(),Object.entries() + +#### Object.keys() + +ES5 引入了`Object.keys`方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。 + +```js +const obj = { name: 'bob', age: 24 }; +Object.keys(obj) +// ["name", "age"] +``` + +#### Object.values() + +`Object.values`方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(`enumerable`)属性的键值。 + +```js +const obj = { name: 'bob', age: 24 }; +Object.values(obj) +// ["bob", 24] +``` + +* `Object.values`只返回对象自身的可遍历属性。 + +* `Object.values`会过滤属性名为 Symbol 值的属性。 + +* 如果参数不是对象,`Object.values`会先将其转为对象。**由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。`Object.values`会返回空数组。** + +#### Object.entries() + +`Object.entries()`方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(`enumerable`)属性的键值对数组。 + +`Object.entries`的基本用途是遍历对象的属性。 + +```js +let obj = { one: 1, two: 2 }; +for (let [k, v] of Object.entries(obj)) { + console.log( + `${JSON.stringify(k)}: ${JSON.stringify(v)}` + ); +} +// "one": 1 +// "two": 2 +``` + +`Object.entries`方法的另一个用处是,将对象转为真正的Map结构。 + +```js +const obj = { name: 'bob', age: 24 }; +const map = new Map(Object.entries(obj)); +map // Map { name: "bob", age: 24 } +``` + +自己实现`Object.entries`方法,循环遍历 + +```js +// Generator函数的版本 +function* entries(obj) { + for (let key of Object.keys(obj)) { + yield [key, obj[key]]; + } +} + +// 非Generator函数的版本 +function entries(obj) { + let arr = []; + for (let key of Object.keys(obj)) { + arr.push([key, obj[key]]); + } + return arr; +} +``` + +### Object.fromEntries() + +`Object.fromEntries()`方法是`Object.entries()`的逆操作,用于将一个键值对数组转为对象。 + +```js +Object.fromEntries([ + ['name', 'bob'], + ['age', 24] +]) +// { name: "bob", age: 24 } +``` + +该方法的主要目的,是将键值对的数据结构还原为对象,**特别适合将 Map 结构转为对象**。 + +```js +// 例一 +const entries = new Map([ + ['name', 'bob'], + ['age', 24] +]); + +Object.fromEntries(entries) +// { name: "bob", age: 24 } + +// 例二 +const map = new Map().set('foo', true).set('bar', false); +Object.fromEntries(map) +// { foo: true, bar: false } +``` + +该方法的一个用处是配合`URLSearchParams`对象,将查询字符串转为对象。 + +```js + +// url模块中获取URLSearchParams +const { URLSearchParams } = require('url'); +Object.fromEntries(new URLSearchParams('name=bob&age=24')) +// { name: "bob", age: 24 } +``` + + + +## 参考资料 + +- + diff --git a/docs/manuscripts/read-books/cs-books/es-standard/es-standard-sidebar.ts b/docs/manuscripts/read-books/cs-books/es-standard/es-standard-sidebar.ts deleted file mode 100644 index 80674f5cd..000000000 --- a/docs/manuscripts/read-books/cs-books/es-standard/es-standard-sidebar.ts +++ /dev/null @@ -1,38 +0,0 @@ -export const esStandardSidebar = [ - { - text: '简介', - link: '简介.md' - }, - { - text: 'let和const', - link: 'let和const.md' - }, - { - text: '解构赋值', - link: '解构赋值.md' - }, - { - text: '字符串', - link: '字符串.md' - }, - { - text: '正则表达式', - link: '正则表达式.md' - }, - { - text: '数值', - link: '数值.md' - }, - { - text: '函数', - link: '函数.md' - }, - { - text: '数组', - link: '数组.md' - }, - { - text: '对象', - link: '对象.md' - } -] diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/let\345\222\214const.md" "b/docs/manuscripts/read-books/cs-books/es-standard/let\345\222\214const.md" deleted file mode 100644 index c711584b2..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/let\345\222\214const.md" +++ /dev/null @@ -1,483 +0,0 @@ ---- -title: let和const命令 -headerDepth: 4 ---- - -### let命令 ->ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,**只在let命令所在的代码块内有效。** - -```javascript - -// 函数内部定义变量 -function test(){ - let a=10 - var b=1 -} - -console.log(a) //输出报错,let块级作用域 - -console.log(b) // 输出1 - -``` -#### 不存在变量提升 - -> var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined - -let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。 - -```javascript -// var -console.log(test) // 输出undefined -var test=2 - -// let -console.log(err) // 输出ReferenceError错误 -let err=1 -``` - -#### 暂时性死区 - ->只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 - -ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,**从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错**。 - -> 代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。 - -```javascript -// 在let命令声明变量tmp之前,都属于变量tmp的“死区”。 -if (true) { - // TDZ开始 - tmp = 'abc'; // ReferenceError - console.log(tmp); // ReferenceError - - let tmp; // TDZ结束 - console.log(tmp); // undefined - - tmp = 123; - console.log(tmp); // 123 -} - -``` - -**ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。** - -总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。 - -#### 不允许重复声明 - -> let不允许在相同作用域内,重复声明同一个变量。 - -```javascript -// 报错 -function func() { - let a = 10; - var a = 1; -} - -// 报错 -function func() { - let a = 10; - let a = 1; -} - -``` - -当然这样写是不报错的,但不建议 -```javascript - -function func(arg) { - { - let arg; - } -} -func() // 不报错 -``` - -### 块级作用域 - -ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。 - -```javascript -// 循环结束后,变量i并没有消失,泄露成了全局变量。 -var s = 'hello'; - -for (var i = 0; i < s.length; i++) { - console.log(s[i]); -} - -console.log(i); // 5 -``` - -#### ES6的块级作用域 - -let为 JavaScript 新增了块级作用域。 - -ES6 允许块级作用域的任意嵌套。 - -```javascript -// 报错情况 -{ - { - { - let instance='test' - } - // 此时并没有变量名instance,输出会报错 - console.log(instance) - } -} - -// 正常情况 -{ - { - let instance='test' - { - // 与上面的instance互不影响 - let instance='test' - } - } -} -``` - -块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。 - -```javascript -// IIFE 写法 -(function () { - var tmp = ...; - ... -}()); - -// 块级作用域写法 -{ - let tmp = ...; - ... -} - -``` - -#### 块级作用域和函数声明 - -ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 - -```javascript -// 按照 ES5 的规定以下情况都是非法的。 -// 情况一 -if (true) { - function f(){ - - } -} - -// 情况二 -try { - function f(){ - - } -} catch(e) { - // ... -} -``` - -ES6 引入了块级作用域,明确允许在块级作用域之中声明函数 - -> ES6 规定,块级作用域之中,**函数声明语句的行为类似于let,在块级作用域之外不可引用。** - -```javascript -function f(){ - console.log('outside') -} - -(function(){ - if(false){ - // 重复声明函数f - function f(){ - console.log('inside') - } - } -}) - -// 运行会得到“inside”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。 -// ES5 环境 -function f() { - console.log('outside') - } - -(function () { - function f() { - console.log('inside') - } - if (false) { - - } - f(); -}()); -``` - -ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢? - -```javascript -// 浏览器的 ES6 环境 -function f() { - console.log('outside') -} - -(function () { - if (false) { - // 重复声明一次函数f - function f() { - console.log('inside') - } - } - f(); -}()); -// Uncaught TypeError: f is not a function - -``` -上面的代码在 ES6 浏览器中,都会报错。 - -原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。 - -允许在块级作用域内声明函数。 -函数声明类似于var,即会提升到全局作用域或函数作用域的头部。 -同时,函数声明还会提升到所在的块级作用域的头部。 -注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。 - -根据这三条规则,浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。上面的例子实际运行的代码如下。 - -```javascript -// 浏览器的 ES6 环境 -function f(){ - console.log('outside') -} -(function () { - var f = undefined; - if (false) { - function f(){ - console.log('inside') - } - } - // 执行函数 - f(); -}()); -// Uncaught TypeError: f is not a function -``` -考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。 - -```javascript -// 块级作用域内部的函数声明语句,建议不要使用 -{ - let a = 'secret'; - function f(){ - return a; - } -} - -// 块级作用域内部,优先使用函数表达式 -{ - let a = 'secret'; - let f = function(){ - return a; - }; -} -``` -另外,还有一个需要注意的地方。ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。 - -```javascript -// 第一种写法,报错 -if (true) let x = 1; - -// 第二种写法,不报错 -if (true) { - let x = 1; -} - -``` -上面代码中,第一种写法没有大括号,所以不存在块级作用域,而let只能出现在当前作用域的顶层,所以报错。第二种写法有大括号,所以块级作用域成立。 - -函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。 - -```javascript -// 不报错 -'use strict'; -if (true) { - function f(){ - - } -} - -// 报错 -'use strict'; -if (true) - function f(){} - -``` - - -### const命令 - -**const声明一个只读的常量。一旦声明,常量的值就不能改变。** - - -```javascript - -const test=2323 - -console.log(test) // 输出:2323 - -// 重新赋值会报错:Assignment to constant variable - -test=4567 - -``` -**const声明的变量不得改变值**,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。 - -```javascript - -// 只声明、不赋值会报错 -const test; - -``` -**const的作用域与let命令相同:只在声明所在的块级作用域内有效。** - -```javascript -if(true){ - const max=5 -} - -// 输出报错:max is not defined -console.log(max) - -``` - -const命令声明的常量也是不提升的,同样存在暂时性死区,**只能在声明的位置后面使用** - -```javascript -// 存在暂时性死区 -if(true){ - // 调用报错 - console.log(max) - const max=34; -} - -``` - -**const声明的常量,也与let一样不可重复声明。** - -```javascript -var student='tom' -let gender='girl' - -// 已声明的变量,重复声明会报错 -const message='go go go' - -const gender='boy' - -``` - -### 重要 - -**const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。** - -对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心 - -```javascript -// 定义对象 -const student={} - -// 添加属性 -student.age=18 - -// 正常输出 18 -console.log(student.age) - -// 此时指针地址发生了变化,报错 -student={} - -``` - -> 常量student储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把student指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性 - - -对象值确保不变,可以考虑使用`Object.freeze()`函数将其冻结 - -```javascript - -const student=Object.freeze({}) - -// 常规模式: 赋值不起作用 -// 严格模式: 报错 -student.age=18 - -``` - -当然,出了冻结对象本身,对象可能存在的属性也需要冻结 - -```javascript -// 冻结对象和属性 -function objectConstant(obj){ - // 冻结对象 - Object.freeze(obj) - // 冻结属性 - Object.keys(obj).forEach((key,index)=>{ - // 属性值为对象 - if(typeof obj[key]==='object'){ - // 递归调用冻结方法 - constantize(obj[key]) - } - }) -} -``` - -#### ES6声明变量的6中方法 - -- var定义 -- function命令 -- let -- const -- import -- class - -#### globalThis 对象 - - -JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。 - -- 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。 - -- 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。 - -- 在Node 里面,顶层对象是global,但其他环境都不支持。 - - 同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。 - -- 全局环境中,this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined。 -- 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。 -- 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。 - - - -**很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。** -```javascript -// 方法一 -(typeof window !== 'undefined' - ? window - : (typeof process === 'object' && - typeof require === 'function' && - typeof global === 'object') - ? global - : this); - -// 方法二 -var getGlobal = function () { - if (typeof self !== 'undefined') { - return self; - } - if (typeof window !== 'undefined') { - return window; - } - if (typeof global !== 'undefined') { - return global; - } - throw new Error('unable to locate global object'); -}; -``` \ No newline at end of file diff --git a/docs/manuscripts/read-books/cs-books/es-standard/readme.md b/docs/manuscripts/read-books/cs-books/es-standard/readme.md deleted file mode 100644 index 786777b4e..000000000 --- a/docs/manuscripts/read-books/cs-books/es-standard/readme.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: ES6标准入门 ---- diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\345\207\275\346\225\260.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\345\207\275\346\225\260.md" deleted file mode 100644 index ad062c9ed..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\345\207\275\346\225\260.md" +++ /dev/null @@ -1,729 +0,0 @@ ---- -title: 函数 -headerDepth: 4 ---- - - -### 函数参数的默认值 - -ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 - -```javascript -function log(x, y) { - y = y || 'World'; - console.log(x, y); -} - -log('Hello') // Hello World -log('Hello', 'China') // Hello China -log('Hello', '') // Hello World - -// ES6中可以 -function log(x, y = 'World') { - console.log(x, y); -} - -log('Hello') // Hello World -log('Hello', 'China') // Hello China -log('Hello', '') // Hello - -``` - -通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。 - -```javascript - -if (typeof y === 'undefined') { - y = 'World'; -} - -``` - -**参数变量是默认声明的,不能用let或const再次声明,否则会报错。** - - -使用参数默认值时,函数不能有同名参数 - -```javascript -// 不报错 -function test(x, x, y) { - // ... -} - -// 函数同名报错 -function test(x, x, y = 1) { - // ... -} -``` - -另外,参数默认值不是传值的,而是每次都重新计算默认值表达式的值。**也就是说,参数默认值是惰性求值的。** - - -```javascript -let x = 99; -function add(p = x + 1) { - console.log(p); -} - -add() // 100 - -// 修改变量值 -x = 100; -add() // 101 -``` - -注意:默认p不是等于100 - -#### 与解构赋值默认值结合使用 - -参数默认值可以与解构赋值的默认值,结合起来使用。 - -```javascript -function add({x, y = 5}) { - console.log(x, y); -} - -add({}) // undefined 5 -add({x: 1}) // 1 5 -add({x: 1, y: 2}) // 1 2 -add() // TypeError: Cannot read property 'x' of undefined -``` - -如果函数`add`调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。 - -```javascript - -// 提供默认值进行解构 -function add({x, y = 5} = {}) { - console.log(x, y); -} - -add() // undefined 5 - -``` - - -#### 参数默认值的位置 - -通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。**如果非尾部的参数设置默认值,实际上这个参数是没法省略的。** - -```javascript -// 存在默认值不是尾参数 -function test(x = 1, y) { - return [x, y]; -} - -test() // [1, undefined] -test(2) // [2, undefined] -test(, 1) // 报错 -test(undefined, 1) // [1, 1] - -``` - -显式输入`undefined`,配合解构的原理,可以省略有默认值的参数 - -**如果传入`undefined`,将触发该参数等于默认值,`null`则没有触发默认值。** - -```javascript -function test(x = 5, y = 6) { - console.log(x, y); -} - -test(undefined, null) -// 5 null -``` - - -### 函数的 length 属性 - -指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。 - -```javascript - -(function (a) {}).length // 1 -(function (a = 5) {}).length // 0 -(function (a, b, c = 5) {}).length // 2 - -``` - -`length`属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了 `3` 个参数,其中有一个参数c指定了默认值,因此`length`属性等于`3`减去`1`,最后得到`2` - -```javascript -(function(...args) {}).length // 0 -``` - -`length`属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。 - -**如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。** - -```javascript -(function (a = 0, b, c) {}).length // 0 -(function (a, b = 1, c) {}).length // 1 -``` - -#### 作用域 - -一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。**这种语法行为,在不设置参数默认值时,是不会出现的。** - -```javascript -var x = 1; - -function add(x, y = x) { - // 默认值变量x指向第一个参数x,而不是全局变量x - console.log(y); -} - -add(2) // 2 -``` - -上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。 - -```javascript -let x = 1; - -function f(y = x) { - let x = 2; - console.log(y); -} - -f() // 1 -``` - -上面代码中,函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。 - -```javascript - -// 此时全局变量x不存在,就会报错。 -function f(y = x) { - let x = 2; - console.log(y); -} - -f() // ReferenceError: x is not defined -``` - -上面代码中,参数`x = x`形成一个单独作用域。实际执行的是`let x = x`,由于暂时性死区的原因,这行代码会报错`x未定义`。 - -**如果参数的默认值是一个函数,该函数的作用域也遵守这个规则** - -```javascript -let foo = 'outer'; - -function bar(func = () => foo) { - let foo = 'inner'; - console.log(func()); -} - -bar(); // outer -``` - -上面代码中,函数`bar`的参数`func`的默认值是一个匿名函数,返回值为变量`foo`。函数参数形成的单独作用域里面,并没有定义变量`foo`,所以`foo`指向外层的全局变量`foo`,因此输出`outer`。 - -```javascript - -function bar(func = () => foo) { - let foo = 'inner'; - console.log(func()); -} - -bar() // ReferenceError: foo is not defined - -``` - -上面代码中,匿名函数里面的`foo`指向函数外层,但是函数外层并没有声明变量`foo`,所以就报错了。 - - -```javascript -var x = 1; -function foo(x, y = function() { x = 2; }) { - var x = 3; - y(); - console.log(x); -} - -foo() // 3 -x // 1 - -``` - -上面代码中,函数`foo`的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量`y`,`y`的默认值是一个匿名函数。这个匿名函数内部的变量`x`,指向同一个作用域的第一个参数x。函数`foo`内部又声明了一个内部变量`x`,该变量与第一个参数`x`由于不是同一个作用域,所以不是同一个变量,因此执行`y`后,内部变量`x`和外部全局变量x的值都没变。 - -```javascript -var x = 1; -function foo(x, y = function() { x = 2; }) { - x = 3; - y(); - console.log(x); -} - -foo() // 2 -x // 1 -``` - -如果将`var x = 3`的`var`去除,函数`foo`的内部变量`x`就指向第一个参数`x`,与匿名函数内部的`x`是一致的,所以最后输出的就是`2`,而外层的全局变量`x`依然不受影响 - - - -### rest 参数 - -`ES6` 引入 `rest` 参数(形式为`...`变量名),用于获取函数的多余参数,这样就不需要使用`arguments`对象了。`rest`参数搭配的变量是一个数组,该变量将多余的参数放入数组中。 - -```javascript -// 利用 rest 参数,可以向该函数传入任意数目的参数。 -function add(...values) { - let sum = 0; - - for (var val of values) { - sum += val; - } - - return sum; -} - -add(2, 5, 3) // 10 -``` - -rest 参数代替arguments变量 - - ```javascript - // arguments变量的写法 -function sortNumbers() { - return Array.prototype.slice.call(arguments).sort(); -} - -// rest参数的写法 -const sortNumbers = (...numbers) => numbers.sort(); - ``` -**rest 参数的写法更自然也更简洁。** - -`arguments`对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用`Array.prototype.slice.call`先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。 - -```javascript - -function push(array, ...items) { - items.forEach(function(item) { - array.push(item); - console.log(item); - }); -} - -var a = []; -push(a, 1, 2, 3) - -``` -**注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。** - -```javascript -(function(a) {}).length // 1 -(function(...a) {}).length // 0 -(function(a, ...b) {}).length // 1 -``` -**函数的length属性,不包括 rest 参数。** - - -### 严格模式 - -从 ES5 开始,函数内部可以设定为严格模式。 - -```javascript -function doSomething(a, b) { - 'use strict'; - // code ES5中是被允许的 -} -``` - -ES2016 做了一点修改,**ES2016中规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。** - -```javascript - -// 报错 -function doSomething(a, b = a) { - 'use strict'; - // code -} - -// 报错 -const doSomething = function ({a, b}) { - 'use strict'; - // code -}; - -// 报错 -const doSomething = (...a) => { - 'use strict'; - // code -}; - -const obj = { - // 报错 - doSomething({a, b}) { - 'use strict'; - // code - } -``` - -函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。 - - -### name 属性 - -函数的name属性,返回该函数的函数名。 - -```javascript -// 函数 -function test(){ - // ...code -} - -test.name // “test” - -``` - -ES6 对这个属性的行为做出了一些修改,如果将一个匿名函数赋值给一个变量: - -- ES5 的name属性,会返回空字符串 - -- ES6 的name属性会返回实际的函数名。 - -```javascript -// 匿名函数 -var f = function () {}; - -// ES5 -f.name // "" - -// ES6 -f.name // "f" -``` - -如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。 - -```javascript -const bar = function test() {}; - -// ES5 -bar.name // "test" - -// ES6 -bar.name // "test" -``` - -**Function构造函数返回的函数实例,name属性的值为anonymous。** - -```javascript -(new Function).name // "anonymous" -``` - -bind返回的函数,name属性值会加上bound前缀。 - -```javascript -// 定义函数 -function foo() { - -}; - -foo.bind({}).name // "bound foo" - -(function(){}).bind({}).name // "bound " -``` - -### 箭头函数 - -ES6 允许使用“箭头”(=>)定义函数。 - -```javascript -var f = v => v; - -// 等同于 -var f = function (v) { - return v; -}; - -``` -如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。 - -```javascript -var f = () => 5; - -// 等同于 -var f = function () { - return 5 -}; - -var sum = (num1, num2) => num1 + num2; -// 等同于 -var sum = function(num1, num2) { - return num1 + num2; -}; -``` - -由于**大括号被解释为代码块**,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。 - -```javascript -// 报错 -let getItem = id => { id: id, name: "tom" }; - -// 不报错 -let getItem = id => ({ id: id, name: "tom" }); - -``` - -下面是一种特殊情况,虽然可以运行,但会得到错误的结果。 - -```javascript -let foo = () => { a: 1 }; -foo() // undefined -``` - -原始意图是返回一个对象`{ a: 1 }`,但是由于引擎认为大括号是代码块,所以执行了一行语句`a: 1`。这时,`a`可以被解释为语句的标签,因此实际执行的语句是`1`;,然后函数就结束了,没有返回值。 - -如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。 - -```javascript - -// void运算符 -let fn = () => void doesNotReturn(); -``` - -**`void` 是一元运算符,它可以出现在任意类型的操作数之前执行操作数,却忽略操作数的返回值,返回一个 `undefined`** - -箭头函数可以与变量解构结合使用。 - -```javascript -const full = ({ first, last }) => first + ' ' + last; - -// 等同于 -function full(person) { - return person.first + ' ' + person.last; -} - -// 模式字符串 -const full= ({first,last})=>`${first}${last}` -``` - -**箭头函数的一个用处是简化回调函数。** - -```javascript -// 正常函数写法 -[1,2,3].map(function (x) { - return x * x; -}); - -// 箭头函数写法 -[1,2,3].map(x => x * x); - - -// 正常函数写法 -var result = values.sort(function (a, b) { - return a - b; -}); - -// 箭头函数写法 -var result = values.sort((a, b) => a - b); - - -// rest 参数与箭头函数结合 -const numbers = (...nums) => nums; - -numbers(1, 2, 3, 4, 5) -// [1,2,3,4,5] - -const headAndTail = (head, ...tail) => [head, tail]; - -headAndTail(1, 2, 3, 4, 5) -// [1,[2,3,4,5]] - -``` - - - - -箭头函数使用需要注意: - -- 函数体内的`this`对象,就是定义时所在的对象,而不是使用时所在的对象。 - -- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 - -- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 - -- 不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数。 - - -**`this`对象的指向是可变的,但是在箭头函数中,this对象的指向是固定的。** - -```javascript -function foo() { - setTimeout(() => { - console.log('id:', this.id); - }, 100); -} - -var id = 21; - -// call()函数修改this指向 -foo.call({ id: 42 }); -// id: 42 -``` - -`setTimeout()`的参数是一个箭头函数,这个箭头函数的定义生效是在`foo`函数生成时,而它的真正执行要等到 `100` 毫秒后。如果是普通函数,执行时`this`应该指向全局对象`window`,这时应该输出`21`。但是,**箭头函数导致this总是指向函数定义生效时所在的对象**(本例是`{id: 42}`),所以打印出来的是`42`。 - -**箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。** - -```js -function Timer() { - this.s1 = 0; - this.s2 = 0; - // 箭头函数 - setInterval(() => this.s1++, 1000); - // 普通函数 - setInterval(function () { - this.s2++; - }, 1000); -} - -var timer = new Timer(); - -setTimeout(() => console.log('s1: ', timer.s1), 3100); -setTimeout(() => console.log('s2: ', timer.s2), 3100); -// s1: 3 -// s2: 0 - -``` - -`Timer`函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的`this`绑定定义时所在的作用域(即`Timer`函数),后者的`this`指向运行时所在的作用域(即全局对象)。所以,`3100` 毫秒之后,`timer.s1`被更新了 `3` 次,而`timer.s2`一次都没更新。 - - -**箭头函数可以让this指向固定化,这种特性很有利于封装回调函数** - -```js -var handler = { - id: '123456', - - init: function() { - document.addEventListener('click', - event => this.doSomething(event.type), false); - }, - - doSomething: function(type) { - console.log('Handling ' + type + ' for ' + this.id); - } -}; -``` - -`init`方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。 - -**`this`指向的固定化,并不是因为箭头函数内部有绑定`this`的机制,实际原因是箭头函数根本没有自己的`this`,导致内部的`this`就是外层代码块的`this`。正是因为它没有`this`,所以也就不能用作构造函数。** - -```js -function foo() { - return () => { - return () => { - return () => { - console.log('id:', this.id); - }; - }; - }; -} - -var f = foo.call({id: 1}); - -var t1 = f.call({id: 2})()(); // id: 1 -var t2 = f().call({id: 3})(); // id: 1 -var t3 = f()().call({id: 4}); // id: 1 - -``` - -只有一个`this`,就是函数`foo`的`this`,所以`t1`、`t2`、`t3`都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的`this`,它们的this其实都是最外层`foo`函数的`this`。 - -除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量: -- arguments -- super -- new.target - -```js -function foo() { - setTimeout(() => { - console.log('args:', arguments); - }, 100); -} - -foo(2, 4, 6, 8) -// args: [2, 4, 6, 8] -``` - -上面代码中,箭头函数内部的变量`arguments`,其实是函数`foo`的`arguments`变量。 - - -由于箭头函数没有自己的`this`,所以当然也就不能用`call()`、`apply()`、`bind()`这些方法去改变`this`的指向。 - -```js -// 箭头函数没有自己的this -// bind方法无效,内部的this指向外部的this。 - -(function() { - return [ - (() => this.x).bind({ x: 'inner' })() - ]; -}).call({ x: 'outer' }); -// ['outer'] -``` - - -### Function.prototype.toString() - -`ES2019` 对函数实例的`toString()`方法做出了修改 - -`toString()`方法返回函数代码本身,以前会省略注释和空格。 - - -```js -// 定义函数【注意注释】 -function /* foo comment */ foo () {} - -foo.toString() -// function foo() {} - -``` - -函数`foo`的原始代码包含注释,函数名`foo`和圆括号之间有空格,但是`toString()`方法都把它们省略了。 - - - -**修改后的`toString()`方法,明确要求返回一模一样的原始代码。** - - -```js -function /* foo comment */ foo () {} - -foo.toString() -// "function /* foo comment */ foo () {}" -``` - - -### catch 命令的参数省略 - -JavaScript 语言的`try...catch`结构,以前明确要求`catch`命令后面必须跟参数,接受try代码块抛出的错误对象。 - -```js -try { - // ... -} catch (err) { - // 处理错误 -} -``` - -catch命令后面带有参数err。 - - - -但是,很多时候,catch代码块可能用不到这个参数。但是,为了保证语法正确,还是必须写。**ES2019 做出了改变,允许catch语句省略参数。** - - -```js - -try { - // ... -} catch { - // ... -} -``` \ No newline at end of file diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\345\255\227\347\254\246\344\270\262.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 1a1962a80..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,592 +0,0 @@ ---- -title: 字符串 -headerDepth: 4 ---- - -### 字符串的遍历器接口 - -ES6 为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历。 - -```javascript -// of遍历 依次输出 -for (let codePoint of 'foo') { - console.log(codePoint) -} - -``` - -### 模板字符串 - -传统的 JavaScript 语言,输出模板通常采用`+`拼接 - -```javascript -// jquery 输出模板 -$('#result').append( - 'There are ' + basket.count + ' ' + - 'items in your basket, ' + - '' + basket.onSale + - ' are on sale!' -); -``` -非常明显,写法相当繁琐且不方便,我最开始写的时候,真的'和"傻傻分不清楚,总觉得多了一个或者少了一个; - -```javascript -// ES6模板字符串 -$('#result').append(` - There are ${basket.count} items - in your basket, ${basket.onSale} - are on sale! -`); - -``` -**模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。** - -```javascript -// 普通字符串 -`In JavaScript '\n' is a line-feed.` - -// 多行字符串 -`In JavaScript this is - not legal.` - -console.log(`string text line 1 -string text line 2`); - -// 字符串中嵌入变量 -let name = "Bob", time = "today"; -`Hello ${name}, how are you ${time}?` - -``` -都是用反引号表示。**如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。** - -```javascript -let greeting = `\`Yo\` World!`; -``` - -如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。模板字符串的空格和换行,都是被保留的,如果不想要这个换行,可以使用trim方法消除它。 -- trim() 方法用于删除字符串的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等。 - -- trim() 方法不会改变原始字符串。 - -- trim() 方法不适用于 null, undefined, Number 类型。 - -**模板字符串中嵌入变量,需要将变量名写在${}之中。** - - -```javascript -// 方式比较 -function authorize(user, action) { - if (!user.hasPrivilege(action)) { - throw new Error( - // 传统写法: - // 'User ' - // + user.name - // + ' is not authorized to do ' - // + action - // + '.' - - // ES6模板语法 - `User ${user.name} is not authorized to do ${action}.`); - } -} - -``` - -**大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。** - - -```javascript -let x = 1; -let y = 2; - -`${x} + ${y} = ${x + y}` -// "1 + 2 = 3" - -`${x} + ${y * 2} = ${x + y * 2}` -// "1 + 4 = 5" - -// 定义对象 -let obj = {x: 1, y: 2}; -// 运算 -`${obj.x + obj.y}` -// "3" - -``` - -在模板字符串中也是可以调用函数的: - -```javascript -function fn() { - return "Hello World"; -} - -// 调用函数 -`foo ${fn()} bar` -// foo Hello World bar - -``` - -**如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。** - -```javascript -// 变量place没有声明,报错 -let msg = `Hello, ${place}`; - -``` - -由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串,将会原样输出。 - -```javascript -// 输出:"Hello World" -`Hello ${'World'}` -``` - -如果需要引用模板字符串本身,在需要时执行,可以写成函数。 - -```javascript -// 函数定义,箭头函数 -let func = (name) => `Hello ${name}!`; - -// 执行 -func('Jack') -// "Hello Jack!" -``` -模板字符串写成了一个函数的返回值。执行这个函数,就相当于执行这个模板字符串了。 - - - - - -### 字符串新增方法 - -- String.fromCodePoint() -- **String.raw()** -- codePointAt() -- normalize() -- **includes()、startsWith()、endsWith()** -- **repeat()** -- **padStart()、padEnd()** -- **trimStart()、trimEnd()** -- **matchAll()** -- **replaceAll()** - -#### String.fromCodePoint() - -ES5 提供`String.fromCharCode()`方法,用于从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于`0xFFFF`的字符 - -**ES6 提供了String.fromCodePoint()方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode()方法的不足。在作用上,正好与下面的codePointAt()方法相反。** - -```javascript - -String.fromCodePoint(0x20BB7) -// 输出: "𠮷" -String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' -// 输出: true - -``` -**如果String.fromCodePoint方法有多个参数,则它们会被合并成一个字符串返回。** - -注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。 - - -#### String.raw() - -raw方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。 - -```javascript -String.raw`Hi\n${2+3}!` -// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!" - -String.raw`Hi\u000A!`; -// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!" -``` -**如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义** - -```javascript -String.raw`Hi\\n` -// 返回 "Hi\\\\n" - -String.raw`Hi\\n` === "Hi\\\\n" // true - -``` - - -- String.raw()方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。 - -- String.raw()本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组,对应模板字符串解析后的值。 - -```javascript -// `foo${1 + 2}bar` -// 等同于 -String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar" - -``` - -**String.raw()方法的第一个参数是一个对象,它的raw属性等同于原始的模板字符串解析后得到的数组。** - -作为函数,String.raw()的代码实现: - -```javascript -// 定义函数,绑定到raw属性上 -String.raw = function (strings, ...values) { - let output = ''; - let index; - for (index = 0; index < values.length; index++) { - output += strings.raw[index] + values[index]; - } - - // 递归 - output += strings.raw[index] - return output; -} - -``` - -### codePointAt() - -JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为它们是两个字符。 - -```javascript -let s = "𠮷"; - -s.length // 2 -s.charAt(0) // '' -s.charAt(1) // '' -s.charCodeAt(0) // 55362 -s.charCodeAt(1) // 57271 - -``` - -ES6 提供了codePointAt()方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。 - -```javascript -let s = '𠮷a'; - -s.codePointAt(0) // 134071 -s.codePointAt(1) // 57271 - -s.codePointAt(2) // 97 - -``` - -**codePointAt()方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。** - -```javascript -function is32Bit(c) { - return c.codePointAt(0) > 0xFFFF; -} - -is32Bit("𠮷") // true -is32Bit("a") // false -``` - -### normalize() - -ES6 提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。 - -```javascript -'\u01D1'.normalize() === '\u004F\u030C'.normalize() -// true - -``` - -normalize方法可以接受一个参数来指定normalize的方式,参数的四个可选值如下。 - -- NFC,默认参数,表示“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。 -- NFD,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。 -- NFKC,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文。) -- NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。 - - - -### includes(), startsWith(), endsWith() - -传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中; - -ES6 又提供了三种新方法: - -- includes() 返回布尔值,表示是否找到了参数字符串。 - -- startsWith() 返回布尔值,表示参数字符串是否在原字符串的头部。 - -- endsWith() 返回布尔值,表示参数字符串是否在原字符串的尾部。 - -例如: - -```javascript - -let s = 'Hello world!'; - -s.startsWith('Hello') // true -s.endsWith('!') // true -s.includes('o') // true - -``` - -**这三个方法都支持第二个参数,表示开始搜索的位置。** - -```javascript -let s = 'Hello world!'; - -s.startsWith('world', 6) // true -s.endsWith('Hello', 5) // true -s.includes('Hello', 6) // false - -``` - -**使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。** - - -### repeat() - -repeat方法返回一个新字符串,表示将原字符串重复n次。 - -```javascript - -'x'.repeat(3) // "xxx" -'hello'.repeat(2) // "hellohello" -'na'.repeat(0) // "" - -``` -- 参数如果是小数,会被取整(向下取整) - -```javascript -'test'.repeat(2.9) // "testtest" -``` - -- 如果repeat的参数是负数或者Infinity,会报错。 - -```javascript -// Infinity 无穷 -'na'.repeat(Infinity) -// RangeError -'na'.repeat(-1) -// RangeError -``` - -- **如果参数是 0 到-1 之间的小数,则等同于 0**,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。 - -```javascript -'na'.repeat(-0.9) // "" - -// 参数NaN等同于 0。 -'na'.repeat(NaN) // "" - -``` - -- 如果repeat的参数是字符串,则会先转换成数字。 - -```javascript - -'na'.repeat('na') // "" -'na'.repeat('3') // "nanana" - -``` - -### padStart()、padEnd() - -ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。 -- padStart()用于头部补全 -- padEnd()用于尾部补全 - -```javascript -// 头部补齐 -'x'.padStart(5, 'ab') // 'ababx' -'x'.padStart(4, 'ab') // 'abax' - -// 尾部补齐 -'x'.padEnd(5, 'ab') // 'xabab' -'x'.padEnd(4, 'ab') // 'xaba' - -``` - -padStart()和padEnd()一共接受两个参数: -- 第一个参数是字符串补全生效的最大长度 - -- 第二个参数是用来补全的字符串。 - -在实际使用过程中,会存在如下情况: - -- **如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串** - -```javascript -'xxx'.padStart(2, 'ab') // 'xxx' -'xxx'.padEnd(2, 'ab') // 'xxx - -``` - -- **如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。** - -```javascript -'abc'.padStart(10, '0123456789') -// '0123456abc' -``` - -- **如果省略第二个参数,默认使用空格补全长度。** - -```javascript - -'x'.padStart(4) // ' x' -'x'.padEnd(4) // 'x ' -``` - - -**padStart()的常见用途是为数值补全指定位数** ,下面代码生成 10 位的数值字符串。 - -```javascript -'1'.padStart(10, '0') // "0000000001" -'12'.padStart(10, '0') // "0000000012" -'123456'.padStart(10, '0') // "0000123456" -``` - -另一个用途是提示字符串格式。 - -```javascript -'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" -'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12 - -``` - -### trimStart()、trimEnd() - -ES2019 对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致。 -- trimStart()消除字符串头部的空格 -- trimEnd()消除尾部的空格 - -它们返回的都是新字符串,不会修改原始字符串。 - -```javascript -// 定义 -const s = ' abc '; - -s.trim() // "abc" -s.trimStart() // "abc " -s.trimEnd() // " abc - -// 原始字符串不变 -console.log(s) // " abc " - -``` - -**除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。** - -浏览器还部署了额外的两个方法: - -- trimLeft()是trimStart()的别名 -- trimRight()是trimEnd()的别名 - - -### matchAll() - -matchAll()方法返回一个正则表达式在当前字符串的**所有匹配** - - -### replaceAll() - -**字符串的实例方法replace()只能替换第一个匹配。** - -```javascript -'aabbcc'.replace('b', '_') -// 'aa_bcc - -``` - -如果要替换所有的匹配,不得不使用正则表达式的g修饰符。 - -```javascript -// 全部匹配 -'aabbcc'.replace(/b/g, '_') -// 'aa__cc' -``` - -正则表达式毕竟不是那么方便和直观,**ES2021 引入了replaceAll()方法**,可以一次性替换所有匹配。 - -```javascript - -// 全局匹配 -'aabbcc'.replaceAll('b','_') -// 'aa__cc' - -``` -用法与replace()相同,返回一个新字符串,不会改变原字符串。 - -```javascript -String.prototype.replaceAll(searchValue, replacement) - -``` -**searchValue是搜索模式,可以是一个字符串,也可以是一个全局的正则表达式(带有g修饰符)。如果searchValue是一个不带有g修饰符的正则表达式,replaceAll()会报错。与replace()不同。** - -```javascript -// 不报错 -'aabbcc'.replace(/b/, '_') - -// /b/不带有g修饰符,会导致replaceAll()报错。 -'aabbcc'.replaceAll(/b/, '_') -``` - -**replaceAll()的第二个参数replacement是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。** - -- $&:匹配的子字符串。 -- $`:匹配结果前面的文本。 -- $':匹配结果后面的文本。 -- $n:匹配成功的第n组内容,n是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。 -- $$:指代美元符号$。 - -```javascript -// $& 表示匹配的字符串,即`b`本身 -// 所以返回结果与原字符串一致 -'abbc'.replaceAll('b', '$&') -// 'abbc' - -// $` 表示匹配结果之前的字符串 -// 对于第一个`b`,$` 指代`a` -// 对于第二个`b`,$` 指代`ab` -'abbc'.replaceAll('b', '$`') -// 'aaabc' - -// $' 表示匹配结果之后的字符串 -// 对于第一个`b`,$' 指代`bc` -// 对于第二个`b`,$' 指代`c` -'abbc'.replaceAll('b', `$'`) -// 'abccc' - -// $1 表示正则表达式的第一个组匹配,指代`ab` -// $2 表示正则表达式的第二个组匹配,指代`bc` -'abbc'.replaceAll(/(ab)(bc)/g, '$2$1') -// 'bcab' - -// $$ 指代 $ -'abc'.replaceAll('b', '$$') -// 'a$c' - -``` - -**replaceAll()的第二个参数replacement也可以是一个函数,该函数的返回值将替换掉第一个参数searchValue匹配的文本。** - -```javascript -// 第二个参数是一个函数,该函数的返回值会替换掉所有b的匹配。 -'aabbcc'.replaceAll('b', () => '_') -// 'aa__cc' - -``` - -**这个替换函数可以接受多个参数** - -- 第一个参数是捕捉到的匹配内容 -- 第二个参数捕捉到是组匹配(有多少个组匹配,就有多少个对应的参数) -- 最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置,最后一个参数是原字符串。 - -```javascript -const str = '123abc456'; -const regex = /(\d+)([a-z]+)(\d+)/g; - -function replacer(match, p1, p2, p3, offset, string) { - return [p1, p2, p3].join(' - '); -} - -str.replaceAll(regex, replacer) -// 123 - abc - 456 - - -``` -上面例子中,正则表达式有三个组匹配,所以replacer()函数的第一个参数match是捕捉到的匹配内容(即字符串123abc456),后面三个参数p1、p2、p3则依次为三个组匹配。 \ No newline at end of file diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\345\257\271\350\261\241.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\345\257\271\350\261\241.md" deleted file mode 100644 index c11e3b87d..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\345\257\271\350\261\241.md" +++ /dev/null @@ -1,1267 +0,0 @@ ---- -title: 对象 -headerDepth: 4 ---- - -> 对象(object)是 JavaScript 最重要的数据结构 - - -### 属性的简洁表示法 - -ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法 - -```js -// 属性名就是变量名, 属性值就是变量值 -const foo = 'bar'; -const baz = {foo}; -baz // {foo: "bar"} - -// 等同于 -const baz = {foo: foo}; -``` - -除了属性简写,方法也可以简写。 - -```js -function test(x, y) { - return {x, y}; -} - -// 等同于 -function test(x, y) { - return {x: x, y: y}; -} - -test(1, 2) // Object {x: 1, y: 2} - - -const fc = { - method() { - return "Hello!"; - } -}; - -// 等同于 -const fc = { - method: function() { - return "Hello!"; - } -}; -``` - - -CommonJS 模块输出一组变量,就非常合适使用简洁写法。 - -```js -let ms = {}; - -function getItem (key) { - return key in ms ? ms[key] : null; -} - -function setItem (key, value) { - ms[key] = value; -} - -function clear () { - ms = {}; -} - -module.exports = { getItem, setItem, clear }; -// 等同于 -module.exports = { - getItem: getItem, - setItem: setItem, - clear: clear -}; -``` - -属性的赋值器(setter)和取值器(getter),事实上也是采用这种写法。 - -```js -const cart = { - // 属性 - _wheels: 4, - - // 取值器 - get wheels () { - return this._wheels; - }, - // 赋值器 - set wheels (value) { - if (value < this._wheels) { - throw new Error('数值太小了!'); - } - this._wheels = value; - } -} -``` - -**注意,简写的对象方法不能用作构造函数,会报错** - -```js -const obj = { - test() { - this.foo = 'bar'; - } -}; -new obj.test() // 报错 -``` - -上面代码中,`test`是一个简写的对象方法,所以`obj.test`不能当作构造函数使用。 - - -### 属性名表达式 - -JavaScript 定义对象属性的两种方法 - -```js -// 方法一:直接用标识符作为属性名 -obj.foo = true; - -// 方法二:用表达式作为属性名 -obj['a' + 'bc'] = 123; - -``` - -如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。 - -```js -var obj = { - foo: true, - abc: 123 -}; -``` - -ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。 - -```js -let propKey = 'foo'; - -let obj = { - [propKey]: true, - ['a' + 'bc']: 123 -}; -``` - -**表达式还可以用于定义方法名。** - -```js -let obj = { - ['h' + 'ello']() { - return 'hi'; - } -}; - -obj.hello() // hi - -``` - -注意,属性名表达式与简洁表示法,不能同时使用,会报错。 - -```js -// 报错 -const foo = 'bar'; -const bar = 'abc'; -const baz = { [foo] }; - -// 正确 -const foo = 'bar'; -const baz = { [foo]: 'abc'}; - -``` -注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串`[object Object]` - - -```js -const keyA = {a: 1}; -const keyB = {b: 2}; - -const myObject = { - [keyA]: 'valueA', - [keyB]: 'valueB' -}; - -myObject // Object {[object Object]: "valueB"} -``` - - -### 方法的 name 属性 - -函数的`name`属性,返回函数名。对象方法也是函数,因此也有`name`属性。 - -```js -const person = { - sayName() { - console.log('hello!'); - }, -}; - -// 方法的name属性返回函数名(即方法名) -person.sayName.name // "sayName" -``` - - -如果对象的方法使用了取值函数(`getter`)和存值函数(`setter`),则name属性不是在该方法上面,而是该方法的属性的描述对象的`get`和`set`属性上面,返回值是方法名前加上`get`和`set` - -```js -const obj = { - get foo() {}, - set foo(x) {} -}; - -obj.foo.name -// TypeError: Cannot read property 'name' of undefined - - - -const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo'); - -descriptor.get.name // "get foo" -descriptor.set.name // "set foo" -``` - -**`Object.getOwnPropertyDescriptor`方法,返回某个对象属性的描述对象( `descriptor` )。** - -有两种特殊情况: - -- `bind`方法创造的函数,`name`属性返回`bound`加上原函数的名字 - -- `Function`构造函数创造的函数,`name`属性返回`anonymous`。 - -```js -(new Function()).name // "anonymous" - -var doSomething = function() { - // ... -}; -doSomething.bind().name // "bound doSomething" -``` - -如果对象的方法是一个 `Symbol` 值,那么`name`属性返回的是这个 `Symbol` 值的描述。 - -```js -const key1 = Symbol('description'); -const key2 = Symbol(); -let obj = { - [key1]() {}, - [key2]() {}, -}; -obj[key1].name // "[description]" - -// 没有Symbol描述,为空字符串 -obj[key2].name // "" - -``` - - -### 属性的可枚举性和遍历 - - -#### 可枚举性 - -对象的每个属性都有一个描述对象(`Descriptor`),用来控制该属性的行为。`Object.getOwnPropertyDescriptor`方法可以获取该属性的描述对象。 - -```js -let obj = { foo: 123 }; -Object.getOwnPropertyDescriptor(obj, 'foo') -// { -// value: 123, -// writable: true, -// enumerable: true, -// configurable: true -// } - -``` - -描述对象的`enumerable`属性,称为“可枚举性”,**如果该属性为`false`,就表示某些操作会忽略当前属性。** - -目前,有四个操作会忽略`enumerable`为`false`的属性 - -- `for...in循环`:只遍历对象自身的和继承的可枚举的属性。 -- `Object.keys()`:返回对象自身的所有可枚举的属性的键名。 -- `JSON.stringify()`:只串行化对象自身的可枚举的属性。 -- `Object.assign()`: 忽略`enumerable`为`false`的属性,只拷贝对象自身的可枚举的属性。【ES6新增】 - - - -**只有`for...in`会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。** - -> 实际上,引入“可枚举”(`enumerable`)这个概念的最初目的,就是让某些属性可以规避掉for...in操作,不然所有内部属性和方法都会被遍历到。 - - - -```js -Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable -// false - -Object.getOwnPropertyDescriptor([], 'length').enumerable -// false -``` - -对象原型的toString方法,以及数组的length属性,就通过“可枚举性”,从而避免被for...in遍历到。 - - -**ES6 规定,所有 Class 的原型的方法都是不可枚举的。** - -```js -Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable -// false -``` - -总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,只关心对象自身的属性。所以,**尽量不要用`for...in`循环,而用`Object.keys()`代替。** - - -#### 属性的遍历 - -ES6 一共有 5 种方法可以遍历对象的属性 - -- `for...in` -- `Object.keys(obj)` -- `Object.getOwnPropertyNames(Obj)` -- `Object.getOwnPropertySymbols(obj)` -- `Reflect.ownKeys(obj)` - -##### for...in - -`for...in`循环遍历对象自身的和继承的可枚举属性(不含 `Symbol`属性)。 - - -##### Object.keys(obj) - -`Object.keys`返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。 - -##### Object.getOwnPropertyNames(obj) - -`Object.getOwnPropertyNames`返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。 - -##### Object.getOwnPropertySymbols(obj) - -`Object.getOwnPropertySymbols`返回一个数组,包含对象自身的所有 `Symbol` 属性的键名。 - - -##### Reflect.ownKeys(obj) - -`Reflect.ownKeys`返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 `Symbol` 或字符串,也不管是否可枚举。 - - -**以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。** - -- 首先遍历所有数值键,按照数值升序排列。 -- 其次遍历所有字符串键,按照加入时间升序排列。 -- 最后遍历所有 `Symbol` 键,按照加入时间升序排列。 - - -```js - -// 排序规则:首先是数值属性2和10,其次是字符串属性b和a,最后是 Symbol 属性。 -Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) -// ['2', '10', 'b', 'a', Symbol()] -``` - -### super 关键字 - -`this`关键字总是指向函数所在的当前对象,`ES6` 新增了另一个类似的关键字`super`,**指向当前对象的原型对象**。 - -```js - -const proto = { - foo: 'hello' -}; - -const obj = { - foo: 'world', - find() { - return super.foo; - } -}; - - -Object.setPrototypeOf(obj, proto); - -// 对象obj.find()方法之中,通过super.foo引用了原型对象proto的foo属性。 -obj.find() // "hello" - -``` - -注意:Object.setPrototypeOf(),为现有对象设置原型,返回一个新对象,接收两个参数: - -- 第一个是现有对象 -- 第二是原型对象。 - - - -**注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。** - -```js -// 报错 -const obj = { - foo: super.foo -} - -// 报错 -const obj = { - foo: () => super.foo -} - -// 报错 -const obj = { - foo: function () { - return super.foo - } -} -``` - -上面三种`super`的用法都会报错,因为对于 `JavaScript` 引擎来说,这里的super都没有用在对象的方法之中。第一种写法是`super`用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给`foo`属性。目前,只有对象方法的简写法可以让 `JavaScript`引擎确认,定义的是对象的方法。 - -`JavaScript` 引擎内部,`super.foo`等同于`Object.getPrototypeOf(this).foo(属性)`或`Object.getPrototypeOf(this).foo.call(this)`(方法)。 - -```js -const proto = { - x: 'hello', - foo() { - console.log(this.x); - }, -}; - -const obj = { - x: 'world', - foo() { - super.foo(); - } -} - -Object.setPrototypeOf(obj, proto); - -obj.foo() // "world" -``` - -上面代码中,`super.foo`指向原型对象`proto`的`foo`方法,但是绑定的`this`却还是当前对象`obj`,因此输出的就是`world`。 - - -### 链判断运算符 - -如果读取对象内部的某个属性,往往需要判断一下该对象是否存在 - -```js -// 错误的写法 -const firstName = message.body.user.firstName; - -// 正确的写法 -const firstName = (message - && message.body - && message.body.user - && message.body.user.firstName) || 'default'; -``` -上面例子中,`firstName`属性在对象的第四层,所以需要判断四次,每一层是否有值。 - - -**三元运算符?:也常用于判断对象是否存在。** - -```js -const fooInput = myForm.querySelector('input[name=foo]') -const fooValue = fooInput ? fooInput.value : undefined -``` -这样的层层判断非常麻烦,ES2020 引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 -```js -const firstName = message?.body?.user?.firstName || 'default'; -const fooValue = myForm.querySelector('input[name=foo]')?.value -``` - -上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null`或`undefined`。如果是的,就不再往下运算,而是返回`undefined`。 - - -```js -// 判断对象方法是否存在,如果存在就立即执行 -iterator.return?.() -``` - -`iterator.return`如果有定义,就会调用该方法,否则`iterator.return`直接返回`undefined`,不再执行`?.`后面的部分。 - -```js -if (myForm.checkValidity?.() === false) { - // 表单校验失败 - return; -} -``` - -链判断运算符有三种用法: - -- `obj?.prop` : 对象属性 -- `obj?.[expr]` : 同上 -- `func?.(...args)` : 函数或对象方法的调用 - -```js -a?.b -// 等同于 -a == null ? undefined : a.b - -a?.[x] -// 等同于 -a == null ? undefined : a[x] - -a?.b() -// 等同于 -a == null ? undefined : a.b() - -a?.() -// 等同于 -a == null ? undefined : a() -``` - -特别注意后两种形式,如果`a?.b()`里面的`a.b`不是函数,不可调用,那么`a?.b()`是会报错的。`a?.()`也是如此,如果`a`不是`null`或`undefined`,但也不是函数,那么`a?.()`会报错。 - -使用链判断运算符,有几个注意点: - -- 短路机制 -- delete运算符 -- 括号的影响 -- 报错场合 -- 右侧不得为十进制数值 - -#### 短路机制 - -`?.`运算符相当于一种短路机制,只要不满足条件,就不再往下执行。**链判断运算符一旦为真,右侧的表达式就不再求值。** - - -#### delete 运算符 - -```js -delete a?.b -// 等同于 -a == null ? undefined : delete a.b -``` - -如果a是`undefined`或`null`,会直接返回`undefined`,而不会进行`delete运算`。 - -#### 括号的影响 - -如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。 - -```js -(a?.b).c -// 等价于 -(a == null ? undefined : a.b).c -``` - -**一般来说,使用`?.`运算符的场合,不应该使用圆括号。** - -#### 报错场合 - -以下写法是禁止的,会报错。 - -```js -// 构造函数 -new a?.() -new a?.b() - -// 链判断运算符的右侧有模板字符串 -a?.`{b}` -a?.b`{c}` - -// 链判断运算符的左侧是 super -super?.() -super?.foo - -// 链运算符用于赋值运算符左侧 -a?.b = c -``` - - -#### 右侧不得为十进制数值 - -为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照`三元运算符`进行处理,**也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。** - - -### Null 判断运算符 - -读取对象属性的时候,如果某个属性的值是`null`或`undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。 - -```js -const headerText = response.settings.headerText || 'Hello, world!'; -const animationDuration = response.settings.animationDuration || 300; -const showSplashScreen = response.settings.showSplashScreen || true; -``` - -开发者的原意是,只要属性的值为null或undefined,默认值就会生效,**但是属性的值如果为空字符串或false或0,默认值也会生效。** - -为了避免这种情况,ES2020 引入了一个新的 `Null` 判断运算符`??`。 - -行为类似`||`,但是**只有运算符左侧的值为`null`或`undefined`时,才会返回右侧的值。** - -这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值。 - -```js -const animationDuration = response.settings?.animationDuration ?? 300; -``` - -这个运算符很适合判断函数参数是否赋值。 - -```js -function Component(props) { - const enable = props.enabled ?? true; - // … -} - -``` - - -## 对象新增方法 - -* `Object.is()` -* `Object.assign()` -* `Object.getOwnPropertyDescriptors()` -* `__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()` -* `Object.keys(),Object.values(),Object.entries()` -* `Object.fromEntries()` - -### `Object.is()` - -ES5 比较两个值是否相等,只有两个运算符:相等运算符(`==`)和严格相等运算符(`===`) - -* 相等运算符(`==`)会自动转换数据类型 -* 严格相等运算符(`===`)的NaN不等于自身 - -> ES6 提出“Same-value equality”(同值相等)算法,**在所有环境中,只要两个值是一样的,它们就应该相等。** - -**`Object.is()`用来比较两个值是否严格相等,与严格比较运算符(`===`)的行为基本一致。** - -```js -Object.is('foo', 'foo') -// true -Object.is({}, {}) -// false -``` - -不同之处只有两个: - -* `Object.is()`的`+0`不等于`-0` -* `Object.is()`的`NaN`等于自身 - -```js -+0 === -0 //true -NaN === NaN // false - -Object.is(+0, -0) // false -Object.is(NaN, NaN) // true -``` - -类似功能实现: - -```js - -// 实现 Object.is()功能 -Object.defineProperty(Object, 'is', { - value: function(x, y) { - if (x === y) { - // 针对+0 不等于 -0的情况 - return x !== 0 || 1 / x === 1 / y; - } - // 针对NaN的情况 - return x !== x && y !== y; - }, - configurable: true, - enumerable: false, - writable: true -}); -``` - -### `Object.assign()` - -`Object.assign()`方法用于对象的合并,将源对象(`source`)的所有可枚举属性,复制到目标对象(`target`)。 - -```js -const target = { a: 1 }; - -const source1 = { b: 2 }; -const source2 = { c: 3 }; - -Object.assign(target, source1, source2); -target // {a:1, b:2, c:3} -``` - -**`Object.assign()`方法的第一个参数是目标对象,后面的参数都是源对象。** - -注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。 - -```js -const target = { a: 1, b: 1 }; - -const source1 = { b: 2, c: 2 }; -const source2 = { c: 3 }; - -Object.assign(target, source1, source2); -target // {a:1, b:2, c:3} -``` - -如果只有一个参数,Object.assign()会直接返回该参数。 - -```js -const obj = {a: 1}; -Object.assign(obj) === obj // true -``` - -如果该参数不是对象,则会先转成对象,然后返回。 - -```js -typeof Object.assign(2) // "object" -``` - -**由于`undefined`和`null`无法转成对象,所以如果它们作为参数,就会报错。** - -```js -Object.assign(undefined) // 报错 -Object.assign(null) // 报错 -``` - -如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果`undefined`和`null`不在首参数,就不会报错。 - -```js -let obj = {a: 1}; -Object.assign(obj, undefined) === obj // true -Object.assign(obj, null) === obj // true -``` - -**其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。** - -除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。 - -```js -const v1 = 'abc'; -const v2 = true; -const v3 = 10; - -const obj = Object.assign({}, v1, v2, v3); -console.log(obj); // { "0": "a", "1": "b", "2": "c" } -``` - -只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。**因为只有字符串的包装对象,会产生可枚举属性。** - -```js -Object(true) // {[[PrimitiveValue]]: true} -Object(10) // {[[PrimitiveValue]]: 10} -Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"} - -``` - -`布尔值`、`数值`、`字符串`分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性`[[PrimitiveValue]]`上面,这个属性是不会被`Object.assign()`拷贝的。只有字符串的包装对象,会产生`可枚举的实义属性`,那些属性则会被拷贝。 - -Object.assign()拷贝的属性是有限制的 - -* 只拷贝源对象的自身属性 -* 不拷贝继承属性 -* 不拷贝不可枚举的属性(enumerable: false)。 - -```js - -Object.assign({b: 'c'}, - Object.defineProperty({}, 'invisible', { - enumerable: false, - value: 'hello' - }) -) - -// Object.assign()要拷贝的对象只有一个不可枚举属性invisible,这个属性没有被拷贝进去。 -// { b: 'c' } -``` - -属性名为 Symbol 值的属性,也会被Object.assign()拷贝。 - -```js -Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) -// { a: 'b', Symbol(c): 'd' } -``` - -#### 需要注意 - -* 浅拷贝 - -`Object.assign()`方法实行的是浅拷贝,而不是深拷贝。 -**如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个`对象的引用`。** - -```js -const obj1 = {a: {b: 1}}; -const obj2 = Object.assign({}, obj1); - -// obj1.a.b的任何变化,都会反映到obj2.a.b上面。 -obj1.a.b = 2; -obj2.a.b // 2 -``` - -* 同名属性的替换 - -对于这种嵌套的对象,一旦遇到同名属性,`Object.assign()`的处理方法是替换,而不是添加。 - -一些函数库提供`Object.assign()`的定制版本(比如 `Lodash` 的`_.defaultsDeep()`方法),可以得到深拷贝的合并。 - -* 数组的处理 - -`Object.assign()`可以用来处理数组,但是会把数组视为对象。 - -```js - -Object.assign([1, 2, 3], [4, 5]) -// [4, 5, 3] - -``` - -上面代码中,`Object.assign()`把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。 - -* 取值函数的处理 - -`Object.assign()`只能进行值的复制,如果要复制的值是一个取值函数,那么**将求值后再复制**。 - -```js -const source = { - get foo() { return 1 } -}; -const target = {}; - -Object.assign(target, source) -// { foo: 1 } -``` - -上面代码中,`source`对象的`foo`属性是一个取值函数,`Object.assign()`不会复制这个取值函数,只会拿到值以后,将这个值复制过去。 - -#### 常见用途 - -* 为对象添加属性 - -```js -// 将x属性和y属性添加到Point类的对象实例。 -class Point { - constructor(x, y) { - Object.assign(this, {x, y}); - } -} -``` - -* 为对象添加方法 - -```js -Object.assign(SomeClass.prototype, { - someMethod(arg1, arg2) { - ··· - }, - anotherMethod() { - ··· - } -}); - -// 等同于下面的写法 -SomeClass.prototype.someMethod = function (arg1, arg2) { - ··· -}; -SomeClass.prototype.anotherMethod = function () { - ··· -}; -``` - -* 克隆对象 - -```js -function clone(origin) { - return Object.assign({}, origin); -} -``` - -**采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值**。 - -如果想要保持继承链,可以采用下面的代码。 - -```js -function clone(origin) { - let originProto = Object.getPrototypeOf(origin); - - return Object.assign(Object.create(originProto), origin); -} -``` - -[Object.create()和new object()和{}的区别](https://www.cnblogs.com/leijee/p/7490822.html) - -* 合并多个对象 - -```js -// 将多个对象合并到某个对象 -const merge = - (target, ...sources) => Object.assign(target, ...sources); - -// 对一个空对象合并,合并后返回一个新对象 -const merge = - (...sources) => Object.assign({}, ...sources); -``` - -* 为属性指定默认值 - -```js -const DEFAULTS = { - logLevel: 0, - outputFormat: 'html' -}; - -function processContent(options) { - options = Object.assign({}, DEFAULTS, options); - console.log(options); - // ... -} -``` - -上面代码中 - -* `DEFAULTS`对象是默认值 -* `options`对象是用户提供的参数。 - -`Object.assign()`方法将`DEFAULTS`和`options`合并成一个新对象,如果两者有同名属性,则`options`的属性值会覆盖`DEFAULTS`的属性值。 - -**注意,由于存在浅拷贝的问题,`DEFAULTS`对象和`options`对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,`DEFAULTS`对象的该属性很可能不起作用。** - -### Object.getOwnPropertyDescriptors() - -`ES5`的`Object.getOwnPropertyDescriptor()`方法会返回某个对象属性的描述对象(`descriptor`) - -`ES2017` 引入了`Object.getOwnPropertyDescriptors()`方法,返回指定对象所有自身属性(非继承属性)的描述对象。 - -```js - -// 相关实现 -function getOwnPropertyDescriptors(obj) { - const result = {}; - for (let key of Reflect.ownKeys(obj)) { - result[key] = Object.getOwnPropertyDescriptor(obj, key); - } - return result; -} -``` - -`getOwnPropertyDescriptors`该方法的引入目的,主要是为了解决`Object.assign()`无法正确拷贝`get`属性和`set`属性的问题。 - -```js -const source = { - set foo(value) { - console.log(value); - } -}; - -const target1 = {}; -Object.assign(target1, source); - -Object.getOwnPropertyDescriptor(target1, 'foo') -// { value: undefined, -// writable: true, -// enumerable: true, -// configurable: true } -``` - -上面代码中,`source`对象的`foo`属性的值是一个赋值函数,`Object.assign`方法将这个属性拷贝给`target1`对象,结果该属性的值变成了`undefined`。 -**这是因为`Object.assign`方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。** - -这时,`Object.getOwnPropertyDescriptors()`方法配合`Object.defineProperties()`方法,就可以实现正确拷贝。 - -```js -const source = { - set foo(value) { - console.log(value); - } -}; - -const target2 = {}; -Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source)); -Object.getOwnPropertyDescriptor(target2, 'foo') -// { get: undefined, -// set: [Function: set foo], -// enumerable: true, -// configurable: true } - - -// 抽象成函数 -const shallowMerge = (target, source) => Object.defineProperties( - target,Object.getOwnPropertyDescriptors(source) -); -``` - -### \_\_proto\_\_属性,Object.setPrototypeOf(),Object.getPrototypeOf() - -> JavaScript 语言的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作方法 - -#### \_\_proto\_\_属性 - -`__proto__`属性(前后各两个下划线),用来读取或设置当前对象的原型对象(`prototype`)。目前,所有浏览器(包括 `IE11`)都部署了这个属性。 - -```js -// es5 的写法 -const obj = { - method: function() { ... } -}; -obj.__proto__ = someOtherObj; - -// es6 的写法 -var obj = Object.create(someOtherObj); -obj.method = function() { ... }; -``` - -可以使用 - -* `Object.setPrototypeOf()`(写操作) -* `Object.getPrototypeOf()`(读操作) -* `Object.create()`(生成操作) - -代替实现。 - -实现上,`__proto__`调用的是`Object.prototype.__proto__` - -```js - -Object.defineProperty(Object.prototype, '__proto__', { - get() { - let _thisObj = Object(this); - return Object.getPrototypeOf(_thisObj); - }, - set(proto) { - if (this === undefined || this === null) { - throw new TypeError(); - } - if (!isObject(this)) { - return undefined; - } - if (!isObject(proto)) { - return undefined; - } - let status = Reflect.setPrototypeOf(this, proto); - if (!status) { - throw new TypeError(); - } - }, -}); - -function isObject(value) { - return Object(value) === value; -} - -``` - -如果一个对象本身部署了`__proto__`属性,该属性的值就是对象的原型。 - -```js -Object.getPrototypeOf({ __proto__: null }) -// null -``` - -#### Object.setPrototypeOf() - -`Object.setPrototypeOf`方法的作用与`__proto__`相同,用来设置一个对象的原型对象(`prototype`),返回参数对象本身,是 ES6 正式推荐的设置原型对象的方法。 - -```js -// 格式 -Object.setPrototypeOf(object, prototype) - -// 用法 -const o = Object.setPrototypeOf({}, null); - -// 等同于 -function setPrototypeOf(obj, proto) { - obj.__proto__ = proto; - return obj; -} -``` - -很经典的例子: - -```js -let proto = {}; -let obj = { x: 10 }; -Object.setPrototypeOf(obj, proto); - -proto.y = 20; -proto.z = 40; - -obj.x // 10 -obj.y // 20 -obj.z // 40 -``` - -将`proto`对象设为`obj`对象的原型,所以从`obj`对象可以读取`proto`对象的属性。 - -**如果第一个参数不是对象,会自动转为对象**。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。 - -```js -Object.setPrototypeOf(1, {}) === 1 // true -Object.setPrototypeOf('foo', {}) === 'foo' // true -Object.setPrototypeOf(true, {}) === true // true -``` - -由于`undefined`和`null`无法转为对象,所以如果第一个参数是`undefined`或`null`,就会报错。 - -```js -Object.setPrototypeOf(undefined, {}) -// TypeError: Object.setPrototypeOf called on null or undefined - -Object.setPrototypeOf(null, {}) -// TypeError: Object.setPrototypeOf called on null or undefined -``` - -#### Object.getPrototypeOf() - -与`Object.setPrototypeOf`方法配套,用于读取一个对象的原型对象。 - -```js -// 基本使用 -Object.getPrototypeOf(obj); - -// 原型设置和获取 -function User() { - // ... -} - -const user = new User(); - -Object.getPrototypeOf(user) === User.prototype -// true - -Object.setPrototypeOf(user, Object.prototype); -Object.getPrototypeOf(user) === User.prototype -// false -``` - -如果参数不是对象,会被自动转为对象。 - -```js -// 等同于 Object.getPrototypeOf(Number(1)) -Object.getPrototypeOf(1) -// Number {[[PrimitiveValue]]: 0} - -// 等同于 Object.getPrototypeOf(String('foo')) -Object.getPrototypeOf('foo') -// String {length: 0, [[PrimitiveValue]]: ""} - -// 等同于 Object.getPrototypeOf(Boolean(true)) -Object.getPrototypeOf(true) -// Boolean {[[PrimitiveValue]]: false} - -Object.getPrototypeOf(1) === Number.prototype // true -Object.getPrototypeOf('foo') === String.prototype // true -Object.getPrototypeOf(true) === Boolean.prototype // true -``` - -如果参数是`undefined`或`null`,它们无法转为对象,所以会报错。 - -```js -Object.getPrototypeOf(null) -// TypeError: Cannot convert undefined or null to object - -Object.getPrototypeOf(undefined) -// TypeError: Cannot convert undefined or null to object -``` - -### Object.keys(),Object.values(),Object.entries() - -#### Object.keys() - -ES5 引入了`Object.keys`方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。 - -```js -const obj = { name: 'bob', age: 24 }; -Object.keys(obj) -// ["name", "age"] -``` - -#### Object.values() - -`Object.values`方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(`enumerable`)属性的键值。 - -```js -const obj = { name: 'bob', age: 24 }; -Object.values(obj) -// ["bob", 24] -``` - -* `Object.values`只返回对象自身的可遍历属性。 - -* `Object.values`会过滤属性名为 Symbol 值的属性。 - -* 如果参数不是对象,`Object.values`会先将其转为对象。**由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。`Object.values`会返回空数组。** - -#### Object.entries() - -`Object.entries()`方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(`enumerable`)属性的键值对数组。 - -`Object.entries`的基本用途是遍历对象的属性。 - -```js -let obj = { one: 1, two: 2 }; -for (let [k, v] of Object.entries(obj)) { - console.log( - `${JSON.stringify(k)}: ${JSON.stringify(v)}` - ); -} -// "one": 1 -// "two": 2 -``` - -`Object.entries`方法的另一个用处是,将对象转为真正的Map结构。 - -```js -const obj = { name: 'bob', age: 24 }; -const map = new Map(Object.entries(obj)); -map // Map { name: "bob", age: 24 } -``` - -自己实现`Object.entries`方法,循环遍历 - -```js -// Generator函数的版本 -function* entries(obj) { - for (let key of Object.keys(obj)) { - yield [key, obj[key]]; - } -} - -// 非Generator函数的版本 -function entries(obj) { - let arr = []; - for (let key of Object.keys(obj)) { - arr.push([key, obj[key]]); - } - return arr; -} -``` - -### Object.fromEntries() - -`Object.fromEntries()`方法是`Object.entries()`的逆操作,用于将一个键值对数组转为对象。 - -```js -Object.fromEntries([ - ['name', 'bob'], - ['age', 24] -]) -// { name: "bob", age: 24 } -``` - -该方法的主要目的,是将键值对的数据结构还原为对象,**特别适合将 Map 结构转为对象**。 - -```js -// 例一 -const entries = new Map([ - ['name', 'bob'], - ['age', 24] -]); - -Object.fromEntries(entries) -// { name: "bob", age: 24 } - -// 例二 -const map = new Map().set('foo', true).set('bar', false); -Object.fromEntries(map) -// { foo: true, bar: false } -``` - -该方法的一个用处是配合`URLSearchParams`对象,将查询字符串转为对象。 - -```js - -// url模块中获取URLSearchParams -const { URLSearchParams } = require('url'); -Object.fromEntries(new URLSearchParams('name=bob&age=24')) -// { name: "bob", age: 24 } -``` - diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\346\225\260\345\200\274.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\346\225\260\345\200\274.md" deleted file mode 100644 index 20df4a665..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\346\225\260\345\200\274.md" +++ /dev/null @@ -1,234 +0,0 @@ ---- -title: 数值 -headerDepth: 4 ---- - - - -### Number.isFinite() 和 Number.isNaN() - -ES6 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法 - - -`Number.isFinite()`用来检查一个数值是否为有限的(`finite`),即不是Infinity。 - - -```javascript -Number.isFinite(15); // true -Number.isFinite(0.8); // true -Number.isFinite(NaN); // false -Number.isFinite(Infinity); // false -Number.isFinite(-Infinity); // false -Number.isFinite('foo'); // false -Number.isFinite('15'); // false -Number.isFinite(true); // false -``` - -**如果参数类型不是数值,Number.isFinite一律返回false** - -`Number.isNaN()`用来检查一个值是否为`NaN`(Not A Number)。 - -```javascript -Number.isNaN(NaN) // true -Number.isNaN(15) // false -Number.isNaN('15') // false -Number.isNaN(true) // false -Number.isNaN(9/NaN) // true -Number.isNaN('true' / 0) // true -Number.isNaN('true' / 'true') // true -``` - -如果参数类型不是`NaN`,`Number.isNaN`一律返回`false`。 - -#### 重要区别 - -> 与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。 - - -```javascript - -isFinite(25) // true -isFinite("25") // true -Number.isFinite(25) // true -Number.isFinite("25") // false - -isNaN(NaN) // true -isNaN("NaN") // true -Number.isNaN(NaN) // true -Number.isNaN("NaN") // false -Number.isNaN(1) // false - -``` - - -### Number.parseInt()和Number.parseFloat() - -ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。 - -```javascript -// ES5的写法 -parseInt('12.34') // 12 -parseFloat('123.45#') // 123.45 - -// ES6的写法 -Number.parseInt('12.34') // 12 -Number.parseFloat('123.45#') // 123.45 -``` - - -**逐步减少全局性方法,使得语言逐步模块化。** - -```javascript -Number.parseInt === parseInt // true -Number.parseFloat === parseFloat // true -``` - -### Number.isInteger() - -`Number.isInteger()`用来判断一个数值是否为整数。 - -```javascript -Number.isInteger(25) // true -Number.isInteger(25.1) // false - -// 整数和浮点数采用的是同样的储存方法 -Number.isInteger(25) // true -Number.isInteger(25.0) // true -``` - -**JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。** - -如果参数不是数值,`Number.isInteger`返回`false`。 - -```javascript -Number.isInteger() // false -Number.isInteger(null) // false -Number.isInteger('15') // false -Number.isInteger(true) // false -``` - -### Math 对象的扩展 - -#### Math.trunc() - -`Math.trunc()`方法用于去除一个数的小数部分,返回整数部分 - -```javascript -Math.trunc(4.1) // 4 -Math.trunc(4.9) // 4 -Math.trunc(-4.1) // -4 -Math.trunc(-4.9) // -4 -Math.trunc(-0.1234) // -0 -``` - -对于非数值,`Math.trunc`内部使用`Number`方法将其先转为数值 - -```javascript -Math.trunc('123.456') // 123 -Math.trunc(true) //1 -Math.trunc(false) // 0 -Math.trunc(null) // 0 -``` - -对于空值和无法截取整数的值,返回`NaN`。 - -```javascript -Math.trunc(NaN); // NaN -Math.trunc('foo'); // NaN -Math.trunc(); // NaN -Math.trunc(undefined) // NaN -``` - -`Math.trunc()`的类似实现: - -```javascript -Math.trunc = Math.trunc || function(x) { - return x < 0 ? Math.ceil(x) : Math.floor(x); -}; - -``` - - -#### Math.sign() - -Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。 - -- 参数为正数,返回+1; -- 参数为负数,返回-1; -- 参数为 0,返回0; -- 参数为-0,返回-0; -- 其他值,返回NaN。 - - -如果参数是非数值,会自动转为数值。对于那些无法转为数值的值,会返回NaN。 - -```javascript -Math.sign('') // 0 -Math.sign(true) // +1 -Math.sign(false) // 0 -Math.sign(null) // 0 -Math.sign('9') // +1 -Math.sign('foo') // NaN -Math.sign() // NaN -Math.sign(undefined) // NaN -``` - -Math.sign()的类似实现: - -```javascript -// 判断正数、负数、还是零 -Math.sign = Math.sign || function(x) { - x = +x; // convert to a number - if (x === 0 || isNaN(x)) { - return x; - } - return x > 0 ? 1 : -1; -}; -``` - - -#### Math.cbrt() - -Math.cbrt()方法用于计算一个数的立方根。 - -```javascript -Math.cbrt(-1) // -1 -Math.cbrt(0) // 0 -Math.cbrt(1) // 1 -Math.cbrt(2) // 1.2599210498948732 -``` - -对于非数值,Math.cbrt()方法内部也是先使用Number()方法将其转为数值。 - -```javascript -Math.cbrt('8') // 2 -Math.cbrt('hello') // NaN -``` - -`Math.cbrt()`类似实现: - -```javascript -// 计算一个数的立方根 -Math.cbrt = Math.cbrt || function(x) { - var y = Math.pow(Math.abs(x), 1/3); - return x < 0 ? -y : y; -}; -``` - - -#### Math.hypot() -Math.hypot方法返回所有参数的平方和的平方根。 - -```javascript - -// 3 的平方加上 4 的平方,等于 5 的平方。 -Math.hypot(3, 4); // 5 -Math.hypot(3, 4, 5); // 7.0710678118654755 -Math.hypot(); // 0 -Math.hypot(NaN); // NaN -Math.hypot(3, 4, 'foo'); // NaN -Math.hypot(3, 4, '5'); // 7.0710678118654755 -Math.hypot(-3); // 3 -``` - -如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。 diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\346\225\260\347\273\204.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\346\225\260\347\273\204.md" deleted file mode 100644 index 83bcc49c3..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\346\225\260\347\273\204.md" +++ /dev/null @@ -1,1003 +0,0 @@ ---- -title: 数组 -headerDepth: 4 ---- - -### 扩展运算符 - -扩展运算符(`spread`)是三个点(`...`)。它好比 `rest` 参数的逆运算,**将一个数组转为用逗号分隔的参数序列**。 - -```js -console.log(...[1, 2, 3]) -// 1 2 3 - -console.log(1, ...[2, 3, 4], 5) -// 1 2 3 4 5 -``` - -主要用于函数调用。 - - -```js -// 将数组转化为逗号分隔的参数序列 -function push(array, ...items) { - array.push(...items); -} - -function add(x, y) { - return x + y; -} - -const numbers = [4, 38]; -add(...numbers) // 42 - -``` - -扩展运算符与正常的函数参数可以结合使用,非常灵活。 - -```js -function test(v, w, x, y, z) { } -const args = [0, 1]; - -// 调用 -test(-1, ...args, 2, ...[3]); -``` - - -扩展运算符后面还可以放置表达式。 - -```js - -// 结合三目运算 -const arr = [ - ...(x > 0 ? ['a'] : []), - 'b', -]; -``` - -**如果扩展运算符后面是一个空数组,则不产生任何效果。** - -```js -[...[], 1] -// [1] -``` - -**只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。** - - -```text - -// 扩展运算符所在的括号不是函数调用。 - -(...[1, 2]) -// Uncaught SyntaxError: Unexpected number - -console.log((...[1, 2])) -// Uncaught SyntaxError: Unexpected number - - -// 正常函数调用情况 -console.log(...[1, 2]) -// 1 2 - -``` - - -#### 替代函数的 apply 方法 - -由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。 - - -```js -// ES5 的写法 -function test(x, y, z) { - // ... -} -// 实际调用 -var args = [0, 1, 2]; -test.apply(null, args); - - -// ES6的写法 -function test(x, y, z) { - // ... -} -// 实际调用 -let args = [0, 1, 2]; -test(...args); - - - -// 应用Math.max方法的简化应用 - -// ES5 的写法 -Math.max.apply(null, [14, 3, 77]) - -// ES6 的写法 -Math.max(...[14, 3, 77]) - -// 等同于 -Math.max(14, 3, 77); -``` - -由于 JavaScript 不提供求数组最大元素的函数,所以只能**套用`Math.max`函数,将数组转为一个参数序列,然后求最大值。** 有了扩展运算符以后,就可以直接用`Math.max`了。 - - - -```js -// ES5的 写法 -var arr1 = [0, 1, 2]; -var arr2 = [3, 4, 5]; -Array.prototype.push.apply(arr1, arr2); - -// ES6 的写法 -let arr1 = [0, 1, 2]; -let arr2 = [3, 4, 5]; -arr1.push(...arr2); - - -// ES5 -new (Date.bind.apply(Date, [null, 2015, 1, 1])) -// ES6 -new Date(...[2015, 1, 1]); -``` - -#### 扩展运算符的应用 - -- 复制数组 -- 合并数组 -- 与解构赋值结合 -- 字符串 -- 实现了 Iterator 接口的对象 -- Map 和 Set 结构,Generator 函数 - - -##### 复制数组 - -**数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组** - - -```js -const a1 = [1, 2]; -const a2 = a1; - - -a2[0] = 2; -a1 // [2, 2] -``` -a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。 - -```js -// ES5 只能用变通方法来复制数组。 -const a1 = [1, 2]; -const a2 = a1.concat(); - -a2[0] = 2; -a1 // [1, 2] - -``` -a1会返回原数组的克隆,再修改a2就不会对a1产生影响。 - -```js -// 扩展运算符提供了复制数组的简便写法【都是克隆】。 -const a1 = [1, 2]; -// 写法一 -const a2 = [...a1]; -// 写法二 -const [...a2] = a1; -``` - -##### 合并数组 - -扩展运算符提供了数组合并的新写法。 - - -```js -const arr1 = ['a', 'b']; -const arr2 = ['c']; -const arr3 = ['d', 'e']; - -// ES5 的合并数组 -arr1.concat(arr2, arr3); -// [ 'a', 'b', 'c', 'd', 'e' ] - -// ES6 的合并数组 -[...arr1, ...arr2, ...arr3] -// [ 'a', 'b', 'c', 'd', 'e' ] -``` - -不过,这两种方法都是浅拷贝,使用的时候需要注意。 - -```js -const a1 = [{ foo: 1 }]; -const a2 = [{ bar: 2 }]; - -const a3 = a1.concat(a2); -const a4 = [...a1, ...a2]; - -a3[0] === a1[0] // true -a4[0] === a1[0] // true -``` -a3和a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。**如果修改了引用指向的值,会同步反映到新数组。** - -#### 与解构赋值结合 - -扩展运算符可以与解构赋值结合起来,用于生成数组。 - -```text -// ES5 -a = list[0], rest = list.slice(1) -// ES6 -[a, ...rest] = list - -const [first, ...rest] = [1, 2, 3, 4, 5]; -first // 1 -rest // [2, 3, 4, 5] - -const [first, ...rest] = []; -first // undefined -rest // [] - -const [first, ...rest] = ["foo"]; -first // "foo" -rest // [] -``` - -如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。 - -```text -const [...butLast, last] = [1, 2, 3, 4, 5]; -// 报错 - -const [first, ...middle, last] = [1, 2, 3, 4, 5]; -// 报错 -``` - -#### 字符串 - -扩展运算符还可以将字符串转为真正的数组。 - -```js -[...'hello'] -// [ "h", "e", "l", "l", "o" ] -``` - - -#### 实现了 Iterator 接口的对象 - -任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组 - -```js -let nodeList = document.querySelectorAll('div'); -let array = [...nodeList]; -``` - -`querySelectorAll`方法返回的是一个`NodeList`对象。**它不是数组,而是一个类似数组的对象**。这时,扩展运算符可以将其转为真正的数组,原因就在于`NodeList`对象实现了`Iterator` 。 - - -```js -// arrayLike是一个类似数组的对象,但是没有部署 Iterator 接口 -let arrayLike = { - '0': 'a', - '1': 'b', - '2': 'c', - length: 3 -}; - -// TypeError: Cannot spread non-iterable object. -let arr = [...arrayLike]; - -``` - -对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。 - - -#### Map 和 Set 结构,Generator 函数 - -**扩展运算符内部调用的是数据结构的 Iterator 接口**,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。 - - -```js -let map = new Map([ - [1, 'one'], - [2, 'two'], - [3, 'three'], -]); - -let arr = [...map.keys()]; // [1, 2, 3] -``` - -**Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。** - -```js -const go = function*(){ - yield 1; - yield 2; - yield 3; -}; - -[...go()] // [1, 2, 3] -``` - -如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。 - -```js -const obj = {a: 1, b: 2}; -// TypeError: Cannot spread non-iterable object -let arr = [...obj]; -``` - - -### Array.from() - -`Array.from`方法用于将两类对象转为真正的数组: - -- 类似数组的对象(array-like object) -- 可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map) - - -```js -let arrayLike = { - '0': 'a', - '1': 'b', - '2': 'c', - length: 3 -}; - -// ES5的写法 -var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] - -// ES6的写法 -let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'] -``` - -实际应用中,常见的类似数组的对象是 `DOM` 操作返回的 `NodeList` 集合,以及函数内部的`arguments`对象。`Array.from`都可以将它们转为真正的数组 - - -```js -// NodeList对象 -let ps = document.querySelectorAll('p'); -Array.from(ps).filter(p => { - return p.textContent.length > 100; -}); - -// arguments对象 -function foo() { - // 转化成数组 - var args = Array.from(arguments); - // ... -} - -``` - -**只要是部署了 `Iterator` 接口的数据结构,`Array.from`都能将其转为数组。** - -```js -// 字符串和 Set 结构都具有 Iterator 接口 -Array.from('hello') -// ['h', 'e', 'l', 'l', 'o'] - -let namesSet = new Set(['a', 'b']) -Array.from(namesSet) // ['a', 'b'] -``` - -**如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组。** - -```js -Array.from([1, 2, 3]) -// [1, 2, 3] -``` - -**扩展运算符(...)也可以将某些数据结构转为数组。** - -```js -// arguments对象 -function foo() { - // 扩展运算符,效果和Array.from一样 - const args = [...arguments]; -} - -``` - -**`Array.from`方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有`length`属性。因此,任何有`length`属性的对象,都可以通过`Array.from`方法转为数组,而此时扩展运算符就无法转换。** - -```js -Array.from({ length: 3 }); -// [ undefined, undefined, undefined ] -``` - -`Array.from`返回了一个具有三个成员的数组,每个位置的值都是`undefined`。扩展运算符转换不了这个对象 - - -对于还没有部署该方法的浏览器,可以用`Array.prototype.slice`方法替代。 - -```js - -// 兼容存在Array.from情况 -const toArray = (() => - Array.from ? Array.from : obj => [].slice.call(obj) -)(); -``` - -**`Array.from`还可以接受第二个参数,作用类似于数组的`map`方法,用来对每个元素进行处理,将处理后的值放入返回的数组。** - -```js -Array.from(arrayLike, x => x * x); -// 等同于 -Array.from(arrayLike).map(x => x * x); - -Array.from([1, 2, 3], (x) => x * x) -// [1, 4, 9] -``` - - -`Array.from()`可以将各种值转为真正的数组,并且还提供`map`功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。 - -```js -Array.from({ length: 2 }, () => 'jack') -// ['jack', 'jack'] -``` - -上面代码中,Array.from的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。 - -`Array.from()`的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种 `Unicode` 字符,可以避免 JavaScript 将大于`\uFFFF`的 `Unicode` 字符,算作两个字符的 `bug`。 - -```js -function countSymbols(string) { - return Array.from(string).length; -} - -``` - - -### Array.of() - -**`Array.of()`方法用于将一组值,转换为数组。** - -```js -Array.of(3, 11, 8) // [3,11,8] -Array.of(3) // [3] -Array.of(3).length // 1 -``` - -弥补数组构造函数`Array()`的不足。因为参数个数的不同,会导致`Array()`的行为有差异。 - -```js -Array() // [] -Array(3) // [, , ,] -Array(3, 11, 8) // [3, 11, 8] -``` -`Array()`方法没有参数、一个参数、三个参数时,返回的结果都不一样。 -- 只有当参数个数不少于 2 个时,`Array()`才会返回由参数组成的新数组。 -- 参数只有一个正整数时,实际上是指定数组的长度。 - - -`Array.of()`基本上可以用来替代`Array()`或`new Array()`,并且不存在由于参数不同而导致的重载,行为非常统一。 - -```js -Array.of() // [] -Array.of(undefined) // [undefined] -Array.of(1) // [1] -Array.of(1, 2) // [1, 2] -``` - -**`Array.of()`总是返回参数值组成的数组。如果没有参数,就返回一个空数组。** - - -`Array.of()`方法可以用下面的代码模拟实现。 - -```js -function ArrayOf(){ - // arguments 参数数组 - return [].slice.call(arguments); -} -``` - -### copyWithin() - -数组实例的`copyWithin()`方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。 - -```js -Array.prototype.copyWithin(target, start = 0, end = this.length) -``` - -接受三个参数: - -- target(必需):从该位置开始替换数据。如果为负值,表示倒数。 -- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。 -- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。 - -```text -[1, 2, 3, 4, 5].copyWithin(0, 3) -// [4, 5, 3, 4, 5] - -// 将3号位复制到0号位 -[1, 2, 3, 4, 5].copyWithin(0, 3, 4) -// [4, 2, 3, 4, 5] - -// -2相当于3号位,-1相当于4号位 -[1, 2, 3, 4, 5].copyWithin(0, -2, -1) -// [4, 2, 3, 4, 5] - -// 将3号位复制到0号位 -[].copyWithin.call({length: 5, 3: 1}, 0, 3) -// {0: 1, 3: 1, length: 5} - -// 将2号位到数组结束,复制到0号位 -let i32a = new Int32Array([1, 2, 3, 4, 5]); -i32a.copyWithin(0, 2); -// Int32Array [3, 4, 5, 4, 5] - -// 对于没有部署 TypedArray 的 copyWithin 方法的平台 -// 需要采用下面的写法 -[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4); -// Int32Array [4, 2, 3, 4, 5] -``` - -### find() 和 findIndex() - -数组实例的`find`方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为`true`的成员,然后返回该成员。如果没有符合条件的成员,则返回`undefined`。 - -```js -[1, 4, -5, 10].find((n) => n < 0) -// -5 - -[1, 5, 10, 15].find(function(value, index, arr) { - return value > 9; -}) // 10 -``` - -**find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。** - -数组实例的findIndex方法的用法与find方法非常类似,**返回第一个符合条件的数组成员的位置**,如果所有成员都不符合条件,则返回`-1`。 - -```js -// 返回第一个符合条件的数组成员的位置 -[1, 5, 10, 15].findIndex(function(value, index, arr) { - return value > 9; -}) // 2 -``` - -两个方法都可以接受第二个参数,用来绑定回调函数的this对象。 - -```js -// 回调函数中的this对象指向person对象。 -function f(v){ - return v > this.age; -} -let person = {name: 'John', age: 20}; -[10, 12, 26, 15].find(f, person); // 26 -``` - -另外,两个方法都可以发现NaN,弥补了数组的indexOf方法的不足 - -```js -[NaN].indexOf(NaN) -// -1 - -[NaN].findIndex(y => Object.is(NaN, y)) -// 0 -``` - -**indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。** - - -### fill() - -`fill`方法使用给定值,填充一个数组。 - -```js -['a', 'b', 'c'].fill(7) -// [7, 7, 7] - -new Array(3).fill(7) -// [7, 7, 7] - -``` - -数组中已有的元素,会被全部抹去。 - - - -```js -// fill方法还可以接受第二个和第三个参数 -// startIndex指定填充的起始位置 -// endIndex指定填充的结束位置 -fill(value,startIndex,endIndex) - -['a', 'b', 'c'].fill(7, 1, 2) -// ['a', 7, 'c'] -``` - -**如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。** - -```js -let arr = new Array(3).fill({name: "Mike"}); -arr[0].name = "Ben"; -arr -// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}] - -let arr = new Array(3).fill([]); -arr[0].push(5); -arr -// [[5], [5], [5]] -``` - - - -### entries(),keys() 和 values() - -ES6 提供三个新的方法,用于遍历数组 - -- `entries()` 对键值对的遍历。 -- `keys()` 对键名的遍历 -- `values()` 对键值的遍历 - -都返回一个遍历器对象【Iterator】,可以用`for...of`循环进行遍历 - -```js -for (let index of ['a', 'b'].keys()) { - console.log(index); -} -// 0 -// 1 - -for (let elem of ['a', 'b'].values()) { - console.log(elem); -} -// 'a' -// 'b' - -for (let [index, elem] of ['a', 'b'].entries()) { - console.log(index, elem); -} -// 0 "a" -// 1 "b" -``` - -如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。 - -```js -let letter = ['a', 'b', 'c']; -let entries = letter.entries(); -console.log(entries.next().value); // [0, 'a'] -console.log(entries.next().value); // [1, 'b'] -console.log(entries.next().value); // [2, 'c'] -``` - -### includes() - -`Array.prototype.includes`方法返回一个布尔值,表示某个数组是否包含给定的值. - - -```js -[1, 2, 3].includes(2) // true -[1, 2, 3].includes(4) // false -[1, 2, NaN].includes(NaN) // true -``` - -与字符串的includes方法类似。ES2016 引入了该方法。 - -```js - -[1, 2, 3].includes(3, 3); // false -[1, 2, 3].includes(3, -1); // true - -``` - -**第二个参数表示搜索的起始位置,默认为0**。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。 - - -通常使用数组的`indexOf`法,也能检查是否包含某个值。 - -```js -if (arr.indexOf(el) !== -1) { - // ... -} -``` - -`indexOf`方法有两个缺点: - -- 不够语义化,它的含义是找到参数值的第一个出现位置,要去比较是否不等于-1,表达起来不够直观 -- 内部使用严格相等运算符(`===`)进行判断,这会导致对NaN的误判。 - - -```js - -// indexof存在NaN误判 -[NaN].indexOf(NaN) -// -1 - -// includes正常 -[NaN].includes(NaN) -// true -``` - - -类似功能替代方案: - -```js -const contains = (() => - Array.prototype.includes - ? (arr, value) => arr.includes(value) - : (arr, value) => arr.some(el => el === value) -)(); -contains(['foo', 'bar'], 'baz'); // => false -``` - - -`Map` 和 `Set` 数据结构有一个`has`方法,需要注意与`includes`区分。 - -- **Map 结构的has方法,是用来查找键名的**,比如Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)。 -- **Set 结构的has方法,是用来查找值的**,比如Set.prototype.has(value)、WeakSet.prototype.has(value)。 - - - -### flat(),flatMap() - -数组的成员有时还是数组,`Array.prototype.flat()`用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。 - -```js - -// flat()方法将子数组的成员取出来,添加在原来的位置。 -[1, 2, [3, 4]].flat() -// [1, 2, 3, 4] -``` - -**flat()默认只会“拉平”一层**,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。 - - -```js - -// 默认拉平一层 -[1, 2, [3, [4, 5]]].flat() -// [1, 2, 3, [4, 5]] - -// 拉平嵌套两层得嵌套数组 -[1, 2, [3, [4, 5]]].flat(2) -// [1, 2, 3, 4, 5] - -``` - -如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。 - -```js -// 不管有多少层嵌套,都要转成一维数组 -[1, [2, [3]]].flat(Infinity) -// [1, 2, 3] - -``` - -如果原数组有空位,flat()方法会跳过空位。 - -```js -[1, 2, , 4, 5].flat() -// [1, 2, 4, 5] -``` - -**flatMap()方法对原数组的每个成员执行一个函数**(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。 - -```js -// 相当于 [[2, 4], [3, 6], [4, 8]].flat() -[2, 3, 4].flatMap((x) => [x, x * 2]) -// [2, 4, 3, 6, 4, 8] -``` - -`flatMap()`只能展开一层数组 - - -```js -// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat() -[1, 2, 3, 4].flatMap(x => [[x * 2]]) -// [[2], [4], [6], [8]] -``` -上面代码中,遍历函数返回的是一个双层的数组,但是默认只能展开一层,因此flatMap()返回的还是一个嵌套数组。 - -**`flatMap()`方法的参数是一个遍历函数,可以接受三个参数** - -- 当前数组成员 -- 当前数组成员的位置(从零开始) -- 原数组 - -```text - -arr.flatMap(function callback(currentValue[, index[, array]]) { - // ... -}[, thisArg]) -``` - -**`flatMap()`方法还可以有第二个参数,用来绑定遍历函数里面的`this`。** - -### 数组的空位 - - -**数组的空位指,数组的某一个位置没有任何值**。比如,Array构造函数返回的数组都是空位。 - -```js -// 返回具有 3 个空位的数组。 -Array(3) // [, , ,] -``` - - -空位不是`undefined`,一个位置的值等于`undefined`,依然是有值的。**空位是没有任何值**,in运算符可以说明这一点。 - -```js -// 数组的 0 号位置是有值的 -0 in [undefined, undefined, undefined] // true - -// 数组的 0 号位置没有值 -0 in [, , ,] // false -``` - -ES5和ES6中空位的区别比较可以参考: -https://es6.ruanyifeng.com/#docs/array#%E6%95%B0%E7%BB%84%E7%9A%84%E7%A9%BA%E4%BD%8D - - - - - -## 扩展运算符 - -扩展运算符(spread)是三个点(...),可以将数组转为用逗号分隔的参数序列 -```javascript -console.log(...[1,2,3]) -// 输出 1 2 3 - -console.log(1,...[2,3,4],5) -// 输出 1 2 3 4 5 - -``` - -可以用在函数调用这样的场景下 - -```javascript - -function push(arr, ...items){ - // 数组中添加元素 - arr.push(...items) -} - -function add(x,y){ - return x+y; -} - -// 定于参数 -const num=[4,22] - -// 调用 - -add(...num) - -// 输出26 - -``` - -从上面的代码例子中可以看出,arr.push(...items)和add(...num)都是函数的调用,也都可以使用扩展运算符,**将数组变为参数序列** - - -```javascript -// 表达式 -const arr=[ - ...(x>0?['a']:[]), - 'b' -] - -// 如上,扩展运算符是空数组,则不产生任何效果 -console.log([...[],1]) -// [1] - -``` - - -### 替代数组的apply()方法 - -> apply()方法可以将数组转为函数的参数 - -```javascript - -// ES5 -function f(x,y,z){ - // ... -} - -const arg=[0,1,2] - -// 利用apply方法 - -f.apply(null,args) - -// 而ES6中可以 - -f(...arg) - -``` - -类似的也可以 - -```javascript -// ES5 - -Math.max.apply(null,[1,2,3]) - -// ES6 -Math.max(...[1,2,3]) - -// 上面个两个等价于 - -Math.max(1,2,3) - -``` - -类似也可以实现元素添加数组到尾部 - -```javascript -const arr1=[0,1,2] -const arr2=[3,4,5] - -// ES5 (apply()劫持属性) -Array.prototype.push.apply(arr1,arr2) - -// 特别注意:Array的原型链上的push方法不能直接使用数组,需要用apply方法劫持变通 - -// ES6 -arr1.push(...arr2) -``` - -### 简单应用 - -#### 合并数组 - -```text -let arr1=['a','b'] -let arr2=['c'] -let arr3=['d','e'] -// ES5 -[1,2].concat(more) -// eg -arr1.concat(arr2,arr3) -// 输出 ['a','b','c','d','e'] - -// ES6 -[1,2,...more] -// eg: -[...arr1,...arr2,...arr3] -// 输出 ['a','b','c','d','e'] - -``` - -#### 解构赋值 - -> 与解构赋值的结合,可以帮助生成数组 - -```javascript -// ES5 -const a=list[0] -const rest=list.slice(1) - -// ES6 -[a,...rest]=list - -const [first,...rest]=[1,2,3,4,5] - -first // 1 -rest // [2,3,4,5] -``` - -#### 函数的返回值 - -> 在Javascript中,函数只能返回一个值,如果需要返回多个值,就通过返回对象或者数组来实现,拓展运算符提供了相对应的变通方法 - -```javascript -const fields=readDateFields(database); - -// 间数据构造传入构造函数Date(),获取新值 -const d=new Date(...fields) - -``` - -#### 字符串 -```javascript -[..."hello"] -// ['h','e','l','l','o'] -``` - diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" deleted file mode 100644 index e754a1663..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" +++ /dev/null @@ -1,266 +0,0 @@ ---- -title: 数值 -headerDepth: 4 ---- - -## - - -### Number.isFinite() 和 Number.isNaN() - -ES6 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法 - - -`Number.isFinite()`用来检查一个数值是否为有限的(`finite`),即不是Infinity。 - - -```javascript -Number.isFinite(15); // true -Number.isFinite(0.8); // true -Number.isFinite(NaN); // false -Number.isFinite(Infinity); // false -Number.isFinite(-Infinity); // false -Number.isFinite('foo'); // false -Number.isFinite('15'); // false -Number.isFinite(true); // false -``` - -**如果参数类型不是数值,Number.isFinite一律返回false** - -`Number.isNaN()`用来检查一个值是否为`NaN`(Not A Number)。 - -```javascript -Number.isNaN(NaN) // true -Number.isNaN(15) // false -Number.isNaN('15') // false -Number.isNaN(true) // false -Number.isNaN(9/NaN) // true -Number.isNaN('true' / 0) // true -Number.isNaN('true' / 'true') // true -``` - -如果参数类型不是`NaN`,`Number.isNaN`一律返回`false`。 - -#### 重要区别 - -> 与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。 - - -```javascript - -isFinite(25) // true -isFinite("25") // true -Number.isFinite(25) // true -Number.isFinite("25") // false - -isNaN(NaN) // true -isNaN("NaN") // true -Number.isNaN(NaN) // true -Number.isNaN("NaN") // false -Number.isNaN(1) // false - -``` - - -### Number.parseInt()和Number.parseFloat() - -ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。 - -```javascript -// ES5的写法 -parseInt('12.34') // 12 -parseFloat('123.45#') // 123.45 - -// ES6的写法 -Number.parseInt('12.34') // 12 -Number.parseFloat('123.45#') // 123.45 -``` - - -**逐步减少全局性方法,使得语言逐步模块化。** - -```javascript -Number.parseInt === parseInt // true -Number.parseFloat === parseFloat // true -``` - -### Number.isInteger() - -`Number.isInteger()`用来判断一个数值是否为整数。 - -```javascript -Number.isInteger(25) // true -Number.isInteger(25.1) // false - -// 整数和浮点数采用的是同样的储存方法 -Number.isInteger(25) // true -Number.isInteger(25.0) // true -``` - -**JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。** - -如果参数不是数值,`Number.isInteger`返回`false`。 - -```javascript -Number.isInteger() // false -Number.isInteger(null) // false -Number.isInteger('15') // false -Number.isInteger(true) // false -``` - -### Math 对象的扩展 - -#### Math.trunc() - -`Math.trunc()`方法用于去除一个数的小数部分,返回整数部分 - -```javascript -Math.trunc(4.1) // 4 -Math.trunc(4.9) // 4 -Math.trunc(-4.1) // -4 -Math.trunc(-4.9) // -4 -Math.trunc(-0.1234) // -0 -``` - -对于非数值,`Math.trunc`内部使用`Number`方法将其先转为数值 - -```javascript -Math.trunc('123.456') // 123 -Math.trunc(true) //1 -Math.trunc(false) // 0 -Math.trunc(null) // 0 -``` - -对于空值和无法截取整数的值,返回`NaN`。 - -```javascript -Math.trunc(NaN); // NaN -Math.trunc('foo'); // NaN -Math.trunc(); // NaN -Math.trunc(undefined) // NaN -``` - -`Math.trunc()`的类似实现: - -```javascript -Math.trunc = Math.trunc || function(x) { - return x < 0 ? Math.ceil(x) : Math.floor(x); -}; - -``` - - -#### Math.sign() - -Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。 - -- 参数为正数,返回+1; -- 参数为负数,返回-1; -- 参数为 0,返回0; -- 参数为-0,返回-0; -- 其他值,返回NaN。 - - -如果参数是非数值,会自动转为数值。对于那些无法转为数值的值,会返回NaN。 - -```javascript -Math.sign('') // 0 -Math.sign(true) // +1 -Math.sign(false) // 0 -Math.sign(null) // 0 -Math.sign('9') // +1 -Math.sign('foo') // NaN -Math.sign() // NaN -Math.sign(undefined) // NaN -``` - -Math.sign()的类似实现: - -```javascript -// 判断正数、负数、还是零 -Math.sign = Math.sign || function(x) { - x = +x; // convert to a number - if (x === 0 || isNaN(x)) { - return x; - } - return x > 0 ? 1 : -1; -}; -``` - - -#### Math.cbrt() - -Math.cbrt()方法用于计算一个数的立方根。 - -```javascript -Math.cbrt(-1) // -1 -Math.cbrt(0) // 0 -Math.cbrt(1) // 1 -Math.cbrt(2) // 1.2599210498948732 -``` - -对于非数值,Math.cbrt()方法内部也是先使用Number()方法将其转为数值。 - -```javascript -Math.cbrt('8') // 2 -Math.cbrt('hello') // NaN -``` - -`Math.cbrt()`类似实现: - -```javascript -// 计算一个数的立方根 -Math.cbrt = Math.cbrt || function(x) { - var y = Math.pow(Math.abs(x), 1/3); - return x < 0 ? -y : y; -}; -``` - - -#### Math.hypot() -Math.hypot方法返回所有参数的平方和的平方根。 - -```javascript - -// 3 的平方加上 4 的平方,等于 5 的平方。 -Math.hypot(3, 4); // 5 -Math.hypot(3, 4, 5); // 7.0710678118654755 -Math.hypot(); // 0 -Math.hypot(NaN); // NaN -Math.hypot(3, 4, 'foo'); // NaN -Math.hypot(3, 4, '5'); // 7.0710678118654755 -Math.hypot(-3); // 3 -``` - -如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。 - - -#### 指数运算符 -ES2016 新增了一个指数运算符(**)。 - -```javascript -2 ** 2 // 4 -2 ** 3 // 8 -``` - -**这个运算符是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。** - -```javascript - -// 首先计算的是第二个指数运算符,而不是第一个 -// 相当于 2 ** (3 ** 2) -2 ** 3 ** 2 -// 512 -``` - -指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。 - -```javascript -let a = 1.5; -a **= 2; -// 等同于 a = a * a; - -let b = 4; -b **= 3; -// 等同于 b = b * b * b; -``` \ No newline at end of file diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\346\267\261\346\213\267\350\264\235\344\270\216\346\265\205\346\213\267\350\264\235.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\346\267\261\346\213\267\350\264\235\344\270\216\346\265\205\346\213\267\350\264\235.md" deleted file mode 100644 index 94425f741..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\346\267\261\346\213\267\350\264\235\344\270\216\346\265\205\346\213\267\350\264\235.md" +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: 深拷贝和浅拷贝 -headerDepth: 4 ---- - - - -```js - -// 判断数据类型 -function checkedType(target){ - // 判断类型 -typeof -instanceof -toString.call() -Array.isArray() - return Object.prototype.toString.call(target).slice(8,-1); -} - - -// 基于Json序列化的深拷贝【不能处理函数】 -function DeepCloneByJSON(target){ - return JSON.parse(Json.stringify(target)) -} - -// 基于递归思想的深拷贝 -function DeepClone(target){ - - let result; - const targetType=checkType(target); - - if(targetType === 'object'){ - // 处理对象 - result={}; - }else if(targetType === 'Array'){ - // 处理数组 - result=[]; - }else{ - // 其他数据类型或者函数 - return result; - } - - // 遍历目标数据 - for(let key in target){ - // 获取每一项值 - const value=target[key]; - - // 递归处理 - result[key]=DeepClone(value) - } - - - // 处理完成后,返回 - return result; -} - -``` - - -也可以使用lodash库的[cloneDeep()](https://www.lodashjs.com/docs/lodash.cloneDeep#_clonedeepvalue)函数来实现 - - -参考:[lodash中深拷贝实现方式](https://github.com/lodash/lodash/blob/master/cloneDeep.js) - - diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\347\256\200\344\273\213.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\347\256\200\344\273\213.md" deleted file mode 100644 index 5fc228ba2..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\347\256\200\344\273\213.md" +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: 简介 -headerDepth: 4 ---- - - -> 这部分知识点了解即可,比较简单,内容也比较少 - - -### ECMAScript 和 JavaScript 的关系 ->前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。 - - -### ES6 与 ECMAScript 2015 的关系 - ->ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言” - -### Babel转码器 - -Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。 - -```javascript -// 转码前的箭头函数 -input.map(item=>item+1) - -// 转码后 -input.map(function (item){ - return item+1 -}) - -``` - -#### 安装Babel - -```bash -## 本地安装 -npm install --save-dev @babel/core -``` - -#### 配置文件.babelrc - -Babel 的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。 - -```json -{ - "presets": [], - "plugins": [] -} - -``` - -presets字段设定转码规则 - -```bash -# 最新转码规则 -$ npm install --save-dev @babel/preset-env - -# react 转码规则 -$ npm install --save-dev @babel/preset-react -``` -下载完成后,可以将规则键入到`.babelrc`文件中 - -```json -{ - "presets": [ - "@babel/env", - "@babel/preset-react" - ], - "plugins": [] -} -``` \ No newline at end of file diff --git "a/docs/manuscripts/read-books/cs-books/es-standard/\350\247\243\346\236\204\350\265\213\345\200\274.md" "b/docs/manuscripts/read-books/cs-books/es-standard/\350\247\243\346\236\204\350\265\213\345\200\274.md" deleted file mode 100644 index 17886d9f6..000000000 --- "a/docs/manuscripts/read-books/cs-books/es-standard/\350\247\243\346\236\204\350\265\213\345\200\274.md" +++ /dev/null @@ -1,584 +0,0 @@ ---- -title: 解构赋值 -headerDepth: 4 ---- - -### 数组的解构赋值 - -#### 基本用法 - ->ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。 - -```javascript -// 变量赋值 -let a=1; -let b=2; -let c=3; - -// ES6中可以从数组中提取值,按照对应位置,对变量赋值。: - -let [a,b,c]=[1,2,3] - -``` - -本质上,这种写法属于“**模式匹配**”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。 - -```javascript -// 嵌套解构 -let [foo, [[bar], baz]] = [1, [[2], 3]]; -foo // 1 -bar // 2 -baz // 3 - -let [ , , third] = ["foo", "bar", "baz"]; -console.log(third) // "baz" - - -let [x, , y] = [1, 2, 3]; -console.log(x) // 1 -console.log(y) // 3 - -let [head, ...tail] = [1, 2, 3, 4]; -console.log(head) // 1 -console.log(tail) // [2, 3, 4] - -let [x, y, ...z] = ['a']; -console.log(x) // "a" -console.log(y) // undefined -console.log(z) // [] - -``` -**解构不成功,变量值等于undefined** - - -```javascript -// 不完全解构, 只匹配部分 -let [x, y] = [1, 2, 3]; -x // 1 -y // 2 - -let [a, [b], d] = [1, [2, 3], 4]; -a // 1 -b // 2 -d // 4 - -``` - -如果等号的右边不是数组(正确的说:不属于可以遍历的结构),就会报错 - -```javascript -// 解构时会报错 -let [foo] = 1; -let [foo] = false; -let [foo] = NaN; -let [foo] = undefined; -let [foo] = null; -let [foo] = {}; - -// 因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式), -// 要么本身就不具备 Iterator 接口(最后一个表达式)。 -``` - -Set结构的数据明显存在递归迭代、遍历的接口,也是可以使用数组的解构赋值的 - -```javascript -let [x, y, z] = new Set(['a', 'b', 'c']); -x // "a" -``` - -**只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值** - - -#### 默认值 - -解构赋值允许指定默认值。 - -```javascript -let [foo = true] = []; -foo // true - -let [x, y = 'b'] = ['a']; // x='a', y='b' -let [x, y = 'b'] = ['a', undefined]; // x='a', y='b' - -``` -**ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。** - -```javascript -// undefined情况 -let [x = 1] = [undefined]; -x // 1 - -// null情况 -let [x = 1] = [null]; -x // null -``` - -**如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。** - -```javascript - -// 定义函数 -function f() { - console.log('aaa'); -} - -// 解构赋值 -let [x = f()] = [1]; - -``` -此时x明显可以拿到值,所以函数f()是不会执行的。 - -```javascript - -let x; - -// 数组[1]中的第一个元素,不严格等于undefined的时候,才会解构成功 -if([1][0]===undefined){ - x=f(); -}else{ - x=[1][0] -} - -``` - -默认值可以引用解构赋值的其他变量,但该变量必须已经声明 - -```javascript -let [x = 1, y = x] = []; // x=1; y=1 -let [x = 1, y = x] = [2]; // x=2; y=2 -let [x = 1, y = x] = [1, 2]; // x=1; y=2 - -// 变量y没有声明 -let [x = y, y = 1] = []; // ReferenceError: y is not defined - -``` - - -### 对象的解构赋值 - -同样,解构赋值可以适用数组,也可以适用于对象 - -```javascript -let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; -foo // "aaa" -bar // "bbb" -``` - -**数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。** - -```javascript -let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; -foo // "aaa" -bar // "bbb" - -// 变量没有对应的同名属性,导致取不到值,最后等于undefined。 -let { baz } = { foo: 'aaa', bar: 'bbb' }; -baz // undefined - -``` - -如果变量名与属性名不一致 - -```javascript -let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; -baz // "aaa" - -let obj = { first: 'hello', last: 'world' }; -let { first: f, last: l } = obj; -f // 'hello' -l // 'world' -``` - -```javascript -// 对象的解构赋值是下面形式的简写 -let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' }; - -// 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者 - -// 前者为:匹配的模式,后者为变量 -``` - -**与数组一样,解构也可以用于嵌套结构的对象** - -```javascript -let obj = { - p: [ - 'Hello', - { y: 'World' } - ] -}; - -let { p: [x, { y }] } = obj; -x // "Hello" -y // "World" - -``` -这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。 - -```javascript -let obj = { - p: [ - 'Hello', - { y: 'World' } - ] -}; - -// 此时p作为了变量进行赋值 -let { p, p: [x, { y }] } = obj; -x // "Hello" -y // "World" -p // ["Hello", {y: "World"}] - -``` - -#### 默认值 - -同样,对象的解构也是可以指定默认值的 - -```javascript -var {x = 3} = {}; -x // 3 - -var {x, y = 5} = {x: 1}; -x // 1 -y // 5 - -var {x: y = 3} = {}; -y // 3 - -var {x: y = 3} = {x: 5}; -y // 5 - -var { message: msg = 'Something went wrong' } = {}; -msg // "Something went wrong" - -``` - -**和数组的解构赋值一样,默认值生效的条件是,对象的属性值严格等于undefined** - -```javascript - -let {x = 3} ={x:undefined} -x //3 - -// 属性x等于null,因为null与undefined不严格相等,所以是个有效的赋值,导致默认值3不会生效。 -let {x = 3} = {x: null}; -x // null - -``` - -需要注意: - -- 如果要将一个已经声明的变量用于解构赋值,必须非常小心。 - -```js - -// 错误的写法 -let x; -{x} = {x: 1}; -// SyntaxError: syntax error - -// JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。 - -// 正确的写法 -let x; -({x} = {x: 1}); -x //1 -``` - -- 解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。 - -```javascript -// 表达式虽然毫无意义,但是语法是合法的,可以执行 -({} = [true, false]); -({} = 'abc'); -({} = []); -``` - - -- 由于**数组本质是特殊的对象**,因此可以对数组进行对象属性的解构 - -```javascript -// 注意将数组理解为特殊的对象 -let arr = [1, 2, 3]; -let {0 : first, [arr.length - 1] : last} = arr; -first // 1 -last // 3 -``` - - - -### 字符串的解构赋值 - -**字符串也可以解构赋值**。这是因为此时,字符串被转换成了一个类似数组的对象。 - -```javascript -const [a, b, c, d, e] = 'hello'; -a // "h" -b // "e" -c // "l" -d // "l" -e // "o" - -``` - -类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。 - -```javascript -// length长度属性 -let {length : len} = 'hello'; -len // 5 -``` - - -### 数值和布尔值的解构赋值 - -**解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。** - -```javascript -// 数值和布尔值的包装对象都有toString属性 -let {toString: s} = 123; -s === Number.prototype.toString // true - -let {toString: s} = true; -s === Boolean.prototype.toString // true - -``` - -解构赋值的规则是,**只要等号右边的值不是对象或数组,就先将其转为对象**。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。 - -```javascript - -// undefined和null无法转为对象 -let { prop: x } = undefined; // TypeError -let { prop: y } = null; // TypeError - -``` - -### 函数参数的解构赋值 - -函数也是可以使用解构赋值的 - -```javascript -function add([x, y]){ - return x + y; -} - -add([1, 2]); // 3 - -``` -函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。 - -```javascript -[[1, 2], [3, 4]].map(([a, b]) => a + b); -// [ 3, 7 ] - -// undefined就会触发函数参数的默认值。 -[1, undefined, 3].map((x = 'yes') => x); -// [ 1, 'yes', 3 ] - -``` - -#### 圆括号问题 - -**解构赋值虽然很方便,但是解析起来并不容易**对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。 - -由此带来的问题是,如果模式中出现圆括号怎么处理。ES6 的规则是,**只要有可能导致解构的歧义,就不得使用圆括号**。 - -但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。 - -##### 不能使用圆括号的情况 - -- 变量声明语句 - -```js -// 全部报错 都是变量声明语句,模式不能使用圆括号。 -let [(a)] = [1]; - -let {x: (c)} = {}; -let ({x: c}) = {}; -let {(x: c)} = {}; -let {(x): c} = {}; - -let { o: ({ p: p }) } = { o: { p: 2 } }; - -``` - -- 函数参数 - -```js -// 函数参数也属于变量声明,因此不能带有圆括号。 -// 报错 -function f([(z)]) { return z; } -// 报错 -function f([z,(x)]) { return x; } - - -``` - -- 赋值语句的模式 - -```js -// 全部报错 整个模式都放在圆括号之中 -({ p: a }) = { p: 42 }; -([a]) = [5]; - -// 报错 一部分模式放在圆括号之中 -[({ p: a }), { x: c }] = [{}, {}]; - -``` - - -##### 可以使用圆括号的情况 - -**赋值语句的非模式部分,可以使用圆括号** - -```javascript -// 都是赋值语句,而不是声明语句 -// 圆括号都不属于模式的一部分 -[(b)] = [3]; // 正确 -({ p: (d) } = {}); // 正确 -[(parseInt.prop)] = [3]; // 正确 - -``` - -### 实际用途 - -- 交换变量的值 - -> 这里简单易读,语义非常清晰 - -```javascript -let x=1; -let y=2; - -// 两值交换 -[x,y]=[y,x] - -``` - -- 从函数返回多个值 - -> 函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回 - -```javascript -// 返回一个数组 -function example() { - return [1, 2, 3]; -} - -// 解构 -let [a, b, c] = example(); - -// 返回一个对象 -function example() { - return { - foo: 1, - bar: 2 - }; -} -// 解构 -let { foo, bar } = example(); - -``` - -- 函数参数的定义 - -> 解构赋值可以方便地将一组参数与变量名对应起来。 - -```javascript -// 参数是一组有次序的值 -function f([x, y, z]) { - ... -} -// 调用 -f([1, 2, 3]); - -// 参数是一组无次序的值 -function f({x, y, z}) { - ... -} - -// 调用 -f({z: 3, y: 2, x: 1}); - -``` - -- 提取 JSON 数据 - -> 解构赋值对提取 JSON 对象中的数据,尤其有用。 - -```javascript -// 定义数据 -let jsonData = { - id: 42, - status: "OK", - data: [867, 5309] -}; - -// 解构 -let { id, status, data: number } = jsonData; - -console.log(id, status, number); -// 42, "OK", [867, 5309] - -``` - -- 函数参数的默认值 - -```javascript -jQuery.ajax = function (url, { - async = true, - beforeSend = function () {}, - cache = true, - complete = function () {}, - crossDomain = false, - global = true, - // ... more config -} = {}) { - // ... do stuff -}; - -``` - -避免了在函数体内部再写`var foo = config.foo || 'default foo'`;这样的语句。 - -- 遍历 Map 结构 - -> 任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。 - -```javascript - -const map = new Map(); -map.set('first', 'hello'); -map.set('second', 'world'); - -for (let [key, value] of map) { - console.log(key + " is " + value); -} -// first is hello -// second is world - -// 获取键名 -for (let [key] of map) { - // ... -} - -// 获取键值 注意此处的逗号 -for (let [,value] of map) { - // ... -} - -``` - - -- 输入模块的指定方法 - -> 加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。 - -```javascript - -// CommonJs写法 -const { SourceMapConsumer, SourceNode } = require("source-map"); - -``` \ No newline at end of file diff --git "a/docs/manuscripts/read-books/cs-books/\344\272\206\344\270\215\350\265\267\347\232\204Node.js .md" "b/docs/manuscripts/read-books/cs-books/\344\272\206\344\270\215\350\265\267\347\232\204Node.js .md" deleted file mode 100644 index 9cbfc2cdd..000000000 --- "a/docs/manuscripts/read-books/cs-books/\344\272\206\344\270\215\350\265\267\347\232\204Node.js .md" +++ /dev/null @@ -1 +0,0 @@ -# 了不起的Node.js \ No newline at end of file diff --git "a/docs/manuscripts/read-books/cs-books/\344\272\206\344\270\215\350\265\267\347\232\204Node.js.md" "b/docs/manuscripts/read-books/cs-books/\344\272\206\344\270\215\350\265\267\347\232\204Node.js.md" new file mode 100644 index 000000000..26a0be8eb --- /dev/null +++ "b/docs/manuscripts/read-books/cs-books/\344\272\206\344\270\215\350\265\267\347\232\204Node.js.md" @@ -0,0 +1,6 @@ +--- +title: 了不起的Node.js +permalink: /manuscripts/read-books/cs-books/good-nodejs.html +--- + +# 了不起的Node.js \ No newline at end of file diff --git "a/docs/manuscripts/read-books/cs-books/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266\345\256\236\350\267\265.md" "b/docs/manuscripts/read-books/cs-books/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266\345\256\236\350\267\265.md" index 7b555be71..e372e686e 100644 --- "a/docs/manuscripts/read-books/cs-books/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266\345\256\236\350\267\265.md" +++ "b/docs/manuscripts/read-books/cs-books/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266\345\256\236\350\267\265.md" @@ -1,6 +1,10 @@ +--- +title: 分布式消息中间件实践 +permalink: /manuscripts/read-books/cs-books/middleware-practice.html +--- # 分布式消息中间件实践 -### 消息队列 +## 消息队列 @@ -8,7 +12,7 @@ -### 消息协议 +## 消息协议 `协议`:计算机术语中的协议,就是一个达成一致的并受规则支配的交互集合,例如计算机网络中的七层网络协议,http协议等-----> protocol @@ -28,7 +32,7 @@ 有些特殊框架(例如:Redis、Kafka、ZeroMQ)更具自身需要未严格遵循MQ规范,而是基于`TCP/IP`封装的一套协议,通过网络Socket接口进行传输,实现了MQ功能。【这里的协议可以理解为双方通信的一个约定】 -#### AMQP协议 +### AMQP协议 > AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。[百度百科](https://baike.baidu.com/item/AMQP/8354716?fr=aladdin) @@ -55,7 +59,7 @@ -#### 基础概念 +### 基础概念 diff --git "a/docs/manuscripts/read-books/cs-books/\346\267\261\345\205\245\346\265\205\345\207\272\347\232\204Node.js.md" "b/docs/manuscripts/read-books/cs-books/\346\267\261\345\205\245\346\265\205\345\207\272\347\232\204Node.js.md" index 8d6b1d251..b0b32d0f9 100644 --- "a/docs/manuscripts/read-books/cs-books/\346\267\261\345\205\245\346\265\205\345\207\272\347\232\204Node.js.md" +++ "b/docs/manuscripts/read-books/cs-books/\346\267\261\345\205\245\346\265\205\345\207\272\347\232\204Node.js.md" @@ -1,9 +1,11 @@ +--- +title: 深入浅出Node.js +permalink: /manuscripts/read-books/cs-books/base-nodejs.html +--- # 深入浅出Node.js -- 图书名称:《深入浅出的Node.js》 -- 文档类型:读书笔记 -- 花费时间:2021.2.18 - 2021.2.20 +>花费时间:2021.2.18 - 2021.2.20 @@ -34,15 +36,14 @@ Node是单线程的,没有提供对多线程的技术支持,但是可以充 - 编写C/C++扩展更加高效地利用CPU - 单线程无法满足需求---->扩展C/C++,最后还可以通过**子进程(child_process)** 处理,通过IPC技术实现进程间的通信。将计算和I/O分离,还能充分利用多CPU -### -### 模块机制 +## 模块机制 -#### CommonJS的模块规范 +### CommonJS的模块规范 > 对模块的定义非常简单,包括模块定义、模块引入、模块标识三个部分。 -##### 模块定义 +#### 模块定义 > 模块中,上下文提供exports对象用于导出当前模块的方法或者变量 **是唯一导出的出口** @@ -50,7 +51,7 @@ Node是单线程的,没有提供对多线程的技术支持,但是可以充 **在Node中一个js文件就是一个模块** -##### 模块引入 +#### 模块引入 ```js const fs=require('fs') @@ -58,7 +59,7 @@ const fs=require('fs') 在CommonJS规范中,存在require()方法,接收模块标识,引入一个模块的API到当前的上下文中; -##### 模块标识 +#### 模块标识 模块标识指的是传递给require()方法的参数,例如:require('path')这里的path参数 @@ -68,7 +69,7 @@ const fs=require('fs') 每个模块都有独立的空间,互相不干扰,通过require() 、exports对象进行导入、导出操作;**用户完全不用去考虑变量污染**,相比之下比命名空间方案要好; -#### Node模块的实现 +### Node模块的实现 引入模块需要经历的步骤: @@ -86,7 +87,7 @@ const fs=require('fs') 文件模块是用户自己编写,可以理解为第三方模块,在运行时需要经历完整的模块引入步骤(路径分析、文件定位、编译执行), **加载速度相比核心模块要慢**; -#### 模块优先从缓存中加载 +### 模块优先从缓存中加载 Node对引入过的模块都会进行缓存,目的是减少二次引入时带来的开销 @@ -94,7 +95,7 @@ Node对引入过的模块都会进行缓存,目的是减少二次引入时带 核心模块的缓存检查要先于文件模块的缓存检查 -#### 路径分析和文件定位 +### 路径分析和文件定位 Node是基于模块标识符进行模块查找的。标识符主要分为: @@ -103,23 +104,23 @@ Node是基于模块标识符进行模块查找的。标识符主要分为: - / 开始的绝对路径文件模块 - 非路径形式的文件模块,如自定义的connect模块【目前还没见过】 -##### 核心模块 +#### 核心模块 优先级仅次于缓存加载,加载过程最快,已经被编译为二进制代码 -##### 路径形式的文件模块 +#### 路径形式的文件模块 加载速度慢于核心模块 require()方法会将路径形式的标识符转化为真实路径,**并且用作索引将编译执行后的结果存放在缓存中,让二次加载时更快【这里可以考虑key/value这种结构】** -##### 自定义模块 +#### 自定义模块 > 这里指的是 非核心模块,也不是路径形式的标识符,是一种特殊的文件模块,可能是一个文件或者包的形式。 这种最费时,加载最慢; **模块的文件路劲越深,模块的查找耗时会越多** -#### Node对Javascript文件内容进行头尾包装 +### Node对Javascript文件内容进行头尾包装 ```js (function (exports, require, module, __filename, __dirname) { @@ -141,11 +142,11 @@ require()方法会将路径形式的标识符转化为真实路径,**并且用 在执行之后,模块的exports属性被返回给了调用方。exports属性上的任何方法和属性都是可以被外部用到,但是模块中的变量或属性则不可直接被调用; -#### AMD规范 +### AMD规范 > 是对CommonJS规范的一个延伸 -```javascript +```js // AMD模块定义 define(function() { var exports = {}; @@ -159,10 +160,12 @@ define(function() { AMD模块是使用define来明确定义一个模块,在Node实现中是隐式包装的,进行作用域隔离;避免变量污染和不小心地被修改 -#### CMD规范 +### CMD规范 > 玉伯提出 + + ### 异步I/O 多线程的代价在于创建线程和执行线程上下文切换的开销较大,在复杂业务中还会面临锁、状态同步等问题。【**多线程能够在多核心CPU上有效提升CPU的利用率**】 @@ -213,7 +216,7 @@ Nginx就是采用了和Node相同的时间驱动,摒弃了多线程的方式 在通常的编程语言中,函数的参数只接受基本的数据类型或者对象引用,返回值也只是基本数据类型和对象引用 -```javascript +```js function test(x) { return x; } @@ -221,7 +224,7 @@ function test(x) { 高阶函数则是可以把函数作为参数,或者是吧函数作为返回值的函数 -```javascript +```js function test(x) { // 返回函数 return function () { @@ -232,7 +235,7 @@ function test(x) { 根据Node提供的最基本的事件模块可以看到,事件的处理方式正是基于高阶函数的特性来完成的 -```javascript +```js // 通过相同时间注册的不同的回调函数,可以很灵活的处理业务逻辑 const emitter= new events.EventEmitter(); // 监听event_test事件 @@ -249,7 +252,7 @@ Node带来的最大特性是**基于事件驱动的非阻塞I/O模型**,可以 Node在处理异常上预定**错误优先**,将异常作为回调函数的第一个实参传回,如果为空值,则表明异步调用没有异常抛出 -```javascript +```js async (function (err,data){ // 判断错误err是否为null if(err){ @@ -262,7 +265,7 @@ Web Workers能够解决利用CPU和减少阻塞UI渲染,但是不能解决前 Node借鉴了Web Workers模式,提出了child_process模块,用来多进程处理 -#### 异步编程解决方案 +### 异步编程解决方案 - 事件发布、订阅模式 - Promise/Deferred模式 @@ -272,7 +275,7 @@ Node借鉴了Web Workers模式,提出了child_process模块,用来多进程 例如:events模块 -```javascript +```js // 事件发布 emitter.emit('event_test',"this is an event message!") @@ -300,24 +303,24 @@ emitter.on('event_test',function(message){ 一般而言,事件和侦听器的关系是一对多的,但是在异步编程中也会出现事件与侦听器的关系是多对一的情况,即:一个业务逻辑可能依赖两个通过回调或者事件传递的结果。 -#### 异步并发控制 +### 异步并发控制 同步I/O和异步I/O的差距: - 同步I/O是彼此阻塞的,在循环体汇中,总是一个接着一个调用,不会出现耗用文件描述符太多的情况,同时性能也是低下的。 - 对于异步I/O,虽然并发容易实现,由于太容易实现,**需要控制流程**。【尽管是要压榨底层系统的性能,但是还需要给予一定的过载保护,防止过犹不及】 -### 内存控制 +## 内存控制 基于无阻塞、事件驱动建立的Node服务,具有内存消耗低的优点,非常适合处理海量的网络请求。**在服务端,资源向来都是寸土寸金的,要为海量用户服务,就得使一切资源都要高效循环利用!** -#### V8的内存限制 +### V8的内存限制 > Node中通过JavaScript使用内存时只能使用部分内存(64位操作系统约等于1.4GB内存可以使用,32位操作系统只能使用0.7GB内存),即使物理内存很大,但是对单个Node进程而言,计算机的内存资源无法得到充足的使用; 主要是Node基于V8构建的,Node中的JavaScript对象基本上都是通过V8的方式进行分配和管理的。**对前端需求足够满足,但是对服务端来说却存在明显不足** -#### V8的对象分配 +### V8的对象分配 在V8中,所有的JavaScript对象都是通过堆来进行分配的,Node提供V8中内存使用量的查看方式: @@ -354,11 +357,11 @@ node --max-new-space-size=1200 microservice.js **在V8初始化时生效,一旦生效就不能动态改变了,可以避免在执行过程中稍微多用了一些内存就轻易程序崩溃** -#### V8垃圾回收算法 +### V8垃圾回收算法 > 主要基于分代式垃圾回收机制。现代的垃圾回收算法中按对象的存活时间将内存的垃圾回收机制进行不同的分代,然后分别对不同的分代的内存进行高效的算法处理; -##### V8的内存分代 +#### V8的内存分代 - 新生代内存空间 运行前通过`--max-new-space-size`指定 - 老生代内存空间 运行前通过`--max-old-space-size`指定 @@ -371,18 +374,18 @@ node --max-new-space-size=1200 microservice.js web服务器的会话实现一般通过内存来存储,**当访问量大的时候会导致老生代中的存活对象骤增,不仅造成清理/整理过程费时,还会造成内存紧张,甚至溢出** -##### 查看垃圾回收日志 +#### 查看垃圾回收日志 在应用启动的时候添加`--trace_gc`参数,在进行垃圾回收时,会将垃圾回收的日志信息打印到标准的控制台输出 -#### 高效地使用内存 +### 高效地使用内存 - 作用域 - 闭包 在JavaScript中能形成作用域的有函数调用、with和全局作用域 -```javascript +```js // test函数 local局部变量 var test = function () { var local = {} @@ -393,19 +396,19 @@ var test = function () { **只被局部变量引用的对象存活周期较短**,当局部变量local失效,其引用的对象非常小会被分配在新生带内存空间中,在下次垃圾回收时被释放 -##### 标识符查找 +#### 标识符查找 > 标识符:可以理解为变量名 JavaScript在执行时会去查找该变量(标识符)定义在哪里,**最先查找是当前作用域**,如果在当前作用域中无法找到该变量的声明,就会去上级作用域中查找,知道查找到为止 -##### 作用域链 +#### 作用域链 变量只能向外访问,不能向内访问,当上层作用域出现了变量,及时在上上层中也定义了变量,也不会继续查找了。 **如果查找一个不存在的变量,将会一直沿着作用域链查找到全局作用域,最后抛出未定义错误** -##### 变量的主动释放 +#### 变量的主动释放 如果变量是全局变量(不通过var声明或定义在global变量上),**全局作用域需要直到进程退出才能释放**,导致全局变量引用的对象常驻内存(老生代内存中)。 @@ -415,7 +418,7 @@ Tips:同样,在非全局作用域中,想要主动释放变量引用的对 **注意:delete删除对象在V8中会干扰到V8的优化,相比之下重新赋值解除引用较好** -#### 闭包 +### 闭包 > 作用域链上的对象访问只能向上,外部无法向内部访问 @@ -423,7 +426,7 @@ Tips:同样,在非全局作用域中,想要主动释放变量引用的对 主要是通过高阶函数的特性(函数可以作为参数或者返回值)完成的 -```javascript +```js var foo=function (){ var bar=function (){ // 定义局部变量 @@ -444,7 +447,7 @@ var foo=function (){ **V8内存的限制,注意防止变量(闭包和全局变量引用的两种情况)无限制地增加,导致老生代中的对象增多;** -#### 查看内存使用情况 +### 查看内存使用情况 ```bash ## 输入node @@ -460,7 +463,7 @@ process.memoryUsage(); } ``` -##### 查看系统的内存占用 +#### 查看系统的内存占用 在os模块中,提供: @@ -469,17 +472,17 @@ process.memoryUsage(); 两个方法查看操作系统的内存使用情况,单位:字节 -##### 堆外内存 +#### 堆外内存 Node中的内存使用不是都通过V8进行分配的,对于不是通过V8分配的内存称为`堆外内存` -##### Node的内存构成 +#### Node的内存构成 V8分配的部分+Node自行分配的部分(堆外内存) **受V8的垃圾回收限制的主要是V8的堆内存** -### 内存泄露 +## 内存泄露 Node对内存泄露非常敏感,一旦线上项目应用拥有成千上万的流量,**即便是一个字节的内存泄露也会导致堆积,垃圾回收的过程中将会耗费更多时间进行对象扫描,应用响应缓慢,知道进程内存溢出,应用程序崩溃** @@ -489,7 +492,7 @@ Node对内存泄露非常敏感,一旦线上项目应用拥有成千上万的 - 队列消费不及时导致 - 作用域没有释放导致 -##### 谨慎将内存当做缓存使用 +### 谨慎将内存当做缓存使用 在大型应用中,缓存能够有效提高检索速度(例如:redis),可以非常有效的节约资源(I/O资源),缓存的访问效率明显要比磁盘I/O的效率高,一旦命中缓存,就可以节省I/O时间 @@ -497,7 +500,7 @@ Node对内存泄露非常敏感,一旦线上项目应用拥有成千上万的 > 注意这里提到的缓存和传统意义上的缓存不一样,例如:Redis是有很好的过期策略的,但是Node中的缓存是没有的,往往只是根据对象的键值对来实现缓存,常驻内存老生代当中; -```javascript +```js // 例如利用cache全局对象来常驻老生代内存中 var cache={}; // 获取目标值 @@ -520,7 +523,7 @@ var set = function (key,value){ 很明显,上面只是通过全局变量的形式实现,没有任何的过期策略,这就有可能到值常驻在内存老生代中,**使用的时候尝试添加过期策略** -##### 缓存的解决方案 +### 缓存的解决方案 直接将内存作为缓存的方案不是很可取,谨慎对待。除了限制缓存大小之外,**进程之间是无法共享内存的,如果在进程内需要使用缓存,那么不同进程之间魂村不可避免的有重复,对物理内存的使用也是一种浪费** @@ -529,15 +532,15 @@ var set = function (key,value){ - 将缓存转移到外部,减少常驻内存的对象的数量,让垃圾回收更加高效; - 外部缓存可以实现进程之间缓存共享,避免缓存重复存储。 -##### 消息队列导致内存泄露 +### 消息队列导致内存泄露 消息队列也是依赖于将数据存储在内存中,当队列中消息过多、消费不及时就会存在堆积,占用大量的内存资源; -##### 作用域未释放导致内存泄露 +### 作用域未释放导致内存泄露 主要是常用的全局变量没有被自动回收,内存占用不会回落,导致内存泄露 -#### 内存泄露排查 +## 内存泄露排查 - V8-profiler - Node-headump @@ -545,7 +548,7 @@ var set = function (key,value){ - drace - Node-memwatch -#### 大内存应用 +### 大内存应用 在Node中不可避免地存在操作大文件的场景。Node提供stream模块来处理大文件 @@ -556,7 +559,7 @@ var set = function (key,value){ 注意,大文件用流操作比较好,基于V8的内存限制,读取小文件的readFile()和writeFile()不能用于大文件操作 -```javascript +```js const reader=fs.createReadStream('xxx.txt'); const writer=fs.createWriteStream('xxx.txt'); // pipe管道加工处理 @@ -567,13 +570,13 @@ read.pipe(writer) > 当然对于大文件不需要进行字符串层面的操作,则不需要借助V8来处理,可以直接尝试用Buffer操作,**这样不会受到V8内存的限制出现内存泄露,但是依然会受到物理内存的限制** -#### 理解Buffer +### 理解Buffer > Buffer占用的内存不是通过V8分配的,属于堆外内存。**Buffer是二进制数据** Node在进程启动的时候就已经加载的Buffer,放在全局对象global上,所以不需要require模块引入可以直接使用Buffer -### 网络编程 +## 网络编程 Node是一个面向网络而生的平台,具有事件驱动、无阻塞、单线程等特性,具备良好的可伸缩性,非常轻量,适用在分布式网络中扮演各种各样的角色。 @@ -586,7 +589,7 @@ Node提供了四个模块 适用于对应的服务端和客户端 -#### TCP与UDP +### TCP与UDP > 两者都是网络传输层协议 @@ -599,12 +602,12 @@ UDP:用户数据包协议 DNS服务基于UDP实现的 - TCP连接一旦建立,所有的会话都会基于连接完成,客户端如果要与另外一个TCP服务通信需要另外通过三次握手创建套接字来完成连接 - **UDP中一个套接字可以和多个UDP服务通信,虽然提供面向事物的简单不可靠信息传输服务,在网络较差的情况下存在丢包的问题【不可靠】,但是由于不需要连接、资源消耗低,处理快速且灵活,应用十分广泛,例如:DNS服务基于UDP实现的** -#### http模块 +### http模块 Node的http模块包含对HTTP处理的封装。在Node中,HTTP服务继承自TCP服务器(net模块),能够和多个客户端保持连接,**采用事件驱动的形式** ,并不会为每一个连接创建额外的线程或者进程;保持很低的内存占用,因此能够实现高并发 -#### Cookie +### Cookie > Http是无状态的协议,现实中业务却是需要一定状态的,否则无法区分用户的身份 @@ -621,7 +624,7 @@ Node的http模块包含对HTTP处理的封装。在Node中,HTTP服务继承自 - 为静态组件使用不同的域名【和服务部署的动静分离效果差不多】 - 减少DNS查询【域名通过DNS解析获取ip,这个步骤用时短的话,提高整体速度】 -#### Session +### Session > 服务端没有设置cookie的httpOnly属性时,前端可以篡改cookie,就算设置了也能通过模拟cookie来篡改信息,**对敏感数据的保护是无效的** @@ -636,7 +639,7 @@ session的数据只保留在服务端,客户端无法修改,相比cookie数 设置接口的时候,注意添加版本号请求参数,避免后端服务发版前端采用的原有的缓存 -#### MVC +### MVC 在MVC流行之前,主流的处理方式都是通过文件路径进行处理的,MVC模型的主要思想是将业务逻辑按职责分离,主要有: @@ -644,13 +647,13 @@ session的数据只保留在服务端,客户端无法修改,相比cookie数 - 模型(Model) 数据相关的操作和封装 - 视图(View) 视图的渲染 -#### RESTful +### RestFul > REST:Representational State Transfer 表现层状态转换 我在项目中早期也是采用RESTful,分多种类型按照规范来约定接口,后来发现还是GET/POST类型好用,其他类型存在安全隐患; -#### 中间件的优化 +### 中间件的优化 - 使用高效的方法,必要时通过jsperf.com测试基准性能 - 缓存需要重复计算的结果(需要控制缓存用量) @@ -668,7 +671,7 @@ JavaScript是运行在单进程的单线程之上,程序状态单一,没有 从严格意义上来看:Node并非真正的单线程架构,Node自身还存在一定的I/O线程,由底层libuv处理 -#### 服务模型变迁 +### 服务模型变迁 - 石器时代: 同步 【一次只为一个请求服务,所有请求都得按次序等待服务,有点阻塞I/O的感觉】 - 青铜时代: 复制进程 【多少请求开辟多少个进程,复制过程缓慢,存在资源浪费】 @@ -689,7 +692,7 @@ JavaScript是运行在单进程的单线程之上,程序状态单一,没有 > > 所有处理都是在单线程上进行,影响事件驱动服务模型性能的点在于CPU的计算能力,**不受多进程或多线程模式中资源上线的影响**,可伸缩性强! -#### 多进程架构 +### 多进程架构 理想状况下启动多个进程来充分利用多核CPU,每个进程各自利用一个CPU; @@ -708,7 +711,7 @@ fork()复制的进程都是一个独立的进程,**具有独立而全新的V8 - Node通过事件驱动的方式在单线程上解决了大并发的问题 - 启动多个进程只是为了将CPU资源利用起来 -##### 创建子进程 +#### 创建子进程 node中提供的child_process模块可以随机创建子进程,提供四个方法创建子进程 @@ -722,7 +725,7 @@ node中提供的child_process模块可以随机创建子进程,提供四个方 - exec()和execFile()创建子进程时,可以指定timeout属性来设置超时时间,即给创建的进程设置有效时间,运行超过设定时间就直接被kill - **exec()适合执行已有的命令,execFile()适合执行文件** -```javascript +```js // 引入模块 const childProcess=require('child_process'); @@ -753,9 +756,8 @@ childProcess.fork('./worker.js') **注意:exec() 、execFile()、fork()都是通过spawn()来延伸实现的** -#### -#### 进程间通信(IPC:Inter-Process Communication) +### 进程间通信(IPC:Inter-Process Communication) 对于child_process模块,创建好了子进程,此时父子进程间通信是非常容易的; @@ -763,7 +765,7 @@ childProcess.fork('./worker.js') 通过fork()或者其他API创建子进程后,可以在父进程和子进程之间创建IPC通道,通过IPC通道,在父子进程之间使用message 和 send() 传递消息 -##### IPC原理 +#### IPC原理 Node中实现IPC通道的是管道(pipe)技术,具体细节由libuv提供 @@ -798,7 +800,7 @@ Node中实现IPC通道的是管道(pipe)技术,具体细节由libuv提供 不过在Node的v0.5.9中引入了进程间发送句柄的功能,**send()方法除了能通过IPC发送数据外,还可以发送句柄** -```javascript +```js // 除了发送数据message(消息),还能发送句柄sendHandle child.send(message,[sendHandle]) ``` @@ -813,7 +815,7 @@ child.send(message,[sendHandle]) 当主进程接收到socket请求后,将这个socket请求直接发送给工作进程,不需要重新与工作进程建立新的socket连接进行消息同行,转发数据 -```javascript +```js // 引入模块,通过child.js创建子进程 const childProcess=require('child_process').fork('child.js'); @@ -888,15 +890,15 @@ IPC通信、多进程架构能够很好的利用CPU资源,,但是在线上 **kill()方法并不能真正地将通过IPC相连的子进程杀死,只是给子进程发送一个系统信号。在默认情况下,父进程将通过kill()方法给子进程发送一个SIGTERM信号** -#### 自动重启 +### 自动重启 子进程退出后,主进程通过监听得知,能够重新复制(fork)一份子进程出来,和其他工作进程一致,继续充分利用多核CPU性能; -#### 自杀信号 +### 自杀信号 在退出的流程中增加一个自杀(suicide)信号,工作进程得知后要退出时,向主进程发送一个自杀信号,然后才停止接收新的连接。**当所有连接断开后才退出,主进程在接收到自杀信号后,立即创建新的工作进程服务** -#### 限量重启 +### 限量重启 通过自杀信号告知主进程可以使得新连接总是有进程服务;但是工作进程不能无限制地被重启 @@ -904,7 +906,7 @@ IPC通信、多进程架构能够很好的利用CPU资源,,但是在线上 通过全局变量来计数,确保重启不要太频繁 -#### 负载均衡 +### 负载均衡 在多进程之间监听相同端口,可以让用户请求分散到多个进程上进行处理,重新发挥多核CPU的性能;就想很多网关的上游服务,不能让某个服务做所有的任务,要将任务分发到不同的进程上,均衡化 @@ -922,7 +924,7 @@ Node在v0.11中提供了新的负载均衡策略——Round-Robin(轮询调度 在cluster模块中使用: -```javascript +```js // 启用Round-Robin cluster.schedulingPolicy = cluster.SCHED_RR // 不启用Round-Robin @@ -931,7 +933,7 @@ cluster.schedulingPolicy = cluster.SCHED_NONE 当然,也可以在环境变量中配置`NODE_CLUSTER_SCHED_POLICY`的值 -```javas +```js export NODE_CLUSTER_SCHED_POLICY=rr export NODE_CLUSTER_SCHED_POLICY=none ``` @@ -942,7 +944,7 @@ export NODE_CLUSTER_SCHED_POLICY=none Node在V0.8版本中新增了cluster模块,可以用来解决CPU的利用率问题,提供了较为完善的API,往常都是用child_process模块实现多进程架构,但是需要去处理很多细节; -```javascript +```js const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; @@ -970,13 +972,13 @@ if (cluster.isMaster) { } ``` -#### cluster模块原理 +### cluster模块原理 > cluster模块是chilid_process模块和net模块的组合应用 工作进程由 `child_process.fork()` 方法创建,因此它们可以使用 IPC 和父进程通信,从而使各进程交替处理连接服务。 -### 测试 +## 测试 JavaScript开发者需要转变观念,正视自己的代码,对自己产出的代码负责。为自己的代码写测试用例是一种非常有效的方法。能够让开发者明确掌握到代码的行为和性能 @@ -991,11 +993,11 @@ JavaScript开发者需要转变观念,正视自己的代码,对自己产出 - 接口抽象 针对接口进行测试 - 层次分离 **是单一职责的一种实现**,例如MVC分层结构 逐层测试、逐层保证 -#### 断言(assert) +### 断言(assert) > 在程序设计中,断言(assertion)是一种放在程序中的一阶逻辑,目的是为了标示程序开发者预期的结果——当程序运行到断言的位置时,对应的断言应该为真,不为真则程序会中止运行,并出现错误信息 -```javascript +```js const assert = require('assert'); // 判断是否相同 assert.equal(Math.max(1, 100), 100); @@ -1010,7 +1012,7 @@ assert.equal(Math.max(1, 100), 100); - notEqual():判断实际值与期望值是否不相等 - .... -#### 测试风格 +### 测试风格 - TDD:测试驱动开发 - BDD:行为驱动开发 @@ -1020,21 +1022,21 @@ assert.equal(Math.max(1, 100), 100); - **关注点不同** TDD关注所有功能是否被正确实现,每个功能都具有对应的测试用例;BDD关注整体行为是否符合预期,适合指定向下的设计方式 - **表达方式不同** TDD的表述方式偏向于功能说明书的风格;BDD的表述方式更加接近自然语言的习惯,以讲故事的风格; -#### 测试框架 +### 测试框架 - mocha - egg框架中自带的assert相关 -#### 工程化和自动化 +### 工程化和自动化 - 工程化: Makefile - 自动化(持续集成):travis-ci -#### 性能测试 +### 性能测试 - benchmark模块 -#### 压力测试 +### 压力测试 对网络的压力测试考察目标: @@ -1054,9 +1056,9 @@ assert.equal(Math.max(1, 100), 100); **单元测试能够保证项目每个局部的正确性,也能够在项目迭代过程中很好地监督和反馈迭代质量。** -##### Node.js发送邮件 +### Node.js发送邮件 -```javascript +```js var nodemailer = require("nodemailer"); // 建立一个SMTP的传输连接 var smtpTransport = nodemailer.createTransport("SMTP", { diff --git "a/docs/manuscripts/read-books/cs-books/\347\213\274\344\271\246.md" "b/docs/manuscripts/read-books/cs-books/\347\213\274\344\271\246.md" index 830338c16..c059328ea 100644 --- "a/docs/manuscripts/read-books/cs-books/\347\213\274\344\271\246.md" +++ "b/docs/manuscripts/read-books/cs-books/\347\213\274\344\271\246.md" @@ -1,9 +1,14 @@ +--- +title: 狼书 - 更了不起的Node.js +permalink: /manuscripts/read-books/cs-books/better-nodejs.html +--- + # 狼书 - 更了不起的Node.js -## 第一章 Node.js初识 +## Node.js初识 -#### 核心命令 +### 核心命令 - npm run dev > 利用Node.js编写的模块辅助开发命令,常用于本地开发,不会产生最终文件 @@ -19,7 +24,7 @@ Atwood定律: >任何能够用Javascript实现的应用系统,最终都必将用Javascript实现 -#### Node.js早期架构 +### Node.js早期架构 - Chrome V8引擎:Google发布的开源Javascript引擎,采用`C/C++`编写,在Google的Chrome浏览器中被使用,Chrome V8引擎可以独立运行,也可以嵌入到`C/C++`应用程序中被执行。 - Node.js内置了Chrome V8引擎,所以使用的Javascript语法 @@ -30,14 +35,14 @@ Atwood定律: - **只要有CPU资源,就应该尽力执行**,榨干硬件性能; -#### Node.js特点 +### Node.js特点 >Node.js是可扩展的适合用于构建高性能Web应用的最简单的解决方案(**适合构建Web应用、高性能、简单、可拓展**) ##### 适合构建Web应用 - 构建网站:用Node.js做入门开发和传统的Java、PHP开发并没有什么区别。构建成功的应用是典型的单体式应用。 - 构建API:后端API接口开发用于数据库访问,将返回的数据进行包裹,以HTTP形式返回;API Proxy针对前端使用的API进行优化,使前端开发更人性化。 -```javascript +```js // 常见接口返回格式: { @@ -73,7 +78,7 @@ Atwood定律: - 适用于Serverless--------->istio -##### 高性能 +#### 高性能 - 执行速度快:构建在ChromeV8引擎之上,执行速度可能是动态语言运行时环境里最快的 @@ -81,7 +86,7 @@ Atwood定律: - 适用于I/O密集的网络应用开发,不是很适合CPU密集型应用,合理的采用技术栈,利用Node.js的优势,不仅能够加快开发迭代的速度,对系统的稳定性也是非常有帮助的; -##### 简单 +#### 简单 - 语法简单:Javascript简单易学,深入比较难 @@ -91,14 +96,14 @@ Atwood定律: - 开发简单:遵寻“小而美”的设计哲学 -##### 可拓展 +#### 可拓展 - npm上有大量模块可以使用 - 通过编写C/C++实现CPU密集型任务开发 - 可以轻松搭配Java、Rust等语言使用 - 架构互补:以业务边界来进行服务拆分,可以让合适的轮子出现在合适的位置上 -#### 应用场景 +### 应用场景 - 网站: Express 、 Koa @@ -127,21 +132,10 @@ Atwood定律: +## Nodejs安装与入门 ---- - - - - - - - - - -## 第二章 Nodejs安装与入门 - -#### 3m安装Node.js +3m安装Node.js - nvm(node version manager):用于开发阶段,解决多版本共存、切换、测试等问题; @@ -150,9 +144,8 @@ Atwood定律: - nrm(node registry manager):解决npm镜像访问慢的问题,提供测速、切换下载源仓库(registry)功能; ----- -#### npm +### npm ```bash ## node版本 @@ -177,7 +170,7 @@ npm install --save-dev 或 npm install -D npm install --global 或 npm install -g ``` -#### nrm +### nrm ```bash ## 安装 @@ -197,7 +190,7 @@ nrm -h ``` -#### Hello Node.js +### Hello Node.js Node.js是基于CommonJS规范的实现,即每个文件都是一个模块,每个模块内代码的写法都必须遵守CommonJS规范,**多文件调用的核心基于模块对外暴露接口和相互引用;** @@ -212,9 +205,8 @@ Node.js是基于CommonJS规范的实现,即每个文件都是一个模块, 3. 打日志; ----- -## 第三章 更了不起的Node.js +## 更了不起的Node.js 这一章节我看完,主要是对于架构和技术演进的说明,对了解技术方向、扩展技术思路还是很有帮助的,建议直接看书或者pdf文档,这里只简单总结 @@ -265,7 +257,7 @@ Node.js是基于CommonJS规范的实现,即每个文件都是一个模块, 例如: -```javascript +```js // 相关模块 @@ -340,11 +332,10 @@ http.request方法的返回值是http.ClientRequest,它继承自OutgoingMessag **编程是没有捷径的,代码是一切的基础,能够做到每日精进自然是极好的** -#### 如果你能够在开源博客、论坛上坚持写博客和开源项目两年,一定能轻松进入BAT,不用你找他们,他们自然会找你 +**如果你能够在开源博客、论坛上坚持写博客和开源项目两年,一定能轻松进入BAT,不用你找他们,他们自然会找你** ----- -## 第四章 更好的Node.js +## 更好的Node.js ### 选择 @@ -353,7 +344,7 @@ http.request方法的返回值是http.ClientRequest,它继承自OutgoingMessag >Express框架就是典型的面向过程的代码,整个框架大体上感觉只需要用心、专注了解中间件,就可以无障碍的开发和编写了; -```javascript +```js // express模块 const express =require('express') // 实例化对象 @@ -380,7 +371,7 @@ app.listen(3000,()=>{ > 在早期的node.js语法中,更多的时候面向过程,随着对Node.js学习和使用的深入,就会了解到ES6语法也借鉴了Java这种面向对象语言的特性,添加class和extends这些关键字,来实现类的封装和继承,这样就可以很简单的写面向对象的程序(当然也是有局限的) -```javascript +```js // 定义学生类Student class Student{ // 构造函数 @@ -418,7 +409,7 @@ const boy=new Boy('Tim') > Javascript式典型的多范式编程语言,而函数式编程的概念是从React框架流行起来的。 -```javascript +```js // 类似箭头函数 可读性不强 const map=fn=>array=>array.map(fn) @@ -456,7 +447,7 @@ const map=fn=>{ 常见单线程实例: -```javascript +```js const fs=require('fs'); const Koa=require('koa') @@ -477,7 +468,7 @@ app.listen(3000) 在koa框架里面,我们可以很熟悉的使用`node app.js`来启动,然而按照这样的代码启动只能初始化一个Node.js进程,往往单进程很容易崩溃,当流量大了、服务过载单线程启动就存在问题,因此可以使用`uncaughtException`捕获异常 -```javascript +```js const fs=require('fs'); const Koa=require('koa') @@ -571,9 +562,8 @@ yarn install 看到上面的指令,使用过npm的应该会感觉很熟悉,至少我个人上手很快,但对于初学者推荐使用npm,因为npm是绕不开的核心 ----- -## 第五章 Node.js是如何执行的 +## Node.js是如何执行的 这一章主要讲解Nodejs的执行流程、原理,建议看书,我这里只是总结认为重要且必须掌握的内容 @@ -589,7 +579,7 @@ yarn install > 可以参考对应的[官方api文档](http://nodejs.cn/api/process.html#process_process_cpuusage_previousvalue) -```javascript +```js const startUsage = process.cpuUsage(); // { user: 38579, system: 6986 } @@ -609,7 +599,7 @@ console.log(process.cpuUsage(startUsage)); 在上述循环队列中,会设置到`nextTick`和`_tickCallback`两个方法 -```javascript +```js // function lateCallback(){ console.log('print me later') @@ -630,7 +620,7 @@ console.log('print me first') > 当Nodejs发现一个没有被捕获的异常时候,会触发这个事件。如果这个事件中存在回调函数,Node.js不会强制结束进程。 -```javascript +```js process.on('uncaughtException',err=>{ // 处理错误 .... @@ -688,13 +678,12 @@ process.nextTick(callback)将一个函数推迟到代码执行的下一个同步 > 原理: 可以基于Node.js的事件循环进行分析,每次循环就是一次tick,每次tick时,Chrome V8引擎都会从事件队列中取出所有事件依次进行处理,如果遇到nextTick()事件,则将其加入事件队尾,等待下一次tick到来时执行。这样直接导致nextTick()事件将会被延迟执行。 ----- -## 第六章 模块与核心 +## 模块与核心 -### Node.js和CommonJS的区别(主要体现在module.exports): +Node.js和CommonJS的区别(主要体现在module.exports): - Node.js中,module.exports是真正的特殊对象,是真正的对外暴露出口,而exports只有一个变量,是被默认的module.exports版绑定的 - CommonJS规范里灭有module.exports对象。 @@ -702,7 +691,7 @@ process.nextTick(callback)将一个函数推迟到代码执行的下一个同步 exports是一个特殊的对象,它的任何输出都将作为一个对外暴露的公共API -```javascript +```js // 导出演示 const PI=Math.PI @@ -718,7 +707,7 @@ const PI=require('XXX') 特别注意的是:当module.exports和exports对象同时存在时,以module.exports为准 -```javascript +```js exports=()=>{ return { a:123 @@ -761,7 +750,7 @@ Node.js对模块的定义非常简单,主要分为模块应用、模块定义 > 可以将关联代码封装到一个代码单元中,创建一个模块可以理解为全部有关联的函数放在一个文件中 -```javascript +```js const sayHelloEnglish=function(){ return 'hello' } @@ -781,14 +770,14 @@ module.exports-->exports ``` **核心是module.exports,exports对象只是module.exports的一个引用** -```javascript +```js // 变量引用 const exports=module.exports={} ``` -```javascript +```js exports.sayHelloInChinese=()=>{ return '你好' } @@ -800,7 +789,7 @@ exports.sayHelloInEnglish=()=>{ ``` -```javascript +```js module.exports={ sayHelloInEnglish:()=>{ return 'Hello'; @@ -832,7 +821,7 @@ module.exports={ module.exports不一定非要返回实例化对象 -```javascript +```js module.exports=1 module.exports=NaN @@ -857,7 +846,7 @@ module.exports=()=>{ 给module.exports添加属性类似给exports添加属性,exports可以看作是module.exports的一个引用; -```javascript +```js module.exports.name=()=>{ console.log('My name is Lemmy Kilmister ') } @@ -876,7 +865,7 @@ exports.name=()=>{ 推荐最佳写法 -```javascript +```js exports=module.exports=opts=>{ // 除了工具类用exports.xxxx 其他都建议用module.exports .... @@ -918,7 +907,7 @@ Node.js模块分为: 在Nodejs中有一批内置对象,无需安装即可使用的模块,不需要依赖global关键字 -##### 第一类 为模块包装提供的全局对象 +##### 为模块包装提供的全局对象 - exports: CommonJS关键字 - require: CommonJS关键字 @@ -927,16 +916,16 @@ Node.js模块分为: - _dirname:当前文件目录 -##### 第二类 内置process对象 +##### 内置process对象 > process为核心模块,可以在当前Nodejs的各种信息进行绑定,Java8中也有类似的思想 -##### 第三类 控制台Console模块 +##### 控制台Console模块 > 比如:console.log(),console.info()... 跟JavaScript里面不一样的是,Nodejs里面的可以在终端中输出,而Javascript中的会在浏览器的控制台进行输出 -##### 第四类 Event Loop相关Api的实现 +##### Event Loop相关Api的实现 - setImmediate(callback[,...args]) - setInterval(callback,delay[,...args]) @@ -949,7 +938,7 @@ Node.js模块分为: Event Loop用全局对象来实现是非常方便的; -##### 第五类 Buffer对象 +##### Buffer对象 - new Buffer() - Buffer.from() : 推荐使用 @@ -963,7 +952,7 @@ Nodejs中的全局对象和Javascript里的普通对象是一样的,主要是 扩展变量: -```javascript +```js // 扩展debug变量,并进行加载 global.debug=true; @@ -978,7 +967,7 @@ if(debug===true){ 扩展方法: -```javascript +```js // log方法扩展 @@ -1023,7 +1012,7 @@ import {readFile} from 'fs' ##### 模块导出 -```javascript +```js // 对所有内容进行导出 export * from 'XXXXX' @@ -1039,7 +1028,7 @@ export {foot as foot_copy,bar} from 'XXXX' ###### 具名导出 > 导出对象的指定别名的过程叫做具名导出 -```javascript +```js export {MY_CONST as FOO,myFunc}; export {foot as test} @@ -1050,7 +1039,7 @@ export {foot as test} > 变量的声明有多种,在内联具名导出时不会受到限制 -```javascript +```js // 导出var定义的foo变量 export var foo; @@ -1063,7 +1052,7 @@ export let test; 函数也可以类似变量进行导出 -```javascript +```js export function myFunc(){ // 处理逻辑 .... @@ -1079,7 +1068,7 @@ export function* myGenFunc(){ ``` -```javascript +```js export class MyClass{ // 类实现 @@ -1094,7 +1083,7 @@ export class MyClass{ > > 值得注意的是: -```javascript +```js export default function myFunc(){ // @@ -1151,14 +1140,8 @@ export default (function(){}); - 使用 export 导出的成员,如果就想换个名称来接收,可以使用 as 来起别名; ----- - - - - - -## 第七章 异步写法与流程控制 +## 异步写法与流程控制 **流程控制是程序中的逻辑控制的统称,** Node.js中每个函数都是异步的,性能虽然会更好,但容易造成**callback hell(回调地狱)**,因此为了解决API级别的回调地狱问题,就需要使用的到流程控制的部分——异步流程控制 @@ -1166,7 +1149,7 @@ export default (function(){}); -#### 异步与同步 +### 异步与同步 > 异步执行效率更高,但结果却不一定是我们想要的,异步执行的结果具有一定的不确定性 @@ -1179,7 +1162,7 @@ export default (function(){}); > 操作必须要等待回复后才能去做其他的事情,有种至死方休的感觉。每一步都需要等待上一步完成才能进行; 例如JQuery中的ajax异步请求 -```javascript +```js // 存在于浏览器中的异步 $.ajax({ url:'XXXXX', @@ -1207,7 +1190,7 @@ EventLoop依赖libuv库,而libuv库采用的是异步和事件驱动的风格 API:Application Programming Interface简称,异步的核心在于Nodejs SDK的API调用,然后交由EventLoop(libuv)去执行。**因此Nodejs的API操作非常重要**; [Nodejs Api官网](http://nodejs.cn/api/) -```javascript +```js const fs=require('fs'); // 文件路劲 const path='.' @@ -1239,7 +1222,7 @@ const result=fs.readdirSync(path); console.log(result) ``` -##### 同步和异步的简单区别 +#### 同步和异步的简单区别 - 同步方式更容易理解,但会造成线程阻塞,无法最大限度利用系统资源; - 异步方式需要嵌套回调,即使代码编写得非常规范也不容易理解和维护,但它能够并行执行,同时处理更多的任务,**效率更高** @@ -1264,7 +1247,7 @@ callback function采用错误优先(error-first)的回调方式,而EventEmitte - 回调函数的第二个参数返回的是所有成功响应的结果数据,如果结果正常,则没有发生错误,参数err就会被设置成null,并在第二个参数处返回成功响应的结果; -```javascript +```js /** * err :错误对象 * data : 成功时返回的数据 @@ -1285,7 +1268,7 @@ function (err,data){ - 模块应该暴露错误优先的回调接口 -```javascript +```js module.exports=function(dragonName,callback){ // 逻辑处理 const dragon=createDragon(dragonName); @@ -1297,7 +1280,7 @@ module.exports=function(dragonName,callback){ **只有同步代码才能使用try-catch,在回调函数中不能随意使用!**经典异常捕获方法: -```javascript +```js const fs=require('fs'); function readJSON(filePath,callback){ fs.readFile(filePath,function(err,data){ @@ -1334,7 +1317,7 @@ on-->emit EventEmitter是Node.js的基础模块,通过EventEmitter属性建立了一个EventEmitter对象实例,即:消息中心 -```javascript +```js const EventEmitter=require('events') // 初始化消息中心实例 const observer=new EventEmitter(); @@ -1364,7 +1347,7 @@ main() 可以简单理解为“发布/订阅”模式,当observer调用emit方法时,所有通过on注册该topic事件的回调函数都会被调用; EventEmitter对象的事件触发和监听时同步的,这里和前端的事件机制很类似,Vue的父组件传值,也有对应的$emit和$on的机制 -```javascript +```js // jquery $('#footer').on('click',function(){ @@ -1384,7 +1367,7 @@ $('#footer').click(()=>{ ``` emit()方法用于触发事件,on()方法用于注册事件。对于on()方法而言,默认情况下,Node.js允许同一个事件最多指定10个回调函数 -```javascript +```js event.on('someEvent',()=>{ console.log('event 1') }) @@ -1405,7 +1388,7 @@ event.setMaxListeners(100) 事件传参举例: -```javascript +```js const eventEmitter=require('events') const myEmitter=new EventEmitter(); @@ -1419,8 +1402,6 @@ myEmitter.on('test',testConnection) myEmitter.emit('test',10) ``` ---- - ### 事件 - 事件操作接口 @@ -1440,7 +1421,7 @@ myEmitter.emit('test',10) **每个Promise对象都有then()方法,也就是说,then()方法是定义在原型对象Promise.protype上的,作用是为Promise实例添加状态改变时的回调函数** -```javascript +```js Promise.protype.then=function(success,fail){ this.done(success); this.fail(fail); @@ -1484,7 +1465,7 @@ pending-->rejected ### Promise的API方法 > Promise规范非常简单,只包含一个构造函数和六个方法 -```javascript +```js // 构造函数 new Promise((resolve,reject)=>{ @@ -1496,7 +1477,7 @@ new Promise((resolve,reject)=>{ - resolve可以让状态从pending切换到fulfilled - reject可以让状态从pending切换到rejected(当然,reject是可选参数) -```javascript +```js Promise.protype.then() 捕获当前操作的resolve结果 @@ -1506,7 +1487,7 @@ Promise.protype.catch() 捕获全局操作的reject异常 **特别值得注意的是:resolve相当于Promise.resolve的别名,reject相当于Promise.reject的别名**。 -```javascript +```js new Promise(resolve=>{ resolve(1) }).then(ret=>{ @@ -1542,7 +1523,7 @@ p.catch(onRejected) 或者p.catch(error=>{.............}) > 只要有一个Promise对象进入fulfilled或者rejected状态,就会继续后面的处理【**多个方法之间并行处理**】 -##### Promise化 +#### Promise化 在Javascript中,不是所有的异步函数和库都支持开箱即用的Promise,在大多数情况下,必须将一个典型的基于回调的函数转换成一个返回Promise的函数,这个过程为**promisification** @@ -1551,7 +1532,7 @@ p.catch(onRejected) 或者p.catch(error=>{.............}) 以上两个api都是基于`bulebird`模块的 -```javascript +```js const Promise =require('bluebird') const fs=Promise.promisifyAll(require('fs')) @@ -1581,7 +1562,6 @@ obj.aAsync().then(obj.bAsync()).then(obj.cAsync()).catch(err=>{ **当然,这里来源于`bluebird`模块的Promise对象,其实是具备Nodejs的Promise对象的所有构造函数和属性,兼容所有原生所有版本的** ----- ### 异常处理 @@ -1608,6 +1588,3 @@ obj.aAsync().then(obj.bAsync()).then(obj.cAsync()).catch(err=>{ **function* 这种声明方式(function关键字后跟一个星号)会定义一个生成器函数 (generator function),它返回一个 Generator 对象。** - - -ending \ No newline at end of file diff --git "a/docs/manuscripts/read-books/cs-books/\351\253\230\346\200\247\350\203\275Web\346\234\215\345\212\241\345\231\250\350\257\246\350\247\243.md" "b/docs/manuscripts/read-books/cs-books/\351\253\230\346\200\247\350\203\275Web\346\234\215\345\212\241\345\231\250\350\257\246\350\247\243.md" index a02c17ba2..7c3654fdf 100644 --- "a/docs/manuscripts/read-books/cs-books/\351\253\230\346\200\247\350\203\275Web\346\234\215\345\212\241\345\231\250\350\257\246\350\247\243.md" +++ "b/docs/manuscripts/read-books/cs-books/\351\253\230\346\200\247\350\203\275Web\346\234\215\345\212\241\345\231\250\350\257\246\350\247\243.md" @@ -1 +1,6 @@ +--- +title: 高性能Web服务器详解 +permalink: /manuscripts/read-books/cs-books/high-performance-web-server.html +--- + # 高性能Web服务器详解 \ No newline at end of file diff --git a/docs/manuscripts/read-books/read-books.sidebar.ts b/docs/manuscripts/read-books/read-books.sidebar.ts index dd22f5b15..146cd77ee 100644 --- a/docs/manuscripts/read-books/read-books.sidebar.ts +++ b/docs/manuscripts/read-books/read-books.sidebar.ts @@ -5,11 +5,10 @@ export const readBooksSidebar = [ children: [ { text: 'ES6标准入门', - link: 'es-standard' + link: 'ES6标准入门.md' }, { text: '更了不起的Node.js ', - collapsible: true, link: '狼书.md' }, { diff --git a/docs/manuscripts/server-end/build-website/nginx-gzip.md b/docs/manuscripts/server-end/build-website/nginx-gzip.md index 2bd19ad74..fd89b4afc 100644 --- a/docs/manuscripts/server-end/build-website/nginx-gzip.md +++ b/docs/manuscripts/server-end/build-website/nginx-gzip.md @@ -9,7 +9,7 @@ npm compression-webpack-plugin --save-dev ``` ### 配置vue.config.js文件 -```JavaScript +```js // 导入compression-webpack-plugin const CompressionWebpackPlugin = require('compression-webpack-plugin') // 定义压缩文件类型 diff --git a/docs/manuscripts/server-end/database/Readme.md b/docs/manuscripts/server-end/database/Readme.md index 92b4cbfdc..fa079c4f0 100644 --- a/docs/manuscripts/server-end/database/Readme.md +++ b/docs/manuscripts/server-end/database/Readme.md @@ -1,3 +1,7 @@ +--- +title: 数据库 +permalink: /manuscripts/server-end/database +--- # 数据库 diff --git a/docs/manuscripts/server-end/database/mysql/test.md b/docs/manuscripts/server-end/database/mysql/test.md deleted file mode 100644 index 4bf1d7b8b..000000000 --- a/docs/manuscripts/server-end/database/mysql/test.md +++ /dev/null @@ -1,498 +0,0 @@ - -## MySQL整理 - -> 参考:https://juejin.cn/post/6850037271233331208#comment -> -> sql解析:https://juejin.cn/post/6850037271233331208#heading-52 - -### 基础架构 - -> 由外向里,逐步深入 - -- 连接层:**完成一些类似于连接处理、授权认证、及相关的安全方案**,引入了**线程池**的概念 -- 服务层:完成大部分的核心服务功能, 包括查询解析、分析、优化、缓存、以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括触发器、存储过程、视图等 -- 引擎层:**存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信** -- 存储层:将数据存储在运行于该设备的文件系统之上,并完成与存储引擎的交互 - - - -### 存储引擎 - -- InnoDB 【MySQL 默认的存储引擎,支持**事务、行级锁定和外键**】 -- MyISAM【**不支持事务,不支持外键**】 -- Memory -- NDB -- ...... - -> 不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能 - -**一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求**,使用合适的存储引擎,将会提高整个数据库的性能 - - - -#### 查看存储引擎 - -```bash --- 查看支持的存储引擎 -SHOW ENGINES - --- 查看默认存储引擎 -SHOW VARIABLES LIKE 'storage_engine' - ---查看具体某一个表所使用的存储引擎,这个默认存储引擎被修改了! -show create table tablename - ---准确查看某个数据库中的某一表所使用的存储引擎 -show table status like 'tablename' -show table status from database where name="tablename" -``` - -**InnoDB 是聚簇索引,MyISAM 是非聚簇索引**。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。 - - - -- MyISAM表会把自增主键的最大ID 记录到**数据文件**中,重启MySQL自增主键的最大ID也不会丢失 -- InnoDB 表只是把自增主键的最大ID记录到**内存**中,所以重启数据库或对表进行OPTION操作,都会导致最大ID丢失。 - - - -### 数据类型 - -- 整数类型:BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT -- 浮点数类型:FLOAT、DOUBLE、DECIMAL -- 字符串类型:CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB -- 日期类型:Date、DateTime、TimeStamp、Time、Year -- 其他数据类型:BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection等 - -> BLOB和TEXT有什么区别? **字符串类型是:SET、BLOB、ENUM、CHAR、TEXT、VARCHAR** - -- BLOB是一个二进制对象,可以容纳可变数量的数据。有四种类型的BLOB:TINYBLOB、BLOB、MEDIUMBLO和 LONGBLOB -- TEXT是一个不区分大小写的BLOB。四种TEXT类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。 -- BLOB 保存二进制数据,TEXT 保存字符数据。 - - - -### 索引(单独重点来) - -> 索引(Index)是帮助MySQL高效获取数据的数据结构,所以说**索引的本质是:数据结构** - -索引本身也很大,不可能全部存储在内存中,**一般以索引文件的形式存储在磁盘上** - -##### 优点 - -- **提高数据检索效率,降低数据库IO成本** -- **降低数据排序的成本,降低CPU的消耗** - -##### 缺点 - -- 索引也是一张表,保存了主键和索引字段,并指向实体表的记录,所以也需要占用内存 -- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,【更新表的时候需要更新索引】 - - - - - -### 事务 - -> 主要用于处理操作量大,复杂度高的数据 - -#### 基本要素(ACID) - -- **A (Atomicity) 原子性**:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样 -- **C (Consistency) 一致性**:在事务开始之前和事务结束以后,数据库的**完整性约束**没有被破坏 -- **I (Isolation)隔离性**:一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰【**加锁实现**】 -- **D (Durability) 持久性**:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚 - -**并发事务处理带来的问题** - -- 更新丢失(Lost Update): 事务A和事务B选择同一行,然后基于最初选定的值更新该行时,由于两个事务都不知道彼此的存在,就会发生丢失更新问题 -- 脏读(Dirty Reads):事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据 -- 不可重复读(Non-Repeatable Reads):事务 A 多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。 -- 幻读(Phantom Reads):幻读与不可重复读类似。它发生在一个事务A读取了几行数据,接着另一个并发事务B插入了一些数据时。在随后的查询中,事务A就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为**幻读**。 - -**幻读和不可重复读的区别:** - -- **不可重复读的重点是修改**:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改) -- **幻读的重点在于新增或者删除**:在同一事务中,同样的条件,第一次和第二次读出来的记录数不一样。(因为中间有其他事务提交了插入/删除) - -**并发事务处理带来的问题的解决办法:** - -- “更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。 -- “脏读” 、 “不可重复读”和“幻读” ,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决: - - 一种是加锁:在读取数据前,对其加锁,阻止其他事务对数据进行修改。 - - 另一种是数据多版本并发控制(MultiVersion Concurrency Control,简称 **MVCC** 或 MCC),也称为多版本数据库:不用加任何锁, 通过一定机制生成一个数据**请求时间点**的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本。 - -### 事务隔离级别 - -数据库事务的隔离级别有4种,由低到高分别为 - -- **READ-UNCOMMITTED(读未提交):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。 -- **READ-COMMITTED(读已提交):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。 -- **REPEATABLE-READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。 -- **SERIALIZABLE(可串行化):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。 - -> 简单来说,Serializable可串行化会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用问题。这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。 - - - - - -**数据库的事务隔离越严格,并发副作用越小,但付出的代价就越大,并发性就越差,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的** - -> MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;` - - - -与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ(可重读)**事务隔离级别下使用的是`Next-Key Lock` 算法,**因此可以避免幻读的产生**,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)已经可以完全保证事务的隔离性要求,**即达到了 SQL标准的 SERIALIZABLE(可串行化)隔离级别,而且保留了比较好的并发性能**。 - -数据库使用锁是为了支持更好的并发,提供数据的完整性和一致性。InnoDB是一个支持行锁的存储引擎,锁的类型有: - -- 共享锁(S) -- 排他锁(X) -- 意向共享(IS) -- 意向排他(IX) - -对应行锁的三种算法: - -- Record Lock:单个行记录上的锁。 -- Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。**GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。** -- Next-Key Lock:1+2,锁定一个范围,并且**锁定记录本身**。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。 - - - -为了提供更好的并发,InnoDB提供了**非锁定读**:不需要等待访问行上的锁释放,读取行的一个快照。**该方法是通过InnoDB的一个特性:MVCC来实现的**。 - - - - - -#### MVCC 多版本并发控制 - -- **乐观(optimistic)并发控制** -- **悲观(pressimistic)并发控制** - -MVCC 是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了**非阻塞的读操作**,写操作也只是锁定必要的行 - - - -**MVCC 的实现是通过保存数据在某个时间点的快照来实现的**。也就是说不管需要执行多长时间,每个事物看到的数据都是一致的。 - - - -> InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存行的过期时间(删除时间)。当然存储的并不是真实的时间,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 - -保存这两个额外系统版本号,使大多数操作都不用加锁。使数据操作简单,性能很好,并且也能保证只会读取到符合要求的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作和一些额外的维护工作。 - -**MVCC 只在 COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作。** - - - - - -### 事务的实现 - -> 事务的实现就是如何实现ACID特性 - -**MySQL 中支持事务的存储引擎有 InnoDB 和 NDB** - -**事务的隔离性是通过锁实现,而事务的原子性、一致性和持久性则是通过事务日志实现** - -- **redo log(重做日志**) 实现持久化和原子性 -- **undo log(回滚日志)** 实现一致性 - -##### MySQL日志分类 - -> 参考:https://www.cnblogs.com/myseries/p/10728533.html - -- **错误日志**:记录出错信息,也记录一些警告信息或者正确的信息。 -- **查询日志**:记录所有对数据库请求的信息,不论这些请求是否得到了正确的执行。 -- **慢查询日志**:设置一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询的日志文件中。 -- **二进制日志**:记录对数据库执行更改的所有操作。 -- **中继日志**:中继日志也是二进制日志,用来给slave 库恢复 -- **事务日志**:重做日志redo和回滚日志undo 【redo和undo都属于处理事务的日志类型】 - -> 事务日志均可以视为一种恢复操作,redo_log是恢复提交事务修改的页操作,而undo_log是回滚行记录到特定版本;redo_log是物理日志,记录页的物理修改操作,而undo_log是逻辑日志 - -``` -redo 重做日志 【实现持久性和一致性】 - 作用:确保事务的持久性,防止在发生故障,脏页未写入磁盘。重启数据库会进行redo log执行重做,到达事务一致性 - -undo 回滚日志 【实现原子性】 - 作用:保证数据的原子性,记录事务发生之前的数据的一个版本,用于回滚。 - innodb事务的可重复读和读取已提交 隔离级别就是通过mvcc+undo实现 - -errorlog 错误日志 - 作用:Mysql本身启动、停止、运行期间发生的错误信息 - -slow query log 慢查询日志 - 作用:记录执行时间过长的sql,时间阈值可以配置,只记录执行成功 - -binlog 二进制日志 - 作用:用于主从复制,实现主从同步 - -relay log 中继日志 - 作用:用于数据库主从同步,将主库发送来的binlog先保存在本地,然后从库进行回放 - -general log 普通日志 - 作用:记录数据库操作明细,默认关闭,开启会降低数据库性能 -``` - -### MySQL锁机制 - -> 锁是计算机协调多个进程或线程并发访问某一资源的机制,是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则 - -#### 锁的分类 - -**从对数据操作的类型分类**: - -- **读锁**(共享锁):针对同一份数据,多个读操作可以同时进行,不会互相影响 -- **写锁**(排他锁):当前写操作没有完成前,它会阻断其他写锁和读锁 - -**从对数据操作的粒度分类**: - -> 为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好,但需要在高并发响应和系统性能两方面进行平衡 - -**表级锁**:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低(MyISAM 和 MEMORY 存储引擎采用的是表级锁); - -**行级锁**:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高(InnoDB 存储引擎既支持行级锁也支持表级锁,但默认情况下是采用行级锁); - -**页面锁**:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。 - -> 适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用 -> -> 而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用 - -| 存储引擎 | 行锁 | 表锁 | 页锁 | -| -------- | ---- | ---- | ---- | -| MyISAM | | √ | | -| BDB | | √ | √ | -| InnoDB | √ | √ | | -| Memory | | √ | | - -### MyISAM 表锁 - -MyISAM 的表锁有两种模式: - -- 表共享读锁 (Table Read Lock):不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求; -- 表独占写锁 (Table Write Lock):会阻塞其他用户对同一表的读和写操作; - -**MyISAM 表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后, 只有持有锁的线程可以对表进行更新操作。 其他线程的读、 写操作都会等待,直到锁被释放为止。** - -默认情况下,写锁比读锁具有更高的优先级:当一个锁释放时,这个锁会优先给写锁队列中等候的获取锁请求,然后再给读锁队列中等候的获取锁请求。 - -### InnoDB 行锁 - -InnoDB 实现了以下两种类型的**行锁**: - -- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。 -- 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。 - -为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是**表锁**: - -- 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。 -- 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。 - -**索引失效会导致行锁变表锁**。比如 vchar 查询不写单引号的情况。 - -#### 加锁机制 - -**乐观锁与悲观锁是两种并发控制的思想,可用于解决丢失更新问题** - -乐观锁会“乐观地”假定大概率不会发生并发更新冲突,访问、处理数据过程中不加锁,只在更新数据时再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务。用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式 - -悲观锁会“悲观地”假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁。另外与乐观锁相对应的,**悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。** - - - - - -#### 锁模式(InnoDB有三种行锁的算法) - -- **记录锁(Record Locks)** -- **间隙锁(Gap Locks)** -- **临键锁(Next-key Locks)**【解决幻读问题】 - -**记录锁(Record Locks)**: 单个行记录上的锁。对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项; - - - -**间隙锁(Gap Locks)**: 当我们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁。对于键值在条件范围内但并不存在的记录,叫做“间隙”。 - -InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。 - -对索引项之间的“间隙”加锁,锁定记录的范围(对第一条记录前的间隙或最后一条将记录后的间隙加锁),不包含索引项本身。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行。 - -**间隙锁基于非唯一索引,它锁定一段范围内的索引记录**。间隙锁基于下面将会提到的`Next-Key Locking` 算法,请务必牢记:**使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据**。 - -**GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况** - - - -**临键锁(Next-key Locks)**: **临键锁**,是**记录锁与间隙锁的组合**,它的封锁范围,既包含索引记录,又包含索引区间。(临键锁的主要目的,也是为了避免**幻读**(Phantom Read)。**如果把事务的隔离级别降级为RC,临键锁则也会失效**。) - -**Next-Key 可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法**。**【通过临建锁可以解决幻读的问题】**。 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。 - -需要强调的一点是,**`InnoDB` 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。** - -- 对于行的查询,都是采用该方法,主要目的是解决幻读的问题。 - -> select for update有什么含义,会锁表还是锁行还是其他 - -**for update 仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效**。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。 - -InnoDB这种行锁实现特点意味着:**只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!** - - - - - - - -#### 死锁 - -**死锁产生**: - -- 死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环 -- 当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁 -- 锁的行为和顺序和存储引擎相关。以同样的顺序执行语句,有些存储引擎会产生死锁有些不会——死锁有双重原因:**真正的数据冲突;存储引擎的实现方式**。 - -**检测死锁**:数据库系统实现了各种死锁检测和死锁超时的机制。InnoDB存储引擎能检测到死锁的循环依赖并立即返回一个错误。 - -**死锁恢复**:死锁发生以后,只有部分或完全回滚其中一个事务,才能打破死锁,InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。所以事务型应用程序在设计时必须考虑如何处理死锁,多数情况下只需要重新执行因死锁回滚的事务即可。 - -**外部锁的死锁检测**:发生死锁后,InnoDB 一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB 并不能完全自动检测到死锁, 这需要通过设置锁等待超时参数 innodb_lock_wait_timeout 来解决 - -**死锁影响性能**:死锁会影响性能而不是会产生严重错误,因为InnoDB会自动检测死锁状况并回滚其中一个受影响的事务。在高并发系统上,当许多线程等待同一个锁时,死锁检测可能导致速度变慢。 有时当发生死锁时,禁用死锁检测(使用innodb_deadlock_detect配置选项)可能会更有效,这时可以依赖`innodb_lock_wait_timeout`设置进行事务回滚。 - -**MyISAM避免死锁**: - -- 在自动加锁的情况下,MyISAM 总是一次获得 SQL 语句所需要的全部锁,所以 MyISAM 表不会出现死锁。 - -**InnoDB避免死锁**: - -- 为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时通过为预期要修改的每个元祖(行)使用`SELECT ... FOR UPDATE`语句来获取必要的锁,即使这些行的更改语句是在之后才执行的。 -- 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁 -- 如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会 -- 通过`SELECT ... LOCK IN SHARE MODE`获取行的读锁后,如果当前事务再需要对该记录进行更新操作,则很有可能造成死锁。 -- 改变事务隔离级别 - -**如果出现死锁,可以用 `show engine innodb status;`命令来确定最后一个死锁产生的原因**。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。 - - - -### MySQL分区 - -一般情况下我们创建的表对应一组存储文件,使用`MyISAM`存储引擎时是一个`.MYI`和`.MYD`文件,使用`Innodb`存储引擎时是一个`.ibd`和`.frm`(表结构)文件。 - -**当数据量较大时(一般千万条记录级别以上),MySQL的性能就会开始下降,这时我们就需要将数据分散到多组存储文件,保证其单个文件的执行效率** - -**分区类型及操作** - -- pange分区 -- list分区 -- hash分区 -- key分区 - -**RANGE分区**:基于属于一个给定连续区间的列值,把多行分配给分区。mysql将会根据指定的拆分策略,,把数据放在不同的表文件上。相当于在文件上,被拆成了小块.但是,对外给客户的感觉还是一张表,透明的。 - -按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,比如交易表啊,销售表啊等,可以根据年月来存放数据。可能会产生热点问题,大量的流量都打在最新的数据上了。 - -range 来分,好处在于说,扩容的时候很简单。 - -**LIST分区**:类似于按RANGE分区,每个分区必须明确定义。它们的主要区别在于,LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分区是从属于一个连续区间值的集合。 - -**HASH分区**:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。 - -hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表 - -**KEY分区**:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。 - - - - - -### MySQL分表 - -**垂直拆分** - -垂直分表,通常是按照业务功能的使用频次,把主要的、热门的字段放在一起做为主要表。然后把不常用的,按照各自的业务属性进行聚集,拆分到不同的次要表中;**主要表和次要表的关系一般都是一对一的**。【**一般是用主键作为约束**】 - -**水平拆分(数据分片)** - -单表的容量不超过500W,否则建议水平拆分。是把一个表复制成同样表结构的不同表,然后把数据按照一定的规则划分,分别存储到这些表中,从而保证单表的容量不会太大,提升性能;当然这些结构一样的表,可以放在一个或多个数据库中。 - -水平分割的几种方法: - -- 使用**MD5哈希**,做法是对UID进行md5加密,然后取前几位(我们这里取前两位),然后就可以将不同的UID哈希到不同的用户表(user_xx)中了。 -- 还可**根据时间**放入不同的表,比如:article_201601,article_201602。 -- 按**热度拆分**,高点击率的词条生成各自的一张表,低热度的词条都放在一张大表里,待低热度的词条达到一定的贴数后,再把低热度的表单独拆分成一张表。 -- 根据**ID的值放入对应的表**,第一个表user_0000,第二个100万的用户数据放在第二 个表user_0001中,随用户增加,直接添加用户表就行了。 - - - - - - - -### MySQL分库 - -> 为什么要分库? - -数据库集群环境后都是多台 slave,基本满足了读取操作; 但是写入或者说大数据、频繁的写入操作对master性能影响就比较大,这个时候,单库并不能解决大规模并发写入的问题,所以就会考虑分库。 - -> 分库是什么? - -一个库里表太多了,导致了海量数据,系统性能下降,把原本存储于一个库的表拆分存储到多个库上, 通常是将表按照功能模块、关系密切程度划分出来,部署到不同库上。 - -优点: - -- 减少增量数据写入时的锁对查询的影响 -- **由于单表数量下降,常见的查询操作由于减少了需要扫描的记录,使得单表单次查询所需的检索行数变少,减少了磁盘IO,时延变短** - - - -微服务下,数据库本来就是分库的 - - - -### 主从复制 - -- master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events; - -- salve 将 master 的 binary log events 拷贝到它的中继日志(relay log); - -- slave 重做中继日志中的事件,将改变应用到自己的数据库中。**MySQL 复制是异步且是串行化的。** - -#### 复制的基本原则 - -- 每个 slave只有一个 master -- 每个 salve只能有一个唯一的服务器 ID【mysql_id唯一性】 -- 每个master可以有多个salve - - - - - -### 三个范式 - -- 第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。 -- 第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。 -- 第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如 果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系: 关键字段 → 非关键字段 x → 非关键字段y - - - -### 百万级别或以上的数据如何删除 - -关于索引:由于**索引需要额外的维护成本**,因为**索引文件是单独存在的文件**,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。 - -1. 所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟) -2. 然后删除其中无用数据(此过程需要不到两分钟) -3. 删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。 -4. 与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了 - - - -**先删除索引--->再删除无用数据----> 对留下来的数据重新简历索引,避免索引丢失** - - - diff --git a/docs/manuscripts/server-end/database/redis/Readme.md b/docs/manuscripts/server-end/database/redis/Readme.md deleted file mode 100644 index 436f1e620..000000000 --- a/docs/manuscripts/server-end/database/redis/Readme.md +++ /dev/null @@ -1,7 +0,0 @@ -# Redis - -- [X] [安装、使用](base-install.md) -- [X] [主从模式](master-slave.md) -- [X] [哨兵模式](sentinel.md) -- [X] [集群模式](cluster.md) -- [X] [项目应用](cluster.md) \ No newline at end of file diff --git a/docs/manuscripts/server-end/database/redis/base-install.md b/docs/manuscripts/server-end/database/redis/base-install.md index 4df3a6631..6a014cd42 100644 --- a/docs/manuscripts/server-end/database/redis/base-install.md +++ b/docs/manuscripts/server-end/database/redis/base-install.md @@ -1,8 +1,12 @@ +--- +title: 基础安装 +permalink: /manuscripts/server-end/database/redis/base-install.html +--- # 基础安装 -### 脚本方式 +## 脚本方式 利用官方脚本进行安装,注意更新apt工具:`apt-get update -y` ```bash @@ -16,10 +20,10 @@ sudo apt-get install redis ``` -### 压缩包方式 +## 压缩包方式 -#### 获取redis压缩包 +### 获取redis压缩包 ```bash @@ -31,7 +35,7 @@ tar xzvf redis-4.0.8.tar.gz ``` -#### 安装 +### 安装 ```bash cd redis-4.0.8 @@ -44,7 +48,7 @@ make install PREFIX=/usr/local/redis -#### 移动配置文件到安装目录下 +### 移动配置文件到安装目录下 ```bash cd ../ @@ -55,7 +59,7 @@ mkdir /usr/local/redis/etc mv redis.conf /usr/local/redis/etc ``` -#### redis配置后台启动 +### redis配置后台启动 将`daemonize no` 改成`daemonize yes` ```bash @@ -65,7 +69,7 @@ vi /usr/local/redis/etc/redis.conf ## wq!退出 ``` -#### 设置开启启动 +### 设置开启启动 编辑`/etc/rc.local`文件,添加内容: @@ -78,7 +82,7 @@ vi /usr/local/redis/etc/redis.conf vi /etc/rc.local ``` -#### 手动开启服务 +### 手动开启服务 注意到redis相关目录下执行`redis-server`命令 @@ -90,7 +94,7 @@ vi /etc/rc.local redis-server & ``` -#### 检查Redis相关服务 +### 检查Redis相关服务 ```bash ## 查看redis进程 @@ -102,15 +106,15 @@ netstat -lntp | grep 6379 ``` -### docker容器方式 +## docker容器方式 -#### 创建容器 +### 创建容器 @[code sh](@code/redis/docker-install.sh) -#### 相关命令 +### 相关命令 ```bash ## 重启 @@ -133,13 +137,13 @@ docker exec -it xxx bash ``` -### docker-compose方式 +## docker-compose方式 -#### 创建服务 +### 创建服务 @[code yaml](@code/redis/docker-compose.yaml) -#### 相关命令 +### 相关命令 容器操作命令和上面的类似 @@ -151,6 +155,8 @@ docker-compose down docker-compose up -d ``` -### 参考资料 + + +## 参考资料 - \ No newline at end of file diff --git a/docs/manuscripts/server-end/database/redis/cluster.md b/docs/manuscripts/server-end/database/redis/cluster.md index aa6949379..bfec1aec5 100644 --- a/docs/manuscripts/server-end/database/redis/cluster.md +++ b/docs/manuscripts/server-end/database/redis/cluster.md @@ -1,3 +1,8 @@ +--- +title: 集群模式 +permalink: /manuscripts/server-end/database/redis/cluster.html +--- + # 集群模式 diff --git a/docs/manuscripts/server-end/database/redis/master-slave.md b/docs/manuscripts/server-end/database/redis/master-slave.md index f44210a48..4009588bc 100644 --- a/docs/manuscripts/server-end/database/redis/master-slave.md +++ b/docs/manuscripts/server-end/database/redis/master-slave.md @@ -1,2 +1,6 @@ +--- +title: 主从模式 +permalink: /manuscripts/server-end/database/redis/master-slave.html +--- # 主从模式 \ No newline at end of file diff --git a/docs/manuscripts/server-end/database/redis/sentinel.md b/docs/manuscripts/server-end/database/redis/sentinel.md index 82a09ee04..a93f915a8 100644 --- a/docs/manuscripts/server-end/database/redis/sentinel.md +++ b/docs/manuscripts/server-end/database/redis/sentinel.md @@ -1,3 +1,7 @@ +--- +title: 哨兵模式 +permalink: /manuscripts/server-end/database/redis/sentinel.html +--- # 哨兵模式 基本原理 diff --git a/docs/manuscripts/server-end/database/redis/use-rule.md b/docs/manuscripts/server-end/database/redis/use-rule.md index 06b3a2965..d54490431 100644 --- a/docs/manuscripts/server-end/database/redis/use-rule.md +++ b/docs/manuscripts/server-end/database/redis/use-rule.md @@ -1,7 +1,12 @@ +--- +title: 数据库 +permalink: /manuscripts/server-end/database/redis/use-rule.html +--- + # Redis-Key命名规范 -### 建议风格一致 +## 建议风格一致 key的书写风格保持一致,常见的变量风格有: - 同时大驼峰,例如:`UserAdmin` - 同时小驼峰,例如:`userAdmin` @@ -9,17 +14,17 @@ key的书写风格保持一致,常见的变量风格有: 做到项目级别的风格统一; **个人建议采用小驼峰** -### 长度约束 +## 长度约束 - key不能太长也不能太短,合理设计 - **键名越长越占资源,太短可读性太差** -### 以冒号分开 +## 以冒号分开 - redis-key 单词与单词之间以,冒号`:`分开 - 市面上的redis可视化工具,冒号`:`比较容易进行下级选择和业务分类 -### 使用命名空间 +## 使用命名空间 **一个项目一个命名空间,项目内业务不同命名空间也不同**。 @@ -40,7 +45,7 @@ key的书写风格保持一致,常见的变量风格有: 以上是单机模式,当在redis集群中使用相关业务时候,常常需要将部分业务散列到相同的slot中,这时hash tag的作用就显得 非常重要; **做法是在key中添加`{}`前缀`prefix`** -### key分发模型 +## key分发模型 key空间分为16384个槽,有效地设置了16384个主节点的簇大小的上限(但建议的最大节点大小约为1000个节点)。 集群中的每个主节点处理16384个散列槽的子集。 @@ -68,7 +73,7 @@ HASH_SLOT = CRC16(key) mod 16384 在实际测试中,CRC16在16384个插槽中均匀分配不同类型的key时表现非常出色。 -### 键哈希标签 +## 键哈希标签 计算用于实现散列标记的散列槽有一个例外。 散列标记是一种确保在同一散列槽中分配多个key的方法。这用于在Redis集群中实现多键操作。 为了实现散列标签,在某些条件下以稍微不同的方式计算key的散列槽。 @@ -97,7 +102,7 @@ HASH_SLOT = CRC16(key) mod 16384 -### 参考资料: +## 参考资料 - diff --git a/docs/manuscripts/server-end/es6/readme.md b/docs/manuscripts/server-end/es6/readme.md index c813a144e..87ce452de 100644 --- a/docs/manuscripts/server-end/es6/readme.md +++ b/docs/manuscripts/server-end/es6/readme.md @@ -1,3 +1,9 @@ - # ES6 + + + +## 参考资料 + +- + diff --git a/docs/manuscripts/server-end/framework/egg/egg.sidebar.ts b/docs/manuscripts/server-end/framework/egg/egg.sidebar.ts index 277d33154..29cf5b9b0 100644 --- a/docs/manuscripts/server-end/framework/egg/egg.sidebar.ts +++ b/docs/manuscripts/server-end/framework/egg/egg.sidebar.ts @@ -1,84 +1,86 @@ export const eggSidebar = [ { - text: '简介', - link: 'egg/简介.md' - }, - { - text: '快速入门', - link: 'egg/快速入门.md' - }, - { - text: '目录结构', - link: 'egg/目录结构.md' - }, - { - text: '框架对象', - link: 'egg/框架对象.md' - }, - { - text: '配置和运行环境', - link: 'egg/配置和运行环境.md' - }, - { - text: '中间件', - link: 'egg/中间件.md' - }, - { - text: '路由的使用', - link: 'egg/路由的使用.md' - }, - { - text: '控制器和服务', - link: 'egg/控制器和服务.md' - }, - { - text: '定时任务', - link: 'egg/定时任务.md' - }, - { - text: '框架拓展', - link: 'egg/框架拓展.md' - }, - { - text: '插件使用', + text: '使用教程', + prefix: 'tutorial', children: [ { - text: 'egg-mysql', - link: 'egg/egg-mysql.md' + text: '简介', + link: '简介.md' }, { - text: 'egg-sequelize', - link: 'egg/egg-sequelize.md' + text: '快速入门', + link: '快速入门.md' }, { - text: 'egg-redis', - link: 'egg/egg-redis.md' + text: '目录结构', + link: '目录结构.md' }, { - text: 'egg-validate', - link: 'egg/egg-validate.md' + text: '框架对象', + link: '框架对象.md' + }, + { + text: '配置和运行环境', + link: '配置和运行环境.md' + }, + { + text: '中间件', + link: '中间件.md' + }, + { + text: '路由的使用', + link: '路由的使用.md' + }, + { + text: '控制器和服务', + link: '控制器和服务.md' + }, + { + text: '定时任务', + link: '定时任务.md' + }, + { + text: '框架拓展', + link: '框架拓展.md' } ] }, { - text: '最佳实践', + text: '插件使用', + prefix: 'plugin', children: [ { - text: '常用配置', - link: 'egg/常用配置.md' + text: 'egg-mysql', + link: 'egg-mysql.md' + }, + { + text: 'egg-redis', + link: 'egg-redis.md' + }, + { + text: 'egg-validate', + link: 'egg-validate.md' + }, + { + text: 'egg-sequelize', + link: 'egg-sequelize.md' }, { text: 'egg-sequelize-plus', - link: 'egg/egg-mysql.md' + link: 'egg-sequelize-plus.md' }, { text: 'egg-grpc-client', - link: 'egg/egg-grpc-client.md' + link: 'egg-grpc-client.md' }, { text: 'egg-grpc-server', - link: 'egg/egg-grpc-server.md' + link: 'egg-grpc-server.md' } ] + }, + { + text: '最佳实践', + link: '最佳实践.md' } ] diff --git a/docs/manuscripts/server-end/framework/egg/egg-mysql.md b/docs/manuscripts/server-end/framework/egg/plugin/egg-mysql.md similarity index 97% rename from docs/manuscripts/server-end/framework/egg/egg-mysql.md rename to docs/manuscripts/server-end/framework/egg/plugin/egg-mysql.md index b66f0f25f..9f4ab132e 100644 --- a/docs/manuscripts/server-end/framework/egg/egg-mysql.md +++ b/docs/manuscripts/server-end/framework/egg/plugin/egg-mysql.md @@ -31,7 +31,7 @@ npm install egg-mysql --save - 修改config.js: 配置数据库连接相关参数; -```javascript +```js // config/plugin.js // 开启egg-mysql插件 exports.mysql = { @@ -45,7 +45,7 @@ exports.mysql = { #### 连接单个数据库 -```javascript +```js // config/xxxx.js exports.mysql = { client: { @@ -70,7 +70,7 @@ exports.mysql = { **获取mysql对象:** -```javascript +```js // 通过app对象获取mysql单个默认实例【前提:mysql加载到app对象中,即配置中app:true】 // 执行自定义sql语句 @@ -82,7 +82,7 @@ app.mysql.query(sql, values); #### 连接多个数据库 -```javascript +```js // config/xxxx.js exports.mysql = { @@ -116,7 +116,7 @@ exports.mysql = { **获取mysql对象:** -```javascript +```js // 获取mysql_salve_01 实例 const mysqlSlaveClient01 = app.mysql.get('mysql_slave_01'); // 执行自定义sql语句 @@ -157,7 +157,7 @@ mysqlSlaveClient02.query(sql, values); #### Transaction -```javascript +```js // 创建事务对象 const tran = await db.beginTransaction(); @@ -187,7 +187,7 @@ try { 这用到的是beginTransactionScope(scope)分布式事务,自动提交、自动回滚操作; -```javascript +```js const result = await db.beginTransactionScope(function* (conn){ // 不需要手动进行事务的提交和回滚 @@ -203,7 +203,7 @@ const result = await db.beginTransactionScope(function* (conn){ **在koa框架中使用Transaction事务,但需要确保在koa的context上下文对象中,仅仅存在一个活跃的transaction对象;** -```javascript +```js /** * @description @@ -291,7 +291,7 @@ async function* bar(ctx, data) { ### 添加(insert) -```javascript +```js // user表中插入单条数据, const result =await app.mysql.insert('user', { name: 'tom' }); @@ -301,7 +301,7 @@ const insertSuccess = result.affectedRows === 1; ``` 从上面可以看到,利用的`insert`的操作,当然也是能够支持多条数据同时添加的 -```javascript +```js const users=[{name:'dog'},{name:'cat'}] const result=await app.mysql.insert('user',users) @@ -312,7 +312,7 @@ const result=await app.mysql.insert('user',users) **注意ES6中讲yield换为await操作,避免异步无法拿到返回结果** -```javascript +```js // 插入单条数据 @@ -379,7 +379,7 @@ console.log(result); #### 获取一行 -```javascript +```js // 根据查询对象,类似sequelize的findOne() let row = await db.get('user', { name: 'tom' }); @@ -390,7 +390,7 @@ let row = await db.get('user', { name: 'tom' }); #### 获取多行 -```javascript +```js // 查询user表中所有数据 @@ -417,7 +417,7 @@ let rows = await db.select('user', { ### 删除(delete) -```javascript +```js // 根据查询条件删除 let result = await db.delete('user', { @@ -436,7 +436,7 @@ let result = await db.delete('user', { #### 根据主键更新一行数据 -```javascript +```js // 单行数据 let row = { id: 123, @@ -465,7 +465,7 @@ let result = await db.update('user', row); #### 根据特定条件和指定字段列更新一行数据 -```javascript +```js // 单行数据 let row = { name: 'tom', @@ -497,7 +497,7 @@ let result = await db.update('user', row, { **这里主要注意:使用的是updateRows()函数** -```javascript +```js // 多行数据 let options = [{ id: 123, @@ -534,7 +534,7 @@ let result = await db.updateRows('user', options); **这里主要注意:使用的是updateRows()函数** -```javascript +```js let options = [{ row: { email: 'tom@161.com', @@ -583,7 +583,7 @@ let result = yield db.updateRows('user', options); 像MySQL等数据库中count()函数计数,统计数据量条数使用是非常频繁的,当然这里也是支持按照条件统计计数 -```javascript +```js // 查询数量 const count = await db.count('user', { @@ -596,7 +596,7 @@ const count = await db.count('user', { ### 自定义SQL拼接 -```javascript +```js // 将数组中的数据与 ? 进行匹配替换 const results = await app.mysql.query('update posts set hits = (hits + ?) where id = ?', [1, postId]); ``` @@ -606,7 +606,7 @@ const results = await app.mysql.query('update posts set hits = (hits + ?) where #### 内置表达式 **NOW(): 数据库当前系统时间,通过app.mysql.literals.now获取**。 -```javascript +```js await app.mysql.insert(table, { create_time: app.mysql.literals.now }); @@ -621,7 +621,7 @@ await app.mysql.insert(table, { 调用mysql内置的CONCAT(s1, ...sn)函数,做字符串拼接。 -```javascript +```js const Literal = app.mysql.literals.Literal; const first_name = 'lisa'; const last_name = 'marry'; diff --git a/docs/manuscripts/server-end/framework/egg/egg-redis.md b/docs/manuscripts/server-end/framework/egg/plugin/egg-redis.md similarity index 100% rename from docs/manuscripts/server-end/framework/egg/egg-redis.md rename to docs/manuscripts/server-end/framework/egg/plugin/egg-redis.md diff --git a/docs/manuscripts/server-end/framework/egg/egg-sequelize.md b/docs/manuscripts/server-end/framework/egg/plugin/egg-sequelize.md similarity index 98% rename from docs/manuscripts/server-end/framework/egg/egg-sequelize.md rename to docs/manuscripts/server-end/framework/egg/plugin/egg-sequelize.md index e50f7101e..d025c51c2 100644 --- a/docs/manuscripts/server-end/framework/egg/egg-sequelize.md +++ b/docs/manuscripts/server-end/framework/egg/plugin/egg-sequelize.md @@ -35,7 +35,7 @@ npm install tedious --save ### 开启插件 -```javascript +```js // config/plugin.js exports.sequelize={ @@ -49,7 +49,7 @@ exports.sequelize={ 众所周知,mysql数据库的连接需要进行用户名/密码/端口/主机等相关配置,在`egg-sequelize`插件中也不例外 -```javascript +```js // config/config.{env}.js exports.sequelize = { @@ -121,7 +121,7 @@ model文件 | 加载后类名 | 定义model文件 -```javascript +```js // app/model/user.js module.exports = app => { @@ -166,7 +166,7 @@ module.exports = app => { 现在可以在`controller`层中,使用封装的方法来操作数据库了 -```javascript +```js // app/controller/user.js class UserController extends Controller { async index() { @@ -195,7 +195,7 @@ class UserController extends Controller { `egg-sequelize`支持加载多个独立的数据库配置,连接多个数据库数据源。可以使用`config.sequelize.datasources`来配置和加载多个数据源 -```javascript +```js // config/config.default.js exports.sequelize = { datasources: [ @@ -220,7 +220,7 @@ exports.sequelize = { 按照上面的示例,配置多数据源后,model可以像下面一样定义: -```javascript +```js // app/model/user.js 【对应model】 module.exports = app => { @@ -260,7 +260,7 @@ module.exports = app => { 如果按照上面的配置,对不同的数据源定义了相同的model,相同的model文件将会在不同的数据库中执行多次,因此可以使用第二个参数去获取`sequelize`实例对象。 -```javascript +```js // app/model/user.js @@ -288,7 +288,7 @@ module.exports = (app, model) => { 默认情况下,`egg-sequelize`将会使用`sequelize@5`,也就是V5版本.可以通过配置`config.sequelize.Sequelize`来自定义`sequelize`的对象版本。 -```javascript +```js // config/config.default.js exports.sequelize = { @@ -302,7 +302,7 @@ exports.sequelize = { ### 完整的示例 -```javascript +```js // app/model/post.js module.exports = app => { const { STRING, INTEGER, DATE } = app.Sequelize; @@ -325,7 +325,7 @@ module.exports = app => { 在`controller`层中使用model,来操作数据库 -```javascript +```js // app/controller/post.js class PostController extends Controller { async index() { diff --git a/docs/manuscripts/server-end/framework/egg/egg-validate.md b/docs/manuscripts/server-end/framework/egg/plugin/egg-validate.md similarity index 100% rename from docs/manuscripts/server-end/framework/egg/egg-validate.md rename to docs/manuscripts/server-end/framework/egg/plugin/egg-validate.md diff --git a/docs/manuscripts/server-end/framework/egg/readme.md b/docs/manuscripts/server-end/framework/egg/readme.md index 1bd50e8aa..78dbcb3dd 100644 --- a/docs/manuscripts/server-end/framework/egg/readme.md +++ b/docs/manuscripts/server-end/framework/egg/readme.md @@ -1,3 +1 @@ - - -## Egg框架 \ No newline at end of file +# Egg框架 \ No newline at end of file diff --git "a/docs/manuscripts/server-end/framework/egg/\344\270\255\351\227\264\344\273\266.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\344\270\255\351\227\264\344\273\266.md" similarity index 98% rename from "docs/manuscripts/server-end/framework/egg/\344\270\255\351\227\264\344\273\266.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\344\270\255\351\227\264\344\273\266.md" index 10ea119b7..9ccd7401c 100644 --- "a/docs/manuscripts/server-end/framework/egg/\344\270\255\351\227\264\344\273\266.md" +++ "b/docs/manuscripts/server-end/framework/egg/tutorial/\344\270\255\351\227\264\344\273\266.md" @@ -12,7 +12,7 @@ Egg 是基于 Koa 实现的,所以 Egg 的中间件形式和 Koa 的中间件 编写一个简单的 gzip 中间件 -```javascript +```js // app/middleware/gzip.js const isJSON = require('koa-is-json'); const zlib = require('zlib'); @@ -51,7 +51,7 @@ module.export=gzip; gzip 中间件做一个简单的优化,让它支持指定只有当 body 大于配置的 threshold 时才进行 gzip 压缩,要在 app/middleware 目录下新建一个文件 gzip.js -```javascript +```js // app/middleware/gzip.js const isJSON = require('koa-is-json'); const zlib = require('zlib'); @@ -96,7 +96,7 @@ module.exports = options => { 在 config.default.js 中加入下面的配置就完成了中间件的开启和配置: -```javascript +```js module.exports = { // 配置需要的中间件,数组顺序即为中间件的加载顺序 @@ -117,7 +117,7 @@ module.exports = { 框架和插件不支持在 `config.default.js `中匹配 `middleware`,需要通过以下方式: -```javascript +```js // app.js module.exports = app => { // 在中间件最前面统计请求时间【unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度】 @@ -142,7 +142,7 @@ module.exports = () => { 在框架、应用、插件中使用中间件,作用范围都是全局的,会处理每一次请求。即便是有ignore、match的配置,但是在处理只针对单个路由生效的问题时,就需要直接在router上使用中间件,**可以直接在 app/router.js 中实例化和挂载**。 -```javascript +```js module.exports = app => { // 获取中间件 const gzip = app.middleware.gzip({ threshold: 1024 }); @@ -159,7 +159,7 @@ module.exports = app => { 例如:框架自带的中间件中有一个 bodyParser 中间件(框**架的加载器会将文件名中的各种分隔符都修改成驼峰形式的变量名**),想要修改 bodyParser 的配置,只需要在 `config/config.default.js` 中编写 -```javascript +```js module.exports = { bodyParser: { // 限制json大小 @@ -178,7 +178,7 @@ module.exports = { 以 `koa-compress` 为例,在 Koa 中使用时: -```javascript +```js const koa = require('koa'); const compress = require('koa-compress'); @@ -191,7 +191,7 @@ app.use(compress(options)); 按照框架的规范来在应用中加载这个 Koa 的中间件: -```javascript +```js // app/middleware/compress.js // koa-compress 暴露的接口(`(options) => middleware`)和框架对中间件要求一致 module.exports = require('koa-compress'); @@ -209,7 +209,7 @@ module.exports = { 如果使用到的 Koa 中间件不符合入参规范,则可以自行处理: -```javascript +```js // config/config.default.js module.exports = { webpack: { @@ -240,7 +240,7 @@ module.exports = (options, app) => { 如果应用并不需要默认的 bodyParser 中间件来进行请求体的解析,此时可以通过配置 enable 为 false 来关闭它 -```javascript +```js module.exports = { bodyParser: { // 关闭 @@ -257,7 +257,7 @@ module.exports = { 如果想让 gzip 只针对 /static 前缀开头的 url 请求开启,我们可以配置 match 选项 -```javascript +```js module.exports = { gzip: { match: '/static', @@ -273,7 +273,7 @@ match 和 ignore 支持多种类型的配置方式 - 函数:当参数为一个函数时,会将请求上下文传递给这个函数,最终取函数返回的结果(`true/false`)来判断是否匹配。 -```javascript +```js // module.exports = { gzip: { diff --git "a/docs/manuscripts/server-end/framework/egg/\345\256\232\346\227\266\344\273\273\345\212\241.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\345\256\232\346\227\266\344\273\273\345\212\241.md" similarity index 98% rename from "docs/manuscripts/server-end/framework/egg/\345\256\232\346\227\266\344\273\273\345\212\241.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\345\256\232\346\227\266\344\273\273\345\212\241.md" index c160dfaf6..1dee3313b 100644 --- "a/docs/manuscripts/server-end/framework/egg/\345\256\232\346\227\266\344\273\273\345\212\241.md" +++ "b/docs/manuscripts/server-end/framework/egg/tutorial/\345\256\232\346\227\266\344\273\273\345\212\241.md" @@ -17,7 +17,7 @@ title: 定时任务 例如:定义一个更新远程数据到内存缓存的定时任务,就可以在 app/schedule 目录下创建一个 update_cache.js 文件 -```javascript +```js const Subscription = require('egg').Subscription; @@ -44,7 +44,7 @@ module.exports = UpdateCache; 可以简写: -```javascript +```js module.exports = { schedule: { interval: '1m', // 1 分钟间隔 @@ -78,7 +78,7 @@ module.exports = { - 数字类型,单位为毫秒数,例如 5000。 - 字符类型,会通过 ms 转换成毫秒数,例如 5s。 -```javascript +```js module.exports = { schedule: { // 每 10 秒执行一次,转化为10000ms @@ -111,7 +111,7 @@ module.exports = { cron表达式解析: -```javascript +```js module.exports = { schedule: { @@ -144,7 +144,7 @@ module.exports = { 执行日志会输出到 `${appInfo.root}/logs/{app_name}/egg-schedule.log`,默认不会输出到控制台,可以通过 `config.customLogger.scheduleLogger` 来自定义。 -```javascript +```js // config/config.default.js config.customLogger = { scheduleLogger: { @@ -160,7 +160,7 @@ config.customLogger = { 有时候需要配置定时任务的参数。定时任务还有支持另一种写法: -```javascript +```js // 直接返回对象 module.exports = app => { return { @@ -186,7 +186,7 @@ module.exports = app => { - 通过手动执行定时任务可以更优雅的编写对定时任务的单元测试。 -```javascript +```js const mm = require('egg-mock'); const assert = require('assert'); @@ -200,7 +200,7 @@ it('should schedule work fine', async () => { - 应用启动时,手动执行定时任务进行系统初始化,等初始化完毕后再启动应用。参见应用启动自定义章节,我们可以在 app.js 中编写初始化逻辑。 -```javascript +```js module.exports = app => { app.beforeStart(async () => { // 保证应用启动监听端口前数据已经准备好了 @@ -218,7 +218,7 @@ module.exports = app => { 在 `agent.js` 中继承 `agent.ScheduleStrategy`,然后通过 `agent.schedule.use()` 注册即可 -```javascript +```js module.exports = agent => { class ClusterStrategy extends agent.ScheduleStrategy { start() { diff --git "a/docs/manuscripts/server-end/framework/egg/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\345\277\253\351\200\237\345\205\245\351\227\250.md" similarity index 98% rename from "docs/manuscripts/server-end/framework/egg/\345\277\253\351\200\237\345\205\245\351\227\250.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\345\277\253\351\200\237\345\205\245\351\227\250.md" index 157c91f01..d4ace63f6 100644 --- "a/docs/manuscripts/server-end/framework/egg/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/manuscripts/server-end/framework/egg/tutorial/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -112,7 +112,7 @@ module.export=HomeController #### 配置路由 -```javascript +```js // app/router.js module.exports = app => { const { router, controller } = app; @@ -182,7 +182,7 @@ $ npm i egg-view-nunjucks --save 模块安装完成后,需要修改插件的参数配置来开启插件 -```javascript +```js // config/plugin.js 注意:是 config 目录,不是 app/config! exports.nunjucks = { @@ -225,7 +225,7 @@ exports.view = { 添加Controller层的页面访问逻辑 -```javascript +```js // app/controller/news.js const Controller = require('egg').Controller; @@ -247,7 +247,7 @@ module.exports = NewsController; 添加Router层中,路由的相关配置 -```javascript +```js // app/router.js module.exports = app => { const { router, controller } = app; @@ -266,7 +266,7 @@ module.exports = app => { 添加一个 Service 抓取 Hacker News 的数据 ,如下: -```javascript +```js // app/service/news.js const Service = require('egg').Service; @@ -303,7 +303,7 @@ module.exports = NewsService; 然后稍微修改下之前的 Controller: -```javascript +```js // app/controller/news.js const Controller = require('egg').Controller; @@ -321,7 +321,7 @@ module.exports = NewsController; 还需增加 app/service/news.js 中读取到的配置: -```javascript +```js // config/config.default.js // 添加 news 的配置项 exports.news = { @@ -342,7 +342,7 @@ exports.news = { ## 安装依赖 $ npm i moment --save ``` -```javascript +```js // app/extend/helper.js const moment = require('moment'); exports.relativeTime = time => moment(new Date(time * 1000)).fromNow(); @@ -362,7 +362,7 @@ exports.relativeTime = time => moment(new Date(time * 1000)).fromNow(); 聪明的朋友们一定很快能想到可以通过 Middleware 判断 User-Agent,如下: -```javascript +```js // app/middleware/robot.js // options === app.config.robot module.exports = (options, app) => { @@ -382,7 +382,7 @@ module.exports = (options, app) => { ``` 修改配置文件: -```javascript +```js // config/config.default.js // add middleware robot exports.middleware = [ @@ -407,7 +407,7 @@ exports.robot = { 应用/插件/框架都可以配置自己的配置文件,框架将按顺序合并加载。 具体合并逻辑可参见配置文件。 -```javascript +```js // config/config.default.js exports.robot = { ua: [ @@ -418,7 +418,7 @@ exports.robot = { ``` -```javascript +```js // config/config.local.js // only read at development mode, will override default @@ -430,7 +430,7 @@ exports.robot = { ``` -```javascript +```js // app/service/some.js const Service = require('egg').Service; @@ -449,7 +449,7 @@ module.exports = SomeService; 测试文件应该放在项目根目录下的 test 目录下,并以 test.js 为后缀名,即 {app_root}/test/**/*.test.js。 -```javascript +```js // test/app/middleware/robot.test.js const { app, mock, assert } = require('egg-mock/bootstrap'); diff --git "a/docs/manuscripts/server-end/framework/egg/\346\216\247\345\210\266\345\231\250\345\222\214\346\234\215\345\212\241.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\346\216\247\345\210\266\345\231\250\345\222\214\346\234\215\345\212\241.md" similarity index 98% rename from "docs/manuscripts/server-end/framework/egg/\346\216\247\345\210\266\345\231\250\345\222\214\346\234\215\345\212\241.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\346\216\247\345\210\266\345\231\250\345\222\214\346\234\215\345\212\241.md" index 4c6be8f80..13f9f1071 100644 --- "a/docs/manuscripts/server-end/framework/egg/\346\216\247\345\210\266\345\231\250\345\222\214\346\234\215\345\212\241.md" +++ "b/docs/manuscripts/server-end/framework/egg/tutorial/\346\216\247\345\210\266\345\231\250\345\222\214\346\234\215\345\212\241.md" @@ -30,7 +30,7 @@ Egg框架的router将用户的请求基于 method 和 URL 分发到了对应的 ##### Controller 类(推荐) -```javascript +```js // 继承基类,编写自定义Controller逻辑 // app/controller/post.js const Controller = require('egg').Controller; @@ -59,7 +59,7 @@ module.exports = PostController; 定义了一个 `PostController` 的类,类里面的每一个方法都可以作为一个 `Controller` 在 `Router` 中引用到,我们可以从 `app.controller` 根据文件名和方法名定位到它 -```javascript +```js // app/router.js module.exports = app => { const { router, controller } = app; @@ -69,7 +69,7 @@ module.exports = app => { `Controller` 支持多级目录,例如如果将上面的 `Controller` 代码放到 `app/controller/sub/post.js` 中,则可以在 `router` 中这样使用: -```javascript +```js // app/router.js module.exports = app => { app.router.post('createPost', '/api/posts', app.controller.sub.post.create); @@ -92,7 +92,7 @@ module.exports = app => { 按照类的方式编写 Controller,不仅可以更好的对 Controller 层代码进行抽象(例如将一些统一的处理抽象成一些私有方法),还可以通过自定义 Controller 基类的方式封装应用中常用的方法。 -```javascript +```js // app/core/base_controller.js const { Controller } = require('egg'); class BaseController extends Controller { @@ -118,7 +118,7 @@ module.exports = BaseController; 此时在编写应用的 Controller 时,可以继承 BaseController,直接使用基类上的方法: -```javascript +```js //app/controller/post.js const Controller = require('../core/base_controller'); class PostController extends Controller { @@ -134,7 +134,7 @@ class PostController extends Controller { 每一个 `Controller` 都是一个 `async function`,它的入参为请求的上下文 `Context` 对象的实例,通过它可以拿到框架封装好的各种便捷属性和方法。 -```javascript +```js // app/controller/post.js exports.create = async ctx => { const createRule = { @@ -166,7 +166,7 @@ exports.create = async ctx => { 在 URL 中 `?` 后面的部分是一个 `Query String`,这一部分经常用于 `GET` 类型的请求中传递参数。例如: `GET /posts?category=egg&language=node` 中 `category=egg&language=node` 就是用户传递过来的参数。我们可以通过 `ctx.query` 拿到解析过后的这个参数体 -```javascript +```js class PostController extends Controller { async listPosts() { @@ -182,7 +182,7 @@ class PostController extends Controller { **当 `Query String` 中的 `key` 重复时,`ctx.query` 只取 key 第一次出现时的值,后面再出现的都会被忽略**。`GET /posts?category=egg&category=koa` 通过 `ctx.query` 拿到的值是: -```javascript +```js { // 第一次出现 category:'egg' @@ -191,7 +191,7 @@ class PostController extends Controller { 这样处理的原因是为了保持统一性,由于通常情况下我们都不会设计让用户传递 `key` 相同的 `Query String`,所以经常会写类似下面的代码: -```javascript +```js const key = ctx.query.key || ''; if (key.startsWith('egg')) { @@ -207,7 +207,7 @@ if (key.startsWith('egg')) { 有时候系统会设计成让用户传递相同的 `key`,例如 `GET /posts?category=egg&id=1&id=2&id=3`。针对此类情况,**框架提供了 `ctx.queries` 对象,这个对象也解析了 `Query String`,但是它不会丢弃任何一个重复的数据,而是将他们都放到一个数组中**: -```javascript +```js // GET /posts?category=egg&id=1&id=2&id=3 class PostController extends Controller { async listPosts() { @@ -227,7 +227,7 @@ class PostController extends Controller { `Router` 上也可以申明参数,这些参数都可以通过 `ctx.params` 获取到。 - ```javascript + ```js // app.get('/projects/:projectId/app/:appId', 'app.listApp'); // GET /projects/1/app/2 class AppController extends Controller { @@ -252,7 +252,7 @@ class AppController extends Controller { 框架内置了 `bodyParser` 中间件来对这两类格式的请求 `body` 解析成 `object` 挂载到 `ctx.request.body` 上。**`HTTP` 协议中并不建议在通过 `GET`、`HEAD` 方法访问时传递 `body`**,所以无法在 `GET`、`HEAD` 方法中按照此方法获取到内容。 -```javascript +```js // POST /api/posts HTTP/1.1 // Host: localhost:3000 @@ -279,7 +279,7 @@ class PostController extends Controller { 可以在 `config/config.default.js` 中覆盖框架的默认值,变更解析时允许的最大长度: -```javascript +```js module.exports = { bodyParser: { @@ -313,7 +313,7 @@ module.exports = { - 在 config 文件中启用 file 模式: -```javascript +```js // config/config.default.js exports.multipart = { mode: 'file', @@ -337,7 +337,7 @@ exports.multipart = { 后端接收代码示例: -```javascript +```js // app/controller/upload.js const Controller = require('egg').Controller; const fs = require('mz/fs'); @@ -379,7 +379,7 @@ module.exports = class extends Controller { 后端多文件上传逻辑: -```javascript +```js // app/controller/upload.js const Controller = require('egg').Controller; const fs = require('mz/fs'); @@ -429,7 +429,7 @@ module.exports = class extends Controller { 后端示例: -```javascript +```js const path = require('path'); const sendToWormhole = require('stream-wormhole'); const Controller = require('egg').Controller; @@ -470,7 +470,7 @@ module.exports = UploaderController; 如果要获取同时上传的多个文件,不能通过 `ctx.getFileStream()` 来获取,只能通过下面这种方式 -```javascript +```js const sendToWormhole = require('stream-wormhole'); const Controller = require('egg').Controller; @@ -521,7 +521,7 @@ module.exports = UploaderController; **为了保证文件上传的安全,框架限制了支持的的文件格式,框架默认支持白名单** -```javascript +```js // images '.jpg', '.jpeg', // image/jpeg '.png', // image/png, image/x-png @@ -552,7 +552,7 @@ module.exports = UploaderController; - 新增支持的文件扩展名 -```javascript +```js // config/config.default.js module.exports = { multipart: { @@ -565,7 +565,7 @@ module.exports = { - 覆盖整个白名单 -```javascript +```js module.exports = { multipart: { // 覆盖整个白名单,只允许上传 '.png' 格式 @@ -622,7 +622,7 @@ HTTP 请求都是无状态的,但是 `Web` 应用通常都需要知道发起 通过 `ctx.cookies`,可以在 `Controller` 中便捷、安全的设置和读取 `Cookie`。 -```javascript +```js class CookieController extends Controller { // 添加cookies @@ -655,7 +655,7 @@ class CookieController extends Controller { **框架内置了 Session 插件,提供了 ctx.session 来访问或者修改当前用户 Session 。** -```javascript +```js class PostController extends Controller { async fetchPosts() { @@ -676,7 +676,7 @@ class PostController extends Controller { Session 的使用方法非常直观,直接读取它或者修改它就可以了,**如果要删除它,直接将它赋值为 null**: -```javascript +```js class SessionController extends Controller { async deleteSession() { this.ctx.session = null; @@ -689,7 +689,7 @@ class SessionController extends Controller { 在获取到用户请求的参数后,不可避免的要对参数进行一些校验。借助 `egg-validate` 插件提供便捷的参数校验机制,帮助完成各种复杂的参数校验。 -```javascript +```js // config/plugin.js exports.validate = { enable: true, @@ -699,7 +699,7 @@ exports.validate = { 通过 `ctx.validate(rule, [body])` 直接对参数进行校验, **如果不传第二个参数会自动校验 `ctx.request.body`** -```javascript +```js class PostController extends Controller { async create() { // 校验参数 @@ -715,7 +715,7 @@ class PostController extends Controller { 当校验异常时,会直接抛出一个异常,异常的状态码为 422,errors 字段包含了详细的验证不通过信息。**如果想要自己处理检查的异常,可以通过 try catch 来自行捕获。** -```javascript +```js class PostController extends Controller { async create() { const ctx = this.ctx; @@ -735,7 +735,7 @@ class PostController extends Controller { 有时候希望自定义一些校验规则,让开发时更便捷,此时可以通过 `app.validator.addRule(type, check)` 的方式新增自定义规则。 -```javascript +```js // app.js app.validator.addRule('json', (rule, value) => { try { @@ -748,7 +748,7 @@ app.validator.addRule('json', (rule, value) => { 添加完自定义规则之后,就可以在 Controller 中直接使用这条规则来进行参数校验了 -```javascript +```js class PostController extends Controller { async handler() { const {ctx}=this; @@ -767,7 +767,7 @@ class PostController extends Controller { **在 `Controller` 中可以调用任何一个 `Service` 上的任何方法,同时 `Service` 是懒加载的,只有当访问到它的时候框架才会去实例化它。** -```javascript +```js class PostController extends Controller { async create() { @@ -793,7 +793,7 @@ class PostController extends Controller { HTTP 设计了非常多的状态码,每一个状态码都代表了一个特定的含义,通过设置正确的状态码,可以让响应更符合语义。 -```javascript +```js // 框架提供了一个便捷的 Setter 来进行状态码的设置 class PostController extends Controller { async create() { @@ -814,7 +814,7 @@ class PostController extends Controller { **注意:`ctx.body` 是 `ctx.response.body` 的简写,和`ctx.request.body`不一致;** -```javascript +```js class ViewController extends Controller { async show() { const {ctx}=this; @@ -836,7 +836,7 @@ class ViewController extends Controller { 由于 Node.js 的流式特性,还有很多场景需要通过 Stream 返回响应,例如返回一个大文件,代理服务器直接返回上游的内容,框架也支持直接将 body 设置成一个 Stream,并会同时处理好这个 Stream 上的错误事件。 -```javascript +```js class ProxyController extends Controller { async proxy() { const {ctx}=this; @@ -855,7 +855,7 @@ class ProxyController extends Controller { 通常来说,不会去手写 `HTML` 页面,而是会通过模板引擎进行生成。 框架自身没有集成任何一个模板引擎,但是约定了 `View` 插件的规范,通过接入的模板引擎,可以直接使用 `ctx.render(template)` 来渲染模板生成 `html`。 -```javascript +```js class HomeController extends Controller { async index() { const ctx = this.ctx; @@ -875,7 +875,7 @@ class HomeController extends Controller { - 通过 `app.jsonp()` 提供的中间件来让一个 `controller` 支持响应 `JSONP` 格式的数据。在路由中,我们给需要支持 `jsonp` 的路由加上这个中间件: -```javascript +```js // app/router.js module.exports = app => { const jsonp = app.jsonp(); @@ -887,7 +887,7 @@ module.exports = app => { - 在 Controller 中,只需要正常编写即可: -```javascript +```js // app/controller/posts.js class PostController extends Controller { async show() { @@ -908,7 +908,7 @@ class PostController extends Controller { 框架默认通过 `query` 中的 `_callback` 参数作为识别是否返回 JSONP 格式数据的依据,并且 `_callback` 中设置的方法名长度最多只允许 50 个字符。应用可以在 `config/config.default.js` 全局覆盖默认的配置: -```javascript +```js // config/config.default.js exports.jsonp = { callback: 'callback', // 识别 query 中的 `callback` 参数 @@ -921,7 +921,7 @@ exports.jsonp = { 同样可以在 `app.jsonp()` 创建中间件时覆盖默认的配置,以达到不同路由使用不同配置的目的: -```javascript +```js // app/router.js module.exports = app => { const { router, controller, jsonp } = app; @@ -946,7 +946,7 @@ module.exports = app => { 在 `JSONP` 配置中,我们只需要打开 `csrf: true`,即可对 `JSONP` 接口开启 `CSRF` 校验。 -```javascript +```js // config/config.default.js module.exports = { jsonp: { @@ -965,7 +965,7 @@ module.exports = { 如果在同一个主域之下,可以通过开启 CSRF 的方式来校验 JSONP 请求的来源,**如果想对其他域名的网页提供 JSONP 服务,我们可以通过配置 referrer 白名单的方式来限制 JSONP 的请求方在可控范围之内。** -```javascript +```js //config/config.default.js exports.jsonp = { whiteList: /^https?:\/\/test.com\//, @@ -980,7 +980,7 @@ exports.jsonp = { - 正则表达式:此时只有请求的 `Referrer` 匹配该正则时才允许访问 JSONP 接口。在设置正则表达式的时候,**注意开头的 ^ 以及结尾的 \/,保证匹配到完整的域名。** -```javascript +```js exports.jsonp = { whiteList: /^https?:\/\/test.com\//, }; @@ -992,7 +992,7 @@ exports.jsonp = { - 字符串:设置字符串形式的白名单时分为两种,当字符串以 . 开头,例如 `.test.com` 时,代表 `referrer` 白名单为 `test.com` 的所有子域名,包括 `test.com` 自身。当字符串不以 . 开头,例如 `sub.test.com`,代表 `referrer` 白名单为 `sub.test.com` 这一个域名。(**同时支持 `HTTP` 和 `HTTPS`**)。 -```javascript +```js exports.jsonp = { whiteList: '.test.com', }; @@ -1015,7 +1015,7 @@ exports.jsonp = { - 数组:当设置的白名单为数组时,代表只要**满足数组中任意一个元素的条件**即可通过 `referrer` 校验。 -```javascript +```js exports.jsonp = { whiteList: [ 'sub.test.com', 'sub2.test.com' ], @@ -1034,7 +1034,7 @@ exports.jsonp = { 通过 `ctx.set(key, value)` 方法可以设置一个响应头,`ctx.set(headers)` 设置多个 `Header` -```javascript +```js // app/controller/api.js class ProxyController extends Controller { async show() { @@ -1059,7 +1059,7 @@ class ProxyController extends Controller { 用户如果使用`ctx.redirect`方法,需要在应用的配置文件中做如下配置: -```javascript +```js // config/config.default.js exports.security = { domainWhiteList:['.domain.com'], // 安全白名单,以 . 开头 @@ -1087,7 +1087,7 @@ exports.security = { #### 定义Service -```javascript +```js // app/service/user.js const Service = require('egg').Service; @@ -1125,7 +1125,7 @@ module.exports = UserService; - Service 文件必须放在 app/service 目录,可以支持多级目录,访问的时候可以通过目录名级联访问。 -```javascript +```js // 注意大小写匹配 app/service/biz/user.js => ctx.service.biz.user @@ -1145,7 +1145,7 @@ app/service/HackerNews.js => ctx.service.hackerNews 简单的service服务调用示例: -```javascript +```js // app/router.js module.exports = app => { app.router.get('/user/:id', app.controller.user.info); diff --git "a/docs/manuscripts/server-end/framework/egg/\346\241\206\346\236\266\345\257\271\350\261\241.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\346\241\206\346\236\266\345\257\271\350\261\241.md" similarity index 98% rename from "docs/manuscripts/server-end/framework/egg/\346\241\206\346\236\266\345\257\271\350\261\241.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\346\241\206\346\236\266\345\257\271\350\261\241.md" index e38638d7b..d326074bf 100644 --- "a/docs/manuscripts/server-end/framework/egg/\346\241\206\346\236\266\345\257\271\350\261\241.md" +++ "b/docs/manuscripts/server-end/framework/egg/tutorial/\346\241\206\346\236\266\345\257\271\350\261\241.md" @@ -33,7 +33,7 @@ Application 是全局应用对象,在一个应用中,只会实例化一个 在项目入口通过app.js来启动项目,在项目运行时,会在Application对象实例上触发一些事件,应用开发者或者插件开发者可以监听这些事件做一些操作,一般在app.js中进行监听 -```javascript +```js // app.js @@ -69,7 +69,7 @@ Application 对象几乎可以在编写应用时的任何一个地方获取到; - 在app.js中使用 -```javascript +```js // app.js module.exports = app => { // 绑定缓存redis @@ -83,7 +83,7 @@ module.exports = app => { - Controller中使用 -```javascript +```js // app/controller/user.js class UserController extends Controller { // 查询用户 @@ -98,7 +98,7 @@ class UserController extends Controller { 和 Koa 一样,在 Context 对象上,可以通过 ctx.app 访问到 Application 对象。上面的UserController文件也可以改为: -```javascript +```js // app/controller/user.js class UserController extends Controller { async getUser() { @@ -112,7 +112,7 @@ class UserController extends Controller { 在继承于 Controller, Service 基类的实例中,可以通过 this.app 访问到 Application 对象。 -```javascript +```js // app/service/user.js class UserService extends Service { async getUser(uid){ @@ -135,7 +135,7 @@ Context 是一个请求级别的对象,继承自 Koa.Context。在每一次收 - 中间件里获取 -```javascript +```js 'use strict' // 中间件 @@ -149,7 +149,7 @@ async function middleware(ctx,next){ - 支持Controller和Service里面获取 -```javascript +```js // 直接在this对象中拿到 const {ctx}=this @@ -159,7 +159,7 @@ const {ctx}=this > 在有些非用户请求的场景下我们需要访问 service / model 等 Context 实例上的对象,可以通过 Application.createAnonymousContext() 方法创建一个匿名 Context 实例 -```javascript +```js // 例如:app.js中 module.exports=app=>{ @@ -178,7 +178,7 @@ module.exports=app=>{ 在定时任务中的每一个 task 都接受一个 Context 实例作为参数,非常方便的帮助执行一些定时的业务逻辑 -```javascript +```js // app/schedule/refresh.js exports.task=async ctx=>{ @@ -200,7 +200,7 @@ exports.task=async ctx=>{ 可以在 Context 的实例上获取到当前请求的 Request(ctx.request) 和 Response(ctx.response) 实例 -```javascript +```js // app/controller/user.js class UserController extends Controller { @@ -245,7 +245,7 @@ class UserController extends Controller { 获取框架提供的Controller对象的方案: - 方案一:从 egg模块上获取(推荐) -```javascript +```js // app/controller/user.js 'use strict' @@ -266,7 +266,7 @@ module.exports = UserController; - 方案二:从 app 实例上获取 -```javascript +```js // app/controller/user.js 'use strict' module.exports = app => { @@ -289,7 +289,7 @@ module.exports = app => { - 方案一:从 egg模块上获取(推荐) -```javascript +```js // app/service/user.js 'use strict' @@ -310,7 +310,7 @@ module.exports = UserService; - 方案二:从 app 实例上获取 -```javascript +```js // app/service/user.js 'use strict' module.exports = app => { @@ -333,7 +333,7 @@ Helper 用来提供一些实用的 utility 函数。作用在于我们可以将 #### 获取方式 可以在 Context 的实例上获取到当前请求的 Helper(ctx.helper) 对象实例 -```javascript +```js // app/controller/user.js class UserController extends Controller { async fetch() { @@ -350,7 +350,7 @@ class UserController extends Controller { 在框架的设计中,helper.js文件的功能主要提供一些常用工具函数的封装,所以除了系统自带的,更多的使用场景是进行方法自定义,比如:常用正则校验、长度检测、排序处理等; -```javascript +```js // app/extend/helper.js module.exports = { // 封装统一返回 @@ -427,7 +427,7 @@ Logger对象根据不同的使用场景,主要有: 简单的定时任务: -```javascript +```js // 引用 Subscription 基类: const Subscription = require('egg').Subscription; diff --git "a/docs/manuscripts/server-end/framework/egg/\346\241\206\346\236\266\346\213\223\345\261\225.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\346\241\206\346\236\266\346\213\223\345\261\225.md" similarity index 97% rename from "docs/manuscripts/server-end/framework/egg/\346\241\206\346\236\266\346\213\223\345\261\225.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\346\241\206\346\236\266\346\213\223\345\261\225.md" index cf91e4c17..e43ce7476 100644 --- "a/docs/manuscripts/server-end/framework/egg/\346\241\206\346\236\266\346\213\223\345\261\225.md" +++ "b/docs/manuscripts/server-end/framework/egg/tutorial/\346\241\206\346\236\266\346\213\223\345\261\225.md" @@ -21,7 +21,7 @@ app 对象指的是 Koa 的全局应用对象,**全局只有一个**,在应 - `ctx.app`、`this.app` - `Controller`,`Middleware`,`Helper`,`Service` 中都可以通过 `this.app` 访问到 `Application` 对象 -```javascript +```js // 一般会通过解构赋值去访问 const {app}=this; @@ -32,7 +32,7 @@ console.log(app.config) - 在 `app.js` 中 `app` 对象会作为第一个参数注入到入口函数中 -```javascript +```js // app.js module.exports = app => { // 使用 app 对象 @@ -48,7 +48,7 @@ module.exports = app => { 我们要增加一个 `app.foo()` 方法: -```javascript +```js // app/extend/application.js module.exports = { foo(param) { @@ -66,7 +66,7 @@ module.exports = { 例如,增加一个 app.bar 属性 Getter: -```javascript +```js // app/extend/application.js const BAR = Symbol('Application#bar'); @@ -103,7 +103,7 @@ module.exports = { ##### 方法扩展 `ctx`对象上扩展一个foo()方法: -```javascript +```js // app/extend/context.js module.exports = { foo(param) { @@ -120,7 +120,7 @@ module.exports = { 推荐的方式是使用 Symbol + Getter 的模式。 增加一个 ctx.bar 属性 Getter: -```javascript +```js // app/extend/context.js const BAR = Symbol('Context#bar'); @@ -143,14 +143,14 @@ module.exports = { #### 访问方式 -```javascript +```js ctx.request // 例如:ctx.request.body ``` **`ctx` 上的很多属性和方法都被代理到 `request` 对象上** -```javascript +```js ctx.header ctx.headers ctx.method @@ -192,7 +192,7 @@ ctx.get() 例如:增加一个 request.foo 属性 Getter -```javascript +```js // app/extend/request.js module.exports = { get foo() { @@ -209,14 +209,14 @@ module.exports = { #### 访问方式 -```javascript +```js ctx.response // 例如:ctx.body=ctx.response.body ``` `ctx` 上的很多属性和方法都被代理到 `response` 对象上 -```javascript +```js ctx.body ctx.body= ctx.status @@ -248,7 +248,7 @@ ctx.etag= 例如:增加一个 response.foo 属性 setter -```javascript +```js // app/extend/response.js module.exports = { set foo(value) { @@ -275,7 +275,7 @@ module.exports = { 通过 `ctx.helper` 访问到 `helper` 对象,例如: -```javascript +```js // 假设在 app/router.js 中定义了 home router app.get('home', '/', 'home.index'); @@ -291,7 +291,7 @@ ctx.helper.pathFor('home', { by: 'recent', limit: 20 }) 例如:增加一个 `helper.foo()` 方法 -```javascript +```js // app/extend/helper.js module.exports = { foo(param) { @@ -308,7 +308,7 @@ module.exports = { **可以根据环境进行有选择的扩展** 例如: 只在 `unittest` 环境中提供 `mockXX()` 方法以便进行 `mock` 方便测试。 -```javascript +```js // app/extend/application.unittest.js module.exports = { mockXX(k, v) { diff --git "a/docs/manuscripts/server-end/framework/egg/\347\233\256\345\275\225\347\273\223\346\236\204.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\347\233\256\345\275\225\347\273\223\346\236\204.md" similarity index 100% rename from "docs/manuscripts/server-end/framework/egg/\347\233\256\345\275\225\347\273\223\346\236\204.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\347\233\256\345\275\225\347\273\223\346\236\204.md" diff --git "a/docs/manuscripts/server-end/framework/egg/\347\256\200\344\273\213.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\347\256\200\344\273\213.md" similarity index 100% rename from "docs/manuscripts/server-end/framework/egg/\347\256\200\344\273\213.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\347\256\200\344\273\213.md" diff --git "a/docs/manuscripts/server-end/framework/egg/\350\267\257\347\224\261\347\232\204\344\275\277\347\224\250.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\350\267\257\347\224\261\347\232\204\344\275\277\347\224\250.md" similarity index 98% rename from "docs/manuscripts/server-end/framework/egg/\350\267\257\347\224\261\347\232\204\344\275\277\347\224\250.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\350\267\257\347\224\261\347\232\204\344\275\277\347\224\250.md" index e2abe966d..cce0446be 100644 --- "a/docs/manuscripts/server-end/framework/egg/\350\267\257\347\224\261\347\232\204\344\275\277\347\224\250.md" +++ "b/docs/manuscripts/server-end/framework/egg/tutorial/\350\267\257\347\224\261\347\232\204\344\275\277\347\224\250.md" @@ -17,7 +17,7 @@ title: 路由的使用 - `app/router.js` 里面定义 URL 路由规则 -```javascript +```js // app/router.js module.exports = app => { const { router, controller } = app; @@ -29,7 +29,7 @@ module.exports = app => { - `app/controller` 目录下面实现 Controller -```javascript +```js // app/controller/user.js class UserController extends Controller { async info() { @@ -50,7 +50,7 @@ class UserController extends Controller { 根据不同的应用场景,可以对路由进行不同的定义,支持: -```javascript +```js router.verb('path-match', app.controller.action); router.verb('router-name', 'path-match', app.controller.action); @@ -89,7 +89,7 @@ router.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.cont - **Controller 支持子目录**,在定义路由的时候,可以通过 `${directoryName}.${fileName}.${functionName}` 的方式制定对应的 Controller。 -```javascript +```js // app/router.js module.exports = app => { const { router, controller } = app; @@ -115,7 +115,7 @@ module.exports = app => { 路由请求时,传递的参数放置在ctx上下文的query对象中,对于需要使用的字段,可以通过对象解构直接获取哦。 -```javascript +```js // app/router.js module.exports = app => { app.router.get('/search', app.controller.search.index); @@ -136,7 +136,7 @@ exports.index = async ctx => { 路由传参是基于RESTful风格将需要传递的参数放置在接口路由中,以动态变化的情况进行参数传递。 -```javascript +```js // app/router.js module.exports = app => { app.router.get('/user/:id/:name', app.controller.user.info); @@ -156,7 +156,7 @@ exports.info = async ctx => { 路由里面也支持定义正则,可以更加灵活的获取参数 -```javascript +```js // app/router.js module.exports = app => { app.router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail); @@ -180,7 +180,7 @@ exports.detail = async ctx => { body对象传参,一般是基于非GET请求的接口路由,传递的内容为前端通过表单传递的数据。 -```javascript +```js // app/router.js module.exports = app => { @@ -207,7 +207,7 @@ exports.post = async ctx => { 内部重定向很简单,就直接在路由的定义上完成302重定向跳转。 -```javascript +```js // app/router.js module.exports = app => { app.router.get('index', '/home/index', app.controller.home.index); @@ -230,7 +230,7 @@ exports.index = async ctx => { 与内部重定向不同的是,外部重定向的实现逻辑需要在路由定义对应的Controller方法中通过redirect()方法来实现。 -```javascript +```js // app/router.js module.exports = app => { app.router.get('/search', app.controller.search.index); @@ -259,7 +259,7 @@ exports.index = async ctx => { 使用Egg框架可以快速构建后端接口,但是随着业务量的增加、功能越发复杂,接口路由势必会非常多,所以可以按照业务范围,分层进行结构划分,避免给排查问题带来困扰。 -```javascript +```js // app/router.js module.exports = app => { require('./router/news')(app); diff --git "a/docs/manuscripts/server-end/framework/egg/\351\205\215\347\275\256\345\222\214\350\277\220\350\241\214\347\216\257\345\242\203.md" "b/docs/manuscripts/server-end/framework/egg/tutorial/\351\205\215\347\275\256\345\222\214\350\277\220\350\241\214\347\216\257\345\242\203.md" similarity index 98% rename from "docs/manuscripts/server-end/framework/egg/\351\205\215\347\275\256\345\222\214\350\277\220\350\241\214\347\216\257\345\242\203.md" rename to "docs/manuscripts/server-end/framework/egg/tutorial/\351\205\215\347\275\256\345\222\214\350\277\220\350\241\214\347\216\257\345\242\203.md" index 8fcc4ddde..25758c934 100644 --- "a/docs/manuscripts/server-end/framework/egg/\351\205\215\347\275\256\345\222\214\350\277\220\350\241\214\347\216\257\345\242\203.md" +++ "b/docs/manuscripts/server-end/framework/egg/tutorial/\351\205\215\347\275\256\345\222\214\350\277\220\350\241\214\347\216\257\345\242\203.md" @@ -33,7 +33,7 @@ Application 是全局应用对象,在一个应用中,只会实例化一个 在项目入口通过app.js来启动项目,在项目运行时,会在Application对象实例上触发一些事件,应用开发者或者插件开发者可以监听这些事件做一些操作,一般在app.js中进行监听 -```javascript +```js // app.js @@ -69,7 +69,7 @@ Application 对象几乎可以在编写应用时的任何一个地方获取到; - 在app.js中使用 -```javascript +```js // app.js module.exports = app => { // 绑定缓存redis @@ -83,7 +83,7 @@ module.exports = app => { - Controller中使用 -```javascript +```js // app/controller/user.js class UserController extends Controller { // 查询用户 @@ -98,7 +98,7 @@ class UserController extends Controller { 和 Koa 一样,在 Context 对象上,可以通过 ctx.app 访问到 Application 对象。上面的UserController文件也可以改为: -```javascript +```js // app/controller/user.js class UserController extends Controller { async getUser() { @@ -112,7 +112,7 @@ class UserController extends Controller { 在继承于 Controller, Service 基类的实例中,可以通过 this.app 访问到 Application 对象。 -```javascript +```js // app/service/user.js class UserService extends Service { async getUser(uid){ @@ -135,7 +135,7 @@ Context 是一个请求级别的对象,继承自 Koa.Context。在每一次收 - 中间件里获取 -```javascript +```js 'use strict' // 中间件 @@ -149,7 +149,7 @@ async function middleware(ctx,next){ - 支持Controller和Service里面获取 -```javascript +```js // 直接在this对象中拿到 const {ctx}=this @@ -176,7 +176,7 @@ module.exports=app=>{ 在定时任务中的每一个 task 都接受一个 Context 实例作为参数,非常方便的帮助执行一些定时的业务逻辑 -```javascript +```js // app/schedule/refresh.js exports.task=async ctx=>{ @@ -198,7 +198,7 @@ exports.task=async ctx=>{ 可以在 Context 的实例上获取到当前请求的 Request(ctx.request) 和 Response(ctx.response) 实例 -```javascript +```js // app/controller/user.js class UserController extends Controller { @@ -243,7 +243,7 @@ class UserController extends Controller { 获取框架提供的Controller对象的方案: - 方案一:从 egg模块上获取(推荐) -```javascript +```js // app/controller/user.js 'use strict' @@ -263,7 +263,7 @@ module.exports = UserController; - 方案二:从 app 实例上获取 -```javascript +```js // app/controller/user.js 'use strict' module.exports = app => { @@ -286,7 +286,7 @@ module.exports = app => { - 方案一:从 egg模块上获取(推荐) -```javascript +```js // app/service/user.js 'use strict' @@ -329,7 +329,7 @@ Helper 用来提供一些实用的 utility 函数。作用在于我们可以将 #### 获取方式 可以在 Context 的实例上获取到当前请求的 Helper(ctx.helper) 对象实例 -```javascript +```js // app/controller/user.js class UserController extends Controller { async fetch() { @@ -346,7 +346,7 @@ class UserController extends Controller { 在框架的设计中,helper.js文件的功能主要提供一些常用工具函数的封装,所以除了系统自带的,更多的使用场景是进行方法自定义,比如:常用正则校验、长度检测、排序处理等; -```javascript +```js // app/extend/helper.js module.exports = { // 封装统一返回 @@ -423,7 +423,7 @@ Logger对象根据不同的使用场景,主要有: 简单的定时任务: -```javascript +```js // 引用 Subscription 基类: const Subscription = require('egg').Subscription; diff --git "a/docs/manuscripts/server-end/framework/egg/\345\270\270\347\224\250\351\205\215\347\275\256.md" "b/docs/manuscripts/server-end/framework/egg/\346\234\200\344\275\263\345\256\236\350\267\265.md" similarity index 97% rename from "docs/manuscripts/server-end/framework/egg/\345\270\270\347\224\250\351\205\215\347\275\256.md" rename to "docs/manuscripts/server-end/framework/egg/\346\234\200\344\275\263\345\256\236\350\267\265.md" index e12fbba7d..7b4797ebf 100644 --- "a/docs/manuscripts/server-end/framework/egg/\345\270\270\347\224\250\351\205\215\347\275\256.md" +++ "b/docs/manuscripts/server-end/framework/egg/\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -1,12 +1,9 @@ --- title: 常用配置 --- +# 常用配置 -官方文档:https://eggjs.org/zh-cn/ - -重要文档:https://www.bookstack.cn/read/eggjs-2.24-zh/204490 - -### 常用代码 +## 常用代码 ```js const {ctx}=this; @@ -38,7 +35,7 @@ app.config.env -### 常用配置 +## 常用配置 ```js // 日志自定义切割 参考: https://eggjs.org/zh-cn/core/logger.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%97%A5%E5%BF%97 @@ -82,7 +79,7 @@ config.bodyParser={ -### sequelize +## sequelize api参考:https://www.sequelize.com.cn/ @@ -215,7 +212,7 @@ ctx.app.model.sync({ ``` -### 服务部署 +## 服务部署 ```dockerfile # 安装node @@ -256,7 +253,7 @@ COPY ./grpcModule ./xxxxx ``` -### rabbitMQ +## rabbitMQ api参考:http://www.squaremobius.net/amqp.node/channel_api.html#channel_consume @@ -311,4 +308,10 @@ await channel.consume(rabbitQueueName, msg => { }); ``` +## 参考资料 + +- 官方文档: + +- 重要文档: + diff --git a/docs/manuscripts/server-end/framework/midway-learn.md b/docs/manuscripts/server-end/framework/midway-learn.md new file mode 100644 index 000000000..b731c2f2b --- /dev/null +++ b/docs/manuscripts/server-end/framework/midway-learn.md @@ -0,0 +1,2 @@ + +# Midway.js \ No newline at end of file diff --git a/docs/manuscripts/server-end/framework/midway/readme.md b/docs/manuscripts/server-end/framework/midway/readme.md new file mode 100644 index 000000000..c1aad6872 --- /dev/null +++ b/docs/manuscripts/server-end/framework/midway/readme.md @@ -0,0 +1,2 @@ + +# MidWay \ No newline at end of file diff --git a/docs/manuscripts/server-end/framework/readme.md b/docs/manuscripts/server-end/framework/readme.md deleted file mode 100644 index 244a95a8d..000000000 --- a/docs/manuscripts/server-end/framework/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -## 服务端框架 - -> todo \ No newline at end of file diff --git a/docs/manuscripts/server-end/linux/base-shell.md b/docs/manuscripts/server-end/linux/base-shell.md index 27620eab6..4f511340f 100644 --- a/docs/manuscripts/server-end/linux/base-shell.md +++ b/docs/manuscripts/server-end/linux/base-shell.md @@ -1,6 +1,6 @@ # 常用命令 -### cd命令 +## cd Change Directory的缩写,用来变换工作目录的命令 @@ -19,7 +19,7 @@ cd .. ``` -### ls命令 +## ls 列出目录及文件名 - `-a` :全部的文件,连同隐藏文件( 开头为 . 的文件) 一起列出来(常用) - `-d` :仅列出目录本身,而不是列出目录内的文件数据(常用) @@ -33,7 +33,7 @@ ls -l ls -al ``` -### pwd命令 +## pwd Print Working Directory 的缩写,显示目前所在目录的命令 - `-P` :显示出确实的路径,而非使用链接 (link) 路径。 @@ -43,7 +43,7 @@ pwd pwd -P ``` -### mkdir命令 +## mkdir 创建新的目录 - `-m` :直接配置文件的权限! @@ -61,7 +61,7 @@ mkdir -p test ``` -### rmdir命令 +## rmdir 删除空的目录 @@ -75,7 +75,7 @@ rmdir test/ rmdir -p test/aaa/bbb ``` -### cp命令 +## cp 拷贝文件和目录 @@ -103,7 +103,7 @@ cp ~/test /tmp/test cp -i ~/test /tmp/test ``` -### mv命令 +## mv 移动文件与目录,或修改名称 @@ -125,7 +125,7 @@ mv -f sourceDir targetDir ``` -### rm命令 +## rm 移除文件或目录 @@ -133,7 +133,7 @@ mv -f sourceDir targetDir - -i :互动模式,在删除前会询问使用者是否动作 - -r :**递归删除!最常用在目录的删除了**! -### head命令 +## head 取出文件前面几行 @@ -143,7 +143,7 @@ mv -f sourceDir targetDir head -n -10 running.log ``` -### tail命令 +## tail 取出文件后面几行,一般用来查看日志 @@ -156,7 +156,7 @@ tail [-n number] 文件 tail -n -10 running.log ``` -### cat命令 +## cat 由第一行开始显示文件内容,对于大文件谨慎使用 @@ -204,7 +204,7 @@ cat 1.txt -### echo命令 +## echo ```bash # 直接输出文本 @@ -249,7 +249,7 @@ echo `date` -### grep命令 +## grep 参考: https://www.cnblogs.com/chentiao/p/16626503.html @@ -276,7 +276,7 @@ grep -v "^$" test.txt grep -n -v "sbin" test.txt ``` -### sed命令 +## sed sed是Stream Editor(字符流编辑器)的缩写,简称流编辑器。 ed是操作、过滤和转换文本内容的强大工具。sed的常用功能包含对文件实现快速增删改查(增加、删 @@ -292,6 +292,6 @@ sed [选项] [sed内置命令字符] [输入文件] ``` -### awk命令 +## awk diff --git a/docs/manuscripts/server-end/linux/curl.md b/docs/manuscripts/server-end/linux/curl.md index c78b5630c..9875c6646 100644 --- a/docs/manuscripts/server-end/linux/curl.md +++ b/docs/manuscripts/server-end/linux/curl.md @@ -169,7 +169,7 @@ Options: (H) means HTTP/HTTPS only, (F) means FTP only ``` -### 基本安装 +## 基本安装 下载链接: @@ -200,10 +200,10 @@ curl --version curl --help ``` -### 常见用法 +## 常见用法 -#### 查看源码 +### 查看源码 直接返回的是网页源码 ```bash @@ -222,7 +222,7 @@ curl www.142vip.cn curl -o xxx www.142vip.cn ``` -#### 显示头信息 +### 显示头信息 - `-i` 参数可以显示 http response 的头信息,连同网页代码一起 - `-I` 参数则只显示 http response 的头信息 @@ -261,7 +261,7 @@ Location: https://www.142vip.cn/ ``` -#### 显示通信过程 +### 显示通信过程 -v 参数可以显示一次 http 通信的整个过程,包括端口连接和 http request 头信息 @@ -308,7 +308,7 @@ cat xxx.txt ``` -#### 网页自动跳转 +### 网页自动跳转 有的网址是自动跳转的。使用`-L`参数,curl 可以跳转到新的网址 ```bash @@ -317,16 +317,16 @@ curl -L www.142vip.cn ## 这里直接跳转到反向代理页面 ``` -### 发送http请求 +## 发送http请求 -#### Get请求 +### Get请求 ```bash ## get请求 query和params传参 curl www.142vip.cn/abc?id=xxx ``` -#### Post请求 +### Post请求 支持 `--data` 或者 `-d` 来传递`body`参数 @@ -355,7 +355,7 @@ curl -X POST www.142vip.cn/abc curl -X DELETE www.142vip.cn/abc ``` -#### 传递Cookie +### 传递Cookie `--cookie` 参数 可以让 curl 发送 cookie。 @@ -363,14 +363,14 @@ curl -X DELETE www.142vip.cn/abc curl --cookie "id=123456" www.142vip.cn ``` -#### 添加请求头 +### 添加请求头 `--header` 参数添加请求头信息 ```bash curl --header "Content-Type:application/json" www.142vip.cn ``` -#### 支持HTTP认证 +### 支持HTTP认证 `--user` 或者 `-u` 参数 来传递用户信息,进行HTTP认证 ```bash ## 格式 @@ -381,7 +381,7 @@ curl --user root:123456 www.142vip.cn ``` -#### 传递User-Agent +### 传递User-Agent 使用`--user-agent`参数 ```bash @@ -415,6 +415,5 @@ curl --user-agent "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTM * Connection #0 to host www.142vip.cn left intact ``` -### 参考资料 -- \ No newline at end of file +## 参考资料 \ No newline at end of file diff --git a/docs/manuscripts/server-end/linux/vim.gif b/docs/manuscripts/server-end/linux/images/vim.gif similarity index 100% rename from docs/manuscripts/server-end/linux/vim.gif rename to docs/manuscripts/server-end/linux/images/vim.gif diff --git a/docs/manuscripts/server-end/linux/package-manage.md b/docs/manuscripts/server-end/linux/package-manage.md index 811a3a2be..bcfe79dfb 100644 --- a/docs/manuscripts/server-end/linux/package-manage.md +++ b/docs/manuscripts/server-end/linux/package-manage.md @@ -1,11 +1,11 @@ # 包管理工具 -### apt-get +## apt-get apt-get 命令适用于 deb 包管理式的 Linux 操作系统(Debian、Ubuntu等),主要用于自动从互联网软件仓库中搜索、下载、安装、升级、卸载软件或操作系统 -#### 安装 +### 安装 ```bash ## 普通安装 @@ -24,7 +24,7 @@ apt-get source xxx ``` -#### 卸载 +### 卸载 ```bash #删除软件包, 保留配置文件 @@ -44,7 +44,7 @@ apt-get clean && apt-get autoclean ``` -#### 更新 +### 更新 ```bash # 更新安装源(Source) @@ -56,14 +56,14 @@ apt-get dist-upgrade ``` -#### 帮助命令 +### 帮助命令 ```bash apt-get --help ``` -#### 配置软件源 +### 配置软件源 ```bash @@ -145,11 +145,11 @@ sudo apt-get upgrade ``` -### yum +## yum 使用yum安装和卸载软件,软件包都是rpm格式的 -#### 安装 +### 安装 ```bash ## -y:不询问,默认安装 @@ -159,7 +159,7 @@ yum install XXX -y yum install/update xxx ``` -#### 查询 +### 查询 ```bash @@ -185,7 +185,7 @@ yum info yum provides~ ``` -#### 清除 +### 清除 ```bash ## 卸载 @@ -203,7 +203,7 @@ yum clean oldheaders ``` -#### 更换软件源 +### 更换软件源 ```bash ## 1. 安装wget @@ -225,7 +225,7 @@ yum makecache -### apk +## apk Alpine Linux 下的包管理工具 @@ -242,7 +242,7 @@ Alpine Linux 下的包管理工具 Alpine使用apk进行包管理,通过apk --help命令查看完整的包管理命令 -#### 基础使用 +### 基础使用 ```bash apk install xxx @@ -264,7 +264,7 @@ $ apk info #列出所有已安装的软件包 ``` -#### 升级 +### 升级 upgrade命令升级系统已安装的所以软件包(一般包括内核),当然也可指定仅升级部分软件包(通过-u或–upgrade选择指定)。 @@ -279,7 +279,7 @@ apk add --upgrade busybox apk add docker --update-cache --repository http://mirrors.ustc.edu.cn/alpine/v3.4/main/ --allow-untrusted ``` -#### 配置软件源 +### 配置软件源 - diff --git a/docs/manuscripts/server-end/linux/soft-install.md b/docs/manuscripts/server-end/linux/soft-install.md index 7dab5b1c0..d0e4dd58a 100644 --- a/docs/manuscripts/server-end/linux/soft-install.md +++ b/docs/manuscripts/server-end/linux/soft-install.md @@ -1,4 +1,3 @@ - # Linux下软件安装 linux系统,主要分debian系和redhat系,还有其它自由的发布版本 @@ -11,19 +10,19 @@ linux系统,主要分debian系和redhat系,还有其它自由的发布版本 -### RedHat系列 +## RedHat系列 - 安装包格式:rpm包 - 包管理工具 yum - 支持tar包 -### Debian系列 +## Debian系列 - 安装包格式 deb包 - 包管理工具 apt-get - 支持tar包 -### 安装方式 +## 安装方式 #### yum @@ -42,7 +41,7 @@ yum update xxx -#### apt-get +### apt-get - 可以用于运作deb包 @@ -54,7 +53,7 @@ apt-get remove ## 更新 apt-get update ``` -#### wget +### wget **本质是一个下载工具** 特点如下: - 只管下载,不会安装 @@ -81,8 +80,8 @@ curl -O https://curl.haxx.se/download/curl-7.30.1.tar.gz ``` -### curl +## curl -### vim +## vim -### gcc \ No newline at end of file +## gcc \ No newline at end of file diff --git a/docs/manuscripts/server-end/linux/vim.md b/docs/manuscripts/server-end/linux/vim.md index 4d2a81397..1ece25b5d 100644 --- a/docs/manuscripts/server-end/linux/vim.md +++ b/docs/manuscripts/server-end/linux/vim.md @@ -1,6 +1,6 @@ # Linux vi/vim -![](./vim.gif) +![](images/vim.gif) - 命令模式(Command mode):VIM 的默认模式,可以用于移动游标查看内容; - 编辑模式(Insert mode):按下 "i" 等按键之后进入,可以对文本进行编辑; @@ -20,7 +20,7 @@ -### 命令模式 +## 命令模式 用户刚刚启动 vi/vim,便进入了命令模式。 @@ -36,7 +36,7 @@ 命令模式只有一些最基本的命令,因此仍要依靠底线命令模式输入更多命令 -### 输入模式 +## 输入模式 在命令模式下按下i就进入了输入模式。 @@ -52,7 +52,7 @@ - `ESC` 退出输入模式,切换到命令模式 -### 指令列模式 +## 指令列模式 在输入模式模式下按下`:`(英文冒号)就能进入。 @@ -66,9 +66,9 @@ 按`ESC`键可随时退出指令列模式。 -### 常用命令操作 +## 常用命令操作 -#### 退出 +### 退出 - `:wq!` 强制保存退出 - `:wq` 保存退出 @@ -77,7 +77,7 @@ - `:q` 退出 - `:q!` 强制退出 -#### 移动 +### 移动 - `h` 在当前行向左移动一个字符 @@ -93,7 +93,7 @@ - `G` 将光标定位到本文章的最后一行,与`:$`功能相同。 -#### 搜索 +### 搜索 - `/` 后面跟要查找的东西,在文件中向前搜索 @@ -103,7 +103,7 @@ -#### 插入 +### 插入 - `i` 在当前位置的字符前面进入插入模式 - `I` 在当前行的开头进行插入 @@ -112,7 +112,7 @@ - `o` 在当前行下面打开一个新行进行插入 - `O` 在当前行上面打开一个新行进行插入 -#### 删除或剪切 +### 删除或剪切 - `dd` 删除当前行 - `dw` 删除一个单词 @@ -126,7 +126,7 @@ - `:1,100 mo $` 将1~100行的内容移动到最后一行。 -#### 参考资料 +## 参考资料 - - diff --git a/docs/manuscripts/server-end/node-learn/axios.md b/docs/manuscripts/server-end/node-learn/axios.md index 6934b79c5..94586c26d 100644 --- a/docs/manuscripts/server-end/node-learn/axios.md +++ b/docs/manuscripts/server-end/node-learn/axios.md @@ -1,4 +1,3 @@ - # Axios `Axios` 是一个基于 `promise` 网络请求库,作用于`node.js` 和浏览器中。 它是 `isomorphic` 的(同一套代码可以运行在浏览器和`node.js`中)。 @@ -6,7 +5,7 @@ - 在服务端它使用原生 node.js `http` 模块 - 在客户端 (浏览端) 则使用 `XMLHttpRequests` -### 优点 +## 优点 - [x] 从浏览器创建 XMLHttpRequests - [x] 从 node.js 创建 http 请求 @@ -18,16 +17,16 @@ - [x] 客户端支持防御XSRF - ... -### 安装 +## 安装 ```bash pnpm install axios npm install axios ``` -### 基本使用 +## 基本使用 -#### TypeScript类型推断 +### TypeScript类型推断 为了在CommonJS中使用 require() 导入时获得TypeScript类型推断(智能感知/自动完成),可以使用以下方法: @@ -37,7 +36,7 @@ const axios = require('axios').default // ... 使用axios对象的api 支持类型推断 ``` -#### 发送请求 +### 发送请求 ```js @@ -63,7 +62,7 @@ axios({ }); ``` -#### 并发请求 +### 并发请求 ```js @@ -83,7 +82,7 @@ Promise.all([getUserAccount(), getUserPermissions()]) }); ``` -### 取消请求 +## 取消请求 axios从`v0.22.0` 开始支持以fetch api的方式:AbortController取消请求 ```js @@ -100,7 +99,7 @@ controller.abort() -### 实例方法 +## 实例方法 支持多种HTTP网络请求类型,例如:Get、Post、Put、Delete等 @@ -117,7 +116,7 @@ controller.abort() **在使用别名方法时, url、method、data 这些属性都不必在配置中指定。** -#### 创建实例 +### 创建实例 ```js const instance = axios.create({ @@ -129,6 +128,7 @@ const instance = axios.create({ #### 配置 + **全局默认配置,将作用于每个请求** ```js @@ -143,9 +143,7 @@ axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded instance.defaults.headers.common['Authorization'] = AUTH_TOKEN; ``` -### 请求配置 - - +## 请求配置 **只有 url 是必需的。如果没有指定 method,请求将默认使用 GET 方法。** @@ -313,7 +311,7 @@ instance.defaults.headers.common['Authorization'] = AUTH_TOKEN; } ``` -### 响应结构 +## 响应结构 ```js { @@ -341,9 +339,9 @@ instance.defaults.headers.common['Authorization'] = AUTH_TOKEN; } ``` -### 拦截器 +## 拦截器 -#### 请求拦截器 +### 请求拦截器 ```js // 添加请求拦截器 axios.interceptors.request.use(function (config) { @@ -355,7 +353,7 @@ axios.interceptors.request.use(function (config) { }); ``` -#### 响应拦截器 +### 响应拦截器 ```js @@ -371,14 +369,14 @@ axios.interceptors.response.use(function (response) { }); ``` -#### 自定义 +### 自定义 ```js const instance = axios.create(); instance.interceptors.request.use(function () {/*...*/}); ``` -#### 移除 +### 移除 使用`eject`方法 @@ -388,7 +386,7 @@ axios.interceptors.request.eject(myInterceptor); ``` -#### 错误处理 +### 错误处理 ```js axios.get('/user/12345') @@ -415,7 +413,7 @@ axios.get('/user/12345') ``` -### 最佳实践 +## 最佳实践 默认情况下,axios将 JavaScript 对象序列化为 JSON,针对不同的传参风格,需要对数据进行处理 @@ -473,7 +471,7 @@ axios.interceptors.request.use(config => { 另外在实际使用axios时,会结合项目开发对axios进行全局封装处理,区分环境切换不同的配置。一般采用`process.env`来区分环境 -### 参考资料 +## 参考资料 - - \ No newline at end of file diff --git a/docs/manuscripts/server-end/node-learn/dayjs.md b/docs/manuscripts/server-end/node-learn/dayjs.md index c2509b029..b47423c60 100644 --- a/docs/manuscripts/server-end/node-learn/dayjs.md +++ b/docs/manuscripts/server-end/node-learn/dayjs.md @@ -5,14 +5,14 @@ Day.js 是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js - 官网: -### 安装 +## 安装 ```bash pnpm install dayjs npm install dayjs ``` -### 高频使用 +## 高频使用 ```js // 时间戳 毫秒 13位 @@ -25,19 +25,19 @@ console.log(dayjs().format('YYYY-MM-DDTHH:mm:ss')) ``` -### 解析 +## 解析 @[code js](@code/node/dayjs/demo-1.js) ### 取值|赋值 @[code js](@code/node/dayjs/demo-2.js) -### 操作 +## 操作 @[code js](@code/node/dayjs/demo-3.js) -### 显示 +## 显示 @[code js](@code/node/dayjs/demo-4.js) -### 查询 +## 查询 @[code js](@code/node/dayjs/demo-5.js) \ No newline at end of file diff --git a/docs/manuscripts/server-end/node-learn/lodash.png b/docs/manuscripts/server-end/node-learn/images/lodash.png similarity index 100% rename from docs/manuscripts/server-end/node-learn/lodash.png rename to docs/manuscripts/server-end/node-learn/images/lodash.png diff --git a/docs/manuscripts/server-end/node-learn/lodash.md b/docs/manuscripts/server-end/node-learn/lodash.md index 9301ae977..2e40fa27f 100644 --- a/docs/manuscripts/server-end/node-learn/lodash.md +++ b/docs/manuscripts/server-end/node-learn/lodash.md @@ -9,7 +9,7 @@ Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库 - 对值进行操作和检测 - 创建符合功能的函数 -### 安装 +## 安装 使用`npm`或者`pnpm`等包管理工具下载依赖都行,注意区分环境 @@ -29,7 +29,7 @@ npm install --save @types/lodash ``` -### 简单使用 +## 简单使用 ```js // 支持import导入 @@ -44,15 +44,15 @@ console.log(_.chunk(['a', 'b', 'c', 'd'], 3)) ``` -### 高频使用 +## 高频使用 以下列举出常用的函数,**有的很容易忘记** -![](./lodash.png) +![](images/lodash.png) -### 实际使用 +## 实际使用 -#### 数组 +### 数组 - compact: 过滤假值(false, null,0, "", undefined,NaN) - concat: 拼接 @@ -82,7 +82,7 @@ console.log(_.chunk(['a', 'b', 'c', 'd'], 3)) @[code js](@code/node/lodash/demo-array.js) -#### 对象 +### 对象 - assign:拷贝 - merge: 深拷贝 @@ -112,7 +112,7 @@ console.log(_.chunk(['a', 'b', 'c', 'd'], 3)) @[code js](@code/node/lodash/demo-object.js) -#### 集合 +### 集合 - countBy: 计数 - each: 遍历 @@ -137,7 +137,7 @@ console.log(_.chunk(['a', 'b', 'c', 'd'], 3)) @[code js](@code/node/lodash/demo-set.js) -#### 数字 +### 数字 - inRange: 判断是否在范围中 - random: 返回指定范围随机值 @@ -154,7 +154,7 @@ console.log(_.chunk(['a', 'b', 'c', 'd'], 3)) - min: 最小值 @[code js](@code/node/lodash/demo-num.js) -#### 字符串 +### 字符串 - camelCase: 小驼峰 - kebabCase: 转换为-连接,例如:Foo Bar ----> foo-bar @@ -181,7 +181,7 @@ console.log(_.chunk(['a', 'b', 'c', 'd'], 3)) -### 参考资料 +## 参考资料 - - diff --git a/docs/manuscripts/server-end/node-learn/npm-package.md b/docs/manuscripts/server-end/node-learn/npm-package.md index 23068ebe4..95d15c5f8 100644 --- a/docs/manuscripts/server-end/node-learn/npm-package.md +++ b/docs/manuscripts/server-end/node-learn/npm-package.md @@ -1,9 +1,9 @@ --- title: 常用模块包 --- +# 常用NPM包 - -### gm +## gm > 使用前,先安装GraphicsMagick或者ImageMagick,支持图片处理,添加水印、裁剪... @@ -36,7 +36,7 @@ gm('/path/to/my/img.jpg') - 在线体验: -### moment +## moment > 时间格式化 @@ -50,7 +50,7 @@ moment(XXXXXX).format('YYYY-MM-DD HH:mm:ss') - -### crypto || crypto.js +## crypto || crypto.js > 支持加密、哈希、指定长度随机字符串 等 【非常好用】 @@ -75,7 +75,7 @@ function hashMac(code,key){ - crypto.js地址: -### path +## path > 内置模块。路径处理,在相对路径、绝对路劲的处理上,很有优势 @@ -94,7 +94,7 @@ path.sep - -### fs-extra +## fs-extra > 文件操作相关,基于fs模块封装 @@ -111,7 +111,7 @@ fse.readdirSync(path.join(_dirname),'XXXX') - -### qr-images +## qr-images > 简单易用的二维码生成,模块[qrcode](https://www.npmjs.com/package/qrcode)也支持类似功能 @@ -130,7 +130,7 @@ const svg_string = qr.imageSync('I love QR!', { type: 'svg' }); - -### lodash +## lodash @@ -152,7 +152,7 @@ const _ = require('lodash'); - 地址: -### bluebird +## bluebird > bluebird是一个第三方Promise类库,相比其它第三方类库或标准对象来说,功能更齐全而不臃肿、浏览器兼容性更好! diff --git a/docs/manuscripts/server-end/node-learn/readme.md b/docs/manuscripts/server-end/node-learn/readme.md deleted file mode 100644 index 8e4958a7f..000000000 --- a/docs/manuscripts/server-end/node-learn/readme.md +++ /dev/null @@ -1,3 +0,0 @@ - - -# Node.js \ No newline at end of file diff --git a/docs/manuscripts/server-end/node-learn/rxjs.md b/docs/manuscripts/server-end/node-learn/rxjs.md index 29d02d1cd..5dc704f8c 100644 --- a/docs/manuscripts/server-end/node-learn/rxjs.md +++ b/docs/manuscripts/server-end/node-learn/rxjs.md @@ -1,5 +1,3 @@ - - # RxJS - diff --git a/docs/manuscripts/server-end/node-learn/stream.md b/docs/manuscripts/server-end/node-learn/stream.md index 3e232e406..ddfee9c29 100644 --- a/docs/manuscripts/server-end/node-learn/stream.md +++ b/docs/manuscripts/server-end/node-learn/stream.md @@ -1,11 +1,11 @@ # node核心模块 stream -### 1.什么是stream流 +## 什么是stream流 流(Stream),是一个数据传输手段,是端到端信息交换的一种方式,而且是有顺序、逐块读取数据、处理内容。常见使用场景http传输大文件,本地文件读取。 -### 2.node中流的基本类型 +## node中流的基本类型 - **Readable** - 可读操作。 - **Writable** - 可写操作。 @@ -19,7 +19,7 @@ - **error** - 在接收和写入过程中发生错误时触发。 - **finish** - 所有数据已被写入到底层系统时触发。 -### 3.使用express框架简单搭一个 http传输流 +## 使用express框架简单搭一个 http传输流 首先需要创建两个文件 input.txt 、output.txt diff --git a/docs/manuscripts/server-end/orm/sequelize.md b/docs/manuscripts/server-end/orm/sequelize.md index 8a59abc44..9aafe63b8 100644 --- a/docs/manuscripts/server-end/orm/sequelize.md +++ b/docs/manuscripts/server-end/orm/sequelize.md @@ -1,10 +1,13 @@ -### 快速入门 + +# SequelizeORM + +## 快速入门 Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能. -### 安装 +## 安装 `Sequelize` 的使用可以通过 npm (或 yarn)模块包管理器进行下载. @@ -25,11 +28,11 @@ $ npm install --save tedious # Microsoft SQL Server ``` -### 数据库连接 +## 数据库连接 要连接到数据库,必须创建一个 `Sequelize` 实例. 这可以通过将连接参数分别传递到 `Sequelize` 构造函数或通过传递一个连接 `URI` 来完成: -```javascript +```js const { Sequelize } = require('sequelize'); // 方法 1: 传递一个连接 URI @@ -54,7 +57,7 @@ const sequelize = new Sequelize('database', 'username', 'password', { -### 测试连接 +## 测试连接 可以使用 `.authenticate()` 函数测试连接是否正常,**无法连接数据库时,会抛出异常** @@ -69,13 +72,13 @@ try { ``` -### 关闭连接 +## 关闭连接 默认情况下,`Sequelize` 将保持连接打开状态,并对所有查询使用相同的连接. 如果需要关闭连接,请调用 `sequelize.close()`(这方法异步的并返回一个 Promise对象). -### 新旧数据库 +## 新旧数据库 如果你是从0开始一个项目,且你的数据库尚不存在,那么一开始就可以使用 `Sequelize`,以便自动创建数据库中的每个表(糟糕的是,`egg-sequelize`插件由于加载的约束,只能连接存在的数据库). @@ -85,7 +88,7 @@ try { -### 模型基础概念 +## 模型基础概念 模型是通过官方文档中Model一词,直接翻译过来,在使用ORM框架去操作数据库的时候,**模型是 Sequelize 的本质. 模型是代表数据库中表的抽象. 在 Sequelize 中,它是一个 Model 的扩展类.** 模型往往和表字段是一一对应的,这样非常利于封装,类似于Java中的MyBatis。 @@ -97,7 +100,7 @@ try { 相比官方文档的详细、全面,这里主要从实用出发,总记一套完整、基础、易于上手的模型笔记文档,开始吧~ -### 模型定义 +## 模型定义 在 `Sequelize` 中可以用两种等效的方式定义模型: @@ -114,7 +117,7 @@ try { -### 使用`sequelize.define` +## 使用`sequelize.define` ```js // 直接使用sequelize缓存封装,方便举例 @@ -139,7 +142,7 @@ const User = sequelize.define('User', { console.log(User === sequelize.models.User); // true ``` -### 扩展 `Model` +## 扩展 `Model` ```js const { Sequelize, DataTypes, Model } = require('sequelize'); @@ -241,7 +244,7 @@ sequelize.define('User', { * `User.sync({ alter: true })` - 这将检查数据库中表的当前状态(它具有哪些列,它们的数据类型等),然后在表中进行必要的更改以使其与模型匹配. -```javascript +```js await User.sync({ force: true }); @@ -263,7 +266,7 @@ console.log("所有模型均已成功同步."); ### 删除表 -```javascript +```js // 删除单个表,例如:User await User.drop(); @@ -295,7 +298,7 @@ await sequelize.drop(); **直接 SQL 查询(例如,通过任何其他方式在不使用 Sequelize 的情况下执行的查询)将不会导致这些字段自动更新.** -```javascript +```js // 配置单个表的字段自动管理 sequelize.define('User', { // ... (属性) @@ -307,19 +310,16 @@ sequelize.define('User', { 也可以只启用 `createdAt`/`updatedAt` 之一,并为这些列提供自定义名称: -```javascript +```js class Foo extends Model {} Foo.init({ // ... (属性) }, { sequelize, - // 这里时间戳必须启用true timestamps: true, - // 不使用createdAt字段 createdAt: false, - // 使用 updatedAt字段 但是希望名称叫做 create_time updatedAt: 'create_time' }); @@ -426,7 +426,6 @@ DataTypes.DOUBLE(11, 10) // DOUBLE(11,10) DataTypes.DECIMAL // DECIMAL DataTypes.DECIMAL(10, 2) // DECIMAL(10,2) - ``` #### 无符号和零填充整数 @@ -555,7 +554,7 @@ Foo.init({ ``` -# 模型实例 +## 模型实例 模型是 [ES6 类](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). @@ -756,8 +755,6 @@ console.log(user.favoriteColor); // "green" ## 改变对save()的原有认知 - - `save()`方法在内部进行了优化,只更新真正更改的字段。这意味着,如果不更改任何内容并调用`save()`方法,`Sequelize`将知道`save()`方法是多余的,并且不执行任何操作,即不会生成任何查询(仍返回一个Promise对象,但会立即解决)。 @@ -773,7 +770,6 @@ console.log(user.favoriteColor); // "green" ```js // 创建实例对象 const user = await User.create({ name: "Lisa", age: 100 }); - // age属性+1 const incrementResult = await user.increment('age', { by: 2 }); ``` @@ -781,7 +777,6 @@ const incrementResult = await user.increment('age', { by: 2 }); **注意: 如只增加 1, 你可以省略 'by' 参数, 只需执行 `user.increment('age')`** - 在 PostgreSQL 中, 除非设置了 `{returning:false}` 参数(不然它将是 `undefined`), 否则 `incrementResult` 将是更新后的 user. - - 在其它数据库方言中, `incrementResult` 将会是 `undefined`. 如果你需要更新的实例, 你需要调用 `user.reload()`. diff --git a/docs/manuscripts/server-end/server-end.sidebar.ts b/docs/manuscripts/server-end/server-end.sidebar.ts index ff175003d..004041b8e 100644 --- a/docs/manuscripts/server-end/server-end.sidebar.ts +++ b/docs/manuscripts/server-end/server-end.sidebar.ts @@ -71,7 +71,7 @@ export const serverEndSidebar = [{ }, { text: '常用框架', - collapsible: true, + // collapsible: true, prefix: 'framework', children: [ { @@ -98,7 +98,7 @@ export const serverEndSidebar = [{ }, { text: 'ORM框架', - collapsible: true, + // collapsible: true, children: [ { text: 'Sequelize', @@ -135,30 +135,31 @@ export const serverEndSidebar = [{ { text: '服务部署', collapsible: true, + prefix: 'docker-cluster', children: [ { text: '基础安装', - link: 'docker-cluster/docker.md' + link: 'docker.md' }, { text: 'PM2管理', - link: 'docker-cluster/pm2.md' + link: 'pm2.md' }, { text: 'egg-cluster', - link: 'docker-cluster/egg-cluster.md' + link: 'egg-cluster.md' }, { text: 'Docker', - link: 'docker-cluster/docker.md' + link: 'docker.md' }, { text: '集群管理', - link: 'docker-cluster/docker-compose.md' + link: 'docker-compose.md' }, { text: 'Dockerfile详解', - link: 'docker-cluster/dockerfile.md' + link: 'dockerfile.md' } ] @@ -186,11 +187,11 @@ export const serverEndSidebar = [{ children: [ { text: 'vim', - link: 'linux/vim.md' + link: 'vim.md' }, { text: 'curl', - link: 'linux/curl.md' + link: 'curl.md' } ] } diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\345\212\250\346\200\201\350\247\204\345\210\222/fibonacci.md" "b/docs/manuscripts/solo-algorithm/interview-101/\345\212\250\346\200\201\350\247\204\345\210\222/fibonacci.md" index 3e34dbb7c..3054b45c1 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\345\212\250\346\200\201\350\247\204\345\210\222/fibonacci.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\345\212\250\346\200\201\350\247\204\345\210\222/fibonacci.md" @@ -1,12 +1,17 @@ +--- +title: 斐波那契数列 +permalink: /manuscripts/solo-algorithm/fibonacci.html +--- + # 斐波那契数列 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3) - [欢迎讨论]() -### 题目描述 +## 题目描述 大家都知道斐波那契数列,现在要求输入一个正整数 n ,请你输出斐波那契数列的第 n 项。 斐波那契数列是一个满足: @@ -18,17 +23,17 @@ 数据范围: 1≤n≤40 要求:空间复杂度O(1),时间复杂度O(n) ,本题也有时间复杂度O(logn) 的解法 -### 思路 +## 刷题思路 方案一:递归 方案二:动态规划、循环迭代 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/fibonacci.js) -### 一些建议 +## 一些建议 - 熟记斐波那契数列特性 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloor.md" "b/docs/manuscripts/solo-algorithm/interview-101/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloor.md" index 0af982897..a5e009396 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloor.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloor.md" @@ -1,19 +1,23 @@ +--- +permalink: /manuscripts/solo-algorithm/jumpFloor.html +--- + # 跳台阶 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4) - [欢迎讨论]() -### 题目描述 +## 题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。 数据范围:1≤n≤40 要求:时间复杂度:O(n) ,空间复杂度:O(1) -### 思路 +## 刷题思路 总共有n个台阶,假如 @@ -26,12 +30,12 @@ 这不就是斐波那契数列么???? 可以参考:[【入门】斐波那契数列](./fibonacci.md) -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/动态规划/jumpFloor.js) -### 一些建议 +## 一些建议 - 数学分析、逻辑思维很重要 - 熟练斐波那契数列 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/addInList.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/addInList.md" index 31f4e681d..10f0b24a8 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/addInList.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/addInList.md" @@ -1,24 +1,29 @@ +--- +title: BM11 链表相加(二) +permalink: /manuscripts/solo-algorithm/link-table/addList.html +--- + # BM11 链表相加(二) -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694840620281) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/addInList.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/addInList.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/deleteDuplicates-1.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/deleteDuplicates-1.md" index 02d994837..85d23950d 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/deleteDuplicates-1.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/deleteDuplicates-1.md" @@ -1,26 +1,28 @@ - +--- +permalink: /manuscripts/solo-algorithm/link-table/deleteDuplicates-one.html +--- # BM15 删除有序链表中重复的元素-I -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694841529345) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/deleteDuplicates-1.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/deleteDuplicates-1.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/deleteDuplicates-2.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/deleteDuplicates-2.md" index 96a7bbf7d..b1f40566f 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/deleteDuplicates-2.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/deleteDuplicates-2.md" @@ -1,21 +1,24 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/deleteDuplicates-two.html +--- # BM16 删除有序链表中重复的元素-II -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694841743148) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/deleteDuplicates-2.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/deleteDuplicates-2.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/entryNodeOfLoop.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/entryNodeOfLoop.md" index 2b0cf765a..d08c72061 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/entryNodeOfLoop.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/entryNodeOfLoop.md" @@ -1,24 +1,27 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/entryNodeOfLoop.html +--- # BM7 链表中环的入口结点 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694593953358) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/entryNodeOfLoop.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/entryNodeOfLoop.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/findFirstCommonNode.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/findFirstCommonNode.md" index f09501030..07c748053 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/findFirstCommonNode.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/findFirstCommonNode.md" @@ -1,24 +1,27 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/findFirstCommonNode.html +--- # BM8 链表中倒数最后k个结点 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694594781904) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/findFirstCommonNode.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/findFirstCommonNode.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/findKthToTail.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/findKthToTail.md" index 8b69a9f85..000a52f38 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/findKthToTail.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/findKthToTail.md" @@ -1,24 +1,27 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/findKthToTail.html +--- # BM8 链表中倒数最后k个结点 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694594062276) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/findKthToTail.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/findKthToTail.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/hasCycle.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/hasCycle.md" index 7f9d4ef46..11ca4ea30 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/hasCycle.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/hasCycle.md" @@ -1,24 +1,24 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/hasCycle.html +--- # BM6 判断链表中是否有环 - - - -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694589556195) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/hasCycle.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/hasCycle.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/isPail.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/isPail.md" index 73eda9ca3..ba7946c9f 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/isPail.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/isPail.md" @@ -1,23 +1,26 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/isPail.html +--- # BM13 判断一个链表是否为回文结构 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694840858005) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/isPail.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/isPail.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/merge.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/merge.md" index 23789960b..97eb62d9c 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/merge.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/merge.md" @@ -1,22 +1,25 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/merge.html +--- # BM4 合并两个排序的链表 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694574392596) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/merge.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/merge.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/mergeKLists.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/mergeKLists.md" index e61959b1c..c69dbb624 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/mergeKLists.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/mergeKLists.md" @@ -1,24 +1,27 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/mergeList.html +--- # BM5 合并k个已排序的链表 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694589240005) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/mergeLists.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/mergeLists.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/oddEvenList.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/oddEvenList.md" index edacbb6a6..3d38d31df 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/oddEvenList.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/oddEvenList.md" @@ -1,21 +1,24 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/link-table/oddEventList.html +--- # BM14 链表的奇偶重排 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694841372669) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/oddEvenList.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/oddEvenList.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/removeNthFromEnd.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/removeNthFromEnd.md" index a62fed107..835ef14c9 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/removeNthFromEnd.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/removeNthFromEnd.md" @@ -1,24 +1,26 @@ - +--- +permalink: /manuscripts/solo-algorithm/link-table/removeNthFromEnd.html +--- # BM9 删除链表的倒数第n个节点 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694594387319) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![区间反转.png](../images/removeNthFromEnd.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/removeNthFromEnd.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseBetween.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseBetween.md" index a6416c0e2..15ca4b090 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseBetween.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseBetween.md" @@ -1,22 +1,25 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/reverseBetween.html +--- # BM2 链表内指定区间反转 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694574050421) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![区间反转.png](../images/reverseBetween.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/reverseBetween.ts) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseGroup.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseGroup.md" index bee039099..fb7fc6e33 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseGroup.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseGroup.md" @@ -1,24 +1,24 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/reverseGroup.html +--- # BM3 链表中的节点每k个一组翻转 - - - -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/reverseList.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/reverseGroup.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseList.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseList.md" index d9c2a6cf8..6cbd40102 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseList.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/reverseList.md" @@ -1,24 +1,25 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/reverseList.html +--- # BM1 反转链表 - - -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/reverseList.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/reverseList.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/sortInList.md" "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/sortInList.md" index 4d840d078..fc0c29084 100644 --- "a/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/sortInList.md" +++ "b/docs/manuscripts/solo-algorithm/interview-101/\351\223\276\350\241\250/sortInList.md" @@ -1,24 +1,27 @@ +--- +permalink: /manuscripts/solo-algorithm/link-table/sortInList.html +--- # BM12 单链表的排序 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/share/jump/8484115461694840715099) - [欢迎讨论]() -### 题目描述 +## 题目描述 ![反转链表.png](../images/sortInList.png) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/interview-101/sortInList.js) -### 一些建议 +## 一些建议 diff --git a/docs/manuscripts/solo-algorithm/question-collections.md b/docs/manuscripts/solo-algorithm/question-collections.md index b0c4b45a2..fd0519280 100644 --- a/docs/manuscripts/solo-algorithm/question-collections.md +++ b/docs/manuscripts/solo-algorithm/question-collections.md @@ -1,5 +1,3 @@ - # 刷题整理 - > todo \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/readme.md b/docs/manuscripts/solo-algorithm/readme.md index 28b17506f..33262cecc 100644 --- a/docs/manuscripts/solo-algorithm/readme.md +++ b/docs/manuscripts/solo-algorithm/readme.md @@ -1,26 +1,26 @@ # SOLO算法 -### 牛客网 +## 牛客网 面试必刷TOP101: -### 算法模型 +## 算法模型 -#### 快速排序 +## 快速排序 -#### 堆排序 +## 堆排序 -### 面试必刷101 +## 面试必刷101 -### 剑指Offer +## 剑指Offer -### 参考资料 +## 参考资料 - diff --git a/docs/manuscripts/solo-algorithm/shell/shell-1.md b/docs/manuscripts/solo-algorithm/shell/shell-1.md index 8cacf21c3..3b0b95c42 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-1.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-1.md @@ -1,11 +1,11 @@ # SHELL-1 统计文件的行数 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/205ccba30b264ae697a78f425f276779) - [欢迎讨论]() -### 题目描述 +## 题目描述 描述 编写一个shell脚本以输出一个文本文件nowcoder.txt中的行数 @@ -25,11 +25,11 @@ int main() ``` 你的脚本应当输出:9 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-10.md b/docs/manuscripts/solo-algorithm/shell/shell-10.md index f89bfb686..773ce5f88 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-10.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-10.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-11.md b/docs/manuscripts/solo-algorithm/shell/shell-11.md index f89bfb686..773ce5f88 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-11.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-11.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-12.md b/docs/manuscripts/solo-algorithm/shell/shell-12.md index f89bfb686..773ce5f88 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-12.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-12.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-13.md b/docs/manuscripts/solo-algorithm/shell/shell-13.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-13.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-13.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-14.md b/docs/manuscripts/solo-algorithm/shell/shell-14.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-14.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-14.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-15.md b/docs/manuscripts/solo-algorithm/shell/shell-15.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-15.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-15.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-16.md b/docs/manuscripts/solo-algorithm/shell/shell-16.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-16.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-16.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-17.md b/docs/manuscripts/solo-algorithm/shell/shell-17.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-17.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-17.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-18.md b/docs/manuscripts/solo-algorithm/shell/shell-18.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-18.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-18.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-19.md b/docs/manuscripts/solo-algorithm/shell/shell-19.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-19.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-19.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-2.md b/docs/manuscripts/solo-algorithm/shell/shell-2.md index c3adef490..6d2237398 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-2.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-2.md @@ -1,11 +1,11 @@ # SHELL-2 统计文件的行数 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/205ccba30b264ae697a78f425f276779) - [欢迎讨论]() -### 题目描述 +## 题目描述 描述 编写一个shell脚本以输出一个文本文件nowcoder.txt中的行数 @@ -24,12 +24,11 @@ int main() ``` 你的脚本应当输出:9 -### -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-2.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-20.md b/docs/manuscripts/solo-algorithm/shell/shell-20.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-20.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-20.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-21.md b/docs/manuscripts/solo-algorithm/shell/shell-21.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-21.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-21.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-22.md b/docs/manuscripts/solo-algorithm/shell/shell-22.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-22.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-22.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-23.md b/docs/manuscripts/solo-algorithm/shell/shell-23.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-23.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-23.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-24.md b/docs/manuscripts/solo-algorithm/shell/shell-24.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-24.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-24.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-25.md b/docs/manuscripts/solo-algorithm/shell/shell-25.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-25.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-25.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-26.md b/docs/manuscripts/solo-algorithm/shell/shell-26.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-26.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-26.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-27.md b/docs/manuscripts/solo-algorithm/shell/shell-27.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-27.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-27.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-28.md b/docs/manuscripts/solo-algorithm/shell/shell-28.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-28.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-28.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-29.md b/docs/manuscripts/solo-algorithm/shell/shell-29.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-29.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-29.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-3.md b/docs/manuscripts/solo-algorithm/shell/shell-3.md index 892193009..9a53df015 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-3.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-3.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-3.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-30.md b/docs/manuscripts/solo-algorithm/shell/shell-30.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-30.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-30.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-31.md b/docs/manuscripts/solo-algorithm/shell/shell-31.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-31.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-31.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-32.md b/docs/manuscripts/solo-algorithm/shell/shell-32.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-32.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-32.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-33.md b/docs/manuscripts/solo-algorithm/shell/shell-33.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-33.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-33.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-34.md b/docs/manuscripts/solo-algorithm/shell/shell-34.md index 0a6702fab..fa794d1ee 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-34.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-34.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-4.md b/docs/manuscripts/solo-algorithm/shell/shell-4.md index 7c49c3dcc..e48228225 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-4.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-4.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-4.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-5.md b/docs/manuscripts/solo-algorithm/shell/shell-5.md index a619f812c..96339b190 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-5.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-5.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-5.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-6.md b/docs/manuscripts/solo-algorithm/shell/shell-6.md index f89bfb686..773ce5f88 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-6.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-6.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-7.md b/docs/manuscripts/solo-algorithm/shell/shell-7.md index 4115fbea9..f842ef785 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-7.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-7.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-8.md b/docs/manuscripts/solo-algorithm/shell/shell-8.md index f89bfb686..773ce5f88 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-8.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-8.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/solo-algorithm/shell/shell-9.md b/docs/manuscripts/solo-algorithm/shell/shell-9.md index f89bfb686..773ce5f88 100644 --- a/docs/manuscripts/solo-algorithm/shell/shell-9.md +++ b/docs/manuscripts/solo-algorithm/shell/shell-9.md @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code bash](@code/algorithm/shell/shell-1.sh) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\344\272\214\345\210\206\346\237\245\346\211\276/getNumberOfK.md" "b/docs/manuscripts/solo-algorithm/sword-point/\344\272\214\345\210\206\346\237\245\346\211\276/getNumberOfK.md" index 8dbe43372..3d900af2d 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\344\272\214\345\210\206\346\237\245\346\211\276/getNumberOfK.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\344\272\214\345\210\206\346\237\245\346\211\276/getNumberOfK.md" @@ -90,19 +90,19 @@ module.exports = { -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/firstAppearingOnce.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\344\272\214\345\210\206\346\237\245\346\211\276/minNumberInRotateArray.md" "b/docs/manuscripts/solo-algorithm/sword-point/\344\272\214\345\210\206\346\237\245\346\211\276/minNumberInRotateArray.md" index cf9d42abd..6df816cd2 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\344\272\214\345\210\206\346\237\245\346\211\276/minNumberInRotateArray.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\344\272\214\345\210\206\346\237\245\346\211\276/minNumberInRotateArray.md" @@ -1,11 +1,11 @@ # 算法相关文档格式模版 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba) - [欢迎讨论]() -### 题目描述 +## 题目描述 有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。 @@ -23,11 +23,11 @@ 1 ``` -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/二分查找/minNumberInRotateArray.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\344\275\215\350\277\220\347\256\227/findNumsAppearOnce.md" "b/docs/manuscripts/solo-algorithm/sword-point/\344\275\215\350\277\220\347\256\227/findNumsAppearOnce.md" index 1cb8f4825..bb64731f8 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\344\275\215\350\277\220\347\256\227/findNumsAppearOnce.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\344\275\215\350\277\220\347\256\227/findNumsAppearOnce.md" @@ -1,11 +1,11 @@ # 数组中只出现一次的两个数字 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/389fc1c3d3be4479a154f63f495abff8) - [欢迎讨论]() -### 题目描述 +## 题目描述 一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。 @@ -26,7 +26,7 @@ #返回的结果中较小的数排在前面 ``` -### 思路 +## 刷题思路 方案一: - 对数组进行排序(升序) @@ -43,12 +43,12 @@ **异或运算不常用,有点难度** -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/位运算/findNumsAppearOnce.js) -### 一些建议 +## 一些建议 - 熟练使用基本数组排序 - Map结构重要,常用api需要熟悉 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\344\275\215\350\277\220\347\256\227/numberOf1.md" "b/docs/manuscripts/solo-algorithm/sword-point/\344\275\215\350\277\220\347\256\227/numberOf1.md" index ff6aa637b..65be6154a 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\344\275\215\350\277\220\347\256\227/numberOf1.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\344\275\215\350\277\220\347\256\227/numberOf1.md" @@ -17,19 +17,19 @@ function NumberOf1 (n) { -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/firstAppearingOnce.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/add.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/add.md" index 3d5808002..ecf4c153f 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/add.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/add.md" @@ -1,11 +1,11 @@ # 不用加减乘除做加法 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215) - [欢迎讨论]() -### 题目描述 +## 题目描述 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。 @@ -13,7 +13,7 @@ 进阶:空间复杂度O(1),时间复杂度O(1) -### 思路 +## 刷题思路 方案一: 利用自增 @@ -41,12 +41,12 @@ 即:**函数的第一个参数接受不进位的操作结果,第二个参数接受进位操作的结果** -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/其他相关/add.js) -### 一些建议 +## 一些建议 - 注意ES6中位运算,左移`<<` ,右移`>>` - 循环的写法 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/isContinuous.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/isContinuous.md" index 2cfed168a..4dc20d23b 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/isContinuous.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/isContinuous.md" @@ -3,18 +3,18 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/其他相关/isContinuous.js) @@ -22,4 +22,4 @@ ``` -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/strToInt.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/strToInt.md" index 2a20155f3..5ce6c55dc 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/strToInt.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\205\266\344\273\226\347\233\270\345\205\263/strToInt.md" @@ -1,19 +1,19 @@ # 算法相关文档格式模版 -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/其他相关/strToInt.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\210\206\346\262\273/power.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\210\206\346\262\273/power.md" index 91b968ab2..0774c11b6 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\210\206\346\262\273/power.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\210\206\346\262\273/power.md" @@ -4,18 +4,18 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/分治/power.js) @@ -52,4 +52,4 @@ function Power(base, exponent) { ``` -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/fibonacci.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/fibonacci.md" index 6bf8adf99..c702624f0 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/fibonacci.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/fibonacci.md" @@ -3,12 +3,12 @@ -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3) - [欢迎讨论]() -### 题目描述 +## 题目描述 大家都知道斐波那契数列,现在要求输入一个正整数 n ,请你输出斐波那契数列的第 n 项。 斐波那契数列是一个满足: @@ -20,17 +20,17 @@ 数据范围: 1≤n≤40 要求:空间复杂度O(1),时间复杂度O(n) ,本题也有时间复杂度O(logn) 的解法 -### 思路 +## 刷题思路 方案一:递归 方案二:动态规划、循环迭代 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/动态规划/fibonacci.js) -### 一些建议 +## 一些建议 - 熟记斐波那契数列特性 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/findGreatestSumOfSubArray.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/findGreatestSumOfSubArray.md" index 65f2592f1..878edeb02 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/findGreatestSumOfSubArray.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/findGreatestSumOfSubArray.md" @@ -1,19 +1,19 @@ # 连续子数组的最大和 -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/动态规划/findGreatestSumOfSubArray.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/getUglyNumber.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/getUglyNumber.md" index b4201e221..8644c1ebb 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/getUglyNumber.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/getUglyNumber.md" @@ -1,18 +1,18 @@ # 丑数 -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/动态规划/getUglyNumber.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloor.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloor.md" index 0af982897..627647ba9 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloor.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloor.md" @@ -1,19 +1,19 @@ # 跳台阶 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4) - [欢迎讨论]() -### 题目描述 +## 题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。 数据范围:1≤n≤40 要求:时间复杂度:O(n) ,空间复杂度:O(1) -### 思路 +## 刷题思路 总共有n个台阶,假如 @@ -26,12 +26,12 @@ 这不就是斐波那契数列么???? 可以参考:[【入门】斐波那契数列](./fibonacci.md) -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/动态规划/jumpFloor.js) -### 一些建议 +## 一些建议 - 数学分析、逻辑思维很重要 - 熟练斐波那契数列 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloorII.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloorII.md" index 084b82570..6a3be7ef5 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloorII.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/jumpFloorII.md" @@ -1,12 +1,12 @@ # 跳台阶扩展问题 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387) - [欢迎讨论]() -### 题目描述 +## 题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。 @@ -14,7 +14,7 @@ 进阶:空间复杂度O(1) , 时间复杂度O(1) -### 思路 +## 刷题思路 - 我tm跳1 还剩下n-1阶 记作 G(n-1) 可选 @@ -32,12 +32,12 @@ 总结:按照推论,结果为`2的n-1幂` -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/动态规划/jumpFloorII.js) -### 一些建议 +## 一些建议 数学分析推论很重要,本身就是一种方法;对于幂的运算,熟练使用Math对象的api方法,也非常建议使用左移运算,例如: diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/multiply.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/multiply.md" index 6e42d8f2b..c54b4fa76 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/multiply.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/multiply.md" @@ -3,17 +3,17 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/动态规划/multiply.js) @@ -43,4 +43,4 @@ console.log(multiply([1, 2, 3, 4, 5])) ``` -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/rectCover.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/rectCover.md" index a929b1309..7f90da491 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/rectCover.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\212\250\346\200\201\350\247\204\345\210\222/rectCover.md" @@ -5,39 +5,39 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/firstAppearingOnce.js) -### 一些建议 +## 一些建议 # 矩形覆盖 -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/动态规划/rectCover.js) @@ -73,4 +73,4 @@ function rectCover(number) { ``` -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/findContinuousSequence.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/findContinuousSequence.md" index 618074ad2..41f09c5d0 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/findContinuousSequence.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/findContinuousSequence.md" @@ -3,18 +3,18 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/双指针/findContinuousSequence.js) @@ -58,4 +58,4 @@ function FindContinuousSequence(sum) { ``` -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/findNumbersWithSum.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/findNumbersWithSum.md" index e03ba04f2..7613c1834 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/findNumbersWithSum.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/findNumbersWithSum.md" @@ -1,15 +1,15 @@ # 和为S的两个数字 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b) - [欢迎讨论]() -### 题目描述 +## 题目描述 输入一个升序数组 array 和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,返回任意一组即可,如果无法找出这样的数字,返回一个空数组即可。 -### 思路 +## 刷题思路 一些基本数学知识: - 根据`x+y=sum` 求`xy`最小 由于array是递增的,则`x` 、`y` 距离越远 xy值越小,x y距离越近xy值越大 @@ -33,12 +33,12 @@ - 直接暴力解法 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/双指针/findNumbersWithSum.js) -### 一些建议 +## 一些建议 - 了解二分(折半)查找的原理 - 熟练掌握Map数据结构的一些api方案 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/leftRotateString.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/leftRotateString.md" index b5921fff5..3617cec91 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/leftRotateString.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/leftRotateString.md" @@ -3,18 +3,18 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/双指针/leftRotateString.js) @@ -67,4 +67,4 @@ console.log(LeftRotateString('', 6)) ``` -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/reverseSentence.md" "b/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/reverseSentence.md" index 86307a0b0..f393b8a4f 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/reverseSentence.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\345\217\214\346\214\207\351\222\210/reverseSentence.md" @@ -4,18 +4,18 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/双指针/reverseSentence.js) @@ -62,4 +62,4 @@ console.log(ReverseSentence01('nowcoder. a am I')) ``` -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\216\222\345\210\227/printMinNumber.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\216\222\345\210\227/printMinNumber.md" index 2a8a0e013..ec9534953 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\216\222\345\210\227/printMinNumber.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\216\222\345\210\227/printMinNumber.md" @@ -5,18 +5,18 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/排列/printMinNumber.js) @@ -40,4 +40,4 @@ function PrintMinNumber(numbers) { ``` -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\216\222\345\210\227/reOrderArray.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\216\222\345\210\227/reOrderArray.md" index 6949dfec1..ef2fcd189 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\216\222\345\210\227/reOrderArray.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\216\222\345\210\227/reOrderArray.md" @@ -3,18 +3,18 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/排列/reOrderArray.js) @@ -43,4 +43,4 @@ function reOrderArray(array) { ``` -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/lastRemaining.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/lastRemaining.md" index ba2ab9a7d..39e16751e 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/lastRemaining.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/lastRemaining.md" @@ -4,12 +4,12 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 把n个人的编号改为0~n-1,然后对删除的过程进行分析。 第一个删除的数字是(m-1)%n,几位k,则剩余的编号为(0,1,...,k-1,k+1,...,n-1),下次开始删除时,顺序为(k+1,...,n-1,0,1,...k-1)。 @@ -32,13 +32,13 @@ f(1,m) = 0; (n=1) f(n,m)=(f(n-1,m)+m)%n; (n>1) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数学/lastRemaining.js) -```javascript +```js /* * @Description: 【中等】圆圈中最后剩下的数 约瑟夫问题 @@ -90,4 +90,4 @@ function LastRemainingSSolution01(n, m) { ``` -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/moreThanHalfNum.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/moreThanHalfNum.md" index 771538823..15195df82 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/moreThanHalfNum.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/moreThanHalfNum.md" @@ -1,12 +1,12 @@ # 数组中出现次数超过一半的数字 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163) - [欢迎讨论]() -### 题目描述 +## 题目描述 给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。 @@ -23,7 +23,7 @@ 2 ``` -### 思路 +## 刷题思路 方案一: - 利用Map计数 @@ -40,11 +40,11 @@ - 遍历数组,判断该数出现是否超过半数 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数学/moreThanHalfNum.js) -### 一些建议 +## 一些建议 - 理解Map数据结构,熟练使用api \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/numberOf1Between1AndN.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/numberOf1Between1AndN.md" index c482195f7..a77544ba0 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/numberOf1Between1AndN.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\345\255\246/numberOf1Between1AndN.md" @@ -1,12 +1,12 @@ # 从1到n整数中1出现的次数 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6) - [欢迎讨论]() -### 题目描述 +## 题目描述 输入一个整数 n ,求 1~n 这 n 个整数的十进制表示中 1 出现的次数 例如, 1~13 中包含 1 的数字有 1 、 10 、 11 、 12 、 13 因此共出现 6 次 @@ -16,7 +16,7 @@ 数据范围:1≤n≤30000 进阶:空间复杂度O(1) ,时间复杂度O(lognn) -### 思路 +## 刷题思路 方案一: - 转化为字符串,再遍历计数,属于投机方法 @@ -24,12 +24,12 @@ 方案二: - 数学方法,对进位、余数进行处理 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数组和矩阵/FirstNotRepeatingChar.js) -### 一些建议 +## 一些建议 - 需要特别注意Math的一些操作方法 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/duplicate.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/duplicate.md" index eb782303e..dc5187da9 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/duplicate.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/duplicate.md" @@ -2,12 +2,12 @@ -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/6fe361ede7e54db1b84adc81d09d8524) - [欢迎讨论](https://github.com/142vip/JavaScriptCollection/issues/19) -### 题目描述 +## 题目描述 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1 @@ -15,10 +15,10 @@ 进阶:时间复杂度O(n) ,空间复杂度 O(n) -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数组和矩阵/duplicate.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/find.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/find.md" index 1a1d62f01..2df72c81c 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/find.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/find.md" @@ -1,11 +1,11 @@ # 二维数组中的查找 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e) - [欢迎讨论](https://github.com/142vip/JavaScriptCollection/issues/20) -### 题目描述 +## 题目描述 在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 @@ -23,10 +23,10 @@ - 给定 target = 3,返回 false。 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数组和矩阵/find.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/firstNotRepeatingChar.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/firstNotRepeatingChar.md" index 8fcc0143f..584f646fb 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/firstNotRepeatingChar.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/firstNotRepeatingChar.md" @@ -1,11 +1,11 @@ # 第一个只出现一次的字符 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c) - [欢迎讨论]() -### 题目描述 +## 题目描述 在一个长为 字符串中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数) @@ -13,7 +13,7 @@ 要求:空间复杂度O(n),时间复杂度O(n) -### 思路 +## 刷题思路 方法一: - 使用hashMap数据不重复特性,来标记字母出现的位置,存在则返回角标index @@ -25,11 +25,11 @@ -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数组和矩阵/FirstNotRepeatingChar.js) -### 一些建议 +## 一些建议 - 熟练使用split、lastIndexOf、indexOf方法 - 熟练操作Map对象 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/printMatrix.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/printMatrix.md" index 533e7998e..88f85a7d8 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/printMatrix.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/printMatrix.md" @@ -3,12 +3,12 @@ -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a) - [欢迎讨论]() -### 题目描述 +## 题目描述 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: ```bash @@ -24,12 +24,12 @@ ``` 数据范围: `0 <= matrix.length <= 100` 、 `0 <= matrix[i].length <= 100` -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数组和矩阵/printMatrix.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/replaceSpace.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/replaceSpace.md" index e55177a7a..1e2081277 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/replaceSpace.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265/replaceSpace.md" @@ -4,12 +4,12 @@ -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/0e26e5551f2b489b9f58bc83aa4b6c68) - [欢迎讨论]() -### 题目描述 +## 题目描述 请实现一个函数,将一个字符串s中的每个空格替换成“%20”。 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 @@ -27,16 +27,16 @@ ``` -### 思路 +## 刷题思路 - 方案一: 使用字符串、数组的api方法,先split进行` `切割,在使用join进行`%20`连接成字符串 - 方案二: 利用循环进行拼接,对于多拼的`%20`进行切割 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数组和矩阵/replaceSpace.js) -### 一些建议 +## 一些建议 - 熟练使用字符串和数组的api方法 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/firstAppearingOnce.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/firstAppearingOnce.md" index 47e910c11..5d6e9f6be 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/firstAppearingOnce.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/firstAppearingOnce.md" @@ -5,22 +5,22 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/firstAppearingOnce.js) -```javascript +```js /* * @Description: 【中等】字符流中的第一个不重复的字符 @@ -60,5 +60,5 @@ function FirstAppearingOnce() { ``` -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/getLeastNumbers.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/getLeastNumbers.md" index edef5f0ce..47392ff3f 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/getLeastNumbers.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/getLeastNumbers.md" @@ -1,11 +1,11 @@ # 最小的k个数 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf) - [欢迎讨论]() -### 题目描述 +## 题目描述 给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。 @@ -26,7 +26,7 @@ #返回最小的4个数即可,返回[1,3,2,4]也可以 ``` -### 思路 +## 刷题思路 方案一: @@ -56,12 +56,12 @@ -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/getLeastNumbers.js) -### 一些建议 +## 一些建议 - 熟练掌握常见排序算法,尤其是:快速排序,很实用 - 大小根堆概念要熟知 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/getMinInJSStack.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/getMinInJSStack.md" index 5279058d2..09910416d 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/getMinInJSStack.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/getMinInJSStack.md" @@ -2,22 +2,22 @@ # 包含min函数的栈 -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/getMinInJSStack.js) -```javascript +```js /* * @Description: * @Version: Beta1.0 @@ -51,4 +51,4 @@ function min() { ``` -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/insertAndGetMedian.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/insertAndGetMedian.md" index fa93fd0a5..afe352df4 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/insertAndGetMedian.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/insertAndGetMedian.md" @@ -3,20 +3,20 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/insertAndGetMedian.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/maxInWindows.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/maxInWindows.md" index 20e3b1cae..d1c2387dd 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/maxInWindows.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/maxInWindows.md" @@ -3,21 +3,21 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/maxInWindows.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/stackToQueue.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/stackToQueue.md" index 7a07034ae..2de2d0306 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/stackToQueue.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\210\351\230\237\345\210\227\345\240\206/stackToQueue.md" @@ -3,12 +3,12 @@ -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6) - [欢迎讨论]() -### 题目描述 +## 题目描述 用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。 @@ -30,7 +30,7 @@ ``` -### 思路 +## 刷题思路 这个题目只需要理解栈和队列的基础特性,即: @@ -40,12 +40,12 @@ 可用通过操作全局数组来实现栈、队列的相关操作 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/栈队列堆/stackToQueue.js) -### 一些建议 +## 一些建议 - 理解栈、队列的基本特性 - 熟练使用数组的api接口,例如:push、pop、unshift、shift等 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/findPath.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/findPath.md" index 7440e3a26..5332500af 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/findPath.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/findPath.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/树/findPath.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/getNext.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/getNext.md" index 832f8053d..feba1f713 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/getNext.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/getNext.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/树/getNext.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/hasSubtree.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/hasSubtree.md" index 7d40260d7..52bce65fa 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/hasSubtree.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/hasSubtree.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/树/hasSubTree.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/isSymmetrical.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/isSymmetrical.md" index b8a55f99c..f4e4728b1 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/isSymmetrical.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/isSymmetrical.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/树/isSymmetrical.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/ktheNode.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/ktheNode.md" index decb28d30..b8f41d1df 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/ktheNode.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/ktheNode.md" @@ -1,18 +1,18 @@ # 算法相关文档格式模版 -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/树/kTheNode.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/mirror.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/mirror.md" index 299da500c..61dfe4480 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/mirror.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/mirror.md" @@ -1,18 +1,18 @@ # 算法相关文档格式模版 -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/树/mirror.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/print.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/print.md" index 08a060063..eda2070e6 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/print.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/print.md" @@ -1,19 +1,19 @@ # 算法相关文档格式模版 -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/树/print.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/reConstructBinaryTree.md" "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/reConstructBinaryTree.md" index 75482647d..5cd60d377 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/reConstructBinaryTree.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\346\240\221/reConstructBinaryTree.md" @@ -4,19 +4,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/树/reConstructBinaryTree.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\350\264\252\345\277\203\346\200\235\346\203\263/cutRope.md" "b/docs/manuscripts/solo-algorithm/sword-point/\350\264\252\345\277\203\346\200\235\346\203\263/cutRope.md" index f34966166..ae0a688e2 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\350\264\252\345\277\203\346\200\235\346\203\263/cutRope.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\350\264\252\345\277\203\346\200\235\346\203\263/cutRope.md" @@ -1,12 +1,12 @@ # 剪绳子 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/57d85990ba5b440ab888fc72b0751bf8) - [欢迎讨论]() -### 题目描述 +## 题目描述 给你一根长度为 n 的绳子,请把绳子剪成整数长的 m 段( m 、 n 都是整数, n > 1 并且 m > 1 , m <= n ),每段绳子的长度记为 k[1],...,k[m] 。请问 k[1]*k[2]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是 8 时,我们把它剪成长度分别为 2、3、3 的三段,此时得到的最大乘积是 18 。 @@ -25,14 +25,14 @@ 8 = 2 +3 +3 , 2*3*3=18 ``` -### 思路 +## 刷题思路 - 贪心思想 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/贪心思想/cutRope.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\350\264\252\345\277\203\346\200\235\346\203\263/maxProfit.md" "b/docs/manuscripts/solo-algorithm/sword-point/\350\264\252\345\277\203\346\200\235\346\203\263/maxProfit.md" index 65237ee92..65e1817d5 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\350\264\252\345\277\203\346\200\235\346\203\263/maxProfit.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\350\264\252\345\277\203\346\200\235\346\203\263/maxProfit.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/贪心思想/maxProfit.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/deleteDuplication.md" "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/deleteDuplication.md" index 9744aa43c..6f1f412bd 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/deleteDuplication.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/deleteDuplication.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/链表/deleteDuplication.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/entryNodeOfLoop.md" "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/entryNodeOfLoop.md" index a57aafb9e..e441a0f95 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/entryNodeOfLoop.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/entryNodeOfLoop.md" @@ -4,19 +4,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/链表/entryNodeOfLoop.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/findFirstCommonNode.md" "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/findFirstCommonNode.md" index fd57c75d6..4e33a6c3f 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/findFirstCommonNode.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/findFirstCommonNode.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/链表/findFirstCommonNode.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/findKthToTail.md" "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/findKthToTail.md" index a0ce2c794..d1ba45b77 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/findKthToTail.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/findKthToTail.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/链表/findKthToTail.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/merge.md" "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/merge.md" index 3be4e628e..dd6ccae57 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/merge.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/merge.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/链表/merge.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/printListFromTailToHead.md" "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/printListFromTailToHead.md" index df0f9663f..1a6e79c2c 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/printListFromTailToHead.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/printListFromTailToHead.md" @@ -1,11 +1,11 @@ # 从尾到头打印链表 -### 题目链接 +## 题目链接 - [牛客网](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035) - [欢迎讨论]() -### 题目描述 +## 题目描述 输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。 @@ -17,7 +17,7 @@ 0 <= 链表长度 <= 10000 -### 思路 +## 刷题思路 单链表比较直接的想法就是:顺讯访问、按照链表结点循环即可,比较好的模型: @@ -30,9 +30,9 @@ while(head!=null){ ``` -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/链表/printListFromTailToHead.js) -### 一些建议 +## 一些建议 diff --git "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/reverseList.md" "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/reverseList.md" index dd4cf113c..ac02b92f8 100644 --- "a/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/reverseList.md" +++ "b/docs/manuscripts/solo-algorithm/sword-point/\351\223\276\350\241\250/reverseList.md" @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/链表/reverseList.js) -### 一些建议 +## 一些建议 diff --git a/docs/manuscripts/solo-algorithm/template.md b/docs/manuscripts/solo-algorithm/template.md index 2e713164c..65fe349ad 100644 --- a/docs/manuscripts/solo-algorithm/template.md +++ b/docs/manuscripts/solo-algorithm/template.md @@ -3,19 +3,19 @@ -### 题目链接 +## 题目链接 - [牛客网]() - [欢迎讨论]() -### 题目描述 +## 题目描述 -### 思路 +## 刷题思路 -### 代码实现 +## 代码实现 @[code js](@code/algorithm/剑指/数组和矩阵/FirstNotRepeatingChar.js) -### 一些建议 \ No newline at end of file +## 一些建议 \ No newline at end of file diff --git a/docs/manuscripts/wechat-list.md b/docs/manuscripts/wechat-list.md new file mode 100644 index 000000000..9bdde555b --- /dev/null +++ b/docs/manuscripts/wechat-list.md @@ -0,0 +1,9 @@ +--- +title: 公众号文章 +permalink: /manuscripts/wechat-list.html +--- + +# 公众号文章 + +> 有时间再整理更新吧 + diff --git "a/docs/manuscripts/other/\345\270\270\347\224\250\347\275\221\347\253\231.md" "b/docs/manuscripts/\345\270\270\347\224\250\347\275\221\347\253\231.md" similarity index 97% rename from "docs/manuscripts/other/\345\270\270\347\224\250\347\275\221\347\253\231.md" rename to "docs/manuscripts/\345\270\270\347\224\250\347\275\221\347\253\231.md" index e60d20305..07c6d1dcc 100644 --- "a/docs/manuscripts/other/\345\270\270\347\224\250\347\275\221\347\253\231.md" +++ "b/docs/manuscripts/\345\270\270\347\224\250\347\275\221\347\253\231.md" @@ -1,6 +1,6 @@ --- title: 常用网站 -permalink: /manuscripts/other/frequent-site-link.html +permalink: /manuscripts/frequent-site-link.html --- # 常用网站 diff --git a/docs/readme.md b/docs/readme.md index 766cb9b09..024a3c083 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -8,8 +8,8 @@ actions: - text: 快速开刷 → link: /quick-start.md type: primary - - text: 公众号 - link: /manuscripts/other/wechat-list.md + - text: 公众号文章💡 + link: /manuscripts/wechat-list.md type: secondary - text: 工作机会 link: /manuscripts/job-chance/job-poster-bytedance.md @@ -17,18 +17,21 @@ actions: features: - title: 前端 - details: 熟练使用前端知识,熟悉常用框架,总结提炼前端部署方案 - - title: 后端 - details: 基础扎实,熟练使用框架,有一定的封装能力,开源npm模块包,定制化插件功能,保姆级开发部署流程 - - title: 算法 - details: 习题整理,代码通过记录,个人实际解题思路,助力刷题通关 - + details: 熟练前端基础知识、常用框架,总结提炼前端部署方案 + - title: 后端&微服务 + details: 基础扎实、熟练使用框架,有一定的封装能力。造轮子、定制化插件功能,保姆级前后端开发部署流程 + - title: SOLO算法 + details: 习题整理,代码通过记录,个人实际解题思路,助力刷题通关 + - title: 开发技巧 + details: 习题整理,代码通过记录,总结刷题、解题思路,助力算法能力Up - title: 面试求职 - details: 校招社招八股文集合,分享面经与常见面试题,总结面试套路经验,对线Battle面试官有条不紊 + details: 校招社招八股文集合,分享面经与常见面试题,总结面试套路经验,对线Battle面试官有条不紊 - title: 读书写作 - details: 代码虽好但读书提升更为重要,不可偏执一端,人文社科生活百态利于提升职场人生软技能 + details: 代码虽好但读书提升更为重要,不可偏执一端。人文社科生活百态提升职场人生软技能 - title: 心路历程 - details: 从小白一路磕磕绊绊,自认并无天赋,个中心酸唯有自知; 做好当下,便是对自己的不辜负 + details: 从小白一路磕磕绊绊,自认并无天赋,个中辛酸、冷暖自知; 做好当下,便是对自己的不辜负 + - title: 自媒体 + details: 尝试尝试再尝试,等等我呀... ---