├── .github └── workflows │ └── blank.yml ├── .gitignore ├── .vscode └── settings.json ├── docs ├── .vuepress │ ├── config.js │ └── theme │ │ ├── LICENSE │ │ ├── components │ │ ├── AlgoliaSearchBox.vue │ │ ├── DropdownLink.vue │ │ ├── DropdownTransition.vue │ │ ├── Home.vue │ │ ├── NavLink.vue │ │ ├── NavLinks.vue │ │ ├── Navbar.vue │ │ ├── Page.vue │ │ ├── PageEdit.vue │ │ ├── PageNav.vue │ │ ├── Sidebar.vue │ │ ├── SidebarButton.vue │ │ ├── SidebarGroup.vue │ │ ├── SidebarLink.vue │ │ └── SidebarLinks.vue │ │ ├── global-components │ │ └── Badge.vue │ │ ├── index.js │ │ ├── layouts │ │ ├── 404.vue │ │ └── Layout.vue │ │ ├── noopModule.js │ │ ├── styles │ │ ├── arrow.styl │ │ ├── code.styl │ │ ├── config.styl │ │ ├── custom-blocks.styl │ │ ├── index.styl │ │ ├── mobile.styl │ │ ├── toc.styl │ │ └── wrapper.styl │ │ └── util │ │ └── index.js ├── JavaScript │ ├── == _===.md │ ├── BOM.md │ ├── Dom.md │ ├── ajax.md │ ├── array_api.md │ ├── bind_call_apply.md │ ├── cache.md │ ├── closure.md │ ├── context_stack.md │ ├── continue_to_upload.md │ ├── copy.md │ ├── data_type.md │ ├── debounce_throttle.md │ ├── event_Model.md │ ├── event_agent.md │ ├── event_loop.md │ ├── function_cache.md │ ├── functional_programming.md │ ├── inherit.md │ ├── js_data_structure.md │ ├── loss_accuracy.md │ ├── memory_leak.md │ ├── new.md │ ├── prototype.md │ ├── pull_up_loading_pull_down_refresh.md │ ├── regexp.md │ ├── scope.md │ ├── security.md │ ├── single_sign.md │ ├── string_api.md │ ├── tail_recursion.md │ ├── this.md │ ├── type_conversion.md │ ├── typeof_instanceof.md │ └── visible.md ├── NodeJS │ ├── Buffer.md │ ├── EventEmitter.md │ ├── Stream.md │ ├── event_loop.md │ ├── file_upload.md │ ├── fs.md │ ├── global.md │ ├── jwt.md │ ├── middleware.md │ ├── nodejs.md │ ├── paging.md │ ├── performance.md │ ├── process.md │ └── require_order.md ├── README.md ├── React │ ├── Binding events.md │ ├── Building components.md │ ├── Fiber.md │ ├── High order components.md │ ├── Improve performance.md │ ├── JSX to DOM.md │ ├── React Hooks.md │ ├── React Router model.md │ ├── React Router.md │ ├── React refs.md │ ├── React.md │ ├── Real DOM_Virtual DOM.md │ ├── Redux Middleware.md │ ├── SyntheticEvent.md │ ├── animation.md │ ├── capture error.md │ ├── class_function component.md │ ├── communication.md │ ├── controlled_Uncontrolled.md │ ├── diff.md │ ├── how to use redux.md │ ├── immutable.md │ ├── import css.md │ ├── improve_render.md │ ├── key.md │ ├── life cycle.md │ ├── redux.md │ ├── render.md │ ├── server side rendering.md │ ├── setState.md │ ├── state_props.md │ ├── summary.md │ └── super()_super(props).md ├── algorithm │ ├── Algorithm.md │ ├── BinarySearch.md │ ├── Heap.md │ ├── Linked List.md │ ├── bubbleSort.md │ ├── design1.md │ ├── design2.md │ ├── graph.md │ ├── insertionSort.md │ ├── mergeSort.md │ ├── quickSort.md │ ├── selectionSort.md │ ├── set.md │ ├── sort.md │ ├── stack_queue.md │ ├── structure.md │ ├── time_space.md │ └── tree.md ├── applet │ ├── WebView_jscore.md │ ├── applet.md │ ├── lifecycle.md │ ├── login.md │ ├── navigate.md │ ├── optimization.md │ ├── publish.md │ └── requestPayment.md ├── css │ ├── BFC.md │ ├── animation.md │ ├── box.md │ ├── center.md │ ├── column_layout.md │ ├── css3_features.md │ ├── css_performance.md │ ├── dp_px_dpr_ppi.md │ ├── em_px_rem_vh_vw.md │ ├── flexbox.md │ ├── grid.md │ ├── hide_attributes.md │ ├── layout_painting.md │ ├── less_12px.md │ ├── responsive_layout.md │ ├── sass_less_stylus.md │ ├── selector.md │ ├── single_multi_line.md │ ├── triangle.md │ └── visual_scrolling.md ├── design │ ├── Factory Pattern.md │ ├── Observer Pattern.md │ ├── Proxy Pattern.md │ ├── Singleton Pattern.md │ ├── Strategy Pattern.md │ └── design.md ├── es6 │ ├── array.md │ ├── decorator.md │ ├── function.md │ ├── generator.md │ ├── module.md │ ├── object.md │ ├── promise.md │ ├── proxy.md │ ├── set_map.md │ └── var_let_const.md ├── git │ ├── Git.md │ ├── HEAD_tree_index.md │ ├── Version control.md │ ├── command.md │ ├── conflict.md │ ├── fork_clone_branch.md │ ├── git pull _git fetch.md │ ├── git rebase_ git merge.md │ ├── git reset_ git revert.md │ └── git stash.md ├── http │ ├── 1.0_1.1_2.0.md │ ├── CDN.md │ ├── DNS.md │ ├── GET_POST.md │ ├── HTTPS.md │ ├── HTTP_HTTPS.md │ ├── OSI.md │ ├── TCP_IP.md │ ├── UDP_TCP.md │ ├── WebSocket.md │ ├── after_url.md │ ├── handshakes_waves.md │ ├── headers.md │ └── status.md ├── linux │ ├── file.md │ ├── linux users.md │ ├── linux.md │ ├── redirect_pipe.md │ ├── shell.md │ ├── thread_process.md │ └── vim.md ├── typescript │ ├── class.md │ ├── data_type.md │ ├── decorator.md │ ├── enum.md │ ├── function.md │ ├── generic.md │ ├── high type.md │ ├── interface.md │ ├── namespace_module.md │ ├── react.md │ ├── typescript_javascript.md │ └── vue.md ├── vue │ ├── 404.md │ ├── axios.md │ ├── axiosCode.md │ ├── bind.md │ ├── communication.md │ ├── components_plugin.md │ ├── cors.md │ ├── data.md │ ├── data_object_add_attrs.md │ ├── diff.md │ ├── directive.md │ ├── error.md │ ├── filter.md │ ├── first_page_time.md │ ├── if_for.md │ ├── keepalive.md │ ├── key.md │ ├── lifecycle.md │ ├── mixin.md │ ├── modifier.md │ ├── new_vue.md │ ├── nexttick.md │ ├── observable.md │ ├── permission.md │ ├── show_if.md │ ├── slot.md │ ├── spa.md │ ├── ssr.md │ ├── structure.md │ ├── vnode.md │ ├── vue.md │ └── vue3_vue2.md ├── vue3 │ ├── composition.md │ ├── goal.md │ ├── modal_component.md │ ├── performance.md │ ├── proxy.md │ └── treeshaking.md └── webpack │ ├── HMR.md │ ├── Loader.md │ ├── Loader_Plugin.md │ ├── Plugin.md │ ├── Rollup_Parcel_snowpack_Vite.md │ ├── build_process.md │ ├── improve_build.md │ ├── performance.md │ ├── proxy.md │ └── webpack.md ├── package-lock.json.bakn ├── package.json └── pnpm-lock.yaml /.github/workflows/blank.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v2 27 | 28 | - name: use Node.js 29 | # 使用action库 actions/setup-node安装node 30 | uses: actions/setup-node@v1 31 | with: 32 | node-version: 10.x 33 | # 安装依赖 34 | - name: npm install 35 | run: npm install 36 | # 打包 37 | - name: npm build 38 | run: npm run build 39 | - name: deploy 40 | uses: easingthemes/ssh-deploy@v2.1.1 41 | env: 42 | # 私钥 43 | SSH_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} 44 | SOURCE: "./dist" 45 | REMOTE_HOST: ${{ secrets.HOST }} 46 | REMOTE_USER: "root" 47 | TARGET: ${{ secrets.PATH }} 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /docs/.vuepress/dist 3 | /examples/**/build.js 4 | /test/e2e/reports 5 | /test/e2e/screenshots 6 | /types/typings 7 | /types/test/*.js 8 | *.log 9 | .DS_Store 10 | node_modules 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eggHelper.serverPort": 35684 3 | } -------------------------------------------------------------------------------- /docs/.vuepress/theme/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-present, Yuxi (Evan) You 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/DropdownTransition.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/NavLink.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 88 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/Page.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | 24 | 32 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | 28 | 65 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/SidebarButton.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 41 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/SidebarLinks.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 103 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/global-components/Badge.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 45 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // Theme API. 4 | module.exports = (options, ctx) => { 5 | const { themeConfig, siteConfig } = ctx 6 | 7 | // resolve algolia 8 | const isAlgoliaSearch = ( 9 | themeConfig.algolia 10 | || Object 11 | .keys(siteConfig.locales && themeConfig.locales || {}) 12 | .some(base => themeConfig.locales[base].algolia) 13 | ) 14 | 15 | const enableSmoothScroll = themeConfig.smoothScroll === true 16 | 17 | return { 18 | alias () { 19 | return { 20 | '@AlgoliaSearchBox': isAlgoliaSearch 21 | ? path.resolve(__dirname, 'components/AlgoliaSearchBox.vue') 22 | : path.resolve(__dirname, 'noopModule.js') 23 | } 24 | }, 25 | 26 | plugins: [ 27 | ['@vuepress/active-header-links', options.activeHeaderLinks], 28 | '@vuepress/search', 29 | '@vuepress/plugin-nprogress', 30 | ['container', { 31 | type: 'tip', 32 | defaultTitle: { 33 | '/': 'TIP', 34 | '/zh/': '提示' 35 | } 36 | }], 37 | ['container', { 38 | type: 'warning', 39 | defaultTitle: { 40 | '/': 'WARNING', 41 | '/zh/': '注意' 42 | } 43 | }], 44 | ['container', { 45 | type: 'danger', 46 | defaultTitle: { 47 | '/': 'WARNING', 48 | '/zh/': '警告' 49 | } 50 | }], 51 | ['container', { 52 | type: 'details', 53 | before: info => `
${info ? `${info}` : ''}\n`, 54 | after: () => '
\n' 55 | }], 56 | ['smooth-scroll', enableSmoothScroll] 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/404.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/noopModule.js: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/arrow.styl: -------------------------------------------------------------------------------- 1 | @require './config' 2 | 3 | .arrow 4 | display inline-block 5 | width 0 6 | height 0 7 | &.up 8 | border-left 4px solid transparent 9 | border-right 4px solid transparent 10 | border-bottom 6px solid $arrowBgColor 11 | &.down 12 | border-left 4px solid transparent 13 | border-right 4px solid transparent 14 | border-top 6px solid $arrowBgColor 15 | &.right 16 | border-top 4px solid transparent 17 | border-bottom 4px solid transparent 18 | border-left 6px solid $arrowBgColor 19 | &.left 20 | border-top 4px solid transparent 21 | border-bottom 4px solid transparent 22 | border-right 6px solid $arrowBgColor 23 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/config.styl: -------------------------------------------------------------------------------- 1 | $contentClass = '.theme-default-content' 2 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/custom-blocks.styl: -------------------------------------------------------------------------------- 1 | .custom-block 2 | .custom-block-title 3 | font-weight 600 4 | margin-bottom -0.4rem 5 | &.tip, &.warning, &.danger 6 | padding .1rem 1.5rem 7 | border-left-width .5rem 8 | border-left-style solid 9 | margin 1rem 0 10 | &.tip 11 | background-color #f3f5f7 12 | border-color #42b983 13 | &.warning 14 | background-color rgba(255,229,100,.3) 15 | border-color darken(#ffe564, 35%) 16 | color darken(#ffe564, 70%) 17 | .custom-block-title 18 | color darken(#ffe564, 50%) 19 | a 20 | color $textColor 21 | &.danger 22 | background-color #ffe6e6 23 | border-color darken(red, 20%) 24 | color darken(red, 70%) 25 | .custom-block-title 26 | color darken(red, 40%) 27 | a 28 | color $textColor 29 | &.details 30 | display block 31 | position relative 32 | border-radius 2px 33 | margin 1.6em 0 34 | padding 1.6em 35 | background-color #eee 36 | h4 37 | margin-top 0 38 | figure, p 39 | &:last-child 40 | margin-bottom 0 41 | padding-bottom 0 42 | summary 43 | outline none 44 | cursor pointer 45 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/mobile.styl: -------------------------------------------------------------------------------- 1 | @require './config' 2 | 3 | $mobileSidebarWidth = $sidebarWidth * 0.82 4 | 5 | // narrow desktop / iPad 6 | @media (max-width: $MQNarrow) 7 | .sidebar 8 | font-size 15px 9 | width $mobileSidebarWidth 10 | .page 11 | padding-left $mobileSidebarWidth 12 | 13 | // wide mobile 14 | @media (max-width: $MQMobile) 15 | .sidebar 16 | top 0 17 | padding-top $navbarHeight 18 | transform translateX(-100%) 19 | transition transform .2s ease 20 | .page 21 | padding-left 0 22 | .theme-container 23 | &.sidebar-open 24 | .sidebar 25 | transform translateX(0) 26 | &.no-navbar 27 | .sidebar 28 | padding-top: 0 29 | #ad 30 | display: none 31 | 32 | // narrow mobile 33 | @media (max-width: $MQMobileNarrow) 34 | h1 35 | font-size 1.9rem 36 | {$contentClass} 37 | div[class*="language-"] 38 | margin 0.85rem -1.5rem 39 | border-radius 0 40 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/toc.styl: -------------------------------------------------------------------------------- 1 | .table-of-contents 2 | .badge 3 | vertical-align middle 4 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/wrapper.styl: -------------------------------------------------------------------------------- 1 | $wrapper 2 | max-width $contentWidth 3 | margin 0 auto 4 | padding 2rem 2.5rem 5 | @media (max-width: $MQNarrow) 6 | padding 2rem 7 | @media (max-width: $MQMobileNarrow) 8 | padding 1.5rem 9 | 10 | -------------------------------------------------------------------------------- /docs/JavaScript/== _===.md: -------------------------------------------------------------------------------- 1 | # 面试官:== 和 ===区别,分别在什么情况使用 2 | 3 | ![](https://static.vue-js.com/51b208f0-68df-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、等于操作符 6 | 7 | 等于操作符用两个等于号( == )表示,如果操作数相等,则会返回 `true` 8 | 9 | 前面文章,我们提到在`JavaScript`中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等 10 | 11 | 遵循以下规则: 12 | 13 | 如果任一操作数是布尔值,则将其转换为数值再比较是否相等 14 | 15 | ```js 16 | let result1 = (true == 1); // true 17 | ``` 18 | 19 | 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等 20 | 21 | ```js 22 | let result1 = ("55" == 55); // true 23 | ``` 24 | 25 | 如果一个操作数是对象,另一个操作数不是,则调用对象的 `valueOf() `方法取得其原始值,再根据前面的规则进行比较 26 | 27 | ```js 28 | let obj = {valueOf:function(){return 1}} 29 | let result1 = (obj == 1); // true 30 | ``` 31 | 32 | `null `和` undefined `相等 33 | 34 | ```js 35 | let result1 = (null == undefined ); // true 36 | ``` 37 | 38 | 如果有任一操作数是 `NaN` ,则相等操作符返回 `false` 39 | 40 | ```js 41 | let result1 = (NaN == NaN ); // false 42 | ``` 43 | 44 | 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回` true ` 45 | 46 | ``` 47 | let obj1 = {name:"xxx"} 48 | let obj2 = {name:"xxx"} 49 | let result1 = (obj1 == obj2 ); // false 50 | ``` 51 | 52 | 下面进一步做个小结: 53 | 54 | - 两个都为简单类型,字符串和布尔值都会转换成数值,再比较 55 | - 简单类型与引用类型比较,对象转化成其原始类型的值,再比较 56 | 57 | - 两个都为引用类型,则比较它们是否指向同一个对象 58 | 59 | - null 和 undefined 相等 60 | - 存在 NaN 则返回 false 61 | 62 | 63 | 64 | ## 二、全等操作符 65 | 66 | 全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 `true`。即类型相同,值也需相同 67 | 68 | ```js 69 | let result1 = ("55" === 55); // false,不相等,因为数据类型不同 70 | let result2 = (55 === 55); // true,相等,因为数据类型相同值也相同 71 | ``` 72 | 73 | `undefined` 和 `null` 与自身严格相等 74 | 75 | ```js 76 | let result1 = (null === null) //true 77 | let result2 = (undefined === undefined) //true 78 | ``` 79 | 80 | 81 | 82 | ## 三、区别 83 | 84 | 相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换 85 | 86 | ```js 87 | let result1 = ("55" === 55); // false,不相等,因为数据类型不同 88 | let result2 = (55 === 55); // true,相等,因为数据类型相同值也相同 89 | ``` 90 | 91 | `null` 和 `undefined` 比较,相等操作符(==)为`true`,全等为`false` 92 | 93 | ```js 94 | let result1 = (null == undefined ); // true 95 | let result2 = (null === undefined); // false 96 | ``` 97 | 98 | 99 | 100 | ### 小结 101 | 102 | 相等运算符隐藏的类型转换,会带来一些违反直觉的结果 103 | 104 | ```js 105 | '' == '0' // false 106 | 0 == '' // true 107 | 0 == '0' // true 108 | 109 | false == 'false' // false 110 | false == '0' // true 111 | 112 | false == undefined // false 113 | false == null // false 114 | null == undefined // true 115 | 116 | ' \t\r\n' == 0 // true 117 | ``` 118 | 119 | 但在比较`null`的情况的时候,我们一般使用相等操作符`==` 120 | 121 | ```js 122 | const obj = {}; 123 | 124 | if(obj.x == null){ 125 | console.log("1"); //执行 126 | } 127 | ``` 128 | 129 | 等同于下面写法 130 | 131 | ```js 132 | if(obj.x === null || obj.x === undefined) { 133 | ... 134 | } 135 | ``` 136 | 137 | 使用相等操作符(==)的写法明显更加简洁了 138 | 139 | 所以,除了在比较对象属性为`null`或者`undefined`的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===) 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /docs/JavaScript/event_agent.md: -------------------------------------------------------------------------------- 1 | # 面试官:解释下什么是事件代理?应用场景? 2 | 3 | ![](https://static.vue-js.com/a33f0ab0-797e-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、是什么 6 | 7 | 事件代理,俗地来讲,就是把一个元素响应事件(`click`、`keydown`......)的函数委托到另一个元素 8 | 9 | 前面讲到,事件流的都会经过三个阶段: 捕获阶段 -> 目标阶段 -> 冒泡阶段,而事件委托就是在冒泡阶段完成 10 | 11 | 事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素 12 | 13 | 当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数 14 | 15 | 下面举个例子: 16 | 17 | 比如一个宿舍的同学同时快递到了,一种笨方法就是他们一个个去领取 18 | 19 | 较优方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个同学 20 | 21 | 在这里,取快递就是一个事件,每个同学指的是需要响应事件的 `DOM `元素,而出去统一领取快递的宿舍长就是代理的元素 22 | 23 | 所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个 24 | 25 | 26 | 27 | ## 二、应用场景 28 | 29 | 如果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件 30 | 31 | ```js 32 | 39 | ``` 40 | 41 | 如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的 42 | 43 | ```js 44 | // 获取目标元素 45 | const lis = document.getElementsByTagName("li") 46 | // 循环遍历绑定事件 47 | for (let i = 0; i < lis.length; i++) { 48 | lis[i].onclick = function(e){ 49 | console.log(e.target.innerHTML) 50 | } 51 | } 52 | ``` 53 | 54 | 这时候就可以事件委托,把点击事件绑定在父级元素`ul`上面,然后执行事件的时候再去匹配目标元素 55 | 56 | ```js 57 | // 给父层元素绑定事件 58 | document.getElementById('list').addEventListener('click', function (e) { 59 | // 兼容性处理 60 | var event = e || window.event; 61 | var target = event.target || event.srcElement; 62 | // 判断是否匹配目标元素 63 | if (target.nodeName.toLocaleLowerCase === 'li') { 64 | console.log('the content is: ', target.innerHTML); 65 | } 66 | }); 67 | ``` 68 | 69 | 还有一种场景是上述列表项并不多,我们给每个列表项都绑定了事件 70 | 71 | 但是如果用户能够随时动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件 72 | 73 | 如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的 74 | 75 | 举个例子: 76 | 77 | 下面`html`结构中,点击`input`可以动态添加元素 78 | 79 | ```html 80 | 81 | 87 | ``` 88 | 89 | 使用事件委托 90 | 91 | ```js 92 | const oBtn = document.getElementById("btn"); 93 | const oUl = document.getElementById("ul1"); 94 | const num = 4; 95 | 96 | //事件委托,添加的子元素也有事件 97 | oUl.onclick = function (ev) { 98 | ev = ev || window.event; 99 | const target = ev.target || ev.srcElement; 100 | if (target.nodeName.toLowerCase() == 'li') { 101 | console.log('the content is: ', target.innerHTML); 102 | } 103 | 104 | }; 105 | 106 | //添加新节点 107 | oBtn.onclick = function () { 108 | num++; 109 | const oLi = document.createElement('li'); 110 | oLi.innerHTML = `item ${num}`; 111 | oUl.appendChild(oLi); 112 | }; 113 | ``` 114 | 115 | 可以看到,使用事件委托,在动态绑定事件的情况下是可以减少很多重复工作的 116 | 117 | 118 | 119 | ## 三、总结 120 | 121 | 适合事件委托的事件有:`click`,`mousedown`,`mouseup`,`keydown`,`keyup`,`keypress` 122 | 123 | 从上面应用场景中,我们就可以看到使用事件委托存在两大优点: 124 | 125 | - 减少整个页面所需的内存,提升整体性能 126 | - 动态绑定,减少重复工作 127 | 128 | 但是使用事件委托也是存在局限性: 129 | 130 | - `focus`、`blur `这些事件没有事件冒泡机制,所以无法进行委托绑定事件 131 | 132 | - `mousemove`、`mouseout `这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的 133 | 134 | 如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件 -------------------------------------------------------------------------------- /docs/JavaScript/function_cache.md: -------------------------------------------------------------------------------- 1 | # 面试官:Javascript中如何实现函数缓存?函数缓存有哪些应用场景? 2 | 3 | ![](https://static.vue-js.com/2ae9dda0-85fa-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | 函数缓存,就是将函数运算过的结果进行缓存 10 | 11 | 本质上就是用空间(缓存存储)换时间(计算过程) 12 | 13 | 常用于缓存数据计算结果和缓存对象 14 | 15 | ```js 16 | const add = (a,b) => a+b; 17 | const calc = memoize(add); // 函数缓存 18 | calc(10,20);// 30 19 | calc(10,20);// 30 缓存 20 | ``` 21 | 22 | 缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理 23 | 24 | 25 | 26 | ## 二、如何实现 27 | 28 | 实现函数缓存主要依靠闭包、柯里化、高阶函数,这里再简单复习下: 29 | 30 | ### 闭包 31 | 32 | 闭包可以理解成,函数 + 函数体内可访问的变量总和 33 | 34 | ```js 35 | (function() { 36 | var a = 1; 37 | function add() { 38 | const b = 2 39 | let sum = b + a 40 | console.log(sum); // 3 41 | } 42 | add() 43 | })() 44 | ``` 45 | 46 | `add `函数本身,以及其内部可访问的变量,即 `a = 1 `,这两个组合在⼀起就形成了闭包 47 | 48 | 49 | 50 | ### 柯里化 51 | 52 | 把接受多个参数的函数转换成接受一个单一参数的函数 53 | 54 | ```js 55 | // 非函数柯里化 56 | var add = function (x,y) { 57 | return x+y; 58 | } 59 | add(3,4) //7 60 | 61 | // 函数柯里化 62 | var add2 = function (x) { 63 | //**返回函数** 64 | return function (y) { 65 | return x+y; 66 | } 67 | } 68 | add2(3)(4) //7 69 | ``` 70 | 71 | 将一个二元函数拆分成两个一元函数 72 | 73 | 74 | 75 | ### 高阶函数 76 | 77 | 通过接收其他函数作为参数或返回其他函数的函数 78 | 79 | ```js 80 | function foo(){ 81 | var a = 2; 82 | 83 | function bar() { 84 | console.log(a); 85 | } 86 | return bar; 87 | } 88 | var baz = foo(); 89 | baz();//2 90 | ``` 91 | 92 | 函数 `foo` 如何返回另一个函数 `bar`,`baz` 现在持有对 `foo` 中定义的`bar` 函数的引用。由于闭包特性,`a`的值能够得到 93 | 94 | 95 | 96 | 下面再看看如何实现函数缓存,实现原理也很简单,把参数和对应的结果数据存在一个对象中,调用时判断参数对应的数据是否存在,存在就返回对应的结果数据,否则就返回计算结果 97 | 98 | 如下所示 99 | 100 | ```js 101 | const memoize = function (func, content) { 102 | let cache = Object.create(null) 103 | content = content || this 104 | return (...key) => { 105 | if (!cache[key]) { 106 | cache[key] = func.apply(content, key) 107 | } 108 | return cache[key] 109 | } 110 | } 111 | ``` 112 | 113 | 调用方式也很简单 114 | 115 | ```js 116 | const calc = memoize(add); 117 | const num1 = calc(100,200) 118 | const num2 = calc(100,200) // 缓存得到的结果 119 | ``` 120 | 121 | 过程分析: 122 | 123 | - 在当前函数作用域定义了一个空对象,用于缓存运行结果 124 | - 运用柯里化返回一个函数,返回的函数由于闭包特性,可以访问到`cache` 125 | - 然后判断输入参数是不是在`cache`的中。如果已经存在,直接返回`cache`的内容,如果没有存在,使用函数`func`对输入参数求值,然后把结果存储在`cache`中 126 | 127 | 128 | 129 | ## 三、应用场景 130 | 131 | 虽然使用缓存效率是非常高的,但并不是所有场景都适用,因此千万不要极端的将所有函数都添加缓存 132 | 133 | 以下几种情况下,适合使用缓存: 134 | 135 | - 对于昂贵的函数调用,执行复杂计算的函数 136 | - 对于具有有限且高度重复输入范围的函数 137 | - 对于具有重复输入值的递归函数 138 | - 对于纯函数,即每次使用特定输入调用时返回相同输出的函数 139 | 140 | 141 | 142 | ## 参考文献 143 | 144 | - https://zhuanlan.zhihu.com/p/112505577 -------------------------------------------------------------------------------- /docs/JavaScript/js_data_structure.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你了解的js数据结构? 2 | 3 | ## 什么是数据结构? 4 | 数据结构是计算机存储、组织数据的方式。 5 | 数据结构意味着接口或封装:一个数据结构可被视为两个函数之间的接口,或者是由数据类型联合组成的存储内容的访问方法封装。 6 | 7 | 我们每天的编码中都会用到数据结构 8 | 数组是最简单的内存数据结构 9 | 下面是常见的数据结构: 10 | 1. 数组(Array) 11 | 2. 栈(Stack) 12 | 3. 队列(Queue) 13 | 4. 链表(Linked List) 14 | 5. 字典 15 | 6. 散列表(Hash table) 16 | 7. 树(Tree) 17 | 8. 图(Graph) 18 | 9. 堆(Heap) 19 | 20 | 21 | 22 | ## 数组(Array) 23 | 数组是最最基本的数据结构,很多语言都内置支持数组。 24 | 数组是使用一块连续的内存空间保存数据,保存的数据的个数在分配内存的时候就是确定的。 25 | 26 | 在日常生活中,人们经常使用列表:待办事项列表、购物清单等。 27 | 28 | 而计算机程序也在使用列表,在下面的条件下,选择列表作为数据结构就显得尤为有用: 29 | 数据结构较为简单 30 | 不需要在一个长序列中查找元素,或者对其进行排序 31 | 反之,如果数据结构非常复杂,列表的作用就没有那么大了。 32 | 33 | 34 | ## 栈(Stack) 35 | 栈是一种遵循后进先出(LIFO)原则的有序集合 36 | 在栈里,新元素都接近栈顶,旧元素都接近栈底。 37 | 每次加入新的元素和拿走元素都在顶部操作 38 | ![](https://upload-images.jianshu.io/upload_images/13253432-ddcb884374470d2c?imageMogr2/auto-orient/strip|imageView2/2/format/webp) 39 | 40 | 41 | ## 队列(Queue) 42 | 队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项 43 | 队列在尾部添加新元素,并从顶部移除元素 44 | 最新添加的元素必须排在队列的末尾 45 | ![](https://upload-images.jianshu.io/upload_images/13253432-55ad7c7db40d3796?imageMogr2/auto-orient/strip|imageView2/2/format/webp) 46 | 47 | 48 | ## 链表(Linked List) 49 | 链表也是一种列表,已经设计了数组,为什么还需要链表呢? 50 | JavaScript中数组的主要问题时,它们被实现成了对象, 51 | 与其他语言(比如C++和Java)的数组相对,效率很低。 52 | 如果你发现数组在实际使用时很慢,就可以考虑使用链表来代替它。 53 | 54 | 使用条件: 55 | 链表几乎可以用在任何可以使用一维数组的情况中。 56 | 如果需要随机访问,数组仍然是更好的选择。 57 | ![](https://raw.githubusercontent.com/zoro-web/blog/master/img/lian.jpg) 58 | 59 | ## 字典 60 | 字典是一种以键-值对存储数据的数据结构,js中的Object类就是以字典的形式设计的。JavaScript可以通过实现字典类,让这种字典类型的对象使用起来更加简单,字典可以实现对象拥有的常见功能,并相应拓展自己想要的功能,而对象在JavaScript编写中随处可见,所以字典的作用也异常明显了。 61 | 62 | 63 | ## 散列表 64 | 也称为哈希表,特点是在散列表上插入、删除和取用数据都非常快。 65 | 为什么要设计这种数据结构呢? 66 | 用数组或链表存储数据,如果想要找到其中一个数据,需要从头进行遍历,因为不知道这个数据存储到了数组的哪个位置。 67 | 68 | 散列表在JavaScript中可以基础数组去进行设计。 69 | 数组的长度是预先设定的,所有元素根据和该元素对应的键,保存在数组的特定位置,这里的键和对象的键是类型的概念。 70 | 使用散列表存储数组时,通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度。 71 | 72 | 即使使用一个高效的散列函数,依然存在将两个键映射为同一个值得可能,这种现象叫做碰撞。常见碰撞的处理方法有:开链法和线性探测法(具体概念有兴趣的可以网上自信了解) 73 | 使用条件: 74 | 可以用于数据的插入、删除和取用,不适用于查找数据 75 | ![](https://raw.githubusercontent.com/zoro-web/blog/master/img/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20170820211406.png) 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/JavaScript/new.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说new操作符具体干了什么? 2 | 3 | ![](https://static.vue-js.com/880d0010-7a39-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、是什么 6 | 7 | 在`JavaScript`中,`new`操作符用于创建一个给定构造函数的实例对象 8 | 9 | 例子 10 | ```js 11 | function Person(name, age){ 12 | this.name = name; 13 | this.age = age; 14 | } 15 | Person.prototype.sayName = function () { 16 | console.log(this.name) 17 | } 18 | const person1 = new Person('Tom', 20) 19 | console.log(person1) // Person {name: "Tom", age: 20} 20 | t.sayName() // 'Tom' 21 | ``` 22 | 23 | 从上面可以看到: 24 | 25 | - `new` 通过构造函数 `Person` 创建出来的实例可以访问到构造函数中的属性 26 | - `new` 通过构造函数 `Person` 创建出来的实例可以访问到构造函数原型链中的属性(即实例与构造函数通过原型链连接了起来) 27 | 28 | 现在在构建函数中显式加上返回值,并且这个返回值是一个原始类型 29 | 30 | ```js 31 | function Test(name) { 32 | this.name = name 33 | return 1 34 | } 35 | const t = new Test('xxx') 36 | console.log(t.name) // 'xxx' 37 | ``` 38 | 39 | 可以发现,构造函数中返回一个原始值,然而这个返回值并没有作用 40 | 41 | 下面在构造函数中返回一个对象 42 | 43 | ```js 44 | function Test(name) { 45 | this.name = name 46 | console.log(this) // Test { name: 'xxx' } 47 | return { age: 26 } 48 | } 49 | const t = new Test('xxx') 50 | console.log(t) // { age: 26 } 51 | console.log(t.name) // 'undefined' 52 | ``` 53 | 54 | 从上面可以发现,构造函数如果返回值为一个对象,那么这个返回值会被正常使用 55 | 56 | 57 | 58 | ## 二、流程 59 | 60 | 从上面介绍中,我们可以看到`new`关键字主要做了以下的工作: 61 | 62 | - 创建一个新的对象`obj` 63 | - 将对象与构建函数通过原型链连接起来 64 | - 将构建函数中的`this`绑定到新建的对象`obj`上 65 | 66 | - 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理 67 | 68 | 举个例子: 69 | 70 | ```js 71 | function Person(name, age){ 72 | this.name = name; 73 | this.age = age; 74 | } 75 | const person1 = new Person('Tom', 20) 76 | console.log(person1) // Person {name: "Tom", age: 20} 77 | t.sayName() // 'Tom' 78 | ``` 79 | 80 | 流程图如下: 81 | 82 | ![](https://static.vue-js.com/b429b990-7a39-11eb-85f6-6fac77c0c9b3.png) 83 | 84 | 85 | 86 | ## 三、手写new操作符 87 | 88 | 现在我们已经清楚地掌握了`new`的执行过程 89 | 90 | 那么我们就动手来实现一下`new` 91 | 92 | ```js 93 | function mynew(Func, ...args) { 94 | // 1.创建一个新对象 95 | const obj = {} 96 | // 2.新对象原型指向构造函数原型对象 97 | obj.__proto__ = Func.prototype 98 | // 3.将构建函数的this指向新对象 99 | let result = Func.apply(obj, args) 100 | // 4.根据返回值判断 101 | return result instanceof Object ? result : obj 102 | } 103 | ``` 104 | 105 | 测试一下 106 | 107 | ```js 108 | function mynew(func, ...args) { 109 | const obj = {} 110 | obj.__proto__ = func.prototype 111 | let result = func.apply(obj, args) 112 | return result instanceof Object ? result : obj 113 | } 114 | function Person(name, age) { 115 | this.name = name; 116 | this.age = age; 117 | } 118 | Person.prototype.say = function () { 119 | console.log(this.name) 120 | } 121 | 122 | let p = mynew(Person, "huihui", 123) 123 | console.log(p) // Person {name: "huihui", age: 123} 124 | p.say() // huihui 125 | ``` 126 | 127 | 可以发现,代码虽然很短,但是能够模拟实现`new` 128 | 129 | -------------------------------------------------------------------------------- /docs/NodeJS/nodejs.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对Node.js 的理解?优缺点?应用场景? 2 | 3 | ![](https://static.vue-js.com/b565d240-c1e6-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | `Node.js` 是一个开源与跨平台的 `JavaScript` 运行时环境 9 | 10 | 在浏览器外运行 V8 JavaScript 引擎(Google Chrome 的内核),利用事件驱动、非阻塞和异步输入输出模型等技术提高性能 11 | 12 | 可以理解为 `Node.js` 就是一个服务器端的、非阻塞式I/O的、事件驱动的`JavaScript`运行环境 13 | 14 | ### 非阻塞异步 15 | 16 | `Nodejs`采用了非阻塞型`I/O`机制,在做`I/O`操作的时候不会造成任何的阻塞,当完成之后,以时间的形式通知执行操作 17 | 18 | 例如在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率 19 | 20 | 21 | 22 | ### 事件驱动 23 | 24 | 事件驱动就是当进来一个新的请求的时,请求将会被压入一个事件队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数 25 | 26 | 比如读取一个文件,文件读取完毕后,就会触发对应的状态,然后通过对应的回调函数来进行处理 27 | 28 | ![](https://static.vue-js.com/a7729590-c1e8-11eb-ab90-d9ae814b240d.png) 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ## 二、优缺点 37 | 38 | 优点: 39 | 40 | - 处理高并发场景性能更佳 41 | - 适合I/O密集型应用,值的是应用在运行极限时,CPU占用率仍然比较低,大部分时间是在做 I/O硬盘内存读写操作 42 | 43 | 因为`Nodejs`是单线程,带来的缺点有: 44 | 45 | - 不适合CPU密集型应用 46 | - 只支持单核CPU,不能充分利用CPU 47 | - 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃 48 | 49 | 50 | 51 | 52 | 53 | ## 三、应用场景 54 | 55 | 借助`Nodejs`的特点和弊端,其应用场景分类如下: 56 | 57 | - 善于`I/O`,不善于计算。因为Nodejs是一个单线程,如果计算(同步)太多,则会阻塞这个线程 58 | - 大量并发的I/O,应用程序内部并不需要进行非常复杂的处理 59 | - 与 websocket 配合,开发长连接的实时交互应用程序 60 | 61 | 具体场景可以表现为如下: 62 | 63 | - 第一大类:用户表单收集系统、后台管理系统、实时交互系统、考试系统、联网软件、高并发量的web应用程序 64 | - 第二大类:基于web、canvas等多人联网游戏 65 | - 第三大类:基于web的多人实时聊天客户端、聊天室、图文直播 66 | - 第四大类:单页面浏览器应用程序 67 | - 第五大类:操作数据库、为前端和移动端提供基于`json`的API 68 | 69 | 其实,`Nodejs`能实现几乎一切的应用,只考虑适不适合使用它 70 | 71 | 72 | 73 | ## 参考文献 74 | 75 | - http://nodejs.cn/ 76 | - https://segmentfault.com/a/1190000019854308 77 | - https://segmentfault.com/a/1190000005173218 -------------------------------------------------------------------------------- /docs/NodeJS/paging.md: -------------------------------------------------------------------------------- 1 | # 面试官:如果让你来设计一个分页功能, 你会怎么设计? 前后端如何交互? 2 | 3 | ![](https://static.vue-js.com/54b0a390-cf14-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | 在我们做数据查询的时候,如果数据量很大,比如几万条数据,放在一个页面显示的话显然不友好,这时候就需要采用分页显示的形式,如每次只显示10条数据 10 | 11 | ![](https://static.vue-js.com/6070e8c0-cf14-11eb-85f6-6fac77c0c9b3.png) 12 | 13 | 要实现分页功能,实际上就是从结果集中显示第1~10条记录作为第1页,显示第11~20条记录作为第2页,以此类推 14 | 15 | 因此,分页实际上就是从结果集中截取出第M~N条记录 16 | 17 | 18 | ## 二、如何实现 19 | 20 | 前端实现分页功能,需要后端返回必要的数据,如总的页数,总的数据量,当前页,当前的数据 21 | 22 | ```js 23 | { 24 | "totalCount": 1836, // 总的条数 25 | "totalPages": 92, // 总页数 26 | "currentPage": 1 // 当前页数 27 | "data": [ // 当前页的数据 28 | { 29 | ... 30 | } 31 | ] 32 | ``` 33 | 34 | 后端采用`mysql`作为数据的持久性存储 35 | 36 | 前端向后端发送目标的页码`page`以及每页显示数据的数量`pageSize`,默认情况每次取10条数据,则每一条数据的起始位置`start`为: 37 | 38 | ```js 39 | const start = (page - 1) * pageSize 40 | ``` 41 | 42 | 当确定了`limit`和`start`的值后,就能够确定`SQL`语句: 43 | 44 | ```JS 45 | const sql = `SELECT * FROM record limit ${pageSize} OFFSET ${start};` 46 | ``` 47 | 48 | 上诉`SQL`语句表达的意思为:截取从`start`到`start`+`pageSize`之间(左闭右开)的数据 49 | 50 | 关于查询数据总数的`SQL`语句为,`record`为表名: 51 | 52 | ```mysql 53 | SELECT COUNT(*) FROM record 54 | ``` 55 | 56 | 因此后端的处理逻辑为: 57 | 58 | - 获取用户参数页码数page和每页显示的数目 pageSize ,其中page 是必须传递的参数,pageSize为可选参数,默认为10 59 | - 编写 SQL 语句,利用 limit 和 OFFSET 关键字进行分页查询 60 | - 查询数据库,返回总数据量、总页数、当前页、当前页数据给前端 61 | 62 | 代码如下所示: 63 | 64 | ```js 65 | router.all('/api', function (req, res, next) { 66 | var param = ''; 67 | // 获取参数 68 | if (req.method == "POST") { 69 | param = req.body; 70 | } else { 71 | param = req.query || req.params; 72 | } 73 | if (param.page == '' || param.page == null || param.page == undefined) { 74 | res.end(JSON.stringify({ msg: '请传入参数page', status: '102' })); 75 | return; 76 | } 77 | const pageSize = param.pageSize || 10; 78 | const start = (param.page - 1) * pageSize; 79 | const sql = `SELECT * FROM record limit ${pageSize} OFFSET ${start};` 80 | pool.getConnection(function (err, connection) { 81 | if (err) throw err; 82 | connection.query(sql, function (err, results) { 83 | connection.release(); 84 | if (err) { 85 | throw err 86 | } else { 87 | // 计算总页数 88 | var allCount = results[0][0]['COUNT(*)']; 89 | var allPage = parseInt(allCount) / 20; 90 | var pageStr = allPage.toString(); 91 | // 不能被整除 92 | if (pageStr.indexOf('.') > 0) { 93 | allPage = parseInt(pageStr.split('.')[0]) + 1; 94 | } 95 | var list = results[1]; 96 | res.end(JSON.stringify({ msg: '操作成功', status: '200', totalPages: allPage, currentPage: param.page, totalCount: allCount, data: list })); 97 | } 98 | }) 99 | }) 100 | }); 101 | ``` 102 | 103 | 104 | 105 | ## 三、总结 106 | 107 | 通过上面的分析,可以看到分页查询的关键在于,要首先确定每页显示的数量`pageSize`,然后根据当前页的索引`pageIndex`(从1开始),确定`LIMIT`和`OFFSET`应该设定的值: 108 | 109 | - LIMIT 总是设定为 pageSize 110 | - OFFSET 计算公式为 pageSize * (pageIndex - 1) 111 | 112 | 确定了这两个值,就能查询出第 `N`页的数据 113 | 114 | 115 | ## 参考文献 116 | 117 | - https://www.liaoxuefeng.com/wiki/1177760294764384/1217864791925600 118 | - https://vue3js.cn/interview/ 119 | -------------------------------------------------------------------------------- /docs/NodeJS/process.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说对 Node 中的 process 的理解?有哪些常用方法? 2 | 3 | ![](https://static.vue-js.com/4f7866b0-c2b2-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、是什么 6 | 7 | `process` 对象是一个全局变量,提供了有关当前 `Node.js `进程的信息并对其进行控制,作为一个全局变量 8 | 9 | 我们都知道,进程计算机系统进行资源分配和调度的基本单位,是操作系统结构的基础,是线程的容器 10 | 11 | 当我们启动一个`js`文件,实际就是开启了一个服务进程,每个进程都拥有自己的独立空间地址、数据栈,像另一个进程无法访问当前进程的变量、数据结构,只有数据通信后,进程之间才可以数据共享 12 | 13 | 由于`JavaScript`是一个单线程语言,所以通过`node xxx`启动一个文件后,只有一条主线程 14 | 15 | 16 | 17 | 18 | 19 | ## 二、属性与方法 20 | 21 | 关于`process`常见的属性有如下: 22 | 23 | - process.env:环境变量,例如通过 `process.env.NODE_ENV 获取不同环境项目配置信息 24 | - process.nextTick:这个在谈及 `EventLoop` 时经常为会提到 25 | - process.pid:获取当前进程id 26 | - process.ppid:当前进程对应的父进程 27 | - process.cwd():获取当前进程工作目录, 28 | - process.platform:获取当前进程运行的操作系统平台 29 | - process.uptime():当前进程已运行时间,例如:pm2 守护进程的 uptime 值 30 | - 进程事件: process.on(‘uncaughtException’,cb) 捕获异常信息、 process.on(‘exit’,cb)进程推出监听 31 | - 三个标准流: process.stdout 标准输出、 process.stdin 标准输入、 process.stderr 标准错误输出 32 | - process.title 指定进程名称,有的时候需要给进程指定一个名称 33 | 34 | 35 | 36 | 下面再稍微介绍下某些方法的使用: 37 | 38 | ### process.cwd() 39 | 40 | 返回当前 `Node `进程执行的目录 41 | 42 | 一个` Node` 模块 `A` 通过 NPM 发布,项目 `B` 中使用了模块 `A`。在 `A` 中需要操作 `B` 项目下的文件时,就可以用 `process.cwd()` 来获取 `B` 项目的路径 43 | 44 | 45 | 46 | ### process.argv 47 | 48 | 在终端通过 Node 执行命令的时候,通过 `process.argv` 可以获取传入的命令行参数,返回值是一个数组: 49 | 50 | - 0: Node 路径(一般用不到,直接忽略) 51 | - 1: 被执行的 JS 文件路径(一般用不到,直接忽略) 52 | - 2~n: 真实传入命令的参数 53 | 54 | 所以,我们只要从 `process.argv[2]` 开始获取就好了 55 | 56 | ```js 57 | const args = process.argv.slice(2); 58 | ``` 59 | 60 | 61 | 62 | ### process.env 63 | 64 | 返回一个对象,存储当前环境相关的所有信息,一般很少直接用到。 65 | 66 | 一般我们会在 `process.env` 上挂载一些变量标识当前的环境。比如最常见的用 `process.env.NODE_ENV` 区分 `development` 和 `production` 67 | 68 | 在 `vue-cli` 的源码中也经常会看到 `process.env.VUE_CLI_DEBUG` 标识当前是不是 `DEBUG` 模式 69 | 70 | 71 | 72 | ### process.nextTick() 73 | 74 | 我们知道`NodeJs`是基于事件轮询,在这个过程中,同一时间只会处理一件事情 75 | 76 | 在这种处理模式下,`process.nextTick()`就是定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行 77 | 78 | 例如下面例子将一个`foo`函数在下一个时间点调用 79 | 80 | ```js 81 | function foo() { 82 | console.error('foo'); 83 | } 84 | 85 | process.nextTick(foo); 86 | console.error('bar'); 87 | ``` 88 | 89 | 输出结果为`bar`、`foo` 90 | 91 | 虽然下述方式也能实现同样效果: 92 | 93 | ```js 94 | setTimeout(foo, 0); 95 | console.log('bar'); 96 | ``` 97 | 98 | 两者区别在于: 99 | 100 | - process.nextTick()会在这一次event loop的call stack清空后(下一次event loop开始前)再调用callback 101 | - setTimeout()是并不知道什么时候call stack清空的,所以何时调用callback函数是不确定的 102 | 103 | 104 | 105 | 106 | 107 | ### 参考文献 108 | 109 | - http://nodejs.cn/api/process.html 110 | - https://vue3js.cn/interview/ 111 | -------------------------------------------------------------------------------- /docs/NodeJS/require_order.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说 Node 文件查找的优先级以及 Require 方法的文件查找策略? 2 | 3 | ![](https://static.vue-js.com/15913530-c9ba-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | 7 | ## 一、模块规范 8 | 9 | `NodeJS`对`CommonJS`进行了支持和实现,让我们在开发`node`的过程中可以方便的进行模块化开发: 10 | 11 | - 在Node中每一个js文件都是一个单独的模块 12 | - 模块中包括CommonJS规范的核心变量:exports、module.exports、require 13 | - 通过上述变量进行模块化开发 14 | 15 | 而模块化的核心是导出与导入,在`Node`中通过`exports`与`module.exports`负责对模块中的内容进行导出,通过`require`函数导入其他模块(自定义模块、系统模块、第三方库模块)中的内容 16 | 17 | 18 | 19 | ## 二、查找策略 20 | 21 | `require`方法接收一下几种参数的传递: 22 | 23 | - 原生模块:http、fs、path等 24 | - 相对路径的文件模块:./mod或../mod 25 | - 绝对路径的文件模块:/pathtomodule/mod 26 | - 目录作为模块:./dirname 27 | - 非原生模块的文件模块:mod 28 | 29 | `require`参数较为简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同,如下图: 30 | 31 | ![](https://static.vue-js.com/33ae8ef0-c9ba-11eb-85f6-6fac77c0c9b3.png) 32 | 33 | 从上图可以看见,文件模块存在缓存区,寻找模块路径的时候都会优先从缓存中加载已经存在的模块 34 | 35 | 36 | 37 | ### 原生模块 38 | 39 | 而像原生模块这些,通过`require `方法在解析文件名之后,优先检查模块是否在原生模块列表中,如果在则从原生模块中加载 40 | 41 | 42 | 43 | ### 绝对路径、相对路径 44 | 45 | 如果`require`绝对路径的文件,则直接查找对应的路径,速度最快 46 | 47 | 相对路径的模块则相对于当前调用`require`的文件去查找 48 | 49 | 如果按确切的文件名没有找到模块,则 `NodeJs` 会尝试带上 `.js`、`.json `或 `.node `拓展名再加载 50 | 51 | 52 | ### 目录作为模块 53 | 54 | 默认情况是根据根目录中`package.json`文件的`main`来指定目录模块,如: 55 | 56 | ```json 57 | { "name" : "some-library", 58 | "main" : "main.js" } 59 | ``` 60 | 61 | 如果这是在` ./some-library node_modules `目录中,则 `require('./some-library')` 会试图加载 `./some-library/main.js` 62 | 63 | 如果目录里没有 `package.json`文件,或者 `main`入口不存在或无法解析,则会试图加载目录下的 `index.js` 或 `index.node` 文件 64 | 65 | 66 | 67 | 68 | 69 | ### 非原生模块 70 | 71 | 在每个文件中都存在`module.paths`,表示模块的搜索路径,`require`就是根据其来寻找文件 72 | 73 | 在`window`下输出如下: 74 | 75 | ```js 76 | [ 'c:\\nodejs\\node_modules', 77 | 'c:\\node_modules' ] 78 | ``` 79 | 80 | 可以看出`module path`的生成规则为:从当前文件目录开始查找`node_modules`目录;然后依次进入父目录,查找父目录下的`node_modules`目录,依次迭代,直到根目录下的`node_modules`目录 81 | 82 | 当都找不到的时候,则会从系统`NODE_PATH`环境变量查找 83 | 84 | #### 举个例子: 85 | 86 | 如果在`/home/ry/projects/foo.js`文件里调用了 `require('bar.js')`,则 Node.js 会按以下顺序查找: 87 | 88 | - /home/ry/projects/node_modules/bar.js 89 | - /home/ry/node_modules/bar.js 90 | - /home/node_modules/bar.js 91 | - /node_modules/bar.js 92 | 93 | 这使得程序本地化它们的依赖,避免它们产生冲突 94 | 95 | 96 | 97 | ## 三、总结 98 | 99 | 通过上面模块的文件查找策略之后,总结下文件查找的优先级: 100 | 101 | - 缓存的模块优先级最高 102 | 103 | - 如果是内置模块,则直接返回,优先级仅次缓存的模块 104 | - 如果是绝对路径 / 开头,则从根目录找 105 | - 如果是相对路径 ./开头,则从当前require文件相对位置找 106 | - 如果文件没有携带后缀,先从js、json、node按顺序查找 107 | - 如果是目录,则根据 package.json的main属性值决定目录下入口文件,默认情况为 index.js 108 | - 如果文件为第三方模块,则会引入 node_modules 文件,如果不在当前仓库文件中,则自动从上级递归查找,直到根目录 109 | 110 | 111 | 112 | ## 参考文献 113 | 114 | - http://nodejs.cn/api/modules.html#modules_file_modules 115 | - https://blog.csdn.net/qq_36801250/article/details/106352686 116 | - https://www.cnblogs.com/samve/p/10805908.html -------------------------------------------------------------------------------- /docs/React/Binding events.md: -------------------------------------------------------------------------------- 1 | # 面试官:React事件绑定的方式有哪些?区别? 2 | 3 | ![](https://static.vue-js.com/e21f5560-d8fa-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、是什么 6 | 7 | 在`react`应用中,事件名都是用小驼峰格式进行书写,例如`onclick`要改写成`onClick` 8 | 9 | 最简单的事件绑定如下: 10 | 11 | ```jsx 12 | class ShowAlert extends React.Component { 13 | showAlert() { 14 | console.log("Hi"); 15 | } 16 | 17 | render() { 18 | return ; 19 | } 20 | } 21 | ``` 22 | 23 | 从上面可以看到,事件绑定的方法需要使用`{}`包住 24 | 25 | 上述的代码看似没有问题,但是当将处理函数输出代码换成`console.log(this)`的时候,点击按钮,则会发现控制台输出`undefined` 26 | 27 | 28 | 29 | ## 二、如何绑定 30 | 31 | 为了解决上面正确输出`this`的问题,常见的绑定方式有如下: 32 | 33 | - render方法中使用bind 34 | - render方法中使用箭头函数 35 | - constructor中bind 36 | - 定义阶段使用箭头函数绑定 37 | 38 | 39 | 40 | ### render方法中使用bind 41 | 42 | 如果使用一个类组件,在其中给某个组件/元素一个`onClick`属性,它现在并会自定绑定其`this`到当前组件,解决这个问题的方法是在事件函数后使用`.bind(this)`将`this`绑定到当前组件中 43 | 44 | ```jsx 45 | class App extends React.Component { 46 | handleClick() { 47 | console.log('this > ', this); 48 | } 49 | render() { 50 | return ( 51 |
test
52 | ) 53 | } 54 | } 55 | ``` 56 | 57 | 这种方式在组件每次`render`渲染的时候,都会重新进行`bind`的操作,影响性能 58 | 59 | 60 | 61 | ### render方法中使用箭头函数 62 | 63 | 通过`ES6`的上下文来将`this`的指向绑定给当前组件,同样再每一次`render`的时候都会生成新的方法,影响性能 64 | 65 | ```jsx 66 | class App extends React.Component { 67 | handleClick() { 68 | console.log('this > ', this); 69 | } 70 | render() { 71 | return ( 72 |
this.handleClick(e)}>test
73 | ) 74 | } 75 | } 76 | ``` 77 | 78 | 79 | 80 | ## constructor中bind 81 | 82 | 在`constructor`中预先`bind`当前组件,可以避免在`render`操作中重复绑定 83 | 84 | ```jsx 85 | class App extends React.Component { 86 | constructor(props) { 87 | super(props); 88 | this.handleClick = this.handleClick.bind(this); 89 | } 90 | handleClick() { 91 | console.log('this > ', this); 92 | } 93 | render() { 94 | return ( 95 |
test
96 | ) 97 | } 98 | } 99 | ``` 100 | 101 | 102 | 103 | ### 定义阶段使用箭头函数绑定 104 | 105 | 跟上述方式三一样,能够避免在`render`操作中重复绑定,实现也非常的简单,如下: 106 | 107 | ```jsx 108 | class App extends React.Component { 109 | constructor(props) { 110 | super(props); 111 | } 112 | handleClick = () => { 113 | console.log('this > ', this); 114 | } 115 | render() { 116 | return ( 117 |
test
118 | ) 119 | } 120 | } 121 | ``` 122 | 123 | 124 | 125 | ## 三、区别 126 | 127 | 上述四种方法的方式,区别主要如下: 128 | 129 | - 编写方面:方式一、方式二写法简单,方式三的编写过于冗杂 130 | - 性能方面:方式一和方式二在每次组件render的时候都会生成新的方法实例,性能问题欠缺。若该函数作为属性值传给子组件的时候,都会导致额外的渲染。而方式三、方式四只会生成一个方法实例 131 | 132 | 综合上述,方式四是最优的事件绑定方式 133 | 134 | 135 | ## 参考文献 136 | 137 | - https://segmentfault.com/a/1190000011317515 138 | - https://vue3js.cn/interview/ -------------------------------------------------------------------------------- /docs/React/Building components.md: -------------------------------------------------------------------------------- 1 | # 面试官:React构建组件的方式有哪些?区别? 2 | 3 | ![](https://static.vue-js.com/04355cb0-da10-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | 组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式 10 | 11 | 在`React`中,一个类、一个函数都可以视为一个组件 12 | 13 | 在[之前文章](https://mp.weixin.qq.com/s/Wi0r38LBopsyQ9HesMID0g)中,我们了解到组件所存在的优势: 14 | 15 | - 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现 16 | - 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单 17 | - 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级 18 | 19 | 20 | 21 | ## 二、如何构建 22 | 23 | 在`React`目前来讲,组件的创建主要分成了三种方式: 24 | 25 | - 函数式创建 26 | - 通过 React.createClass 方法创建 27 | - 继承 React.Component 创建 28 | 29 | 30 | 31 | ### 函数式创建 32 | 33 | 在`React Hooks`出来之前,函数式组件可以视为无状态组件,只负责根据传入的`props`来展示视图,不涉及对`state`状态的操作 34 | 35 | 大多数组件可以写为无状态组件,通过简单组合构建其他组件 36 | 37 | 在`React`中,通过函数简单创建组件的示例如下: 38 | 39 | ```jsx 40 | function HelloComponent(props, /* context */) { 41 | return
Hello {props.name}
42 | } 43 | ``` 44 | 45 | 46 | 47 | 48 | 49 | ### 通过 React.createClass 方法创建 50 | 51 | `React.createClass`是react刚开始推荐的创建组件的方式,目前这种创建方式已经不怎么用了 52 | 53 | 像上述通过函数式创建的组件的方式,最终会通过`babel`转化成`React.createClass`这种形式,转化成如下: 54 | 55 | ```jsx 56 | function HelloComponent(props) /* context */{ 57 | return React.createElement( 58 | "div", 59 | null, 60 | "Hello ", 61 | props.name 62 | ); 63 | } 64 | ``` 65 | 66 | 由于上述的编写方式过于冗杂,目前基本上不使用上 67 | 68 | 69 | 70 | ### 继承 React.Component 创建 71 | 72 | 同样在`react hooks`出来之前,有状态的组件只能通过继承`React.Component`这种形式进行创建 73 | 74 | 有状态的组件也就是组件内部存在维护的数据,在类创建的方式中通过`this.state`进行访问 75 | 76 | 当调用`this.setState`修改组件的状态时,组价会再次会调用`render()`方法进行重新渲染 77 | 78 | 通过继承`React.Component`创建一个时钟示例如下: 79 | 80 | ```jsx 81 | class Timer extends React.Component { 82 | constructor(props) { 83 | super(props); 84 | this.state = { seconds: 0 }; 85 | } 86 | 87 | tick() { 88 | this.setState(state => ({ 89 | seconds: state.seconds + 1 90 | })); 91 | } 92 | 93 | componentDidMount() { 94 | this.interval = setInterval(() => this.tick(), 1000); 95 | } 96 | 97 | componentWillUnmount() { 98 | clearInterval(this.interval); 99 | } 100 | 101 | render() { 102 | return ( 103 |
104 | Seconds: {this.state.seconds} 105 |
106 | ); 107 | } 108 | } 109 | ``` 110 | 111 | 112 | 113 | ## 三、区别 114 | 115 | 由于`React.createClass `创建的方式过于冗杂,并不建议使用 116 | 117 | 而像函数式创建和类组件创建的区别主要在于需要创建的组件是否需要为有状态组件: 118 | 119 | - 对于一些无状态的组件创建,建议使用函数式创建的方式 120 | 121 | - 由于`react hooks`的出现,函数式组件创建的组件通过使用`hooks`方法也能使之成为有状态组件,再加上目前推崇函数式编程,所以这里建议都使用函数式的方式来创建组件 122 | 123 | 在考虑组件的选择原则上,能用无状态组件则用无状态组件 124 | 125 | 126 | 127 | ## 参考文献 128 | 129 | - https://react.docschina.org/ 130 | 131 | -------------------------------------------------------------------------------- /docs/React/React refs.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说对React refs 的理解?应用场景? 2 | 3 | ![](https://static.vue-js.com/25162040-de02-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、是什么 6 | `Refs` 在计算机中称为弹性文件系统(英语:Resilient File System,简称ReFS) 7 | 8 | `React` 中的 `Refs`提供了一种方式,允许我们访问 `DOM `节点或在 `render `方法中创建的 `React `元素 9 | 10 | 本质为`ReactDOM.render()`返回的组件实例,如果是渲染组件则返回的是组件实例,如果渲染`dom`则返回的是具体的`dom`节点 11 | 12 | 13 | ## 二、如何使用 14 | 15 | 创建`ref`的形式有三种: 16 | 17 | - 传入字符串,使用时通过 this.refs.传入的字符串的格式获取对应的元素 18 | - 传入对象,对象是通过 React.createRef() 方式创建出来,使用时获取到创建的对象中存在 current 属性就是对应的元素 19 | - 传入函数,该函数会在 DOM 被挂载时进行回调,这个函数会传入一个 元素对象,可以自己保存,使用时,直接拿到之前保存的元素对象即可 20 | - 传入hook,hook是通过 useRef() 方式创建,使用时通过生成hook对象的 current 属性就是对应的元素 21 | 22 | 23 | 24 | ### 传入字符串 25 | 26 | 只需要在对应元素或组件中`ref`属性 27 | 28 | ```jsx 29 | class MyComponent extends React.Component { 30 | constructor(props) { 31 | super(props); 32 | this.myRef = React.createRef(); 33 | } 34 | render() { 35 | return
; 36 | } 37 | } 38 | ``` 39 | 40 | 访问当前节点的方式如下: 41 | 42 | ```js 43 | this.refs.myref.innerHTML = "hello"; 44 | ``` 45 | 46 | 47 | ### 传入对象 48 | 49 | `refs`通过`React.createRef()`创建,然后将`ref`属性添加到`React`元素中,如下: 50 | 51 | ```jsx 52 | class MyComponent extends React.Component { 53 | constructor(props) { 54 | super(props); 55 | this.myRef = React.createRef(); 56 | } 57 | render() { 58 | return
; 59 | } 60 | } 61 | ``` 62 | 63 | 当 `ref` 被传递给 `render` 中的元素时,对该节点的引用可以在 `ref` 的 `current` 属性中访问 64 | 65 | ```js 66 | const node = this.myRef.current; 67 | ``` 68 | 69 | 70 | ### 传入函数 71 | 72 | 当`ref`传入为一个函数的时候,在渲染过程中,回调函数参数会传入一个元素对象,然后通过实例将对象进行保存 73 | 74 | ```jsx 75 | class MyComponent extends React.Component { 76 | constructor(props) { 77 | super(props); 78 | this.myRef = React.createRef(); 79 | } 80 | render() { 81 | return
this.myref = element} />; 82 | } 83 | } 84 | ``` 85 | 86 | 获取`ref`对象只需要通过先前存储的对象即可 87 | 88 | ```js 89 | const node = this.myref 90 | ``` 91 | 92 | 93 | ### 传入hook 94 | 95 | 通过`useRef`创建一个`ref`,整体使用方式与`React.createRef`一致 96 | 97 | ```jsx 98 | function App(props) { 99 | const myref = useRef() 100 | return ( 101 | <> 102 |
103 | 104 | ) 105 | } 106 | ``` 107 | 108 | 获取`ref`属性也是通过`hook`对象的`current`属性 109 | 110 | ```js 111 | const node = myref.current; 112 | ``` 113 | 114 | 上述三种情况都是`ref`属性用于原生`HTML`元素上,如果`ref`设置的组件为一个类组件的时候,`ref`对象接收到的是组件的挂载实例 115 | 116 | 注意的是,不能在函数组件上使用`ref`属性,因为他们并没有实例 117 | 118 | 119 | ## 三、应用场景 120 | 121 | 在某些情况下,我们会通过使用`refs`来更新组件,但这种方式并不推荐,更多情况我们是通过`props`与`state`的方式进行去重新渲染子元素 122 | 123 | 过多使用`refs`,会使组件的实例或者是`DOM`结构暴露,违反组件封装的原则 124 | 125 | 例如,避免在 `Dialog` 组件里暴露 `open()` 和 `close()` 方法,最好传递 `isOpen` 属性 126 | 127 | 但下面的场景使用`refs`非常有用: 128 | 129 | - 对Dom元素的焦点控制、内容选择、控制 130 | - 对Dom元素的内容设置及媒体播放 131 | - 对Dom元素的操作和对组件实例的操作 132 | - 集成第三方 DOM 库 133 | 134 | 135 | ## 参考文献 136 | 137 | - https://zh-hans.reactjs.org/docs/refs-and-the-dom.html 138 | - https://segmentfault.com/a/1190000020842342 139 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/React/React.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说对 React 的理解?有哪些特性? 2 | 3 | ![](https://static.vue-js.com/671f5a90-d265-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、是什么 6 | 7 | React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案 8 | 9 | 遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效 10 | 11 | 使用虚拟 `DOM` 来有效地操作 `DOM`,遵循从高阶组件到低阶组件的单向数据流 12 | 13 | 帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面 14 | 15 | `react` 类组件使用一个名为 `render()` 的方法或者函数组件`return`,接收输入的数据并返回需要展示的内容 16 | 17 | ```jsx 18 | class HelloMessage extends React.Component { 19 | render() { 20 | return
Hello {this.props.name}
; 21 | } 22 | } 23 | 24 | ReactDOM.render( 25 | , 26 | document.getElementById("hello-example") 27 | ); 28 | ``` 29 | 30 | 上述这种类似 `XML` 形式就是 `JSX`,最终会被 `babel` 编译为合法的 `JS` 语句调用 31 | 32 | 被传入的数据可在组件中通过 `this.props` 在 `render()` 访问 33 | 34 | ## 二、特性 35 | 36 | `React` 特性有很多,如: 37 | 38 | - JSX 语法 39 | - 单向数据绑定 40 | - 虚拟 DOM 41 | - 声明式编程 42 | - Component 43 | 44 | 着重介绍下声明式编程及 Component 45 | 46 | ### 声明式编程 47 | 48 | 声明式编程是一种编程范式,它关注的是你要做什么,而不是如何做 49 | 50 | 它表达逻辑而不显式地定义步骤。这意味着我们需要根据逻辑的计算来声明要显示的组件 51 | 52 | 如实现一个标记的地图: 53 | 54 | 通过命令式创建地图、创建标记、以及在地图上添加的标记的步骤如下: 55 | 56 | ```js 57 | // 创建地图 58 | const map = new Map.map(document.getElementById("map"), { 59 | zoom: 4, 60 | center: { lat, lng }, 61 | }); 62 | 63 | // 创建标记 64 | const marker = new Map.marker({ 65 | position: { lat, lng }, 66 | title: "Hello Marker", 67 | }); 68 | 69 | // 地图上添加标记 70 | marker.setMap(map); 71 | ``` 72 | 73 | 而用 `React` 实现上述功能则如下: 74 | 75 | ```jsx 76 | 77 | 78 | 79 | ``` 80 | 81 | 声明式编程方式使得 `React` 组件很容易使用,最终的代码简单易于维护 82 | 83 | ### Component 84 | 85 | 在 `React` 中,一切皆为组件。通常将应用程序的整个逻辑分解为小的单个部分。 我们将每个单独的部分称为组件 86 | 87 | 组件可以是一个函数或者是一个类,接受数据输入,处理它并返回在 `UI` 中呈现的 `React` 元素 88 | 89 | 函数式组件如下: 90 | 91 | ```jsx 92 | const Header = () => { 93 | return ( 94 | 95 |

TODO App

96 |
97 | ); 98 | }; 99 | ``` 100 | 101 | 类组件(有状态组件)如下: 102 | 103 | ```jsx 104 | class Dashboard extends React.Component { 105 | constructor(props) { 106 | super(props); 107 | 108 | this.state = {}; 109 | } 110 | render() { 111 | return ( 112 |
113 | 114 | 115 |
116 | ); 117 | } 118 | } 119 | ``` 120 | 121 | 一个组件该有的特点如下: 122 | 123 | - 可组合:每个组件易于和其它组件一起使用,或者嵌套在另一个组件内部 124 | - 可重用:每个组件都是具有独立功能的,它可以被使用在多个 UI 场景 125 | - 可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护 126 | 127 | ## 三、优势 128 | 129 | 通过上面的初步了解,可以感受到 `React` 存在的优势: 130 | 131 | - 高效灵活 132 | - 声明式的设计,简单使用 133 | - 组件式开发,提高代码复用率 134 | - 单向响应的数据流会比双向绑定的更安全,速度更快 135 | 136 | ## 参考文献 137 | 138 | - [https://segmentfault.com/a/1190000015924762](https://segmentfault.com/a/1190000015924762) 139 | - [https://react.docschina.org/](https://react.docschina.org/) 140 | -------------------------------------------------------------------------------- /docs/React/Real DOM_Virtual DOM.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说 Real DOM 和 Virtual DOM 的区别?优缺点? 2 | 3 | ![](https://static.vue-js.com/f1d36350-d302-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、是什么 6 | 7 | Real DOM,真实 `DOM`,意思为文档对象模型,是一个结构化文本的抽象,在页面渲染出的每一个结点都是一个真实 `DOM` 结构,如下: 8 | 9 | ![](https://static.vue-js.com/fc7ba8d0-d302-11eb-85f6-6fac77c0c9b3.png) 10 | 11 | `Virtual Dom`,本质上是以 `JavaScript` 对象形式存在的对 `DOM` 的描述 12 | 13 | 创建虚拟 `DOM` 目的就是为了更好将虚拟的节点渲染到页面视图中,虚拟 `DOM` 对象的节点与真实 `DOM` 的属性一一照应 14 | 15 | 在 `React` 中,`JSX` 是其一大特性,可以让你在 `JS` 中通过使用 `XML` 的方式去直接声明界面的 `DOM` 结构 16 | 17 | ```jsx 18 | // 创建 h1 标签,右边千万不能加引号 19 | const vDom =

Hello World

; 20 | // 找到
节点 21 | const root = document.getElementById("root"); 22 | // 把创建的 h1 标签渲染到 root 节点上 23 | ReactDOM.render(vDom, root); 24 | ``` 25 | 26 | 上述中,`ReactDOM.render()` 用于将你创建好的虚拟 `DOM` 节点插入到某个真实节点上,并渲染到页面上 27 | 28 | `JSX` 实际是一种语法糖,在使用过程中会被 `babel` 进行编译转化成 `JS` 代码,上述 `VDOM` 转化为如下: 29 | 30 | ```jsx 31 | const vDom = React.createElement( 32 | 'h1', 33 | { className: 'hClass', id: 'hId' }, 34 | 'hello world' 35 | ) 36 | ``` 37 | 38 | 可以看到,`JSX` 就是为了简化直接调用 `React.createElement()` 方法: 39 | 40 | - 第一个参数是标签名,例如 h1、span、table... 41 | 42 | - 第二个参数是个对象,里面存着标签的一些属性,例如 id、class 等 43 | 44 | - 第三个参数是节点中的文本 45 | 46 | 通过 `console.log(VDOM)`,则能够得到虚拟 `VDOM` 消息 47 | 48 | ![](https://static.vue-js.com/1716b9a0-d303-11eb-ab90-d9ae814b240d.png) 49 | 50 | 所以可以得到,`JSX` 通过 `babel` 的方式转化成 `React.createElement` 执行,返回值是一个对象,也就是虚拟 `DOM` 51 | 52 | ## 二、区别 53 | 54 | 两者的区别如下: 55 | 56 | - 虚拟 DOM 不会进行排版与重绘操作,而真实 DOM 会频繁重排与重绘 57 | - 虚拟 DOM 的总损耗是“虚拟 DOM 增删改+真实 DOM 差异增删改+排版与重绘”,真实 DOM 的总损耗是“真实 DOM 完全增删改+排版与重绘” 58 | 59 | 拿[以前文章](https://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484516&idx=1&sn=965a4ce32bf93adb9ed112922c5cb8f5&chksm=fc10c632cb674f2484fdf914d76fba55afcefca3b5adcbe6cf4b0c7fd36e29d0292e8cefceb5&scene=178&cur_album_id=1711105826272116736#rd)举过的例子: 60 | 61 | 传统的原生 `api` 或 `jQuery` 去操作 `DOM` 时,浏览器会从构建 `DOM` 树开始从头到尾执行一遍流程 62 | 63 | 当你在一次操作时,需要更新 10 个 `DOM` 节点,浏览器没这么智能,收到第一个更新 `DOM` 请求后,并不知道后续还有 9 次更新操作,因此会马上执行流程,最终执行 10 次流程 64 | 65 | 而通过 `VNode`,同样更新 10 个 `DOM` 节点,虚拟 `DOM` 不会立即操作 `DOM`,而是将这 10 次更新的 `diff` 内容保存到本地的一个 `js` 对象中,最终将这个 `js` 对象一次性 `attach` 到 `DOM` 树上,避免大量的无谓计算 66 | 67 | ## 三、优缺点 68 | 69 | 真实 `DOM` 的优势: 70 | 71 | - 易用 72 | 73 | 缺点: 74 | 75 | - 效率低,解析速度慢,内存占用量过高 76 | - 性能差:频繁操作真实 DOM,易于导致重绘与回流 77 | 78 | 使用虚拟 `DOM` 的优势如下: 79 | 80 | - 简单方便:如果使用手动操作真实 `DOM` 来完成页面,繁琐又容易出错,在大规模应用下维护起来也很困难 81 | 82 | - 性能方面:使用 Virtual DOM,能够有效避免真实 DOM 数频繁更新,减少多次引起重绘与回流,提高性能 83 | - 跨平台:React 借助虚拟 DOM,带来了跨平台的能力,一套代码多端运行 84 | 85 | 缺点: 86 | 87 | - 在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化 88 | - 首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,速度比正常稍慢 89 | 90 | ## 参考文献 91 | 92 | - [https://juejin.cn/post/6844904052971536391](https://juejin.cn/post/6844904052971536391) 93 | - [https://www.html.cn/qa/other/22832.html](https://www.html.cn/qa/other/22832.html) 94 | -------------------------------------------------------------------------------- /docs/React/capture error.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你在React项目是如何捕获错误的? 2 | 3 | ![](https://static.vue-js.com/8db1b5c0-f288-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、是什么 6 | 7 | 错误在我们日常编写代码是非常常见的 8 | 9 | 举个例子,在`react`项目中去编写组件内`JavaScript`代码错误会导致 `React` 的内部状态被破坏,导致整个应用崩溃,这是不应该出现的现象 10 | 11 | 作为一个框架,`react`也有自身对于错误的处理的解决方案 12 | 13 | 14 | ## 二、如何做 15 | 16 | 为了解决出现的错误导致整个应用崩溃的问题,`react16`引用了**错误边界**新的概念 17 | 18 | 错误边界是一种 `React` 组件,这种组件可以捕获发生在其子组件树任何位置的 `JavaScript` 错误,并打印这些错误,同时展示降级 `UI`,而并不会渲染那些发生崩溃的子组件树 19 | 20 | 错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误 21 | 22 | 形成错误边界组件的两个条件: 23 | 24 | - 使用了 static getDerivedStateFromError() 25 | - 使用了 componentDidCatch() 26 | 27 | 抛出错误后,请使用 `static getDerivedStateFromError()` 渲染备用 UI ,使用 `componentDidCatch()` 打印错误信息,如下: 28 | 29 | ```jsx 30 | class ErrorBoundary extends React.Component { 31 | constructor(props) { 32 | super(props); 33 | this.state = { hasError: false }; 34 | } 35 | 36 | static getDerivedStateFromError(error) { 37 | // 更新 state 使下一次渲染能够显示降级后的 UI 38 | return { hasError: true }; 39 | } 40 | 41 | componentDidCatch(error, errorInfo) { 42 | // 你同样可以将错误日志上报给服务器 43 | logErrorToMyService(error, errorInfo); 44 | } 45 | 46 | render() { 47 | if (this.state.hasError) { 48 | // 你可以自定义降级后的 UI 并渲染 49 | return

Something went wrong.

; 50 | } 51 | 52 | return this.props.children; 53 | } 54 | } 55 | ``` 56 | 57 | 然后就可以把自身组件的作为错误边界的子组件,如下: 58 | 59 | ```jsx 60 | 61 | 62 | 63 | ``` 64 | 65 | 下面这些情况无法捕获到异常: 66 | 67 | - 事件处理 68 | - 异步代码 69 | - 服务端渲染 70 | - 自身抛出来的错误 71 | 72 | 在`react 16`版本之后,会把渲染期间发生的所有错误打印到控制台 73 | 74 | 除了错误信息和 JavaScript 栈外,React 16 还提供了组件栈追踪。现在你可以准确地查看发生在组件树内的错误信息: 75 | 76 | ![](https://static.vue-js.com/7b2b51d0-f289-11eb-ab90-d9ae814b240d.png) 77 | 78 | 可以看到在错误信息下方文字中存在一个组件栈,便于我们追踪错误 79 | 80 | 对于错误边界无法捕获的异常,如事件处理过程中发生问题并不会捕获到,是因为其不会在渲染期间触发,并不会导致渲染时候问题 81 | 82 | 这种情况可以使用`js`的`try...catch...`语法,如下: 83 | 84 | ```jsx 85 | class MyComponent extends React.Component { 86 | constructor(props) { 87 | super(props); 88 | this.state = { error: null }; 89 | this.handleClick = this.handleClick.bind(this); 90 | } 91 | 92 | handleClick() { 93 | try { 94 | // 执行操作,如有错误则会抛出 95 | } catch (error) { 96 | this.setState({ error }); 97 | } 98 | } 99 | 100 | render() { 101 | if (this.state.error) { 102 | return

Caught an error.

103 | } 104 | return 105 | } 106 | } 107 | ``` 108 | 109 | 110 | 除此之外还可以通过监听`onerror`事件 111 | 112 | ```js 113 | window.addEventListener('error', function(event) { ... }) 114 | ``` 115 | 116 | 117 | ## 参考文献 118 | 119 | - https://zh-hans.reactjs.org/docs/error-boundaries.html -------------------------------------------------------------------------------- /docs/React/controlled_Uncontrolled.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说对受控组件和非受控组件的理解?应用场景? 2 | 3 | ![](https://static.vue-js.com/12990fd0-df2f-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | 7 | ## 一、受控组件 8 | 9 | 受控组件,简单来讲,就是受我们控制的组件,组件的状态全程响应外部数据 10 | 11 | 举个简单的例子: 12 | 13 | ```jsx 14 | class TestComponent extends React.Component { 15 | constructor (props) { 16 | super(props); 17 | this.state = { username: 'lindaidai' }; 18 | } 19 | render () { 20 | return 21 | } 22 | } 23 | ``` 24 | 25 | 这时候当我们在输入框输入内容的时候,会发现输入的内容并无法显示出来,也就是`input`标签是一个可读的状态 26 | 27 | 这是因为`value`被`this.state.username`所控制住。当用户输入新的内容时,`this.state.username`并不会自动更新,这样的话`input`内的内容也就不会变了 28 | 29 | 如果想要解除被控制,可以为`input`标签设置`onChange`事件,输入的时候触发事件函数,在函数内部实现`state`的更新,从而导致`input`框的内容页发现改变 30 | 31 | 因此,受控组件我们一般需要初始状态和一个状态更新事件函数 32 | 33 | 34 | 35 | ## 二、非受控组件 36 | 37 | 非受控组件,简单来讲,就是不受我们控制的组件 38 | 39 | 一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态 40 | 41 | 当需要时,可以使用` ref ` 查询 `DOM `并查找其当前值,如下: 42 | 43 | ```jsx 44 | import React, { Component } from 'react'; 45 | 46 | export class UnControll extends Component { 47 | constructor (props) { 48 | super(props); 49 | this.inputRef = React.createRef(); 50 | } 51 | handleSubmit = (e) => { 52 | console.log('我们可以获得input内的值为', this.inputRef.current.value); 53 | e.preventDefault(); 54 | } 55 | render () { 56 | return ( 57 |
this.handleSubmit(e)}> 58 | 59 | 60 |
61 | ) 62 | } 63 | } 64 | ``` 65 | 66 | 关于`refs`的详情使用可以参考[之前文章](https://mp.weixin.qq.com/s/ZBKWcslVBi0IKQgz7lYzbA) 67 | 68 | 69 | 70 | ## 三、应用场景 71 | 72 | 大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由`React`组件负责处理 73 | 74 | 如果选择非受控组件的话,控制能力较弱,表单数据就由`DOM`本身处理,但更加方便快捷,代码量少 75 | 76 | 针对两者的区别,其应用场景如下图所示: 77 | 78 | ![](https://static.vue-js.com/f28aed20-df2f-11eb-ab90-d9ae814b240d.png) 79 | 80 | 81 | 82 | 83 | 84 | ## 参考文献 85 | 86 | - http://meloguo.com/2018/10/08/受控与非受控组件/ 87 | - https://zhuanlan.zhihu.com/p/37579677 88 | 89 | -------------------------------------------------------------------------------- /docs/React/key.md: -------------------------------------------------------------------------------- 1 | # 面试官:React中的key有什么作用? 2 | 3 | ![](https://static.vue-js.com/31677360-dd69-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、是什么 6 | 7 | 首先,先给出`react`组件中进行列表渲染的一个示例: 8 | 9 | ```jsx 10 | const data = [ 11 | { id: 0, name: 'abc' }, 12 | { id: 1, name: 'def' }, 13 | { id: 2, name: 'ghi' }, 14 | { id: 3, name: 'jkl' } 15 | ]; 16 | 17 | const ListItem = (props) => { 18 | return
  • {props.name}
  • ; 19 | }; 20 | 21 | const List = () => { 22 | return ( 23 |
      24 | {data.map((item) => ( 25 | 26 | ))} 27 |
    28 | ); 29 | }; 30 | ``` 31 | 32 | 然后在输出就可以看到`react`所提示的警告信息: 33 | 34 | ```tex 35 | Each child in a list should have a unique "key" prop. 36 | ``` 37 | 38 | 根据意思就可以得到渲染列表的每一个子元素都应该需要一个唯一的`key`值 39 | 40 | 在这里可以使用列表的`id`属性作为`key`值以解决上面这个警告 41 | 42 | ```jsx 43 | const List = () => { 44 | return ( 45 |
      46 | {data.map((item) => ( 47 | 48 | ))} 49 |
    50 | ); 51 | }; 52 | ``` 53 | 54 | 55 | 56 | ## 二、作用 57 | 58 | 跟`Vue`一样,`React` 也存在 `Diff`算法,而元素`key`属性的作用是用于判断元素是新创建的还是被移动的元素,从而减少不必要的元素渲染 59 | 60 | 因此`key`的值需要为每一个元素赋予一个确定的标识 61 | 62 | 如果列表数据渲染中,在数据后面插入一条数据,`key`作用并不大,如下: 63 | 64 | ```jsx 65 | this.state = { 66 | numbers:[111,222,333] 67 | } 68 | 69 | insertMovie() { 70 | const newMovies = [...this.state.numbers, 444]; 71 | this.setState({ 72 | movies: newMovies 73 | }) 74 | } 75 | 76 |
      77 | { 78 | this.state.movies.map((item, index) => { 79 | return
    • {item}
    • 80 | }) 81 | } 82 |
    83 | ``` 84 | 85 | 前面的元素在`diff`算法中,前面的元素由于是完全相同的,并不会产生删除创建操作,在最后一个比较的时候,则需要插入到新的`DOM`树中 86 | 87 | 因此,在这种情况下,元素有无`key`属性意义并不大 88 | 89 | 下面再来看看在前面插入数据时,使用`key`与不使用`key`的区别: 90 | 91 | ```js 92 | insertMovie() { 93 | const newMovies = [000 ,...this.state.numbers]; 94 | this.setState({ 95 | movies: newMovies 96 | }) 97 | } 98 | ``` 99 | 100 | 当拥有`key`的时候,`react`根据`key`属性匹配原有树上的子元素以及最新树上的子元素,像上述情况只需要将000元素插入到最前面位置 101 | 102 | 当没有`key`的时候,所有的`li`标签都需要进行修改 103 | 104 | 同样,并不是拥有`key`值代表性能越高,如果说只是文本内容改变了,不写`key`反而性能和效率更高 105 | 106 | 主要是因为不写`key`是将所有的文本内容替换一下,节点不会发生变化 107 | 108 | 而写`key`则涉及到了节点的增和删,发现旧`key`不存在了,则将其删除,新`key`在之前没有,则插入,这就增加性能的开销 109 | 110 | 111 | 112 | ## 三、总结 113 | 114 | 良好使用`key`属性是性能优化的非常关键的一步,注意事项为: 115 | 116 | - key 应该是唯一的 117 | - key不要使用随机值(随机数在下一次 render 时,会重新生成一个数字) 118 | 119 | - 使用 index 作为 key值,对性能没有优化 120 | 121 | `react`判断`key`的流程具体如下图: 122 | 123 | ![](https://static.vue-js.com/3b9afe10-dd69-11eb-ab90-d9ae814b240d.png) 124 | 125 | 126 | 127 | ## 参考文献 128 | 129 | - https://zh-hans.reactjs.org/docs/lists-and-keys.html#gatsby-focus-wrapper 130 | - https://segmentfault.com/a/1190000017511836 131 | -------------------------------------------------------------------------------- /docs/React/state_props.md: -------------------------------------------------------------------------------- 1 | # 面试官:state 和 props 有什么区别? 2 | 3 | ![](https://static.vue-js.com/7f272780-d440-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、state 6 | 7 | 一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是 `state`,一般在 `constructor` 中初始化 8 | 9 | 当需要修改里面的值的状态需要通过调用 `setState` 来改变,从而达到更新组件内部数据的作用,并且重新调用组件 `render` 方法,如下面的例子: 10 | 11 | ```jsx 12 | class Button extends React.Component { 13 | constructor() { 14 | super(); 15 | this.state = { 16 | count: 0, 17 | }; 18 | } 19 | 20 | updateCount() { 21 | this.setState((prevState, props) => { 22 | return { count: prevState.count + 1 }; 23 | }); 24 | } 25 | 26 | render() { 27 | return ( 28 | 31 | ); 32 | } 33 | } 34 | ``` 35 | 36 | `setState` 还可以接受第二个参数,它是一个函数,会在 `setState` 调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成 37 | 38 | ```js 39 | this.setState( 40 | { 41 | name: "JS每日一题", 42 | }, 43 | () => console.log("setState finished") 44 | ); 45 | ``` 46 | 47 | ## 二、props 48 | 49 | `React` 的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件 50 | 51 | 组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是 `props`,所以可以把 `props` 理解为从外部传入组件内部的数据 52 | 53 | `react` 具有单向数据流的特性,所以他的主要作用是从父组件向子组件中传递数据 54 | 55 | `props` 除了可以传字符串,数字,还可以传递对象,数组甚至是回调函数,如下: 56 | 57 | ```jsx 58 | class Welcome extends React.Component { 59 | render() { 60 | return

    Hello {this.props.name}

    ; 61 | } 62 | } 63 | 64 | const element = ; 65 | ``` 66 | 67 | 上述 `name` 属性与 `onNameChanged` 方法都能在子组件的 `props` 变量中访问 68 | 69 | 在子组件中,`props` 在内部不可变的,如果想要改变它看,只能通过外部组件传入新的 `props` 来重新渲染子组件,否则子组件的 `props` 和展示形式不会改变 70 | 71 | ## 三、区别 72 | 73 | 相同点: 74 | 75 | - 两者都是 JavaScript 对象 76 | - 两者都是用于保存信息 77 | - props 和 state 都能触发渲染更新 78 | 79 | 区别: 80 | 81 | - props 是外部传递给组件的,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化 82 | - props 在组件内部是不可修改的,但 state 在组件内部可以进行修改 83 | - state 是多变的、可以修改 84 | 85 | ## 参考文献 86 | 87 | - [https://lucybain.com/blog/2016/react-state-vs-pros/](https://lucybain.com/blog/2016/react-state-vs-pros/) 88 | - [https://juejin.cn/post/6844904009203974158](https://juejin.cn/post/6844904009203974158) 89 | -------------------------------------------------------------------------------- /docs/React/super()_super(props).md: -------------------------------------------------------------------------------- 1 | # 面试官:super() 和 super(props) 有什么区别? 2 | 3 | ![](https://static.vue-js.com/618abaf0-d71c-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、ES6 类 6 | 7 | 在 `ES6` 中,通过 `extends` 关键字实现类的继承,方式如下: 8 | 9 | ```js 10 | class sup { 11 | constructor(name) { 12 | this.name = name; 13 | } 14 | 15 | printName() { 16 | console.log(this.name); 17 | } 18 | } 19 | 20 | class sub extends sup { 21 | constructor(name, age) { 22 | super(name); // super代表的事父类的构造函数 23 | this.age = age; 24 | } 25 | 26 | printAge() { 27 | console.log(this.age); 28 | } 29 | } 30 | 31 | let jack = new sub("jack", 20); 32 | jack.printName(); //输出 : jack 33 | jack.printAge(); //输出 : 20 34 | ``` 35 | 36 | 在上面的例子中,可以看到通过 `super` 关键字实现调用父类,`super` 代替的是父类的构建函数,使用 `super(name)` 相当于调用 `sup.prototype.constructor.call(this,name)` 37 | 38 | 如果在子类中不使用 `super`,关键字,则会引发报错,如下: 39 | 40 | ![](https://static.vue-js.com/6ab40190-d71c-11eb-85f6-6fac77c0c9b3.png) 41 | 42 | 报错的原因是 子类是没有自己的 `this` 对象的,它只能继承父类的 `this` 对象,然后对其进行加工 43 | 44 | 而 `super()` 就是将父类中的 `this` 对象继承给子类的,没有 `super()` 子类就得不到 `this` 对象 45 | 46 | 如果先调用 `this`,再初始化 `super()`,同样是禁止的行为 47 | 48 | ```js 49 | class sub extends sup { 50 | constructor(name, age) { 51 | this.age = age; 52 | super(name); // super代表的事父类的构造函数 53 | } 54 | } 55 | ``` 56 | 57 | 所以在子类 `constructor` 中,必须先代用 `super` 才能引用 `this` 58 | 59 | ## 二、类组件 60 | 61 | 在 `React` 中,类组件是基于 `ES6` 的规范实现的,继承 `React.Component`,因此如果用到 `constructor` 就必须写 `super()` 才初始化 `this` 62 | 63 | 这时候,在调用 `super()` 的时候,我们一般都需要传入 `props` 作为参数,如果不传进去,`React` 内部也会将其定义在组件实例中 64 | 65 | ```js 66 | // React 内部 67 | const instance = new YourComponent(props); 68 | instance.props = props; 69 | ``` 70 | 71 | 所以无论有没有 `constructor`,在 `render` 中 `this.props` 都是可以使用的,这是 `React` 自动附带的,是可以不写的: 72 | 73 | ```jsx 74 | class HelloMessage extends React.Component { 75 | render() { 76 | return
    nice to meet you! {this.props.name}
    ; 77 | } 78 | } 79 | ``` 80 | 81 | 但是也不建议使用 `super()` 代替 `super(props)` 82 | 83 | 因为在 `React` 会在类组件构造函数生成实例后再给 `this.props` 赋值,所以在不传递 `props` 在 `super` 的情况下,调用 `this.props` 为 `undefined`,如下情况: 84 | 85 | ```jsx 86 | class Button extends React.Component { 87 | constructor(props) { 88 | super(); // 没传入 props 89 | console.log(props); // {} 90 | console.log(this.props); // undefined 91 | // ... 92 | } 93 | } 94 | ``` 95 | 96 | 而传入 `props` 的则都能正常访问,确保了 `this.props` 在构造函数执行完毕之前已被赋值,更符合逻辑,如下: 97 | 98 | ```jsx 99 | class Button extends React.Component { 100 | constructor(props) { 101 | super(props); // 没传入 props 102 | console.log(props); // {} 103 | console.log(this.props); // {} 104 | // ... 105 | } 106 | } 107 | ``` 108 | 109 | ## 三、总结 110 | 111 | 在 `React` 中,类组件基于 `ES6`,所以在 `constructor` 中必须使用 `super` 112 | 113 | 在调用 `super` 过程,无论是否传入 `props`,`React` 内部都会将 `porps` 赋值给组件实例 `porps` 属性中 114 | 115 | 如果只调用了 `super()`,那么 `this.props` 在 `super()` 和构造函数结束之间仍是 `undefined` 116 | 117 | ## 参考文献 118 | 119 | - [https://overreacted.io/zh-hans/why-do-we-write-super-props/](https://overreacted.io/zh-hans/why-do-we-write-super-props/) 120 | - [https://segmentfault.com/q/1010000008340434](https://segmentfault.com/q/1010000008340434) 121 | -------------------------------------------------------------------------------- /docs/algorithm/Algorithm.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对算法的理解?应用场景? 2 | 3 | 4 | ![](https://static.vue-js.com/eca03690-1620-11ec-8e64-91fdec0f05a1.png) 5 | 6 | ## 一、是什么 7 | 8 | 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制 9 | 10 | 也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出 11 | 12 | 如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题 13 | 14 | 一个程序=算法+数据结构,数据结构是算法实现的基础,算法总是要依赖于某种数据结构来实现的,两者不可分割 15 | 16 | 因此,算法的设计和选择要同时结合数据结构,简单地说数据结构的设计就是选择存储方式,如确定问题中的信息是用数组存储还是用普通的变量存储或其他更加复杂的数据结构 17 | 18 | 针对上述,可以得出一个总结:不同的算法可能用不同的时间、空间或效率来完成同样的任务 19 | 20 | ## 二、特性 21 | 22 | 关于算法的五大特性,有如下: 23 | 24 | - 有限性(Finiteness):一个算法必须保证执行有限步之后结束 25 | - 确切性(Definiteness): 一个算法的每一步骤必须有确切的定义 26 | - 输入(Input):一个算法有零个或多个输入,以刻画运算对象的初始情况,所谓零个输入是指算法本身给定了初始条件 27 | - 输出(Output):一个算法有一个或多个输出。没有输出的算法毫无意义 28 | - 可行性(Effectiveness):算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成(也称之为有效性) 29 | 30 | 31 | ## 三、应用场景 32 | 33 | 在前端领域中,数据结构与算法无法不在,例如现在的`vue`或者`react`项目,实现虚拟`DOM`或者`Fiber`结构,本质就是一种数据结构,如下一个简单的虚拟`DOM`: 34 | 35 | ```js 36 | { 37 | type: 'div', 38 | props: { 39 | name: 'lucifer' 40 | }, 41 | children: [{ 42 | type: 'span', 43 | props: {}, 44 | children: [] 45 | }] 46 | } 47 | ``` 48 | 49 | `vue`与`react`都能基于基于对应的数据结构实现`diff`算法,提高了整个框架的性能以及拓展性 50 | 51 | 包括在前端`javascript`编译的时候,都会生成对应的抽象语法树`AST`,其本身不涉及到任何语法,因此你只要编写相应的转义规则,就可以将任何语法转义到任何语法,也是`babel`, `PostCSS`, `prettier`, `typescript` 52 | 53 | 除了这些框架或者工具底层用到算法与数据结构之外,日常业务也无处不在,例如实现一个输入框携带联想功能,如下: 54 | 55 | ![](https://static.vue-js.com/682d16c0-1621-11ec-8e64-91fdec0f05a1.png) 56 | 57 | 如果我们要实现这个功能, 则可以使用前缀树,如下: 58 | 59 | ![](https://static.vue-js.com/55a1ed50-1621-11ec-8e64-91fdec0f05a1.png) 60 | 61 | 包括前端可能会做一些对字符串进行相似度检测,例如"每日一题"和"js每日一题"两个字符串进行相似度对比,这种情况可以通过“最小编辑距离”算法,如果`a`和`b`的编辑距离越小,我们认为越相似 62 | 63 | 日常在编写任何代码的都需要一个良好的算法思维,选择好的算法或者数据结构,能让整个程序效率更高 64 | 65 | 66 | ## 参考文献 67 | 68 | - https://baike.baidu.com/item/%E7%AE%97%E6%B3%95/209025 69 | - https://lucifer.ren/blog/2019/09/18/algorthimn-fe-1/ -------------------------------------------------------------------------------- /docs/algorithm/Linked List.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对链表的理解?常见的操作有哪些? 2 | 3 | ![](https://static.vue-js.com/d6638dd0-1c76-11ec-8e64-91fdec0f05a1.png) 4 | 5 | ## 一、是什么 6 | 7 | 链表(Linked List)是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,由一系列结点(链表中每一个元素称为结点)组成 8 | 9 | 每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域 10 | 11 | ![](https://static.vue-js.com/e4e93490-1c76-11ec-8e64-91fdec0f05a1.png) 12 | 13 | 节点用代码表示,则如下: 14 | 15 | ```js 16 | class Node { 17 | constructor(val) { 18 | this.val = val; 19 | this.next = null; 20 | } 21 | } 22 | ``` 23 | 24 | - data 表示节点存放的数据 25 | - next 表示下一个节点指向的内存空间 26 | 27 | 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到`O(1)`的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是`O(logn)`和`O(1)` 28 | 29 | 链表的结构也十分多,常见的有四种形式: 30 | 31 | - 单链表:除了头节点和尾节点,其他节点只包含一个后继指针 32 | - 循环链表:跟单链表唯一的区别就在于它的尾结点又指回了链表的头结点,首尾相连,形成了一个环 33 | - 双向链表:每个结点具有两个方向指针,后继指针(next)指向后面的结点,前驱指针(prev)指向前面的结点,其中节点的前驱指针和尾结点的后继指针均指向空地址NULL 34 | - 双向循环链表:跟双向链表基本一致,不过头节点前驱指针指向尾迹诶单和尾节点的后继指针指向头节点 35 | 36 | 37 | 38 | ## 二、操作 39 | 40 | 关于链表的操作可以主要分成如下: 41 | 42 | - 遍历 43 | - 插入 44 | - 删除 45 | 46 | ### 遍历 47 | 48 | 遍历很好理解,就是根据`next`指针遍历下去,直到为`null`,如下: 49 | 50 | ```js 51 | let current = head 52 | while(current){ 53 | console.log(current.val) 54 | current = current.next 55 | } 56 | ``` 57 | 58 | ### 插入 59 | 60 | 向链表中间插入一个元素,可以如下图所示: 61 | 62 | ![](https://static.vue-js.com/f5fe5fd0-1c76-11ec-8e64-91fdec0f05a1.png) 63 | 64 | 可以看到,插入节点可以分成如下步骤: 65 | 66 | - 存储插入位置的前一个节点 67 | - 存储插入位置的后一个节点 68 | 69 | - 将插入位置的前一个节点的 next 指向插入节点 70 | - 将插入节点的 next 指向前面存储的 next 节点 71 | 72 | 相关代码如下所示: 73 | 74 | ```js 75 | let current = head 76 | while (current < position){ 77 | pervious = current; 78 | current = current.next; 79 | } 80 | pervious.next = node; 81 | node.next = current; 82 | 83 | ``` 84 | 85 | 如果在头节点进行插入操作的时候,会实现`previousNode`节点为`undefined`,不适合上述方式 86 | 87 | 解放方式可以是在头节点前面添加一个虚拟头节点,保证插入行为一致 88 | 89 | 90 | 91 | ### 删除 92 | 93 | 向链表任意位置删除节点,如下图操作: 94 | 95 | ![](https://static.vue-js.com/0160cd90-1c77-11ec-a752-75723a64e8f5.png) 96 | 97 | 从上图可以看到删除节点的步骤为如下: 98 | 99 | - 获取删除节点的前一个节点 100 | - 获取删除节点的后一个节点 101 | - 将前一个节点的 next 指向后一个节点 102 | - 向删除节点的 next 指向为null 103 | 104 | 如果想要删除制定的节点,示意代码如下: 105 | 106 | ```js 107 | while (current != node){ 108 | pervious = current; 109 | current = current.next; 110 | nextNode = current.next; 111 | } 112 | pervious.next = nextNode 113 | ``` 114 | 115 | 同样如何希望删除节点处理行为一致,可以在头节点前面添加一个虚拟头节点 116 | 117 | 118 | 119 | ## 三、应用场景 120 | 121 | 缓存是一种提高数据读取性能的技术,在硬件设计、软件开发中都有着非常广泛的应用,比如常见的`CPU`缓存、数据库缓存、浏览器缓存等等 122 | 123 | 当缓存空间被用满时,我们可能会使用`LRU`最近最好使用策略去清楚,而实现`LRU`算法的数据结构是链表,思路如下: 124 | 125 | 维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时,我们从链表头部开始顺序遍历链表 126 | 127 | - 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据的对应结点,并将其从原来的位置删除,并插入到链表头部 128 | - 如果此数据没在缓存链表中 129 | - 如果此时缓存未满,可直接在链表头部插入新节点存储此数据 130 | - 如果此时缓存已满,则删除链表尾部节点,再在链表头部插入新节点 131 | 132 | 由于链表插入删除效率极高,达到O(1)。对于不需要搜索但变动频繁且无法预知数量上限的数据的情况的时候,都可以使用链表 133 | 134 | 135 | ## 参考文献 136 | - https://zh.wikipedia.org/zh-hans/%E9%93%BE%E8%A1%A8 137 | - https://mah93.github.io/2019/07/19/js-linked/ 138 | -------------------------------------------------------------------------------- /docs/algorithm/bubbleSort.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对冒泡排序的理解?如何实现?应用场景? 2 | 3 | ![](https://static.vue-js.com/6f5e0850-2652-11ec-a752-75723a64e8f5.png) 4 | 5 | 6 | ## 一、是什么 7 | 8 | 冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法 9 | 10 | 冒泡排序的思想就是在每次遍历一遍未排序的数列之后,将一个数据元素浮上去(也就是排好了一个数据) 11 | 12 | 如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序” 13 | 14 | 假如我们要把 12、35、99、18、76 这 5 个数从大到小进行排序,那么数越大,越需要把它放在前面 15 | 16 | 思路如下: 17 | 18 | - 从后开始遍历,首先比较 18 和 76,发现 76 比 18 大,就把两个数交换顺序,得到 12、35、99、76、18 19 | - 接着比较 76 和 99,发现 76 比 99 小,所以不用交换顺序 20 | - 接着比较 99 和 35,发现 99 比 35 大,交换顺序 21 | - 接着比较 99 和 12,发现 99 比 12 大,交换顺序 22 | 23 | 最终第 1 趟排序的结果变成了 99、12、35、76、18,如下图所示: 24 | 25 | ![](https://static.vue-js.com/7a363770-2652-11ec-8e64-91fdec0f05a1.png) 26 | 27 | 上述可以看到,经过第一趟的排序,可以得到最大的元素,接下来第二趟排序则对剩下的的4个元素进行排序,如下图所示: 28 | 29 | ![](https://static.vue-js.com/84b9ddf0-2652-11ec-a752-75723a64e8f5.png) 30 | 31 | 经过第 2 趟排序,结果为 99、76、12、35、18 32 | 33 | 然后开始第3趟的排序,结果为99、76、35、12、18 34 | 35 | 然后第四趟排序结果为99、76、35、18、12 36 | 37 | 经过 4 趟排序之后,只剩一个 12 需要排序了,这时已经没有可比较的元素了,这时排序完成 38 | 39 | ## 二、如何实现 40 | 41 | 如果要实现一个从小到大的排序,算法原理如下: 42 | 43 | - 首先比较相邻的元素,如果第一个元素比第二个元素大,则交换它们 44 | - 针对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,这样,最后的元素回事最大的数 45 | - 针对所有的元素重复以上的步骤,除了最后一个 46 | - 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较 47 | 48 | ![](https://www.runoob.com/wp-content/uploads/2019/03/bubbleSort.gif) 49 | 50 | 用代码表示则如下: 51 | 52 | ```js 53 | function bubbleSort(arr) { 54 | const len = arr.length; 55 | for (let i = 0; i < len - 1; i++) { 56 | for (let j = 0; j < len - 1 - i; j++) { 57 | if (arr[j] > arr[j+1]) { // 相邻元素两两对比 58 | var temp = arr[j+1]; // 元素交换 59 | arr[j+1] = arr[j]; 60 | arr[j] = temp; 61 | } 62 | } 63 | } 64 | return arr; 65 | } 66 | ``` 67 | 68 | 可以看到:冒泡排序在每一轮排序中都会使一个元素排到一趟, 也就是最终需要 n-1 轮这样的排序 69 | 70 | 而在每轮排序中都需要对相邻的两个元素进行比较,在最坏的情况下,每次比较之后都需要交换位置,此时时间复杂度为`O(n^2)` 71 | 72 | 73 | 74 | ### 优化 75 | 76 | 对冒泡排序常见的改进方法是加入一标志性变量`exchange`,用于标志某一趟排序过程中是否有数据交换 77 | 78 | 如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程 79 | 80 | 可以设置一标志性变量`pos`,用于记录每趟排序中最后一次进行交换的位置,由于`pos`位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到`pos`位置即可,如下: 81 | 82 | ```js 83 | function bubbleSort1(arr){ 84 | const i=arr.length-1;//初始时,最后位置保持不变 85 | while(i>0){ 86 | let pos = 0;//每趟开始时,无记录交换 87 | for(let j = 0; j < i; j++){ 88 | if(arr[j] > arr[j+1]){ 89 | let tmp = arr[j]; 90 | arr[j] = arr[j+1]; 91 | arr[j+1] = tmp; 92 | pos = j;//记录最后交换的位置 93 | } 94 | } 95 | i = pos;//为下一趟排序作准备 96 | } 97 | return arr; 98 | } 99 | ``` 100 | 101 | 在待排序的数列有序的情况下,只需要一轮排序并且不用交换,此时情况最好,时间复杂度为`O(n)` 102 | 103 | 并且从上述比较中看到,只有后一个元素比前面的元素大(小)时才会对它们交换位置并向上冒出,对于同样大小的元素,是不需要交换位置的,所以对于同样大小的元素来说,相对位置是不会改变的,因此, 冒泡排序是稳定的 104 | 105 | 106 | 107 | 108 | 109 | ## 三、应用场景 110 | 冒泡排的核心部分是双重嵌套循环, 111 | 时间复杂度是 O(N 2 ),相比其它排序算法,这是一个相对较高的时间复杂度,一般情况不推荐使用,由于冒泡排序的简洁性,通常被用来对于程序设计入门的学生介绍算法的概念 112 | 113 | ## 参考文献 114 | 115 | - https://baike.baidu.com/item/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F/4602306 116 | - https://www.runoob.com/w3cnote/bubble-sort.html 117 | - http://data.biancheng.net/view/116.html 118 | - https://dsb123dsb.github.io/2017/03/07/js%E5%AE%9E%E7%8E%B0%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E4%BB%A5%E5%8F%8A%E4%BC%98%E5%8C%96/ -------------------------------------------------------------------------------- /docs/algorithm/design1.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对分而治之、动态规划的理解?区别? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/298437b0-29d0-11ec-a752-75723a64e8f5.png) 6 | 7 | 8 | ## 一、分而治之 9 | 10 | 分而治之是算法设计中的一种方法,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并 11 | 12 | 关于分而治之的实现,都会经历三个步骤: 13 | 14 | - 分解:将原问题分解为若干个规模较小,相对独立,与原问题形式相同的子问题 15 | - 解决:若子问题规模较小且易于解决时,则直接解。否则,递归地解决各子问题 16 | - 合并:将各子问题的解合并为原问题的解 17 | 18 | 实际上,关于分而治之的思想,我们在前面已经使用,例如归并排序的实现,同样经历了实现分而治之的三个步骤: 19 | 20 | - 分解:把数组从中间一分为二 21 | - 解决:递归地对两个子数组进行归并排序 22 | 23 | - 合并:将两个字数组合并称有序数组 24 | 25 | 同样关于快速排序的实现,亦如此: 26 | 27 | - 分:选基准,按基准把数组分成两个字数组 28 | - 解:递归地对两个字数组进行快速排序 29 | - 合:对两个字数组进行合并 30 | 31 | 同样二分搜索也能使用分而治之的思想去实现,代码如下: 32 | 33 | ```js 34 | function binarySearch(arr,l,r,target){ 35 | if(l> r){ 36 | return -1; 37 | } 38 | let mid = l + Math.floor((r-l)/2) 39 | if(arr[mid] === target){ 40 | return mid; 41 | }else if(arr[mid] < target ){ 42 | return binarySearch(arr,mid + 1,r,target) 43 | }else{ 44 | return binarySearch(arr,l,mid - 1,target) 45 | } 46 | } 47 | ``` 48 | 49 | 50 | 51 | ## 二、动态规划 52 | 53 | 动态规划,同样是算法设计中的一种方法,是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法 54 | 55 | 常常适用于有重叠子问题和最优子结构性质的问题 56 | 57 | 简单来说,动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决 58 | 59 | 然后呢,把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法。 60 | 61 | 一般这些子问题很相似,可以通过函数关系式递推出来,例如斐波那契数列,我们可以得到公式:当 n 大于 2的时候,F(n) = F(n-1) + F(n-2) , 62 | 63 | f(10)= f(9)+f(8),f(9) = f(8) + f(7)...是重叠子问题,当n = 1、2的时候,对应的值为2,这时候就通过可以使用一个数组记录每一步计算的结果,以此类推,减少不必要的重复计算 64 | 65 | 66 | 67 | ### 适用场景 68 | 69 | 如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划 70 | 71 | 比如一些求最值的场景,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等,都是动态规划的经典应用场景 72 | 73 | 关于动态规划题目解决的步骤,一般如下: 74 | 75 | - 描述最优解的结构 76 | - 递归定义最优解的值 77 | - 按自底向上的方式计算最优解的值 78 | - 由计算出的结果构造一个最优解 79 | 80 | 81 | ## 三、区别 82 | 83 | 动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解 84 | 85 | 与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往**不是互相独立**的,而分而治之的子问题是相互独立的 86 | 87 | 若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次 88 | 89 | 如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间 90 | 91 | 综上,可得: 92 | 93 | - 动态规划:有最优子结构和重叠子问题 94 | 95 | - 分而治之:各子问题独立 96 | 97 | 98 | 99 | ## 参考文献 100 | 101 | - https://baike.baidu.com/item/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/529408 102 | - https://juejin.cn/post/6951922898638471181 -------------------------------------------------------------------------------- /docs/algorithm/design2.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对贪心算法、回溯算法的理解?应用场景? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/1d49eae0-2e8e-11ec-a752-75723a64e8f5.png) 6 | 7 | ## 一、贪心算法 8 | 9 | 贪心算法,又称贪婪算法,是算法设计中的一种思想 10 | 11 | 其期待每一个阶段都是局部最优的选择,从而达到全局最优,但是结果并不一定是最优的 12 | 13 | 举个零钱兑换的例子,如果你有1元、2元、5元的钱币数张,用于兑换一定的金额,但是要求兑换的钱币张数最少 14 | 15 | 如果现在你要兑换11元,按照贪心算法的思想,先选择面额最大的5元钱币进行兑换,那么就得到11 = 5 + 5 + 1 的选择,这种情况是最优的 16 | 17 | 但是如果你手上钱币的面额为1、3、4,想要兑换6元,按照贪心算法的思路,我们会 6 = 4 + 1 + 1这样选择,这种情况结果就不是最优的选择 18 | 19 | 从上面例子可以看到,贪心算法存在一些弊端,使用到贪心算法的场景,都会存在一个特性: 20 | 21 | 一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法 22 | 23 | 至于是否选择贪心算法,主要看是否有如下两大特性: 24 | 25 | - 贪心选择:当某一个问题的整体最优解可通过一系列局部的最优解的选择达到,并且每次做出的选择可以依赖以前做出的选择,但不需要依赖后面需要做出的选择 26 | - 最优子结构:如果一个问题的最优解包含其子问题的最优解,则此问题具备最优子结构的性质。问题的最优子结构性质是该问题是否可以用贪心算法求解的关键所在 27 | 28 | 29 | 30 | ## 二、回溯算法 31 | 32 | 回溯算法,也是算法设计中的一种思想,是一种渐进式寻找并构建问题解决方式的策略 33 | 34 | 回溯算法会先从一个可能的工作开始解决问题,如果不行,就回溯并选择另一个动作,知道将问题解决 35 | 36 | 使用回溯算法的问题,有如下特性: 37 | 38 | - 有很多路,例如一个矩阵的方向或者树的路径 39 | - 在这些的路里面,有死路也有生路,思路即不符合题目要求的路,生路则符合 40 | - 通常使用递归来模拟所有的路 41 | 42 | 常见的伪代码如下: 43 | 44 | ```js 45 | result = [] 46 | function backtrack(路径, 选择列表): 47 | if 满足结束条件: 48 | result.add(路径) 49 | return 50 | 51 | for 选择 of 选择列表: 52 | 做选择 53 | backtrack(路径, 选择列表) 54 | 撤销选择 55 | ``` 56 | 57 | 重点解决三个问题: 58 | 59 | - 路径:也就是已经做出的选择 60 | - 选择列表 61 | - 结束条件 62 | 63 | 64 | 65 | 例如经典使用回溯算法为解决全排列的问题,如下: 66 | 67 | 一个不含重复数字的数组 `nums` ,我们要返回其所有可能的全排列,解决这个问题的思路是: 68 | 69 | - 用递归模拟所有的情况 70 | - 遇到包含重复元素的情况则回溯 71 | - 收集到所有到达递归终点的情况,并返回、 72 | 73 | ![](https://static.vue-js.com/2a030f00-2e8e-11ec-8e64-91fdec0f05a1.png) 74 | 75 | 用代码表示则如下: 76 | 77 | ```js 78 | var permute = function(nums) { 79 | const res = [], path = []; 80 | backtracking(nums, nums.length, []); 81 | return res; 82 | 83 | function backtracking(n, k, used) { 84 | if(path.length === k) { 85 | res.push(Array.from(path)); 86 | return; 87 | } 88 | for (let i = 0; i < k; i++ ) { 89 | if(used[i]) continue; 90 | path.push(n[i]); 91 | used[i] = true; // 同支 92 | backtracking(n, k, used); 93 | path.pop(); 94 | used[i] = false; 95 | } 96 | } 97 | }; 98 | ``` 99 | 100 | 101 | 102 | 103 | 104 | ## 三、总结 105 | 106 | 前面也初步了解到分而治之、动态规划,现在再了解到贪心算法、回溯算法 107 | 108 | 其中关于分而治之、动态规划、贪心策略三者的求解思路如下: 109 | 110 | ![](https://static.vue-js.com/504b5230-2e8e-11ec-8e64-91fdec0f05a1.png) 111 | 112 | 其中三者对应的经典问题如下图: 113 | 114 | ![](https://static.vue-js.com/62cdc910-2e8e-11ec-8e64-91fdec0f05a1.png) 115 | 116 | 117 | 118 | ## 参考文献 119 | 120 | - https://zh.wikipedia.org/wiki/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95 121 | - https://leetcode-cn.com/problems/permutations/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-hui-s-mfrp/ 122 | - https://cloud.tencent.com/developer/article/1767046 -------------------------------------------------------------------------------- /docs/algorithm/graph.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对图的理解?相关操作有哪些? 2 | 3 | 4 | ![](https://static.vue-js.com/7876c2f0-2059-11ec-8e64-91fdec0f05a1.png) 5 | 6 | 7 | ## 一、是什么 8 | 9 | 在计算机科学中,图是一种抽象的数据类型,在图中的数据元素通常称为结点,`V`是所有顶点的集合,`E`是所有边的集合 10 | 11 | 如果两个顶点`v`,` w`,只能由`v`向`w`,而不能由`w`向`v`,那么我们就把这种情况叫做一个从 `v` 到 `w` 的有向边。`v `也被称做初始点,`w`也被称为终点。这种图就被称做有向图 12 | 13 | 如果`v`和`w`是没有顺序的,从`v`到达`w`和从`w`到达`v`是完全相同的,这种图就被称为无向图 14 | 15 | 图的结构比较复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在存储区中的物理位置来表示元素之间的关系 16 | 17 | 常见表达图的方式有如下: 18 | 19 | - 邻接矩阵 20 | 21 | - 邻接表 22 | 23 | ### 邻接矩阵 24 | 25 | 通过使用一个二维数组`G[N][N]`进行表示`N`个点到`N-1`编号,通过邻接矩阵可以立刻看出两顶点之间是否存在一条边,只需要检查邻接矩阵行`i`和列`j`是否是非零值,对于无向图,邻接矩阵是对称的 26 | 27 | ![](https://static.vue-js.com/881d4300-2059-11ec-a752-75723a64e8f5.png) 28 | 29 | 30 | 31 | ### 邻接表 32 | 33 | 存储方式如下图所示: 34 | 35 | ![](https://static.vue-js.com/949fedd0-2059-11ec-a752-75723a64e8f5.png) 36 | 37 | 在`javascript`中,可以使用`Object`进行表示,如下: 38 | 39 | ```js 40 | const graph = { 41 | A: [2, 3, 5], 42 | B: [2], 43 | C: [0, 1, 3], 44 | D: [0, 2], 45 | E: [6], 46 | F: [0, 6], 47 | G: [4, 5] 48 | } 49 | ``` 50 | 51 | 图的数据结构还可能包含和每条边相关联的数值(edge value),例如一个标号或一个数值(即权重,weight;表示花费、容量、长度等) 52 | 53 | 54 | 55 | 56 | 57 | ## 二、操作 58 | 59 | 关于的图的操作常见的有: 60 | 61 | - 深度优先遍历 62 | - 广度优先遍历 63 | 64 | 65 | 66 | 首先构建一个图的邻接矩阵表示,如下面的图: 67 | 68 | ![](https://static.vue-js.com/a1311790-2059-11ec-8e64-91fdec0f05a1.png) 69 | 70 | 用代码表示则如下: 71 | 72 | ```js 73 | const graph = { 74 | 0: [1, 4], 75 | 1: [2, 4], 76 | 2: [2, 3], 77 | 3: [], 78 | 4: [3], 79 | } 80 | ``` 81 | 82 | 83 | 84 | 85 | 86 | ### 深度优先遍历 87 | 88 | 也就是尽可能的往深处的搜索图的分支 89 | 90 | 实现思路是,首先应该确定一个根节点,然后对根节点的没访问过的相邻节点进行深度优先遍历 91 | 92 | 确定以 0 为根节点,然后进行深度遍历,然后遍历1,接着遍历 2,然后3,此时完成一条分支`0 - 1- 2- 3`的遍历,换一条分支,也就是4,4后面因为3已经遍历过了,所以就不访问了 93 | 94 | 用代码表示则如下: 95 | 96 | ```js 97 | const visited = new Set() 98 | const dfs = (n) => { 99 | console.log(n) 100 | visited.add(n) // 访问过添加记录 101 | graph[n].forEach(c => { 102 | if(!visited.has(c)){ // 判断是否访问呢过 103 | dfs(c) 104 | } 105 | }) 106 | } 107 | ``` 108 | 109 | 110 | 111 | ### 广度优先遍历 112 | 113 | 先访问离根节点最近的节点,然后进行入队操作,解决思路如下: 114 | 115 | - 新建一个队列,把根节点入队 116 | - 把队头出队并访问 117 | - 把队头的没访问过的相邻节点入队 118 | - 重复二、三步骤,知道队列为空 119 | 120 | 用代码标识则如下: 121 | 122 | ```js 123 | const visited = new Set() 124 | const dfs = (n) => { 125 | visited.add(n) 126 | const q = [n] 127 | while(q.length){ 128 | const n = q.shift() 129 | console.log(n) 130 | graph[n].forEach(c => { 131 | if(!visited.has(c)){ 132 | q.push(c) 133 | visited.add(c) 134 | } 135 | }) 136 | } 137 | } 138 | ``` 139 | 140 | 141 | 142 | ## 三、总结 143 | 144 | 通过上面的初步了解,可以看到图就是由顶点的有穷非空集合和顶点之间的边组成的集合,分成了无向图与有向图 145 | 146 | 图的表达形式可以分成邻接矩阵和邻接表两种形式,在`javascript`中,则可以通过二维数组和对象的形式进行表达 147 | 148 | 图实际是很复杂的,后续还可以延伸出无向图和带权图,对应如下图所示: 149 | 150 | ![](https://static.vue-js.com/b0d88200-2059-11ec-8e64-91fdec0f05a1.png) 151 | 152 | 153 | 154 | 155 | 156 | ## 参考文献 157 | 158 | - https://zh.wikipedia.org/wiki/%E5%9B%BE_(%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84) 159 | - https://www.kancloud.cn/imnotdown1019/java_core_full/2159607 160 | -------------------------------------------------------------------------------- /docs/algorithm/insertionSort.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对插入排序的理解?如何实现?应用场景? 2 | 3 | ![](https://static.vue-js.com/912adc10-267f-11ec-a752-75723a64e8f5.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | 插入排序(Insertion Sort),一般也被称为直接插入排序。对于少量元素的排序,它是一个有效、简单的算法 10 | 11 | 其主要的实现思想是将数据按照一定的顺序一个一个的插入到有序的表中,最终得到的序列就是已经排序好的数据 12 | 13 | 插入排序的工作方式像许多人排序一手扑克牌,开始时,我们的左手为空并且桌子上的牌面向下 14 | 15 | 然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置,该正确位置需要从右到左将它与已在手中的每张牌进行比较 16 | 17 | 例如一个无序数组 3、1、7、5、2、4、9、6,将其升序的结果则如下: 18 | 19 | 一开始有序表中无数据,直接插入3 20 | 21 | 从第二个数开始,插入一个元素1,然后和有序表中记录3比较,1<3,所以插入到记录 3 的左侧 22 | 23 | ![](https://static.vue-js.com/9d24f5f0-267f-11ec-a752-75723a64e8f5.png) 24 | 25 | 向有序表插入记录 7 时,同有序表中记录 3 进行比较,3<7,所以插入到记录 3 的右侧 26 | 27 | ![](https://static.vue-js.com/a6a954e0-267f-11ec-8e64-91fdec0f05a1.png) 28 | 29 | 向有序表中插入记录 5 时,同有序表中记录 7 进行比较,5<7,同时 5>3,所以插入到 3 和 7 中间 30 | 31 | ![](https://static.vue-js.com/b1981940-267f-11ec-8e64-91fdec0f05a1.png) 32 | 33 | 照此规律,依次将无序表中的记录 4,9 和 6插入到有序表中 34 | 35 | ![](https://static.vue-js.com/bc2ed290-267f-11ec-a752-75723a64e8f5.png) 36 | 37 | ## 二、如何实现 38 | 39 | 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。 40 | 41 | 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置 42 | 43 | 如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面 44 | 45 | ![](https://www.runoob.com/wp-content/uploads/2019/03/insertionSort.gif) 46 | 47 | 用代码表示则如下: 48 | 49 | ```js 50 | function insertionSort(arr) { 51 | const len = arr.length; 52 | let preIndex, current; 53 | for (let i = 1; i < len; i++) { 54 | preIndex = i - 1; 55 | current = arr[i]; 56 | while(preIndex >= 0 && arr[preIndex] > current) { 57 | arr[preIndex+1] = arr[preIndex]; 58 | preIndex--; 59 | } 60 | arr[preIndex+1] = current; 61 | } 62 | return arr; 63 | } 64 | ``` 65 | 66 | 在插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较`N- 1`次,时间复杂度为`O(n)` 67 | 68 | 最坏的情况是待排序数组是逆序的,此时需要比较次数最多,总次数记为:1+2+3+…+N-1,所以,插入排序最坏情况下的时间复杂度为`O(n^2)` 69 | 70 | 通过上面了解,可以看到插入排序是一种稳定的排序方式 71 | 72 | 73 | 74 | ## 三、应用场景 75 | 76 | 插入排序时间复杂度是 O(n2),适用于数据量不大,算法稳定性要求高,且数据局部或整体有序的数列排序 77 | 78 | ## 参考文献 79 | 80 | - https://baike.baidu.com/item/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F/7214992 81 | - http://data.biancheng.net/view/65.html -------------------------------------------------------------------------------- /docs/algorithm/mergeSort.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对归并排序的理解?如何实现?应用场景? 2 | 3 | ![](https://static.vue-js.com/fa1d5720-26ac-11ec-8e64-91fdec0f05a1.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | 归并排序(Merge Sort)是建立归并操作上的一种有效,稳定的排序算法,该算法是采用分治法的一个非常典型的应用 10 | 11 | 将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序 12 | 13 | 例如对于含有 `n` 个记录的无序表,首先默认表中每个记录各为一个有序表(只不过表的长度都为 1) 14 | 15 | 然后进行两两合并,使 `n` 个有序表变为`n/2` 个长度为 2 或者 1 的有序表(例如 4 个小有序表合并为 2 个大的有序表) 16 | 17 | 通过不断地进行两两合并,直到得到一个长度为 `n` 的有序表为止 18 | 19 | 例如对无序表{49,38,65,97,76,13,27}进行归并排序分成了分、合两部分: 20 | 21 | 如下图所示: 22 | 23 | ![](https://static.vue-js.com/05f14b60-26ad-11ec-a752-75723a64e8f5.png) 24 | 25 | 归并合过程中,每次得到的新的子表本身有序,所以最终得到有序表 26 | 27 | 上述分成两部分,则称为二路归并,如果分成三个部分则称为三路归并,以此类推 28 | 29 | 30 | 31 | ## 二、如何实现 32 | 33 | 关于归并排序的算法思路如下: 34 | 35 | - 分:把数组分成两半,再递归对子数组进行分操作,直至到一个个单独数字 36 | 37 | - 合:把两个数合成有序数组,再对有序数组进行合并操作,直到全部子数组合成一个完整的数组 38 | - 合并操作可以新建一个数组,用于存放排序后的数组 39 | - 比较两个有序数组的头部,较小者出队并且推入到上述新建的数组中 40 | - 如果两个数组还有值,则重复上述第二步 41 | - 如果只有一个数组有值,则将该数组的值出队并推入到上述新建的数组中 42 | 43 | ![](https://www.runoob.com/wp-content/uploads/2019/03/mergeSort.gif) 44 | 45 | 用代码表示则如下图所示: 46 | 47 | ```js 48 | function mergeSort(arr) { // 采用自上而下的递归方法 49 | const len = arr.length; 50 | if(len < 2) { 51 | return arr; 52 | } 53 | let middle = Math.floor(len / 2), 54 | left = arr.slice(0, middle), 55 | right = arr.slice(middle); 56 | return merge(mergeSort(left), mergeSort(right)); 57 | } 58 | 59 | function merge(left, right) 60 | { 61 | const result = []; 62 | 63 | while (left.length && right.length) { 64 | if (left[0] <= right[0]) { 65 | result.push(left.shift()); 66 | } else { 67 | result.push(right.shift()); 68 | } 69 | } 70 | 71 | while (left.length) 72 | result.push(left.shift()); 73 | 74 | while (right.length) 75 | result.push(right.shift()); 76 | 77 | return result; 78 | } 79 | ``` 80 | 81 | 上述归并分成了分、合两部分,在处理分过程中递归调用两个分的操作,所花费的时间为2乘`T(n/2)`,合的操作时间复杂度则为`O(n)`,因此可以得到以下公式: 82 | 83 | 总的执行时间 = 2 × 输入长度为`n/2`的`sort`函数的执行时间 + `merge`函数的执行时间`O(n)` 84 | 85 | 当只有一个元素时,`T(1) = O(1)` 86 | 87 | 如果对`T(n) = 2 * T(n/2) + O(n) `进行左右 / n的操作,得到 `T(n) / n = (n / 2) * T(n/2) + O(1)` 88 | 89 | 现在令 `S(n) = T(n)/n`,则`S(1) = O(1)`,然后利用表达式带入得到`S(n) = S(n/2) + O(1)` 90 | 91 | 所以可以得到:`S(n) = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S(n/2^k) + O(k) = S(1) + O(logn) = O(logn)` 92 | 93 | 综上可得,`T(n) = n * log(n) = nlogn` 94 | 95 | 关于归并排序的稳定性,在进行合并过程,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也不会交换,由此可见归并排序是稳定的排序算法 96 | 97 | 98 | 99 | ## 三、应用场景 100 | 101 | 在外排序中通常使用排序-归并的策略,外排序是指处理超过内存限度的数据的排序算法,通常将中间结果放在读写较慢的外存储器,如下分成两个阶段: 102 | 103 | - 排序阶段:读入能够放进内存中的数据量,将其排序输出到临时文件,一次进行,将带排序数据组织为多个有序的临时文件 104 | - 归并阶段:将这些临时文件组合为大的有序文件 105 | 106 | 例如,使用100m内存对900m的数据进行排序,过程如下: 107 | 108 | - 读入100m数据内存,用常规方式排序 109 | - 将排序后的数据写入磁盘 110 | - 重复前两个步骤,得到9个100m的临时文件 111 | - 将100m的内存划分为10份,将9份为输入缓冲区,第10份为输出缓冲区 112 | - 进行九路归并排序,将结果输出到缓冲区 113 | - 若输出缓冲区满,将数据写到目标文件,清空缓冲区 114 | - 若缓冲区空,读入相应文件的下一份数据 115 | 116 | 117 | 118 | ## 参考文献 119 | 120 | - https://baike.baidu.com/item/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/1639015 121 | - https://chowdera.com/2021/09/20210920201630258d.html#_127 122 | - https://juejin.cn/post/6844904007899561998 -------------------------------------------------------------------------------- /docs/algorithm/quickSort.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对快速排序的理解?如何实现?应用场景? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/bafae570-268a-11ec-8e64-91fdec0f05a1.png) 6 | 7 | ## 一、是什么 8 | 9 | 快速排序(Quick Sort)算法是在冒泡排序的基础上进行改进的一种算法,从名字上看就知道该排序算法的特点是快、效率高,是处理大数据最快的排序算法之一 10 | 11 | 实现的基本思想是:通过一次排序将整个无序表分成相互独立的两部分,其中一部分中的数据都比另一部分中包含的数据的值小 12 | 13 | 然后继续沿用此方法分别对两部分进行同样的操作,直到每一个小部分不可再分,所得到的整个序列就变成有序序列 14 | 15 | 例如,对无序表49,38,65,97,76,13,27,49进行快速排序,大致过程为: 16 | 17 | - 首先从表中选取一个记录的关键字作为分割点(称为“枢轴”或者支点,一般选择第一个关键字),例如选取 49 18 | 19 | - 将表格中大于 49 个放置于 49 的右侧,小于 49 的放置于 49 的左侧,假设完成后的无序表为:{27,38,13,49,65,97,76,49} 20 | 21 | - 以 49 为支点,将整个无序表分割成了两个部分,分别为{27,38,13}和{65,97,76,49},继续采用此种方法分别对两个子表进行排序 22 | 23 | - 前部分子表以 27 为支点,排序后的子表为{13,27,38},此部分已经有序;后部分子表以 65 为支点,排序后的子表为{49,65,97,76} 24 | 25 | - 此时前半部分子表中的数据已完成排序;后部分子表继续以 65 为支点,将其分割为{49}和{97,76},前者不需排序,后者排序后的结果为{76, 97} 26 | 27 | - 通过以上几步的排序,最后由子表{13,27,38}、{49}、{49}、{65}、{76,97}构成有序表:{13,27,38,49,49,65,76,97} 28 | 29 | 30 | 31 | ## 二、如何实现 32 | 33 | 可以分成以下步骤: 34 | 35 | - 分区:从数组中选择任意一个基准,所有比基准小的元素放在基准的左边,比基准大的元素放到基准的右边 36 | - 递归:递归地对基准前后的子数组进行分区 37 | 38 | ![](https://www.runoob.com/wp-content/uploads/2019/03/quickSort.gif) 39 | 40 | 用代码表示则如下: 41 | 42 | ```js 43 | function quickSort (arr) { 44 | const rec = (arr) => { 45 | if (arr.length <= 1) { return arr; } 46 | const left = []; 47 | const right = []; 48 | const mid = arr[0]; // 基准元素 49 | for (let i = 1; i < arr.length; i++){ 50 | if (arr[i] < mid) { 51 | left.push(arr[i]); 52 | } else { 53 | right.push(arr[i]); 54 | } 55 | } 56 | return [...rec(left), mid, ...rec(right)] 57 | } 58 | return res(arr) 59 | }; 60 | ``` 61 | 62 | 快速排序是冒泡排序的升级版,最坏情况下每一次基准元素都是数组中最小或者最大的元素,则快速排序就是冒泡排序 63 | 64 | 这种情况时间复杂度就是冒泡排序的时间复杂度:`T[n] = n * (n-1) = n^2 + n`,也就是`O(n^2)` 65 | 66 | 最好情况下是`O(nlogn)`,其中递归算法的时间复杂度公式:`T[n] = aT[n/b] + f(n)`,推导如下所示: 67 | 68 | ![](https://static.vue-js.com/b6019540-2b5e-11ec-8e64-91fdec0f05a1.png) 69 | 70 | 关于上述代码实现的快速排序,可以看到是稳定的 71 | 72 | 73 | ## 三、应用场景 74 | 75 | 快速排序时间复杂度为`O(nlogn)`,是目前基于比较的内部排序中被认为最好的方法,当数据过大且数据杂乱无章时,则适合采用快速排序 76 | 77 | ## 参考文献 78 | 79 | - https://baike.baidu.com/item/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/369842 80 | - https://www.cnblogs.com/l199616j/p/10597245.html -------------------------------------------------------------------------------- /docs/algorithm/selectionSort.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对选择排序的理解?如何实现?应用场景? 2 | 3 | ![](https://static.vue-js.com/50a05ed0-2671-11ec-a752-75723a64e8f5.png) 4 | 5 | ## 一、是什么 6 | 7 | 选择排序(Selection sort)是一种简单直观的排序算法,无论什么数据进去都是 `O(n²) `的时间复杂度,所以用到它的时候,数据规模越小越好 8 | 9 | 其基本思想是:首先在未排序的数列中找到最小(or最大)元素,然后将其存放到数列的起始位置 10 | 11 | 然后再从剩余未排序的元素中继续寻找最小(or最大)元素,然后放到已排序序列的末尾 12 | 13 | 以此类推,直到所有元素均排序完毕 14 | 15 | 举个例子,一个数组为 56、12、80、91、29,其排序过程如下: 16 | 17 | - 第一次遍历时,从下标为 1 的位置即 56 开始,找出关键字值最小的记录 12,同下标为 0 的关键字 56 交换位置。此时数组为 12、56、80、91、20 18 | 19 | ![](https://static.vue-js.com/60bd2050-2671-11ec-a752-75723a64e8f5.png) 20 | 21 | - 第二次遍历时,从下标为 2 的位置即 56 开始,找出最小值 20,同下标为 2 的关键字 56 互换位置,此时数组为12、20、80、91、56 22 | 23 | ![](https://static.vue-js.com/6b04cf40-2671-11ec-8e64-91fdec0f05a1.png) 24 | 25 | - 第三次遍历时,从下标为 3 的位置即 80 开始,找出最小值 56,同下标为 3 的关键字 80 互换位置,此时数组为 12、20、56、91、80 26 | 27 | ![](https://static.vue-js.com/757f4e00-2671-11ec-a752-75723a64e8f5.png) 28 | 29 | - 第四次遍历时,从下标为 4 的位置即 91 开始,找出最小是 80,同下标为 4 的关键字 91 互换位置,此时排序完成,变成有序数组 30 | 31 | ![](https://static.vue-js.com/757f4e00-2671-11ec-a752-75723a64e8f5.png) 32 | 33 | 34 | 35 | ## 二、如何实现 36 | 37 | 从上面可以看到,对于具有 `n` 个记录的无序表遍历 `n-1` 次,第` i` 次从无序表中第 `i` 个记录开始,找出后序关键字中最小的记录,然后放置在第 `i` 的位置上 38 | 39 | 直至到从第`n`个和第`n-1`个元素中选出最小的放在第`n-1`个位置 40 | 41 | 如下动画所示: 42 | 43 | ![](https://www.runoob.com/wp-content/uploads/2019/03/selectionSort.gif) 44 | 45 | 用代码表示则如下: 46 | 47 | ```js 48 | function selectionSort(arr) { 49 | var len = arr.length; 50 | var minIndex, temp; 51 | for (var i = 0; i < len - 1; i++) { 52 | minIndex = i; 53 | for (var j = i + 1; j < len; j++) { 54 | if (arr[j] < arr[minIndex]) { // 寻找最小的数 55 | minIndex = j; // 将最小数的索引保存 56 | } 57 | } 58 | temp = arr[i]; 59 | arr[i] = arr[minIndex]; 60 | arr[minIndex] = temp; 61 | } 62 | return arr; 63 | } 64 | ``` 65 | 66 | 第一次内循环比较`N - 1`次,然后是`N-2`次,`N-3`次,……,最后一次内循环比较1次 67 | 共比较的次数是 `(N - 1) + (N - 2) + ... + 1`,求等差数列和,得 `(N - 1 + 1)* N / 2 = N^2 / 2`,舍去最高项系数,其时间复杂度为 `O(N^2)` 68 | 69 | 从上述也可以看到,选择排序是一种稳定的排序 70 | 71 | 72 | 73 | 74 | 75 | ## 三、应用场景 76 | 77 | 和冒泡排序一致,相比其它排序算法,这也是一个相对较高的时间复杂度,一般情况不推荐使用 78 | 79 | 但是我们还是要掌握冒泡排序的思想及实现,这对于我们的算法思维是有很大帮助的 80 | 81 | 82 | ## 参考文献 83 | 84 | - https://baike.baidu.com/item/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F/9762418 85 | - https://zhuanlan.zhihu.com/p/29889599 86 | - http://data.biancheng.net/view/72.html -------------------------------------------------------------------------------- /docs/algorithm/set.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对集合的理解?常见的操作有哪些? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/e3de7810-1d36-11ec-8e64-91fdec0f05a1.png) 6 | 7 | 8 | 9 | ## 一、是什么 10 | 11 | 集合(Set),指具有某种特定性质的事物的总体,里面的每一项内容称作元素 12 | 13 | 在数学中,我们经常会遇到集合的概念: 14 | 15 | - 有限集合:例如一个班集所有的同学构成的集合 16 | - 无限集合:例如全体自然数集合 17 | 18 | 在计算机中集合道理也基本一致,具有三大特性: 19 | 20 | - 确定性:于一个给定的集合,集合中的元素是确定的。即一个元素,或者属于该集合,或者不属于该集合,两者必居其一 21 | - 无序性:在一个集合中,不考虑元素之间的顺序,只要元素完全相同,就认为是同一个集合 22 | - 互异性:集合中任意两个元素都是不同的 23 | 24 | 25 | ## 二、操作 26 | 27 | 在`ES6`中,集合本身是一个构建函数`Set`,用来生成 `Set` 数据结构,如下: 28 | 29 | ```js 30 | const s = new Set(); 31 | ``` 32 | 33 | 关于集合常见的方法有: 34 | 35 | - add():增 36 | - delete():删 37 | - has():改 38 | - clear():查 39 | 40 | 41 | 42 | ### add() 43 | 44 | 添加某个值,返回 `Set` 结构本身 45 | 46 | 当添加实例中已经存在的元素,`set`不会进行处理添加 47 | 48 | ```js 49 | s.add(1).add(2).add(2); // 2只被添加了一次 50 | ``` 51 | 52 | 体现了集合的互异性特性 53 | 54 | ### delete() 55 | 56 | 删除某个值,返回一个布尔值,表示删除是否成功 57 | 58 | ```js 59 | s.delete(1) 60 | ``` 61 | 62 | ### has() 63 | 64 | 返回一个布尔值,判断该值是否为`Set`的成员 65 | 66 | ```js 67 | s.has(2) 68 | ``` 69 | 70 | ### clear() 71 | 72 | 清除所有成员,没有返回值 73 | 74 | ```js 75 | s.clear() 76 | ``` 77 | 78 | 79 | 80 | 关于多个集合常见的操作有: 81 | 82 | - 并集 83 | - 交集 84 | - 差集 85 | 86 | 87 | 88 | ### 并集 89 | 90 | 两个集合的共同元素,如下图所示: 91 | 92 | ![](https://static.vue-js.com/ed96df50-1d36-11ec-a752-75723a64e8f5.png) 93 | 94 | 代码实现方式如下: 95 | 96 | ```js 97 | let a = new Set([1, 2, 3]); 98 | let b = new Set([4, 3, 2]); 99 | 100 | // 并集 101 | let union = new Set([...a, ...b]); 102 | // Set {1, 2, 3, 4} 103 | ``` 104 | 105 | 106 | 107 | 108 | 109 | ### 交集 110 | 111 | 两个集合`A` 和 `B`,即属于`A`又属于`B`的元素,如下图所示: 112 | 113 | ![](https://static.vue-js.com/f8a9cd80-1d36-11ec-a752-75723a64e8f5.png) 114 | 115 | 用代码标识则如下: 116 | 117 | ```js 118 | let a = new Set([1, 2, 3]); 119 | let b = new Set([4, 3, 2]); 120 | 121 | // 交集 122 | let intersect = new Set([...a].filter(x => b.has(x))); 123 | // set {2, 3} 124 | ``` 125 | 126 | 127 | 128 | ### 差集 129 | 130 | 两个集合`A` 和 `B`,属于`A`的元素但不属于`B`的元素称为`A`相对于`B`的差集,如下图所示: 131 | 132 | ![](https://static.vue-js.com/0191c560-1d37-11ec-8e64-91fdec0f05a1.png) 133 | 134 | 代码标识则如下: 135 | 136 | ```js 137 | let a = new Set([1, 2, 3]); 138 | let b = new Set([4, 3, 2]); 139 | 140 | // (a 相对于 b 的)差集 141 | let difference = new Set([...a].filter(x => !b.has(x))); 142 | // Set {1} 143 | ``` 144 | 145 | 146 | 147 | 148 | 149 | ## 三、应用场景 150 | 151 | 一般情况下,使用数组的概率会比集合概率高很多 152 | 153 | 使用`set`集合的场景一般是借助其确定性,其本身只包含不同的元素 154 | 155 | 所以,可以利用`Set`的一些原生方法轻松的完成数组去重,查找数组公共元素及不同元素等操作 156 | 157 | 158 | 159 | 160 | 161 | ## 参考文献 162 | 163 | - https://zh.wikipedia.org/wiki/%E5%B9%B6%E9%9B%86 164 | - https://zh.wikipedia.org/wiki/%E8%A1%A5%E9%9B%86 -------------------------------------------------------------------------------- /docs/algorithm/sort.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说常见的排序算法有哪些?区别? 2 | 3 | 4 | ![](https://static.vue-js.com/63eb7920-211c-11ec-a752-75723a64e8f5.png) 5 | 6 | ## 一、是什么 7 | 8 | 排序是程序开发中非常常见的操作,对一组任意的数据元素经过排序操作后,就可以把他们变成一组一定规则排序的有序序列 9 | 10 | 排序算法属于算法中的一种,而且是覆盖范围极小的一种,彻底掌握排序算法对程序开发是有很大的帮助的 11 | 12 | 对与排序算法的好坏衡量,主要是从时间复杂度、空间复杂度、稳定性 13 | 14 | 时间复杂度、空间复杂度前面已经讲过,这里主要看看稳定性的定义 15 | 16 | 稳定性指的是假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变 17 | 18 | 即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的;否则称为不稳定的 19 | 20 | 21 | 22 | ## 二、有哪些 23 | 24 | 常见的算法排序算法有: 25 | 26 | - 冒泡排序 27 | - 选择排序 28 | - 插入排序 29 | - 归并排序 30 | - 快速排序 31 | 32 | ### 冒泡排序 33 | 34 | 一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来 35 | 36 | 思路如下: 37 | 38 | - 比较相邻的元素,如果第一个比第二个大,就交换它们两个 39 | 40 | - 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数 41 | - 针对所有的元素重复以上的步骤,除了最后一个 42 | - 重复上述步骤,直到没有任何一堆数字需要比较 43 | 44 | ![](https://pic4.zhimg.com/v2-33a947c71ad62b254cab62e5364d2813_b.webp) 45 | 46 | 47 | 48 | ### 选择排序 49 | 50 | 选择排序是一种简单直观的排序算法,它也是一种交换排序算法 51 | 52 | 无论什么数据进去都是 `O(n²) `的时间复杂度。所以用到它的时候,数据规模越小越好 53 | 54 | 唯一的好处是不占用额外的内存存储空间 55 | 56 | 思路如下: 57 | 58 | - 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置 59 | - 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。 60 | - 重复第二步,直到所有元素均排序完毕 61 | 62 | ![](https://pic1.zhimg.com/v2-1c7e20f306ddc02eb4e3a50fa7817ff4_b.webp) 63 | 64 | 65 | 66 | 67 | 68 | ### 插入排序 69 | 70 | 插入排序是一种简单直观的排序算法 71 | 72 | 它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入 73 | 74 | 解决思路如下: 75 | 76 | - 把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的 77 | - 从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。) 78 | - 重复上述过程直到最后一个元素被插入有序子数组中 79 | 80 | ![](https://pic3.zhimg.com/v2-91b76e8e4dab9b0cad9a017d7dd431e2_b.webp) 81 | 82 | 83 | 84 | ### 归并排序 85 | 86 | 归并排序是建立在归并操作上的一种有效的排序算法 87 | 88 | 该算法是采用分治法的一个非常典型的应用 89 | 90 | 将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序 91 | 92 | 解决思路如下: 93 | 94 | - 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 95 | - 设定两个指针,最初位置分别为两个已经排序序列的起始位置 96 | - 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置 97 | - 重复步骤3直到某一指针到达序列尾 98 | - 将另一序列剩下的所有元素直接复制到合并序列尾 99 | 100 | ![](https://pic3.zhimg.com/v2-cdda3f11c6efbc01577f5c29a9066772_b.jpg) 101 | 102 | 103 | 104 | ### 快速排序 105 | 106 | 快速排序是对冒泡排序算法的一种改进,基本思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小 107 | 108 | 再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列 109 | 110 | 解决思路如下: 111 | 112 | - 从数列中挑出一个元素,称为"基准"(pivot) 113 | - 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作 114 | - 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序 115 | 116 | ![](https://pic1.zhimg.com/v2-c411339b79f92499dcb7b5f304c826f4_b.jpg) 117 | 118 | 119 | 120 | ## 三、区别 121 | 122 | 除了上述的排序算法之外,还存在其他的排序算法,例如希尔排序、堆排序等等...... 123 | 124 | 区别如下图所示: 125 | 126 | ![](https://static.vue-js.com/5c3d7b50-2131-11ec-a752-75723a64e8f5.png) 127 | 128 | 129 | 130 | ## 参考文献 131 | 132 | - https://www.runoob.com/w3cnote/bubble-sort.html 133 | - http://www.x-lab.info/post/sort-algorithm/ 134 | - https://zhuanlan.zhihu.com/p/42586566 -------------------------------------------------------------------------------- /docs/algorithm/structure.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对数据结构的理解?有哪些?区别? 2 | 3 | ![](https://static.vue-js.com/3d87b540-1aa6-11ec-a752-75723a64e8f5.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | 数据结构是计算机存储、组织数据的方式,是指相互之间存在一种或多种特定关系的数据元素的集合 10 | 11 | 前面讲到,一个程序 = 算法 + 数据结构,数据结构是实现算法的基础,选择合适的数据结构可以带来更高的运行或者存储效率 12 | 13 | 数据元素相互之间的关系称为结构,根据数据元素之间关系的不同特性,通常有如下四类基本的结构: 14 | 15 | - 集合结构:该结构的数据元素间的关系是“属于同一个集合” 16 | - 线性结构:该结构的数据元素之间存在着一对一的关系 17 | - 树型结构:该结构的数据元素之间存在着一对多的关系 18 | - 图形结构:该结构的数据元素之间存在着多对多的关系,也称网状结构 19 | 20 | 由于数据结构种类太多,逻辑结构可以再分成为: 21 | 22 | - 线性结构:有序数据元素的集合,其中数据元素之间的关系是一对一的关系,除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的 23 | - 非线性结构:各个数据元素不再保持在一个线性序列中,每个数据元素可能与零个或者多个其他数据元素发生关联 24 | 25 | ![](https://static.vue-js.com/9aedc5d0-1aa6-11ec-8e64-91fdec0f05a1.png) 26 | 27 | ## 二、有哪些 28 | 29 | 常见的数据结构有如下: 30 | 31 | - 数组 32 | - 栈 33 | - 队列 34 | - 链表 35 | - 树 36 | - 图 37 | - 堆 38 | - 散列表 39 | 40 | 41 | ### 数组 42 | 43 | 在程序设计中,为了处理方便, 一般情况把具有相同类型的若干变量按有序的形式组织起来,这些按序排列的同类数据元素的集合称为数组 44 | 45 | 46 | ### 栈 47 | 48 | 一种特殊的线性表,只能在某一端插入和删除的特殊线性表,按照先进后出的特性存储数据 49 | 50 | 先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据 51 | 52 | 53 | 54 | ### 队列 55 | 56 | 跟栈基本一致,也是一种特殊的线性表,其特性是先进先出,只允许在表的前端进行删除操作,而在表的后端进行插入操作 57 | 58 | 59 | 60 | ### 链表 61 | 62 | 是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 63 | 64 | 链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成 65 | 66 | 一般情况,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域 67 | 68 | 69 | 70 | ### 树 71 | 72 | 树是典型的非线性结构,在树的结构中,有且仅有一个根结点,该结点没有前驱结点。在树结构中的其他结点都有且仅有一个前驱结点,而且可以有两个以上的后继结点 73 | 74 | 75 | 76 | ### 图 77 | 78 | 一种非线性结构。在图结结构中,数据结点一般称为顶点,而边是顶点的有序偶对。如果两个顶点之间存在一条边,那么就表示这两个顶点具有相邻关系 79 | 80 | 81 | 82 | ### 堆 83 | 84 | 堆是一种特殊的树形数据结构,每个结点都有一个值,特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆 85 | 86 | 87 | 88 | ### 散列表 89 | 90 | 若结构中存在关键字和`K`相等的记录,则必定在`f(K)`的存储位置上,不需比较便可直接取得所查记录 91 | 92 | 93 | 94 | ## 三、区别 95 | 96 | 上述的数据结构,之前的区别可以分成线性结构和非线性结构: 97 | 98 | - 线性结构有:数组、栈、队列、链表等 99 | - 非线性结构有:树、图、堆等 100 | 101 | 102 | ## 参考文献 103 | 104 | - https://zh.wikipedia.org/wiki/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84 105 | - https://baike.baidu.com/item/数据结构/1450 106 | -------------------------------------------------------------------------------- /docs/applet/WebView_jscore.md: -------------------------------------------------------------------------------- 1 | - # 面试官:说说微信小程序的实现原理? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/4407cb60-3722-11ec-a752-75723a64e8f5.png) 6 | 7 | ## 一、背景 8 | 9 | 网页开发,渲染线程和脚本是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应的原因,本质就是我们常说的 `JS` 是单线程的 10 | 11 | 而在小程序中,选择了 `Hybrid` 的渲染方式,将视图层和逻辑层是分开的,双线程同时运行,视图层的界面使用 `WebView` 进行渲染,逻辑层运行在 `JSCore` 中 12 | 13 | ![](https://static.vue-js.com/4e322e50-3722-11ec-8e64-91fdec0f05a1.png) 14 | 15 | - 渲染层:界面渲染相关的任务全都在 WebView 线程里执行。一个小程序存在多个界面,所以渲染层存在多个 WebView 线程 16 | - 逻辑层:采用 JsCore 线程运行 JS 脚本,在这个环境下执行的都是有关小程序业务逻辑的代码 17 | 18 | 19 | 20 | ## 二、通信 21 | 22 | 小程序在渲染层,宿主环境会把`wxml`转化成对应的`JS`对象 23 | 24 | 在逻辑层发生数据变更的时候,通过宿主环境提供的`setData`方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的`Dom`树上,渲染出正确的视图 25 | 26 | ![](https://static.vue-js.com/5948ed10-3722-11ec-a752-75723a64e8f5.png) 27 | 28 | 当视图存在交互的时候,例如用户点击你界面上某个按钮,这类反馈应该通知给开发者的逻辑层,需要将对应的处理状态呈现给用户 29 | 30 | 对于事件的分发处理,微信进行了特殊的处理,将所有的事件拦截后,丢到逻辑层交给`JavaScript`进行处理 31 | 32 | ![](https://static.vue-js.com/61f9f670-3722-11ec-a752-75723a64e8f5.png) 33 | 34 | 由于小程序是基于双线程的,也就是任何在视图层和逻辑层之间的数据传递都是线程间的通信,会有一定的延时,因此在小程序中,页面更新成了异步操作 35 | 36 | 异步会使得各部分的运行时序变得复杂一些,比如在渲染首屏的时候,逻辑层与渲染层会同时开始初始化工作,但是渲染层需要有逻辑层的数据才能把界面渲染出来 37 | 38 | 如果渲染层初始化工作较快完成,就要等逻辑层的指令才能进行下一步工作 39 | 40 | 因此逻辑层与渲染层需要有一定的机制保证时序正确,在每个小程序页面的生命周期中,存在着若干次页面数据通信 41 | 42 | ![](https://static.vue-js.com/6cb798b0-3722-11ec-a752-75723a64e8f5.png) 43 | 44 | ## 三、运行机制 45 | 46 | 小程序启动运行两种情况: 47 | 48 | - 冷启动(重新开始):用户首次打开或者小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动,即为冷启动 49 | - 热启动:用户已经打开过小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需要将后台态的小程序切换到前台,这个过程就是热启动 50 | 51 | #### 需要注意: 52 | > 1.小程序没有重启的概念 53 | > 2.当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后会被微信主动销毁 54 | > 3.短时间内收到系统两次以上内存警告,也会对小程序进行销毁,这也就为什么一旦页面内存溢出,页面会奔溃的本质原因了 55 | 56 | ![](https://static.vue-js.com/968c8510-3722-11ec-a752-75723a64e8f5.png) 57 | 58 | 59 | 60 | 开发者在后台发布新版本之后,无法立刻影响到所有现网用户,但最差情况下,也在发布之后 24 小时之内下发新版本信息到用户 61 | 62 | 每次冷启动时,都会检查是否有更新版本,如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上 63 | 64 | 65 | 66 | ## 参考文献 67 | 68 | - https://developers.weixin.qq.com/community/develop/article/doc/0008a4c4f28f30fe3eb863b2750813 69 | - https://juejin.cn/post/6976805521407868958#heading-5 70 | - https://juejin.cn/post/6844903805675388942 71 | - https://juejin.cn/post/6844903999863259144#heading-1 -------------------------------------------------------------------------------- /docs/applet/applet.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对微信小程序的理解?优缺点? 2 | 3 | ![](https://static.vue-js.com/be367c80-300e-11ec-8e64-91fdec0f05a1.png) 4 | 5 | ## 一、是什么 6 | 7 | 2017年,微信正式推出了小程序,允许外部开发者在微信内部运行自己的代码,开展业务 8 | 9 | 截至目前,小程序已经成为国内前端的一个重要业务,跟 `Web` 和手机 `App` 有着同等的重要性 10 | 11 | ![](https://static.vue-js.com/ce751de0-300e-11ec-8e64-91fdec0f05a1.png) 12 | 13 | 小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用 14 | 15 | 也体现了“用完即走”的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载 16 | 17 | 注意的是,除了微信小程序,还有百度小程序、微信小程序、支付宝小程序、抖音小程序,都是每个平台自己开发的,都是有针对性平台的应用程序 18 | 19 | 20 | 21 | ## 二、背景 22 | 23 | ⼩程序并⾮凭空冒出来的⼀个概念,当微信中的 `WebView` 逐渐成为移动 `Web`的⼀个重要⼊⼝时,微信就有相关的 `JS-SDK` 24 | 25 | `JS-SDK` 解决了移动⽹⻚能⼒不⾜的问题,通过暴露微信的接⼝使得 `Web` 开发者能够拥有更多的能⼒,然⽽在更多的能⼒之外,`JS-SDK`的模式并没有解决使⽤移动⽹⻚遇到的体验不良的问题 26 | 27 | 因此需要设计⼀个⽐较好的系统,使得所有开发者在微信中都能获得⽐较好的体验: 28 | - 快速的加载 29 | - 更强⼤的能⼒ 30 | - 原⽣的体验 31 | - 易⽤且安全的微信数据开放 32 | - ⾼效和简单的开发 33 | 34 | 这些是`JS-SDK`做不到的,需要设计一个全新的小程序系统 35 | 36 | 对于小程序的开发,提供一个简单、高效的应用开发框架和丰富的组件及`API`,帮助开发者开发出具有原生体验的服务 37 | 38 | 其中相比`H5`,小程序与其的区别有如下: 39 | - 运⾏环境:⼩程序基于浏览器内核重构的内置解析器 40 | - 系统权限:⼩程序能获得更多的系统权限,如⽹络通信状态、数据缓存能⼒等 41 | - 渲染机制:⼩程序的逻辑层和渲染层是分开的 42 | 43 | 小程序可以视为只能用微信打开和浏览的`H5`,小程序和网页的技术模型是一样的,用到的 `JavaScript` 语言和 `CSS` 样式也是一样的,只是网页的 `HTML` 标签被稍微修改成了 `WXML` 标签 44 | 45 | 因此可以说,小程序页面本质上就是网页 46 | 47 | 其中关于微信小程序的实现原理,我们在后面的文章讲到 48 | 49 | 50 | 51 | ## 三、优缺点 52 | 53 | 优点: 54 | - 随搜随用,用完即走:使得小程序可以代替许多APP,或是做APP的整体嫁接,或是作为阉割版功能的承载体 55 | - 流量大,易接受:小程序借助自身平台更加容易引入更多的流量 56 | - 安全 57 | - 开发门槛低 58 | - 降低兼容性限制 59 | 60 | 61 | 62 | 缺点: 63 | 64 | - 用户留存:及相关数据显示,小程序的平均次日留存在13%左右,但是双周留存骤降到仅有1% 65 | - 体积限制:微信小程序只有2M的大小,这样导致无法开发大型一些的小程序 66 | - 受控微信:比起APP,尤其是安卓版的高自由度,小程序要面对很多来自微信的限制,从功能接口,甚至到类别内容,都要接受微信的管控 67 | 68 | 69 | 70 | ## 参考文献 71 | 72 | - https://developers.weixin.qq.com/miniprogram/dev/framework/ 73 | - https://www.zhihu.com/question/263816362 -------------------------------------------------------------------------------- /docs/applet/login.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说微信小程序的登录流程? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/aa3ccbd0-3428-11ec-8e64-91fdec0f05a1.png) 6 | 7 | 8 | ## 一、背景 9 | 10 | 传统的`web`开发实现登陆功能,一般的做法是输入账号密码、或者输入手机号及短信验证码进行登录 11 | 12 | 服务端校验用户信息通过之后,下发一个代表登录态的 `token` 给客户端,以便进行后续的交互,每当`token`过期,用户都需要重新登录 13 | 14 | 而在微信小程序中,可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系,从而实现登陆功能 15 | 16 | 实现小程序用户体系主要涉及到`openid`和`code`的概念: 17 | 18 | - 调用`wx.login()`方法会生成`code`,将`code`作为参数传递给微信服务器指定接口,就可以获取用户的`openid` 19 | 20 | 对于每个小程序,微信都会将用户的微信`ID`映射出一个小程序 `openid`,作为这个用户在这个小程序的唯一标识 21 | 22 | 23 | 24 | 25 | 26 | ## 二、流程 27 | 28 | 微信小程序登陆具体实现的逻辑如下图所示: 29 | 30 | ![](https://static.vue-js.com/b60638c0-3428-11ec-a752-75723a64e8f5.png) 31 | 32 | - 通过 wx.login() 获取到用户的code判断用户是否授权读取用户信息,调用wx.getUserInfo 读取用户数据 33 | - 由于小程序后台授权域名无法授权微信的域名,所以需要自身后端调用微信服务器获取用户信息 34 | - 通过 wx.request() 方法请求业务方服务器,后端把 appid , appsecret 和 code 一起发送到微信服务器。 appid 和 appsecret 都是微信提供的,可以在管理员后台找到 35 | - 微信服务器返回了 openid 及本次登录的会话密钥 session_key 36 | - 后端从数据库中查找 openid ,如果没有查到记录,说明该用户没有注册,如果有记录,则继续往下走 37 | - session_key 是对用户数据进行加密签名的密钥。为了自身应用安全,session_key 不应该在网络上传输 38 | - 然后生成 session并返回给小程序 39 | - 小程序把 session 存到 storage 里面 40 | - 下次请求时,先从 storage 里面读取,然后带给服务端 41 | - 服务端对比 session 对应的记录,然后校验有效期 42 | 43 | 更加详细的功能图如下所示: 44 | 45 | ![](https://static.vue-js.com/c3cfbb70-3428-11ec-8e64-91fdec0f05a1.png) 46 | 47 | 48 | 49 | 50 | 51 | ## 三、扩展 52 | 53 | 实际业务中,我们还需要登录态是否过期,通常的做法是在登录态(临时令牌)中保存有效期数据,该有效期数据应该在服务端校验登录态时和约定的时间(如服务端本地的系统时间或时间服务器上的标准时间)做对比 54 | 55 | 这种方法需要将本地存储的登录态发送到小程序的服务端,服务端判断为无效登录态时再返回需重新执行登录过程的消息给小程 56 | 57 | 另一种方式可以通过调用`wx.checkSession`检查微信登陆态是否过期: 58 | 59 | - 如果过期,则发起完整的登录流程 60 | - 如果不过期,则继续使用本地保存的自定义登录态 61 | 62 | 这种方式的好处是不需要小程序服务端来参与校验,而是在小程序端调用AP,流程如下所示: 63 | 64 | ![](https://static.vue-js.com/8b446d30-349d-11ec-a752-75723a64e8f5.png) 65 | 66 | 67 | 68 | ## 参考文献 69 | 70 | - https://segmentfault.com/a/1190000016750340 71 | - https://juejin.cn/post/6955754095860776973 72 | - https://www.cnblogs.com/zwh0910/p/13977278.html -------------------------------------------------------------------------------- /docs/applet/navigate.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说微信小程序中路由跳转的方式有哪些?区别? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/52bd3820-31a5-11ec-8e64-91fdec0f05a1.png) 6 | 7 | 8 | 9 | ## 一、是什么 10 | 11 | 微信小程序拥有`web`网页和`Application`共同的特征,我们的页面都不是孤立存在的,而是通过和其他页面进行交互,来共同完成系统的功能 12 | 13 | 在微信小程序中,每个页面可以看成是一个` pageModel`,`pageModel `全部以栈的形式进行管理 14 | 15 | 16 | 17 | ## 二、有哪些 18 | 19 | 常见的微信小程序页面跳转方式有如下: 20 | 21 | - wx.navigateTo(Object) 22 | - wx.redirectTo(Object) 23 | - wx.switchTab(Object) 24 | - wx.navigateBack(Object) 25 | - wx.reLaunch(Object) 26 | 27 | 28 | 29 | ### wx.navigateTo(Object) 30 | 31 | `wx.navigateTo()`用于保留当前页面、跳转到应用内的某个页面,使用 `wx.navigateBack`可以返回到原页面 32 | 33 | 对于页面不是特别多的小程序,通常推荐使用 `wx.navigateTo`进行跳转, 以便返回原页面,以提高加载速度。当页面特别多时,则不推荐使用 34 | 35 | 参数表如下所示: 36 | 37 | ![](https://static.vue-js.com/5e524ea0-31a5-11ec-8e64-91fdec0f05a1.png) 38 | 39 | 流程图如下: 40 | 41 | ![](https://static.vue-js.com/68f033e0-31a5-11ec-8e64-91fdec0f05a1.png) 42 | 43 | 44 | 45 | ### wx.redirectTo(Object) 46 | 47 | 重定向,当页面过多时,被保留页面会挤占微信分配给小程序的内存,或是达到微信所限制的 10 层页面栈的情况下,我们应该考虑选择 `wx.redirectTo` 48 | 49 | `wx.redirectTo()`用于关闭当前页面,跳转到应用内的某个页面 50 | 51 | 这样的跳转,可以避免跳转前页面占据运行内存,但返回时页面需要重新加载,增加了返回页面的显示时间 52 | 53 | 参数表如下所示: 54 | 55 | ![](https://static.vue-js.com/76066c20-31a5-11ec-8e64-91fdec0f05a1.png) 56 | 57 | 流程图如下所示: 58 | 59 | ![](https://static.vue-js.com/828c4b40-31a5-11ec-a752-75723a64e8f5.png) 60 | 61 | 62 | 63 | ### wx.switchTab(Object) 64 | 65 | 跳转到 `tabBar `页面,并关闭其他所有非 `tabBar` 页面 66 | 67 | 参数表如下所示: 68 | 69 | ![](https://static.vue-js.com/968869d0-31a5-11ec-a752-75723a64e8f5.png) 70 | 71 | 72 | 73 | ### wx.navigateBack(Object) 74 | 75 | `wx.navigateBack()` 用于关闭当前页面,并返回上一页面或多级页面,开发者可通过 `getCurrentPages()` 获取当前的页面栈,决定需要返回几层则设置对象的`delta`属性即可 76 | 77 | 参数表如下: 78 | 79 | ![](https://static.vue-js.com/a28d8030-31a5-11ec-a752-75723a64e8f5.png) 80 | 81 | 82 | 83 | ### wx.reLaunch(Object) 84 | 85 | 关闭所有页面,打开到应用内的某个页面,返回的时候跳到首页 86 | 87 | 流程图如下所示: 88 | 89 | ![](https://static.vue-js.com/accca3a0-31a5-11ec-8e64-91fdec0f05a1.png) 90 | 91 | 参数表如下所示: 92 | 93 | ![](https://static.vue-js.com/b98c7e80-31a5-11ec-8e64-91fdec0f05a1.png) 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | ## 三、总结 102 | 103 | 关于上述五种跳转方式,做下总结: 104 | 105 | - navigateTo 保留当前页面,跳转到应用内的某个页面,使用 wx.navigateBack 可以返回到原页 106 | - redirectTo 关闭当前页面,跳转到应用内的某个页面 107 | - switchTab 跳转到 tabBar 页面,同时关闭其他非 tabBar 页面 108 | - navigateBack 返回上一页面 109 | - reLanch 关闭所有页面,打开到应用内的某个页面 110 | 111 | 其中关于它们的页面栈的关系如下: 112 | 113 | - avigateTo 新页面入栈 114 | 115 | - redirectTo 当前页面出栈,新页面入栈 116 | - navigateBack 页面不断出栈,直到目标返回页,新页面入栈 117 | - switchTab 页面全部出栈,只留下新的 Tab 页面 118 | - reLanch 页面全部出栈,只留下新的页面 119 | 120 | 121 | 122 | ## 参考文献 123 | 124 | - https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.navigateBack.html -------------------------------------------------------------------------------- /docs/applet/optimization.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说提高微信小程序的应用速度的手段有哪些? 2 | 3 | ![](https://static.vue-js.com/f606d530-3278-11ec-a752-75723a64e8f5.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | 小程序启动会常常遇到如下图场景: 10 | 11 | ![](https://static.vue-js.com/03941230-3279-11ec-8e64-91fdec0f05a1.png) 12 | 13 | 这是因为,小程序首次启动前,微信会在小程序启动前为小程序准备好通用的运行环境,如运行中的线程和一些基础库的初始化 14 | 15 | 然后才开始进入启动状态,展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作: 16 | 17 | - 下载小程序代码包 18 | - 加载小程序代码包 19 | - 初始化小程序首页 20 | 21 | 下载到的小程序代码包不是小程序的源代码,而是编译、压缩、打包之后的代码包 22 | 23 | 整体流程如下图: 24 | 25 | ![](https://static.vue-js.com/11c0ea90-3279-11ec-a752-75723a64e8f5.png) 26 | 27 | 28 | 29 | 30 | 31 | ## 二、手段 32 | 33 | 围绕上图小程序的启动流程, 我们可以从加载、渲染两个纬度进行切入: 34 | 35 | 36 | 37 | ### 加载 38 | 39 | 提升体验最直接的方法是控制小程序包的大小,常见手段有如下: 40 | 41 | - 代码包的体积压缩可以通过勾选开发者工具中“上传代码时,压缩代码”选项 42 | 43 | - 及时清理无用的代码和资源文件 44 | - 减少资源包中的图片等资源的数量和大小(理论上除了小icon,其他图片资源从网络下载),图片资源压缩率有限 45 | 46 | 并且可以采取分包加载的操作,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载 47 | 48 | 当用户点击到子包的目录时,还是有一个代码包下载的过程,这会感觉到明显的卡顿,所以子包也不建议拆的太大,当然我们可以采用子包预加载技术,并不需要等到用户点击到子包页面后在下载子包 49 | 50 | ![](https://static.vue-js.com/2034de10-3279-11ec-8e64-91fdec0f05a1.png) 51 | 52 | 53 | 54 | ### 渲染 55 | 56 | 关于微信小程序首屏渲染优化的手段如下: 57 | 58 | - 请求可以在页面onLoad就加载,不需要等页面ready后在异步请求数据 59 | - 尽量减少不必要的https请求,可使用 getStorageSync() 及 setStorageSync() 方法将数据存储在本地 60 | - 可以在前置页面将一些有用的字段带到当前页,进行首次渲染(列表页的某些数据--> 详情页),没有数据的模块可以进行骨架屏的占位 61 | 62 | 63 | 64 | 在微信小程序中,提高页面的多次渲染效率主要在于正确使用`setData`: 65 | 66 | - 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用 67 | - 数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用`setData`来设置这些数据 68 | - 与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下 69 | 70 | 71 | 72 | 除此之外,对于一些独立的模块我们尽可能抽离出来,这是因为自定义组件的更新并不会影响页面上其他元素的更新 73 | 74 | 各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立的数据、`setData`调用 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | ## 三、总结 85 | 86 | **小程序启动加载性能**: 87 | 88 | - 控制代码包的大小 89 | - 分包加载 90 | - 首屏体验(预请求,利用缓存,避免白屏,及时反馈 91 | 92 | **小程序渲染性能**: 93 | 94 | - 避免不当的使用setData 95 | - 使用自定义组件 96 | 97 | 98 | 99 | ### 参考文献 100 | 101 | - https://juejin.cn/post/6969779451177484296 102 | - https://segmentfault.com/a/1190000008925450 103 | - https://juejin.cn/post/6844903638226173965 104 | - https://juejin.cn/post/6844903726939897869 105 | -------------------------------------------------------------------------------- /docs/applet/publish.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说微信小程序的发布流程? 2 | 3 | ![](https://static.vue-js.com/d5cccdf0-3652-11ec-8e64-91fdec0f05a1.png) 4 | 5 | ## 一、背景 6 | 7 | 在中大型的公司里,人员的分工非常仔细,一般会有不同岗位角色的员工同时参与同一个小程序项目。为此,小程序平台设计了不同的权限管理使得项目管理者可以更加高效管理整个团队的协同工作 8 | 9 | ![](https://static.vue-js.com/e76aff50-3652-11ec-8e64-91fdec0f05a1.png) 10 | 11 | 以往我们在开发完网页之后,需要把网页的代码和资源放在服务器上,让用户通过互联网来访问 12 | 13 | 在小程序的平台里,开发者完成开发之后,需要在开发者工具提交小程序的代码包,然后在小程序后台发布小程序 14 | 15 | ![](https://static.vue-js.com/fe5da190-3652-11ec-8e64-91fdec0f05a1.png) 16 | 17 | 18 | 19 | 20 | 21 | ## 二、流程 22 | 23 | 关于发布的流程,主要分成了三个部分: 24 | 25 | - 上传代码 26 | - 提交审核 27 | - 发布版本 28 | 29 | 30 | 31 | ### 上传代码 32 | 33 | 在开发者工具中,可以点击代码上传功能: 34 | 35 | ![](https://static.vue-js.com/08f19bc0-3653-11ec-a752-75723a64e8f5.png) 36 | 37 | 然后就可以填写版本信息: 38 | 39 | ![](https://static.vue-js.com/1d02c8f0-3653-11ec-a752-75723a64e8f5.png) 40 | 41 | 然后点击上传,编译器则会提示上传代码成功 42 | 43 | 44 | 45 | ### 提交审核 46 | 47 | 代码上传完毕,就可以登陆微信公众号的官网首页,点击【开发管理】,查看应用详情: 48 | 49 | ![](https://static.vue-js.com/281038e0-3653-11ec-8e64-91fdec0f05a1.png) 50 | 51 | 提交审核过程需要填写审核信息,如下图: 52 | 53 | ![](https://static.vue-js.com/33d97ec0-3653-11ec-a752-75723a64e8f5.png) 54 | 55 | 提交审核成功之后如下图: 56 | 57 | ![](https://static.vue-js.com/3e4c3550-3653-11ec-a752-75723a64e8f5.png) 58 | 59 | ### 发布版本 60 | 61 | 当审核通过之后,即可提交发布 62 | 63 | ![](https://static.vue-js.com/495140d0-3653-11ec-8e64-91fdec0f05a1.png) 64 | 65 | 发布成功之后则如下: 66 | 67 | ![](https://static.vue-js.com/5293b4c0-3653-11ec-8e64-91fdec0f05a1.png) 68 | 69 | 70 | 71 | ## 三、扩展 72 | 73 | 上述是最简单的小程序代码发布的流程,通常的流程如下: 74 | 75 | - 代码管理服务器上新建分支 76 | - 开发测试新需求 77 | - 测试完成后,将本地分支合并到 master 分支 78 | - 拉取 master 分支最新代码,执行 build 命令生成小程序可执行文件 79 | - 开发者工具点击“上传” 80 | - 提审 81 | - 发布 82 | 83 | 但是面对多人协调开发的时候,有可能出现已经上线的代码还没合并到`master`的情况 84 | 85 | 因此可以考虑自动化构建部署,就是将从开发到部署的一系列流程变成自动化,衔接连贯,在构建失败时能够告知开发者,构建成功后能够告知测试和实施人员,可参考如下流程图: 86 | 87 | ![](https://static.vue-js.com/602d9bf0-3653-11ec-a752-75723a64e8f5.png) 88 | 89 | 90 | ## 参考文献 91 | 92 | - https://juejin.cn/post/6994414162700927012 93 | - https://www.leapcloud.cn/website/docs/doc_config/xiaochengxu/xiaochengxu.html -------------------------------------------------------------------------------- /docs/applet/requestPayment.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说微信小程序的支付流程? 2 | 3 | ![](https://static.vue-js.com/2266fff0-34a0-11ec-8e64-91fdec0f05a1.png) 4 | 5 | 6 | 7 | ## 一、前言 8 | 9 | 微信小程序为电商类小程序,提供了非常完善、优秀、安全的支付功能 10 | 11 | 在小程序内可调用微信的`API`完成支付功能,方便、快捷 12 | 13 | 场景如下图所示: 14 | 15 | ![](https://static.vue-js.com/6e0cff40-34a0-11ec-a752-75723a64e8f5.png) 16 | 17 | ![](https://static.vue-js.com/34864830-34a0-11ec-8e64-91fdec0f05a1.png) 18 | 19 | - 用户通过分享或扫描二维码进入商户小程序,用户选择购买,完成选购流程 20 | - 调起微信支付控件,用户开始输入支付密码 21 | - 密码验证通过,支付成功。商户后台得到支付成功的通知 22 | - 返回商户小程序,显示购买成功 23 | - 微信支付公众号下发支付凭证 24 | 25 | 26 | 27 | ## 二、流程 28 | 29 | 以电商小程序为例 30 | 31 | 支付流程图如下所示: 32 | 33 | ![](https://static.vue-js.com/76b66780-34a0-11ec-8e64-91fdec0f05a1.png) 34 | 35 | 具体的做法: 36 | 37 | - 打开某小程序,点击直接下单 38 | - wx.login获取用户临时登录凭证code,发送到后端服务器换取openId 39 | - 在下单时,小程序需要将购买的商品Id,商品数量,以及用户的openId传送到服务器 40 | - 服务器在接收到商品Id、商品数量、openId后,生成服务期订单数据,同时经过一定的签名算法,向微信支付发送请求,获取预付单信息(prepay_id),同时将获取的数据再次进行相应规则的签名,向小程序端响应必要的信息 41 | - 小程序端在获取对应的参数后,调用wx.requestPayment()发起微信支付,唤醒支付工作台,进行支付 42 | - 接下来的一些列操作都是由用户来操作的包括了微信支付密码,指纹等验证,确认支付之后执行鉴权调起支付 43 | - 鉴权调起支付:在微信后台进行鉴权,微信后台直接返回给前端支付的结果,前端收到返回数据后对支付结果进行展示 44 | - 推送支付结果:微信后台在给前端返回支付的结果后,也会向后台也返回一个支付结果,后台通过这个支付结果来更新订单的状态 45 | 46 | 其中后端响应数据必要的信息则是`wx.requestPayment`方法所需要的参数,大致如下: 47 | 48 | ```JS 49 | wx.requestPayment({ 50 | // 时间戳 51 | timeStamp: '', 52 | // 随机字符串 53 | nonceStr: '', 54 | // 统一下单接口返回的 prepay_id 参数值 55 | package: '', 56 | // 签名类型 57 | signType: '', 58 | // 签名 59 | paySign: '', 60 | // 调用成功回调 61 | success () {}, 62 | // 失败回调 63 | fail () {}, 64 | // 接口调用结束回调 65 | complete () {} 66 | }) 67 | ``` 68 | 69 | 参数表如下所示: 70 | 71 | 72 | ![](https://files.mdnice.com/user/155/48efed1f-d67f-45a7-ab2c-89a6424fafa0.png) 73 | 74 | 75 | 76 | ## 三、结束 77 | 78 | 小程序支付和以往的网页、APP微信支付大同小异,可以说小程序的支付变得更加简洁,不需要设置支付目录、域名授权等操作 79 | 80 | 81 | ## 参考文献 82 | 83 | - https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml 84 | - https://juejin.cn/post/6844903895970349064 -------------------------------------------------------------------------------- /docs/css/box.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对盒子模型的理解? 2 | 3 | ![](https://static.vue-js.com/8d0e9ca0-8f9b-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | ## 一、是什么 7 | 当对一个文档进行布局(layout)的时候,浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box) 8 | 9 | 一个盒子由四个部分组成:`content`、`padding`、`border`、`margin` 10 | 11 | ![](https://static.vue-js.com/976789a0-8f9b-11eb-85f6-6fac77c0c9b3.png) 12 | 13 | `content`,即实际内容,显示文本和图像 14 | 15 | `boreder`,即边框,围绕元素内容的内边距的一条或多条线,由粗细、样式、颜色三部分组成 16 | 17 | `padding`,即内边距,清除内容周围的区域,内边距是透明的,取值不能为负,受盒子的`background`属性影响 18 | 19 | `margin`,即外边距,在元素外创建额外的空白,空白通常指不能放其他元素的区域 20 | 21 | 上述是一个从二维的角度观察盒子,下面再看看看三维图: 22 | 23 | ![](https://static.vue-js.com/b2548b00-8f9b-11eb-ab90-d9ae814b240d.png) 24 | 25 | 26 | 27 | 下面来段代码: 28 | 29 | ```html 30 | 37 |
    38 | 盒子模型 39 |
    40 | ``` 41 | 42 | 当我们在浏览器查看元素时,却发现元素的大小变成了`240px` 43 | 44 | 这是因为,在`CSS`中,盒子模型可以分成: 45 | 46 | - W3C 标准盒子模型 47 | - IE 怪异盒子模型 48 | 49 | 默认情况下,盒子模型为`W3C` 标准盒子模型 50 | 51 | 52 | ## 二、标准盒子模型 53 | 54 | 标准盒子模型,是浏览器默认的盒子模型 55 | 56 | 下面看看标准盒子模型的模型图: 57 | 58 | ![](https://static.vue-js.com/c0e1d2e0-8f9b-11eb-85f6-6fac77c0c9b3.png) 59 | 60 | 从上图可以看到: 61 | 62 | - 盒子总宽度 = width + padding + border + margin; 63 | 64 | - 盒子总高度 = height + padding + border + margin 65 | 66 | 也就是,`width/height` 只是内容高度,不包含 `padding` 和 `border `值 67 | 68 | 所以上面问题中,设置`width`为200px,但由于存在`padding`,但实际上盒子的宽度有240px 69 | 70 | ## 三、IE 怪异盒子模型 71 | 72 | 同样看看IE 怪异盒子模型的模型图: 73 | 74 | ![](https://static.vue-js.com/cfbb3ef0-8f9b-11eb-ab90-d9ae814b240d.png) 75 | 76 | 从上图可以看到: 77 | 78 | - 盒子总宽度 = width + margin; 79 | 80 | - 盒子总高度 = height + margin; 81 | 82 | 也就是,`width/height` 包含了 `padding `和 `border `值 83 | 84 | ## Box-sizing 85 | 86 | CSS 中的 box-sizing 属性定义了引擎应该如何计算一个元素的总宽度和总高度 87 | 88 | 语法: 89 | ```css 90 | box-sizing: content-box|border-box|inherit: 91 | ``` 92 | 93 | - content-box 默认值,元素的 width/height 不包含padding,border,与标准盒子模型表现一致 94 | - border-box 元素的 width/height 包含 padding,border,与怪异盒子模型表现一致 95 | - inherit 指定 box-sizing 属性的值,应该从父元素继承 96 | 97 | 回到上面的例子里,设置盒子为 border-box 模型 98 | 99 | ```html 100 | 108 |
    109 | 盒子模型 110 |
    111 | ``` 112 | 这时候,就可以发现盒子的所占据的宽度为200px 113 | 114 | ## 参考文献 115 | - https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model 116 | - https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-sizing 117 | -------------------------------------------------------------------------------- /docs/css/css_performance.md: -------------------------------------------------------------------------------- 1 | # 面试官:如果要做优化,CSS提高性能的方法有哪些? 2 | 3 | ![](https://static.vue-js.com/c071c820-9fa3-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | 7 | ## 一、前言 8 | 9 | 每一个网页都离不开`css`,但是很多人又认为,`css`主要是用来完成页面布局的,像一些细节或者优化,就不需要怎么考虑,实际上这种想法是不正确的 10 | 11 | 作为页面渲染和内容展现的重要环节,`css`影响着用户对整个网站的第一体验 12 | 13 | 因此,在整个产品研发过程中,`css`性能优化同样需要贯穿全程 14 | 15 | 16 | 17 | ## 二、实现方式 18 | 19 | 20 | 21 | 实现方式有很多种,主要有如下: 22 | 23 | - 内联首屏关键CSS 24 | - 异步加载CSS 25 | - 资源压缩 26 | - 合理使用选择器 27 | - 减少使用昂贵的属性 28 | - 不要使用@import 29 | 30 | ### 内联首屏关键CSS 31 | 32 | 在打开一个页面,页面首要内容出现在屏幕的时间影响着用户的体验,而通过内联`css`关键代码能够使浏览器在下载完`html`后就能立刻渲染 33 | 34 | 而如果外部引用`css`代码,在解析`html`结构过程中遇到外部`css`文件,才会开始下载`css`代码,再渲染 35 | 36 | 所以,`CSS`内联使用使渲染时间提前 37 | 38 | 注意:但是较大的`css`代码并不合适内联(初始拥塞窗口、没有缓存),而其余代码则采取外部引用方式 39 | 40 | 41 | 42 | ### 异步加载CSS 43 | 44 | 在`CSS`文件请求、下载、解析完成之前,`CSS`会阻塞渲染,浏览器将不会渲染任何已处理的内容 45 | 46 | 前面加载内联代码后,后面的外部引用`css`则没必要阻塞浏览器渲染。这时候就可以采取异步加载的方案,主要有如下: 47 | 48 | - 使用javascript将link标签插到head标签最后 49 | 50 | ```js 51 | // 创建link标签 52 | const myCSS = document.createElement( "link" ); 53 | myCSS.rel = "stylesheet"; 54 | myCSS.href = "mystyles.css"; 55 | // 插入到header的最后位置 56 | document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling ); 57 | ``` 58 | 59 | - 设置link标签media属性为noexis,浏览器会认为当前样式表不适用当前类型,会在不阻塞页面渲染的情况下再进行下载。加载完成后,将`media`的值设为`screen`或`all`,从而让浏览器开始解析CSS 60 | 61 | ```html 62 | 63 | ``` 64 | 65 | - 通过rel属性将link元素标记为alternate可选样式表,也能实现浏览器异步加载。同样别忘了加载完成之后,将rel设回stylesheet 66 | 67 | ```html 68 | 69 | ``` 70 | 71 | 72 | 73 | ### 资源压缩 74 | 75 | 利用`webpack`、`gulp/grunt`、`rollup`等模块化工具,将`css`代码进行压缩,使文件变小,大大降低了浏览器的加载时间 76 | 77 | 78 | 79 | ### 合理使用选择器 80 | 81 | `css`匹配的规则是从右往左开始匹配,例如`#markdown .content h3`匹配规则如下: 82 | 83 | - 先找到h3标签元素 84 | - 然后去除祖先不是.content的元素 85 | - 最后去除祖先不是#markdown的元素 86 | 87 | 如果嵌套的层级更多,页面中的元素更多,那么匹配所要花费的时间代价自然更高 88 | 89 | 所以我们在编写选择器的时候,可以遵循以下规则: 90 | 91 | - 不要嵌套使用过多复杂选择器,最好不要三层以上 92 | - 使用id选择器就没必要再进行嵌套 93 | - 通配符和属性选择器效率最低,避免使用 94 | 95 | 96 | 97 | ### 减少使用昂贵的属性 98 | 99 | 在页面发生重绘的时候,昂贵属性如`box-shadow`/`border-radius`/`filter`/透明度/`:nth-child`等,会降低浏览器的渲染性能 100 | 101 | 102 | 103 | ### 不要使用@import 104 | 105 | css样式文件有两种引入方式,一种是`link`元素,另一种是`@import` 106 | 107 | `@import`会影响浏览器的并行下载,使得页面在加载时增加额外的延迟,增添了额外的往返耗时 108 | 109 | 而且多个`@import`可能会导致下载顺序紊乱 110 | 111 | 比如一个css文件`index.css`包含了以下内容:`@import url("reset.css")` 112 | 113 | 那么浏览器就必须先把`index.css`下载、解析和执行后,才下载、解析和执行第二个文件`reset.css` 114 | 115 | 116 | 117 | ### 其他 118 | 119 | - 减少重排操作,以及减少不必要的重绘 120 | - 了解哪些属性可以继承而来,避免对这些属性重复编写 121 | - cssSprite,合成所有icon图片,用宽高加上backgroud-position的背景图方式显现出我们要的icon图,减少了http请求 122 | - 把小的icon图片转成base64编码 123 | - CSS3动画或者过渡尽量使用transform和opacity来实现动画,不要使用left和top属性 124 | 125 | 126 | 127 | ## 三、总结 128 | 129 | `css`实现性能的方式可以从选择器嵌套、属性特性、减少`http`这三面考虑,同时还要注意`css`代码的加载顺序 130 | 131 | 132 | 133 | ## 参考文献 134 | - https://www.zhihu.com/question/19886806 135 | - https://juejin.cn/post/6844903649605320711#heading-1 136 | - https://vue3js.cn/interview/ -------------------------------------------------------------------------------- /docs/css/em_px_rem_vh_vw.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说em/px/rem/vh/vw区别? 2 | 3 | ![](https://static.vue-js.com/51b036e0-9131-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、介绍 6 | 7 | 传统的项目开发中,我们只会用到`px`、`%`、`em`这几个单位,它可以适用于大部分的项目开发,且拥有比较良好的兼容性 8 | 9 | 从`CSS3`开始,浏览器对计量单位的支持又提升到了另外一个境界,新增了`rem`、`vh`、`vw`、`vm`等一些新的计量单位 10 | 11 | 利用这些新的单位开发出比较良好的响应式页面,适应多种不同分辨率的终端,包括移动设备等 12 | 13 | ## 二、单位 14 | 15 | 在`css`单位中,可以分为长度单位、绝对单位,如下表所指示 16 | 17 | | CSS单位 | | 18 | | ------------ | -------------------------------------- | 19 | | 相对长度单位 | em、ex、ch、rem、vw、vh、vmin、vmax、% | 20 | | 绝对长度单位 | cm、mm、in、px、pt、pc | 21 | 22 | 这里我们主要讲述px、em、rem、vh、vw 23 | 24 | 25 | 26 | ### px 27 | 28 | px,表示像素,所谓像素就是呈现在我们显示器上的一个个小点,每个像素点都是大小等同的,所以像素为计量单位被分在了绝对长度单位中 29 | 30 | 有些人会把`px`认为是相对长度,原因在于在移动端中存在设备像素比,`px`实际显示的大小是不确定的 31 | 32 | 这里之所以认为`px`为绝对单位,在于`px`的大小和元素的其他属性无关 33 | 34 | ### em 35 | 36 | em是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(`1em = 16px`) 37 | 38 | 为了简化 `font-size` 的换算,我们需要在` css `中的 `body` 选择器中声明` font-size `= `62.5%`,这就使 em 值变为 `16px*62.5% = 10px` 39 | 40 | 这样 `12px = 1.2em`, `10px = 1em`, 也就是说只需要将你的原来的` px` 数值除以 10,然后换上 `em `作为单位就行了 41 | 42 | 特点: 43 | 44 | - em 的值并不是固定的 45 | - em 会继承父级元素的字体大小 46 | - em 是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸 47 | - 任意浏览器的默认字体高都是 16px 48 | 49 | 50 | 51 | 举个例子 52 | 53 | ```html 54 |
    55 | 我是14px=1.4rem
    我是12px=1.2rem
    56 |
    57 | ``` 58 | 59 | 样式为 60 | 61 | ```css 62 | 67 | ``` 68 | 69 | 这时候`.big`元素的`font-size`为14px,而`.small`元素的`font-size`为12px 70 | 71 | 72 | 73 | 74 | 75 | ### rem 76 | 77 | rem,相对单位,相对的只是HTML根元素`font-size`的值 78 | 79 | 同理,如果想要简化`font-size`的转化,我们可以在根元素`html`中加入`font-size: 62.5%` 80 | 81 | ```css 82 | html {font-size: 62.5%; } /* 公式16px*62.5%=10px */ 83 | ``` 84 | 85 | 这样页面中1rem=10px、1.2rem=12px、1.4rem=14px、1.6rem=16px;使得视觉、使用、书写都得到了极大的帮助 86 | 87 | 特点: 88 | 89 | - rem单位可谓集相对大小和绝对大小的优点于一身 90 | - 和em不同的是rem总是相对于根元素,而不像em一样使用级联的方式来计算尺寸 91 | 92 | 93 | 94 | ### vh、vw 95 | 96 | vw ,就是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,`vh`则为窗口的高度 97 | 98 | 这里的窗口分成几种情况: 99 | 100 | - 在桌面端,指的是浏览器的可视区域 101 | 102 | - 移动端指的就是布局视口 103 | 104 | 像`vw`、`vh`,比较容易混淆的一个单位是`%`,不过百分比宽泛的讲是相对于父元素: 105 | 106 | - 对于普通定位元素就是我们理解的父元素 107 | - 对于position: absolute;的元素是相对于已定位的父元素 108 | - 对于position: fixed;的元素是相对于 ViewPort(可视窗口) 109 | 110 | 111 | 112 | ## 三、总结 113 | 114 | **px**:绝对单位,页面按精确像素展示 115 | 116 | **em**:相对单位,基准点为父节点字体的大小,如果自身定义了`font-size`按自身来计算,整个页面内`1em`不是一个固定的值 117 | 118 | **rem**:相对单位,可理解为`root em`, 相对根节点`html`的字体大小来计算 119 | 120 | **vh、vw**:主要用于页面视口大小布局,在页面布局上更加方便简单 -------------------------------------------------------------------------------- /docs/css/hide_attributes.md: -------------------------------------------------------------------------------- 1 | # 面试官:css中,有哪些方式可以隐藏页面元素?区别? 2 | 3 | ![](https://static.vue-js.com/ccf96f50-929a-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、前言 6 | 7 | 在平常的样式排版中,我们经常遇到将某个模块隐藏的场景 8 | 9 | 通过`css`隐藏元素的方法有很多种,它们看起来实现的效果是一致的 10 | 11 | 但实际上每一种方法都有一丝轻微的不同,这些不同决定了在一些特定场合下使用哪一种方法 12 | 13 | ## 二、实现方式 14 | 15 | 通过`css`实现隐藏元素方法有如下: 16 | 17 | - display:none 18 | - visibility:hidden 19 | - opacity:0 20 | - 设置height、width模型属性为0 21 | - position:absolute 22 | - clip-path 23 | 24 | ### display:none 25 | 26 | 设置元素的`display`为`none`是最常用的隐藏元素的方法 27 | 28 | ```css 29 | .hide { 30 | display:none; 31 | } 32 | ``` 33 | 34 | 将元素设置为`display:none`后,元素在页面上将彻底消失 35 | 36 | 元素本身占有的空间就会被其他元素占有,也就是说它会导致浏览器的重排和重绘 37 | 38 | 消失后,自身绑定的事件不会触发,也不会有过渡效果 39 | 40 | 特点:元素不可见,不占据空间,无法响应点击事件 41 | 42 | ### visibility:hidden 43 | 44 | 设置元素的`visibility`为`hidden`也是一种常用的隐藏元素的方法 45 | 46 | 从页面上仅仅是隐藏该元素,DOM结果均会存在,只是当时在一个不可见的状态,不会触发重排,但是会触发重绘 47 | 48 | ```css 49 | .hidden{ 50 | visibility:hidden 51 | } 52 | ``` 53 | 54 | 给人的效果是隐藏了,所以他自身的事件不会触发 55 | 56 | 特点:元素不可见,占据页面空间,无法响应点击事件 57 | 58 | 59 | ### opacity:0 60 | 61 | `opacity`属性表示元素的透明度,将元素的透明度设置为0后,在我们用户眼中,元素也是隐藏的 62 | 63 | 不会引发重排,一般情况下也会引发重绘 64 | 65 | > 如果利用 animation 动画,对 opacity 做变化(animation会默认触发GPU加速),则只会触发 GPU 层面的 composite,不会触发重绘 66 | 67 | ```css 68 | .transparent { 69 | opacity:0; 70 | } 71 | ``` 72 | 73 | 由于其仍然是存在于页面上的,所以他自身的的事件仍然是可以触发的,但被他遮挡的元素是不能触发其事件的 74 | 75 | 需要注意的是:其子元素不能设置opacity来达到显示的效果 76 | 77 | 特点:改变元素透明度,元素不可见,占据页面空间,可以响应点击事件 78 | 79 | 80 | 81 | ### 设置height、width属性为0 82 | 83 | 将元素的`margin`,`border`,`padding`,`height`和`width`等影响元素盒模型的属性设置成0,如果元素内有子元素或内容,还应该设置其`overflow:hidden`来隐藏其子元素 84 | 85 | ```css 86 | .hiddenBox { 87 | margin:0; 88 | border:0; 89 | padding:0; 90 | height:0; 91 | width:0; 92 | overflow:hidden; 93 | } 94 | ``` 95 | 96 | 特点:元素不可见,不占据页面空间,无法响应点击事件 97 | 98 | 99 | 100 | ### position:absolute 101 | 102 | 将元素移出可视区域 103 | 104 | ```css 105 | .hide { 106 | position: absolute; 107 | top: -9999px; 108 | left: -9999px; 109 | } 110 | ``` 111 | 112 | 特点:元素不可见,不影响页面布局 113 | 114 | 115 | ### clip-path 116 | 117 | 通过裁剪的形式 118 | 119 | ```css 120 | .hide { 121 | clip-path: polygon(0px 0px,0px 0px,0px 0px,0px 0px); 122 | } 123 | ``` 124 | 125 | 特点:元素不可见,占据页面空间,无法响应点击事件 126 | 127 | 128 | ### 小结 129 | 130 | 最常用的还是`display:none`和`visibility:hidden`,其他的方式只能认为是奇招,它们的真正用途并不是用于隐藏元素,所以并不推荐使用它们 131 | 132 | 133 | ## 三、区别 134 | 135 | 关于`display: none`、` visibility: hidden`、`opacity: 0`的区别,如下表所示: 136 | 137 | | | display: none | visibility: hidden | opacity: 0 | 138 | | :--------------------- | :------------ | :----------------- | ---------- | 139 | | 页面中 | 不存在 | 存在 | 存在 | 140 | | 重排 | 会 | 不会 | 不会 | 141 | | 重绘 | 会 | 会 | 不一定 | 142 | | 自身绑定事件 | 不触发 | 不触发 | 可触发 | 143 | | transition | 不支持 | 支持 | 支持 | 144 | | 子元素可复原 | 不能 | 能 | 不能 | 145 | | 被遮挡的元素可触发事件 | 能 | 能 | 不能 | 146 | 147 | 148 | ## 参考文献 149 | 150 | - https://www.cnblogs.com/a-cat/p/9039962.html -------------------------------------------------------------------------------- /docs/css/less_12px.md: -------------------------------------------------------------------------------- 1 | # 面试官:让Chrome支持小于12px 的文字方式有哪些?区别? 2 | 3 | ![](https://static.vue-js.com/62945fd0-a334-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、背景 6 | 7 | Chrome 中文版浏览器会默认设定页面的最小字号是12px,英文版没有限制 8 | 9 | 原由 Chrome 团队认为汉字小于12px就会增加识别难度 10 | 11 | - 中文版浏览器 12 | 13 | 与网页语言无关,取决于用户在Chrome的设置里(chrome://settings/languages)把哪种语言设置为默认显示语言 14 | 15 | - 系统级最小字号 16 | 17 | 浏览器默认设定页面的最小字号,用户可以前往 chrome://settings/fonts 根据需求更改 18 | 19 | 而我们在实际项目中,不能奢求用户更改浏览器设置 20 | 21 | 对于文本需要以更小的字号来显示,就需要用到一些小技巧 22 | 23 | 24 | ## 二、解决方案 25 | 26 | 常见的解决方案有: 27 | 28 | - zoom 29 | - -webkit-transform:scale() 30 | - -webkit-text-size-adjust:none 31 | 32 | ### Zoom 33 | 34 | `zoom` 的字面意思是“变焦”,可以改变页面上元素的尺寸,属于真实尺寸 35 | 36 | 其支持的值类型有: 37 | 38 | - zoom:50%,表示缩小到原来的一半 39 | - zoom:0.5,表示缩小到原来的一半 40 | 41 | 使用 `zoom` 来”支持“ 12px 以下的字体 42 | 43 | 代码如下: 44 | 45 | ```html 46 | 57 | 58 | 测试10px 59 | 测试12px 60 | 61 | ``` 62 | 63 | 效果如下: 64 | 65 | ![](https://static.vue-js.com/d5243980-a334-11eb-ab90-d9ae814b240d.png) 66 | 67 | > 需要注意的是,`Zoom` 并不是标准属性,需要考虑其兼容性 68 | 69 | ![image.png](https://static.vue-js.com/3defe3c0-a343-11eb-85f6-6fac77c0c9b3.png) 70 | 71 | 72 | ### -webkit-transform:scale() 73 | 74 | 针对`chrome`浏览器,加`webkit`前缀,用`transform:scale()`这个属性进行放缩 75 | 76 | 注意的是,使用`scale`属性只对可以定义宽高的元素生效,所以,下面代码中将`span`元素转为行内块元素 77 | 78 | 实现代码如下: 79 | 80 | ```html 81 | 92 | 93 | 测试10px 94 | 测试12px 95 | 96 | ``` 97 | 98 | 效果如下: 99 | 100 | ![](https://static.vue-js.com/d5243980-a334-11eb-ab90-d9ae814b240d.png) 101 | 102 | 103 | ### -webkit-text-size-adjust:none 104 | 105 | 该属性用来设定文字大小是否根据设备(浏览器)来自动调整显示大小 106 | 107 | 属性值: 108 | 109 | - percentage:字体显示的大小; 110 | - auto:默认,字体大小会根据设备/浏览器来自动调整; 111 | - none:字体大小不会自动调整 112 | 113 | ```css 114 | html { -webkit-text-size-adjust: none; } 115 | ``` 116 | 117 | 这样设置之后会有一个问题,就是当你放大网页时,一般情况下字体也会随着变大,而设置了以上代码后,字体只会显示你当前设置的字体大小,不会随着网页放大而变大了 118 | 119 | 所以,我们不建议全局应用该属性,而是单独对某一属性使用 120 | 121 | > 需要注意的是,自从`chrome 27`之后,就取消了对这个属性的支持。同时,该属性只对英文、数字生效,对中文不生效 122 | 123 | ## 三、总结 124 | 125 | `Zoom` 非标属性,有兼容问题,缩放会改变了元素占据的空间大小,触发重排 126 | 127 | `-webkit-transform:scale()` 大部分现代浏览器支持,并且对英文、数字、中文也能够生效,缩放不会改变了元素占据的空间大小,页面布局不会发生变化 128 | 129 | `-webkit-text-size-adjust`对谷歌浏览器有版本要求,在27之后,就取消了该属性的支持,并且只对英文、数字生效 130 | 131 | ## 参考文献 132 | 133 | - https://developer.mozilla.org/zh-CN/docs/Web/CSS/text-size-adjust 134 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/design/design.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说对设计模式的理解?常见的设计模式有哪些? 2 | 3 | ![](https://static.vue-js.com/065bc170-37ce-11ec-a752-75723a64e8f5.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | 在软件工程中,设计模式是对软件设计中普遍存在的各种问题所提出的解决方案 10 | 11 | 设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案 12 | 13 | 设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力 14 | 15 | 因此,当我们遇到合适的场景时,我们可能会条件反射一样自然而然想到符合这种场景的设计模式 16 | 17 | 比如,当系统中某个接口的结构已经无法满足我们现在的业务需求,但又不能改动这个接口,因为可能原来的系统很多功能都依赖于这个接口,改动接口会牵扯到太多文件 18 | 19 | 因此应对这种场景,我们可以很快地想到可以用适配器模式来解决这个问题 20 | 21 | 22 | 23 | ## 二、有哪些 24 | 25 | 常见的设计模式有: 26 | 27 | - 单例模式 28 | - 工厂模式 29 | - 策略模式 30 | - 代理模式 31 | - 中介者模式 32 | - 装饰者模式 33 | - ...... 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ### 单例模式 42 | 43 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象 44 | 45 | 如下图的车,只有一辆,一旦借出去则不能再借给别人: 46 | 47 | ![](https://static.vue-js.com/ea527aa0-37cd-11ec-8e64-91fdec0f05a1.png) 48 | 49 | 50 | 51 | 52 | 53 | ### 工厂模式 54 | 55 | 工厂模式通常会分成3个角色: 56 | 57 | - 工厂角色-负责实现创建所有实例的内部逻辑. 58 | - 抽象产品角色-是所创建的所有对象的父类,负责描述所有实例所共有的公共接口 59 | - 具体产品角色-是创建目标,所有创建的对象都充当这个角色的某个具体类的实例 60 | 61 | ![](https://static.vue-js.com/fadd1920-37cd-11ec-8e64-91fdec0f05a1.png) 62 | 63 | 64 | 65 | ### 策略模式 66 | 67 | 策略模式,就是定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换 68 | 69 | 至少分成两部分: 70 | 71 | - 策略类(可变),策略类封装了具体的算法,并负责具体的计算过程 72 | - 环境类(不变),接受客户的请求,随后将请求委托给某一个策略类 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | ### 代理模式 81 | 82 | 代理模式:为对象提供一个代用品或占位符,以便控制对它的访问 83 | 84 | 例如实现图片懒加载的功能,先通过一张`loading`图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到`img`标签里面 85 | 86 | 87 | 88 | ### 中介者模式 89 | 90 | 中介者模式的定义:通过一个中介者对象,其他所有的相关对象都通过该中介者对象来通信,而不是相互引用,当其中的一个对象发生改变时,只需要通知中介者对象即可 91 | 92 | 通过中介者模式可以解除对象与对象之间的紧耦合关系 93 | 94 | 95 | 96 | ### 装饰者模式 97 | 98 | 装饰者模式的定义:在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法 99 | 100 | 通常运用在原有方法维持不变,在原有方法上再挂载其他方法来满足现有需求 101 | 102 | 103 | 104 | ## 三、总结 105 | 106 | 不断去学习设计模式,会对我们有着极大的帮助,主要如下: 107 | 108 | - 从许多优秀的软件系统中总结出的成功的、能够实现可维护性、复用的设计方案,使用这些方案将可以让我们避免做一些重复性的工作 109 | - 设计模式提供了一套通用的设计词汇和一种通用的形式来方便开发人员之间沟通和交流,使得设计方案更加通俗易懂 110 | 111 | - 大部分设计模式都兼顾了系统的可重用性和可扩展性,这使得我们可以更好地重用一些已有的设计方案、功能模块甚至一个完整的软件系统,避免我们经常做一些重复的设计、编写一些重复的代码 112 | 113 | - 合理使用设计模式并对设计模式的使用情况进行文档化,将有助于别人更快地理解系统 114 | 115 | - 学习设计模式将有助于初学者更加深入地理解面向对象思想 116 | 117 | 118 | ## 参考文献 119 | 120 | - https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA) 121 | - https://juejin.cn/post/6844903795017646094 122 | - https://segmentfault.com/a/1190000030850326 -------------------------------------------------------------------------------- /docs/git/Git.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对Git的理解? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/213eba50-f79c-11eb-bc6f-3f06e1491664.png) 6 | 7 | ## 一、是什么 8 | 9 | git,是一个分布式版本控制软件,最初目的是为更好地管理`Linux`内核开发而设计 10 | 11 | 分布式版本控制系统的客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复 12 | 13 | ![](https://static.vue-js.com/29240f40-f79c-11eb-991d-334fd31f0201.png) 14 | 15 | 项目开始,只有一个原始版仓库,别的机器可以`clone`这个原始版本库,那么所有`clone`的机器,它们的版本库其实都是一样的,并没有主次之分 16 | 17 | 所以在实现团队协作的时候,只要有一台电脑充当服务器的角色,其他每个人都从这个“服务器”仓库`clone`一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交 18 | 19 | `github`实际就可以充当这个服务器角色,其是一个开源协作社区,提供`Git`仓库托管服务,既可以让别人参与你的开源项目,也可以参与别人的开源项目 20 | 21 | 22 | 23 | ## 二、工作原理 24 | 25 | 当我们通过`git init`创建或者`git clone`一个项目的时候,项目目录会隐藏一个`.git`子目录,其作用是用来跟踪管理版本库的 26 | 27 | `Git` 中所有数据在存储前都计算校验和,然后以校验和来引用,所以在我们修改或者删除文件的时候,`git`能够知道 28 | 29 | `Git `用以计算校验和的机制叫做 SHA-1 散列(hash,哈希), 这是一个由 40 个十六进制字符(0-9 和 a-f)组成字符串,基于 Git 中文件的内容或目录结构计算出来,如下: 30 | 31 | ```text 32 | 24b9da6552252987aa493b52f8696cd6d3b00373 33 | ``` 34 | 35 | 当我们修改文件的时候,`git`就会修改文件的状态,可以通过`git status`进行查询,状态情况如下: 36 | 37 | - 已修改(modified):表示修改了文件,但还没保存到数据库中。 38 | - 已暂存(staged):表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。 39 | - 已提交(committed):表示数据已经安全的保存在本地数据库中。 40 | 41 | 文件状态对应的,不同状态的文件在` Git `中处于不同的工作区域,主要分成了四部分: 42 | 43 | - 工作区:相当于本地写代码的区域,如 git clone 一个项目到本地,相当于本地克隆了远程仓库项目的一个副本 44 | - 暂存区:暂存区是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中 45 | - 本地仓库:提交更新,找到暂存区域的文件,将快照永久性存储到 Git 本地仓库 46 | - 远程仓库:远程的仓库,如 github 47 | 48 | ![](https://static.vue-js.com/3273c9a0-f79c-11eb-bc6f-3f06e1491664.png) 49 | 50 | 51 | 52 | ## 三、命令 53 | 54 | 从上图可以看到,`git`日常简单的使用就只有上图6个命令: 55 | 56 | - add 57 | - commit 58 | - push 59 | - pull 60 | - clone 61 | - checkout 62 | 63 | 但实际上还有很多命令,如果想要熟练使用,还有60个多命令,通过这些命令的配合使用,能够提高个人工作效率和团队协助能力 64 | 65 | 66 | 67 | ## 参考文献 68 | 69 | - https://zh.wikipedia.org/wiki/Git 70 | - https://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html -------------------------------------------------------------------------------- /docs/git/HEAD_tree_index.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说Git 中 HEAD、工作树和索引之间的区别? 2 | 3 | ![](https://static.vue-js.com/2de056a0-fa40-11eb-991d-334fd31f0201.png) 4 | 5 | ## 一、HEAD 6 | 7 | 在`git`中,可以存在很多分支,其本质上是一个指向`commit`对象的可变指针,而`Head`是一个特别的指针,是一个指向你正在工作中的本地分支的指针 8 | 9 | 简单来讲,就是你现在在哪儿,HEAD 就指向哪儿 10 | 11 | 例如当前我们处于`master`分支,所以`HEAD`这个指针指向了`master`分支指针 12 | 13 | ![](https://static.vue-js.com/36cb0da0-fa40-11eb-991d-334fd31f0201.png) 14 | 15 | 然后通过调用`git checkout test`切换到`test`分支,那么`HEAD`则指向`test`分支,如下图: 16 | 17 | ![](https://static.vue-js.com/3e86ba80-fa40-11eb-991d-334fd31f0201.png) 18 | 19 | 但我们在`test`分支再一次`commit`信息的时候,`HEAD`指针仍然指向了`test`分支指针,而`test`分支指针已经指向了最新创建的提交,如下图: 20 | 21 | ![](https://static.vue-js.com/439839b0-fa66-11eb-991d-334fd31f0201.png) 22 | 23 | 这个`HEAD`存储的位置就在`.git/HEAD`目录中,查看信息可以看到`HEAD`指向了另一个文件 24 | 25 | ```cmd 26 | $ cat .git/HEAD 27 | ref: refs/heads/master 28 | 29 | $ cat .git/refs/heads/master 30 | 7406a10efcc169bbab17827aeda189aa20376f7f 31 | ``` 32 | 33 | 这个文件的内容是一串哈希码,而这个哈希码正是`master`分支上最新的提交所对应的哈希码 34 | 35 | 所以,当我们切换分支的时候,`HEAD`指针通常指向我们所在的分支,当我们在某个分支上创建新的提交时,分支指针总是会指向当前分支的最新提交 36 | 37 | 所以,HEAD指针 ——–> 分支指针 ——–> 最新提交 38 | 39 | 40 | 41 | ## 二、工作树和索引 42 | 43 | 在`Git`管理下,大家实际操作的目录被称为工作树,也就是工作区域 44 | 45 | 在数据库和工作树之间有索引,索引是为了向数据库提交作准备的区域,也被称为暂存区域 46 | 47 | ![](https://static.vue-js.com/46e5ac40-fa40-11eb-bc6f-3f06e1491664.png) 48 | 49 | `Git`在执行提交的时候,不是直接将工作树的状态保存到数据库,而是将设置在中间索引区域的状态保存到数据库 50 | 51 | 因此,要提交文件,首先需要把文件加入到索引区域中。 52 | 53 | 所以,凭借中间的索引,可以避免工作树中不必要的文件提交,还可以将文件修改内容的一部分加入索引区域并提交 54 | 55 | 56 | 57 | ## 三、区别 58 | 59 | 从所在的位置来看: 60 | 61 | - HEAD 指针通常指向我们所在的分支,当我们在某个分支上创建新的提交时,分支指针总是会指向当前分支的最新提交 62 | 63 | - 工作树是查看和编辑的(源)文件的实际内容 64 | 65 | - 索引是放置你想要提交给 git仓库文件的地方,如工作树的代码通过 git add 则添加到 git 索引中,通过git commit 则将索引区域的文件提交到 git 仓库中 66 | 67 | 68 | 69 | 70 | ## 参考文献 71 | 72 | - https://backlog.com/git-tutorial/cn/intro/intro1_4.html 73 | - https://juejin.cn/post/6844903598522908686 74 | - https://www.zsythink.net/archives/3412 -------------------------------------------------------------------------------- /docs/git/Version control.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对版本管理的理解?常用的版本管理工具有哪些? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/f0e8a2d0-f5ac-11eb-ab90-d9ae814b240d.png) 6 | 7 | ## 一、是什么 8 | 9 | 版本控制(Version control),是维护工程蓝图的标准作法,能追踪工程蓝图从诞生一直到定案的过程。此外,版本控制也是一种软件工程技巧,借此能在软件开发的过程中,确保由不同人所编辑的同一程序文件都得到同步 10 | 11 | 透过文档控制,能记录任何工程项目内各个模块的改动历程,并为每次改动编上序号 12 | 13 | 一种简单的版本控制形式如下:赋给图的初版一个版本等级“A”。当做了第一次改变后,版本等级改为“B”,以此类推 14 | 15 | 版本控制能提供项目的设计者,将设计恢复到之前任一状态的选择权 16 | 17 | 简言之,你的修改只要提到到版本控制系统,基本都可以找回,版本控制系统就像一台时光机器,可以让你回到任何一个时间点 18 | 19 | 20 | 21 | 22 | 23 | ## 二、有哪些 24 | 25 | 版本控制系统在当今的软件开发中,被认为是理所当然的配备工具之一,根据类别可以分成: 26 | 27 | - 本地版本控制系统 28 | - 集中式版本控制系统 29 | - 分布式版本控制系统 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ### 本地版本控制系统 38 | 39 | 结构如下图所示: 40 | 41 | ![](https://static.vue-js.com/c545ded0-f5ad-11eb-ab90-d9ae814b240d.png) 42 | 43 | 优点: 44 | 45 | - 简单,很多系统中都有内置 46 | - 适合管理文本,如系统配置 47 | 48 | 缺点: 49 | 50 | - 其不支持远程操作,因此并不适合多人版本开发 51 | 52 | 53 | 54 | ### 集中式版本控制系统 55 | 56 | 结构如下图所示: 57 | 58 | ![](https://static.vue-js.com/8b4b3040-f5ad-11eb-85f6-6fac77c0c9b3.png) 59 | 60 | 优点: 61 | 62 | - 适合多人团队协作开发 63 | - 代码集中化管理 64 | 65 | 缺点: 66 | 67 | - 单点故障 68 | - 必须联网,无法单机工作 69 | 70 | 71 | 72 | 73 | 74 | 代表工具有`SVN`、`CVS`: 75 | 76 | ### SVN 77 | 78 | `TortoiseSVN`是一款非常易于使用的跨平台的 版本控制/版本控制/源代码控制软件 79 | 80 | 81 | 82 | ### CVS 83 | 84 | `CVS`是版本控制系统,是源配置管理(SCM)的重要组成部分。使用它,您可以记录源文件和文档的历史记录 85 | 86 | 老牌的版本控制系统,它是基于客户端/服务器的行为使得其可容纳多用户,构成网络也很方便 87 | 88 | 这一特性使得`CVS`成为位于不同地点的人同时处理数据文件(特别是程序的源代码)时的首选 89 | 90 | 91 | 92 | 93 | 94 | #### 分布式版本控制系统 95 | 96 | 结构如下图: 97 | 98 | ![](https://static.vue-js.com/4301a260-f5ad-11eb-85f6-6fac77c0c9b3.png) 99 | 100 | 101 | 102 | 优点: 103 | 104 | - 适合多人团队协作开发 105 | - 代码集中化管理 106 | - 可以离线工作 107 | - 每个计算机都是一个完整仓库 108 | 109 | 分布式版本管理系统每个计算机都有一个完整的仓库,可本地提交,可以做到离线工作,则不用像集中管理那样因为断网情况而无法工作 110 | 111 | 112 | 113 | 代表工具为`Git`、`HG`: 114 | 115 | ### Git 116 | 117 | `Git`是目前世界上最先进的分布式版本控制系统,旨在快速高效地处理从小型到大型项目的所有事务 118 | 119 | 特性:易于学习,占用内存小,具有闪电般快速的性能 120 | 121 | 使用`Git`和`Gitlab`搭建版本控制环境是现在互联网公司最流行的版本控制方式 122 | 123 | 124 | 125 | ### HG 126 | 127 | `Mercurial`是一个免费的分布式源代码管理工具。它可以有效地处理任何规模的项目,并提供简单直观的界面 128 | 129 | `Mercurial `是一种轻量级分布式版本控制系统,采用 `Python `语言实现,易于学习和使用,扩展性强 130 | 131 | 132 | 133 | 134 | 135 | ## 三、总结 136 | 137 | 版本控制系统的优点如下: 138 | 139 | - 记录文件所有历史变化,这是版本控制系统的基本能力 140 | - 随时恢复到任意时间点,历史记录功能使我们不怕改错代码了 141 | - 支持多功能并行开发,通常版本控制系统都支持分支,保证了并行开发的可行 142 | - 多人协作并行开发,对于多人协作项目,支持多人协作开发的版本管理将事半功倍 143 | 144 | 145 | 146 | ## 参考文献 147 | 148 | - https://pm.readthedocs.io/vcs/understanding.html 149 | - https://zh.wikipedia.org/wiki/%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6 -------------------------------------------------------------------------------- /docs/git/conflict.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说 git 发生冲突的场景?如何解决? 2 | 3 | ![](https://static.vue-js.com/8aeccc40-fdb3-11eb-bc6f-3f06e1491664.png) 4 | 5 | ## 一、是什么 6 | 7 | 一般情况下,出现分支的场景有如下: 8 | 9 | - 多个分支代码合并到一个分支时 10 | - 多个分支向同一个远端分支推送 11 | 12 | 具体情况就是,多个分支修改了同一个文件(任何地方)或者多个分支修改了同一个文件的名称 13 | 14 | 如果两个分支中分别修改了不同文件中的部分,是不会产生冲突,直接合并即可 15 | 16 | 应用在命令中,就是`push`、`pull`、`stash`、`rebase`等命令下都有可能产生冲突情况,从本质上来讲,都是`merge`和`patch`(应用补丁)时产生冲突 17 | 18 | 19 | 20 | ## 二、分析 21 | 22 | 在本地主分值`master`创建一个`a.txt`文件,文件起始位置写上`master commit`,如下: 23 | 24 | ![](https://static.vue-js.com/959ade20-fdb3-11eb-991d-334fd31f0201.png) 25 | 26 | 然后提交到仓库: 27 | 28 | - git add a.txt 29 | - git commit -m 'master first commit' 30 | 31 | 创建一个新的分支`featurel1`分支,并进行切换,如下: 32 | 33 | ```cmd 34 | git checkout -b featurel1 35 | ``` 36 | 37 | 然后修改`a.txt`文件首行文字为 `featurel commit`,然后添加到暂存区,并开始进行提交到仓库: 38 | 39 | - git add a.txt 40 | - git commit -m 'featurel first change' 41 | 42 | 然后通过`git checkout master`切换到主分支,通过`git merge`进行合并,发现不会冲突 43 | 44 | 此时`a.txt`文件的内容变成`featurel commit`,没有出现冲突情况,这是因为`git`在内部发生了快速合并 45 | 46 | > 如果当前分支的每一个提交(commit)都已经存在另一个分支里了,git 就会执行一个“快速向前”(fast forward)操作 47 | > 48 | > git 不创建任何新的提交(commit),只是将当前分支指向合并进来的分支 49 | 50 | 如果此时切换到`featurel`分支,将文件的内容修改成`featrue second commit`,然后提交到本地仓库 51 | 52 | 然后切换到主分支,如果此时在`a.txt`文件再次修改,修改成`mastet second commit`,然后再次提交到本地仓库 53 | 54 | 此时,`master`分支和`feature1`分支各自都分别有新的提交,变成了下图所示: 55 | 56 | ![](https://static.vue-js.com/a05488c0-fdb3-11eb-991d-334fd31f0201.png) 57 | 58 | 这种情况下,无法执行快速合并,只能试图把各自的修改合并起来,但这种合并就可能会有冲突 59 | 60 | 现在通过`git merge featurel`进行分支合并,如下所示: 61 | 62 | ![](https://static.vue-js.com/b0991d90-fdb3-11eb-bc6f-3f06e1491664.png) 63 | 64 | 从冲突信息可以看到,`a.txt`发生冲突,必须手动解决冲突之后再提交 65 | 66 | 而`git status`同样可以告知我们冲突的文件: 67 | 68 | ![](https://static.vue-js.com/c5823430-fdb3-11eb-991d-334fd31f0201.png) 69 | 70 | 打开`a.txt`文件,可以看到如下内容: 71 | 72 | ![](https://static.vue-js.com/ce7a0a90-fdb3-11eb-bc6f-3f06e1491664.png) 73 | 74 | `git`用`<<<<<<<`,`=======`,`>>>>>>>`标记出不同分支的内容: 75 | 76 | - <<<<<<< 和 ======= 之间的区域就是当前更改的内容 77 | - ======= 和 >>>>>>> 之间的区域就是传入进来更改的内容 78 | 79 | 现在要做的事情就是将冲突的内容进行更改,对每个文件使用 `git add` 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,`Git `就会将它们标记为冲突已解决然后再提交: 80 | 81 | - git add a.txt 82 | - git commit -m "conflict fixed" 83 | 84 | 此时`master`分支和`feature1`分支变成了下图所示: 85 | 86 | ![](https://static.vue-js.com/d7421e60-fdb3-11eb-bc6f-3f06e1491664.png) 87 | 88 | 使用`git log`命令可以看到合并的信息: 89 | 90 | ![](https://static.vue-js.com/e0dfd1b0-fdb3-11eb-991d-334fd31f0201.png) 91 | 92 | 93 | 94 | 95 | 96 | ## 三、总结 97 | 98 | 当`Git`无法自动合并分支时,就必须首先解决冲突,解决冲突后,再提交,合并完成 99 | 100 | 解决冲突就是把`Git`合并失败的文件手动编辑为我们希望的内容,再提交 101 | 102 | 103 | 104 | ## 参考文献 105 | 106 | - https://www.liaoxuefeng.com/wiki/896043488029600/900004111093344 -------------------------------------------------------------------------------- /docs/git/fork_clone_branch.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说Git中 fork, clone,branch这三个概念,有什么区别? 2 | 3 | ![](https://static.vue-js.com/9c4eb9a0-f7ad-11eb-bc6f-3f06e1491664.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | ### fork 10 | 11 | `fork`,英语翻译过来就是叉子,动词形式则是分叉,如下图,从左到右,一条直线变成多条直线 12 | 13 | ![](https://static.vue-js.com/ad04ade0-f7ad-11eb-991d-334fd31f0201.png) 14 | 15 | 转到`git`仓库中,`fork`则可以代表分叉、克隆 出一个(仓库的)新拷贝 16 | 17 | ![](https://static.vue-js.com/b4b31450-f7ad-11eb-991d-334fd31f0201.png) 18 | 19 | 包含了原来的仓库(即upstream repository,上游仓库)所有内容,如分支、Tag、提交 20 | 21 | 如果想将你的修改合并到原项目中时,可以通过的 Pull Request 把你的提交贡献回 原仓库 22 | 23 | ### clone 24 | 25 | `clone`,译为克隆,它的作用是将文件从远程代码仓下载到本地,从而形成一个本地代码仓 26 | 27 | 执行`clone`命令后,会在当前目录下创建一个名为`xxx`的目录,并在这个目录下初始化一个 `.git` 文件夹,然后从中读取最新版本的文件的拷贝 28 | 29 | 默认配置下远程 `Git` 仓库中的每一个文件的每一个版本都将被拉取下来 30 | 31 | ### branch 32 | 33 | `branch`,译为分支,其作用简单而言就是开启另一个分支, 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线 34 | 35 | ` Git` 处理分支的方式十分轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷 36 | 37 | 在我们开发中,默认只有一条`master`分支,如下图所示: 38 | 39 | ![](https://static.vue-js.com/7fa8e9c0-f923-11eb-991d-334fd31f0201.png) 40 | 41 | 通过`git branch `可以创建一个分支,但并不会自动切换到新分支中去 42 | 43 | ![](https://static.vue-js.com/89efd560-f923-11eb-bc6f-3f06e1491664.png) 44 | 45 | 通过`git checkout`可以切换到另一个`testing`分支 46 | 47 | ![](https://static.vue-js.com/91d1cef0-f923-11eb-bc6f-3f06e1491664.png) 48 | 49 | 50 | ## 二、如何使用 51 | 52 | ### fork 53 | 54 | 当你在`github`发现感兴趣开源项目的时候,可以通过点击`github`仓库中右上角`fork`标识的按钮,如下图: 55 | 56 | ![](https://static.vue-js.com/bc4c4510-f7ad-11eb-991d-334fd31f0201.png) 57 | 58 | 点击这个操作后会将这个仓库的文件、提交历史、issues和其余东西的仓库复制到自己的`github`仓库中,而你本地仓库是不会存在任何更改 59 | 60 | 然后你就可以通过`git clone`对你这个复制的远程仓库进行克隆 61 | 62 | 后续更改任何东西都可以在本地完成,如`git add`、`git commit`一系列的操作,然后通过`push`命令推到自己的远程仓库 63 | 64 | 如果希望对方接受你的修改,可以通过发送`pull requests`给对方,如果对方接受。则会将你的修改内容更新到仓库中 65 | 66 | ![](https://static.vue-js.com/c5265a40-f7ad-11eb-991d-334fd31f0201.png) 67 | 68 | 整体流程如下图: 69 | 70 | ![](https://static.vue-js.com/ced8ce10-f7ad-11eb-bc6f-3f06e1491664.png) 71 | 72 | 73 | ### clone 74 | 75 | 在`github`中,开源项目右侧存在`code`按钮,点击后则会显示开源项目`url`信息,如下图所示: 76 | 77 | ![](https://static.vue-js.com/d8685090-f7ad-11eb-bc6f-3f06e1491664.png) 78 | 79 | 通过`git clone xxx`则能完成远程项目的下载 80 | 81 | 82 | ### branch 83 | 84 | 可通过`git branch`进行查看当前的分支状态, 85 | 86 | 如果给了`--list`,或者没有非选项参数,现有的分支将被列出;当前的分支将以绿色突出显示,并标有星号 87 | 88 | 以及通过`git branch`创建一个新的分支出来 89 | 90 | 91 | ## 三、区别 92 | 93 | 其三者区别如下: 94 | 95 | - fork 只能对代码仓进行操作,且 fork 不属于 git 的命令,通常用于代码仓托管平台的一种“操作” 96 | - clone 是 git 的一种命令,它的作用是将文件从远程代码仓下载到本地,从而形成一个本地代码仓 97 | - branch 特征与 fork 很类似,fork 得到的是一个新的、自己的代码仓,而 branch 得到的是一个代码仓的一个新分支 98 | 99 | ## 参考文献 100 | 101 | - https://git-scm.com/book/zh/v2/Git-基础-获取-Git-仓库 102 | - https://git-scm.com/book/zh/v2/Git-分支-分支简介 103 | -------------------------------------------------------------------------------- /docs/git/git pull _git fetch.md: -------------------------------------------------------------------------------- 1 | # 说说对git pull 和 git fetch 的理解?有什么区别? 2 | 3 | ![](https://static.vue-js.com/cc90c050-fac2-11eb-991d-334fd31f0201.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 先回顾两个命令的定义 9 | - git fetch 命令用于从另一个存储库下载对象和引用 10 | - git pull 命令用于从另一个存储库或本地分支获取并集成(整合) 11 | 12 | 再来看一次`git`的工作流程图,如下所示: 13 | 14 | ![](https://static.vue-js.com/d523ba60-fac2-11eb-991d-334fd31f0201.png) 15 | 16 | 可以看到,`git fetch`是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中 17 | 18 | 而`git pull` 则是将远程主机的最新内容拉下来后直接合并,即:`git pull = git fetch + git merge`,这样可能会产生冲突,需要手动解决 19 | 20 | 在我们本地的`git`文件中对应也存储了`git`本地仓库分支的`commit ID `和 跟踪的远程分支的`commit ID`,对应文件如下: 21 | 22 | - .git/refs/head/[本地分支] 23 | - .git/refs/remotes/[正在跟踪的分支] 24 | 25 | 使用 `git fetch`更新代码,本地的库中`master`的`commitID`不变 26 | 27 | 但是与`git`上面关联的那个`orign/master`的`commit ID`发生改变 28 | 29 | 这时候我们本地相当于存储了两个代码的版本号,我们还要通过`merge`去合并这两个不同的代码版本 30 | 31 | ![](https://static.vue-js.com/fd23ff70-fb12-11eb-bc6f-3f06e1491664.png) 32 | 33 | 也就是`fetch`的时候本地的`master`没有变化,但是与远程仓关联的那个版本号被更新了,接下来就是在本地`merge`合并这两个版本号的代码 34 | 35 | 相比之下,使用`git pull`就更加简单粗暴,会将本地的代码更新至远程仓库里面最新的代码版本,如下图: 36 | 37 | ![](https://static.vue-js.com/091b8140-fb13-11eb-bc6f-3f06e1491664.png) 38 | 39 | 40 | 41 | 42 | 43 | ## 二、用法 44 | 45 | 一般远端仓库里有新的内容更新,当我们需要把新内容下载的时候,就使用到`git pull`或者`git fetch`命令 46 | 47 | ### fetch 48 | 49 | 用法如下: 50 | 51 | ```cmd 52 | git fetch <远程主机名> <远程分支名>:<本地分支名> 53 | ``` 54 | 55 | 例如从远程的`origin`仓库的`master`分支下载代码到本地并新建一个`temp`分支 56 | 57 | ```cmd 58 | git fetch origin master:temp 59 | ``` 60 | 61 | 如果上述没有冒号,则表示将远程`origin`仓库的`master`分支拉取下来到本地当前分支 62 | 63 | 这里`git fetch`不会进行合并,执行后需要手动执行`git merge`合并,如下: 64 | 65 | ```cmd 66 | git merge temp 67 | ``` 68 | 69 | 70 | 71 | ### pull 72 | 73 | 两者的用法十分相似,`pull`用法如下: 74 | 75 | ```cmd 76 | git pull <远程主机名> <远程分支名>:<本地分支名> 77 | ``` 78 | 79 | 例如将远程主机`origin`的`master`分支拉取过来,与本地的`branchtest`分支合并,命令如下: 80 | 81 | ```cmd 82 | git pull origin master:branchtest 83 | ``` 84 | 85 | 同样如果上述没有冒号,则表示将远程`origin`仓库的`master`分支拉取下来与本地当前分支合并 86 | 87 | 88 | 89 | ## 三、区别 90 | 91 | 相同点: 92 | 93 | - 在作用上他们的功能是大致相同的,都是起到了更新代码的作用 94 | 95 | 不同点: 96 | 97 | - git pull是相当于从远程仓库获取最新版本,然后再与本地分支merge,即git pull = git fetch + git merge 98 | - 相比起来,git fetch 更安全也更符合实际要求,在 merge 前,我们可以查看更新情况,根据实际情况再决定是否合并 99 | 100 | 101 | 102 | ## 参考文献 103 | 104 | - https://zhuanlan.zhihu.com/p/123370920 105 | - https://segmentfault.com/a/1190000017030384 106 | - https://juejin.cn/post/6844903921794859021 107 | -------------------------------------------------------------------------------- /docs/git/git rebase_ git merge.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对git rebase 和 git merge的理解?区别? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/77590970-fdd4-11eb-bc6f-3f06e1491664.png) 6 | 7 | ## 一、是什么 8 | 9 | 在使用 `git` 进行版本管理的项目中,当完成一个特性的开发并将其合并到 `master` 分支时,会有两种方式: 10 | 11 | - git merge 12 | - git rebase 13 | 14 | `git rebase` 与 `git merge`都有相同的作用,都是将一个分支的提交合并到另一分支上,但是在原理上却不相同 15 | 16 | 17 | 18 | 用法上两者也十分的简单: 19 | 20 | ### git merge 21 | 22 | 将当前分支合并到指定分支,命令用法如下: 23 | 24 | ```cmd 25 | git merge xxx 26 | ``` 27 | 28 | 29 | 30 | ### git rebase 31 | 32 | 将当前分支移植到指定分支或指定`commit`之上,用法如下: 33 | 34 | ```cmd 35 | git rebase -i 36 | ``` 37 | 38 | 常见的参数有`--continue`,用于解决冲突之后,继续执行`rebase` 39 | 40 | ```cmd 41 | git rebase --continue 42 | ``` 43 | 44 | 45 | 46 | 47 | 48 | ## 二、分析 49 | 50 | ### git merge 51 | 52 | 通过`git merge`将当前分支与`xxx`分支合并,产生的新的`commit`对象有两个父节点 53 | 54 | 如果“指定分支”本身是当前分支的一个直接子节点,则会产生快照合并 55 | 56 | 举个例子,`bugfix`分支是从`master`分支分叉出来的,如下所示: 57 | 58 | ![](https://static.vue-js.com/88410a30-fdd4-11eb-991d-334fd31f0201.png) 59 | 60 | 合并` bugfix`分支到`master`分支时,如果`master`分支的状态没有被更改过,即 `bugfix`分支的历史记录包含`master`分支所有的历史记录 61 | 62 | 所以通过把`master`分支的位置移动到`bugfix`的最新分支上,就完成合并 63 | 64 | 如果`master`分支的历史记录在创建`bugfix`分支后又有新的提交,如下情况: 65 | 66 | ![](https://static.vue-js.com/929eb220-fdd4-11eb-991d-334fd31f0201.png) 67 | 68 | 这时候使用`git merge`的时候,会生成一个新的提交,并且`master`分支的`HEAD`会移动到新的分支上,如下: 69 | 70 | ![](https://static.vue-js.com/9fdfa3e0-fdd4-11eb-991d-334fd31f0201.png) 71 | 72 | 73 | 74 | 从上面可以看到,会把两个分支的最新快照以及二者最近的共同祖先进行三方合并,合并的结果是生成一个新的快照 75 | 76 | 77 | 78 | ### git rebase 79 | 80 | 同样,`master`分支的历史记录在创建`bugfix`分支后又有新的提交,如下情况: 81 | 82 | ![](https://static.vue-js.com/ab2d5120-fdd4-11eb-bc6f-3f06e1491664.png) 83 | 84 | 通过`git rebase`,会变成如下情况: 85 | 86 | ![](https://static.vue-js.com/b72aed70-fdd4-11eb-991d-334fd31f0201.png) 87 | 88 | 在移交过程中,如果发生冲突,需要修改各自的冲突,如下: 89 | 90 | ![](https://static.vue-js.com/c9ba0e80-fdd4-11eb-bc6f-3f06e1491664.png) 91 | 92 | `rebase`之后,`master`的`HEAD`位置不变。因此,要合并`master`分支和`bugfix`分支 93 | 94 | ![](https://static.vue-js.com/dc660660-fdd4-11eb-991d-334fd31f0201.png) 95 | 96 | 从上面可以看到,`rebase`会找到不同的分支的最近共同祖先,如上图的`B` 97 | 98 | 然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件(老的提交`X`和`Y`也没有被销毁,只是简单地不能再被访问或者使用) 99 | 100 | 然后将当前分支指向目标最新位置`D`, 然后将之前另存为临时文件的修改依序应用 101 | 102 | 103 | 104 | 105 | 106 | ## 三、区别 107 | 108 | 从上面可以看到,`merge`和`rebasea`都是合并历史记录,但是各自特性不同: 109 | 110 | ### merge 111 | 112 | 通过`merge`合并分支会新增一个`merge commit`,然后将两个分支的历史联系起来 113 | 114 | 其实是一种非破坏性的操作,对现有分支不会以任何方式被更改,但是会导致历史记录相对复杂 115 | 116 | 117 | 118 | ### rebase 119 | 120 | `rebase `会将整个分支移动到另一个分支上,有效地整合了所有分支上的提交 121 | 122 | 主要的好处是历史记录更加清晰,是在原有提交的基础上将差异内容反映进去,消除了 ` git merge `所需的不必要的合并提交 123 | 124 | 125 | ## 参考文献 126 | 127 | - https://zhuanlan.zhihu.com/p/361182707 128 | - https://yuweijun.github.io/git-zh/1-git-branching.html#_rebasing 129 | - https://backlog.com/git-tutorial/cn/stepup/stepup1_4.html 130 | -------------------------------------------------------------------------------- /docs/git/git reset_ git revert.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对git reset 和 git revert 的理解?区别? 2 | 3 | ![](https://static.vue-js.com/046b4440-ff74-11eb-bc6f-3f06e1491664.png) 4 | 5 | 6 | ## 一、是什么 7 | 8 | ### git reset 9 | 10 | `reset`用于回退版本,可以遗弃不再使用的提交 11 | 12 | 执行遗弃时,需要根据影响的范围而指定不同的参数,可以指定是否复原索引或工作树内容 13 | 14 | ![](https://static.vue-js.com/ab4d0c00-ff72-11eb-bc6f-3f06e1491664.png) 15 | 16 | 17 | 18 | ### git revert 19 | 20 | 在当前提交后面,新增一次提交,抵消掉上一次提交导致的所有变化,不会改变过去的历史,主要是用于安全地取消过去发布的提交 21 | 22 | ![](https://static.vue-js.com/bd12c290-ff72-11eb-991d-334fd31f0201.png) 23 | 24 | 25 | ## 二、如何用 26 | 27 | ### git reset 28 | 29 | 当没有指定`ID`的时候,默认使用`HEAD`,如果指定`ID`,那么就是基于指向`ID`去变动暂存区或工作区的内容 30 | 31 | ```cmd 32 | // 没有指定ID, 暂存区的内容会被当前ID版本号的内容覆盖,工作区不变 33 | git reset 34 | 35 | // 指定ID,暂存区的内容会被指定ID版本号的内容覆盖,工作区不变 36 | git reset 37 | ``` 38 | 39 | 日志`ID`可以通过查询,可以`git log`进行查询,如下: 40 | 41 | ```cmd 42 | commit a7700083ace1204ccdff9f71631fb34c9913f7c5 (HEAD -> master) 43 | Author: linguanghui 44 | Date: Tue Aug 17 22:34:40 2021 +0800 45 | 46 | second commit 47 | 48 | commit e31118663ce66717edd8a179688a7f3dde5a9393 49 | Author: linguanghui 50 | Date: Tue Aug 17 22:20:01 2021 +0800 51 | 52 | first commit 53 | ``` 54 | 55 | 常见命令如下: 56 | 57 | - --mixed(默认):默认的时候,只有暂存区变化 58 | 59 | - --hard参数:如果使用 --hard 参数,那么工作区也会变化 60 | 61 | - --soft:如果使用 --soft 参数,那么暂存区和工作区都不会变化 62 | 63 | ![](https://static.vue-js.com/225b41e0-ff73-11eb-bc6f-3f06e1491664.png) 64 | 65 | 66 | 67 | ### git revert 68 | 69 | 跟`git reset`用法基本一致,`git revert` 撤销某次操作,此次操作之前和之后的 `commit`和`history`都会保留,并且把这次撤销,作为一次最新的提交,如下: 70 | 71 | ```cmd 72 | git revert 73 | ``` 74 | 75 | 如果撤销前一个版本,可以通过如下命令: 76 | 77 | ```cmd 78 | git revert HEAD 79 | ``` 80 | 81 | 撤销前前一次,如下: 82 | 83 | ```cmd 84 | git revert HEAD^ 85 | ``` 86 | 87 | ## 三、区别 88 | 89 | 撤销(revert)被设计为撤销公开的提交(比如已经push)的安全方式,`git reset`被设计为重设本地更改 90 | 91 | 因为两个命令的目的不同,它们的实现也不一样:重设完全地移除了一堆更改,而撤销保留了原来的更改,用一个新的提交来实现撤销 92 | 93 | 两者主要区别如下: 94 | 95 | - git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit 96 | - git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容 97 | - 在回滚这一操作上看,效果差不多。但是在日后继续 merge 以前的老版本时有区别 98 | 99 | > git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,之前提交合并的代码仍然存在,导致不能够重新合并 100 | > 101 | > 但是git reset是之间把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入 102 | 103 | - 如果回退分支的代码以后还需要的情况则使用`git revert`, 如果分支是提错了没用的并且不想让别人发现这些错误代码,则使用`git reset` 104 | 105 | 106 | ## 参考文献 107 | 108 | - https://juejin.cn/post/6844903542931587086 109 | - https://marklodato.github.io/visual-git-guide/index-zh-cn.html#reset -------------------------------------------------------------------------------- /docs/git/git stash.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对git stash 的理解?应用场景? 2 | 3 | ![](https://static.vue-js.com/83ddf210-fd6f-11eb-bc6f-3f06e1491664.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | stash,译为存放,在 git 中,可以理解为保存当前工作进度,会把暂存区和工作区的改动进行保存,这些修改会保存在一个栈上 10 | 11 | 后续你可以在任何时候任何分支重新将某次的修改推出来,重新应用这些更改的代码 12 | 13 | 默认情况下,`git stash`会缓存下列状态的文件: 14 | 15 | - 添加到暂存区的修改(staged changes) 16 | - Git跟踪的但并未添加到暂存区的修改(unstaged changes) 17 | 18 | 但以下状态的文件不会缓存: 19 | 20 | - 在工作目录中新的文件(untracked files) 21 | - 被忽略的文件(ignored files) 22 | 23 | 如果想要上述的文件都被缓存,可以使用`-u`或者`--include-untracked`可以工作目录新的文件,使用`-a`或者`--all`命令可以当前目录下的所有修改 24 | 25 | 26 | 27 | ## 二、如何使用 28 | 29 | 关于`git stash`常见的命令如下: 30 | 31 | - git stash 32 | - git stash save 33 | 34 | - git stash list 35 | - git stash pop 36 | - git stash apply 37 | - git stash show 38 | 39 | - git stash drop 40 | - git stash clear 41 | 42 | 43 | 44 | ### git stash 45 | 46 | 保存当前工作进度,会把暂存区和工作区的改动保存起来 47 | 48 | 49 | 50 | ### git stash save 51 | 52 | `git stash save`可以用于存储修改.并且将`git`的工作状态切回到`HEAD`也就是上一次合法提交上 53 | 54 | 如果给定具体的文件路径,`git stash`只会处理路径下的文件.其他的文件不会被存储,其存在一些参数: 55 | 56 | - --keep-index 或者 -k 只会存储为加入 git 管理的文件 57 | 58 | - --include-untracked 为追踪的文件也会被缓存,当前的工作空间会被恢复为完全清空的状态 59 | - -a 或者 --all 命令可以当前目录下的所有修改,包括被 git 忽略的文件 60 | 61 | 62 | 63 | ### git stash list 64 | 65 | 显示保存进度的列表。也就意味着,`git stash`命令可以多次执行,当多次使用`git stash`命令后,栈里会充满未提交的代码,如下: 66 | 67 | ![](https://static.vue-js.com/50216dd0-fccf-11eb-bc6f-3f06e1491664.png) 68 | 69 | 其中,`stash@{0}`、`stash@{1}`就是当前`stash`的名称 70 | 71 | 72 | 73 | ### git stash pop 74 | 75 | `git stash pop` 从栈中读取最近一次保存的内容,也就是栈顶的`stash`会恢复到工作区 76 | 77 | 也可以通过 `git stash pop` + `stash`名字执行恢复哪个`stash`恢复到当前目录 78 | 79 | 如果从`stash`中恢复的内容和当前目录中的内容发生了冲突,则需要手动修复冲突或者创建新的分支来解决冲突 80 | 81 | 82 | 83 | 84 | 85 | ### git stash apply 86 | 87 | 将堆栈中的内容应用到当前目录,不同于`git stash pop`,该命令不会将内容从堆栈中删除 88 | 89 | 也就说该命令能够将堆栈的内容多次应用到工作目录中,适应于多个分支的情况 90 | 91 | 同样,可以通过`git stash apply` + `stash`名字执行恢复哪个`stash`恢复到当前目录 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | ### git stash show 100 | 101 | 查看堆栈中最新保存的`stash`和当前目录的差异 102 | 103 | 通过使用`git stash show -p`查看详细的不同 104 | 105 | 通过使用`git stash show stash@{1}`查看指定的`stash`和当前目录差异 106 | 107 | ![](https://static.vue-js.com/458620a0-fccf-11eb-bc6f-3f06e1491664.png) 108 | 109 | 110 | 111 | ### git stash drop 112 | 113 | `git stash drop` + `stash`名称表示从堆栈中移除某个指定的stash 114 | 115 | 116 | 117 | ### git stash clear 118 | 119 | 删除所有存储的进度 120 | 121 | 122 | 123 | ## 三、应用场景 124 | 125 | 当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态, 而这时你想要切换到另一个分支或者拉下远端的代码去做一点别的事情 126 | 127 | 但是你创建一次未完成的代码的`commit`提交,这时候就可以使用`git stash` 128 | 129 | 例如以下场景: 130 | 131 | 当你的开发进行到一半,但是代码还不想进行提交 ,然后需要同步去关联远端代码时.如果你本地的代码和远端代码没有冲突时,可以直接通过`git pull`解决 132 | 133 | 但是如果可能发生冲突怎么办.直接`git pull`会拒绝覆盖当前的修改,这时候就可以依次使用下述的命令: 134 | 135 | - git stash 136 | - git pull 137 | - git stash pop 138 | 139 | 或者当你开发到一半,现在要修改别的分支问题的时候,你也可以使用`git stash`缓存当前区域的代码 140 | 141 | - git stash:保存开发到一半的代码 142 | - git commit -m '修改问题' 143 | - git stash pop:将代码追加到最新的提交之后 -------------------------------------------------------------------------------- /docs/http/CDN.md: -------------------------------------------------------------------------------- 1 | # 面试官:如何理解CDN?说说实现原理? 2 | 3 | ![](https://static.vue-js.com/437ae0f0-b86b-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、是什么 6 | 7 | CDN (全称 Content Delivery Network),即内容分发网络 8 | 9 | 构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。`CDN` 的关键技术主要有内容存储和分发技术 10 | 11 | 简单来讲,`CDN`就是根据用户位置分配最近的资源 12 | 13 | 于是,用户在上网的时候不用直接访问源站,而是访问离他“最近的”一个 CDN 节点,术语叫**边缘节点**,其实就是缓存了源站内容的代理服务器。如下图: 14 | 15 | ![](https://static.vue-js.com/4f0289f0-b86b-11eb-85f6-6fac77c0c9b3.png) 16 | 17 | 18 | 19 | ## 二、原理分析 20 | 21 | 在没有应用`CDN`时,我们使用域名访问某一个站点时的路径为 22 | > 用户提交域名→浏览器对域名进行解释→`DNS` 解析得到目的主机的IP地址→根据IP地址访问发出请求→得到请求数据并回复 23 | 24 | 应用`CDN`后,`DNS` 返回的不再是 `IP` 地址,而是一个`CNAME`(Canonical Name ) 别名记录,指向`CDN`的全局负载均衡 25 | 26 | `CNAME`实际上在域名解析的过程中承担了中间人(或者说代理)的角色,这是`CDN`实现的关键 27 | 28 | #### 负载均衡系统 29 | 30 | 由于没有返回`IP`地址,于是本地`DNS`会向负载均衡系统再发送请求 ,则进入到`CDN`的全局负载均衡系统进行智能调度: 31 | 32 | - 看用户的 IP 地址,查表得知地理位置,找相对最近的边缘节点 33 | - 看用户所在的运营商网络,找相同网络的边缘节点 34 | 35 | - 检查边缘节点的负载情况,找负载较轻的节点 36 | - 其他,比如节点的“健康状况”、服务能力、带宽、响应时间等 37 | 38 | 结合上面的因素,得到最合适的边缘节点,然后把这个节点返回给用户,用户就能够就近访问`CDN`的缓存代理 39 | 40 | 整体流程如下图: 41 | 42 | ![](https://static.vue-js.com/588d7890-b86b-11eb-85f6-6fac77c0c9b3.png) 43 | 44 | 45 | 46 | #### 缓存代理 47 | 48 | 缓存系统是 `CDN `的另一个关键组成部分,缓存系统会有选择地缓存那些最常用的那些资源 49 | 50 | 其中有两个衡量`CDN`服务质量的指标: 51 | 52 | - 命中率:用户访问的资源恰好在缓存系统里,可以直接返回给用户,命中次数与所有访问次数之比 53 | - 回源率:缓存里没有,必须用代理的方式回源站取,回源次数与所有访问次数之比 54 | 55 | 缓存系统也可以划分出层次,分成一级缓存节点和二级缓存节点。一级缓存配置高一些,直连源站,二级缓存配置低一些,直连用户 56 | 57 | 回源的时候二级缓存只找一级缓存,一级缓存没有才回源站,可以有效地减少真正的回源 58 | 59 | 现在的商业 `CDN`命中率都在 90% 以上,相当于把源站的服务能力放大了 10 倍以上 60 | 61 | 62 | 63 | ## 三、总结 64 | `CDN` 目的是为了改善互联网的服务质量,通俗一点说其实就是提高访问速度 65 | 66 | `CDN` 构建了全国、全球级别的专网,让用户就近访问专网里的边缘节点,降低了传输延迟,实现了网站加速 67 | 68 | 通过`CDN`的负载均衡系统,智能调度边缘节点提供服务,相当于`CDN`服务的大脑,而缓存系统相当于`CDN`的心脏,缓存命中直接返回给用户,否则回源 69 | 70 | 71 | 72 | ## 参考文献 73 | 74 | - https://zh.wikipedia.org/wiki/內容傳遞網路 75 | - https://juejin.cn/post/6844903890706661389#heading-5 76 | - https://blog.csdn.net/lxx309707872/article/details/109078783 -------------------------------------------------------------------------------- /docs/http/DNS.md: -------------------------------------------------------------------------------- 1 | # 面试官:DNS协议 是什么?说说DNS 完整的查询过程? 2 | 3 | ![](https://static.vue-js.com/88081710-b78f-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | DNS(Domain Names System),域名系统,是互联网一项服务,是进行域名和与之相对应的 IP 地址进行转换的服务器 10 | 11 | 简单来讲,`DNS`相当于一个翻译官,负责将域名翻译成`ip`地址 12 | 13 | - IP 地址:一长串能够唯一地标记网络上的计算机的数字 14 | - 域名:是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识 15 | 16 | ![](https://static.vue-js.com/965a03a0-b78f-11eb-ab90-d9ae814b240d.png) 17 | 18 | 19 | 20 | 21 | 22 | ## 二、域名 23 | 24 | 域名是一个具有层次的结构,从上到下一次为根域名、顶级域名、二级域名、三级域名... 25 | 26 | ![](https://static.vue-js.com/9f112780-b78f-11eb-85f6-6fac77c0c9b3.png) 27 | 28 | 例如`www.xxx.com`,`www`为三级域名、`xxx`为二级域名、`com`为顶级域名,系统为用户做了兼容,域名末尾的根域名`.`一般不需要输入 29 | 30 | 在域名的每一层都会有一个域名服务器,如下图: 31 | 32 | ![](https://static.vue-js.com/f40e0090-b7a4-11eb-85f6-6fac77c0c9b3.png) 33 | 34 | 除此之外,还有电脑默认的本地域名服务器 35 | 36 | 37 | 38 | ## 三、查询方式 39 | 40 | DNS 查询的方式有两种: 41 | 42 | - 递归查询:如果 A 请求 B,那么 B 作为请求的接收者一定要给 A 想要的答案 43 | 44 | ![](https://static.vue-js.com/a73be9e0-b78f-11eb-85f6-6fac77c0c9b3.png) 45 | 46 | - 迭代查询:如果接收者 B 没有请求者 A 所需要的准确内容,接收者 B 将告诉请求者 A,如何去获得这个内容,但是自己并不去发出请求 47 | 48 | ![](https://static.vue-js.com/b023e1c0-b78f-11eb-85f6-6fac77c0c9b3.png) 49 | 50 | 51 | 52 | ## 四、域名缓存 53 | 54 | 在域名服务器解析的时候,使用缓存保存域名和`IP`地址的映射 55 | 56 | 计算机中`DNS`的记录也分成了两种缓存方式: 57 | 58 | - 浏览器缓存:浏览器在获取网站域名的实际 IP 地址后会对其进行缓存,减少网络请求的损耗 59 | - 操作系统缓存:操作系统的缓存其实是用户自己配置的 `hosts` 文件 60 | 61 | 62 | 63 | ## 五、查询过程 64 | 65 | 解析域名的过程如下: 66 | 67 | - 首先搜索浏览器的 DNS 缓存,缓存中维护一张域名与 IP 地址的对应表 68 | - 若没有命中,则继续搜索操作系统的 DNS 缓存 69 | - 若仍然没有命中,则操作系统将域名发送至本地域名服务器,本地域名服务器采用递归查询自己的 DNS 缓存,查找成功则返回结果 70 | - 若本地域名服务器的 DNS 缓存没有命中,则本地域名服务器向上级域名服务器进行迭代查询 71 | - 首先本地域名服务器向根域名服务器发起请求,根域名服务器返回顶级域名服务器的地址给本地服务器 72 | - 本地域名服务器拿到这个顶级域名服务器的地址后,就向其发起请求,获取权限域名服务器的地址 73 | - 本地域名服务器根据权限域名服务器的地址向其发起请求,最终得到该域名对应的 IP 地址 74 | 75 | - 本地域名服务器将得到的 IP 地址返回给操作系统,同时自己将 IP 地址缓存起来 76 | 77 | - 操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起 78 | 79 | - 至此,浏览器就得到了域名对应的 IP 地址,并将 IP 地址缓存起 80 | 81 | 流程如下图所示: 82 | 83 | ![](https://static.vue-js.com/bec3c740-b78f-11eb-ab90-d9ae814b240d.png) 84 | 85 | 86 | 87 | ## 参考文献 88 | - https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D%E7%B3%BB%E7%BB%9F 89 | - https://www.cnblogs.com/jmilkfan-fanguiju/p/12789677.html 90 | - https://segmentfault.com/a/1190000039039275 91 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/http/GET_POST.md: -------------------------------------------------------------------------------- 1 | # 面试官:说一下 GET 和 POST 的区别? 2 | 3 | ![](https://static.vue-js.com/6e8d19e0-bc3d-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | `GET`和`POST`,两者是`HTTP`协议中发送请求的方法 10 | 11 | #### GET 12 | 13 | `GET`方法请求一个指定资源的表示形式,使用GET的请求应该只被用于获取数据 14 | 15 | #### POST 16 | `POST`方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或**副作用** 17 | 18 | 本质上都是`TCP`链接,并无差别 19 | 20 | 但是由于`HTTP`的规定和浏览器/服务器的限制,导致他们在应用过程中会体现出一些区别 21 | 22 | ## 二、区别 23 | 24 | 从`w3schools`得到的标准答案的区别如下: 25 | 26 | - GET在浏览器回退时是无害的,而POST会再次提交请求。 27 | - GET产生的URL地址可以被Bookmark,而POST不可以。 28 | - GET请求会被浏览器主动cache,而POST不会,除非手动设置。 29 | - GET请求只能进行url编码,而POST支持多种编码方式。 30 | - GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。 31 | - GET请求在URL中传送的参数是有长度限制的,而POST没有。 32 | - 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。 33 | - GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。 34 | - GET参数通过URL传递,POST放在Request body中 35 | 36 | 37 | ### 参数位置 38 | 39 | 貌似从上面看到`GET`与`POST`请求区别非常大,但两者实质并没有区别 40 | 41 | 无论 `GET `还是 `POST`,用的都是同一个传输层协议,所以在传输上没有区别 42 | 43 | 当不携带参数的时候,两者最大的区别为第一行方法名不同 44 | 45 | > POST /uri HTTP/1.1 \r\n 46 | > 47 | > GET /uri HTTP/1.1 \r\n 48 | 49 | 当携带参数的时候,我们都知道`GET`请求是放在`url`中,`POST`则放在`body`中 50 | 51 | `GET` 方法简约版报文是这样的 52 | 53 | ``` 54 | GET /index.html?name=qiming.c&age=22 HTTP/1.1 55 | Host: localhost 56 | ``` 57 | 58 | `POST `方法简约版报文是这样的 59 | 60 | ``` 61 | POST /index.html HTTP/1.1 62 | Host: localhost 63 | Content-Type: application/x-www-form-urlencoded 64 | 65 | name=qiming.c&age=22 66 | ``` 67 | 68 | 注意:这里只是约定,并不属于`HTTP`规范,相反的,我们可以在`POST`请求中`url`中写入参数,或者`GET`请求中的`body`携带参数 69 | 70 | 71 | ### 参数长度 72 | 73 | `HTTP `协议没有` Body `和 `URL` 的长度限制,对 `URL `限制的大多是浏览器和服务器的原因 74 | 75 | `IE`对`URL`长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持 76 | 77 | 这里限制的是整个`URL`长度,而不仅仅是参数值的长度 78 | 79 | 服务器处理长` URL` 要消耗比较多的资源,为了性能和安全考虑,会给 `URL` 长度加限制 80 | 81 | ### 安全 82 | 83 | `POST `比` GET` 安全,因为数据在地址栏上不可见 84 | 85 | 然而,从传输的角度来说,他们都是不安全的,因为` HTTP` 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文 86 | 87 | 只有使用`HTTPS`才能加密安全 88 | 89 | 90 | ### 数据包 91 | 92 | 对于`GET`方式的请求,浏览器会把`http header`和`data`一并发送出去,服务器响应200(返回数据) 93 | 94 | 对于`POST`,浏览器先发送`header`,服务器响应100 `continue`,浏览器再发送`data`,服务器响应200 ok 95 | 96 | 并不是所有浏览器都会在`POST`中发送两次包,`Firefox`就只发送一次 97 | 98 | 99 | 100 | ## 参考文献 101 | 102 | - https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&mid=100000054&idx=1&sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd 103 | - https://blog.fundebug.com/2019/02/22/compare-http-method-get-and-post/ 104 | - https://www.w3school.com.cn/tags/html_ref_httpmethods.asp 105 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/http/HTTP_HTTPS.md: -------------------------------------------------------------------------------- 1 | # 面试官:什么是HTTP? HTTP 和 HTTPS 的区别? 2 | 3 | ![](https://static.vue-js.com/f50c71f0-b20b-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、HTTP 6 | 7 | `HTTP` (HyperText Transfer Protocol),即超文本运输协议,是实现网络通信的一种规范 8 | 9 | ![](https://static.vue-js.com/fda119b0-b20b-11eb-85f6-6fac77c0c9b3.png) 10 | 11 | 在计算机和网络世界有,存在不同的协议,如广播协议、寻址协议、路由协议等等...... 12 | 13 | 而`HTTP`是一个传输协议,即将数据由A传到B或将B传输到A,并且 A 与 B 之间能够存放很多第三方,如: A<=>X<=>Y<=>Z<=>B 14 | 15 | 传输的数据并不是计算机底层中的二进制包,而是完整的、有意义的数据,如HTML 文件, 图片文件, 查询结果等超文本,能够被上层应用识别 16 | 17 | 在实际应用中,`HTTP`常被用于在`Web`浏览器和网站服务器之间传递信息,以明文方式发送内容,不提供任何方式的数据加密 18 | 19 | 特点如下: 20 | 21 | - 支持客户/服务器模式 22 | 23 | - 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快 24 | - 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记 25 | - 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间 26 | - 无状态:HTTP协议无法根据之前的状态进行本次的请求处理 27 | 28 | 29 | 30 | 31 | 32 | ## 二、HTTPS 33 | 34 | 在上述介绍`HTTP`中,了解到`HTTP`传递信息是以明文的形式发送内容,这并不安全。而`HTTPS`出现正是为了解决`HTTP`不安全的特性 35 | 36 | 为了保证这些隐私数据能加密传输,让`HTTP`运行安全的`SSL/TLS`协议上,即 HTTPS = HTTP + SSL/TLS,通过 `SSL`证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密 37 | 38 | `SSL` 协议位于` TCP/IP` 协议与各种应用层协议之间,浏览器和服务器在使用 `SSL` 建立连接时需要选择一组恰当的加密算法来实现安全通信,为数据通讯提供安全支持 39 | 40 | ![](https://static.vue-js.com/078c50c0-b20c-11eb-ab90-d9ae814b240d.png) 41 | 42 | 流程图如下所示: 43 | 44 | ![](https://static.vue-js.com/0e409fc0-b20c-11eb-85f6-6fac77c0c9b3.png) 45 | 46 | - 首先客户端通过URL访问服务器建立SSL连接 47 | - 服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端 48 | - 客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级 49 | - 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站 50 | - 服务器利用自己的私钥解密出会话密钥 51 | - 服务器利用会话密钥加密与客户端之间的通信 52 | 53 | 54 | 55 | ## 三、区别 56 | 57 | - HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理,相对更安全 58 | - HTTP 和 HTTPS 使用连接方式不同,默认端口也不一样,HTTP是80,HTTPS是443 59 | - HTTPS 由于需要设计加密以及多次握手,性能方面不如 HTTP 60 | - HTTPS需要SSL,SSL 证书需要钱,功能越强大的证书费用越高 61 | 62 | 63 | 64 | 65 | 66 | ## 参考文献 67 | 68 | - https://www.cnblogs.com/klb561/p/10289199.html 69 | - https://www.jianshu.com/p/205c0fc51c97 70 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/http/OSI.md: -------------------------------------------------------------------------------- 1 | # 面试官:如何理解OSI七层模型? 2 | 3 | ![](https://static.vue-js.com/e2e1b910-b61e-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、是什么 6 | 7 | OSI (Open System Interconnect)模型全称为开放式通信系统互连参考模型,是国际标准化组织 ( ISO ) 提出的一个试图使各种计算机在世界范围内互连为网络的标准框架 8 | 9 | `OSI `将计算机网络体系结构划分为七层,每一层实现各自的功能和协议,并完成与相邻层的接口通信。即每一层扮演固定的角色,互不打扰 10 | 11 | 12 | ## 二、划分 13 | 14 | `OSI`主要划分了七层,如下图所示: 15 | 16 | ![](https://static.vue-js.com/eb1b2170-b61e-11eb-ab90-d9ae814b240d.png) 17 | 18 | 19 | ### 应用层 20 | 21 | 应用层位于 OSI 参考模型的第七层,其作用是通过应用程序间的交互来完成特定的网络应用 22 | 23 | 该层协议定义了应用进程之间的交互规则,通过不同的应用层协议为不同的网络应用提供服务。例如域名系统 `DNS`,支持万维网应用的 `HTTP` 协议,电子邮件系统采用的 `SMTP `协议等 24 | 25 | 在应用层交互的数据单元我们称之为报文 26 | 27 | 28 | 29 | ### 表示层 30 | 31 | 表示层的作用是使通信的应用程序能够解释交换数据的含义,其位于 `OSI `参考模型的第六层,向上为应用层提供服务,向下接收来自会话层的服务 32 | 33 | 该层提供的服务主要包括数据压缩,数据加密以及数据描述,使应用程序不必担心在各台计算机中表示和存储的内部格式差异 34 | 35 | 36 | 37 | ### 会话层 38 | 39 | 会话层就是负责建立、管理和终止表示层实体之间的通信会话 40 | 41 | 该层提供了数据交换的定界和同步功能,包括了建立检查点和恢复方案的方法 42 | 43 | 44 | 45 | ### 传输层 46 | 47 | 传输层的主要任务是为两台主机进程之间的通信提供服务,处理数据包错误、数据包次序,以及其他一些关键传输问题 48 | 49 | 传输层向高层屏蔽了下层数据通信的细节。因此,它是计算机通信体系结构中关键的一层 50 | 51 | 其中,主要的传输层协议是`TCP`和`UDP` 52 | 53 | 54 | 55 | 56 | 57 | ### 网络层 58 | 59 | 两台计算机之间传送数据时其通信链路往往不止一条,所传输的信息甚至可能经过很多通信子网 60 | 61 | 网络层的主要任务就是选择合适的网间路由和交换节点,确保数据按时成功传送 62 | 63 | 在发送数据时,网络层把传输层产生的报文或用户数据报封装成分组和包,向下传输到数据链路层 64 | 65 | 在网络层使用的协议是无连接的网际协议(Internet Protocol)和许多路由协议,因此我们通常把该层简单地称为 IP 层 66 | 67 | 68 | 69 | ### 数据链路层 70 | 71 | 数据链路层通常也叫做链路层,在物理层和网络层之间。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层协议 72 | 73 | 在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 `IP `数据报组装成帧,在两个相邻节点间的链路上传送帧 74 | 75 | 每一帧的数据可以分成:报头`head`和数据`data`两部分: 76 | 77 | - head 标明数据发送者、接受者、数据类型,如 MAC地址 78 | - data 存储了计算机之间交互的数据 79 | 80 | 通过控制信息我们可以知道一个帧的起止比特位置,此外,也能使接收端检测出所收到的帧有无差错,如果发现差错,数据链路层能够简单的丢弃掉这个帧,以避免继续占用网络资源 81 | 82 | 83 | 84 | ### 物理层 85 | 86 | 作为` OSI` 参考模型中最低的一层,物理层的作用是实现计算机节点之间比特流的透明传送 87 | 88 | 该层的主要任务是确定与传输媒体的接口的一些特性(机械特性、电气特性、功能特性,过程特性) 89 | 90 | 该层主要是和硬件有关,与软件关系不大 91 | 92 | 93 | 94 | 95 | 96 | ## 三、传输过程 97 | 98 | 数据在各层之间的传输如下图所示: 99 | 100 | ![](https://static.vue-js.com/f3a89d40-b61e-11eb-85f6-6fac77c0c9b3.png) 101 | 102 | - 应用层报文被传送到运输层 103 | - 在最简单的情况下,运输层收取到报文并附上附加信息,该首部将被接收端的运输层使用 104 | - 应用层报文和运输层首部信息一道构成了运输层报文段。附加的信息可能包括:允许接收端运输层向上向适当的应用程序交付报文的信息以及差错检测位信息。该信息让接收端能够判断报文中的比特是否在途中已被改变 105 | - 运输层则向网络层传递该报文段,网络层增加了如源和目的端系统地址等网络层首部信息,生成了网络层数据报 106 | - 网络层数据报接下来被传递给链路层,在数据链路层数据包添加发送端 MAC 地址和接收端 MAC 地址后被封装成数据帧 107 | - 在物理层数据帧被封装成比特流,之后通过传输介质传送到对端 108 | - 对端再一步步解开封装,获取到传送的数据 109 | 110 | 111 | 112 | 113 | 114 | ## 参考文献 115 | - https://zh.wikipedia.org/wiki/OSI%E6%A8%A1%E5%9E%8B 116 | - https://zhuanlan.zhihu.com/p/32059190 117 | - https://leetcode-cn.com/leetbook/detail/networks-interview-highlights/ 118 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/http/TCP_IP.md: -------------------------------------------------------------------------------- 1 | # 面试官:如何理解TCP/IP协议? 2 | 3 | ![](https://static.vue-js.com/4f69a930-b647-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | TCP/IP,**传输控制协议**/**网际协议**,是指能够在多个不同网络间实现信息传输的协议簇 10 | 11 | - TCP(传输控制协议) 12 | 13 | 一种面向连接的、可靠的、基于字节流的传输层通信协议 14 | 15 | - IP(网际协议) 16 | 17 | 用于封包交换数据网络的协议 18 | 19 | TCP/IP协议不仅仅指的是`TCP `和`IP`两个协议,而是指一个由`FTP`、`SMTP`、`TCP`、`UDP`、`IP`等协议构成的协议簇, 20 | 21 | 只是因为在`TCP/IP`协议中`TCP`协议和`IP`协议最具代表性,所以通称为TCP/IP协议族(英语:TCP/IP Protocol Suite,或TCP/IP Protocols) 22 | 23 | 24 | ## 二、划分 25 | 26 | TCP/IP协议族按层次分别了五层体系或者四层体系 27 | 28 | 五层体系的协议结构是综合了 OSI 和 TCP/IP 优点的一种协议,包括应用层、传输层、网络层、数据链路层和物理层 29 | 30 | 五层协议的体系结构只是为介绍网络原理而设计的,实际应用还是 TCP/IP 四层体系结构,包括应用层、传输层、网络层(网际互联层)、网络接口层 31 | 32 | 如下图所示: 33 | 34 | ![](https://static.vue-js.com/5bb93610-b647-11eb-85f6-6fac77c0c9b3.png) 35 | 36 | 37 | 38 | ### 五层体系 39 | 40 | #### 应用层 41 | 42 | `TCP/IP` 模型将 `OSI `参考模型中的会话层、表示层和应用层的功能合并到一个应用层实现,通过不同的应用层协议为不同的应用提供服务 43 | 44 | 如:`FTP`、`Telnet`、`DNS`、`SMTP` 等 45 | 46 | 47 | 48 | #### 传输层 49 | 50 | 该层对应于 OSI 参考模型的传输层,为上层实体提供源端到对端主机的通信功能 51 | 52 | 传输层定义了两个主要协议:传输控制协议(TCP)和用户数据报协议(UDP) 53 | 54 | 其中面向连接的 TCP 协议保证了数据的传输可靠性,面向无连接的 UDP 协议能够实现数据包简单、快速地传输 55 | 56 | 57 | 58 | #### 网络层 59 | 60 | 负责为分组网络中的不同主机提供通信服务,并通过选择合适的路由将数据传递到目标主机 61 | 62 | 在发送数据时,网络层把运输层产生的报文段或用户数据封装成分组或包进行传送 63 | 64 | 65 | 66 | #### 数据链路层 67 | 68 | 数据链路层在两个相邻节点传输数据时,将网络层交下来的IP数据报组装成帧,在两个相邻节点之间的链路上传送帧 69 | 70 | 71 | 72 | #### 物理层 73 | 74 | 保数据可以在各种物理媒介上进行传输,为数据的传输提供可靠的环境 75 | 76 | 77 | 78 | ### 四层体系 79 | 80 | TCP/IP 的四层结构则如下表所示: 81 | 82 | | 层次名称 | 单位 | 功 能 | 协 议 | 83 | | ---------- | ------ | --------------------------------------------------------- | ------------------------------------------------------------ | 84 | | 网络接口层 | 帧 | 负责实际数据的传输,对应OSI参考模型的下两层 | HDLC(高级链路控制协议)PPP(点对点协议) SLIP(串行线路接口协议) | 85 | | 网络层 | 数据报 | 负责网络间的寻址数据传输,对应OSI参考模型的第三层 | IP(网际协议) ICMP(网际控制消息协议)ARP(地址解析协议) RARP(反向地址解析协议) | 86 | | 传输层 | 报文段 | 负责提供可靠的传输服务,对应OSI参考模型的第四层 | TCP(控制传输协议) UDP(用户数据报协议) | 87 | | 应用层 | | 负责实现一切与应用程序相关的功能,对应OSI参考模型的上三层 | FTP(文件传输协议) HTTP(超文本传输协议) DNS(域名服务器协议)SMTP(简单邮件传输协议)NFS(网络文件系统协议) | 88 | 89 | 90 | 91 | ## 三、总结 92 | 93 | OSI 参考模型与 TCP/IP 参考模型区别如下: 94 | 95 | 相同点: 96 | 97 | - OSI 参考模型与 TCP/IP 参考模型都采用了层次结构 98 | - 都能够提供面向连接和无连接两种通信服务机制 99 | 100 | 不同点: 101 | 102 | - OSI 采用的七层模型; TCP/IP 是四层或五层结构 103 | - TCP/IP 参考模型没有对网络接口层进行细分,只是一些概念性的描述; OSI 参考模型对服务和协议做了明确的区分 104 | - OSI 参考模型虽然网络划分为七层,但实现起来较困难。TCP/IP 参考模型作为一种简化的分层结构是可以的 105 | 106 | - TCP/IP协议去掉表示层和会话层的原因在于会话层、表示层、应用层都是在应用程序内部实现的,最终产出的是一个应用数据包,而应用程序之间是几乎无法实现代码的抽象共享的,这也就造成 `OSI` 设想中的应用程序维度的分层是无法实现的 107 | 108 | 109 | 110 | 三种模型对应关系如下图所示: 111 | 112 | ![](https://static.vue-js.com/3fbff4d0-b647-11eb-ab90-d9ae814b240d.png) 113 | 114 | 115 | 116 | 117 | 118 | ## 参考文献 119 | - https://zh.wikipedia.org/wiki/TCP/IP%E5%8D%8F%E8%AE%AE%E6%97%8F 120 | - https://zhuanlan.zhihu.com/p/103162095 121 | - https://segmentfault.com/a/1190000039204681 122 | - https://leetcode-cn.com/leetbook/detail/networks-interview-highlights/ 123 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/http/UDP_TCP.md: -------------------------------------------------------------------------------- 1 | # 面试官:如何理解UDP 和 TCP? 区别? 应用场景? 2 | 3 | ![](https://static.vue-js.com/85ad65b0-b393-11eb-ab90-d9ae814b240d.png) 4 | 5 | 6 | ## 一、UDP 7 | 8 | UDP(User Datagram Protocol),用户数据包协议,是一个简单的**面向数据报的通信协议**,即对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层 9 | 10 | 也就是说无论应用层交给`UDP`多长的报文,它统统发送,一次发送一个报文 11 | 12 | 而对接收方,接到后直接去除首部,交给上面的应用层就完成任务 13 | 14 | `UDP`报头包括4个字段,每个字段占用2个字节(即16个二进制位),标题短,开销小 15 | 16 | ![](https://static.vue-js.com/928e5d20-b393-11eb-ab90-d9ae814b240d.png) 17 | 18 | 19 | 20 | 特点如下: 21 | 22 | - UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务 23 | - 传输途中出现丢包,UDP 也不负责重发 24 | - 当包的到达顺序出现乱序时,UDP没有纠正的功能。 25 | - 并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况,UDP 也无法进行流量控制等避免网络拥塞行为 26 | 27 | 28 | 29 | ## 二、TCP 30 | 31 | TCP(Transmission Control Protocol),传输控制协议,是一种可靠、**面向字节流的通信协议**,把上面应用层交下来的数据看成无结构的字节流来发送 32 | 33 | 可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着,TCP会根据当前网络的拥塞状态来确定每个报文段的大小 34 | 35 | `TCP`报文首部有20个字节,额外开销大 36 | 37 | ![](https://static.vue-js.com/a0010d40-b393-11eb-ab90-d9ae814b240d.png) 38 | 39 | 40 | 41 | 特点如下: 42 | 43 | - TCP充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。 44 | - 此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。 45 | - 根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信( 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现) 46 | 47 | 48 | 49 | ## 三、区别 50 | 51 | `UDP`与`TCP`两者的都位于传输层,如下图所示: 52 | 53 | ![](https://static.vue-js.com/a92bda80-b393-11eb-ab90-d9ae814b240d.png) 54 | 55 | 两者区别如下表所示: 56 | 57 | | | TCP | UDP | 58 | | -------- | -------------------------------- | ------------------------------ | 59 | | 可靠性 | 可靠 | 不可靠 | 60 | | 连接性 | 面向连接 | 无连接 | 61 | | 报文 | 面向字节流 | 面向报文 | 62 | | 效率 | 传输效率低 | 传输效率高 | 63 | | 双共性 | 全双工 | 一对一、一对多、多对一、多对多 | 64 | | 流量控制 | 滑动窗口 | 无 | 65 | | 拥塞控制 | 慢开始、拥塞避免、快重传、快恢复 | 无 | 66 | | 传输效率 | 慢 | 快 | 67 | 68 | - TCP 是面向连接的协议,建立连接3次握手、断开连接四次挥手,UDP是面向无连接,数据传输前后不连接连接,发送端只负责将数据发送到网络,接收端从消息队列读取 69 | - TCP 提供可靠的服务,传输过程采用流量控制、编号与确认、计时器等手段确保数据无差错,不丢失。UDP 则尽可能传递数据,但不保证传递交付给对方 70 | - TCP 面向字节流,将应用层报文看成一串无结构的字节流,分解为多个TCP报文段传输后,在目的站重新装配。UDP协议面向报文,不拆分应用层报文,只保留报文边界,一次发送一个报文,接收方去除报文首部后,原封不动将报文交给上层应用 71 | 72 | - TCP 只能点对点全双工通信。UDP 支持一对一、一对多、多对一和多对多的交互通信 73 | 74 | 两者应用场景如下图: 75 | 76 | ![](https://static.vue-js.com/b6cdd800-b393-11eb-ab90-d9ae814b240d.png) 77 | 78 | 可以看到,TCP 应用场景适用于对效率要求低,对准确性要求高或者要求有链接的场景,而UDP 适用场景为对效率要求高,对准确性要求低的场景 79 | 80 | 81 | ## 参考文献 82 | - https://zh.wikipedia.org 83 | - https://www.shangmayuan.com/a/a1e3ceb218284cefb95de7fd.html 84 | - https://segmentfault.com/a/1190000021815671 85 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/http/WebSocket.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说对WebSocket的理解?应用场景? 2 | 3 | ![](https://static.vue-js.com/a358a8c0-c0f1-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、是什么 6 | 7 | WebSocket,是一种网络传输协议,位于`OSI`模型的应用层。可在单个`TCP`连接上进行全双工通信,能更好的节省服务器资源和带宽并达到实时通迅 8 | 9 | 客户端和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输 10 | 11 | ![](https://static.vue-js.com/ad386e20-c0f1-11eb-85f6-6fac77c0c9b3.png) 12 | 13 | 从上图可见,`websocket`服务器与客户端通过握手连接,连接成功后,两者都能主动的向对方发送或接受数据 14 | 15 | 而在`websocket`出现之前,开发实时`web`应用的方式为轮询 16 | 17 | 不停地向服务器发送 HTTP 请求,问有没有数据,有数据的话服务器就用响应报文回应。如果轮询的频率比较高,那么就可以近似地实现“实时通信”的效果 18 | 19 | 轮询的缺点也很明显,反复发送无效查询请求耗费了大量的带宽和 `CPU `资源 20 | 21 | 22 | 23 | ## 二、特点 24 | 25 | 26 | 27 | ### 全双工 28 | 29 | 通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合 30 | 31 | 例如指 A→B 的同时 B→A ,是瞬时同步的 32 | 33 | 34 | 35 | ### 二进制帧 36 | 37 | 采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,相比`http/2`,`WebSocket `更侧重于“实时通信”,而`HTTP/2` 更侧重于提高传输效率,所以两者的帧结构也有很大的区别 38 | 39 | 不像 `HTTP/2` 那样定义流,也就不存在多路复用、优先级等特性 40 | 41 | 自身就是全双工,也不需要服务器推送 42 | 43 | 44 | 45 | 46 | 47 | ### 协议名 48 | 49 | 引入`ws`和`wss`分别代表明文和密文的`websocket`协议,且默认端口使用80或443,几乎与`http`一致 50 | 51 | ```http 52 | ws://www.chrono.com 53 | ws://www.chrono.com:8080/srv 54 | wss://www.chrono.com:445/im?user_id=xxx 55 | ``` 56 | 57 | 58 | 59 | ### 握手 60 | 61 | `WebSocket `也要有一个握手过程,然后才能正式收发数据 62 | 63 | 客户端发送数据格式如下: 64 | 65 | ```http 66 | GET /chat HTTP/1.1 67 | Host: server.example.com 68 | Upgrade: websocket 69 | Connection: Upgrade 70 | Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== 71 | Origin: http://example.com 72 | Sec-WebSocket-Protocol: chat, superchat 73 | Sec-WebSocket-Version: 13 74 | ``` 75 | 76 | - Connection:必须设置Upgrade,表示客户端希望连接升级 77 | - Upgrade:必须设置Websocket,表示希望升级到Websocket协议 78 | - Sec-WebSocket-Key:客户端发送的一个 base64 编码的密文,用于简单的认证秘钥。要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept应答,否则客户端会抛出错误,并关闭连接 79 | - Sec-WebSocket-Version :表示支持的Websocket版本 80 | 81 | 服务端返回的数据格式: 82 | 83 | ```http 84 | HTTP/1.1 101 Switching Protocols 85 | Upgrade: websocket 86 | Connection: Upgrade 87 | Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chat 88 | ``` 89 | 90 | - HTTP/1.1 101 Switching Protocols:表示服务端接受 WebSocket 协议的客户端连接 91 | - Sec-WebSocket-Accep:验证客户端请求报文,同样也是为了防止误连接。具体做法是把请求头里“Sec-WebSocket-Key”的值,加上一个专用的 UUID,再计算摘要 92 | 93 | 94 | 95 | ### 优点 96 | 97 | - 较少的控制开销:数据包头部协议较小,不同于http每次请求需要携带完整的头部 98 | - 更强的实时性:相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少 99 | - 保持创连接状态:创建通信后,可省略状态信息,不同于HTTP每次请求需要携带身份验证 100 | - 更好的二进制支持:定义了二进制帧,更好处理二进制内容 101 | - 支持扩展:用户可以扩展websocket协议、实现部分自定义的子协议 102 | - 更好的压缩效果:Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率 103 | 104 | 105 | 106 | ## 二、应用场景 107 | 108 | 基于`websocket`的事实通信的特点,其存在的应用场景大概有: 109 | 110 | - 弹幕 111 | - 媒体聊天 112 | - 协同编辑 113 | - 基于位置的应用 114 | - 体育实况更新 115 | - 股票基金报价实时更新 116 | 117 | 118 | 119 | ## 参考文献 120 | 121 | - https://zh.wikipedia.org/wiki/WebSocket 122 | - https://www.oschina.net/translate/9-killer-uses-for-websockets 123 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/http/after_url.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说地址栏输入 URL 敲下回车后发生了什么? 2 | 3 | ![](https://static.vue-js.com/11bf1f20-bdf4-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | 6 | ## 一、简单分析 7 | 8 | 简单的分析,从输入 `URL`到回车后发生的行为如下: 9 | 10 | - URL解析 11 | - DNS 查询 12 | - TCP 连接 13 | - HTTP 请求 14 | - 响应请求 15 | - 页面渲染 16 | 17 | 18 | ## 二、详细分析 19 | 20 | ### URL解析 21 | 22 | 首先判断你输入的是一个合法的` URL` 还是一个待搜索的关键词,并且根据你输入的内容进行对应操作 23 | 24 | `URL`的解析第过程中的第一步,一个`url`的结构解析如下: 25 | 26 | ![](https://static.vue-js.com/27a0c690-bdf4-11eb-ab90-d9ae814b240d.png) 27 | 28 | 29 | 30 | ### DNS查询 31 | 32 | 在之前文章中讲过`DNS`的查询,这里就不再讲述了 33 | 34 | 整个查询过程如下图所示: 35 | 36 | ![](https://static.vue-js.com/330fb770-bdf4-11eb-85f6-6fac77c0c9b3.png) 37 | 38 | 最终,获取到了域名对应的目标服务器`IP`地址 39 | 40 | 41 | 42 | ### TCP连接 43 | 44 | 在之前文章中,了解到`tcp`是一种面向有连接的传输层协议 45 | 46 | 在确定目标服务器服务器的`IP`地址后,则经历三次握手建立`TCP`连接,流程如下: 47 | 48 | ![](https://static.vue-js.com/ad750790-bdf4-11eb-85f6-6fac77c0c9b3.png) 49 | 50 | 51 | 52 | 53 | 54 | ### 发送 http 请求 55 | 56 | 当建立`tcp`连接之后,就可以在这基础上进行通信,浏览器发送 `http` 请求到目标服务器 57 | 58 | 请求的内容包括: 59 | 60 | - 请求行 61 | - 请求头 62 | - 请求主体 63 | 64 | ![](https://static.vue-js.com/bbcb60f0-bdf4-11eb-ab90-d9ae814b240d.png) 65 | 66 | 67 | 68 | ### 响应请求 69 | 70 | 当服务器接收到浏览器的请求之后,就会进行逻辑操作,处理完成之后返回一个`HTTP`响应消息,包括: 71 | 72 | - 状态行 73 | - 响应头 74 | - 响应正文 75 | 76 | ![](https://static.vue-js.com/c5fe0140-bdf4-11eb-ab90-d9ae814b240d.png) 77 | 78 | 在服务器响应之后,由于现在`http`默认开始长连接`keep-alive`,当页面关闭之后,`tcp`链接则会经过四次挥手完成断开 79 | 80 | 81 | 82 | ### 页面渲染 83 | 84 | 当浏览器接收到服务器响应的资源后,首先会对资源进行解析: 85 | 86 | - 查看响应头的信息,根据不同的指示做对应处理,比如重定向,存储cookie,解压gzip,缓存资源等等 87 | - 查看响应头的 Content-Type的值,根据不同的资源类型采用不同的解析方式 88 | 89 | 关于页面的渲染过程如下: 90 | 91 | - 解析HTML,构建 DOM 树 92 | - 解析 CSS ,生成 CSS 规则树 93 | - 合并 DOM 树和 CSS 规则,生成 render 树 94 | - 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算 95 | - 绘制 render 树( paint ),绘制页面像素信息 96 | - 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上 97 | 98 | ![](https://static.vue-js.com/db7bddd0-bdf4-11eb-85f6-6fac77c0c9b3.png) 99 | 100 | 101 | 102 | ## 参考文献 103 | 104 | - https://github.com/febobo/web-interview/issues/141 105 | - https://zhuanlan.zhihu.com/p/80551769 -------------------------------------------------------------------------------- /docs/http/handshakes_waves.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说TCP为什么需要三次握手和四次挥手? 2 | 3 | ![](https://static.vue-js.com/ef4696a0-beb9-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、三次握手 6 | 7 | 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包 8 | 9 | 主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备 10 | 11 | 过程如下: 12 | 13 | - 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c),此时客户端处于 SYN_SENT 状态 14 | - 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,为了确认客户端的 SYN,将客户端的 ISN+1作为ACK的值,此时服务器处于 SYN_RCVD 的状态 15 | - 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,值为服务器的ISN+1。此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接 16 | 17 | ![](https://static.vue-js.com/fb489fc0-beb9-11eb-85f6-6fac77c0c9b3.png) 18 | 19 | 上述每一次握手的作用如下: 20 | 21 | - 第一次握手:客户端发送网络包,服务端收到了 22 | 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。 23 | - 第二次握手:服务端发包,客户端收到了 24 | 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常 25 | - 第三次握手:客户端发包,服务端收到了。 26 | 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常 27 | 28 | 通过三次握手,就能确定双方的接收和发送能力是正常的。之后就可以正常通信了 29 | 30 | 31 | ### 为什么不是两次握手? 32 | 33 | 如果是两次握手,发送端可以确定自己发送的信息能对方能收到,也能确定对方发的包自己能收到,但接收端只能确定对方发的包自己能收到 无法确定自己发的包对方能收到 34 | 35 | 并且两次握手的话, 客户端有可能因为网络阻塞等原因会发送多个请求报文,延时到达的请求又会与服务器建立连接,浪费掉许多服务器的资源 36 | 37 | 38 | 39 | ## 二、四次挥手 40 | 41 | `tcp`终止一个连接,需要经过四次挥手 42 | 43 | 过程如下: 44 | 45 | - 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态,停止发送数据,等待服务端的确认 46 | - 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态 47 | - 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 `LAST_ACK` 的状态 48 | - 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态 49 | 50 | ![](https://static.vue-js.com/0a3ebb90-beba-11eb-85f6-6fac77c0c9b3.png) 51 | 52 | 53 | 54 | ### 四次挥手原因 55 | 56 | 服务端在收到客户端断开连接`Fin`报文后,并不会立即关闭连接,而是先发送一个`ACK`包先告诉客户端收到关闭连接的请求,只有当服务器的所有报文发送完毕之后,才发送`FIN`报文断开连接,因此需要四次挥手 57 | 58 | 59 | 60 | 61 | 62 | ## 三、总结 63 | 64 | 一个完整的三次握手四次挥手如下图所示: 65 | 66 | ![](https://static.vue-js.com/65941490-beba-11eb-85f6-6fac77c0c9b3.png) 67 | 68 | 69 | 70 | ## 参考文献 71 | 72 | - https://zhuanlan.zhihu.com/p/53374516 73 | - https://segmentfault.com/a/1190000020610336 -------------------------------------------------------------------------------- /docs/http/status.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说HTTP 常见的状态码有哪些,适用场景? 2 | 3 | ![](https://static.vue-js.com/038831d0-bbc9-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、是什么 6 | 7 | HTTP状态码(英语:HTTP Status Code),用以表示网页服务器超文本传输协议响应状态的3位数字代码 8 | 9 | 它由 RFC 2616规范定义的,并得到 `RFC 2518`、`RFC 2817`、`RFC 2295`、`RFC 2774`与 `RFC 4918`等规范扩展 10 | 11 | 简单来讲,`http`状态码的作用是服务器告诉客户端当前请求响应的状态,通过状态码就能判断和分析服务器的运行状态 12 | 13 | 14 | 15 | ## 二、分类 16 | 17 | 状态码第一位数字决定了不同的响应状态,有如下: 18 | 19 | - 1 表示消息 20 | - 2 表示成功 21 | - 3 表示重定向 22 | - 4 表示请求错误 23 | - 5 表示服务器错误 24 | 25 | 26 | 27 | ### 1xx 28 | 29 | 代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束 30 | 31 | 常见的有: 32 | 33 | - 100(客户端继续发送请求,这是临时响应):这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应 34 | - 101:服务器根据客户端的请求切换协议,主要用于websocket或http2升级 35 | 36 | 37 | 38 | 39 | 40 | ### 2xx 41 | 42 | 代表请求已成功被服务器接收、理解、并接受 43 | 44 | 常见的有: 45 | 46 | - 200(成功):请求已成功,请求所希望的响应头或数据体将随此响应返回 47 | - 201(已创建):请求成功并且服务器创建了新的资源 48 | 49 | - 202(已创建):服务器已经接收请求,但尚未处理 50 | - 203(非授权信息):服务器已成功处理请求,但返回的信息可能来自另一来源 51 | - 204(无内容):服务器成功处理请求,但没有返回任何内容 52 | - 205(重置内容):服务器成功处理请求,但没有返回任何内容 53 | - 206(部分内容):服务器成功处理了部分请求 54 | 55 | 56 | 57 | ### 3xx 58 | 59 | 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向 60 | 61 | 常见的有: 62 | 63 | - 300(多种选择):针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择 64 | - 301(永久移动):请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置 65 | - 302(临时移动): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求 66 | - 303(查看其他位置):请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码 67 | 68 | - 305 (使用代理): 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理 69 | - 307 (临时重定向): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求 70 | 71 | 72 | 73 | ### 4xx 74 | 75 | 代表了客户端看起来可能发生了错误,妨碍了服务器的处理 76 | 77 | 常见的有: 78 | 79 | - 400(错误请求): 服务器不理解请求的语法 80 | - 401(未授权): 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。 81 | - 403(禁止): 服务器拒绝请求 82 | - 404(未找到): 服务器找不到请求的网页 83 | - 405(方法禁用): 禁用请求中指定的方法 84 | - 406(不接受): 无法使用请求的内容特性响应请求的网页 85 | - 407(需要代理授权): 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理 86 | - 408(请求超时): 服务器等候请求时发生超时 87 | 88 | 89 | 90 | ### 5xx 91 | 92 | 表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生 93 | 94 | 常见的有: 95 | 96 | - 500(服务器内部错误):服务器遇到错误,无法完成请求 97 | - 501(尚未实施):服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码 98 | - 502(错误网关): 服务器作为网关或代理,从上游服务器收到无效响应 99 | - 503(服务不可用): 服务器目前无法使用(由于超载或停机维护) 100 | - 504(网关超时): 服务器作为网关或代理,但是没有及时从上游服务器收到请求 101 | - 505(HTTP 版本不受支持): 服务器不支持请求中所用的 HTTP 协议版本 102 | 103 | 104 | 105 | ## 三、适用场景 106 | 107 | 下面给出一些状态码的适用场景: 108 | 109 | - 100:客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。常用于POST大数据传输 110 | 111 | - 206:一般用来做断点续传,或者是视频文件等大文件的加载 112 | 113 | - 301:永久重定向会缓存。新域名替换旧域名,旧的域名不再使用时,用户访问旧域名时用301就重定向到新的域名 114 | 115 | - 302:临时重定向不会缓存,常用 于未登陆的用户访问用户中心重定向到登录页面 116 | - 304:协商缓存,告诉客户端有缓存,直接使用缓存中的数据,返回页面的只有头部信息,是没有内容部分 117 | - 400:参数有误,请求无法被服务器识别 118 | - 403:告诉客户端进制访问该站点或者资源,如在外网环境下,然后访问只有内网IP才能访问的时候则返回 119 | - 404:服务器找不到资源时,或者服务器拒绝请求又不想说明理由时 120 | - 503:服务器停机维护时,主动用503响应请求或 nginx 设置限速,超过限速,会返回503 121 | 122 | - 504:网关超时 123 | 124 | 125 | 126 | 127 | ## 参考文献 128 | 129 | - https://zh.wikipedia.org/wiki/HTTP状态码 130 | - https://kebingzao.com/2018/10/05/http-status-code/ 131 | - https://vue3js.cn/interview -------------------------------------------------------------------------------- /docs/linux/linux.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对操作系统的理解?核心概念有哪些? 2 | 3 | ![](https://static.vue-js.com/0f06bf30-008a-11ec-8e64-91fdec0f05a1.png) 4 | 5 | ## 一、是什么 6 | 7 | 操作系统(Operating System,缩写:OS)是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序,同时也是计算机系统的内核与基石 8 | 9 | 简单来讲,操作系统就是一种复杂的软件,相当于软件管家 10 | 11 | 操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务, 12 | 13 | 操作系统的类型非常多样,不同机器安装的操作系统可从简单到复杂,可从移动电话的嵌入式系统到超级电脑的大型操作系统,在计算机与用户之间起接口的作用,如下图: 14 | 15 | ![](https://static.vue-js.com/0ad1b850-009b-11ec-8e64-91fdec0f05a1.png) 16 | 17 | 许多操作系统制造者对它涵盖范畴的定义也不尽一致,例如有些操作系统集成了图形用户界面,而有些仅使用命令行界面,将图形用户界面视为一种非必要的应用程序 18 | 19 | 20 | 21 | 22 | ## 二、核心概念 23 | 24 | 操作系统的核心概念都是对具体物理硬件的抽象,主要有如下: 25 | 26 | - 进程(线程):进程(线程)是操作系统对CPU的抽象 27 | - 虚拟内存(地址空间):虚拟内存是操作系统对物理内存的抽象 28 | - 文件:文件是操作系统对物理磁盘的抽象 29 | - shell:它是一个程序,可从键盘获取命令并将其提供给操作系统以执行。 30 | - GUI :是一种用户界面,允许用户通过图形图标和音频指示符与电子设备进行交互 31 | - 计算机架构(computer architecture): 在计算机工程中,计算机体系结构是描述计算机系统功能,组织和实现的一组规则和方法。它主要包括指令集、内存管理、I/O 和总线结构 32 | - 多处理系统(Computer multitasking):是指计算机同时运行多个程序的能力 33 | - 程序计数器(Program counter):程序计数器 是一个 CPU 中的寄存器,用于指示计算机在其程序序列中的位置 34 | - 多线程(multithreading):是指从软件或者硬件上实现多个线程并发执行的技术 35 | 36 | - CPU 核心(core):它是 CPU 的大脑,它接收指令,并执行计算或运算以满足这些指令。一个 CPU 可以有多个内核 37 | - 图形处理器(Graphics Processing Unit):又称显示核心、视觉处理器、显示芯片或绘图芯片 38 | - 缓存命中(cache hit):当应用程序或软件请求数据时,会首先发生缓存命中 39 | 40 | - RAM((Random Access Memory):随机存取存储器,也叫主存,是与 CPU 直接交换数据的内部存储器 41 | 42 | - ROM (Read Only Memory):只读存储器是一种半导体存储器,其特性是一旦存储数据就无法改变或删除 43 | 44 | - 虚拟地址(virtual memory): 虚拟内存是计算机系统内存管理的一种机制 45 | 46 | - 驱动程序(device driver):设备驱动程序,简称驱动程序(driver),是一个允许高级别电脑软件与硬件交互的程序 47 | 48 | - USB(Universal Serial Bus):是连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规范 49 | 50 | - 地址空间(address space):地址空间是内存中可供程序或进程使用的有效地址范 51 | 52 | - 进程间通信(interprocess communication): 指至少两个进程或线程间传送数据或信号的一些技术或方法 53 | 54 | - 目录(directory): 在计算机或相关设备中,一个目录或文件夹就是一个装有数字文件系统的虚拟容器 55 | 56 | - 路径(path name): 路径是一种电脑文件或目录的名称的通用表现形式,它指向文件系统上的一个唯一位置。 57 | - 根目录(root directory):根目录指的就是计算机系统中的顶层目录,比如 Windows 中的 C 盘和 D 盘,Linux 中的 / 58 | - 工作目录(Working directory):它是一个计算机用语。用户在操作系统内所在的目录,用户可在此目录之下,用相对文件名访问文件。 59 | - 文件描述符(file descriptor): 文件描述符是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念 60 | - 客户端(clients):客户端是访问服务器提供的服务的计算机硬件或软件。 61 | - 服务端(servers): 在计算中,服务器是为其他程序或设备提供功能的计算机程序或设备 62 | 63 | 64 | 65 | ## 三、总结 66 | 67 | - 操作系统是管理计算机硬件与软件资源的程序,是计算机的基石 68 | - 操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源 69 | - 操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人,统筹着各种相关事项 70 | - 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性 71 | 72 | 73 | 74 | ## 参考文献 75 | 76 | - https://www.cnblogs.com/cxuanBlog/p/13297199.html 77 | - https://www.cnblogs.com/cxuanblog/p/12607608.html 78 | - https://www.anvilliu.com/2021/03/06/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E2%80%94%E2%80%94%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/ -------------------------------------------------------------------------------- /docs/linux/redirect_pipe.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对输入输出重定向和管道的理解?应用场景? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/1036dde0-0634-11ec-a752-75723a64e8f5.png) 6 | 7 | ## 一、是什么 8 | 9 | `linux`中有三种标准输入输出,分别是`STDIN`,`STDOUT`,`STDERR`,对应的数字是0、1、2: 10 | 11 | - STDIN 是标准输入,默认从键盘读取信息 12 | - STDOUT 是标准输出,默认将输出结果输出至终端 13 | - STDERR 是标准错误,默认将输出结果输出至终端 14 | 15 | 对于任何`linux`命令的执行会有下面的过程: 16 | 17 | ![](https://static.vue-js.com/1a57caf0-0634-11ec-8e64-91fdec0f05a1.png) 18 | 19 | 一条命令的执行需要键盘等的标准输入,命令的执行和正确或错误,其中的每一个双向箭头就是一个通道,所以数据流可以流入到文件端(**重定向或管道**) 20 | 21 | 简单来讲,重定向就是把本来要显示在终端的命令结果,输送到别的地方,分成: 22 | 23 | - 输入重定向:流出到屏幕如果命令所需的输入不是来自键盘,而是来自指定的文件 24 | - 输出重定向:命令的输出可以不显示在屏幕,而是写在指定的文件中 25 | 26 | 管道就是把两个命令连接起来使用,一个命令的输出作为另一个命令的输入 27 | 28 | 两者的区别在于: 29 | 30 | - 管道触发两个子进程,执行 | 两边的程序;而重定向是在一个进程内执行。 31 | - 管道两边都是shell命令 32 | - 重定向符号的右边只能是Linux文件 33 | - 重定向符号的优先级大于管道 34 | 35 | 36 | 37 | ## 二、命令 38 | 39 | 重定向常见的命令符号有: 40 | 41 | - \> : 输出重定向到一个文件或设备 覆盖原来的文件 42 | 43 | > 如果该文件不存在,则新建一个文件 44 | > 45 | > 如果该文件已经存在,会把文件内容覆盖 46 | > 47 | > 这些操纵不会征用用户的确认 48 | 49 | - \>> :输出重定向到一个文件或设备,但是是 追加原来的文件的末尾 50 | - < :用于制定命令的输入 51 | - << :从键盘的输入重定向为某个命令的输入 52 | 53 | > 以逐行输入的模式(回车键进行换行) 54 | > 55 | > 所有输入的行都将在输入结束字符串之后发送给命令 56 | 57 | - 2> 将一个标准错误输出重定向到一个文件或设备,会覆盖原来的文件 58 | - 2>> 将一个标准错误输出重定向到一个文件或设备,是追加到原来的文件 59 | - 2>&1:组合符号,将标准错误输出重定向到标准输出相同的地方 60 | 61 | > 1就是代表标准输出 62 | 63 | - \>& 将一个标准错误输出重定向到一个文件或设备覆盖原来的文件 64 | - |& 将一个标准错误管道输出到另一个命令作为输入 65 | 66 | 67 | 68 | ## 三、应用场景 69 | 70 | 将当前目录的文件输出重定向到`1.txt`文件中,并且会清空原有的`1.txt`的内容 71 | 72 | ```cmd 73 | ls -a > 1.txt 74 | ``` 75 | 76 | 或者以追加的形式,重定向输入到`1.txt`中 77 | 78 | ```cmd 79 | ls -a >> 1.txt 80 | ``` 81 | 82 | 将标准错误输出到某个文件,可以如下: 83 | 84 | ```cmd 85 | $ touch 2> 2.txt 86 | $ cat 2.txt 87 | touch: 缺少了文件操作数 88 | 请尝试执行 "touch --help" 来获取更多信息。 89 | ``` 90 | 91 | 通过组合符号将两者结合一起,无论进程输出的信息是正确还是错误的信息,都会重定向到指定的文件里 92 | 93 | ```cmd 94 | [root@linguanghui home]# abc &> file.txt 95 | [root@linguanghui home]# cat file.txt 96 | -bash: abc: command not found 97 | ``` 98 | 99 | 再者通过管道查询文件内容是否包含想要的信息: 100 | 101 | ```cmd 102 | cat test.txt | grep -n 'xxx' 103 | ``` 104 | 105 | 上述`cat test.txt`会将`test.txt`的内容作为标准输出,然后利用管道,将其作为`grep -n 'xxx'`命令的标准输入。 106 | 107 | 108 | 109 | ### 参考文献 110 | 111 | - https://segmentfault.com/a/1190000020519335 112 | - https://murphypei.github.io/blog/2018/04/linux-redirect-pipe 113 | - https://www.huaweicloud.com/articles/0fb70e8c724ae79f4fc8d676cd6160d3.html -------------------------------------------------------------------------------- /docs/linux/shell.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对 shell 的理解?常见的命令? 2 | 3 | ![](https://static.vue-js.com/71003620-0883-11ec-a752-75723a64e8f5.png) 4 | 5 | 6 | ## 一、是什么 7 | 8 | `Shell `是一个由`c`语言编写的应用程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言 9 | 10 | 它连接了用户和` Linux `内核,让用户能够更加高效、安全、低成本地使用 `Linux` 内核 11 | 12 | 其本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序,它和 QQ、微信等其它软件没有什么区别,特殊的地方就是开机立马启动,并呈现在用户面前 13 | 14 | 主要作用是接收用户输入的命令,并对命令进行处理,处理完毕后再将结果反馈给用户,比如输出到显示器、写入到文件等,同样能够调用和组织其他的应用程序,相当于一个领导者的身份,如下图: 15 | 16 | ![](https://static.vue-js.com/80db0ca0-0883-11ec-8e64-91fdec0f05a1.png) 17 | 18 | 那么`shell`脚本就是多个 `Shell` 命令的组合并通过 `if` 条件分支控制或循环来组合运算,实现一些复杂功能,文件后缀名为`.sh` 19 | 20 | 常用的 `ls` 命令,它本身也是一个 `Shell` 脚本,通过执行这个 `Shell` 脚本可以列举当前目录下的文件列表,如下创建一个`hello.sh`脚本 21 | 22 | ```shell 23 | #!/bin/bash 24 | 25 | # 执行的命令主体 26 | ls 27 | echo "hello world" 28 | ``` 29 | 30 | - #!/bin/bash :指定脚本要使用的 Shell 类型为 Bash 31 | 32 | - ls、echo: 脚本文件的内容,表明我们执行 hello.sh 脚本时会列举出当前目录的文件列表并且会向控制台打印 `hello world 33 | 34 | 执行方式为`.hello.zsh` 35 | 36 | 37 | 38 | ## 二、种类 39 | 40 | `Linux` 的 `Shell` 种类众多,只要能给用户提供命令行环境的程序,常见的有: 41 | 42 | - Bourne Shell(sh),是目前所有 Shell 的祖先,被安装在几乎所有发源于 Unix 的操作系统上 43 | 44 | - Bourne Again shell(bash) ,是 sh 的一个进阶版本,比 sh 更优秀, bash 是目前大多数 Linux 发行版以及 macOS 操作系统的默认 Shell 45 | 46 | - C Shell(csh) ,它的语法类似 C 语言 47 | 48 | - TENEX C Shell(tcsh) ,它是 csh 的优化版本 49 | 50 | - Korn shell(ksh) ,一般在收费的 Unix 版本上比较多见 51 | 52 | - Z Shell(zsh) ,它是一种比较新近的 Shell ,集 bash 、 ksh 和 tcsh 各家之大成 53 | 54 | ![](https://static.vue-js.com/8e739440-0883-11ec-a752-75723a64e8f5.png) 55 | 56 | 关于 `Shell` 的几个常见命令: 57 | 58 | - ls:查看文件 59 | - cd:切换工作目录 60 | - pwd:显示用户当前目录 61 | - mkdir:创建目录 62 | - cp:拷贝 63 | - rm:删除 64 | - mv:移动 65 | - du:显示目录所占用的磁盘空间 66 | 67 | ## 三、命令 68 | 69 | `Shell` 并不是简单的堆砌命令,我们还可以在 `Shell` 中编程,这和使用 `C++`、`C#`、`Java`、`Python` 等常见的编程语言并没有什么两样。 70 | 71 | Shell 虽然没有 C++、Java、Python 等强大,但也支持了基本的编程元素,例如: 72 | 73 | - if...else 选择结构,case...in 开关语句,for、while、until 循环; 74 | - 变量、数组、字符串、注释、加减乘除、逻辑运算等概念; 75 | - 函数,包括用户自定义的函数和内置函数(例如 printf、export、eval 等) 76 | 77 | 78 | 79 | 下面以`bash`为例简单了解一下`shell`的基本使用 80 | 81 | ### 变量 82 | 83 | `Bash` 没有数据类型的概念,所有的变量值都是字符串,可以保存一个数字、一个字符、一个字符串等等 84 | 85 | 同时无需提前声明变量,给变量赋值会直接创建变量 86 | 87 | 访问变量的语法形式为:`${var}` 和 `$var` 。 88 | 89 | 变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号。 90 | 91 | ```bash 92 | word="hello" 93 | echo ${word} 94 | # Output: hello 95 | ``` 96 | 97 | 98 | 99 | ### 条件控制 100 | 101 | 跟其它程序设计语言一样,Bash 中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在`[[ ]]`里的表达式 102 | 103 | 跟其他语言一样,使用`if...else`进行表达,如果中括号里的表达式为真,那么`then`和`fi`之间的代码会被执行,如果则`else`和`fi`之间的代码会被执行 104 | 105 | ```shell 106 | if [[ 2 -ne 1 ]]; then 107 | echo "true" 108 | else 109 | echo "false" 110 | fi 111 | # Output: true 112 | ``` 113 | 114 | `fi`标志着条件代码块的结束 115 | 116 | 117 | 118 | ### 函数 119 | 120 | bash 函数定义语法如下: 121 | 122 | ```bash 123 | [ function ] funname [()] { 124 | action; 125 | [return int;] 126 | } 127 | ``` 128 | 129 | - 函数定义时,function 关键字可有可无 130 | - 函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。如果不加 return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回值 131 | - 函数返回值在调用该函数后通过 $? 来获得 132 | - 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可 133 | 134 | ## 参考文献 135 | 136 | - http://c.biancheng.net/view/706.html 137 | - https://juejin.cn/post/6930013333454061575 -------------------------------------------------------------------------------- /docs/linux/thread_process.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说什么是进程?什么是线程?区别? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/f414d8a0-02f6-11ec-a752-75723a64e8f5.png) 6 | 7 | 8 | 9 | ## 一、进程 10 | 11 | 操作系统中最核心的概念就是进程,进程是对正在运行中的程序的一个抽象,是系统进行资源分配和调度的基本单位 12 | 13 | 操作系统的其他所有内容都是围绕着进程展开的,负责执行这些任务的是`CPU` 14 | 15 | ![](https://static.vue-js.com/3ff146b0-02f6-11ec-8e64-91fdec0f05a1.png) 16 | 17 | 18 | 19 | 进程是一种抽象的概念,从来没有统一的标准定义看,一般由程序、数据集合和进程控制块三部分组成: 20 | 21 | - 程序用于描述进程要完成的功能,是控制进程执行的指令集 22 | - 数据集合是程序在执行时所需要的数据和工作区 23 | - 程序控制块,包含进程的描述信息和控制信息,是进程存在的唯一标志 24 | 25 | 26 | ## 二、线程 27 | 28 | **线程**(thread)是操作系统能够进行**运算调度**的最小单位,其是进程中的一个执行任务(控制单元),负责当前进程中程序的执行 29 | 30 | 一个进程至少有一个线程,一个进程可以运行多个线程,这些线程共享同一块内存,线程之间可以共享对象、资源,如果有冲突或需要协同,还可以随时沟通以解决冲突或保持同步 31 | 32 | 举个例子,假设你经营着一家物业管理公司。最初,业务量很小,事事都需要你亲力亲为。给老张家修完暖气管道,立马再去老李家换电灯泡——这叫单线程,所有的工作都得顺序执行 33 | 34 | 后来业务拓展了,你雇佣了几个工人,这样,你的物业公司就可以同时为多户人家提供服务了——这叫多线程,你是主线程 35 | 36 | ![](https://static.vue-js.com/63de34c0-02f6-11ec-a752-75723a64e8f5.png) 37 | 38 | 但实际上,并不是线程越多,进程的工作效率越高,这是因为在一个进程内,不管你创建了多少线程,它们总是被限定在一颗`CPU`内,或者多核`CPU`的一个核内 39 | 40 | 这意味着,多线程在宏观上是并行的,在微观上则是分时切换串行的,多线程编程无法充分发挥多核计算资源的优势 41 | 42 | 这导致使用多线程做任务并行处理时,线程数量超过一定数值后,线程越多速度反倒越慢的原因 43 | 44 | 45 | 46 | ## 三、区别 47 | 48 | - **本质区别**:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位 49 | 50 | - **在开销方面**:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小 51 | 52 | - **所处环境**:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行) 53 | 54 | - **内存分配方面**:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源 55 | 56 | - **包含关系**:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程 57 | 58 | 59 | 举个例子:进程=火车,线程=车厢 60 | 61 | - 线程在进程下行进(单纯的车厢无法运行) 62 | - 一个进程可以包含多个线程(一辆火车可以有多个车厢) 63 | - 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘) 64 | - 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易) 65 | - 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源) 66 | - 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢) 67 | 68 | 69 | ## 参考文献 70 | 71 | - https://zhuanlan.zhihu.com/p/106283969 72 | - https://blog.csdn.net/ThinkWAon/article/details/102021274 73 | - https://www.zhihu.com/question/25532384 -------------------------------------------------------------------------------- /docs/linux/vim.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说 linux 系统下 文本编辑常用的命令有哪些? 2 | 3 | ![](https://static.vue-js.com/1062b8b0-049b-11ec-8e64-91fdec0f05a1.png) 4 | 5 | ## 一、是什么 6 | 7 | `Vim`是从 `vi` 发展出来的一个文本编辑器,代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。 8 | 9 | 简单的来说, `vi` 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方 10 | 11 | 而`vim `可以说是程序开发者的一项很好用的工具 12 | 13 | 14 | 15 | ## 二、使用 16 | 17 | 基本上 vi/vim 共分为三种模式,分别是: 18 | 19 | - 命令模式(Command mode) 20 | - 输入模式(Insert mode) 21 | - 底线命令模式(Last line mode) 22 | 23 | ![](https://static.vue-js.com/265a0080-03d6-11ec-a752-75723a64e8f5.png) 24 | 25 | 26 | 27 | ### 命令模式 28 | 29 | `Vim` 的默认模式,在这个模式下,你不能输入文本,但是可以让我们在文本间移动,删除一行文本,复制黏贴文本,跳转到指定行,撤销操作,等等 30 | 31 | 32 | 33 | #### 移动光标 34 | 35 | 常用的命令如下: 36 | 37 | - h 向左移动一个字符 38 | - j 向下移动一个字符 39 | - k 向上移动一个字符 40 | - i 向右移动一个字符 41 | 42 | 或者使用方向键进行控制 43 | 44 | 如果想要向下移动`n`行,可通过使用 "nj" 或 "n↓" 的组合按键 45 | 46 | 47 | 48 | #### 搜索 49 | 50 | 常见的命令如下: 51 | 52 | - /word:向光标之下寻找一个名称为 word 的字符 53 | 54 | - ?word:向光标之上寻找一个字符串名称为 word 的字符串 55 | - n:代表重复前一个搜寻的动作,即再次执行上一次的操作 56 | - N:反向进行前一个搜索动作 57 | 58 | 59 | 60 | 61 | 62 | #### 删除、复制、粘贴 63 | 64 | 常用的命令如下: 65 | 66 | - x:向后删除一个字符 67 | - X:向前删除一个字符 68 | - nc:n 为数字,连续向后删除 n 个字符 69 | - dd:删除游标所在的那一整行 70 | - d0:删除游标所在处,到该行的最前面一个字符 71 | - d$删除游标所在处,到该行的最后一个字符 72 | - ndd:除光标所在的向下 n 行 73 | - yy:复制游标所在的那一行 74 | - y0:复制光标所在的那个字符到该行行首的所有数据 75 | - y$:复制光标所在的那个字符到该行行尾的所有数据 76 | - p:已复制的数据在光标下一行贴上 77 | - P:已复制的数据在光标上一行贴上 78 | - nc:重复删除n行数据 79 | 80 | 81 | 82 | ### 输入模式 83 | 84 | 命令模式通过输入大小写`i`、`a`、`o`可以切换到输入模式,如下: 85 | 86 | - i:从目前光标所在处输入 87 | - I:在目前所在行的第一个非空格符处开始输入 88 | - a:从目前光标所在的下一个字符处开始输入 89 | - A:从光标所在行的最后一个字符处开始输入 90 | - o:在目前光标所在的下一行处输入新的一行 91 | - O:目前光标所在的上一行处输入新的一行 92 | 93 | 输入模式我们熟悉的文本编辑器的模式,就是可以输入任何你想输入的内容 94 | 95 | 如果想从插入模式回到命令模式,使用按下键盘左上角的`ESC`键 96 | 97 | 98 | 99 | 100 | 101 | ### 底线命令模式 102 | 103 | 这个模式下可以运行一些命令例如“退出”,“保存”,等动作,为了进入底线命令模式,首先要进入命令模式,再按下冒号键: 104 | 105 | 常见的命令如下: 106 | 107 | - w:将编辑的数据写入硬盘档案中 108 | - w!:若文件属性为『只读』时,强制写入该档案 109 | - q:未修改,直接退出 110 | - q!:修改过但不存储 111 | - wq:储存后离开 112 | 113 | 114 | 115 | ## 参考文献 116 | 117 | - https://www.runoob.com/linux/linux-vim.html -------------------------------------------------------------------------------- /docs/typescript/function.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对 TypeScript 中函数的理解?与 JavaScript 函数的区别? 2 | 3 | 4 | 5 | ![](https://static.vue-js.com/3f1c1390-0d42-11ec-a752-75723a64e8f5.png) 6 | 7 | ## 一、是什么 8 | 9 | 函数是` JavaScript` 应用程序的基础,帮助我们实现抽象层、模拟类、信息隐藏和模块 10 | 11 | 在` TypeScript` 里,虽然已经支持类、命名空间和模块,但函数仍然是主要定义行为的方式,`TypeScript` 为 `JavaScript` 函数添加了额外的功能,丰富了更多的应用场景 12 | 13 | 函数类型在 `TypeScript` 类型系统中扮演着非常重要的角色,它们是可组合系统的核心构建块 14 | 15 | 16 | ## 二、使用方式 17 | 18 | 跟`javascript` 定义函数十分相似,可以通过`funciton` 关键字、箭头函数等形式去定义,例如下面一个简单的加法函数: 19 | 20 | ```ts 21 | const add = (a: number, b: number) => a + b 22 | ``` 23 | 24 | 上述只定义了函数的两个参数类型,这个时候整个函数虽然没有被显式定义,但是实际上` TypeScript` 编译器是能够通过类型推断到这个函数的类型,如下图所示: 25 | 26 | ![](https://static.vue-js.com/4b3415b0-0d42-11ec-8e64-91fdec0f05a1.png) 27 | 28 | 当鼠标放置在第三行`add`函数名的时候,会出现完整的函数定义类型,通过`:` 的形式来定于参数类型,通过 `=>` 连接参数和返回值类型 29 | 30 | 当我们没有提供函数实现的情况下,有两种声明函数类型的方式,如下所示: 31 | 32 | ```ts 33 | // 方式一 34 | type LongHand = { 35 | (a: number): number; 36 | }; 37 | 38 | // 方式二 39 | type ShortHand = (a: number) => number; 40 | ``` 41 | 42 | 当存在函数重载时,只能使用方式一的形式 43 | 44 | 45 | 46 | ### 可选参数 47 | 48 | 当函数的参数可能是不存在的,只需要在参数后面加上 `?` 代表参数可能不存在,如下: 49 | 50 | ```ts 51 | const add = (a: number, b?: number) => a + (b ? b : 0) 52 | ``` 53 | 54 | 这时候参数`b`可以是`number`类型或者`undefined`类型,即可以传一个`number`类型或者不传都可以 55 | 56 | 57 | 58 | ### 剩余类型 59 | 60 | 剩余参数与`JavaScript`的语法类似,需要用 `...` 来表示剩余参数 61 | 62 | 如果剩余参数 `rest` 是一个由`number`类型组成的数组,则如下表示: 63 | 64 | ```ts 65 | const add = (a: number, ...rest: number[]) => rest.reduce(((a, b) => a + b), a) 66 | ``` 67 | 68 | 69 | 70 | ### 函数重载 71 | 72 | 允许创建数项名称相同但输入输出类型或个数不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力 73 | 74 | 关于`typescript`函数重载,必须要把精确的定义放在前面,最后函数实现时,需要使用 `|`操作符或者`?`操作符,把所有可能的输入类型全部包含进去,用于具体实现 75 | 76 | 这里的函数重载也只是多个函数的声明,具体的逻辑还需要自己去写,`typescript`并不会真的将你的多个重名 `function `的函数体进行合并 77 | 78 | 例如我们有一个add函数,它可以接收 `string`类型的参数进行拼接,也可以接收 `number` 类型的参数进行相加,如下: 79 | 80 | ```ts 81 | // 上边是声明 82 | function add (arg1: string, arg2: string): string 83 | function add (arg1: number, arg2: number): number 84 | // 因为我们在下边有具体函数的实现,所以这里并不需要添加 declare 关键字 85 | 86 | // 下边是实现 87 | function add (arg1: string | number, arg2: string | number) { 88 | // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 arg1 + arg2 89 | if (typeof arg1 === 'string' && typeof arg2 === 'string') { 90 | return arg1 + arg2 91 | } else if (typeof arg1 === 'number' && typeof arg2 === 'number') { 92 | return arg1 + arg2 93 | } 94 | } 95 | ``` 96 | 97 | 98 | 99 | ## 三、区别 100 | 101 | 从上面可以看到: 102 | 103 | - 从定义的方式而言,typescript 声明函数需要定义参数类型或者声明返回值类型 104 | - typescript 在参数中,添加可选参数供使用者选择 105 | - typescript 增添函数重载功能,使用者只需要通过查看函数声明的方式,即可知道函数传递的参数个数以及类型 106 | 107 | ## 参考文献 108 | 109 | - https://www.tslang.cn/docs/handbook/functions.html 110 | - https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD 111 | - https://jkchao.github.io/typescript-book-chinese/typings/functions.html#%E9%87%8D%E8%BD%BD -------------------------------------------------------------------------------- /docs/typescript/namespace_module.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说对 TypeScript 中命名空间与模块的理解?区别? 2 | 3 | ![](https://static.vue-js.com/9378d760-137e-11ec-8e64-91fdec0f05a1.png) 4 | 5 | 6 | ## 一、模块 7 | 8 | `TypeScript` 与` ECMAScript` 2015 一样,任何包含顶级 `import` 或者 `export` 的文件都被当成一个模块 9 | 10 | 相反地,如果一个文件不带有顶级的`import`或者`export`声明,那么它的内容被视为全局可见的 11 | 12 | 例如我们在在一个 `TypeScript` 工程下建立一个文件 `1.ts`,声明一个变量`a`,如下: 13 | 14 | ```ts 15 | const a = 1 16 | ``` 17 | 18 | 然后在另一个文件同样声明一个变量`a`,这时候会出现错误信息 19 | 20 | ![](https://static.vue-js.com/a239d970-137e-11ec-a752-75723a64e8f5.png) 21 | 22 | 提示重复声明`a`变量,但是所处的空间是全局的 23 | 24 | 如果需要解决这个问题,则通过`import`或者`export`引入模块系统即可,如下: 25 | 26 | ```ts 27 | const a = 10; 28 | 29 | export default a 30 | ``` 31 | 32 | 在`typescript`中,`export`关键字可以导出变量或者类型,用法与`es6`模块一致,如下: 33 | 34 | ```ts 35 | export const a = 1 36 | export type Person = { 37 | name: String 38 | } 39 | ``` 40 | 41 | 通过`import` 引入模块,如下: 42 | 43 | ```ts 44 | import { a, Person } from './export'; 45 | ``` 46 | 47 | 48 | 49 | ## 二、命名空间 50 | 51 | 命名空间一个最明确的目的就是解决重名问题 52 | 53 | 命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的 54 | 55 | 这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中 56 | 57 | `TypeScript` 中命名空间使用 `namespace` 来定义,语法格式如下: 58 | 59 | ```ts 60 | namespace SomeNameSpaceName { 61 | export interface ISomeInterfaceName { } 62 | export class SomeClassName { } 63 | } 64 | ``` 65 | 66 | 以上定义了一个命名空间 `SomeNameSpaceName`,如果我们需要在外部可以调用 `SomeNameSpaceName` 中的类和接口,则需要在类和接口添加 `export` 关键字 67 | 68 | 使用方式如下: 69 | 70 | ```ts 71 | SomeNameSpaceName.SomeClassName 72 | ``` 73 | 74 | 命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性,如下: 75 | 76 | ```ts 77 | namespace Letter { 78 | export let a = 1; 79 | export let b = 2; 80 | export let c = 3; 81 | // ... 82 | export let z = 26; 83 | } 84 | ``` 85 | 86 | 编译成`js`如下: 87 | 88 | ```js 89 | var Letter; 90 | (function (Letter) { 91 | Letter.a = 1; 92 | Letter.b = 2; 93 | Letter.c = 3; 94 | // ... 95 | Letter.z = 26; 96 | })(Letter || (Letter = {})); 97 | ``` 98 | 99 | 100 | 101 | 102 | 103 | ## 三、区别 104 | 105 | - 命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中 106 | 107 | - 像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖 108 | 109 | - 在正常的TS项目开发过程中并不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用 110 | 111 | 112 | ## 参考文献 113 | 114 | - https://www.tslang.cn/docs/handbook/modules.html 115 | - https://www.tslang.cn/docs/handbook/namespaces.html 116 | - https://www.tslang.cn/docs/handbook/namespaces-and-modules.html -------------------------------------------------------------------------------- /docs/typescript/typescript_javascript.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对 TypeScript 的理解?与 JavaScript 的区别? 2 | 3 | ![](https://static.vue-js.com/58cd3580-0950-11ec-8e64-91fdec0f05a1.png) 4 | 5 | ## 一、是什么 6 | 7 | `TypeScript` 是 `JavaScript` 的类型的超集,支持`ES6`语法,支持面向对象编程的概念,如类、接口、继承、泛型等 8 | 9 | > 超集,不得不说另外一个概念,子集,怎么理解这两个呢,举个例子,如果一个集合 A 里面的的所有元素集合 B 里面都存在,那么我们可以理解集合 B 是集合 A 的超集,集合 A 为集合 B 的子集 10 | 11 | ![](https://static.vue-js.com/61c2c1f0-0950-11ec-a752-75723a64e8f5.png) 12 | 13 | 其是一种静态类型检查的语言,提供了类型注解,在代码编译阶段就可以检查出数据类型的错误 14 | 15 | 同时扩展了` JavaScript` 的语法,所以任何现有的` JavaScript` 程序可以不加改变的在 `TypeScript` 下工作 16 | 17 | 为了保证兼容性,`TypeScript` 在编译阶段需要编译器编译成纯 `JavaScript` 来运行,是为大型应用之开发而设计的语言,如下: 18 | 19 | `ts` 文件如下: 20 | 21 | ```ts 22 | const hello: string = "Hello World!"; 23 | console.log(hello); 24 | ``` 25 | 26 | 编译文件后: 27 | 28 | ```js 29 | const hello = "Hello World!"; 30 | console.log(hello); 31 | ``` 32 | 33 | ## 二、特性 34 | 35 | `TypeScript` 的特性主要有如下: 36 | 37 | - **类型批注和编译时类型检查** :在编译时批注变量类型 38 | - **类型推断**:ts 中没有批注变量类型会自动推断变量的类型 39 | - **类型擦除**:在编译过程中批注的内容和接口会在运行时利用工具擦除 40 | - **接口**:ts 中用接口来定义对象类型 41 | - **枚举**:用于取值被限定在一定范围内的场景 42 | - **Mixin**:可以接受任意类型的值 43 | - **泛型编程**:写代码时使用一些以后才指定的类型 44 | - **名字空间**:名字只在该区域内有效,其他区域可重复使用该名字而不冲突 45 | - **元组**:元组合并了不同类型的对象,相当于一个可以装不同类型数据的数组 46 | - ... 47 | 48 | ### 类型批注 49 | 50 | 通过类型批注提供在编译时启动类型检查的静态类型,这是可选的,而且可以忽略而使用 `JavaScript` 常规的动态类型 51 | 52 | ```tsx 53 | function Add(left: number, right: number): number { 54 | return left + right; 55 | } 56 | ``` 57 | 58 | 对于基本类型的批注是 `number`、`bool` 和 `string`,而弱或动态类型的结构则是 `any` 类型 59 | 60 | ### 类型推断 61 | 62 | 当类型没有给出时,TypeScript 编译器利用类型推断来推断类型,如下: 63 | 64 | ```ts 65 | let str = "string"; 66 | ``` 67 | 68 | 变量 `str` 被推断为字符串类型,这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时 69 | 70 | 如果缺乏声明而不能推断出类型,那么它的类型被视作默认的动态 `any` 类型 71 | 72 | ### 接口 73 | 74 | 接口简单来说就是用来描述对象的类型 数据的类型有 `number`、`null`、` string` 等数据格式,对象的类型就是用接口来描述的 75 | 76 | ```tsx 77 | interface Person { 78 | name: string; 79 | age: number; 80 | } 81 | 82 | let tom: Person = { 83 | name: "Tom", 84 | age: 25, 85 | }; 86 | ``` 87 | 88 | ## 三、区别 89 | 90 | - TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法 91 | - TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译 92 | - TypeScript 文件的后缀名 .ts (.ts,.tsx,.dts),JavaScript 文件是 .js 93 | - 在编写 TypeScript 的文件的时候就会自动编译成 js 文件 94 | 95 | 更多的区别如下图所示: 96 | 97 | ![](https://static.vue-js.com/6b544040-0950-11ec-8e64-91fdec0f05a1.png) 98 | 99 | ## 参考文献 100 | 101 | - [https://zhuanlan.zhihu.com/p/140012915](https://zhuanlan.zhihu.com/p/140012915) 102 | - [https://www.jianshu.com/p/c8aaba6e8ce0](https://www.jianshu.com/p/c8aaba6e8ce0) 103 | - [https://www.cnblogs.com/powertoolsteam/p/13500668.html](https://www.cnblogs.com/powertoolsteam/p/13500668.html) 104 | -------------------------------------------------------------------------------- /docs/vue/components_plugin.md: -------------------------------------------------------------------------------- 1 | # 面试官:Vue中组件和插件有什么区别? 2 | 3 | ![image.png](https://static.vue-js.com/683475e0-3acc-11eb-ab90-d9ae814b240d.png) 4 | 5 | ## 一、组件是什么 6 | 7 | 回顾以前对组件的定义: 8 | 9 | 组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在`Vue`中每一个`.vue`文件都可以视为一个组件 10 | 11 | 组件的优势 12 | 13 | - 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现 14 | 15 | - 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单 16 | 17 | - 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级 18 | 19 | ## 二、插件是什么 20 | 21 | 插件通常用来为 `Vue` 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种: 22 | 23 | - 添加全局方法或者属性。如: `vue-custom-element` 24 | - 添加全局资源:指令/过滤器/过渡等。如 `vue-touch` 25 | - 通过全局混入来添加一些组件选项。如` vue-router` 26 | - 添加 `Vue` 实例方法,通过把它们添加到 `Vue.prototype` 上实现。 27 | - 一个库,提供自己的 `API`,同时提供上面提到的一个或多个功能。如` vue-router` 28 | 29 | ## 三、两者的区别 30 | 31 | 两者的区别主要表现在以下几个方面: 32 | 33 | - 编写形式 34 | - 注册形式 35 | - 使用场景 36 | 37 | 38 | ### 编写形式 39 | 40 | #### 编写组件 41 | 42 | 编写一个组件,可以有很多方式,我们最常见的就是`vue`单文件的这种格式,每一个`.vue`文件我们都可以看成是一个组件 43 | 44 | `vue`文件标准格式 45 | 46 | ```vue 47 | 49 | 54 | 56 | ``` 57 | 58 | 我们还可以通过`template`属性来编写一个组件,如果组件内容多,我们可以在外部定义`template`组件内容,如果组件内容并不多,我们可直接写在`template`属性上 59 | 60 | ```js 61 | 64 | 65 | Vue.component('componentA',{ 66 | template: '#testComponent' 67 | template: `
    component
    ` // 组件内容少可以通过这种形式 68 | }) 69 | ``` 70 | 71 | #### 编写插件 72 | `vue`插件的实现应该暴露一个 `install` 方法。这个方法的第一个参数是 `Vue` 构造器,第二个参数是一个可选的选项对象 73 | 74 | ```js 75 | MyPlugin.install = function (Vue, options) { 76 | // 1. 添加全局方法或 property 77 | Vue.myGlobalMethod = function () { 78 | // 逻辑... 79 | } 80 | 81 | // 2. 添加全局资源 82 | Vue.directive('my-directive', { 83 | bind (el, binding, vnode, oldVnode) { 84 | // 逻辑... 85 | } 86 | ... 87 | }) 88 | 89 | // 3. 注入组件选项 90 | Vue.mixin({ 91 | created: function () { 92 | // 逻辑... 93 | } 94 | ... 95 | }) 96 | 97 | // 4. 添加实例方法 98 | Vue.prototype.$myMethod = function (methodOptions) { 99 | // 逻辑... 100 | } 101 | } 102 | ``` 103 | 104 | 105 | ### 注册形式 106 | 107 | #### 组件注册 108 | 109 | `vue`组件注册主要分为全局注册与局部注册 110 | 111 | 全局注册通过`Vue.component`方法,第一个参数为组件的名称,第二个参数为传入的配置项 112 | 113 | ```js 114 | Vue.component('my-component-name', { /* ... */ }) 115 | ``` 116 | 117 | 局部注册只需在用到的地方通过`components`属性注册一个组件 118 | 119 | ```js 120 | const component1 = {...} // 定义一个组件 121 | 122 | export default { 123 | components:{ 124 | component1 // 局部注册 125 | } 126 | } 127 | ``` 128 | 129 | 130 | #### 插件注册 131 | 插件的注册通过`Vue.use()`的方式进行注册(安装),第一个参数为插件的名字,第二个参数是可选择的配置项 132 | 133 | ```js 134 | Vue.use(插件名字,{ /* ... */} ) 135 | ``` 136 | 137 | 注意的是: 138 | 139 | 注册插件的时候,需要在调用 `new Vue()` 启动应用之前完成 140 | 141 | `Vue.use`会自动阻止多次注册相同插件,只会注册一次 142 | 143 | 144 | 145 | ### 使用场景 146 | 147 | 具体的其实在插件是什么章节已经表述了,这里在总结一下 148 | 149 | 组件 `(Component)` 是用来构成你的 `App` 的业务模块,它的目标是 `App.vue` 150 | 151 | 插件 `(Plugin)` 是用来增强你的技术栈的功能模块,它的目标是 `Vue` 本身 152 | 153 | 简单来说,插件就是指对`Vue`的功能的增强或补充 154 | 155 | 156 | ## 参考文献 157 | 158 | - https://vue3js.cn/docs/zh -------------------------------------------------------------------------------- /docs/webpack/HMR.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说webpack的热更新是如何做到的?原理是什么? 2 | 3 | ![](https://static.vue-js.com/a076da40-acd4-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | 6 | 7 | ### 一、是什么 8 | 9 | `HMR `全称 `Hot Module Replacement`,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用 10 | 11 | 例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失 12 | 13 | 如果使用的是 `HMR`,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用 14 | 15 | 在`webpack`中配置开启热模块也非常的简单,如下代码: 16 | 17 | ```js 18 | const webpack = require('webpack') 19 | module.exports = { 20 | // ... 21 | devServer: { 22 | // 开启 HMR 特性 23 | hot: true 24 | // hotOnly: true 25 | } 26 | } 27 | ``` 28 | 29 | 通过上述这种配置,如果我们修改并保存`css`文件,确实能够以不刷新的形式更新到页面中 30 | 31 | 但是,当我们修改并保存`js`文件之后,页面依旧自动刷新了,这里并没有触发热模块 32 | 33 | 所以,`HMR `并不像 `Webpack` 的其他特性一样可以开箱即用,需要有一些额外的操作 34 | 35 | 我们需要去指定哪些模块发生更新时进行`HRM`,如下代码: 36 | 37 | ```js 38 | if(module.hot){ 39 | module.hot.accept('./util.js',()=>{ 40 | console.log("util.js更新了") 41 | }) 42 | } 43 | ``` 44 | 45 | 46 | 47 | ## 二、实现原理 48 | 49 | 首先来看看一张图,如下: 50 | 51 | ![](https://static.vue-js.com/adc05780-acd4-11eb-ab90-d9ae814b240d.png) 52 | 53 | - Webpack Compile:将 JS 源代码编译成 bundle.js 54 | - HMR Server:用来将热更新的文件输出给 HMR Runtime 55 | - Bundle Server:静态资源文件服务器,提供文件访问路径 56 | - HMR Runtime:socket服务器,会被注入到浏览器,更新文件的变化 57 | - bundle.js:构建输出的文件 58 | - 在HMR Runtime 和 HMR Server之间建立 websocket,即图上4号线,用于实时更新文件变化 59 | 60 | 上面图中,可以分成两个阶段: 61 | 62 | - 启动阶段为上图 1 - 2 - A - B 63 | 64 | 在编写未经过`webpack`打包的源代码后,`Webpack Compile` 将源代码和 `HMR Runtime` 一起编译成 `bundle `文件,传输给` Bundle Server` 静态资源服务器 65 | 66 | - 更新阶段为上图 1 - 2 - 3 - 4 67 | 68 | 当某一个文件或者模块发生变化时,`webpack `监听到文件变化对文件重新编译打包,编译生成唯一的` hash `值,这个`hash `值用来作为下一次热更新的标识 69 | 70 | 根据变化的内容生成两个补丁文件:`manifest`(包含了 `hash` 和 `chundId `,用来说明变化的内容)和` chunk.js` 模块 71 | 72 | 由于`socket`服务器在`HMR Runtime` 和 `HMR Server`之间建立 `websocket`链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的`hash`值,如下图的`h`属性,作为下一次热更细的标识 73 | 74 | ![](https://static.vue-js.com/05a0edf0-ad4a-11eb-85f6-6fac77c0c9b3.png) 75 | 76 | 在浏览器接受到这条消息之前,浏览器已经在上一次` socket` 消息中已经记住了此时的` hash` 标识,这时候我们会创建一个 `ajax` 去服务端请求获取到变化内容的 `manifest` 文件 77 | 78 | `mainfest`文件包含重新`build`生成的`hash`值,以及变化的模块,对应上图的`c`属性 79 | 80 | 浏览器根据 `manifest` 文件获取模块变化的内容,从而触发`render`流程,实现局部模块更新 81 | 82 | ![](https://static.vue-js.com/0e7b7850-ad4a-11eb-ab90-d9ae814b240d.png) 83 | 84 | 85 | 86 | ## 三、总结 87 | 88 | 关于`webpack`热模块更新的总结如下: 89 | 90 | - 通过`webpack-dev-server`创建两个服务器:提供静态资源的服务(express)和Socket服务 91 | - express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析) 92 | - socket server 是一个 websocket 的长连接,双方可以通信 93 | - 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk) 94 | - 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器) 95 | - 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新 96 | 97 | 98 | 99 | ## 参考文献 100 | 101 | - https://zhuanlan.zhihu.com/p/138446061 102 | - https://github.com/Jocs/jocs.github.io/issues/15 103 | - https://juejin.cn/post/6844904134697549832 104 | - https://vue3js.cn/interview/ -------------------------------------------------------------------------------- /docs/webpack/proxy.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说webpack proxy工作原理?为什么能解决跨域? 2 | 3 | ![](https://static.vue-js.com/5b871600-ace5-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | 6 | 7 | ## 一、是什么 8 | 9 | `webpack proxy`,即`webpack`提供的代理服务 10 | 11 | 基本行为就是接收客户端发送的请求后转发给其他服务器 12 | 13 | 其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制) 14 | 15 | 想要实现代理首先需要一个中间服务器,`webpack`中提供服务器的工具为`webpack-dev-server` 16 | 17 | #### webpack-dev-server 18 | `webpack-dev-server`是 `webpack` 官方推出的一款开发工具,将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起 19 | 20 | 目的是为了提高开发者日常的开发效率,**只适用在开发阶段** 21 | 22 | 关于配置方面,在`webpack`配置对象属性中通过`devServer`属性提供,如下: 23 | 24 | ```js 25 | // ./webpack.config.js 26 | const path = require('path') 27 | 28 | module.exports = { 29 | // ... 30 | devServer: { 31 | contentBase: path.join(__dirname, 'dist'), 32 | compress: true, 33 | port: 9000, 34 | proxy: { 35 | '/api': { 36 | target: 'https://api.github.com' 37 | } 38 | } 39 | // ... 40 | } 41 | } 42 | ``` 43 | 44 | `devServetr`里面`proxy`则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配 45 | 46 | 属性的名称是需要被代理的请求路径前缀,一般为了辨别都会设置前缀为` /api`,值为对应的代理匹配规则,对应如下: 47 | 48 | - target:表示的是代理到的目标地址 49 | - pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite 50 | - secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false 51 | - changeOrigin:它表示是否更新代理后请求的 headers 中host地址 52 | 53 | 54 | 55 | 56 | 57 | ## 二、工作原理 58 | 59 | `proxy`工作原理实质上是利用`http-proxy-middleware` 这个`http`代理中间件,实现请求转发给其他服务器 60 | 61 | 举个例子: 62 | 63 | 在开发阶段,本地地址为`http://localhost:3000`,该浏览器发送一个前缀带有`/api`标识的请求到服务端获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器中 64 | 65 | ```js 66 | const express = require('express'); 67 | const proxy = require('http-proxy-middleware'); 68 | 69 | const app = express(); 70 | 71 | app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true})); 72 | app.listen(3000); 73 | 74 | // http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar 75 | ``` 76 | 77 | 78 | 79 | ## 三、跨域 80 | 81 | 在开发阶段, `webpack-dev-server` 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 `localhost `的一个端口上,而后端服务又是运行在另外一个地址上 82 | 83 | 所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题 84 | 85 | 通过设置`webpack proxy`实现代理请求后,相当于浏览器与服务端中添加一个代理者 86 | 87 | 当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地 88 | 89 | ![](https://static.vue-js.com/65b5e5c0-ace5-11eb-85f6-6fac77c0c9b3.png) 90 | 91 | 在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据 92 | 93 | 注意:**服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制** 94 | 95 | 96 | ## 参考文献 97 | 98 | - https://webpack.docschina.org/configuration/dev-server/#devserverproxy -------------------------------------------------------------------------------- /docs/webpack/webpack.md: -------------------------------------------------------------------------------- 1 | # 面试官:说说你对webpack的理解?解决了什么问题? 2 | 3 | ![](https://static.vue-js.com/898ed570-a578-11eb-85f6-6fac77c0c9b3.png) 4 | 5 | ## 一、背景 6 | 7 | `Webpack` 最初的目标是实现前端项目的模块化,旨在更高效地管理和维护项目中的每一个资源 8 | 9 | #### 模块化 10 | 11 | 最早的时候,我们会通过文件划分的形式实现模块化,也就是将每个功能及其相关状态数据各自单独放到不同的` JS` 文件中 12 | 13 | 约定每个文件是一个独立的模块,然后再将这些`js`文件引入到页面,一个`script`标签对应一个模块,然后调用模块化的成员 14 | 15 | ```html 16 | 17 | 18 | ``` 19 | 20 | 但这种模块弊端十分的明显,模块都是在全局中工作,大量模块成员污染了环境,模块与模块之间并没有依赖关系、维护困难、没有私有空间等问题 21 | 22 | 项目一旦变大,上述问题会尤其明显 23 | 24 | 随后,就出现了命名空间方式,规定每个模块只暴露一个全局对象,然后模块的内容都挂载到这个对象中 25 | 26 | ```js 27 | window.moduleA = { 28 | method1: function () { 29 | console.log('moduleA#method1') 30 | } 31 | } 32 | ``` 33 | 34 | 这种方式也并没有解决第一种方式的依赖等问题 35 | 36 | 再后来,我们使用立即执行函数为模块提供私有空间,通过参数的形式作为依赖声明,如下 37 | 38 | ```js 39 | // module-a.js 40 | (function ($) { 41 | var name = 'module-a' 42 | 43 | function method1 () { 44 | console.log(name + '#method1') 45 | $('body').animate({ margin: '200px' }) 46 | } 47 | 48 | window.moduleA = { 49 | method1: method1 50 | } 51 | })(jQuery) 52 | ``` 53 | 54 | 上述的方式都是早期解决模块的方式,但是仍然存在一些没有解决的问题。例如,我们是用过`script`标签在页面引入这些模块的,这些模块的加载并不受代码的控制,时间一久维护起来也十分的麻烦 55 | 56 | 理想的解决方式是,在页面中引入一个` JS `入口文件,其余用到的模块可以通过代码控制,按需加载进来 57 | 58 | 除了模块加载的问题以外,还需要规定模块化的规范,如今流行的则是`CommonJS `、`ES Modules` 59 | 60 | 61 | ## 二、问题 62 | 63 | 从后端渲染的`JSP`、`PHP`,到前端原生`JavaScript`,再到`jQuery`开发,再到目前的三大框架`Vue`、`React`、`Angular` 64 | 65 | 开发方式,也从`javascript`到后面的`es5`、`es6、7、8、9、10`,再到`typescript`,包括编写`CSS`的预处理器`less`、`scss`等 66 | 67 | 现代前端开发已经变得十分的复杂,所以我们开发过程中会遇到如下的问题: 68 | 69 | - 需要通过模块化的方式来开发 70 | - 使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码 71 | - 监听文件的变化来并且反映到浏览器上,提高开发的效率 72 | - JavaScript 代码需要模块化,HTML 和 CSS 这些资源文件也会面临需要被模块化的问题 73 | - 开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化 74 | 75 | 而`webpack`恰巧可以解决以上问题 76 | 77 | 78 | ## 三、是什么 79 | 80 | `webpack` 是一个用于现代` JavaScript `应用程序的静态模块打包工具 81 | 82 | - 静态模块 83 | 84 | 这里的静态模块指的是开发阶段,可以被 `webpack` 直接引用的资源(可以直接被获取打包进`bundle.js`的资源) 85 | 86 | 当 `webpack `处理应用程序时,它会在内部构建一个依赖图,此依赖图对应映射到项目所需的每个模块(不再局限`js`文件),并生成一个或多个 `bundle` 87 | 88 | ![](https://static.vue-js.com/9ce194a0-a578-11eb-85f6-6fac77c0c9b3.png) 89 | 90 | #### `webpack`的能力: 91 | 92 | **编译代码能力**,提高效率,解决浏览器兼容问题 93 | ![](https://static.vue-js.com/c5c2d360-a592-11eb-ab90-d9ae814b240d.png) 94 | **模块整合能力**,提高性能,可维护性,解决浏览器频繁请求文件的问题 95 | ![](https://static.vue-js.com/d306d260-a592-11eb-ab90-d9ae814b240d.png) 96 | **万物皆可模块能力**,项目维护性增强,支持不同种类的前端模块类型,统一的模块化方案,所有资源文件的加载都可以通过代码控制 97 | ![](https://static.vue-js.com/e3c5a040-a592-11eb-ab90-d9ae814b240d.png) 98 | 99 | ## 参考文献 100 | - https://webpack.docschina.org/concepts/ 101 | - https://zhuanlan.zhihu.com/p/267875652 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-interview", 3 | "version": "1.0.0", 4 | "description": "web面试题库", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "vuepress dev docs", 9 | "build": "vuepress build docs" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/febobo/web-interview.git" 14 | }, 15 | "keywords": [ 16 | "web", 17 | "interview" 18 | ], 19 | "author": "", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/febobo/web-interview/issues" 23 | }, 24 | "homepage": "https://github.com/febobo/web-interview#readme", 25 | "devDependencies": { 26 | "vuepress": "^1.7.1" 27 | } 28 | } 29 | --------------------------------------------------------------------------------