Full Code of febobo/web-interview for AI

master 7a688c8d941d cached
272 files
1.3 MB
548.7k tokens
17 symbols
1 requests
Download .txt
Showing preview only (1,386K chars total). Download the full file or copy to clipboard to get everything.
Repository: febobo/web-interview
Branch: master
Commit: 7a688c8d941d
Files: 272
Total size: 1.3 MB

Directory structure:
gitextract_tq4ln75s/

├── .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

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/blank.yml
================================================
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the action will run. 
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2

      - name: use Node.js
        # 使用action库  actions/setup-node安装node
        uses: actions/setup-node@v1
        with:
          node-version: 10.x
      # 安装依赖
      - name: npm install
        run: npm install
      # 打包
      - name: npm build
        run: npm run build
      - name: deploy
        uses: easingthemes/ssh-deploy@v2.1.1
        env:
          # 私钥
          SSH_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
          SOURCE: "./dist"
          REMOTE_HOST: ${{ secrets.HOST }}
          REMOTE_USER: "root"
          TARGET: ${{ secrets.PATH }}




          


================================================
FILE: .gitignore
================================================
/coverage
/docs/.vuepress/dist
/examples/**/build.js
/test/e2e/reports
/test/e2e/screenshots
/types/typings
/types/test/*.js
*.log
.DS_Store
node_modules


================================================
FILE: .vscode/settings.json
================================================
{
    "eggHelper.serverPort": 35684
}

================================================
FILE: docs/.vuepress/config.js
================================================
module.exports = {
  // title: "Vue3源码解析 - vue中文社区",
  title: "web前端面试 - 面试官系列",
  description: "web前端面试,vue面试题,react面试题,js面试题,大厂面试题,阿里面试题,京东面试题",
  base: '/interview/',
  head: [
    ["link", { rel: "icon", href: "/onepunch.jpeg" }],
    [
      "meta",
      {
        name: "keywords",
        content:
          "web前端面试,vue面试题,react面试题,js面试题,大厂面试题,阿里面试题,京东面试题",
      },
    ],
    // [
    //   "script",
    //   { src: "https://hm.baidu.com/hm.js?db1f163122162bcdb6d04f76b5c1df17" },
    // ],
  ],
  themeConfig: {
    repo: "febobo/web-interview",
    // 自定义仓库链接文字。默认从 `themeConfig.repo` 中自动推断为
    // "GitHub"/"GitLab"/"Bitbucket" 其中之一,或是 "Source"。
    repoLabel: "Github",

    // 以下为可选的编辑链接选项

    // 假如你的文档仓库和项目本身不在一个仓库:
    docsRepo: "febobo/web-interview",
    docsDir: "docs",
    docsBranch: "master",
    // 默认是 false, 设置为 true 来启用
    editLinks: false,
    // 默认为 "Edit this page"
    editLinkText: "帮助我们改善此页面!",
    // displayAllHeaders: true,
    sidebar: [
      {
        title: "Vue系列  ( 已完结..)",
        collapsable: false,
        children: [
          ["vue/vue", "说说你对vue的理解?"],
          ["vue/spa", "说说你对SPA(单页应用)的理解?"],
          ["vue/show_if", "Vue中的v-show和v-if怎么理解?"],
          ["vue/new_vue", "Vue实例挂载的过程中发生了什么?"],
          ["vue/lifecycle", "说说你对Vue生命周期的理解?"],
          ["vue/if_for", "为什么Vue中的v-if和v-for不建议一起用?"],
          ["vue/first_page_time", "SPA(单页应用)首屏加载速度慢怎么解决?"],
          ["vue/data", "为什么data属性是一个函数而不是一个对象?"],
          ["vue/data_object_add_attrs", "Vue中给对象添加新属性界面不刷新?"],
          ["vue/components_plugin", "Vue中组件和插件有什么区别?"],
          ["vue/communication", "Vue组件间通信方式都有哪些?"],
          ["vue/bind", "说说你对双向绑定的理解?"],
          ["vue/nexttick", "说说你对nexttick的理解?"],
          ["vue/mixin", "说说你对vue的mixin的理解,有什么应用场景?"],
          ["vue/slot", "说说你对slot的理解?slot使用场景有哪些?"],
          ["vue/observable", "Vue.observable你有了解过吗?说说看"],
          ["vue/key", "你知道vue中key的原理吗?说说你对它的理解?"],
          ["vue/keepalive", "怎么缓存当前的组件?缓存后怎么更新?说说你对keep-alive的理解是什么?"],
          ["vue/modifier", "Vue常用的修饰符有哪些?有什么应用场景?"],
          ["vue/directive", "你有写过自定义指令吗?自定义指令的应用场景有哪些?"],
          ["vue/filter", "Vue中的过滤器了解吗?过滤器的应用场景有哪些?"],
          ["vue/vnode", "什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路"],
          ["vue/diff", "你了解vue的diff算法吗?说说看"],
          ["vue/axios", "Vue项目中有封装过axios吗?主要是封装哪方面的?"],
          ["vue/axiosCode", "你了解axios的原理吗?有看过它的源码吗?"],
          ["vue/ssr", "SSR解决了什么问题?有做过SSR吗?你是怎么做的?"],
          ["vue/structure", "说下你的vue项目的目录结构,如果是大型项目你该怎么划分结构和划分组件呢?"],
          ["vue/permission", "vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?"],
          ["vue/cors", "Vue项目中你是如何解决跨域的呢?"],
          ["vue/404", "vue项目本地开发完成后部署到服务器后报404是什么原因呢?"],
          ["vue/error", "你是怎么处理vue项目中的错误的?"],
          ["vue/vue3_vue2", "Vue3有了解过吗?能说说跟Vue2的区别吗?"]
        ],
      },
      {
        title: "Vue3系列  ( 已完结..)",
        collapsable: false,
        children: [
          ["vue3/goal", "Vue3.0的设计目标是什么?做了哪些优化?"],
          ["vue3/performance", "Vue3.0 性能提升主要是通过哪几方面体现的?"],
          ["vue3/proxy", "Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?"],
          ["vue3/composition", "Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?"],
          ["vue3/treeshaking", "说说Vue 3.0中Treeshaking特性?举例说明一下?"],
          ["vue3/modal_component", "用Vue3.0 写过组件吗?如果想实现一个 Modal你会怎么设计?"],
        ],
      },
      {
        title: "ES6系列  ( 已完结..)",
        collapsable: false,
        children: [
          ["es6/var_let_const", "说说var、let、const之间的区别"],
          ["es6/array", "ES6中数组新增了哪些扩展?"],
          ["es6/object", "ES6中对象新增了哪些扩展?"],
          ["es6/function", "ES6中函数新增了哪些扩展?"],
          ["es6/set_map", "ES6中新增的Set、Map两种数据结构怎么理解?"],
          ["es6/promise", "你是怎么理解ES6中 Promise的?使用场景?"],
          ["es6/generator", "怎么理解ES6中 Generator的?使用场景?"],
          ["es6/proxy", "你是怎么理解ES6中Proxy的?使用场景?"],
          ["es6/module", "你是怎么理解ES6中Module的?使用场景?"],
          ["es6/decorator", "你是怎么理解ES6中 Decorator 的?使用场景?"],
        ],
      },
      {
        title: "JavaScript系列  ( 已完结..)",
        collapsable: false,
        children: [
          ["JavaScript/data_type", "说说JavaScript中的数据类型?存储上的差别?"],
          ["JavaScript/array_api", "数组的常用方法有哪些?"],
          ["JavaScript/string_api", "JavaScript字符串的常用方法有哪些?"],
          ["JavaScript/type_conversion", "谈谈 JavaScript 中的类型转换机制"],
          ["JavaScript/== _===", "== 和 ===区别,分别在什么情况使用"],
          ["JavaScript/copy", "深拷贝浅拷贝的区别?如何实现一个深拷贝?"],
          ["JavaScript/closure", "说说你对闭包的理解?闭包使用场景"],
          ["JavaScript/scope", "说说你对作用域链的理解"],
          ["JavaScript/prototype", "JavaScript原型,原型链 ? 有什么特点?"],
          ["JavaScript/inherit", "Javascript如何实现继承?"],
          ["JavaScript/this", "谈谈this对象的理解"],
          ["JavaScript/context_stack", "JavaScript中执行上下文和执行栈是什么?"],
          ["JavaScript/event_Model", "说说JavaScript中的事件模型"],
          ["JavaScript/typeof_instanceof", "typeof 与 instanceof 区别"],
          ["JavaScript/event_agent", "解释下什么是事件代理?应用场景?"],
          ["JavaScript/new", "说说new操作符具体干了什么?"],
          ["JavaScript/ajax", "ajax原理是什么?如何实现?"],
          ["JavaScript/bind_call_apply", "bind、call、apply 区别?如何实现一个bind?"],
          ["JavaScript/regexp", "说说你对正则表达式的理解?应用场景?"],
          ["JavaScript/event_loop", "说说你对事件循环的理解"],
          ["JavaScript/Dom", "DOM常见的操作有哪些?"],
          ["JavaScript/BOM", "说说你对BOM的理解,常见的BOM对象你了解哪些?"],
          ["JavaScript/tail_recursion", "举例说明你对尾递归的理解,有哪些应用场景"],
          ["JavaScript/memory_leak", "说说 JavaScript 中内存泄漏的几种情况?"],
          ["JavaScript/cache", "Javascript本地存储的方式有哪些?区别及应用场景?"],
          ["JavaScript/functional_programming", "说说你对函数式编程的理解?优缺点?"],
          ["JavaScript/function_cache", "Javascript中如何实现函数缓存?函数缓存有哪些应用场景?"],
          ["JavaScript/loss_accuracy", "说说 Javascript 数字精度丢失的问题,如何解决?"],
          ["JavaScript/debounce_throttle", "什么是防抖和节流?有什么区别?如何实现?"],
          ["JavaScript/visible", "如何判断一个元素是否在可视区域中?"],
          ["JavaScript/continue_to_upload", "大文件上传如何做断点续传?"],
          ["JavaScript/pull_up_loading_pull_down_refresh", "如何实现上拉加载,下拉刷新?"],
          ["JavaScript/single_sign", "什么是单点登录?如何实现?"],
          ["JavaScript/security", "web常见的攻击方式有哪些?如何防御?"],
        ],
      },
      {
        title: "CSS系列  ( 已完结..)",
        collapsable: false,
        children: [
          ["css/box", "说说你对盒子模型的理解?"],
          ["css/selector", "css选择器有哪些?优先级?哪些属性可以继承?"],
          ["css/em_px_rem_vh_vw", "说说em/px/rem/vh/vw区别?"],
          ["css/dp_px_dpr_ppi", "说说设备像素、css像素、设备独立像素、dpr、ppi 之间的区别?"],
          ["css/hide_attributes", "css中,有哪些方式可以隐藏页面元素?区别?"],
          ["css/BFC", "谈谈你对BFC的理解?"],
          ["css/center", "元素水平垂直居中的方法有哪些?如果元素不定宽高呢?"],
          ["css/column_layout", "如何实现两栏布局,右侧自适应?三栏布局中间自适应呢?"],
          ["css/flexbox", "说说flexbox(弹性盒布局模型),以及适用场景?"],
          ["css/grid", "介绍一下grid网格布局"],
          ["css/css3_features", "CSS3新增了哪些新特性?"],
          ["css/animation", "css3动画有哪些?"],
          ["css/layout_painting", "怎么理解回流跟重绘?什么场景下会触发?"],
          ["css/responsive_layout", "什么是响应式设计?响应式设计的基本原理是什么?如何做?"],
          ["css/css_performance", "如果要做优化,CSS提高性能的方法有哪些?"],
          ["css/single_multi_line", "如何实现单行/多行文本溢出的省略样式?"],
          ["css/visual_scrolling", "如何使用css完成视差滚动效果?"],
          ["css/triangle", "CSS如何画一个三角形?原理是什么?"],
          ["css/less_12px", "让Chrome支持小于12px 的文字方式有哪些?区别?"],
          ["css/sass_less_stylus", "说说对Css预编语言的理解?有哪些区别?"],
        ],
      },
      {
        title: "Webpack系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["webpack/webpack", "说说你对webpack的理解?解决了什么问题?"],
          ["webpack/build_process", "说说webpack的构建流程?"],
          ["webpack/Loader", "说说webpack中常见的Loader?解决了什么问题?"],
          ["webpack/Plugin", "说说webpack中常见的Plugin?解决了什么问题?"],
          ["webpack/Loader_Plugin", "说说Loader和Plugin的区别?编写Loader,Plugin的思路?"],
          ["webpack/HMR", "说说webpack的热更新是如何做到的?原理是什么?"],
          ["webpack/proxy", "说说webpack proxy工作原理?为什么能解决跨域?"],
          ["webpack/performance", "说说如何借助webpack来优化前端性能?"],
          ["webpack/improve_build", "如何提高webpack的构建速度?"],
          ["webpack/Rollup_Parcel_snowpack_Vite", "与webpack类似的工具还有哪些?区别?"],
        ]
      },
      {
        title: "HTTP系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["http/HTTP_HTTPS", "什么是HTTP? HTTP 和 HTTPS 的区别?"],
          ["http/HTTPS", "为什么说HTTPS比HTTP安全? HTTPS是如何保证安全的?"],
          ["http/UDP_TCP", "如何理解UDP 和 TCP? 区别? 应用场景?"],
          ["http/OSI", "如何理解OSI七层模型?"],
          ["http/TCP_IP", "如何理解TCP/IP协议?"],
          ["http/DNS", "DNS协议 是什么?说说DNS 完整的查询过程?"],
          ["http/CDN", "如何理解CDN?说说实现原理?"],
          ["http/1.0_1.1_2.0", "说说 HTTP1.0/1.1/2.0 的区别?"],
          ["http/status", "说说 HTTP 常见的状态码有哪些,适用场景?"],
          ["http/GET_POST", "说一下 GET 和 POST 的区别?"],
          ["http/headers", "说说 HTTP 常见的请求头有哪些? 作用?"],
          ["http/after_url", "说说地址栏输入 URL 敲下回车后发生了什么?"],
          ["http/handshakes_waves", "说说TCP为什么需要三次握手和四次挥手?"],
          ["http/WebSocket", "说说对WebSocket的理解?应用场景?"]
        ]
      },
      {
        title: "NodeJS系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["NodeJS/nodejs", "说说你对 Node.js 的理解?优缺点?应用场景?"],
          ["NodeJS/global", "说说 Node.js 有哪些全局对象?"],
          ["NodeJS/process", "说说对 Node 中的 process 的理解?有哪些常用方法?"],
          ["NodeJS/fs", "说说对 Node 中的 fs模块的理解? 有哪些常用方法"],
          ["NodeJS/Buffer", "说说对 Node 中的 Buffer 的理解?应用场景?"],
          ["NodeJS/Stream", "说说对 Node 中的 Stream 的理解?应用场景?"],
          ["NodeJS/EventEmitter", "说说Node中的EventEmitter? 如何实现一个EventEmitter?"],
          ["NodeJS/event_loop", "说说对 Nodejs 中的事件循环机制理解?"],
          ["NodeJS/require_order", "说说 Node 文件查找的优先级以及 Require 方法的文件查找策略?"],
          ["NodeJS/middleware", "说说对中间件概念的理解,如何封装 node 中间件?"],
          ["NodeJS/jwt", "如何实现jwt鉴权机制?说说你的思路"],
          ["NodeJS/file_upload", "如何实现文件上传?说说你的思路"],
          ["NodeJS/paging", "如果让你来设计一个分页功能, 你会怎么设计? 前后端如何交互?"],
          ["NodeJS/performance", "Node性能如何进行监控以及优化?"],
        ]
      },
      {
        title: "React系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["React/React", "说说对React的理解?有哪些特性?"],
          ["React/Real DOM_Virtual DOM", "说说 Real DOM和 Virtual DOM 的区别?优缺点?"],
          ["React/life cycle", "说说 React 生命周期有哪些不同阶段?每个阶段对应的方法是?"],
          ["React/state_props", "state 和 props有什么区别?"],
          ["React/super()_super(props)", "super()和super(props)有什么区别?"],
          ["React/setState", "说说 React中的setState执行机制"],
          ["React/SyntheticEvent", "说说React的事件机制?"],
          ["React/Binding events", "React事件绑定的方式有哪些?区别?"],
          ["React/Building components", "React构建组件的方式有哪些?区别?"],
          ["React/communication", "React中组件之间如何通信?"],
          ["React/key", "React中的key有什么作用?"],
          ["React/React refs", "说说对React refs 的理解?应用场景?"],
          ["React/class_function component", "说说对React中类组件和函数组件的理解?有什么区别?"],
          ["React/controlled_Uncontrolled", "说说对受控组件和非受控组件的理解?应用场景?"],
          ["React/High order components", "说说对高阶组件的理解?应用场景?"],
          ["React/React Hooks", "说说对React Hooks的理解?解决了什么问题?"],
          ["React/import css", "说说react中引入css的方式有哪几种?区别?"],
          ["React/animation", "在react中组件间过渡动画如何实现?"],
          ["React/redux", "说说你对Redux的理解?其工作原理?"],
          ["React/Redux Middleware", "说说对Redux中间件的理解?常用的中间件有哪些?实现原理?"],
          ["React/how to use redux", "你在React项目中是如何使用Redux的? 项目结构是如何划分的?"],
          ["React/React Router", "说说你对React Router的理解?常用的Router组件有哪些?"],
          ["React/React Router model", "说说React Router有几种模式?实现原理??"],
          ["React/immutable", "说说你对immutable的理解?如何应用在react项目中?"],
          ["React/render", "说说React render方法的原理?在什么时候会被触发?"],
          ["React/improve_render", "说说你是如何提高组件的渲染效率的?在React中如何避免不必要的render?"],
          ["React/diff", "说说React diff的原理是什么?"],
          ["React/Fiber", "说说对Fiber架构的理解?解决了什么问题?"],
          ["React/JSX to DOM", "说说React Jsx转换成真实DOM过程?"],
          ["React/Improve performance", "说说 React 性能优化的手段有哪些?"],
          ["React/capture error", "说说你在React项目是如何捕获错误的?"],
          ["React/server side rendering", "说说React服务端渲染怎么做?原理是什么?"],
          ["React/summary", "说说你在使用React 过程中遇到的常见问题?如何解决?"]
        ]
      },
      {
        title: "版本控制系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["git/Version control", "说说你对版本管理的理解?常用的版本管理工具有哪些?"],
          ["git/Git", "说说你对Git的理解?"],
          ["git/fork_clone_branch", "说说Git中 fork, clone,branch这三个概念,有什么区别?"],
          ["git/command", "说说Git常用的命令有哪些?"],
          ["git/HEAD_tree_index", "说说Git 中 HEAD、工作树和索引之间的区别?"],
          ["git/git pull _git fetch", "说说对git pull 和 git fetch 的理解?有什么区别?"],
          ["git/git stash", "说说你对git stash 的理解?应用场景?"],
          ["git/git rebase_ git merge", "说说你对git rebase 和 git merge的理解?区别?"],
          ["git/conflict", "说说 git 发生冲突的场景?如何解决?"],
          ["git/git reset_ git revert", "说说你对git reset 和 git revert 的理解?区别?"],
        ]
      },
      {
        title: "操作系统系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["linux/linux", "说说你对操作系统的理解?核心概念有哪些?"],
          ["linux/thread_process", "说说什么是进程?什么是线程?区别?"],
          ["linux/file", "说说 linux系统下 文件操作常用的命令有哪些?"],
          ["linux/vim", "说说 linux 系统下 文本编辑常用的命令有哪些?"],
          ["linux/linux users", "说说你对 linux 用户管理的理解?相关的命令有哪些?"],
          ["linux/redirect_pipe", "说说你对输入输出重定向和管道的理解?应用场景?"],
          ["linux/shell", "说说你对 shell 的理解?常见的命令?"],
        ]
      },
      {
        title: "TypeScript 系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["typescript/typescript_javascript", "说说你对 TypeScript 的理解?与 JavaScript 的区别?"],
          ["typescript/data_type", "说说 typescript 的数据类型有哪些?"],
          ["typescript/enum", "说说你对 TypeScript 中枚举类型的理解?应用场景?"],
          ["typescript/interface", "说说你对 TypeScript 中接口的理解?应用场景?"],
          ["typescript/class", "说说你对 TypeScript 中类的理解?应用场景?"],
          ["typescript/function", "说说你对 TypeScript 中函数的理解?与 JavaScript 函数的区别?"],
          ["typescript/generic", "说说你对 TypeScript 中泛型的理解?应用场景?"],
          ["typescript/high type", "说说你对 TypeScript 中高级类型的理解?有哪些?"],
          ["typescript/decorator", "说说你对 TypeScript 装饰器的理解?应用场景?"],
          ["typescript/namespace_module", "说说对 TypeScript 中命名空间与模块的理解?区别?"],
          ["typescript/react", "说说如何在 React 项目中应用 TypeScript?"],
          ["typescript/vue", "说说如何在Vue项目中应用TypeScript?"]
        ]
      },
      {
        title: "算法与数据结构系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["algorithm/Algorithm", "说说你对算法的理解?应用场景?"],
          ["algorithm/time_space", "说说你对算法中时间复杂度,空间复杂度的理解?如何计算?"],
          ["algorithm/structure", "说说你对数据结构的理解?有哪些?区别?"],
          ["algorithm/stack_queue", "说说你对栈、队列的理解?应用场景?"],
          ["algorithm/Linked List", "说说你对链表的理解?常见的操作有哪些?"],
          ["algorithm/set", "说说你对集合的理解?常见的操作有哪些?"],
          ["algorithm/tree", "说说你对树的理解?相关的操作有哪些?"],
          ["algorithm/Heap", "说说你对堆的理解?如何实现?应用场景?"],
          ["algorithm/graph", "说说你对图的理解?相关操作有哪些?"],
          ["algorithm/sort", "说说常见的排序算法有哪些?区别?"],
          ["algorithm/bubbleSort", "说说你对冒泡排序的理解?如何实现?应用场景?"],
          ["algorithm/selectionSort", "说说你对选择排序的理解?如何实现?应用场景?"],
          ["algorithm/insertionSort", "说说你对插入排序的理解?如何实现?应用场景?"],
          ["algorithm/mergeSort", "说说你对归并排序的理解?如何实现?应用场景?"],
          ["algorithm/quickSort", "说说你对快速排序的理解?如何实现?应用场景?"],
          ["algorithm/BinarySearch", "说说你对二分查找的理解?如何实现?应用场景?"],
          ["algorithm/design1", "说说说你对分而治之、动态规划的理解?区别?"],
          ["algorithm/design2", "说说你对贪心算法、回溯算法的理解?应用场景?"],
        ]
      },
      {
        title: "小程序系列  ( 已完结..)",
        collapsable: false,
        children:[
          ["applet/applet", "说说你对微信小程序的理解?优缺点?"],
          ["applet/lifecycle", "说说微信小程序的生命周期函数有哪些?"],
          ["applet/navigate", "说说微信小程序中路由跳转的方式有哪些?区别?"],
          ["applet/optimization", "说说提高微信小程序的应用速度的手段有哪些?"],
          ["applet/login", "说说微信小程序的登录流程?"],
          ["applet/publish", "说说微信小程序的发布流程?"],
          ["applet/requestPayment", "说说微信小程序的支付流程?"],
          ["applet/WebView_jscore", "说说微信小程序的实现原理?"],
        ]
      },
      {
        title: "设计模式系列  ( 进行中..)",
        collapsable: false,
        children:[
          ["design/design", "说说对设计模式的理解?常见的设计模式有哪些?"],
          ["design/Singleton Pattern", "说说你对单例模式的理解?如何实现?"],
          ["design/Factory  Pattern", "说说你对工厂模式的理解?应用场景?"],
          ["design/Strategy Pattern", "说说你对策略模式的理解?应用场景?"],
          ["design/Proxy Pattern", "说说你对代理模式的理解?应用场景?"],
          ["design/Observer  Pattern", "说说你对发布订阅、观察者模式的理解?区别?"],
        ]
      },
    ],
  },
  markdown: {
    lineNumbers: true,
  },
};


================================================
FILE: docs/.vuepress/theme/LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2018-present, Yuxi (Evan) You

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: docs/.vuepress/theme/components/AlgoliaSearchBox.vue
================================================
<template>
  <form
    id="search-form"
    class="algolia-search-wrapper search-box"
    role="search"
  >
    <input
      id="algolia-search-input"
      class="search-query"
      :placeholder="placeholder"
    >
  </form>
</template>

<script>
export default {
  name: 'AlgoliaSearchBox',

  props: ['options'],

  data () {
    return {
      placeholder: undefined
    }
  },

  watch: {
    $lang (newValue) {
      this.update(this.options, newValue)
    },

    options (newValue) {
      this.update(newValue, this.$lang)
    }
  },

  mounted () {
    this.initialize(this.options, this.$lang)
    this.placeholder = this.$site.themeConfig.searchPlaceholder || ''
  },

  methods: {
    initialize (userOptions, lang) {
      Promise.all([
        import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.js'),
        import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.css')
      ]).then(([docsearch]) => {
        docsearch = docsearch.default
        const { algoliaOptions = {}} = userOptions
        docsearch(Object.assign(
          {},
          userOptions,
          {
            inputSelector: '#algolia-search-input',
            // #697 Make docsearch work well at i18n mode.
            algoliaOptions: Object.assign({
              'facetFilters': [`lang:${lang}`].concat(algoliaOptions.facetFilters || [])
            }, algoliaOptions),
            handleSelected: (input, event, suggestion) => {
              const { pathname, hash } = new URL(suggestion.url)
              const routepath = pathname.replace(this.$site.base, '/')
              this.$router.push(`${routepath}${hash}`)
            }
          }
        ))
      })
    },

    update (options, lang) {
      this.$el.innerHTML = '<input id="algolia-search-input" class="search-query">'
      this.initialize(options, lang)
    }
  }
}
</script>

<style lang="stylus">
.algolia-search-wrapper
  & > span
    vertical-align middle
  .algolia-autocomplete
    line-height normal
    .ds-dropdown-menu
      background-color #fff
      border 1px solid #999
      border-radius 4px
      font-size 16px
      margin 6px 0 0
      padding 4px
      text-align left
      &:before
        border-color #999
      [class*=ds-dataset-]
        border none
        padding 0
      .ds-suggestions
        margin-top 0
      .ds-suggestion
        border-bottom 1px solid $borderColor
    .algolia-docsearch-suggestion--highlight
      color #2c815b
    .algolia-docsearch-suggestion
      border-color $borderColor
      padding 0
      .algolia-docsearch-suggestion--category-header
        padding 5px 10px
        margin-top 0
        background $accentColor
        color #fff
        font-weight 600
        .algolia-docsearch-suggestion--highlight
          background rgba(255, 255, 255, 0.6)
      .algolia-docsearch-suggestion--wrapper
        padding 0
      .algolia-docsearch-suggestion--title
        font-weight 600
        margin-bottom 0
        color $textColor
      .algolia-docsearch-suggestion--subcategory-column
        vertical-align top
        padding 5px 7px 5px 5px
        border-color $borderColor
        background #f1f3f5
        &:after
          display none
      .algolia-docsearch-suggestion--subcategory-column-text
        color #555
    .algolia-docsearch-footer
      border-color $borderColor
    .ds-cursor .algolia-docsearch-suggestion--content
      background-color #e7edf3 !important
      color $textColor

@media (min-width: $MQMobile)
  .algolia-search-wrapper
    .algolia-autocomplete
      .algolia-docsearch-suggestion
        .algolia-docsearch-suggestion--subcategory-column
          float none
          width 150px
          min-width 150px
          display table-cell
        .algolia-docsearch-suggestion--content
          float none
          display table-cell
          width 100%
          vertical-align top
        .ds-dropdown-menu
          min-width 515px !important

@media (max-width: $MQMobile)
  .algolia-search-wrapper
    .ds-dropdown-menu
      min-width calc(100vw - 4rem) !important
      max-width calc(100vw - 4rem) !important
    .algolia-docsearch-suggestion--wrapper
      padding 5px 7px 5px 5px !important
    .algolia-docsearch-suggestion--subcategory-column
      padding 0 !important
      background white !important
    .algolia-docsearch-suggestion--subcategory-column-text:after
      content " > "
      font-size 10px
      line-height 14.4px
      display inline-block
      width 5px
      margin -3px 3px 0
      vertical-align middle

</style>


================================================
FILE: docs/.vuepress/theme/components/DropdownLink.vue
================================================
<template>
  <div
    class="dropdown-wrapper"
    :class="{ open }"
  >
    <button
      class="dropdown-title"
      type="button"
      :aria-label="dropdownAriaLabel"
      @click="setOpen(!open)"
    >
      <span class="title">{{ item.text }}</span>
      <span
        class="arrow"
        :class="open ? 'down' : 'right'"
      />
    </button>

    <DropdownTransition>
      <ul
        v-show="open"
        class="nav-dropdown"
      >
        <li
          v-for="(subItem, index) in item.items"
          :key="subItem.link || index"
          class="dropdown-item"
        >
          <h4 v-if="subItem.type === 'links'">
            {{ subItem.text }}
          </h4>

          <ul
            v-if="subItem.type === 'links'"
            class="dropdown-subitem-wrapper"
          >
            <li
              v-for="childSubItem in subItem.items"
              :key="childSubItem.link"
              class="dropdown-subitem"
            >
              <NavLink
                :item="childSubItem"
                @focusout="
                  isLastItemOfArray(childSubItem, subItem.items) &&
                    isLastItemOfArray(subItem, item.items) &&
                    setOpen(false)
                "
              />
            </li>
          </ul>

          <NavLink
            v-else
            :item="subItem"
            @focusout="isLastItemOfArray(subItem, item.items) && setOpen(false)"
          />
        </li>
      </ul>
    </DropdownTransition>
  </div>
</template>

<script>
import NavLink from '@theme/components/NavLink.vue'
import DropdownTransition from '@theme/components/DropdownTransition.vue'
import last from 'lodash/last'

export default {
  name: 'DropdownLink',

  components: {
    NavLink,
    DropdownTransition
  },

  props: {
    item: {
      required: true
    }
  },

  data () {
    return {
      open: false
    }
  },

  computed: {
    dropdownAriaLabel () {
      return this.item.ariaLabel || this.item.text
    }
  },

  watch: {
    $route () {
      this.open = false
    }
  },

  methods: {
    setOpen (value) {
      this.open = value
    },

    isLastItemOfArray (item, array) {
      return last(array) === item
    }
  }
}
</script>

<style lang="stylus">
.dropdown-wrapper
  cursor pointer
  .dropdown-title
    display block
    font-size 0.9rem
    font-family inherit
    cursor inherit
    padding inherit
    line-height 1.4rem
    background transparent
    border none
    font-weight 500
    color $textColor
    &:hover
      border-color transparent
    .arrow
      vertical-align middle
      margin-top -1px
      margin-left 0.4rem
  .nav-dropdown
    .dropdown-item
      color inherit
      line-height 1.7rem
      h4
        margin 0.45rem 0 0
        border-top 1px solid #eee
        padding 0.45rem 1.5rem 0 1.25rem
      .dropdown-subitem-wrapper
        padding 0
        list-style none
        .dropdown-subitem
          font-size 0.9em
      a
        display block
        line-height 1.7rem
        position relative
        border-bottom none
        font-weight 400
        margin-bottom 0
        padding 0 1.5rem 0 1.25rem
        &:hover
          color $accentColor
        &.router-link-active
          color $accentColor
          &::after
            content ""
            width 0
            height 0
            border-left 5px solid $accentColor
            border-top 3px solid transparent
            border-bottom 3px solid transparent
            position absolute
            top calc(50% - 2px)
            left 9px
      &:first-child h4
        margin-top 0
        padding-top 0
        border-top 0

@media (max-width: $MQMobile)
  .dropdown-wrapper
    &.open .dropdown-title
      margin-bottom 0.5rem
    .dropdown-title
      font-weight 600
      font-size inherit
      &:hover
        color $accentColor
    .nav-dropdown
      transition height .1s ease-out
      overflow hidden
      .dropdown-item
        h4
          border-top 0
          margin-top 0
          padding-top 0
        h4, & > a
          font-size 15px
          line-height 2rem
        .dropdown-subitem
          font-size 14px
          padding-left 1rem

@media (min-width: $MQMobile)
  .dropdown-wrapper
    height 1.8rem
    &:hover .nav-dropdown,
    &.open .nav-dropdown
      // override the inline style.
      display block !important
    &.open:blur
      display none
    .dropdown-title .arrow
      // make the arrow always down at desktop
      border-left 4px solid transparent
      border-right 4px solid transparent
      border-top 6px solid $arrowBgColor
      border-bottom 0
    .nav-dropdown
      display none
      // Avoid height shaked by clicking
      height auto !important
      box-sizing border-box;
      max-height calc(100vh - 2.7rem)
      overflow-y auto
      position absolute
      top 100%
      right 0
      background-color #fff
      padding 0.6rem 0
      border 1px solid #ddd
      border-bottom-color #ccc
      text-align left
      border-radius 0.25rem
      white-space nowrap
      margin 0
</style>


================================================
FILE: docs/.vuepress/theme/components/DropdownTransition.vue
================================================
<template>
  <transition
    name="dropdown"
    @enter="setHeight"
    @after-enter="unsetHeight"
    @before-leave="setHeight"
  >
    <slot />
  </transition>
</template>

<script>
export default {
  name: 'DropdownTransition',

  methods: {
    setHeight (items) {
      // explicitly set height so that it can be transitioned
      items.style.height = items.scrollHeight + 'px'
    },

    unsetHeight (items) {
      items.style.height = ''
    }
  }
}
</script>

<style lang="stylus">
.dropdown-enter, .dropdown-leave-to
  height 0 !important

</style>


================================================
FILE: docs/.vuepress/theme/components/Home.vue
================================================
<template>
  <main
    class="home"
    aria-labelledby="main-title"
  >
    <header class="hero">
      <img
        v-if="data.heroImage"
        :src="$withBase(data.heroImage)"
        :alt="data.heroAlt || 'hero'"
      >

      <h1
        v-if="data.heroText !== null"
        id="main-title"
      >
        {{ data.heroText || $title || 'Hello' }}
      </h1>

      <p
        v-if="data.tagline !== null"
        class="description"
      >
        {{ data.tagline || $description || 'Welcome to your VuePress site' }}
      </p>

      <p
        v-if="data.actionText && data.actionLink"
        class="action"
      >
        <NavLink
          class="action-button"
          :item="actionLink"
        />
      </p>
    </header>

    <div
      v-if="data.features && data.features.length"
      class="features"
    >
      <div
        v-for="(feature, index) in data.features"
        :key="index"
        class="feature"
      >
        <h2>{{ feature.title }}</h2>
        <p>{{ feature.details }}</p>
      </div>
    </div>

    <Content class="theme-default-content custom" />

    <div
      v-if="data.footer"
      class="footer"
    >
      {{ data.footer }}
    </div>
  </main>
</template>

<script>
import NavLink from '@theme/components/NavLink.vue'

export default {
  name: 'Home',

  components: { NavLink },

  computed: {
    data () {
      return this.$page.frontmatter
    },

    actionLink () {
      return {
        link: this.data.actionLink,
        text: this.data.actionText
      }
    }
  }
}
</script>

<style lang="stylus">
.home
  padding $navbarHeight 2rem 0
  max-width $homePageWidth
  margin 0px auto
  display block
  .hero
    text-align center
    img
      max-width: 100%
      max-height 280px
      display block
      margin 3rem auto 1.5rem
    h1
      font-size 3rem
    h1, .description, .action
      margin 1.8rem auto
    .description
      max-width 35rem
      font-size 1.6rem
      line-height 1.3
      color lighten($textColor, 40%)
    .action-button
      display inline-block
      font-size 1.2rem
      color #fff
      background-color $accentColor
      padding 0.8rem 1.6rem
      border-radius 4px
      transition background-color .1s ease
      box-sizing border-box
      border-bottom 1px solid darken($accentColor, 10%)
      &:hover
        background-color lighten($accentColor, 10%)
  .features
    border-top 1px solid $borderColor
    padding 1.2rem 0
    margin-top 2.5rem
    display flex
    flex-wrap wrap
    align-items flex-start
    align-content stretch
    justify-content space-between
  .feature
    flex-grow 1
    flex-basis 30%
    max-width 30%
    h2
      font-size 1.4rem
      font-weight 500
      border-bottom none
      padding-bottom 0
      color lighten($textColor, 10%)
    p
      color lighten($textColor, 25%)
  .footer
    padding 2.5rem
    border-top 1px solid $borderColor
    text-align center
    color lighten($textColor, 25%)

@media (max-width: $MQMobile)
  .home
    .features
      flex-direction column
    .feature
      max-width 100%
      padding 0 2.5rem

@media (max-width: $MQMobileNarrow)
  .home
    padding-left 1.5rem
    padding-right 1.5rem
    .hero
      img
        max-height 210px
        margin 2rem auto 1.2rem
      h1
        font-size 2rem
      h1, .description, .action
        margin 1.2rem auto
      .description
        font-size 1.2rem
      .action-button
        font-size 1rem
        padding 0.6rem 1.2rem
    .feature
      h2
        font-size 1.25rem
</style>


================================================
FILE: docs/.vuepress/theme/components/NavLink.vue
================================================
<template>
  <RouterLink
    v-if="isInternal"
    class="nav-link"
    :to="link"
    :exact="exact"
    @focusout.native="focusoutAction"
  >
    {{ item.text }}
  </RouterLink>
  <a
    v-else
    :href="link"
    class="nav-link external"
    :target="target"
    :rel="rel"
    @focusout="focusoutAction"
  >
    {{ item.text }}
    <OutboundLink v-if="isBlankTarget" />
  </a>
</template>

<script>
import { isExternal, isMailto, isTel, ensureExt } from '../util'

export default {
  name: 'NavLink',

  props: {
    item: {
      required: true
    }
  },

  computed: {
    link () {
      return ensureExt(this.item.link)
    },

    exact () {
      if (this.$site.locales) {
        return Object.keys(this.$site.locales).some(rootLink => rootLink === this.link)
      }
      return this.link === '/'
    },

    isNonHttpURI () {
      return isMailto(this.link) || isTel(this.link)
    },

    isBlankTarget () {
      return this.target === '_blank'
    },

    isInternal () {
      return !isExternal(this.link) && !this.isBlankTarget
    },

    target () {
      if (this.isNonHttpURI) {
        return null
      }
      if (this.item.target) {
        return this.item.target
      }
      return isExternal(this.link) ? '_blank' : ''
    },

    rel () {
      if (this.isNonHttpURI) {
        return null
      }
      if (this.item.rel) {
        return this.item.rel
      }
      return this.isBlankTarget ? 'noopener noreferrer' : ''
    }
  },

  methods: {
    focusoutAction () {
      this.$emit('focusout')
    }
  }
}
</script>


================================================
FILE: docs/.vuepress/theme/components/NavLinks.vue
================================================
<template>
  <nav
    v-if="userLinks.length || repoLink"
    class="nav-links"
  >
    <!-- user links -->
    <div
      v-for="item in userLinks"
      :key="item.link"
      class="nav-item"
    >
      <DropdownLink
        v-if="item.type === 'links'"
        :item="item"
      />
      <NavLink
        v-else
        :item="item"
      />
    </div>

    <!-- repo link -->
    <a
      v-if="repoLink"
      :href="repoLink"
      class="repo-link"
      target="_blank"
      rel="noopener noreferrer"
    >
      {{ repoLabel }}
      <OutboundLink />
    </a>
  </nav>
</template>

<script>
import DropdownLink from '@theme/components/DropdownLink.vue'
import { resolveNavLinkItem } from '../util'
import NavLink from '@theme/components/NavLink.vue'

export default {
  name: 'NavLinks',

  components: {
    NavLink,
    DropdownLink
  },

  computed: {
    userNav () {
      return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || []
    },

    nav () {
      const { locales } = this.$site
      if (locales && Object.keys(locales).length > 1) {
        const currentLink = this.$page.path
        const routes = this.$router.options.routes
        const themeLocales = this.$site.themeConfig.locales || {}
        const languageDropdown = {
          text: this.$themeLocaleConfig.selectText || 'Languages',
          ariaLabel: this.$themeLocaleConfig.ariaLabel || 'Select language',
          items: Object.keys(locales).map(path => {
            const locale = locales[path]
            const text = themeLocales[path] && themeLocales[path].label || locale.lang
            let link
            // Stay on the current page
            if (locale.lang === this.$lang) {
              link = currentLink
            } else {
              // Try to stay on the same page
              link = currentLink.replace(this.$localeConfig.path, path)
              // fallback to homepage
              if (!routes.some(route => route.path === link)) {
                link = path
              }
            }
            return { text, link }
          })
        }
        return [...this.userNav, languageDropdown]
      }
      return this.userNav
    },

    userLinks () {
      return (this.nav || []).map(link => {
        return Object.assign(resolveNavLinkItem(link), {
          items: (link.items || []).map(resolveNavLinkItem)
        })
      })
    },

    repoLink () {
      const { repo } = this.$site.themeConfig
      if (repo) {
        return /^https?:/.test(repo)
          ? repo
          : `https://github.com/${repo}`
      }
      return null
    },

    repoLabel () {
      if (!this.repoLink) return
      if (this.$site.themeConfig.repoLabel) {
        return this.$site.themeConfig.repoLabel
      }

      const repoHost = this.repoLink.match(/^https?:\/\/[^/]+/)[0]
      const platforms = ['GitHub', 'GitLab', 'Bitbucket']
      for (let i = 0; i < platforms.length; i++) {
        const platform = platforms[i]
        if (new RegExp(platform, 'i').test(repoHost)) {
          return platform
        }
      }

      return 'Source'
    }
  }
}
</script>

<style lang="stylus">
.nav-links
  display inline-block
  a
    line-height 1.4rem
    color inherit
    &:hover, &.router-link-active
      color $accentColor
  .nav-item
    position relative
    display inline-block
    margin-left 1.5rem
    line-height 2rem
    &:first-child
      margin-left 0
  .repo-link
    margin-left 1.5rem

@media (max-width: $MQMobile)
  .nav-links
    .nav-item, .repo-link
      margin-left 0

@media (min-width: $MQMobile)
  .nav-links a
    &:hover, &.router-link-active
      color $textColor
  .nav-item > a:not(.external)
    &:hover, &.router-link-active
      margin-bottom -2px
      border-bottom 2px solid lighten($accentColor, 8%)
</style>


================================================
FILE: docs/.vuepress/theme/components/Navbar.vue
================================================
<template>
  <header class="navbar">
    <SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" />

    <RouterLink
      :to="$localePath"
      class="home-link"
    >
      <img
        v-if="$site.themeConfig.logo"
        class="logo"
        :src="$withBase($site.themeConfig.logo)"
        :alt="$siteTitle"
      >
      <span
        v-if="$siteTitle"
        ref="siteName"
        class="site-name"
        :class="{ 'can-hide': $site.themeConfig.logo }"
      >{{ $siteTitle }}</span>
    </RouterLink>

    <div
      class="links"
      :style="linksWrapMaxWidth ? {
        'max-width': linksWrapMaxWidth + 'px'
      } : {}"
    >
      <AlgoliaSearchBox
        v-if="isAlgoliaSearch"
        :options="algolia"
      />
      <SearchBox v-else-if="$site.themeConfig.search !== false && $page.frontmatter.search !== false" />
      <NavLinks class="can-hide" />
    </div>
  </header>
</template>

<script>
import AlgoliaSearchBox from '@AlgoliaSearchBox'
import SearchBox from '@SearchBox'
import SidebarButton from '@theme/components/SidebarButton.vue'
import NavLinks from '@theme/components/NavLinks.vue'

export default {
  name: 'Navbar',

  components: {
    SidebarButton,
    NavLinks,
    SearchBox,
    AlgoliaSearchBox
  },

  data () {
    return {
      linksWrapMaxWidth: null
    }
  },

  computed: {
    algolia () {
      return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
    },

    isAlgoliaSearch () {
      return this.algolia && this.algolia.apiKey && this.algolia.indexName
    }
  },

  mounted () {
    const MOBILE_DESKTOP_BREAKPOINT = 719 // refer to config.styl
    const NAVBAR_VERTICAL_PADDING = parseInt(css(this.$el, 'paddingLeft')) + parseInt(css(this.$el, 'paddingRight'))
    const handleLinksWrapWidth = () => {
      if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
        this.linksWrapMaxWidth = null
      } else {
        this.linksWrapMaxWidth = this.$el.offsetWidth - NAVBAR_VERTICAL_PADDING
          - (this.$refs.siteName && this.$refs.siteName.offsetWidth || 0)
      }
    }
    handleLinksWrapWidth()
    window.addEventListener('resize', handleLinksWrapWidth, false)
  }
}

function css (el, property) {
  // NOTE: Known bug, will return 'auto' if style value is 'auto'
  const win = el.ownerDocument.defaultView
  // null means not to return pseudo styles
  return win.getComputedStyle(el, null)[property]
}
</script>

<style lang="stylus">
$navbar-vertical-padding = 0.7rem
$navbar-horizontal-padding = 1.5rem

.navbar
  padding $navbar-vertical-padding $navbar-horizontal-padding
  line-height $navbarHeight - 1.4rem
  a, span, img
    display inline-block
  .logo
    height $navbarHeight - 1.4rem
    min-width $navbarHeight - 1.4rem
    margin-right 0.8rem
    vertical-align top
  .site-name
    font-size 1.3rem
    font-weight 600
    color $textColor
    position relative
  .links
    padding-left 1.5rem
    box-sizing border-box
    background-color white
    white-space nowrap
    font-size 0.9rem
    position absolute
    right $navbar-horizontal-padding
    top $navbar-vertical-padding
    display flex
    .search-box
      flex: 0 0 auto
      vertical-align top

@media (max-width: $MQMobile)
  .navbar
    padding-left 4rem
    .can-hide
      display none
    .links
      padding-left 1.5rem
    .site-name
      width calc(100vw - 9.4rem)
      overflow hidden
      white-space nowrap
      text-overflow ellipsis
</style>


================================================
FILE: docs/.vuepress/theme/components/Page.vue
================================================
<template>
  <main class="page">
    <slot name="top" />

    <Content class="theme-default-content" />
    <PageEdit />

    <PageNav v-bind="{ sidebarItems }" />

    <slot name="bottom" />
  </main>
</template>

<script>
import PageEdit from '@theme/components/PageEdit.vue'
import PageNav from '@theme/components/PageNav.vue'

export default {
  components: { PageEdit, PageNav },
  props: ['sidebarItems']
}
</script>

<style lang="stylus">
@require '../styles/wrapper.styl'

.page
  padding-bottom 2rem
  display block

</style>


================================================
FILE: docs/.vuepress/theme/components/PageEdit.vue
================================================
<template>
  <footer class="page-edit">
    <div
      v-if="editLink"
      class="edit-link"
    >
      <a
        :href="editLink"
        target="_blank"
        rel="noopener noreferrer"
      >{{ editLinkText }}</a>
      <OutboundLink />
    </div>

    <div
      v-if="lastUpdated"
      class="last-updated"
    >
      <span class="prefix">{{ lastUpdatedText }}:</span>
      <span class="time">{{ lastUpdated }}</span>
    </div>
  </footer>
</template>

<script>
import isNil from 'lodash/isNil'
import { endingSlashRE, outboundRE } from '../util'

export default {
  name: 'PageEdit',

  computed: {
    lastUpdated () {
      return this.$page.lastUpdated
    },

    lastUpdatedText () {
      if (typeof this.$themeLocaleConfig.lastUpdated === 'string') {
        return this.$themeLocaleConfig.lastUpdated
      }
      if (typeof this.$site.themeConfig.lastUpdated === 'string') {
        return this.$site.themeConfig.lastUpdated
      }
      return 'Last Updated'
    },

    editLink () {
      const showEditLink = isNil(this.$page.frontmatter.editLink)
        ? this.$site.themeConfig.editLinks
        : this.$page.frontmatter.editLink

      const {
        repo,
        docsDir = '',
        docsBranch = 'master',
        docsRepo = repo
      } = this.$site.themeConfig

      if (showEditLink && docsRepo && this.$page.relativePath) {
        return this.createEditLink(
          repo,
          docsRepo,
          docsDir,
          docsBranch,
          this.$page.relativePath
        )
      }
      return null
    },

    editLinkText () {
      return (
        this.$themeLocaleConfig.editLinkText
        || this.$site.themeConfig.editLinkText
        || `Edit this page`
      )
    }
  },

  methods: {
    createEditLink (repo, docsRepo, docsDir, docsBranch, path) {
      const bitbucket = /bitbucket.org/
      if (bitbucket.test(repo)) {
        const base = outboundRE.test(docsRepo) ? docsRepo : repo
        return (
          base.replace(endingSlashRE, '')
          + `/src`
          + `/${docsBranch}/`
          + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
          + path
          + `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`
        )
      }

      const base = outboundRE.test(docsRepo)
        ? docsRepo
        : `https://github.com/${docsRepo}`
      return (
        base.replace(endingSlashRE, '')
        + `/edit`
        + `/${docsBranch}/`
        + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
        + path
      )
    }
  }
}
</script>

<style lang="stylus">
@require '../styles/wrapper.styl'

.page-edit
  @extend $wrapper
  padding-top 1rem
  padding-bottom 1rem
  overflow auto

  .edit-link
    display inline-block
    a
      color lighten($textColor, 25%)
      margin-right 0.25rem
  .last-updated
    float right
    font-size 0.9em
    .prefix
      font-weight 500
      color lighten($textColor, 25%)
    .time
      font-weight 400
      color #aaa

@media (max-width: $MQMobile)
  .page-edit
    .edit-link
      margin-bottom 0.5rem
    .last-updated
      font-size 0.8em
      float none
      text-align left

</style>


================================================
FILE: docs/.vuepress/theme/components/PageNav.vue
================================================
<template>
  <div
    v-if="prev || next"
    class="page-nav"
  >
    <p class="inner">
      <span
        v-if="prev"
        class="prev"
      >
        ←
        <a
          v-if="prev.type === 'external'"
          class="prev"
          :href="prev.path"
          target="_blank"
          rel="noopener noreferrer"
        >
          {{ prev.title || prev.path }}

          <OutboundLink />
        </a>

        <RouterLink
          v-else
          class="prev"
          :to="prev.path"
        >
          {{ prev.title || prev.path }}
        </RouterLink>
      </span>

      <span
        v-if="next"
        class="next"
      >
        <a
          v-if="next.type === 'external'"
          :href="next.path"
          target="_blank"
          rel="noopener noreferrer"
        >
          {{ next.title || next.path }}

          <OutboundLink />
        </a>

        <RouterLink
          v-else
          :to="next.path"
        >
          {{ next.title || next.path }}
        </RouterLink>
        →
      </span>
    </p>
  </div>
</template>

<script>
import { resolvePage } from '../util'
import isString from 'lodash/isString'
import isNil from 'lodash/isNil'

export default {
  name: 'PageNav',

  props: ['sidebarItems'],

  computed: {
    prev () {
      return resolvePageLink(LINK_TYPES.PREV, this)
    },

    next () {
      return resolvePageLink(LINK_TYPES.NEXT, this)
    }
  }
}

function resolvePrev (page, items) {
  return find(page, items, -1)
}

function resolveNext (page, items) {
  return find(page, items, 1)
}

const LINK_TYPES = {
  NEXT: {
    resolveLink: resolveNext,
    getThemeLinkConfig: ({ nextLinks }) => nextLinks,
    getPageLinkConfig: ({ frontmatter }) => frontmatter.next
  },
  PREV: {
    resolveLink: resolvePrev,
    getThemeLinkConfig: ({ prevLinks }) => prevLinks,
    getPageLinkConfig: ({ frontmatter }) => frontmatter.prev
  }
}

function resolvePageLink (
  linkType,
  { $themeConfig, $page, $route, $site, sidebarItems }
) {
  const { resolveLink, getThemeLinkConfig, getPageLinkConfig } = linkType

  // Get link config from theme
  const themeLinkConfig = getThemeLinkConfig($themeConfig)

  // Get link config from current page
  const pageLinkConfig = getPageLinkConfig($page)

  // Page link config will overwrite global theme link config if defined
  const link = isNil(pageLinkConfig) ? themeLinkConfig : pageLinkConfig

  if (link === false) {
    return
  } else if (isString(link)) {
    return resolvePage($site.pages, link, $route.path)
  } else {
    return resolveLink($page, sidebarItems)
  }
}

function find (page, items, offset) {
  const res = []
  flatten(items, res)
  for (let i = 0; i < res.length; i++) {
    const cur = res[i]
    if (cur.type === 'page' && cur.path === decodeURIComponent(page.path)) {
      return res[i + offset]
    }
  }
}

function flatten (items, res) {
  for (let i = 0, l = items.length; i < l; i++) {
    if (items[i].type === 'group') {
      flatten(items[i].children || [], res)
    } else {
      res.push(items[i])
    }
  }
}
</script>

<style lang="stylus">
@require '../styles/wrapper.styl'

.page-nav
  @extend $wrapper
  padding-top 1rem
  padding-bottom 0
  .inner
    min-height 2rem
    margin-top 0
    border-top 1px solid $borderColor
    padding-top 1rem
    overflow auto // clear float
  .next
    float right
</style>


================================================
FILE: docs/.vuepress/theme/components/Sidebar.vue
================================================
<template>
  <aside class="sidebar">
    <NavLinks />

    <slot name="top" />

    <SidebarLinks
      :depth="0"
      :items="items"
    />
    <slot name="bottom" />
  </aside>
</template>

<script>
import SidebarLinks from '@theme/components/SidebarLinks.vue'
import NavLinks from '@theme/components/NavLinks.vue'

export default {
  name: 'Sidebar',

  components: { SidebarLinks, NavLinks },

  props: ['items']
}
</script>

<style lang="stylus">
.sidebar
  ul
    padding 0
    margin 0
    list-style-type none
  a
    display inline-block
  .nav-links
    display none
    border-bottom 1px solid $borderColor
    padding 0.5rem 0 0.75rem 0
    a
      font-weight 600
    .nav-item, .repo-link
      display block
      line-height 1.25rem
      font-size 1.1em
      padding 0.5rem 0 0.5rem 1.5rem
  & > .sidebar-links
    padding 1.5rem 0
    & > li > a.sidebar-link
      font-size 1.1em
      line-height 1.7
      font-weight bold
    & > li:not(:first-child)
      margin-top .75rem

@media (max-width: $MQMobile)
  .sidebar
    .nav-links
      display block
      .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after
        top calc(1rem - 2px)
    & > .sidebar-links
      padding 1rem 0
</style>


================================================
FILE: docs/.vuepress/theme/components/SidebarButton.vue
================================================
<template>
  <div
    class="sidebar-button"
    @click="$emit('toggle-sidebar')"
  >
    <svg
      class="icon"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      role="img"
      viewBox="0 0 448 512"
    >
      <path
        fill="currentColor"
        d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"
        class=""
      />
    </svg>
  </div>
</template>

<style lang="stylus">
.sidebar-button
  cursor pointer
  display none
  width 1.25rem
  height 1.25rem
  position absolute
  padding 0.6rem
  top 0.6rem
  left 1rem
  .icon
    display block
    width 1.25rem
    height 1.25rem

@media (max-width: $MQMobile)
  .sidebar-button
    display block
</style>


================================================
FILE: docs/.vuepress/theme/components/SidebarGroup.vue
================================================
<template>
  <section
    class="sidebar-group"
    :class="[
      {
        collapsable,
        'is-sub-group': depth !== 0
      },
      `depth-${depth}`
    ]"
  >
    <RouterLink
      v-if="item.path"
      class="sidebar-heading clickable"
      :class="{
        open,
        'active': isActive($route, item.path)
      }"
      :to="item.path"
      @click.native="$emit('toggle')"
    >
      <span>{{ item.title }}</span>
      <span
        v-if="collapsable"
        class="arrow"
        :class="open ? 'down' : 'right'"
      />
    </RouterLink>

    <p
      v-else
      class="sidebar-heading"
      :class="{ open }"
      @click="$emit('toggle')"
    >
      <span>{{ item.title }}</span>
      <span
        v-if="collapsable"
        class="arrow"
        :class="open ? 'down' : 'right'"
      />
    </p>

    <DropdownTransition>
      <SidebarLinks
        v-if="open || !collapsable"
        class="sidebar-group-items"
        :items="item.children"
        :sidebar-depth="item.sidebarDepth"
        :depth="depth + 1"
      />
    </DropdownTransition>
  </section>
</template>

<script>
import { isActive } from '../util'
import DropdownTransition from '@theme/components/DropdownTransition.vue'

export default {
  name: 'SidebarGroup',

  components: {
    DropdownTransition
  },

  props: [
    'item',
    'open',
    'collapsable',
    'depth'
  ],

  // ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
  beforeCreate () {
    this.$options.components.SidebarLinks = require('@theme/components/SidebarLinks.vue').default
  },

  methods: { isActive }
}
</script>

<style lang="stylus">
.sidebar-group
  .sidebar-group
    padding-left 0.5em
  &:not(.collapsable)
    .sidebar-heading:not(.clickable)
      cursor auto
      color inherit
  // refine styles of nested sidebar groups
  &.is-sub-group
    padding-left 0
    & > .sidebar-heading
      font-size 0.95em
      line-height 1.4
      font-weight normal
      padding-left 2rem
      &:not(.clickable)
        opacity 0.5
    & > .sidebar-group-items
      padding-left 1rem
      & > li > .sidebar-link
        font-size: 0.95em;
        border-left none
  &.depth-2
    & > .sidebar-heading
      border-left none

.sidebar-heading
  color $textColor
  transition color .15s ease
  cursor pointer
  font-size 1.1em
  font-weight bold
  // text-transform uppercase
  padding 0.35rem 1.5rem 0.35rem 1.25rem
  width 100%
  box-sizing border-box
  margin 0
  border-left 0.25rem solid transparent
  &.open, &:hover
    color inherit
  .arrow
    position relative
    top -0.12em
    left 0.5em
  &.clickable
    &.active
      font-weight 600
      color $accentColor
      border-left-color $accentColor
    &:hover
      color $accentColor

.sidebar-group-items
  transition height .1s ease-out
  font-size 0.95em
  overflow hidden
</style>


================================================
FILE: docs/.vuepress/theme/components/SidebarLink.vue
================================================
<script>
import { isActive, hashRE, groupHeaders } from '../util'

export default {
  functional: true,

  props: ['item', 'sidebarDepth'],

  render (h,
    {
      parent: {
        $page,
        $site,
        $route,
        $themeConfig,
        $themeLocaleConfig
      },
      props: {
        item,
        sidebarDepth
      }
    }) {
    // use custom active class matching logic
    // due to edge case of paths ending with / + hash
    const selfActive = isActive($route, item.path)
    // for sidebar: auto pages, a hash link should be active if one of its child
    // matches
    const active = item.type === 'auto'
      ? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))
      : selfActive
    const link = item.type === 'external'
      ? renderExternal(h, item.path, item.title || item.path)
      : renderLink(h, item.path, item.title || item.path, active)

    const maxDepth = [
      $page.frontmatter.sidebarDepth,
      sidebarDepth,
      $themeLocaleConfig.sidebarDepth,
      $themeConfig.sidebarDepth,
      1
    ].find(depth => depth !== undefined)

    const displayAllHeaders = $themeLocaleConfig.displayAllHeaders
      || $themeConfig.displayAllHeaders

    if (item.type === 'auto') {
      return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]
    } else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {
      const children = groupHeaders(item.headers)
      return [link, renderChildren(h, children, item.path, $route, maxDepth)]
    } else {
      return link
    }
  }
}

function renderLink (h, to, text, active, level) {
  const component = {
    props: {
      to,
      activeClass: '',
      exactActiveClass: ''
    },
    class: {
      active,
      'sidebar-link': true
    }
  }

  if (level > 2) {
    component.style = {
      'padding-left': level + 'rem'
    }
  }

  return h('RouterLink', component, text)
}

function renderChildren (h, children, path, route, maxDepth, depth = 1) {
  if (!children || depth > maxDepth) return null
  return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
    const active = isActive(route, path + '#' + c.slug)
    return h('li', { class: 'sidebar-sub-header' }, [
      renderLink(h, path + '#' + c.slug, c.title, active, c.level - 1),
      renderChildren(h, c.children, path, route, maxDepth, depth + 1)
    ])
  }))
}

function renderExternal (h, to, text) {
  return h('a', {
    attrs: {
      href: to,
      target: '_blank',
      rel: 'noopener noreferrer'
    },
    class: {
      'sidebar-link': true
    }
  }, [text, h('OutboundLink')])
}
</script>

<style lang="stylus">
.sidebar .sidebar-sub-headers
  padding-left 1rem
  font-size 0.95em

a.sidebar-link
  font-size 1em
  font-weight 400
  display inline-block
  color $textColor
  border-left 0.25rem solid transparent
  padding 0.35rem 1rem 0.35rem 1.25rem
  line-height 1.4
  width: 100%
  box-sizing: border-box
  &:hover
    color $accentColor
  &.active
    font-weight 600
    color $accentColor
    border-left-color $accentColor
  .sidebar-group &
    padding-left 2rem
  .sidebar-sub-headers &
    padding-top 0.25rem
    padding-bottom 0.25rem
    border-left none
    &.active
      font-weight 500
</style>


================================================
FILE: docs/.vuepress/theme/components/SidebarLinks.vue
================================================
<template>
  <ul
    v-if="items.length"
    class="sidebar-links"
  >
    <li
      v-for="(item, i) in items"
      :key="i"
    >
      <SidebarGroup
        v-if="item.type === 'group'"
        :item="item"
        :open="i === openGroupIndex"
        :collapsable="item.collapsable || item.collapsible"
        :depth="depth"
        @toggle="toggleGroup(i)"
      />
      <SidebarLink
        v-else
        :sidebar-depth="sidebarDepth"
        :item="item"
      />
    </li>
  </ul>
</template>

<script>
import SidebarGroup from '@theme/components/SidebarGroup.vue'
import SidebarLink from '@theme/components/SidebarLink.vue'
import { isActive } from '../util'

export default {
  name: 'SidebarLinks',

  components: { SidebarGroup, SidebarLink },

  props: [
    'items',
    'depth',  // depth of current sidebar links
    'sidebarDepth' // depth of headers to be extracted
  ],

  data () {
    return {
      openGroupIndex: 0
    }
  },

  watch: {
    '$route' () {
      this.refreshIndex()
    }
  },

  created () {
    this.refreshIndex()
  },

  methods: {
    refreshIndex () {
      const index = resolveOpenGroupIndex(
        this.$route,
        this.items
      )
      if (index > -1) {
        this.openGroupIndex = index
      }
    },

    toggleGroup (index) {
      this.openGroupIndex = index === this.openGroupIndex ? -1 : index
    },

    isActive (page) {
      return isActive(this.$route, page.regularPath)
    }
  }
}

function resolveOpenGroupIndex (route, items) {
  for (let i = 0; i < items.length; i++) {
    const item = items[i]
    if (descendantIsActive(route, item)) {
      return i
    }
  }
  return -1
}

function descendantIsActive (route, item) {
  if (item.type === 'group') {
    return item.children.some(child => {
      if (child.type === 'group') {
        return descendantIsActive(route, child)
      } else {
        return child.type === 'page' && isActive(route, child.path)
      }
    })
  }
  return false
}
</script>


================================================
FILE: docs/.vuepress/theme/global-components/Badge.vue
================================================
<script>
export default {
  functional: true,
  props: {
    type: {
      type: String,
      default: 'tip'
    },
    text: String,
    vertical: {
      type: String,
      default: 'top'
    }
  },
  render (h, { props, slots }) {
    return h('span', {
      class: ['badge', props.type],
      style: {
        verticalAlign: props.vertical
      }
    }, props.text || slots().default)
  }
}
</script>

<style lang="stylus" scoped>
.badge
  display inline-block
  font-size 14px
  height 18px
  line-height 18px
  border-radius 3px
  padding 0 6px
  color white
  background-color #42b983
  &.tip, &.green
    background-color $badgeTipColor
  &.error
    background-color $badgeErrorColor
  &.warning, &.warn, &.yellow
    background-color $badgeWarningColor
  & + &
    margin-left 5px
</style>


================================================
FILE: docs/.vuepress/theme/index.js
================================================
const path = require('path')

// Theme API.
module.exports = (options, ctx) => {
  const { themeConfig, siteConfig } = ctx

  // resolve algolia
  const isAlgoliaSearch = (
    themeConfig.algolia
    || Object
        .keys(siteConfig.locales && themeConfig.locales || {})
        .some(base => themeConfig.locales[base].algolia)
  )

  const enableSmoothScroll = themeConfig.smoothScroll === true

  return {
    alias () {
      return {
        '@AlgoliaSearchBox': isAlgoliaSearch
          ? path.resolve(__dirname, 'components/AlgoliaSearchBox.vue')
          : path.resolve(__dirname, 'noopModule.js')
      }
    },

    plugins: [
      ['@vuepress/active-header-links', options.activeHeaderLinks],
      '@vuepress/search',
      '@vuepress/plugin-nprogress',
      ['container', {
        type: 'tip',
        defaultTitle: {
          '/': 'TIP',
          '/zh/': '提示'
        }
      }],
      ['container', {
        type: 'warning',
        defaultTitle: {
          '/': 'WARNING',
          '/zh/': '注意'
        }
      }],
      ['container', {
        type: 'danger',
        defaultTitle: {
          '/': 'WARNING',
          '/zh/': '警告'
        }
      }],
      ['container', {
        type: 'details',
        before: info => `<details class="custom-block details">${info ? `<summary>${info}</summary>` : ''}\n`,
        after: () => '</details>\n'
      }],
      ['smooth-scroll', enableSmoothScroll]
    ]
  }
}


================================================
FILE: docs/.vuepress/theme/layouts/404.vue
================================================
<template>
  <div class="theme-container">
    <div class="theme-default-content">
      <h1>404</h1>

      <blockquote>{{ getMsg() }}</blockquote>

      <RouterLink to="/">
        Take me home.
      </RouterLink>
    </div>
  </div>
</template>

<script>
const msgs = [
  `There's nothing here.`,
  `How did we get here?`,
  `That's a Four-Oh-Four.`,
  `Looks like we've got some broken links.`
]

export default {
  methods: {
    getMsg () {
      return msgs[Math.floor(Math.random() * msgs.length)]
    }
  }
}
</script>


================================================
FILE: docs/.vuepress/theme/layouts/Layout.vue
================================================
<template>
  <div
    class="theme-container"
    :class="pageClasses"
    @touchstart="onTouchStart"
    @touchend="onTouchEnd"
  >
    <Navbar
      v-if="shouldShowNavbar"
      @toggle-sidebar="toggleSidebar"
    />

    <div
      class="sidebar-mask"
      @click="toggleSidebar(false)"
    />

    <Sidebar
      :items="sidebarItems"
      @toggle-sidebar="toggleSidebar"
    >
      <template #top>
        <slot name="sidebar-top" />
      </template>
      <template #bottom>
        <slot name="sidebar-bottom" />
      </template>
    </Sidebar>

    <Home v-if="$page.frontmatter.home" />

    <Page
      v-else
      :sidebar-items="sidebarItems"
    >
      <template #top>
        <slot name="page-top" />
      </template>
      <template #bottom>
        <slot name="page-bottom" />
      </template>
    </Page>
    <div id="ad">
      <a href="javascript:viod(0)" target="_blank" rel="sponsored noopener">
        关注JS每日一题回复1加入语音社群
        <img src="https://static.vue-js.com/b4b71a30-443b-11eb-85f6-6fac77c0c9b3.png"></a> 
        <!-- <a href="javascript:viod(0)" target="_blank" rel="sponsored noopener">
        拉你进500人前端群
        <img src="http://static.vue-js.com/Fr6pvXKNio5Pe4kEHk0ufXdZ1sHT"></a> -->
    </div>
  </div>
</template>

<script>
import Home from '@theme/components/Home.vue'
import Navbar from '@theme/components/Navbar.vue'
import Page from '@theme/components/Page.vue'
import Sidebar from '@theme/components/Sidebar.vue'
import { resolveSidebarItems } from '../util'

export default {
  name: 'Layout',

  components: {
    Home,
    Page,
    Sidebar,
    Navbar
  },

  data () {
    return {
      isSidebarOpen: false
    }
  },

  computed: {
    shouldShowNavbar () {
      const { themeConfig } = this.$site
      const { frontmatter } = this.$page
      if (
        frontmatter.navbar === false
        || themeConfig.navbar === false) {
        return false
      }
      return (
        this.$title
        || themeConfig.logo
        || themeConfig.repo
        || themeConfig.nav
        || this.$themeLocaleConfig.nav
      )
    },

    shouldShowSidebar () {
      const { frontmatter } = this.$page
      return (
        !frontmatter.home
        && frontmatter.sidebar !== false
        && this.sidebarItems.length
      )
    },

    sidebarItems () {
      return resolveSidebarItems(
        this.$page,
        this.$page.regularPath,
        this.$site,
        this.$localePath
      )
    },

    pageClasses () {
      const userPageClass = this.$page.frontmatter.pageClass
      return [
        {
          'no-navbar': !this.shouldShowNavbar,
          'sidebar-open': this.isSidebarOpen,
          'no-sidebar': !this.shouldShowSidebar
        },
        userPageClass
      ]
    }
  },

  mounted () {
    this.$router.afterEach(() => {
      this.isSidebarOpen = false
    })
  },

  methods: {
    toggleSidebar (to) {
      this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen
      this.$emit('toggle-sidebar', this.isSidebarOpen)
    },

    // side swipe
    onTouchStart (e) {
      this.touchStart = {
        x: e.changedTouches[0].clientX,
        y: e.changedTouches[0].clientY
      }
    },

    onTouchEnd (e) {
      const dx = e.changedTouches[0].clientX - this.touchStart.x
      const dy = e.changedTouches[0].clientY - this.touchStart.y
      if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) {
        if (dx > 0 && this.touchStart.x <= 80) {
          this.toggleSidebar(true)
        } else {
          this.toggleSidebar(false)
        }
      }
    }
  }
}
</script>


================================================
FILE: docs/.vuepress/theme/noopModule.js
================================================
export default {}


================================================
FILE: docs/.vuepress/theme/styles/arrow.styl
================================================
@require './config'

.arrow
  display inline-block
  width 0
  height 0
  &.up
    border-left 4px solid transparent
    border-right 4px solid transparent
    border-bottom 6px solid $arrowBgColor
  &.down
    border-left 4px solid transparent
    border-right 4px solid transparent
    border-top 6px solid $arrowBgColor
  &.right
    border-top 4px solid transparent
    border-bottom 4px solid transparent
    border-left 6px solid $arrowBgColor
  &.left
    border-top 4px solid transparent
    border-bottom 4px solid transparent
    border-right 6px solid $arrowBgColor


================================================
FILE: docs/.vuepress/theme/styles/code.styl
================================================
{$contentClass}
  code
    color lighten($textColor, 20%)
    padding 0.25rem 0.5rem
    margin 0
    font-size 0.85em
    background-color rgba(27,31,35,0.05)
    border-radius 3px
    .token
      &.deleted
        color #EC5975
      &.inserted
        color $accentColor

{$contentClass}
  pre, pre[class*="language-"]
    line-height 1.4
    padding 1.25rem 1.5rem
    margin 0.85rem 0
    background-color $codeBgColor
    border-radius 6px
    overflow auto
    code
      color #fff
      padding 0
      background-color transparent
      border-radius 0

div[class*="language-"]
  position relative
  background-color $codeBgColor
  border-radius 6px
  .highlight-lines
    user-select none
    padding-top 1.3rem
    position absolute
    top 0
    left 0
    width 100%
    line-height 1.4
    .highlighted
      background-color rgba(0, 0, 0, 66%)
  pre, pre[class*="language-"]
    background transparent
    position relative
    z-index 1
  &::before
    position absolute
    z-index 3
    top 0.8em
    right 1em
    font-size 0.75rem
    color rgba(255, 255, 255, 0.4)
  &:not(.line-numbers-mode)
    .line-numbers-wrapper
      display none
  &.line-numbers-mode
    .highlight-lines .highlighted
        position relative
        &:before
          content ' '
          position absolute
          z-index 3
          left 0
          top 0
          display block
          width $lineNumbersWrapperWidth
          height 100%
          background-color rgba(0, 0, 0, 66%)
    pre
      padding-left $lineNumbersWrapperWidth + 1 rem
      vertical-align middle
    .line-numbers-wrapper
      position absolute
      top 0
      width $lineNumbersWrapperWidth
      text-align center
      color rgba(255, 255, 255, 0.3)
      padding 1.25rem 0
      line-height 1.4
      br
        user-select none
      .line-number
        position relative
        z-index 4
        user-select none
        font-size 0.85em
    &::after
      content ''
      position absolute
      z-index 2
      top 0
      left 0
      width $lineNumbersWrapperWidth
      height 100%
      border-radius 6px 0 0 6px
      border-right 1px solid rgba(0, 0, 0, 66%)
      background-color $codeBgColor


for lang in $codeLang
  div{'[class~="language-' + lang + '"]'}
    &:before
      content ('' + lang)

div[class~="language-javascript"]
  &:before
    content "js"

div[class~="language-typescript"]
  &:before
    content "ts"

div[class~="language-markup"]
  &:before
    content "html"

div[class~="language-markdown"]
  &:before
    content "md"

div[class~="language-json"]:before
  content "json"

div[class~="language-ruby"]:before
  content "rb"

div[class~="language-python"]:before
  content "py"

div[class~="language-bash"]:before
  content "sh"

div[class~="language-php"]:before
  content "php"

@import '~prismjs/themes/prism-tomorrow.css'


================================================
FILE: docs/.vuepress/theme/styles/config.styl
================================================
$contentClass = '.theme-default-content'


================================================
FILE: docs/.vuepress/theme/styles/custom-blocks.styl
================================================
.custom-block
  .custom-block-title
    font-weight 600
    margin-bottom -0.4rem
  &.tip, &.warning, &.danger
    padding .1rem 1.5rem
    border-left-width .5rem
    border-left-style solid
    margin 1rem 0
  &.tip
    background-color #f3f5f7
    border-color #42b983
  &.warning
    background-color rgba(255,229,100,.3)
    border-color darken(#ffe564, 35%)
    color darken(#ffe564, 70%)
    .custom-block-title
      color darken(#ffe564, 50%)
    a
      color $textColor
  &.danger
    background-color #ffe6e6
    border-color darken(red, 20%)
    color darken(red, 70%)
    .custom-block-title
      color darken(red, 40%)
    a
      color $textColor
  &.details
    display block
    position relative
    border-radius 2px
    margin 1.6em 0
    padding 1.6em
    background-color #eee
    h4
      margin-top 0
    figure, p
      &:last-child
        margin-bottom 0
        padding-bottom 0
    summary
      outline none
      cursor pointer


================================================
FILE: docs/.vuepress/theme/styles/index.styl
================================================
@require './config'
@require './code'
@require './custom-blocks'
@require './arrow'
@require './wrapper'
@require './toc'

html, body
  padding 0
  margin 0
  background-color #fff

body
  font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif
  -webkit-font-smoothing antialiased
  -moz-osx-font-smoothing grayscale
  font-size 16px
  color $textColor

#ad
  width: 125px;
  position: fixed;
  z-index: $z-header -1;
  right: 10px;
  top: 120px;
  padding: 10px;
  background-color: #fff;
  border-radius: 3px;
  font-size: 13px;
  text-align: center;
  img
    width:  100%;
    
.page
  padding-left $sidebarWidth

.navbar
  position fixed
  z-index 20
  top 0
  left 0
  right 0
  height $navbarHeight
  background-color #fff
  box-sizing border-box
  border-bottom 1px solid $borderColor

.sidebar-mask
  position fixed
  z-index 9
  top 0
  left 0
  width 100vw
  height 100vh
  display none

.sidebar
  font-size 16px
  background-color #fff
  width $sidebarWidth
  position fixed
  z-index 10
  margin 0
  top $navbarHeight
  left 0
  bottom 0
  box-sizing border-box
  border-right 1px solid $borderColor
  overflow-y auto

{$contentClass}:not(.custom)
  @extend $wrapper
  > *:first-child
    margin-top $navbarHeight

  a:hover
    text-decoration underline

  p.demo
    padding 1rem 1.5rem
    border 1px solid #ddd
    border-radius 4px

  img
    max-width 100%

{$contentClass}.custom
  padding 0
  margin 0

  img
    max-width 100%

a
  font-weight 500
  color $accentColor
  text-decoration none

p a code
  font-weight 400
  color $accentColor

kbd
  background #eee
  border solid 0.15rem #ddd
  border-bottom solid 0.25rem #ddd
  border-radius 0.15rem
  padding 0 0.15em

blockquote
  font-size 1rem
  color #999;
  border-left .2rem solid #dfe2e5
  margin 1rem 0
  padding .25rem 0 .25rem 1rem

  & > p
    margin 0

ul, ol
  padding-left 1.2em

strong
  font-weight 600

h1, h2, h3, h4, h5, h6
  font-weight 600
  line-height 1.25

  {$contentClass}:not(.custom) > &
    margin-top (0.5rem - $navbarHeight)
    padding-top ($navbarHeight + 1rem)
    margin-bottom 0

    &:first-child
      margin-top -1.5rem
      margin-bottom 1rem

      + p, + pre, + .custom-block
        margin-top 2rem

  &:hover .header-anchor
    opacity: 1

h1
  font-size 2.2rem

h2
  font-size 1.65rem
  padding-bottom .3rem
  border-bottom 1px solid $borderColor

h3
  font-size 1.35rem

a.header-anchor
  font-size 0.85em
  float left
  margin-left -0.87em
  padding-right 0.23em
  margin-top 0.125em
  opacity 0

  &:hover
    text-decoration none

code, kbd, .line-number
  font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace

p, ul, ol
  line-height 1.7

hr
  border 0
  border-top 1px solid $borderColor

table
  border-collapse collapse
  margin 1rem 0
  display: block
  overflow-x: auto

tr
  border-top 1px solid #dfe2e5

  &:nth-child(2n)
    background-color #f6f8fa

th, td
  border 1px solid #dfe2e5
  padding .6em 1em

.theme-container
  &.sidebar-open
    .sidebar-mask
      display: block

  &.no-navbar
    {$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6
      margin-top 1.5rem
      padding-top 0

    .sidebar
      top 0


@media (min-width: ($MQMobile + 1px))
  .theme-container.no-sidebar
    .sidebar
      display none

    .page
      padding-left 0

@require 'mobile.styl'


================================================
FILE: docs/.vuepress/theme/styles/mobile.styl
================================================
@require './config'

$mobileSidebarWidth = $sidebarWidth * 0.82

// narrow desktop / iPad
@media (max-width: $MQNarrow)
  .sidebar
    font-size 15px
    width $mobileSidebarWidth
  .page
    padding-left $mobileSidebarWidth

// wide mobile
@media (max-width: $MQMobile)
  .sidebar
    top 0
    padding-top $navbarHeight
    transform translateX(-100%)
    transition transform .2s ease
  .page
    padding-left 0
  .theme-container
    &.sidebar-open
      .sidebar
        transform translateX(0)
    &.no-navbar
      .sidebar
        padding-top: 0
  #ad
    display: none

// narrow mobile
@media (max-width: $MQMobileNarrow)
  h1
    font-size 1.9rem
  {$contentClass}
    div[class*="language-"]
      margin 0.85rem -1.5rem
      border-radius 0


================================================
FILE: docs/.vuepress/theme/styles/toc.styl
================================================
.table-of-contents
  .badge
    vertical-align middle


================================================
FILE: docs/.vuepress/theme/styles/wrapper.styl
================================================
$wrapper
  max-width $contentWidth
  margin 0 auto
  padding 2rem 2.5rem
  @media (max-width: $MQNarrow)
    padding 2rem
  @media (max-width: $MQMobileNarrow)
    padding 1.5rem



================================================
FILE: docs/.vuepress/theme/util/index.js
================================================
export const hashRE = /#.*$/
export const extRE = /\.(md|html)$/
export const endingSlashRE = /\/$/
export const outboundRE = /^[a-z]+:/i

export function normalize (path) {
  return decodeURI(path)
    .replace(hashRE, '')
    .replace(extRE, '')
}

export function getHash (path) {
  const match = path.match(hashRE)
  if (match) {
    return match[0]
  }
}

export function isExternal (path) {
  return outboundRE.test(path)
}

export function isMailto (path) {
  return /^mailto:/.test(path)
}

export function isTel (path) {
  return /^tel:/.test(path)
}

export function ensureExt (path) {
  if (isExternal(path)) {
    return path
  }
  const hashMatch = path.match(hashRE)
  const hash = hashMatch ? hashMatch[0] : ''
  const normalized = normalize(path)

  if (endingSlashRE.test(normalized)) {
    return path
  }
  return normalized + '.html' + hash
}

export function isActive (route, path) {
  const routeHash = decodeURIComponent(route.hash)
  const linkHash = getHash(path)
  if (linkHash && routeHash !== linkHash) {
    return false
  }
  const routePath = normalize(route.path)
  const pagePath = normalize(path)
  return routePath === pagePath
}

export function resolvePage (pages, rawPath, base) {
  if (isExternal(rawPath)) {
    return {
      type: 'external',
      path: rawPath
    }
  }
  if (base) {
    rawPath = resolvePath(rawPath, base)
  }
  const path = normalize(rawPath)
  for (let i = 0; i < pages.length; i++) {
    if (normalize(pages[i].regularPath) === path) {
      return Object.assign({}, pages[i], {
        type: 'page',
        path: ensureExt(pages[i].path)
      })
    }
  }
  console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`)
  return {}
}

function resolvePath (relative, base, append) {
  const firstChar = relative.charAt(0)
  if (firstChar === '/') {
    return relative
  }

  if (firstChar === '?' || firstChar === '#') {
    return base + relative
  }

  const stack = base.split('/')

  // remove trailing segment if:
  // - not appending
  // - appending to trailing slash (last segment is empty)
  if (!append || !stack[stack.length - 1]) {
    stack.pop()
  }

  // resolve relative path
  const segments = relative.replace(/^\//, '').split('/')
  for (let i = 0; i < segments.length; i++) {
    const segment = segments[i]
    if (segment === '..') {
      stack.pop()
    } else if (segment !== '.') {
      stack.push(segment)
    }
  }

  // ensure leading slash
  if (stack[0] !== '') {
    stack.unshift('')
  }

  return stack.join('/')
}

/**
 * @param { Page } page
 * @param { string } regularPath
 * @param { SiteData } site
 * @param { string } localePath
 * @returns { SidebarGroup }
 */
export function resolveSidebarItems (page, regularPath, site, localePath) {
  const { pages, themeConfig } = site

  const localeConfig = localePath && themeConfig.locales
    ? themeConfig.locales[localePath] || themeConfig
    : themeConfig

  const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar
  if (pageSidebarConfig === 'auto') {
    return resolveHeaders(page)
  }

  const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar
  if (!sidebarConfig) {
    return []
  } else {
    const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig)
    return config
      ? config.map(item => resolveItem(item, pages, base))
      : []
  }
}

/**
 * @param { Page } page
 * @returns { SidebarGroup }
 */
function resolveHeaders (page) {
  const headers = groupHeaders(page.headers || [])
  return [{
    type: 'group',
    collapsable: false,
    title: page.title,
    path: null,
    children: headers.map(h => ({
      type: 'auto',
      title: h.title,
      basePath: page.path,
      path: page.path + '#' + h.slug,
      children: h.children || []
    }))
  }]
}

export function groupHeaders (headers) {
  // group h3s under h2
  headers = headers.map(h => Object.assign({}, h))
  let lastH2
  headers.forEach(h => {
    if (h.level === 2) {
      lastH2 = h
    } else if (lastH2) {
      (lastH2.children || (lastH2.children = [])).push(h)
    }
  })
  return headers.filter(h => h.level === 2)
}

export function resolveNavLinkItem (linkItem) {
  return Object.assign(linkItem, {
    type: linkItem.items && linkItem.items.length ? 'links' : 'link'
  })
}

/**
 * @param { Route } route
 * @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config
 * @returns { base: string, config: SidebarConfig }
 */
export function resolveMatchingConfig (regularPath, config) {
  if (Array.isArray(config)) {
    return {
      base: '/',
      config: config
    }
  }
  for (const base in config) {
    if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {
      return {
        base,
        config: config[base]
      }
    }
  }
  return {}
}

function ensureEndingSlash (path) {
  return /(\.html|\/)$/.test(path)
    ? path
    : path + '/'
}

function resolveItem (item, pages, base, groupDepth = 1) {
  if (typeof item === 'string') {
    return resolvePage(pages, item, base)
  } else if (Array.isArray(item)) {
    return Object.assign(resolvePage(pages, item[0], base), {
      title: item[1]
    })
  } else {
    if (groupDepth > 3) {
      console.error(
        '[vuepress] detected a too deep nested sidebar group.'
      )
    }
    const children = item.children || []
    if (children.length === 0 && item.path) {
      return Object.assign(resolvePage(pages, item.path, base), {
        title: item.title
      })
    }
    return {
      type: 'group',
      path: item.path,
      title: item.title,
      sidebarDepth: item.sidebarDepth,
      children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)),
      collapsable: item.collapsable !== false
    }
  }
}


================================================
FILE: docs/JavaScript/== _===.md
================================================
# 面试官:== 和 ===区别,分别在什么情况使用

 ![](https://static.vue-js.com/51b208f0-68df-11eb-85f6-6fac77c0c9b3.png)

## 一、等于操作符

等于操作符用两个等于号( == )表示,如果操作数相等,则会返回 `true`

前面文章,我们提到在`JavaScript`中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等

遵循以下规则:

如果任一操作数是布尔值,则将其转换为数值再比较是否相等

```js
let result1 = (true == 1); // true
```

如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等

```js
let result1 = ("55" == 55); // true
```

如果一个操作数是对象,另一个操作数不是,则调用对象的 `valueOf() `方法取得其原始值,再根据前面的规则进行比较

```js
let obj = {valueOf:function(){return 1}}
let result1 = (obj == 1); // true
```

`null `和` undefined `相等

```js
let result1 = (null == undefined ); // true
```

如果有任一操作数是 `NaN` ,则相等操作符返回 `false` 

```js
let result1 = (NaN == NaN ); // false
```

如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回` true `

```
let obj1 = {name:"xxx"}
let obj2 = {name:"xxx"}
let result1 = (obj1 == obj2 ); // false
```

下面进一步做个小结:

- 两个都为简单类型,字符串和布尔值都会转换成数值,再比较
- 简单类型与引用类型比较,对象转化成其原始类型的值,再比较

- 两个都为引用类型,则比较它们是否指向同一个对象

- null 和 undefined 相等
- 存在 NaN 则返回 false



## 二、全等操作符

全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 `true`。即类型相同,值也需相同

```js
let result1 = ("55" === 55); // false,不相等,因为数据类型不同
let result2 = (55 === 55); // true,相等,因为数据类型相同值也相同
```

`undefined` 和 `null` 与自身严格相等

```js
let result1 = (null === null)  //true
let result2 = (undefined === undefined)  //true
```



## 三、区别

相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换

```js
let result1 = ("55" === 55); // false,不相等,因为数据类型不同
let result2 = (55 === 55); // true,相等,因为数据类型相同值也相同
```

`null` 和 `undefined` 比较,相等操作符(==)为`true`,全等为`false`

```js
let result1 = (null == undefined ); // true
let result2 = (null  === undefined); // false
```



### 小结

相等运算符隐藏的类型转换,会带来一些违反直觉的结果

```js
'' == '0' // false
0 == '' // true
0 == '0' // true

false == 'false' // false
false == '0' // true

false == undefined // false
false == null // false
null == undefined // true

' \t\r\n' == 0 // true
```

但在比较`null`的情况的时候,我们一般使用相等操作符`==`

```js
const obj = {};

if(obj.x == null){
  console.log("1");  //执行
}
```

等同于下面写法

```js
if(obj.x === null || obj.x === undefined) {
    ...
}
```

使用相等操作符(==)的写法明显更加简洁了

所以,除了在比较对象属性为`null`或者`undefined`的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===)





================================================
FILE: docs/JavaScript/BOM.md
================================================
# 面试官:说说你对BOM的理解,常见的BOM对象你了解哪些?

 ![](https://static.vue-js.com/3e191c40-8089-11eb-85f6-6fac77c0c9b3.png)

## 一、是什么

`BOM` (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象

其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率

浏览器的全部内容可以看成`DOM`,整个浏览器可以看成`BOM`。区别如下:

![](https://static.vue-js.com/482f33e0-8089-11eb-85f6-6fac77c0c9b3.png)



## 二、window

`Bom`的核心对象是`window`,它表示浏览器的一个实例

在浏览器中,`window`对象有双重角色,即是浏览器窗口的一个接口,又是全局对象

因此所有在全局作用域中声明的变量、函数都会变成`window`对象的属性和方法

```js
var name = 'js每日一题';
function lookName(){
  alert(this.name);
}

console.log(window.name);  //js每日一题
lookName();                //js每日一题
window.lookName();         //js每日一题
```

关于窗口控制方法如下:

- `moveBy(x,y)`:从当前位置水平移动窗体x个像素,垂直移动窗体y个像素,x为负数,将向左移动窗体,y为负数,将向上移动窗体
- `moveTo(x,y)`:移动窗体左上角到相对于屏幕左上角的(x,y)点
- `resizeBy(w,h)`:相对窗体当前的大小,宽度调整w个像素,高度调整h个像素。如果参数为负值,将缩小窗体,反之扩大窗体
- `resizeTo(w,h)`:把窗体宽度调整为w个像素,高度调整为h个像素
- `scrollTo(x,y)`:如果有滚动条,将横向滚动条移动到相对于窗体宽度为x个像素的位置,将纵向滚动条移动到相对于窗体高度为y个像素的位置
- `scrollBy(x,y)`: 如果有滚动条,将横向滚动条向左移动x个像素,将纵向滚动条向下移动y个像素

 `window.open()` 既可以导航到一个特定的`url`,也可以打开一个新的浏览器窗口

如果 `window.open()` 传递了第二个参数,且该参数是已有窗口或者框架的名称,那么就会在目标窗口加载第一个参数指定的URL

```js
window.open('htttp://www.vue3js.cn','topFrame')
==> < a href=" " target="topFrame"></ a>
```

`window.open()` 会返回新窗口的引用,也就是新窗口的 `window` 对象

```js
const myWin = window.open('http://www.vue3js.cn','myWin')
```

`window.close()` 仅用于通过 `window.open()` 打开的窗口

新创建的 `window` 对象有一个 `opener` 属性,该属性指向打开他的原始窗口对象





## 三、location

`url`地址如下:

```js
http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents
```

`location`属性描述如下:

| 属性名   | 例子                                                   | 说明                                |
| -------- | ------------------------------------------------------ | ----------------------------------- |
| hash     | "#contents"                                            | utl中#后面的字符,没有则返回空串    |
| host     | www.wrox.com:80                                        | 服务器名称和端口号                  |
| hostname | www.wrox.com                                           | 域名,不带端口号                    |
| href     | http://www.wrox.com:80/WileyCDA/?q=javascript#contents | 完整url                             |
| pathname | "/WileyCDA/"                                           | 服务器下面的文件路径                |
| port     | 80                                                     | url的端口号,没有则为空             |
| protocol | http:                                                  | 使用的协议                          |
| search   | ?q=javascript                                          | url的查询字符串,通常为?后面的内容 |

除了 `hash `之外,只要修改` location `的一个属性,就会导致页面重新加载新` URL`

`location.reload()`,此方法可以重新刷新当前页面。这个方法会根据最有效的方式刷新页面,如果页面自上一次请求以来没有改变过,页面就会从浏览器缓存中重新加载

如果要强制从服务器中重新加载,传递一个参数`true`即可



## 四、navigator

`navigator` 对象主要用来获取浏览器的属性,区分浏览器类型。属性较多,且兼容性比较复杂

下表列出了`navigator`对象接口定义的属性和方法:

 ![](https://static.vue-js.com/6797ab40-8089-11eb-ab90-d9ae814b240d.png)

 ![](https://static.vue-js.com/74096620-8089-11eb-ab90-d9ae814b240d.png)



## 五、screen

保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度

 ![](https://static.vue-js.com/7d6b21e0-8089-11eb-85f6-6fac77c0c9b3.png)



## 六、history

`history`对象主要用来操作浏览器`URL`的历史记录,可以通过参数向前,向后,或者向指定`URL`跳转

常用的属性如下:

- `history.go()`

接收一个整数数字或者字符串参数:向最近的一个记录中包含指定字符串的页面跳转,

```js
history.go('maixaofei.com')
```

当参数为整数数字的时候,正数表示向前跳转指定的页面,负数为向后跳转指定的页面

```js
history.go(3) //向前跳转三个记录
history.go(-1) //向后跳转一个记录
```

- `history.forward()`:向前跳转一个页面
- `history.back()`:向后跳转一个页面
- `history.length`:获取历史记录数

================================================
FILE: docs/JavaScript/Dom.md
================================================
# 面试官:DOM常见的操作有哪些?

 ![](https://static.vue-js.com/a89c99a0-7fdc-11eb-ab90-d9ae814b240d.png)

## 一、DOM

文档对象模型 (DOM) 是 `HTML` 和 `XML` 文档的编程接口

它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容

任何 `HTML `或` XML `文档都可以用 `DOM `表示为一个由节点构成的层级结构

节点分很多类型,每种类型对应着文档中不同的信息和(或)标记,也都有自己不同的特性、数据和方法,而且与其他类型有某种关系,如下所示:

```html
<html>
    <head>
        <title>Page</title>
    </head>
    <body>
        <p>Hello World!</p >
    </body>
</html>
```

`DOM`像原子包含着亚原子微粒那样,也有很多类型的`DOM`节点包含着其他类型的节点。接下来我们先看看其中的三种:

```html
<div>
    <p title="title">
        content
    </p >
</div>
```

上述结构中,`div`、`p`就是元素节点,`content`就是文本节点,`title`就是属性节点



## 二、操作

日常前端开发,我们都离不开`DOM`操作

在以前,我们使用`Jquery`,`zepto`等库来操作`DOM`,之后在`vue`,`Angular`,`React`等框架出现后,我们通过操作数据来控制`DOM`(绝大多数时候),越来越少的去直接操作`DOM`

但这并不代表原生操作不重要。相反,`DOM`操作才能有助于我们理解框架深层的内容

下面就来分析`DOM`常见的操作,主要分为:

- 创建节点
- 查询节点
- 更新节点
- 添加节点
- 删除节点



### 创建节点

#### createElement

创建新元素,接受一个参数,即要创建元素的标签名

```js
const divEl = document.createElement("div");
```



#### createTextNode

创建一个文本节点

```js
const textEl = document.createTextNode("content");
```



#### createDocumentFragment

用来创建一个文档碎片,它表示一种轻量级的文档,主要是用来存储临时节点,然后把文档碎片的内容一次性添加到`DOM`中

```js
const fragment = document.createDocumentFragment();
```

当请求把一个`DocumentFragment` 节点插入文档树时,插入的不是 `DocumentFragment `自身,而是它的所有子孙节点



#### createAttribute

创建属性节点,可以是自定义属性

```js
const dataAttribute = document.createAttribute('custom');
consle.log(dataAttribute);
```



### 获取节点

#### querySelector

传入任何有效的` css` 选择器,即可选中单个 `DOM `元素(首个):

```js
document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')
```

如果页面上没有指定的元素时,返回 `null`



#### querySelectorAll

返回一个包含节点子树内所有与之相匹配的`Element`节点列表,如果没有相匹配的,则返回一个空节点列表

```js
const notLive = document.querySelectorAll("p");
```

需要注意的是,该方法返回的是一个 `NodeList `的静态实例,它是一个静态的“快照”,而非“实时”的查询





关于获取`DOM`元素的方法还有如下,就不一一述说

```js
document.getElementById('id属性值');返回拥有指定id的对象的引用
document.getElementsByClassName('class属性值');返回拥有指定class的对象集合
document.getElementsByTagName('标签名');返回拥有指定标签名的对象集合
document.getElementsByName('name属性值'); 返回拥有指定名称的对象结合
document/element.querySelector('CSS选择器');  仅返回第一个匹配的元素
document/element.querySelectorAll('CSS选择器');   返回所有匹配的元素
document.documentElement;  获取页面中的HTML标签
document.body; 获取页面中的BODY标签
document.all[''];  获取页面中的所有元素节点的对象集合型
```

除此之外,每个`DOM`元素还有`parentNode`、`childNodes`、`firstChild`、`lastChild`、`nextSibling`、`previousSibling`属性,关系图如下图所示

 ![](https://static.vue-js.com/c100f450-7fdc-11eb-ab90-d9ae814b240d.png)



### 更新节点

#### innerHTML

不但可以修改一个`DOM`节点的文本内容,还可以直接通过`HTML`片段修改`DOM`节点内部的子树

```js
// 获取<p id="p">...</p >
var p = document.getElementById('p');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p">ABC</p >
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p >的内部结构已修改
```



#### innerText、textContent

自动对字符串进行`HTML`编码,保证无法设置任何`HTML`标签

```
// 获取<p id="p-id">...</p >
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
// <p id="p-id">&lt;script&gt;alert("Hi")&lt;/script&gt;</p >
```

两者的区别在于读取属性时,`innerText`不返回隐藏元素的文本,而`textContent`返回所有文本



#### style

`DOM`节点的`style`属性对应所有的`CSS`,可以直接获取或设置。遇到`-`需要转化为驼峰命名

```js
// 获取<p id="p-id">...</p >
const p = document.getElementById('p-id');
// 设置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px'; // 驼峰命名
p.style.paddingTop = '2em';
```





### 添加节点

#### innerHTML

如果这个DOM节点是空的,例如,`<div></div>`,那么,直接使用`innerHTML = '<span>child</span>'`就可以修改`DOM`节点的内容,相当于添加了新的`DOM`节点

如果这个DOM节点不是空的,那就不能这么做,因为`innerHTML`会直接替换掉原来的所有子节点



#### appendChild

把一个子节点添加到父节点的最后一个子节点

举个例子

```js
<!-- HTML结构 -->
<p id="js">JavaScript</p >
<div id="list">
    <p id="java">Java</p >
    <p id="python">Python</p >
    <p id="scheme">Scheme</p >
</div>
```

添加一个`p`元素

```js
const js = document.getElementById('js')
js.innerHTML = "JavaScript"
const list = document.getElementById('list');
list.appendChild(js);
```

现在`HTML`结构变成了下面

```js
<!-- HTML结构 -->
<div id="list">
    <p id="java">Java</p >
    <p id="python">Python</p >
    <p id="scheme">Scheme</p >
    <p id="js">JavaScript</p >  <!-- 添加元素 -->
</div>
```

上述代码中,我们是获取`DOM`元素后再进行添加操作,这个`js`节点是已经存在当前文档树中,因此这个节点首先会从原先的位置删除,再插入到新的位置

如果动态添加新的节点,则先创建一个新的节点,然后插入到指定的位置

```js
const list = document.getElementById('list'),
const haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
```



#### insertBefore

把子节点插入到指定的位置,使用方法如下:

```js
parentElement.insertBefore(newElement, referenceElement)
```

子节点会插入到`referenceElement`之前



#### setAttribute

在指定元素中添加一个属性节点,如果元素中已有该属性改变属性值

```js
const div = document.getElementById('id')
div.setAttribute('class', 'white');//第一个参数属性名,第二个参数属性值。
```



### 删除节点

删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的`removeChild`把自己删掉

```js
// 拿到待删除节点:
const self = document.getElementById('to-be-removed');
// 拿到父节点:
const parent = self.parentElement;
// 删除:
const removed = parent.removeChild(self);
removed === self; // true
```

删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置

## 相关链接
https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model

================================================
FILE: docs/JavaScript/ajax.md
================================================
# 面试官:ajax原理是什么?如何实现?

 ![](https://static.vue-js.com/a35a2950-7b2a-11eb-ab90-d9ae814b240d.png)

## 一、是什么

`AJAX `全称(Async Javascript and XML)

即异步的` JavaScript` 和` XML`,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页

`Ajax`的原理简单来说通过`XmlHttpRequest`对象来向服务器发异步请求,从服务器获得数据,然后用`JavaScript`来操作`DOM`而更新页面

流程图如下:

 ![](https://static.vue-js.com/af42de10-7b2a-11eb-85f6-6fac77c0c9b3.png)

下面举个例子:

领导想找小李汇报一下工作,就委托秘书去叫小李,自己就接着做其他事情,直到秘书告诉他小李已经到了,最后小李跟领导汇报工作

`Ajax`请求数据流程与“领导想找小李汇报一下工作”类似,上述秘书就相当于`XMLHttpRequest`对象,领导相当于浏览器,响应数据相当于小李

浏览器可以发送`HTTP`请求后,接着做其他事情,等收到`XHR`返回来的数据再进行操作



## 二、实现过程

实现 `Ajax `异步交互需要服务器逻辑进行配合,需要完成以下步骤:

- 创建 `Ajax `的核心对象 `XMLHttpRequest `对象

- 通过 `XMLHttpRequest` 对象的 `open()` 方法与服务端建立连接

- 构建请求所需的数据内容,并通过` XMLHttpRequest` 对象的 `send()` 方法发送给服务器端

- 通过 `XMLHttpRequest` 对象提供的 `onreadystatechange` 事件监听服务器端你的通信状态

- 接受并处理服务端向客户端响应的数据结果

- 将处理结果更新到 `HTML `页面中



### 创建XMLHttpRequest对象

通过`XMLHttpRequest()` 构造函数用于初始化一个 `XMLHttpRequest` 实例对象

```js
const xhr = new XMLHttpRequest();
```



### 与服务器建立连接

通过 `XMLHttpRequest` 对象的 `open()` 方法与服务器建立连接

```js
xhr.open(method, url, [async][, user][, password])
```

参数说明:

- `method`:表示当前的请求方式,常见的有`GET`、`POST`

- `url`:服务端地址

- `async`:布尔值,表示是否异步执行操作,默认为`true`

-  `user`: 可选的用户名用于认证用途;默认为`null


- `password`: 可选的密码用于认证用途,默认为`null



### 给服务端发送数据

通过 `XMLHttpRequest` 对象的 `send()` 方法,将客户端页面的数据发送给服务端

```js
xhr.send([body])
```

`body`: 在 `XHR` 请求中要发送的数据体,如果不传递数据则为 `null`

如果使用`GET`请求发送数据的时候,需要注意如下:

- 将请求数据添加到`open()`方法中的`url`地址中
- 发送请求数据中的`send()`方法中参数设置为`null`



### 绑定onreadystatechange事件

`onreadystatechange` 事件用于监听服务器端的通信状态,主要监听的属性为`XMLHttpRequest.readyState` ,

关于`XMLHttpRequest.readyState`属性有五个状态,如下图显示

![](https://static.vue-js.com/9782fc90-7b31-11eb-ab90-d9ae814b240d.png)

只要 `readyState `属性值一变化,就会触发一次 `readystatechange` 事件

`XMLHttpRequest.responseText`属性用于接收服务器端的响应结果

举个例子:

```js
const request = new XMLHttpRequest()
request.onreadystatechange = function(e){
    if(request.readyState === 4){ // 整个请求过程完毕
        if(request.status >= 200 && request.status <= 300){
            console.log(request.responseText) // 服务端返回的结果
        }else if(request.status >=400){
            console.log("错误信息:" + request.status)
        }
    }
}
request.open('POST','http://xxxx')
request.send()
```





## 三、封装

通过上面对`XMLHttpRequest `对象的了解,下面来封装一个简单的`ajax`请求

```js
//封装一个ajax请求
function ajax(options) {
    //创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest()


    //初始化参数的内容
    options = options || {}
    options.type = (options.type || 'GET').toUpperCase()
    options.dataType = options.dataType || 'json'
    const params = options.data

    //发送请求
    if (options.type === 'GET') {
        xhr.open('GET', options.url + '?' + params, true)
        xhr.send(null)
    } else if (options.type === 'POST') {
        xhr.open('POST', options.url, true)
        xhr.send(params)

    //接收请求
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            let status = xhr.status
            if (status >= 200 && status < 300) {
                options.success && options.success(xhr.responseText, xhr.responseXML)
            } else {
                options.fail && options.fail(status)
            }
        }
    }
}
```

使用方式如下

```js
ajax({
    type: 'post',
    dataType: 'json',
    data: {},
    url: 'https://xxxx',
    success: function(text,xml){//请求成功后的回调函数
        console.log(text)
    },
    fail: function(status){////请求失败后的回调函数
        console.log(status)
    }
})
```

================================================
FILE: docs/JavaScript/array_api.md
================================================
# 面试官:数组的常用方法有哪些?

 ![](https://static.vue-js.com/5842e560-67b6-11eb-85f6-6fac77c0c9b3.png)



## 一、操作方法

数组基本操作可以归纳为 增、删、改、查,需要留意的是哪些方法会对原数组产生影响,哪些方法不会

下面对数组常用的操作方法做一个归纳

### 增 

下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响

- push()
- unshift()
- splice()
- concat()


#### push()

`push()`方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度

```js
let colors = []; // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
console.log(count) // 2
```



#### unshift()

unshift()在数组开头添加任意多个值,然后返回新的数组长度

```js
let colors = new Array(); // 创建一个数组
let count = colors.unshift("red", "green"); // 从数组开头推入两项
alert(count); // 2
```



#### splice

传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组

```js
let colors = ["red", "green", "blue"];
let removed = colors.splice(1, 0, "yellow", "orange")
console.log(colors) // red,yellow,orange,green,blue
console.log(removed) // []
```


#### concat()

首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组

```js
let colors = ["red", "green", "blue"];
let colors2 = colors.concat("yellow", ["black", "brown"]);
console.log(colors); // ["red", "green","blue"]
console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]
```



### 删

下面三种都会影响原数组,最后一项不影响原数组:

- pop()
- shift()
- splice()
- slice()



#### pop()

 `pop()` 方法用于删除数组的最后一项,同时减少数组的` length` 值,返回被删除的项

```js
let colors = ["red", "green"]
let item = colors.pop(); // 取得最后一项
console.log(item) // green
console.log(colors.length) // 1
```



#### shift()

` shift() `方法用于删除数组的第一项,同时减少数组的` length` 值,返回被删除的项

```js
let colors = ["red", "green"]
let item = colors.shift(); // 取得第一项
console.log(item) // red
console.log(colors.length) // 1
```



#### splice()

传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组

```js
let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
console.log(colors); // green,blue
console.log(removed); // red,只有一个元素的数组
```



### slice()

 slice() 用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组

```js
let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
console.log(colors)   // red,green,blue,yellow,purple
concole.log(colors2); // green,blue,yellow,purple
concole.log(colors3); // green,blue,yellow
```



#### 改
即修改原来数组的内容,常用`splice`
#### splice() 

传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响

```js
let colors = ["red", "green", "blue"];
let removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
console.log(colors); // red,red,purple,blue
console.log(removed); // green,只有一个元素的数组
```



#### 查

即查找元素,返回元素坐标或者元素值

- indexOf()
- includes()
- find()

#### indexOf()

返回要查找的元素在数组中的位置,如果没找到则返回 -1

```js
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.indexOf(4) // 3
```



#### includes()

返回要查找的元素在数组中的位置,找到返回`true`,否则`false`

```js
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.includes(4) // true
```

#### find()

返回第一个匹配的元素

```js
const people = [
    {
        name: "Matt",
        age: 27
    },
    {
        name: "Nicholas",
        age: 29
    }
];
people.find((element, index, array) => element.age < 28) // // {name: "Matt", age: 27}
```



## 二、排序方法

数组有两个方法可以用来对元素重新排序:

- reverse() 
- sort()

### reverse()

顾名思义,将数组元素方向反转

```js
let values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); // 5,4,3,2,1
```



### sort()

sort()方法接受一个比较函数,用于判断哪个值应该排在前面

```js
function compare(value1, value2) {
    if (value1 < value2) {
        return -1;
    } else if (value1 > value2) {
        return 1;
    } else {
        return 0;
    }
}
let values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); // 0,1,5,10,15
```



## 三、转换方法

常见的转换方法有:

### join()

join() 方法接收一个参数,即字符串分隔符,返回包含所有项的字符串

```js
let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue
```





## 四、迭代方法

常用来迭代数组的方法(都不改变原数组)有如下:

- some()
- every()
- forEach()
- filter()
- map()



### some()

对数组每一项都运行传入的测试函数,如果至少有1个元素返回 true ,则这个方法返回 true

```js
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let someResult = numbers.some((item, index, array) => item > 2);
console.log(someResult) // true
```



### every()

对数组每一项都运行传入的测试函数,如果所有元素都返回 true ,则这个方法返回 true

```js
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult) // false
```



### forEach()

对数组每一项都运行传入的函数,没有返回值

```js
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach((item, index, array) => {
    // 执行某些操作
});
```



### filter()

对数组每一项都运行传入的函数,函数返回 `true` 的项会组成数组之后返回

```js
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // 3,4,5,4,3
```



### map()

对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组

```js
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult) // 2,4,6,8,10,8,6,4,2
```


================================================
FILE: docs/JavaScript/bind_call_apply.md
================================================
# 面试官:bind、call、apply 区别?如何实现一个bind?

 ![](https://static.vue-js.com/a900e460-7be4-11eb-ab90-d9ae814b240d.png)



## 一、作用

`call `、`apply `、`bind `作用是改变函数执行时的上下文,简而言之就是改变函数运行时的`this`指向

那么什么情况下需要改变`this`的指向呢?下面举个例子

```js
var name = "lucy";
var obj = {
    name: "martin",
    say: function () {
        console.log(this.name);
    }
};
obj.say(); // martin,this 指向 obj 对象
setTimeout(obj.say,0); // lucy,this 指向 window 对象
```

从上面可以看到,正常情况`say`方法输出`martin`

但是我们把`say`放在`setTimeout`方法中,在定时器中是作为回调函数来执行的,因此回到主栈执行时是在全局执行上下文的环境中执行的,这时候`this`指向`window`,所以输出`lucy`

我们实际需要的是`this`指向`obj`对象,这时候就需要该改变`this`指向了

```js
setTimeout(obj.say.bind(obj),0); //martin,this指向obj对象
```



## 二、区别

下面再来看看`apply`、`call`、`bind`的使用

### apply

`apply`接受两个参数,第一个参数是`this`的指向,第二个参数是函数接受的参数,以数组的形式传入

改变`this`指向后原函数会立即执行,且此方法只是临时改变`this`指向一次

```js
function fn(...args){
    console.log(this,args);
}
let obj = {
    myname:"张三"
}

fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
```

当第一个参数为`null`、`undefined`的时候,默认指向`window`(在浏览器中)

```js
fn.apply(null,[1,2]); // this指向window
fn.apply(undefined,[1,2]); // this指向window
```



### call

`call`方法的第一个参数也是`this`的指向,后面传入的是一个参数列表

跟`apply`一样,改变`this`指向后原函数会立即执行,且此方法只是临时改变`this`指向一次

```js
function fn(...args){
    console.log(this,args);
}
let obj = {
    myname:"张三"
}

fn.call(obj,1,2); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
```

同样的,当第一个参数为`null`、`undefined`的时候,默认指向`window`(在浏览器中)

```js
fn.call(null,[1,2]); // this指向window
fn.call(undefined,[1,2]); // this指向window
```



### bind

bind方法和call很相似,第一参数也是`this`的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)

改变`this`指向后不会立即执行,而是返回一个永久改变`this`指向的函数

```js
function fn(...args){
    console.log(this,args);
}
let obj = {
    myname:"张三"
}

const bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1,2) // this指向obj
fn(1,2) // this指向window
```


### 小结

从上面可以看到,`apply`、`call`、`bind`三者的区别在于:

- 三者都可以改变函数的`this`对象指向
- 三者第一个参数都是`this`要指向的对象,如果如果没有这个参数或参数为`undefined`或`null`,则默认指向全局`window`
- 三者都可以传参,但是`apply`是数组,而`call`是参数列表,且`apply`和`call`是一次性传入参数,而`bind`可以分为多次传入
- `bind `是返回绑定this之后的函数,`apply `、`call` 则是立即执行 



## 三、实现

实现`bind`的步骤,我们可以分解成为三部分:

- 修改`this`指向
- 动态传递参数

```js
// 方式一:只在bind中传递函数参数
fn.bind(obj,1,2)()

// 方式二:在bind中传递函数参数,也在返回函数中传递参数
fn.bind(obj,1)(2)
```

- 兼容`new`关键字

整体实现代码如下:

```js
Function.prototype.myBind = function (context) {
    // 判断调用对象是否为函数
    if (typeof this !== "function") {
        throw new TypeError("Error");
    }

    // 获取参数
    const args = [...arguments].slice(1),
          fn = this;

    return function Fn() {

        // 根据调用方式,传入不同绑定值
        return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments)); 
    }
}
```

================================================
FILE: docs/JavaScript/cache.md
================================================
# 面试官:Javascript本地存储的方式有哪些?区别及应用场景?

 ![](https://static.vue-js.com/68dccf20-849f-11eb-ab90-d9ae814b240d.png)

## 一、方式

`javaScript`本地缓存的方法我们主要讲述以下四种:

- cookie
- sessionStorage
- localStorage
- indexedDB


### cookie

`Cookie`,类型为「小型文本文件」,指某些网站为了辨别用户身份而储存在用户本地终端上的数据。是为了解决 `HTTP `无状态导致的问题

作为一段一般不超过 4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制 `cookie `有效期、安全性、使用范围的可选属性组成

但是`cookie`在每次请求中都会被发送,如果不使用 `HTTPS `并对其加密,其保存的信息很容易被窃取,导致安全风险。举个例子,在一些使用 `cookie `保持登录态的网站上,如果 `cookie `被窃取,他人很容易利用你的 `cookie `来假扮成你登录网站

关于`cookie`常用的属性如下:

- Expires 用于设置 Cookie 的过期时间

```js
Expires=Wed, 21 Oct 2015 07:28:00 GMT
```

- Max-Age 用于设置在 Cookie 失效之前需要经过的秒数(优先级比`Expires`高)

```js
Max-Age=604800
```

- `Domain `指定了 `Cookie` 可以送达的主机名
- `Path `指定了一个 `URL `路径,这个路径必须出现在要请求的资源的路径中才可以发送 `Cookie` 首部

```js
Path=/docs   # /docs/Web/ 下的资源会带 Cookie 首部
```

- 标记为 `Secure `的 `Cookie `只应通过被`HTTPS`协议加密过的请求发送给服务端

通过上述,我们可以看到`cookie`又开始的作用并不是为了缓存而设计出来,只是借用了`cookie`的特性实现缓存

关于`cookie`的使用如下:

```js
document.cookie = '名字=值';
```

关于`cookie`的修改,首先要确定`domain`和`path`属性都是相同的才可以,其中有一个不同得时候都会创建出一个新的`cookie`

```js
Set-Cookie:name=aa; domain=aa.net; path=/  # 服务端设置
document.cookie =name=bb; domain=aa.net; path=/  # 客户端设置
```

最后`cookie`的删除,最常用的方法就是给`cookie`设置一个过期的事件,这样`cookie`过期后会被浏览器删除



### localStorage

`HTML5`新方法,IE8及以上浏览器都兼容

### 特点

- 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的
- 存储的信息在同一域中是共享的
- 当本页操作(新增、修改、删除)了`localStorage`的时候,本页面不会触发`storage`事件,但是别的页面会触发`storage`事件。
- 大小:5M(跟浏览器厂商有关系)
- `localStorage`本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡
- 受同源策略的限制

下面再看看关于`localStorage`的使用

设置

```js
localStorage.setItem('username','cfangxu');
```

获取

```js
localStorage.getItem('username')
```

获取键名

```js
localStorage.key(0) //获取第一个键名
```

删除

```js
localStorage.removeItem('username')
```

一次性清除所有存储

```js
localStorage.clear()
```

`localStorage` 也不是完美的,它有两个缺点:

- 无法像` Cookie `一样设置过期时间
- 只能存入字符串,无法直接存对象

```js
localStorage.setItem('key', {name: 'value'});
console.log(localStorage.getItem('key')); // '[object, Object]'
```



### sessionStorage

`sessionStorage `和 `localStorage `使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,`sessionStorage` 将会删除数据



### 扩展的前端存储方式

`indexedDB `是一种低级API,用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索

虽然 `Web Storage `对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。`IndexedDB`提供了一个解决方案

#### 优点:

- 储存量理论上没有上限
- 所有操作都是异步的,相比 `LocalStorage` 同步操作性能更高,尤其是数据量较大时
- 原生支持储存` JS `的对象
- 是个正经的数据库,意味着数据库能干的事它都能干

#### 缺点:

- 操作非常繁琐
- 本身有一定门槛

关于`indexedDB`的使用基本使用步骤如下:

- 打开数据库并且开始一个事务

- 创建一个 `object store`
- 构建一个请求来执行一些数据库操作,像增加或提取数据等。
- 通过监听正确类型的 `DOM` 事件以等待操作完成。
- 在操作结果上进行一些操作(可以在 `request `对象中找到)

关于使用`indexdb`的使用会比较繁琐,大家可以通过使用`Godb.js`库进行缓存,最大化的降低操作难度




## 二、区别

关于`cookie`、`sessionStorage`、`localStorage`三者的区别主要如下:

- 存储大小:` cookie`数据大小不能超过`4k`,`sessionStorage`和`localStorage `虽然也有存储大小的限制,但比`cookie`大得多,可以达到5M或更大

- 有效时间:` localStorage   `存储持久数据,浏览器关闭后数据不丢失除非主动删除数据; `sessionStorage  `数据在当前浏览器窗口关闭后自动删除;` cookie `设置的`cookie`过期时间之前一直有效,即使窗口或浏览器关闭

- 数据与服务器之间的交互方式,`  cookie`的数据会自动的传递到服务器,服务器端也可以写`cookie`到客户端; `sessionStorage`和`localStorage`不会自动把数据发给服务器,仅在本地保存



## 三、应用场景

在了解了上述的前端的缓存方式后,我们可以看看针对不对场景的使用选择:

- 标记用户与跟踪用户行为的情况,推荐使用`cookie`
- 适合长期保存在本地的数据(令牌),推荐使用`localStorage`
- 敏感账号一次性登录,推荐使用`sessionStorage`
- 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用`indexedDB`



## 相关连接

- https://mp.weixin.qq.com/s/mROjtpoXarN--UDfEMqwhQ
- https://github.com/chenstarx/GoDB.js

================================================
FILE: docs/JavaScript/closure.md
================================================
# 面试官:说说你对闭包的理解?闭包使用场景

 ![](https://static.vue-js.com/c141a030-6a7a-11eb-ab90-d9ae814b240d.png)

## 一、是什么

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)

也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域

在 `JavaScript `中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁

下面给出一个简单的例子

```js
function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init();
```

`displayName()` 没有自己的局部变量。然而,由于闭包的特性,它可以访问到外部函数的变量



## 二、使用场景

任何闭包的使用场景都离不开这两点:

- 创建私有变量
- 延长变量的生命周期

> 一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的


下面举个例子:

在页面上添加一些可以调整字号的按钮

```js
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
```



### 柯里化函数

柯里化的目的在于避免频繁调用具有相同参数函数的同时,又能够轻松的重用

```js
// 假设我们有一个求长方形面积的函数
function getArea(width, height) {
    return width * height
}
// 如果我们碰到的长方形的宽老是10
const area1 = getArea(10, 20)
const area2 = getArea(10, 30)
const area3 = getArea(10, 40)

// 我们可以使用闭包柯里化这个计算面积的函数
function getArea(width) {
    return height => {
        return width * height
    }
}

const getTenWidthArea = getArea(10)
// 之后碰到宽度为10的长方形就可以这样计算面积
const area1 = getTenWidthArea(20)

// 而且如果遇到宽度偶尔变化也可以轻松复用
const getTwentyWidthArea = getArea(20)
```



### 使用闭包模拟私有方法

在`JavaScript`中,没有支持声明私有变量,但我们可以使用闭包来模拟私有方法


下面举个例子:

```js
var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
```

上述通过使用闭包来定义公共函数,并令其可以访问私有函数和变量,这种方式也叫模块方式

两个计数器 `Counter1` 和 `Counter2` 是维护它们各自的独立性的,每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境,不会影响另一个闭包中的变量



### 其他

例如计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期



## 三、注意事项

如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响

例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。

原因在于每个对象的创建,方法都会被重新赋值

```js
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}
```

上面的代码中,我们并没有利用到闭包的好处,因此可以避免使用闭包。修改成如下:

```js
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};
```

================================================
FILE: docs/JavaScript/context_stack.md
================================================
# 面试官:JavaScript中执行上下文和执行栈是什么?

![](https://static.vue-js.com/8652b710-74c1-11eb-85f6-6fac77c0c9b3.png)


## 一、执行上下文

简单的来说,执行上下文是一种对`Javascript`代码执行环境的抽象概念,也就是说只要有`Javascript`代码运行,那么它就一定是运行在执行上下文中

执行上下文的类型分为三种:

- 全局执行上下文:只有一个,浏览器中的全局对象就是 `window `对象,`this` 指向这个全局对象
- 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
- Eval 函数执行上下文: 指的是运行在 `eval` 函数中的代码,很少用而且不建议使用

下面给出全局上下文和函数上下文的例子:

 ![](https://static.vue-js.com/90dd3b60-74c1-11eb-85f6-6fac77c0c9b3.png)

紫色框住的部分为全局上下文,蓝色和橘色框起来的是不同的函数上下文。只有全局上下文(的变量)能被其他任何上下文访问

可以有任意多个函数上下文,每次调用函数创建一个新的上下文,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问



## 二、生命周期

执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段

### 创建阶段

创建阶段即当函数被调用,但未执行任何其内部代码之前

创建阶段做了三件事:

- 确定 this 的值,也被称为 `This Binding`
- LexicalEnvironment(词法环境) 组件被创建
- VariableEnvironment(变量环境) 组件被创建

伪代码如下:

```js
ExecutionContext = {  
  ThisBinding = <this value>,     // 确定this 
  LexicalEnvironment = { ... },   // 词法环境
  VariableEnvironment = { ... },  // 变量环境
}
```



#### This Binding

确定`this`的值我们前面讲到,`this`的值是在执行的时候才能确认,定义的时候不能确认


#### 词法环境

词法环境有两个组成部分:

- 全局环境:是一个没有外部环境的词法环境,其外部环境引用为` null`,有一个全局对象,`this` 的值指向这个全局对象

- 函数环境:用户在函数中定义的变量被存储在环境记录中,包含了`arguments` 对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境

伪代码如下:

```js
GlobalExectionContext = {  // 全局执行上下文
  LexicalEnvironment: {       // 词法环境
    EnvironmentRecord: {     // 环境记录
      Type: "Object",           // 全局环境
      // 标识符绑定在这里 
      outer: <null>           // 对外部环境的引用
  }  
}

FunctionExectionContext = { // 函数执行上下文
  LexicalEnvironment: {     // 词法环境
    EnvironmentRecord: {    // 环境记录
      Type: "Declarative",      // 函数环境
      // 标识符绑定在这里      // 对外部环境的引用
      outer: <Global or outer function environment reference>  
  }  
}
```



#### 变量环境

变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性

在 ES6 中,词法环境和变量环境的区别在于前者用于存储函数声明和变量( `let` 和 `const` )绑定,而后者仅用于存储变量( `var` )绑定

举个例子

```js
let a = 20;  
const b = 30;  
var c;

function multiply(e, f) {  
 var g = 20;  
 return e * f * g;  
}

c = multiply(20, 30);
```

执行上下文如下:

```js
GlobalExectionContext = {

  ThisBinding: <Global Object>,

  LexicalEnvironment: {  // 词法环境
    EnvironmentRecord: {  
      Type: "Object",  
      // 标识符绑定在这里  
      a: < uninitialized >,  
      b: < uninitialized >,  
      multiply: < func >  
    }  
    outer: <null>  
  },

  VariableEnvironment: {  // 变量环境
    EnvironmentRecord: {  
      Type: "Object",  
      // 标识符绑定在这里  
      c: undefined,  
    }  
    outer: <null>  
  }  
}

FunctionExectionContext = {  
   
  ThisBinding: <Global Object>,

  LexicalEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // 标识符绑定在这里  
      Arguments: {0: 20, 1: 30, length: 2},  
    },  
    outer: <GlobalLexicalEnvironment>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // 标识符绑定在这里  
      g: undefined  
    },  
    outer: <GlobalLexicalEnvironment>  
  }  
}
```

留意上面的代码,`let`和`const`定义的变量`a`和`b`在创建阶段没有被赋值,但`var`声明的变量从在创建阶段被赋值为`undefined`

这是因为,创建阶段,会在代码中扫描变量和函数声明,然后将函数声明存储在环境中

但变量会被初始化为`undefined`(`var`声明的情况下)和保持`uninitialized`(未初始化状态)(使用`let`和`const`声明的情况下)

这就是变量提升的实际原因



### 执行阶段

在这阶段,执行变量赋值、代码执行

如果 `Javascript` 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配 `undefined` 值



### 回收阶段

执行上下文出栈等待虚拟机回收执行上下文



## 二、执行栈

执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文

 ![](https://static.vue-js.com/9eda0310-74c1-11eb-ab90-d9ae814b240d.png)

当`Javascript`引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中

每当引擎碰到一个函数的时候,它就会创建一个函数执行上下文,然后将这个执行上下文压到执行栈中

引擎会执行位于执行栈栈顶的执行上下文(一般是函数执行上下文),当该函数执行结束后,对应的执行上下文就会被弹出,然后控制流程到达执行栈的下一个执行上下文

举个例子:

```js
let a = 'Hello World!';
function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}
function second() {
  console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
```

转化成图的形式

 ![](https://static.vue-js.com/ac11a600-74c1-11eb-ab90-d9ae814b240d.png)

简单分析一下流程:

- 创建全局上下文请压入执行栈
- `first`函数被调用,创建函数执行上下文并压入栈
- 执行`first`函数过程遇到`second`函数,再创建一个函数执行上下文并压入栈
- `second`函数执行完毕,对应的函数执行上下文被推出执行栈,执行下一个执行上下文`first`函数
- `first`函数执行完毕,对应的函数执行上下文也被推出栈中,然后执行全局上下文
- 所有代码执行完毕,全局上下文也会被推出栈中,程序结束


## 参考文献

- https://zhuanlan.zhihu.com/p/107552264

================================================
FILE: docs/JavaScript/continue_to_upload.md
================================================
# 面试官:大文件上传如何做断点续传?

 ![](https://static.vue-js.com/3ccb0e90-8ba4-11eb-85f6-6fac77c0c9b3.png)

## 一、是什么

不管怎样简单的需求,在量级达到一定层次时,都会变得异常复杂

文件上传简单,文件变大就复杂

上传大文件时,以下几个变量会影响我们的用户体验

- 服务器处理数据的能力
- 请求超时
- 网络波动

上传时间会变长,高频次文件上传失败,失败后又需要重新上传等等

为了解决上述问题,我们需要对大文件上传单独处理

这里涉及到分片上传及断点续传两个概念

#### 分片上传

分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传

如下图

 ![](https://static.vue-js.com/21db7520-8ba4-11eb-85f6-6fac77c0c9b3.png)

上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件

大致流程如下:
1. 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
2. 初始化一个分片上传任务,返回本次分片上传唯一标识;
3. 按照一定的策略(串行或并行)发送各个分片数据块;
4. 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件

#### 断点续传
断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分

每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度

一般实现方式有两种:

- 服务器端返回,告知从哪开始
- 浏览器端自行处理

上传过程中将文件在服务器写为临时文件,等全部写完了(文件上传完),将此临时文件重命名为正式文件即可

如果中途上传中断过,下次上传的时候根据当前临时文件大小,作为在客户端读取文件的偏移量,从此位置继续读取文件数据块,上传到服务器从此偏移量继续写入文件即可

## 二、实现思路

整体思路比较简单,拿到文件,保存文件唯一性标识,切割文件,分段上传,每次上传一段,根据唯一性标识判断文件上传进度,直到文件的全部片段上传完毕

![](https://static.vue-js.com/465d2920-8ba4-11eb-85f6-6fac77c0c9b3.png)

下面的内容都是伪代码

读取文件内容:

```js
const input = document.querySelector('input');
input.addEventListener('change', function() {
    var file = this.files[0];
});
```

可以使用`md5`实现文件的唯一性

```js
const md5code = md5(file);
```

然后开始对文件进行分割

```js
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener("load", function(e) {
    //每10M切割一段,这里只做一个切割演示,实际切割需要循环切割,
    var slice = e.target.result.slice(0, 10*1024*1024);
});
```

h5上传一个(一片)

```js
const formdata = new FormData();
formdata.append('0', slice);
//这里是有一个坑的,部分设备无法获取文件名称,和文件类型,这个在最后给出解决方案
formdata.append('filename', file.filename);
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
    //xhr.responseText
});
xhr.open('POST', '');
xhr.send(formdata);
xhr.addEventListener('progress', updateProgress);
xhr.upload.addEventListener('progress', updateProgress);

function updateProgress(event) {
    if (event.lengthComputable) {
        //进度条
    }
}
```

这里给出常见的图片和视频的文件类型判断

```js
function checkFileType(type, file, back) {
/**
* type png jpg mp4 ...
* file input.change=> this.files[0]
* back callback(boolean)
*/
    var args = arguments;
    if (args.length != 3) {
        back(0);
    }
    var type = args[0]; // type = '(png|jpg)' , 'png'
    var file = args[1];
    var back = typeof args[2] == 'function' ? args[2] : function() {};
    if (file.type == '') {
        // 如果系统无法获取文件类型,则读取二进制流,对二进制进行解析文件类型
        var imgType = [
            'ff d8 ff', //jpg
            '89 50 4e', //png

            '0 0 0 14 66 74 79 70 69 73 6F 6D', //mp4
            '0 0 0 18 66 74 79 70 33 67 70 35', //mp4
            '0 0 0 0 66 74 79 70 33 67 70 35', //mp4
            '0 0 0 0 66 74 79 70 4D 53 4E 56', //mp4
            '0 0 0 0 66 74 79 70 69 73 6F 6D', //mp4

            '0 0 0 18 66 74 79 70 6D 70 34 32', //m4v
            '0 0 0 0 66 74 79 70 6D 70 34 32', //m4v

            '0 0 0 14 66 74 79 70 71 74 20 20', //mov
            '0 0 0 0 66 74 79 70 71 74 20 20', //mov
            '0 0 0 0 6D 6F 6F 76', //mov

            '4F 67 67 53 0 02', //ogg
            '1A 45 DF A3', //ogg

            '52 49 46 46 x x x x 41 56 49 20', //avi (RIFF fileSize fileType LIST)(52 49 46 46,DC 6C 57 09,41 56 49 20,4C 49 53 54)
        ];
        var typeName = [
            'jpg',
            'png',
            'mp4',
            'mp4',
            'mp4',
            'mp4',
            'mp4',
            'm4v',
            'm4v',
            'mov',
            'mov',
            'mov',
            'ogg',
            'ogg',
            'avi',
        ];
        var sliceSize = /png|jpg|jpeg/.test(type) ? 3 : 12;
        var reader = new FileReader();
        reader.readAsArrayBuffer(file);
        reader.addEventListener("load", function(e) {
            var slice = e.target.result.slice(0, sliceSize);
            reader = null;
            if (slice && slice.byteLength == sliceSize) {
                var view = new Uint8Array(slice);
                var arr = [];
                view.forEach(function(v) {
                    arr.push(v.toString(16));
                });
                view = null;
                var idx = arr.join(' ').indexOf(imgType);
                if (idx > -1) {
                    back(typeName[idx]);
                } else {
                    arr = arr.map(function(v) {
                        if (i > 3 && i < 8) {
                            return 'x';
                        }
                        return v;
                    });
                    var idx = arr.join(' ').indexOf(imgType);
                    if (idx > -1) {
                        back(typeName[idx]);
                    } else {
                        back(false);
                    }

                }
            } else {
                back(false);
            }

        });
    } else {
        var type = file.name.match(/\.(\w+)$/)[1];
        back(type);
    }
}
```

调用方法如下

```js
checkFileType('(mov|mp4|avi)',file,function(fileType){
    // fileType = mp4,
    // 如果file的类型不在枚举之列,则返回false
});
```

上面上传文件的一步,可以改成:

```js
formdata.append('filename', md5code+'.'+fileType);
```

有了切割上传后,也就有了文件唯一标识信息,断点续传变成了后台的一个小小的逻辑判断

后端主要做的内容为:根据前端传给后台的`md5`值,到服务器磁盘查找是否有之前未完成的文件合并信息(也就是未完成的半成品文件切片),取到之后根据上传切片的数量,返回数据告诉前端开始从第几节上传

如果想要暂停切片的上传,可以使用`XMLHttpRequest `的 `abort `方法


## 三、使用场景

- 大文件加速上传:当文件大小超过预期大小时,使用分片上传可实现并行上传多个 Part, 以加快上传速度
- 网络环境较差:建议使用分片上传。当出现上传失败的时候,仅需重传失败的Part
- 流式上传:可以在需要上传的文件大小还不确定的情况下开始上传。这种场景在视频监控等行业应用中比较常见

## 小结
当前的伪代码,只是提供一个简单的思路,想要把事情做到极致,我们还需要考虑到更多场景,比如

- 切片上传失败怎么办
- 上传过程中刷新页面怎么办
- 如何进行并行上传
- 切片什么时候按数量切,什么时候按大小切
- 如何结合 Web Worker 处理大文件上传
- 如何实现秒传

人生又何尝不是如此,极致的人生体验有无限可能,越是后面才发现越是精彩 ~_~

## 参考文献

- https://segmentfault.com/a/1190000009448892
- https://baike.baidu.com/


================================================
FILE: docs/JavaScript/copy.md
================================================
# 面试官:深拷贝浅拷贝的区别?如何实现一个深拷贝?

 ![](https://static.vue-js.com/cdf952e0-69b8-11eb-85f6-6fac77c0c9b3.png)

## 一、数据类型存储

前面文章我们讲到,`JavaScript`中存在两大数据类型:

- 基本类型
- 引用类型 

基本类型数据保存在在栈内存中

引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中



## 二、浅拷贝

浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

下面简单实现一个浅拷贝

```js
function shallowClone(obj) {
    const newObj = {};
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}
```

在`JavaScript`中,存在浅拷贝的现象有:

- `Object.assign`
- `Array.prototype.slice()`, `Array.prototype.concat()`
- 使用拓展运算符实现的复制





### Object.assign

```js
var obj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newObj = Object.assign({}, fxObj);
```



### slice()

```js
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
```



### concat()

```js
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
```







### 拓展运算符

```js
const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
```





## 三、深拷贝

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

- _.cloneDeep()

- jQuery.extend()
- JSON.stringify()
- 手写循环递归



### _.cloneDeep()

```js
const _ = require('lodash');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
```



### jQuery.extend()

```js
const $ = require('jquery');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
```





### JSON.stringify()

```js
const obj2=JSON.parse(JSON.stringify(obj1));
```

但是这种方式存在弊端,会忽略`undefined`、`symbol`和`函数`

```js
const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}
```



### 循环递归

```js
function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}
```







## 四、区别

下面首先借助两张图,可以更加清晰看到浅拷贝与深拷贝的区别

 ![](https://static.vue-js.com/d9862c00-69b8-11eb-ab90-d9ae814b240d.png)

从上图发现,浅拷贝和深拷贝都创建出一个新的对象,但在复制对象属性的时候,行为就不一样

浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象

```js
// 浅拷贝
const obj1 = {
    name : 'init',
    arr : [1,[2,3],4],
};
const obj3=shallowClone(obj1) // 一个浅拷贝方法
obj3.name = "update";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存

console.log('obj1',obj1) // obj1 { name: 'init',  arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }
```

但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

```js
// 深拷贝
const obj1 = {
    name : 'init',
    arr : [1,[2,3],4],
};
const obj4=deepClone(obj1) // 一个深拷贝方法
obj4.name = "update";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存

console.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }
```



### 小结

前提为拷贝类型为引用类型的情况下:

- 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址

- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

================================================
FILE: docs/JavaScript/data_type.md
================================================
# 面试官:说说JavaScript中的数据类型?存储上的差别?

 ![](https://static.vue-js.com/6d133f90-6463-11eb-ab90-d9ae814b240d.png)

## 前言

在`JavaScript`中,我们可以分成两种类型:

- 基本类型
- 复杂类型

两种类型的区别是:存储位置不同



## 一、基本类型

基本类型主要为以下6种:

- Number
- String
- Boolean
- Undefined
- null
- symbol



### Number

数值最常见的整数类型格式则为十进制,还可以设置八进制(零开头)、十六进制(0x开头)

```js
let intNum = 55 // 10进制的55
let num1 = 070 // 8进制的56
let hexNum1 = 0xA //16进制的10
```

浮点类型则在数值汇总必须包含小数点,还可通过科学计数法表示

```js
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // 有效,但不推荐
let floatNum = 3.125e7; // 等于 31250000
```

在数值类型中,存在一个特殊数值`NaN`,意为“不是数值”,用于表示本来要返回数值的操作失败了(而不是抛出错误)

```js
console.log(0/0); // NaN
console.log(-0/+0); // NaN
```



### Undefined

`Undefined` 类型只有一个值,就是特殊值 `undefined`。当使用 `var `或 `let `声明了变量但没有初始化时,就相当于给变量赋予了 `undefined `值

```js
let message;
console.log(message == undefined); // true
```

包含` undefined` 值的变量跟未定义变量是有区别的

```js
let message; // 这个变量被声明了,只是值为 undefined

console.log(message); // "undefined"
console.log(age); // 没有声明过这个变量,报错
```



### String

字符串可以使用双引号(")、单引号(')或反引号(`)标示

```js
let firstName = "John";
let lastName = 'Jacob';
let lastName = `Jingleheimerschmidt`
```

字符串是不可变的,意思是一旦创建,它们的值就不能变了

```js
let lang = "Java";
lang = lang + "Script";  // 先销毁再创建
```



### Null

`Null `类型同样只有一个值,即特殊值 `null`

逻辑上讲, null 值表示一个空对象指针,这也是给`typeof `传一个 `null` 会返回 `"object"` 的原因

```js
let car = null;
console.log(typeof car); // "object"
```

`undefined` 值是由 `null `值派生而来

```js
console.log(null == undefined); // true
```

只要变量要保存对象,而当时又没有那个对象可保存,就可用 `null `来填充该变量



### Boolean

`Boolean `(布尔值)类型有两个字面值: `true` 和` false`

通过`Boolean`可以将其他类型的数据转化成布尔值

规则如下:

```js
数据类型      				转换为 true 的值      				转换为 false 的值
 String        				 非空字符串          					"" 
 Number 				非零数值(包括无穷值)						0 、 NaN 
 Object 					 任意对象 							   null
Undefined 					N/A (不存在) 						undefined
```



### Symbol

Symbol (符号)是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险

```js
let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
console.log(genericSymbol == otherGenericSymbol); // false

let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');
console.log(fooSymbol == otherFooSymbol); // false
```





## 二、引用类型

复杂类型统称为`Object`,我们这里主要讲述下面三种:

- Object
- Array
- Function



### Object

创建`object`常用方式为对象字面量表示法,属性名可以是字符串或数值

```js
let person = {
    name: "Nicholas",
    "age": 29,
    5: true
};
```



### Array

`JavaScript`数组是一组有序的数据,但跟其他语言不同的是,数组中每个槽位可以存储任意类型的数据。并且,数组也是动态大小的,会随着数据添加而自动增长

```js
let colors = ["red", 2, {age: 20 }]
colors.push(2)
```



### Function 

函数实际上是对象,每个函数都是 `Function`类型的实例,而 `Function `也有属性和方法,跟其他引用类型一样

函数存在三种常见的表达方式:

- 函数声明

```js
// 函数声明
function sum (num1, num2) {
    return num1 + num2;
}
```

- 函数表达式

```js
let sum = function(num1, num2) {
    return num1 + num2;
};
```

- 箭头函数

函数声明和函数表达式两种方式

```js
let sum = (num1, num2) => {
    return num1 + num2;
};
```



### 其他引用类型

除了上述说的三种之外,还包括`Date`、`RegExp`、`Map`、`Set`等......



## 三、存储区别

基本数据类型和引用数据类型存储在内存中的位置不同:

- 基本数据类型存储在栈中

- 引用类型的对象存储于堆中

当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值

下面来举个例子

### 基本类型

```js
let a = 10;
let b = a; // 赋值操作
b = 20;
console.log(a); // 10值
```

`a`的值为一个基本类型,是存储在栈中,将`a`的值赋给`b`,虽然两个变量的值相等,但是两个变量保存了两个不同的内存地址

下图演示了基本类型赋值的过程:

 ![](https://static.vue-js.com/906ffb90-6463-11eb-85f6-6fac77c0c9b3.png)



### 引用类型

```js
var obj1 = {}
var obj2 = obj1;
obj2.name = "Xxx";
console.log(obj1.name); // xxx
```

引用类型数据存放在堆中,每个堆内存对象都有对应的引用地址指向它,引用地址存放在栈中。

`obj1`是一个引用类型,在赋值操作过程汇总,实际是将堆内存对象在栈内存的引用地址复制了一份给了`obj2`,实际上他们共同指向了同一个堆内存对象,所以更改`obj2`会对`obj1`产生影响

下图演示这个引用类型赋值过程

 ![](https://static.vue-js.com/a34bdd10-6463-11eb-ab90-d9ae814b240d.png)



### 小结

- 声明变量时不同的内存地址分配:
  - 简单类型的值存放在栈中,在栈中存放的是对应的值
  - 引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
- 不同的类型数据导致赋值变量时的不同:
  - 简单类型赋值,是生成相同的值,两个对象对应不同的地址
  - 复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象


================================================
FILE: docs/JavaScript/debounce_throttle.md
================================================
# 面试官:什么是防抖和节流?有什么区别?如何实现?

 ![](https://static.vue-js.com/912f1a10-8787-11eb-85f6-6fac77c0c9b3.png)

## 一、是什么
本质上是优化高频率执行代码的一种手段

如:浏览器的 `resize`、`scroll`、`keypress`、`mousemove` 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能

为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 **防抖(debounce)** 和 **节流(throttle)** 的方式来减少调用频率

#### 定义
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

一个经典的比喻:

想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应

假设电梯有两种运行策略 `debounce` 和 `throttle`,超时设定为15秒,不考虑容量限制

电梯第一个人进来后,15秒后准时运送一次,这是节流

电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖

## 代码实现

### 节流

完成节流可以使用时间戳与定时器的写法

使用时间戳写法,事件会立即执行,停止触发后没有办法再次执行

```js
function throttled1(fn, delay = 500) {
    let oldtime = Date.now()
    return function (...args) {
        let newtime = Date.now()
        if (newtime - oldtime >= delay) {
            fn.apply(null, args)
            oldtime = Date.now()
        }
    }
}

```

使用定时器写法,`delay`毫秒后第一次执行,第二次事件停止触发后依然会再一次执行

```js
function throttled2(fn, delay = 500) {
    let timer = null
    return function (...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args)
                timer = null
            }, delay);
        }
    }
}
```

可以将时间戳写法的特性与定时器写法的特性相结合,实现一个更加精确的节流。实现如下

```js
function throttled(fn, delay) {
    let timer = null
    let starttime = Date.now()
    return function () {
        let curTime = Date.now() // 当前时间
        let remaining = delay - (curTime - starttime)  // 从上一次到现在,还剩下多少多余时间
        let context = this
        let args = arguments
        clearTimeout(timer)
        if (remaining <= 0) {
            fn.apply(context, args)
            starttime = Date.now()
        } else {
            timer = setTimeout(fn, remaining);
        }
    }
}
```

### 防抖

简单版本的实现

```js
function debounce(func, wait) {
    let timeout;

    return function () {
        let context = this; // 保存this指向
        let args = arguments; // 拿到event对象

        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}
```

防抖如果需要立即执行,可加入第三个参数用于判断,实现如下:

```js
function debounce(func, wait, immediate) {

    let timeout;

    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout); // timeout 不为null
        if (immediate) {
            let callNow = !timeout; // 第一次会立即执行,以后只有事件执行后才会再次触发
            timeout = setTimeout(function () {
                timeout = null;
            }, wait)
            if (callNow) {
                func.apply(context, args)
            }
        }
        else {
            timeout = setTimeout(function () {
                func.apply(context, args)
            }, wait);
        }
    }
}
```

## 二、区别

相同点:

- 都可以通过使用 `setTimeout` 实现
- 目的都是,降低回调执行频率。节省计算资源

不同点:

- 函数防抖,在一段连续操作结束后,处理回调,利用`clearTimeout `和 `setTimeout`实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
- 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次

例如,都设置时间频率为500ms,在2秒时间内,频繁触发函数,节流,每隔 500ms 就执行一次。防抖,则不管调动多少次方法,在2s后,只会执行一次

如下图所示:

 ![](https://static.vue-js.com/a2c81b50-8787-11eb-ab90-d9ae814b240d.png)


## 三、应用场景

防抖在连续的事件,只需触发一次回调的场景有:

- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小`resize`。只需窗口调整完成后,计算窗口大小。防止重复渲染。

节流在间隔一段时间执行一次回调的场景有:

- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能

================================================
FILE: docs/JavaScript/event_Model.md
================================================
# 面试官:说说JavaScript中的事件模型

![](https://static.vue-js.com/32a182f0-74cf-11eb-85f6-6fac77c0c9b3.png)

## 一、事件与事件流

`javascript`中的事件,可以理解就是在`HTML`文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件、鼠标事件、自定义事件等

由于`DOM`是一个树结构,如果在父子节点绑定事件时候,当触发子节点的时候,就存在一个顺序问题,这就涉及到了事件流的概念

事件流都会经历三个阶段:

- 事件捕获阶段(capture phase)
- 处于目标阶段(target phase)
- 事件冒泡阶段(bubbling phase)

 ![](https://static.vue-js.com/3e9a6450-74cf-11eb-85f6-6fac77c0c9b3.png)

事件冒泡是一种从下往上的传播方式,由最具体的元素(触发节点)然后逐渐向上传播到最不具体的那个节点,也就是`DOM`中最高层的父节点

```html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Event Bubbling</title>
    </head>
    <body>
        <button id="clickMe">Click Me</button>
    </body>
</html>
```

然后,我们给`button`和它的父元素,加入点击事件

```js
var button = document.getElementById('clickMe');

button.onclick = function() {
  console.log('1.Button');
};
document.body.onclick = function() {
  console.log('2.body');
};
document.onclick = function() {
  console.log('3.document');
};
window.onclick = function() {
  console.log('4.window');
};
```

点击按钮,输出如下

```js
1.button
2.body
3.document
4.window
```

点击事件首先在`button`元素上发生,然后逐级向上传播

事件捕获与事件冒泡相反,事件最开始由不太具体的节点最早接受事件, 而最具体的节点(触发节点)最后接受事件



## 二、事件模型

事件模型可以分为三种:

- 原始事件模型(DOM0级)
- 标准事件模型(DOM2级)
- IE事件模型(基本不用)



### 原始事件模型

事件绑定监听函数比较简单, 有两种方式:

- HTML代码中直接绑定

```js
<input type="button" onclick="fun()">
```

- 通过`JS`代码绑定

```js
var btn = document.getElementById('.btn');
btn.onclick = fun;
```

#### 特性

- 绑定速度快

`DOM0`级事件具有很好的跨浏览器优势,会以最快的速度绑定,但由于绑定速度太快,可能页面还未完全加载出来,以至于事件可能无法正常运行

- 只支持冒泡,不支持捕获

- 同一个类型的事件只能绑定一次

```js
<input type="button" id="btn" onclick="fun1()">

var btn = document.getElementById('.btn');
btn.onclick = fun2;
```

如上,当希望为同一个元素绑定多个同类型事件的时候(上面的这个`btn`元素绑定2个点击事件),是不被允许的,后绑定的事件会覆盖之前的事件

删除 `DOM0` 级事件处理程序只要将对应事件属性置为`null`即可

```js
btn.onclick = null;
```





### 标准事件模型

在该事件模型中,一次事件共有三个过程:

- 事件捕获阶段:事件从`document`一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行
- 事件处理阶段:事件到达目标元素, 触发目标元素的监听函数
- 事件冒泡阶段:事件从目标元素冒泡到`document`, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行

事件绑定监听函数的方式如下:

```
addEventListener(eventType, handler, useCapture)
```

事件移除监听函数的方式如下:

```
removeEventListener(eventType, handler, useCapture)
```

参数如下:

- `eventType`指定事件类型(不要加on)
- `handler`是事件处理函数
- `useCapture`是一个`boolean`用于指定是否在捕获阶段进行处理,一般设置为`false`与IE浏览器保持一致

举个例子:

```js
var btn = document.getElementById('.btn');
btn.addEventListener(‘click’, showMessage, false);
btn.removeEventListener(‘click’, showMessage, false);
```

#### 特性

- 可以在一个`DOM`元素上绑定多个事件处理器,各自并不会冲突

```js
btn.addEventListener(‘click’, showMessage1, false);
btn.addEventListener(‘click’, showMessage2, false);
btn.addEventListener(‘click’, showMessage3, false);
```

- 执行时机

当第三个参数(`useCapture`)设置为`true`就在捕获过程中执行,反之在冒泡过程中执行处理函数

下面举个例子:

```js
<div id='div'>
    <p id='p'>
        <span id='span'>Click Me!</span>
    </p >
</div>
```

设置点击事件

```js
var div = document.getElementById('div');
var p = document.getElementById('p');

function onClickFn (event) {
    var tagName = event.currentTarget.tagName;
    var phase = event.eventPhase;
    console.log(tagName, phase);
}

div.addEventListener('click', onClickFn, false);
p.addEventListener('click', onClickFn, false);
```

上述使用了`eventPhase`,返回一个代表当前执行阶段的整数值。1为捕获阶段、2为事件对象触发阶段、3为冒泡阶段

点击`Click Me!`,输出如下

```js
P 3
DIV 3
```

可以看到,`p`和`div`都是在冒泡阶段响应了事件,由于冒泡的特性,裹在里层的`p`率先做出响应

如果把第三个参数都改为`true`

```js
div.addEventListener('click', onClickFn, true);
p.addEventListener('click', onClickFn, true);
```

输出如下

```js
DIV 1
P 1
```

两者都是在捕获阶段响应事件,所以`div`比`p`标签先做出响应



### IE事件模型

IE事件模型共有两个过程:

- 事件处理阶段:事件到达目标元素, 触发目标元素的监听函数。
- 事件冒泡阶段:事件从目标元素冒泡到`document`, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行

事件绑定监听函数的方式如下:

```
attachEvent(eventType, handler)
```

事件移除监听函数的方式如下:

```
detachEvent(eventType, handler)
```

举个例子:

```js
var btn = document.getElementById('.btn');
btn.attachEvent(‘onclick’, showMessage);
btn.detachEvent(‘onclick’, showMessage);
```

================================================
FILE: docs/JavaScript/event_agent.md
================================================
# 面试官:解释下什么是事件代理?应用场景?

![](https://static.vue-js.com/a33f0ab0-797e-11eb-ab90-d9ae814b240d.png)

## 一、是什么

事件代理,俗地来讲,就是把一个元素响应事件(`click`、`keydown`......)的函数委托到另一个元素

前面讲到,事件流的都会经过三个阶段: 捕获阶段 -> 目标阶段 -> 冒泡阶段,而事件委托就是在冒泡阶段完成

事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素

当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数

下面举个例子:

比如一个宿舍的同学同时快递到了,一种笨方法就是他们一个个去领取

较优方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个同学

在这里,取快递就是一个事件,每个同学指的是需要响应事件的 `DOM `元素,而出去统一领取快递的宿舍长就是代理的元素

所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个



## 二、应用场景

如果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件

```js
<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
```

如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的

```js
// 获取目标元素
const lis = document.getElementsByTagName("li")
// 循环遍历绑定事件
for (let i = 0; i < lis.length; i++) {
    lis[i].onclick = function(e){
        console.log(e.target.innerHTML)
    }
}
```

这时候就可以事件委托,把点击事件绑定在父级元素`ul`上面,然后执行事件的时候再去匹配目标元素

```js
// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
    // 兼容性处理
    var event = e || window.event;
    var target = event.target || event.srcElement;
    // 判断是否匹配目标元素
    if (target.nodeName.toLocaleLowerCase === 'li') {
        console.log('the content is: ', target.innerHTML);
    }
});
```

还有一种场景是上述列表项并不多,我们给每个列表项都绑定了事件

但是如果用户能够随时动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的

举个例子:

下面`html`结构中,点击`input`可以动态添加元素

```html
<input type="button" name="" id="btn" value="添加" />
<ul id="ul1">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    <li>item 4</li>
</ul>
```

使用事件委托

```js
const oBtn = document.getElementById("btn");
const oUl = document.getElementById("ul1");
const num = 4;

//事件委托,添加的子元素也有事件
oUl.onclick = function (ev) {
    ev = ev || window.event;
    const target = ev.target || ev.srcElement;
    if (target.nodeName.toLowerCase() == 'li') {
        console.log('the content is: ', target.innerHTML);
    }

};

//添加新节点
oBtn.onclick = function () {
    num++;
    const oLi = document.createElement('li');
    oLi.innerHTML = `item ${num}`;
    oUl.appendChild(oLi);
};
```

可以看到,使用事件委托,在动态绑定事件的情况下是可以减少很多重复工作的



## 三、总结

适合事件委托的事件有:`click`,`mousedown`,`mouseup`,`keydown`,`keyup`,`keypress`

从上面应用场景中,我们就可以看到使用事件委托存在两大优点:

- 减少整个页面所需的内存,提升整体性能
- 动态绑定,减少重复工作

但是使用事件委托也是存在局限性:

- `focus`、`blur `这些事件没有事件冒泡机制,所以无法进行委托绑定事件

- `mousemove`、`mouseout `这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的

如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件

================================================
FILE: docs/JavaScript/event_loop.md
================================================
# 面试官:说说你对事件循环的理解

 ![](https://static.vue-js.com/50f062d0-7cb8-11eb-ab90-d9ae814b240d.png)

## 一、是什么

首先,`JavaScript `是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环

在`JavaScript`中,所有的任务都可以分为

- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行

- 异步任务:异步执行的任务,比如`ajax`网络请求,`setTimeout `定时函数等

同步任务与异步任务的运行流程图如下:

 ![](https://static.vue-js.com/61efbc20-7cb8-11eb-85f6-6fac77c0c9b3.png)

从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环



## 二、宏任务与微任务

如果将任务划分为同步任务和异步任务并不是那么的准确,举个例子:

```js
console.log(1)

setTimeout(()=>{
    console.log(2)
}, 0)

new Promise((resolve, reject)=>{
    console.log('new Promise')
    resolve()
}).then(()=>{
    console.log('then')
})

console.log(3)
```

如果按照上面流程图来分析代码,我们会得到下面的执行步骤:

- `console.log(1) `,同步任务,主线程中执行
- `setTimeout()` ,异步任务,放到 `Event Table`,0 毫秒后`console.log(2) `回调推入 `Event Queue` 中
- `new Promise` ,同步任务,主线程直接执行
- `.then` ,异步任务,放到 `Event Table`
- `console.log(3)`,同步任务,主线程执行

所以按照分析,它的结果应该是 `1` => `'new Promise'` => `3` => `2` => `'then'`

但是实际结果是:`1`=>`'new Promise'`=> `3` => `'then'` => `2`

出现分歧的原因在于异步任务执行顺序,事件队列其实是一个“先进先出”的数据结构,排在前面的事件会优先被主线程读取

例子中 `setTimeout`回调事件是先进入队列中的,按理说应该先于 `.then` 中的执行,但是结果却偏偏相反

原因在于异步任务还可以细分为微任务与宏任务

### 微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

常见的微任务有:

- Promise.then

- MutaionObserver

- Object.observe(已废弃;Proxy 对象替代)

- process.nextTick(Node.js)

  

### 宏任务

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

- script (可以理解为外层同步代码) 
- setTimeout/setInterval 
- UI rendering/UI事件 
- postMessage、MessageChannel 
- setImmediate、I/O(Node.js)



这时候,事件循环,宏任务,微任务的关系如图所示

 ![](https://static.vue-js.com/6e80e5e0-7cb8-11eb-85f6-6fac77c0c9b3.png)

按照这个流程,它的执行机制是:

- 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
- 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完



回到上面的题目

```js
console.log(1)
setTimeout(()=>{
    console.log(2)
}, 0)
new Promise((resolve, reject)=>{
    console.log('new Promise')
    resolve()
}).then(()=>{
    console.log('then')
})
console.log(3)
```

流程如下

```js
// 遇到 console.log(1) ,直接打印 1
// 遇到定时器,属于新的宏任务,留着后面执行
// 遇到 new Promise,这个是直接执行的,打印 'new Promise'
// .then 属于微任务,放入微任务队列,后面再执行
// 遇到 console.log(3) 直接打印 3
// 好了本轮宏任务执行完毕,现在去微任务列表查看是否有微任务,发现 .then 的回调,执行它,打印 'then'
// 当一次宏任务执行完,再去执行新的宏任务,这里就剩一个定时器的宏任务了,执行它,打印 2
```



## 三、async与await

`async` 是异步的意思,`await `则可以理解为 `async wait`。所以可以理解` async `就是用来声明一个异步方法,而 `await `是用来等待异步方法执行

### async

`async`函数返回一个`promise`对象,下面两种方法是等效的

```js
function f() {
    return Promise.resolve('TEST');
}

// asyncF is equivalent to f!
async function asyncF() {
    return 'TEST';
}
```

### await

正常情况下,`await`命令后面是一个 `Promise `对象,返回该对象的结果。如果不是 `Promise `对象,就直接返回对应的值

```js
async function f(){
    // 等同于
    // return 123
    return await 123
}
f().then(v => console.log(v)) // 123
```

不管`await`后面跟着的是什么,`await`都会阻塞后面的代码

```js
async function fn1 (){
    console.log(1)
    await fn2()
    console.log(2) // 阻塞
}

async function fn2 (){
    console.log('fn2')
}

fn1()
console.log(3)
```

上面的例子中,`await` 会阻塞下面的代码(即加入微任务队列),先执行 `async `外面的同步代码,同步代码执行完,再回到 `async` 函数中,再执行之前阻塞的代码

所以上述输出结果为:`1`,`fn2`,`3`,`2`



## 四、流程分析

通过对上面的了解,我们对`JavaScript`对各种场景的执行顺序有了大致的了解

这里直接上代码:

```js
async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('settimeout')
})
async1()
new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})
console.log('script end')
```

分析过程:

1. 执行整段代码,遇到 `console.log('script start')` 直接打印结果,输出 `script start`
2. 遇到定时器了,它是宏任务,先放着不执行
3. 遇到 `async1()`,执行 `async1` 函数,先打印 `async1 start`,下面遇到` await `怎么办?先执行 `async2`,打印 `async2`,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码
4. 跳到 `new Promise` 这里,直接执行,打印 `promise1`,下面遇到 `.then()`,它是微任务,放到微任务列表等待执行
5. 最后一行直接打印 `script end`,现在同步代码执行完了,开始执行微任务,即 `await `下面的代码,打印 `async1 end`
6. 继续执行下一个微任务,即执行 `then` 的回调,打印 `promise2`
7. 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印 `settimeout`

所以最后的结果是:`script start`、`async1 start`、`async2`、`promise1`、`script end`、`async1 end`、`promise2`、`settimeout`



================================================
FILE: docs/JavaScript/function_cache.md
================================================
# 面试官:Javascript中如何实现函数缓存?函数缓存有哪些应用场景?

 ![](https://static.vue-js.com/2ae9dda0-85fa-11eb-ab90-d9ae814b240d.png)



## 一、是什么

函数缓存,就是将函数运算过的结果进行缓存

本质上就是用空间(缓存存储)换时间(计算过程)

常用于缓存数据计算结果和缓存对象

```js
const add = (a,b) => a+b;
const calc = memoize(add); // 函数缓存
calc(10,20);// 30
calc(10,20);// 30 缓存
```

缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理



## 二、如何实现

实现函数缓存主要依靠闭包、柯里化、高阶函数,这里再简单复习下:

### 闭包

闭包可以理解成,函数 + 函数体内可访问的变量总和

```js
(function() {
    var a = 1;
    function add() {
        const b = 2
        let sum = b + a
        console.log(sum); // 3
    }
    add()
})()
```

`add `函数本身,以及其内部可访问的变量,即 `a = 1 `,这两个组合在⼀起就形成了闭包



### 柯里化

把接受多个参数的函数转换成接受一个单一参数的函数

```js
// 非函数柯里化
var add = function (x,y) {
    return x+y;
}
add(3,4) //7

// 函数柯里化
var add2 = function (x) {
    //**返回函数**
    return function (y) {
        return x+y;
    }
}
add2(3)(4) //7
```

将一个二元函数拆分成两个一元函数



### 高阶函数

通过接收其他函数作为参数或返回其他函数的函数

```js
function foo(){
  var a = 2;

  function bar() {
    console.log(a);
  }
  return bar;
}
var baz = foo();
baz();//2
```

函数 `foo` 如何返回另一个函数 `bar`,`baz` 现在持有对 `foo` 中定义的`bar` 函数的引用。由于闭包特性,`a`的值能够得到



下面再看看如何实现函数缓存,实现原理也很简单,把参数和对应的结果数据存在一个对象中,调用时判断参数对应的数据是否存在,存在就返回对应的结果数据,否则就返回计算结果

如下所示

```js
const memoize = function (func, content) {
  let cache = Object.create(null)
  content = content || this
  return (...key) => {
    if (!cache[key]) {
      cache[key] = func.apply(content, key)
    }
    return cache[key]
  }
}
```

调用方式也很简单

```js
const calc = memoize(add);
const num1 = calc(100,200)
const num2 = calc(100,200) // 缓存得到的结果
```

过程分析:

- 在当前函数作用域定义了一个空对象,用于缓存运行结果
- 运用柯里化返回一个函数,返回的函数由于闭包特性,可以访问到`cache`
- 然后判断输入参数是不是在`cache`的中。如果已经存在,直接返回`cache`的内容,如果没有存在,使用函数`func`对输入参数求值,然后把结果存储在`cache`中



## 三、应用场景

虽然使用缓存效率是非常高的,但并不是所有场景都适用,因此千万不要极端的将所有函数都添加缓存

以下几种情况下,适合使用缓存:

- 对于昂贵的函数调用,执行复杂计算的函数
- 对于具有有限且高度重复输入范围的函数
- 对于具有重复输入值的递归函数
- 对于纯函数,即每次使用特定输入调用时返回相同输出的函数



## 参考文献

- https://zhuanlan.zhihu.com/p/112505577

================================================
FILE: docs/JavaScript/functional_programming.md
================================================
# 面试官:说说你对函数式编程的理解?优缺点?

 ![](https://static.vue-js.com/ec0f6e80-8534-11eb-85f6-6fac77c0c9b3.png)

## 一、是什么

函数式编程是一种"编程范式"(programming paradigm),一种编写程序的方法论

主要的编程范式有三种:命令式编程,声明式编程和函数式编程

相比命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而非设计一个复杂的执行过程

举个例子,将数组每个元素进行平方操作,命令式编程与函数式编程如下

```js
// 命令式编程
var array = [0, 1, 2, 3]
for(let i = 0; i < array.length; i++) {
    array[i] = Math.pow(array[i], 2)
}

// 函数式方式
[0, 1, 2, 3].map(num => Math.pow(num, 2))
```

简单来讲,就是要把过程逻辑写成函数,定义好输入参数,只关心它的输出结果

即是一种描述集合和集合之间的转换关系,输入通过函数都会返回有且只有一个输出值

 ![](https://static.vue-js.com/f9f83900-8534-11eb-85f6-6fac77c0c9b3.png)

可以看到,函数实际上是一个关系,或者说是一种映射,而这种映射关系是可以组合的,一旦我们知道一个函数的输出类型可以匹配另一个函数的输入,那他们就可以进行组合


## 二、概念

### 纯函数

函数式编程旨在尽可能的提高代码的无状态性和不变性。要做到这一点,就要学会使用无副作用的函数,也就是纯函数

纯函数是对给定的输入返还相同输出的函数,并且要求你所有的数据都是不可变的,即纯函数=无状态+数据不可变

 ![](https://static.vue-js.com/04f50720-8535-11eb-ab90-d9ae814b240d.png)

举一个简单的例子

```js
let double = value=>value*2;
```

特性:

- 函数内部传入指定的值,就会返回确定唯一的值
- 不会造成超出作用域的变化,例如修改全局变量或引用传递的参数

优势:

- 使用纯函数,我们可以产生可测试的代码

```js
test('double(2) 等于 4', () => {
  expect(double(2)).toBe(4);
})
```

- 不依赖外部环境计算,不会产生副作用,提高函数的复用性

- 可读性更强 ,函数不管是否是纯函数  都会有一个语义化的名称,更便于阅读

- 可以组装成复杂任务的可能性。符合模块化概念及单一职责原则



### 高阶函数

在我们的编程世界中,我们需要处理的其实也只有“数据”和“关系”,而关系就是函数

编程工作也就是在找一种映射关系,一旦关系找到了,问题就解决了,剩下的事情,就是让数据流过这种关系,然后转换成另一个数据,如下图所示

 ![](https://static.vue-js.com/104af1c0-8535-11eb-ab90-d9ae814b240d.png)

在这里,就是高阶函数的作用。高级函数,就是以函数作为输入或者输出的函数被称为高阶函数

通过高阶函数抽象过程,注重结果,如下面例子

```js
const forEach = function(arr,fn){
    for(let i=0;i<arr.length;i++){
        fn(arr[i]);
    }
}
let arr = [1,2,3];
forEach(arr,(item)=>{
    console.log(item);
})
```

上面通过高阶函数 `forEach`来抽象循环如何做的逻辑,直接关注做了什么

高阶函数存在缓存的特性,主要是利用闭包作用

```js
const once = (fn)=>{
    let done = false;
    return function(){
        if(!done){
            fn.apply(this,fn);
        }else{
            console.log("该函数已经执行");
        }
        done = true;
    }
}
```

### 柯里化

柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程

一个二元函数如下:

```js
let fn = (x,y)=>x+y;
```

转化成柯里化函数如下:

```js
const curry = function(fn){
    return function(x){
        return function(y){
            return fn(x,y);
        }
    }
}
let myfn = curry(fn);
console.log( myfn(1)(2) );
```

上面的`curry`函数只能处理二元情况,下面再来实现一个实现多参数的情况

```js
// 多参数柯里化;
const curry = function(fn){
    return function curriedFn(...args){
        if(args.length<fn.length){
            return function(){
                return curriedFn(...args.concat([...arguments]));
            }
        }
        return fn(...args);
    }
}
const fn = (x,y,z,a)=>x+y+z+a;
const myfn = curry(fn);
console.log(myfn(1)(2)(3)(1));
```

关于柯里化函数的意义如下:

- 让纯函数更纯,每次接受一个参数,松散解耦
- 惰性执行



### 组合与管道

组合函数,目的是将多个函数组合成一个函数

举个简单的例子:

```js
function afn(a){
    return a*2;
}
function bfn(b){
    return b*3;
}
const compose = (a,b)=>c=>a(b(c));
let myfn =  compose(afn,bfn);
console.log( myfn(2));
```

可以看到`compose`实现一个简单的功能:形成了一个新的函数,而这个函数就是一条从 `bfn -> afn` 的流水线

下面再来看看如何实现一个多函数组合:

```js
const compose = (...fns)=>val=>fns.reverse().reduce((acc,fn)=>fn(acc),val);
```

`compose`执行是从右到左的。而管道函数,执行顺序是从左到右执行的

```js
const pipe = (...fns)=>val=>fns.reduce((acc,fn)=>fn(acc),val);
```

组合函数与管道函数的意义在于:可以把很多小函数组合起来完成更复杂的逻辑

## 三、优缺点

#### 优点

- 更好的管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少这些未知、优化代码、减少出错情况

- 更简单的复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响

- 更优雅的组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性

- 隐性好处。减少代码量,提高维护性

#### 缺点:

- 性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销

- 资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式

- 递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作


## 参考文献

- https://zhuanlan.zhihu.com/p/81302150

================================================
FILE: docs/JavaScript/inherit.md
================================================
# 面试官:Javascript如何实现继承?

![](https://static.vue-js.com/5d9c4450-72a3-11eb-85f6-6fac77c0c9b3.png)

## 一、是什么

继承(inheritance)是面向对象软件技术当中的一个概念。

如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”

- 继承的优点

继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码

在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能

虽然`JavaScript`并不是真正的面向对象语言,但它天生的灵活性,使应用场景更加丰富

关于继承,我们举个形象的例子:

定义一个类(Class)叫汽车,汽车的属性包括颜色、轮胎、品牌、速度、排气量等

```js
class Car{
    constructor(color,speed){
        this.color = color
        this.speed = speed
        // ...
    }
}
```

由汽车这个类可以派生出“轿车”和“货车”两个类,在汽车的基础属性上,为轿车添加一个后备厢、给货车添加一个大货箱

```js
// 货车
class Truck extends Car{
    constructor(color,speed){
        super(color,speed)
        this.Container = true // 货箱
    }
}
```

这样轿车和货车就是不一样的,但是二者都属于汽车这个类,汽车、轿车继承了汽车的属性,而不需要再次在“轿车”中定义汽车已经有的属性

在“轿车”继承“汽车”的同时,也可以重新定义汽车的某些属性,并重写或覆盖某些属性和方法,使其获得与“汽车”这个父类不同的属性和方法

```js
class Truck extends Car{
    constructor(color,speed){
        super(color,speed)
        this.color = "black" //覆盖
        this.Container = true // 货箱
    }
}
```

从这个例子中就能详细说明汽车、轿车以及卡车之间的继承关系



## 二、实现方式

下面给出`JavaScripy`常见的继承方式:

- 原型链继承

- 构造函数继承(借助 call)
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承



### 原型链继承

原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针

举个例子

```js
 function Parent() {
    this.name = 'parent1';
    this.play = [1, 2, 3]
  }
  function Child() {
    this.type = 'child2';
  }
  Child1.prototype = new Parent();
  console.log(new Child())
```

上面代码看似没问题,实际存在潜在问题

```js
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]
```

改变`s1`的`play`属性,会发现`s2`也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的



### 构造函数继承

借助 `call `调用`Parent`函数

```js
function Parent(){
    this.name = 'parent1';
}

Parent.prototype.getName = function () {
    return this.name;
}

function Child(){
    Parent1.call(this);
    this.type = 'child'
}

let child = new Child();
console.log(child);  // 没问题
console.log(child.getName());  // 会报错
```

可以看到,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法

相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法



### 组合继承

前面我们讲到两种继承方式,各有优缺点。组合继承则将前两种方式继承起来

```js
function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
}

Parent3.prototype.getName = function () {
    return this.name;
}
function Child3() {
    // 第二次调用 Parent3()
    Parent3.call(this);
    this.type = 'child3';
}

// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);  // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'
```

这种方式看起来就没什么问题,方式一和方式二的问题都解决了,但是从上面代码我们也可以看到` Parent3` 执行了两次,造成了多构造一次的性能开销



### 原型式继承

这里主要借助`Object.create`方法实现普通对象的继承

同样举个例子

```js
let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };

  let person4 = Object.create(parent4);
  person4.name = "tom";
  person4.friends.push("jerry");

  let person5 = Object.create(parent4);
  person5.friends.push("lucy");

  console.log(person4.name); // tom
  console.log(person4.name === person4.getName()); // true
  console.log(person5.name); // parent4
  console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]
  console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]
```

这种继承方式的缺点也很明显,因为`Object.create `方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能



### 寄生式继承

寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法

```js
let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
        return this.friends;
    };
    return clone;
}

let person5 = clone(parent5);

console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]
```

其优缺点也很明显,跟上面讲的原型式继承一样



### 寄生组合式继承

寄生组合式继承,借助解决普通对象的继承问题的` Object.create` 方法,在前面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式

```js
function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
}

function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
    return this.name;
}
function Child6() {
    Parent6.call(this);
    this.friends = 'child5';
}

clone(Parent6, Child6);

Child6.prototype.getFriends = function () {
    return this.friends;
}

let person6 = new Child6();
console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child5
```

可以看到 person6 打印出来的结果,属性都得到了继承,方法也没问题



文章一开头,我们是使用`ES6` 中的`extends `关键字直接实现 `JavaScript `的继承

```js
class Person {
  constructor(name) {
    this.name = name
  }
  // 原型方法
  // 即 Person.prototype.getName = function() { }
  // 下面可以简写为 getName() {...}
  getName = function () {
    console.log('Person:', this.name)
  }
}
class Gamer extends Person {
  constructor(name, age) {
    // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
    super(name)
    this.age = age
  }
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法
```

利用`babel`工具进行转换,我们会发现`extends`实际采用的也是寄生组合继承方式,因此也证明了这种方式是较优的解决继承的方式



## 三、总结

下面以一张图作为总结:

 ![](https://static.vue-js.com/0df74700-731c-11eb-ab90-d9ae814b240d.png)

通过`Object.create` 来划分不同的继承方式,最后的寄生式组合继承方式是通过组合继承改造之后的最优继承方式,而 `extends` 的语法糖和寄生组合继承的方式基本类似

## 相关链接
https://zh.wikipedia.org/wiki/%E7%BB%A7%E6%89%BF

================================================
FILE: docs/JavaScript/js_data_structure.md
================================================
# 面试官:说说你了解的js数据结构?

## 什么是数据结构?
数据结构是计算机存储、组织数据的方式。
数据结构意味着接口或封装:一个数据结构可被视为两个函数之间的接口,或者是由数据类型联合组成的存储内容的访问方法封装。

我们每天的编码中都会用到数据结构
数组是最简单的内存数据结构
下面是常见的数据结构:
1. 数组(Array)
2. 栈(Stack)
3. 队列(Queue)
4. 链表(Linked List)
5. 字典
6. 散列表(Hash table)
7. 树(Tree)
8. 图(Graph)
9. 堆(Heap)



## 数组(Array)
数组是最最基本的数据结构,很多语言都内置支持数组。
数组是使用一块连续的内存空间保存数据,保存的数据的个数在分配内存的时候就是确定的。

在日常生活中,人们经常使用列表:待办事项列表、购物清单等。

而计算机程序也在使用列表,在下面的条件下,选择列表作为数据结构就显得尤为有用:
数据结构较为简单
不需要在一个长序列中查找元素,或者对其进行排序
反之,如果数据结构非常复杂,列表的作用就没有那么大了。


## 栈(Stack)
栈是一种遵循后进先出(LIFO)原则的有序集合
在栈里,新元素都接近栈顶,旧元素都接近栈底。
每次加入新的元素和拿走元素都在顶部操作
![](https://upload-images.jianshu.io/upload_images/13253432-ddcb884374470d2c?imageMogr2/auto-orient/strip|imageView2/2/format/webp)


## 队列(Queue)
队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项
队列在尾部添加新元素,并从顶部移除元素
最新添加的元素必须排在队列的末尾
![](https://upload-images.jianshu.io/upload_images/13253432-55ad7c7db40d3796?imageMogr2/auto-orient/strip|imageView2/2/format/webp)


## 链表(Linked List)
链表也是一种列表,已经设计了数组,为什么还需要链表呢?
JavaScript中数组的主要问题时,它们被实现成了对象,
与其他语言(比如C++和Java)的数组相对,效率很低。
如果你发现数组在实际使用时很慢,就可以考虑使用链表来代替它。

使用条件:
链表几乎可以用在任何可以使用一维数组的情况中。
如果需要随机访问,数组仍然是更好的选择。
![](https://raw.githubusercontent.com/zoro-web/blog/master/img/lian.jpg)

## 字典
字典是一种以键-值对存储数据的数据结构,js中的Object类就是以字典的形式设计的。JavaScript可以通过实现字典类,让这种字典类型的对象使用起来更加简单,字典可以实现对象拥有的常见功能,并相应拓展自己想要的功能,而对象在JavaScript编写中随处可见,所以字典的作用也异常明显了。


## 散列表
也称为哈希表,特点是在散列表上插入、删除和取用数据都非常快。
为什么要设计这种数据结构呢?
用数组或链表存储数据,如果想要找到其中一个数据,需要从头进行遍历,因为不知道这个数据存储到了数组的哪个位置。

散列表在JavaScript中可以基础数组去进行设计。
数组的长度是预先设定的,所有元素根据和该元素对应的键,保存在数组的特定位置,这里的键和对象的键是类型的概念。
使用散列表存储数组时,通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度。

即使使用一个高效的散列函数,依然存在将两个键映射为同一个值得可能,这种现象叫做碰撞。常见碰撞的处理方法有:开链法和线性探测法(具体概念有兴趣的可以网上自信了解)
使用条件:
可以用于数据的插入、删除和取用,不适用于查找数据
![](https://raw.githubusercontent.com/zoro-web/blog/master/img/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20170820211406.png)




================================================
FILE: docs/JavaScript/loss_accuracy.md
================================================
# 面试官:说说 Javascript 数字精度丢失的问题,如何解决?

 ![](https://static.vue-js.com/09646a10-86f4-11eb-85f6-6fac77c0c9b3.png)

## 一、场景复现

一个经典的面试题

```js
0.1 + 0.2 === 0.3 // false
```
为什么是`false`呢?

先看下面这个比喻

比如一个数 1÷3=0.33333333...... 

3会一直无限循环,数学可以表示,但是计算机要存储,方便下次取出来再使用,但0.333333...... 这个数无限循环,再大的内存它也存不下,所以不能存储一个相对于数学来说的值,只能存储一个近似值,当计算机存储后再取出时就会出现精度丢失问题

## 二、浮点数

“浮点数”是一种表示数字的标准,整数也可以用浮点数的格式来存储

我们也可以理解成,浮点数就是小数

在`JavaScript`中,现在主流的数值类型是`Number`,而`Number`采用的是`IEEE754`规范中64位双精度浮点数编码

这样的存储结构优点是可以归一化处理整数和小数,节省存储空间

对于一个整数,可以很轻易转化成十进制或者二进制。但是对于一个浮点数来说,因为小数点的存在,小数点的位置不是固定的。解决思路就是使用科学计数法,这样小数点位置就固定了

而计算机只能用二进制(0或1)表示,二进制转换为科学记数法的公式如下:

 ![](https://static.vue-js.com/1b4b1620-86f4-11eb-ab90-d9ae814b240d.png)

其中,`a`的值为0或者1,e为小数点移动的位置

举个例子:

27.0转化成二进制为11011.0 ,科学计数法表示为:

 ![](https://static.vue-js.com/37007090-86f4-11eb-ab90-d9ae814b240d.png)

前面讲到,`javaScript`存储方式是双精度浮点数,其长度为8个字节,即64位比特

64位比特又可分为三个部分:

- 符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
- 指数位E:中间的 11 位存储指数(exponent),用来表示次方数,可以为正负数。在双精度浮点数中,指数的固定偏移量为1023
- 尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零

如下图所示:

 ![](https://static.vue-js.com/430d0100-86f4-11eb-85f6-6fac77c0c9b3.png)

举个例子:

27.5 转换为二进制11011.1

11011.1转换为科学记数法 ![[公式]](https://www.zhihu.com/equation?tex=1.10111%2A2%5E4)

符号位为1(正数),指数位为4+,1023+4,即1027

因为它是十进制的需要转换为二进制,即 `10000000011`,小数部分为`10111`,补够52位即: 1011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`

所以27.5存储为计算机的二进制标准形式(符号位+指数位+小数部分 (阶数)),既下面所示

0+10000000011+011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`


## 二、问题分析

再回到问题上

```js
0.1 + 0.2 === 0.3 // false
```

通过上面的学习,我们知道,在`javascript`语言中,0.1 和 0.2 都转化成二进制后再进行运算

```js
// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

// 转成十进制正好是 0.30000000000000004
```

所以输出`false`

再来一个问题,那么为什么`x=0.1`得到`0.1`?

主要是存储二进制时小数点的偏移量最大为52位,最多可以表达的位数是`2^53=9007199254740992`,对应科学计数尾数是 `9.007199254740992`,这也是 JS 最多能表示的精度

它的长度是 16,所以可以使用 `toPrecision(16)` 来做精度运算,超过的精度会自动做凑整处理

```js
.10000000000000000555.toPrecision(16)
// 返回 0.1000000000000000,去掉末尾的零后正好为 0.1
```

但看到的 `0.1` 实际上并不是 `0.1`。不信你可用更高的精度试试:

```js
0.1.toPrecision(21) = 0.100000000000000005551
```

如果整数大于 `9007199254740992` 会出现什么情况呢?

由于指数位最大值是1023,所以最大可以表示的整数是 `2^1024 - 1`,这就是能表示的最大整数。但你并不能这样计算这个数字,因为从 `2^1024` 开始就变成了 `Infinity`

```
> Math.pow(2, 1023)
8.98846567431158e+307

> Math.pow(2, 1024)
Infinity
```

那么对于 `(2^53, 2^63)` 之间的数会出现什么情况呢?

- `(2^53, 2^54)` 之间的数会两个选一个,只能精确表示偶数
- `(2^54, 2^55)` 之间的数会四个选一个,只能精确表示4个倍数
- ... 依次跳过更多2的倍数

要想解决大数的问题你可以引用第三方库 `bignumber.js`,原理是把所有数字当作字符串,重新实现了计算逻辑,缺点是性能比原生差很多

### 小结

计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法

因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差


## 三、解决方案

理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果

当你拿到 `1.4000000000000001` 这样的数据要展示时,建议使用 `toPrecision` 凑整并 `parseFloat` 转成数字后再显示,如下:

```
parseFloat(1.4000000000000001.toPrecision(12)) === 1.4  // True
```

封装成方法就是:

```js
function strip(num, precision = 12) {
  return +parseFloat(num.toPrecision(precision));
}
```

对于运算类操作,如 `+-*/`,就不能使用 `toPrecision` 了。正确的做法是把小数转成整数后再运算。以加法为例:

```js
/**
 * 精确加法
 */
function add(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length;
  const num2Digits = (num2.toString().split('.')[1] || '').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}
```

最后还可以使用第三方库,如`Math.js`、`BigDecimal.js`


## 参考文献

- https://zhuanlan.zhihu.com/p/100353781
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt

================================================
FILE: docs/JavaScript/memory_leak.md
================================================
# 面试官:说说 JavaScript 中内存泄漏的几种情况?

  ![](https://static.vue-js.com/19f76b30-824d-11eb-ab90-d9ae814b240d.png)

## 一、是什么

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存

并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费

程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存

对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃

 ![](https://static.vue-js.com/56d4bd90-821c-11eb-ab90-d9ae814b240d.png)

在`C`语言中,因为是手动管理内存,内存泄露是经常出现的事情。

```clang
char * buffer;
buffer = (char*) malloc(42);

// Do something with buffer

free(buffer);
```

上面是 C 语言代码,`malloc`方法用来申请内存,使用完毕之后,必须自己用`free`方法释放内存。

这很麻烦,所以大多数语言提供自动内存管理,减轻程序员的负担,这被称为"垃圾回收机制"


## 二、垃圾回收机制

Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

通常情况下有两种实现方式:

- 标记清除
- 引用计数



### 标记清除

`JavaScript`最常用的垃圾收回机制

当变量进入执行环境是,就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境“

垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉

在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了

随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存

举个例子:

```js
var m = 0,n = 19 // 把 m,n,add() 标记为进入环境。
add(m, n) // 把 a, b, c标记为进入环境。
console.log(n) // a,b,c标记为离开环境,等待垃圾回收。
function add(a, b) {
  a++
  var c = a + b
  return c
}
```



### 引用计数

语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是`0`,就表示这个值不再用到了,因此可以将这块内存释放

如果一个值不再需要了,引用数却不为`0`,垃圾回收机制无法释放这块内存,从而导致内存泄漏

```javascript
const arr = [1, 2, 3, 4];
console.log('hello world');
```

上面代码中,数组`[1, 2, 3, 4]`是一个值,会占用内存。变量`arr`是仅有的对这个值的引用,因此引用次数为`1`。尽管后面的代码没有用到`arr`,它还是会持续占用内存

如果需要这块内存被垃圾回收机制释放,只需要设置如下:

```js
arr = null
```

通过设置`arr`为`null`,就解除了对数组`[1,2,3,4]`的引用,引用次数变为 0,就被垃圾回收了



### 小结

有了垃圾回收机制,不代表不用关注内存泄露。那些很占空间的值,一旦不再用到,需要检查是否还存在对它们的引用。如果是的话,就必须手动解除引用



## 三、常见内存泄露情况

意外的全局变量

```js
function foo(arg) {
    bar = "this is a hidden global variable";
}
```

另一种意外的全局变量可能由 `this` 创建:

```js
function foo() {
    this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();
```

上述使用严格模式,可以避免意外的全局变量

定时器也常会造成内存泄露

```js
var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
```

如果`id`为Node的元素从`DOM`中移除,该定时器仍会存在,同时,因为回调函数中包含对`someResource`的引用,定时器外面的`someResource`也不会被释放

包括我们之前所说的闭包,维持函数内局部变量,使其得不到释放

```js
function bindEvent() {
  var obj = document.createElement('XXX');
  var unused = function () {
    console.log(obj, '闭包内引用obj obj不会被释放');
  };
  obj = null; // 解决方法
}
```

没有清理对`DOM`元素的引用同样造成内存泄露

```js
const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用
```

包括使用事件监听`addEventListener`监听的时候,在不监听的情况下使用`removeEventListener`取消对事件监听


## 参考文献

- http://www.ruanyifeng.com/blog/2017/04/memory-leak.html
- https://zh.wikipedia.org/wiki


================================================
FILE: docs/JavaScript/new.md
================================================
# 面试官:说说new操作符具体干了什么?

![](https://static.vue-js.com/880d0010-7a39-11eb-85f6-6fac77c0c9b3.png)

## 一、是什么

在`JavaScript`中,`new`操作符用于创建一个给定构造函数的实例对象

例子
```js
function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function () {
    console.log(this.name)
}
const person1 = new Person('Tom', 20)
console.log(person1)  // Person {name: "Tom", age: 20}
t.sayName() // 'Tom'
```

从上面可以看到:

- `new` 通过构造函数 `Person` 创建出来的实例可以访问到构造函数中的属性
- `new` 通过构造函数 `Person` 创建出来的实例可以访问到构造函数原型链中的属性(即实例与构造函数通过原型链连接了起来)

现在在构建函数中显式加上返回值,并且这个返回值是一个原始类型

```js
function Test(name) {
  this.name = name
  return 1
}
const t = new Test('xxx')
console.log(t.name) // 'xxx'
```

可以发现,构造函数中返回一个原始值,然而这个返回值并没有作用

下面在构造函数中返回一个对象

```js
function Test(name) {
  this.name = name
  console.log(this) // Test { name: 'xxx' }
  return { age: 26 }
}
const t = new Test('xxx')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'
```

从上面可以发现,构造函数如果返回值为一个对象,那么这个返回值会被正常使用



## 二、流程

从上面介绍中,我们可以看到`new`关键字主要做了以下的工作:

- 创建一个新的对象`obj`
- 将对象与构建函数通过原型链连接起来
- 将构建函数中的`this`绑定到新建的对象`obj`上

- 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理

举个例子:

```js
function Person(name, age){
    this.name = name;
    this.age = age;
}
const person1 = new Person('Tom', 20)
console.log(person1)  // Person {name: "Tom", age: 20}
t.sayName() // 'Tom'
```

流程图如下:

 ![](https://static.vue-js.com/b429b990-7a39-11eb-85f6-6fac77c0c9b3.png)



## 三、手写new操作符

现在我们已经清楚地掌握了`new`的执行过程

那么我们就动手来实现一下`new`

```js
function mynew(Func, ...args) {
    // 1.创建一个新对象
    const obj = {}
    // 2.新对象原型指向构造函数原型对象
    obj.__proto__ = Func.prototype
    // 3.将构建函数的this指向新对象
    let result = Func.apply(obj, args)
    // 4.根据返回值判断
    return result instanceof Object ? result : obj
}
```

测试一下

```js
function mynew(func, ...args) {
    const obj = {}
    obj.__proto__ = func.prototype
    let result = func.apply(obj, args)
    return result instanceof Object ? result : obj
}
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function () {
    console.log(this.name)
}

let p = mynew(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123}
p.say() // huihui
```

可以发现,代码虽然很短,但是能够模拟实现`new`



================================================
FILE: docs/JavaScript/prototype.md
================================================
# 面试官:JavaScript原型,原型链 ? 有什么特点?

 ![](https://static.vue-js.com/4500e170-725e-11eb-85f6-6fac77c0c9b3.png)

## 一、原型

`JavaScript` 常被描述为一种基于原型的语言——每个对象拥有一个原型对象

当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的`prototype`属性上,而非实例对象本身

下面举个例子:

函数可以有属性。 每个函数都有一个特殊的属性叫作原型`prototype` 

```js
function doSomething(){}
console.log( doSomething.prototype );
```

控制台输出

```js
{
    constructor: ƒ doSomething(),
    __proto__: {
        constructor: ƒ Object(),
        hasOwnProperty: ƒ hasOwnProperty(),
        isPrototypeOf: ƒ isPrototypeOf(),
        propertyIsEnumerable: ƒ propertyIsEnumerable(),
        toLocaleString: ƒ toLocaleString(),
        toString: ƒ toString(),
        valueOf: ƒ valueOf()
    }
}
```

上面这个对象,就是大家常说的原型对象

可以看到,原型对象有一个自有属性`constructor`,这个属性指向该函数,如下图关系展示

 ![](https://static.vue-js.com/56d87250-725e-11eb-ab90-d9ae814b240d.png)





## 二、原型链

原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法

在对象实例和它的构造器之间建立一个链接(它是`__proto__`属性,是从构造函数的`prototype`属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法

下面举个例子:

```js
function Person(name) {
    this.name = name;
    this.age = 18;
    this.sayName = function() {
        console.log(this.name);
    }
}
// 第二步 创建实例
var person = new Person('person')
```

根据代码,我们可以得到下图

 ![](https://static.vue-js.com/60825aa0-725e-11eb-85f6-6fac77c0c9b3.png)

下面分析一下:

- 构造函数`Person`存在原型对象`Person.prototype`
- 构造函数生成实例对象`person`,`person`的`__proto__`指向构造函数`Person`原型对象
- `Person.prototype.__proto__` 指向内置对象,因为 `Person.prototype` 是个对象,默认是由 `Object `函数作为类创建的,而 `Object.prototype` 为内置对象

- `Person.__proto__` 指向内置匿名函数 `anonymous`,因为 Person 是个函数对象,默认由 Function 作为类创建

- `Function.prototype` 和 `Function.__proto__ `同时指向内置匿名函数 `anonymous`,这样原型链的终点就是 `null`



## 三、总结

下面首先要看几个概念:

`__proto__`作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的

 ![](https://static.vue-js.com/6a742160-725e-11eb-ab90-d9ae814b240d.png)

每个对象的`__proto__`都是指向它的构造函数的原型对象`prototype`的

```js
person1.__proto__ === Person.prototype
```

构造函数是一个函数对象,是通过 `Function `构造器产生的

```js
Person.__proto__ === Function.prototype
```

原型对象本身是一个普通对象,而普通对象的构造函数都是`Object`

```js
Person.prototype.__proto__ === Object.prototype
```

刚刚上面说了,所有的构造器都是函数对象,函数对象都是 `Function `构造产生的

```js
Object.__proto__ === Function.prototype
```

`Object `的原型对象也有`__proto__`属性指向`null`,`null`是原型链的顶端

```js
Object.prototype.__proto__ === null
```

下面作出总结:

- 一切对象都是继承自`Object`对象,`Object` 对象直接继承根源对象` null`

- 一切的函数对象(包括 `Object` 对象),都是继承自 `Function` 对象

- `Object` 对象直接继承自 `Function` 对象

- `Function`对象的`__proto__`会指向自己的原型对象,最终还是继承自`Object`对象




## 参考文献

- https://juejin.cn/post/6870732239556640775#heading-7
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

================================================
FILE: docs/JavaScript/pull_up_loading_pull_down_refresh.md
================================================
# 面试官:如何实现上拉加载,下拉刷新?

 ![](https://static.vue-js.com/89cd1850-8adc-11eb-ab90-d9ae814b240d.png)

## 一、前言

下拉刷新和上拉加载这两种交互方式通常出现在移动端中

本质上等同于PC网页中的分页,只是交互形式不同

开源社区也有很多优秀的解决方案,如`iscroll`、`better-scroll`、`pulltorefresh.js`库等等

这些第三方库使用起来非常便捷

我们通过原生的方式实现一次上拉加载,下拉刷新,有助于对第三方库有更好的理解与使用

## 二、实现原理

上拉加载及下拉刷新都依赖于用户交互

最重要的是要理解在什么场景,什么时机下触发交互动作

### 上拉加载

首先可以看一张图

 ![](https://static.vue-js.com/df498a00-8ae3-11eb-ab90-d9ae814b240d.png)

上拉加载的本质是页面触底,或者快要触底时的动作

判断页面触底我们需要先了解一下下面几个属性

- `scrollTop`:滚动视窗的高度距离`window`顶部的距离,它会随着往上滚动而不断增加,初始值是0,它是一个变化的值

- `clientHeight`:它是一个定值,表示屏幕可视区域的高度;
- `scrollHeight`:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示`body`所有元素的总长度(包括body元素自身的padding)

综上我们得出一个触底公式:
```js
scrollTop + clientHeight >= scrollHeight
```

简单实现
```js
let clientHeight  = document.documentElement.clientHeight; //浏览器高度
let scrollHeight = document.body.scrollHeight;
let scrollTop = document.documentElement.scrollTop;
 
let distance = 50;  //距离视窗还用50的时候,开始触发;

if ((scrollTop + clientHeight) >= (scrollHeight - distance)) {
    console.log("开始加载数据");
}
```


### 下拉刷新
下拉刷新的本质是页面本身置于顶部时,用户下拉时需要触发的动作

关于下拉刷新的原生实现,主要分成三步:

- 监听原生`touchstart`事件,记录其初始位置的值,`e.touches[0].pageY`;
- 监听原生`touchmove`事件,记录并计算当前滑动的位置值与初始位置值的差值,大于`0`表示向下拉动,并借助CSS3的`translateY`属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
- 监听原生`touchend`事件,若此时元素滑动达到最大值,则触发`callback`,同时将`translateY`重设为`0`,元素回到初始位置

举个例子:

`Html`结构如下:

```js
<main>
    <p class="refreshText"></p >
    <ul id="refreshContainer">
        <li>111</li>
        <li>222</li>
        <li>333</li>
        <li>444</li>
        <li>555</li>
        ...
    </ul>
</main>
```

监听`touchstart`事件,记录初始的值

```js
var _element = document.getElementById('refreshContainer'),
    _refreshText = document.querySelector('.refreshText'),
    _startPos = 0,  // 初始的值
    _transitionHeight = 0; // 移动的距离

_element.addEventListener('touchstart', function(e) {
    _startPos = e.touches[0].pageY; // 记录初始位置
    _element.style.position = 'relative';
    _element.style.transition = 'transform 0s';
}, false);
```

监听`touchmove`移动事件,记录滑动差值

```js
_element.addEventListener('touchmove', function(e) {
    // e.touches[0].pageY 当前位置
    _transitionHeight = e.touches[0].pageY - _startPos; // 记录差值

    if (_transitionHeight > 0 && _transitionHeight < 60) { 
        _refreshText.innerText = '下拉刷新'; 
        _element.style.transform = 'translateY('+_transitionHeight+'px)';

        if (_transitionHeight > 55) {
            _refreshText.innerText = '释放更新';
        }
    }                
}, false);
```

最后,就是监听`touchend`离开的事件

```js
_element.addEventListener('touchend', function(e) {
    _element.style.transition = 'transform 0.5s ease 1s';
    _element.style.transform = 'translateY(0px)';
    _refreshText.innerText = '更新中...';
    // todo...

}, false);
```

从上面可以看到,在下拉到松手的过程中,经历了三个阶段:

- 当前手势滑动位置与初始位置差值大于零时,提示正在进行下拉刷新操作
- 下拉到一定值时,显示松手释放后的操作提示
- 下拉到达设定最大值松手时,执行回调,提示正在进行更新操作



## 三、案例

在实际开发中,我们更多的是使用第三方库,下面以`better-scroll`进行举例:

HTML结构

```js
<div id="position-wrapper">
    <div>
        <p class="refresh">下拉刷新</p >
        <div class="position-list">
   <!--列表内容-->
        </div>
        <p class="more">查看更多</p >
    </div>
</div>
```

实例化上拉下拉插件,通过`use`来注册插件

```js
import BScroll from "@better-scroll/core";
import PullDown from "@better-scroll/pull-down";
import PullUp from '@better-scroll/pull-up';
BScroll.use(PullDown);
BScroll.use(PullUp);
```

实例化`BetterScroll`,并传入相关的参数

```js
let pageNo = 1,pageSize = 10,dataList = [],isMore = true;  
var scroll= new BScroll("#position-wrapper",{
    scrollY:true,//垂直方向滚动
    click:true,//默认会阻止浏览器的原生click事件,如果需要点击,这里要设为true
    pullUpLoad:true,//上拉加载更多
    pullDownRefresh:{
        threshold:50,//触发pullingDown事件的位置
        stop:0//下拉回弹后停留的位置
    }
});
//监听下拉刷新
scroll.on("pullingDown",pullingDownHandler);
//监测实时滚动
scroll.on("scroll",scrollHandler);
//上拉加载更多
scroll.on("pullingUp",pullingUpHandler);

async function pullingDownHandler(){
    dataList=[];
    pageNo=1;
    isMore=true;
    $(".more").text("查看更多");
    await getlist();//请求数据
    scroll.finishPullDown();//每次下拉结束后,需要执行这个操作
    scroll.refresh();//当滚动区域的dom结构有变化时,需要执行这个操作
}
async function pullingUpHandler(){
    if(!isMore){
        $(".more").text("没有更多数据了");
        scroll.finishPullUp();//每次上拉结束后,需要执行这个操作
        return;
    }
    pageNo++;
    await this.getlist();//请求数据
    scroll.finishPullUp();//每次上拉结束后,需要执行这个操作
    scroll.refresh();//当滚动区域的dom结构有变化时,需要执行这个操作    
}
function scrollHandler(){
    if(this.y>50) $('.refresh').text("松手开始加载");
    else $('.refresh').text("下拉刷新");
}
function getlist(){
    //返回的数据
    let result=....;
    dataList=dataList.concat(result);
    //判断是否已加载完
    if(result.length<pageSize) isMore=false;
    //将dataList渲染到html内容中
}    
```

注意点:

使用`better-scroll `实现下拉刷新、上拉加载时要注意以下几点:

- `wrapper`里必须只有一个子元素
- 子元素的高度要比`wrapper`要高
- 使用的时候,要确定`DOM`元素是否已经生成,必须要等到`DOM`渲染完成后,再`new BScroll()`
- 滚动区域的`DOM`元素结构有变化后,需要执行刷新 `refresh() `
- 上拉或者下拉,结束后,需要执行`finishPullUp()`或者`finishPullDown()`,否则将不会执行下次操作
- `better-scroll`,默认会阻止浏览器的原生`click`事件,如果滚动内容区要添加点击事件,需要在实例化属性里设置`click:true`

### 小结

下拉刷新、上拉加载原理本身都很简单,真正复杂的是封装过程中,要考虑的兼容性、易用性、性能等诸多细节

## 参考文献

- https://segmentfault.com/a/1190000014423308
- https://github.com/ustbhuangyi/better-scroll


================================================
FILE: docs/JavaScript/regexp.md
================================================
# 面试官:说说你对正则表达式的理解?应用场景?

![](https://static.vue-js.com/55388a40-7f1d-11eb-ab90-d9ae814b240d.png)

## 一、是什么

正则表达式是一种用来匹配字符串的强有力的武器

它的设计思想是用一种描述性的语言定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的

在 `JavaScript`中,正则表达式也是对象,构建正则表达式有两种方式:

1. 字面量创建,其由包含在斜杠之间的模式组成

```js
const re = /\d+/g;
```

2. 调用`RegExp`对象的构造函数

```js
const re = new RegExp("\\d+","g");

const rul = "\\d+"
const re1 = new RegExp(rul,"g");
```

使用构建函数创建,第一个参数可以是一个变量,遇到特殊字符`\`需要使用`\\`进行转义



## 二、匹配规则

常见的校验规则如下:

| 规则        | 描述                                                  |
| ----------- | ----------------------------------------------------- |
| \           | 转义                                                  |
| ^           | 匹配输入的开始                                        |
| $           | 匹配输入的结束                                        |
| *           | 匹配前一个表达式 0 次或多次                           |
| +           | 匹配前面一个表达式 1 次或者多次。等价于 `{1,}`        |
| ?           | 匹配前面一个表达式 0 次或者 1 次。等价于`{0,1}`       |
| .           | 默认匹配除换行符之外的任何单个字符                    |
| x(?=y)      | 匹配'x'仅仅当'x'后面跟着'y'。这种叫做先行断言         |
| (?<=y)x     | 匹配'x'仅当'x'前面是'y'.这种叫做后行断言              |
| x(?!y)      | 仅仅当'x'后面不跟着'y'时匹配'x',这被称为正向否定查找 |
| (?<!*y*)*x* | 仅仅当'x'前面不是'y'时匹配'x',这被称为反向否定查找   |
| x\|y        | 匹配‘x’或者‘y’                                        |
| {n}         | n 是一个正整数,匹配了前面一个字符刚好出现了 n 次     |
| {n,}        | n是一个正整数,匹配前一个字符至少出现了n次            |
| {n,m}       | n 和 m 都是整数。匹配前面的字符至少n次,最多m次       |
| [xyz\]      | 一个字符集合。匹配方括号中的任意字符                  |
| [^xyz\]     | 匹配任何没有包含在方括号中的字符                      |
| \b          | 匹配一个词的边界,例如在字母和空格之间                |
| \B          | 匹配一个非单词边界                                    |
| \d          | 匹配一个数字                                          |
| \D          | 匹配一个非数字字符                                    |
| \f          | 匹配一个换页符                                        |
| \n          | 匹配一个换行符                                        |
| \r          | 匹配一个回车符                                        |
| \s          | 匹配一个空白字符,包括空格、制表符、换页符和换行符    |
| \S          | 匹配一个非空白字符                                    |
| \w          | 匹配一个单字字符(字母、数字或者下划线)              |
| \W          | 匹配一个非单字字符                                    |

### 正则表达式标记

| 标志 | 描述                                                      |
| :--- | :-------------------------------------------------------- |
| `g`  | 全局搜索。                                                |
| `i`  | 不区分大小写搜索。                                        |
| `m`  | 多行搜索。                                                |
| `s`  | 允许 `.` 匹配换行符。                                     |
| `u`  | 使用`unicode`码的模式进行匹配。                           |
| `y`  | 执行“粘性(`sticky`)”搜索,匹配从目标字符串的当前位置开始。 |

使用方法如下:

```js
var re = /pattern/flags;
var re = new RegExp("pattern", "flags");
```

在了解下正则表达式基本的之外,还可以掌握几个正则表达式的特性:

### 贪婪模式

在了解贪婪模式前,首先举个例子:

```js
const reg = /ab{1,3}c/
```

在匹配过程中,尝试可能的顺序是从多往少的方向去尝试。首先会尝试`bbb`,然后再看整个正则是否能匹配。不能匹配时,吐出一个`b`,即在`bb`的基础上,再继续尝试,以此重复

如果多个贪婪量词挨着,则深度优先搜索

```js
const string = "12345";
const regx = /(\d{1,3})(\d{1,3})/;
console.log( string.match(reg) );
// => ["12345", "123", "45", index: 0, input: "12345"]
```

其中,前面的`\d{1,3}`匹配的是"123",后面的`\d{1,3}`匹配的是"45"

### 懒惰模式

惰性量词就是在贪婪量词后面加个问号。表示尽可能少的匹配

```js
var string = "12345";
var regex = /(\d{1,3}?)(\d{1,3})/;
console.log( string.match(regex) );
// => ["1234", "1", "234", index: 0, input: "12345"]
```

其中`\d{1,3}?`只匹配到一个字符"1",而后面的`\d{1,3}`匹配了"234"

### 分组

分组主要是用过`()`进行实现,比如`beyond{3}`,是匹配`d`字母3次。而`(beyond){3}`是匹配`beyond`三次

在`()`内使用`|`达到或的效果,如`(abc | xxx)`可以匹配`abc`或者`xxx`

反向引用,巧用`$`分组捕获

```js
let str = "John Smith";

// 交换名字和姓氏
console.log(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John
```





## 三、匹配方法

正则表达式常被用于某些方法,我们可以分成两类:

- 字符串(str)方法:`match`、`matchAll`、`search`、`replace`、`split`
- 正则对象下(regexp)的方法:`test`、`exec`

| 方法     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| exec     | 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回 null)。 |
| test     | 一个在字符串中测试是否匹配的RegExp方法,它返回 true 或 false。 |
| match    | 一个在字符串中执行查找匹配的String方法,它返回一个数组,在未匹配到时会返回 null。 |
| matchAll | 一个在字符串中执行查找所有匹配的String方法,它返回一个迭代器(iterator)。 |
| search   | 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。 |
| replace  | 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。 |
| split    | 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 `String` 方法。 |



### str.match(regexp)

`str.match(regexp)` 方法在字符串 `str` 中找到匹配 `regexp` 的字符

如果 `regexp` 不带有 `g` 标记,则它以数组的形式返回第一个匹配项,其中包含分组和属性 `index`(匹配项的位置)、`input`(输入字符串,等于 `str`)

```js
let str = "I love JavaScript";

let result = str.match(/Java(Script)/);

console.log( result[0] );     // JavaScript(完全匹配)
console.log( result[1] );     // Script(第一个分组)
console.log( result.length ); // 2

// 其他信息:
console.log( result.index );  // 7(匹配位置)
console.log( result.input );  // I love JavaScript(源字符串)
```

如果 `regexp` 带有 `g` 标记,则它将所有匹配项的数组作为字符串返回,而不包含分组和其他详细信息

```js
let str = "I love JavaScript";

let result = str.match(/Java(Script)/g);

console.log( result[0] ); // JavaScript
console.log( result.length ); // 1
```

如果没有匹配项,则无论是否带有标记 `g` ,都将返回 `null`

```js
let str = "I love JavaScript";

let result = str.match(/HTML/);

console.log(result); // null
```



### str.matchAll(regexp)

返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器

```js
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]
```







### str.search(regexp)

返回第一个匹配项的位置,如果未找到,则返回 `-1`

```js
let str = "A drop of ink may make a million think";

console.log( str.search( /ink/i ) ); // 10(第一个匹配位置)
```

这里需要注意的是,`search` 仅查找第一个匹配项







## str.replace(regexp)

替换与正则表达式匹配的子串,并返回替换后的字符串。在不设置全局匹配`g`的时候,只替换第一个匹配成功的字符串片段

```js
const reg1=/javascript/i;
const reg2=/javascript/ig;
console.log('hello Javascript Javascript Javascript'.replace(reg1,'js'));
//hello js Javascript Javascript
console.log('hello Javascript Javascript Javascript'.replace(reg2,'js'));
//hello js js js
```



### str.split(regexp)

使用正则表达式(或子字符串)作为分隔符来分割字符串

```js
console.log('12, 34, 56'.split(/,\s*/)) // 数组 ['12', '34', '56']
```



### regexp.exec(str)

`regexp.exec(str)` 方法返回字符串 `str` 中的 `regexp` 匹配项,与以前的方法不同,它是在正则表达式而不是字符串上调用的

根据正则表达式是否带有标志 `g`,它的行为有所不同

如果没有 `g`,那么 `regexp.exec(str)` 返回的第一个匹配与 `str.match(regexp)` 完全相同

如果有标记 `g`,调用 `regexp.exec(str)` 会返回第一个匹配项,并将紧随其后的位置保存在属性`regexp.lastIndex` 中。 下一次同样的调用会从位置 `regexp.lastIndex` 开始搜索,返回下一个匹配项,并将其后的位置保存在 `regexp.lastIndex` 中

```js
let str = 'More about JavaScript at https://javascript.info';
let regexp = /javascript/ig;

let result;

while (result = regexp.exec(str)) {
  console.log( `Found ${result[0]} at position ${result.index}` );
  // Found JavaScript at position 11
  // Found javascript at position 33
}
```



### regexp.test(str)

查找匹配项,然后返回 `true/false` 表示是否存在

```js
let str = "I love JavaScript";

// 这两个测试相同
console.log( /love/i.test(str) ); // true
```





## 四、应用场景

通过上面的学习,我们对正则表达式有了一定的了解

下面再来看看正则表达式一些案例场景:

验证QQ合法性(5~15位、全是数字、不以0开头):

```js
const reg = /^[1-9][0-9]{4,14}$/
const isvalid = patrn.exec(s)
```

校验用户账号合法性(只能输入5-20个以字母开头、可带数字、“_”、“.”的字串):

```js
var patrn=/^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){4,19}$/;
const isvalid = patrn.exec(s)
```

将`url`参数解析为对象

```js
const protocol = '(?<protocol>https?:)';
const host = '(?<host>(?<hostname>[^/#?:]+)(?::(?<port>\\d+))?)';
const path = '(?<pathname>(?:\\/[^/#?]+)*\\/?)';
const search = '(?<search>(?:\\?[^#]*)?)';
const hash = '(?<hash>(?:#.*)?)';
const reg = new RegExp(`^${protocol}\/\/${host}${path}${search}${hash}$`);
function execURL(url){
    const result = reg.exec(url);
    if(result){
        result.groups.port = result.groups.port || '';
        return result.groups;
    }
    return {
        protocol:'',host:'',hostname:'',port:'',
        pathname:'',search:'',hash:'',
    };
}

console.log(execURL('https://localhost:8080/?a=b#xxxx'));
protocol: "https:"
host: "localhost:8080"
hostname: "localhost"
port: "8080"
pathname: "/"
search: "?a=b"
hash: "#xxxx"
```

再将上面的`search`和`hash`进行解析

```js
function execUrlParams(str){
    str = str.replace(/^[#?&]/,'');
    const result = {};
    if(!str){ //如果正则可能配到空字符串,极有可能造成死循环,判断很重要
        return result; 
    }
    const reg = /(?:^|&)([^&=]*)=?([^&]*?)(?=&|$)/y
    let exec = reg.exec(str);
    while(exec){
        result[exec[1]] = exec[2];
        exec = reg.exec(str);
    }
    return result;
}
console.log(execUrlParams('#'));// {}
console.log(execUrlParams('##'));//{'#':''}
console.log(execUrlParams('?q=3606&src=srp')); //{q: "3606", src: "srp"}
console.log(execUrlParams('test=a=b=c&&==&a='));//{test: "a=b=c", "": "=", a: ""}
```

## 参考文献

- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions

================================================
FILE: docs/JavaScript/scope.md
================================================
# 面试官:说说你对作用域链的理解

 ![](https://static.vue-js.com/16f614a0-718f-11eb-ab90-d9ae814b240d.png)

## 一、作用域

作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合

换句话说,作用域决定了代码区块中变量和其他资源的可见性

举个例子

```js
function myFunction() {
    let inVariable = "函数内部变量";
}
myFunction();//要先执行这个函数,否则根本不知道里面是啥
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
```

上述例子中,函数`myFunction`内部创建一个`inVariable`变量,当我们在全局访问这个变量的时候,系统会报错

这就说明我们在全局是无法获取到(闭包除外)函数内部的变量



我们一般将作用域分成:

- 全局作用域
- 函数作用域

- 块级作用域



### 全局作用域

任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问

```js
// 全局变量
var greeting = 'Hello World!';
function greet() {
  console.log(greeting);
}
// 打印 'Hello World!'
greet();
```


### 函数作用域

函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问

```js
function greet() {
  var greeting = 'Hello World!';
  console.log(greeting);
}
// 打印 'Hello World!'
greet();
// 报错: Uncaught ReferenceError: greeting is not defined
console.log(greeting);
```
可见上述代码中在函数内部声明的变量或函数,在函数外部是无法访问的,这说明在函数内部定义的变量或者方法只是函数作用域



### 块级作用域

ES6引入了`let`和`const`关键字,和`var`关键字不同,在大括号中使用`let`和`const`声明的变量存在于块级作用域中。在大括号之外不能访问这些变量

```js
{
  // 块级作用域中的变量
  let greeting = 'Hello World!';
  var lang = 'English';
  console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);
```



## 二、词法作用域

词法作用域,又叫静态作用域,变量被创建时就确定好了,而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了,`JavaScript` 遵循的就是词法作用域

```js
var a = 2;
function foo(){
    console.log(a)
}
function bar(){
    var a = 3;
    foo();
}
bar()
```

上述代码改变成一张图

 ![](https://static.vue-js.com/29fab3d0-718f-11eb-85f6-6fac77c0c9b3.png)

由于`JavaScript`遵循词法作用域,相同层级的 `foo` 和 `bar` 就没有办法访问到彼此块作用域中的变量,所以输出2



## 三、作用域链

当在`Javascript`中使用一个变量的时候,首先`Javascript`引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域

如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错

这里拿《你不知道的Javascript(上)》中的一张图解释:

把作用域比喻成一个建筑,这份建筑代表程序中的嵌套作用域链,第一层代表当前的执行作用域,顶层代表全局作用域

 ![](https://static.vue-js.com/33f9c100-718f-11eb-85f6-6fac77c0c9b3.png)

变量的引用会顺着当前楼层进行查找,如果找不到,则会往上一层找,一旦到达顶层,查找的过程都会停止

下面代码演示下:

```js
var sex = '男';
function person() {
    var name = '张三';
    function student() {
        var age = 18;
        console.log(name); // 张三
        console.log(sex); // 男 
    }
    student();
    console.log(age); // Uncaught ReferenceError: age is not defined
}
person();
```

上述代码主要主要做了以下工作:

- `student`函数内部属于最内层作用域,找不到`name`,向上一层作用域`person`函数内部找,找到了输出“张三”
- `student`内部输出`sex`时找不到,向上一层作用域`person`函数找,还找不到继续向上一层找,即全局作用域,找到了输出“男”
- 在`person`函数内部输出`age`时找不到,向上一层作用域找,即全局作用域,还是找不到则报错


================================================
FILE: docs/JavaScript/security.md
================================================
# 面试官:web常见的攻击方式有哪些?如何防御?

 ![](https://static.vue-js.com/d0892930-8d1d-11eb-ab90-d9ae814b240d.png)

## 一、是什么
Web攻击(WebAttack)是针对用户上网行为或网站服务器等设备进行攻击的行为

如植入恶意代码,修改网站权限,获取网站用户隐私信息等等

Web应用程序的安全性是任何基于Web业务的重要组成部分

确保Web应用程序安全十分重要,即使是代码中很小的 bug 也有可能导致隐私信息被泄露

站点安全就是为保护站点不受未授权的访问、使用、修改和破坏而采取的行为或实践

我们常见的Web攻击方式有
- XSS (Cross Site Scripting) 跨站脚本攻击
- CSRF(Cross-site request forgery)跨站请求伪造
- SQL注入攻击


## 二、XSS

XSS,跨站脚本攻击,允许攻击者将恶意代码植入到提供给其它用户使用的页面中

`XSS`涉及到三方,即攻击者、客户端与`Web`应用

`XSS`的攻击目标是为了盗取存储在客户端的`cookie`或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互

举个例子:

一个搜索页面,根据`url`参数决定关键词的内容

```html
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div>
  您搜索的关键词是:<%= getParameter("keyword") %>
</div>
```

这里看似并没有问题,但是如果不按套路出牌呢?

用户输入`"><script>alert('XSS');</script>`,拼接到 HTML 中返回给浏览器。形成了如下的 HTML:

```html
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜索</button>
<div>
  您搜索的关键词是:"><script>alert('XSS');</script>
</div>
```

浏览器无法分辨出 `<script>alert('XSS');</script>` 是恶意代码,因而将其执行,试想一下,如果是获取`cookie`发送对黑客服务器呢?

根据攻击的来源,`XSS`攻击可以分成:

- 存储型
- 反射型
- DOM 型



### 存储型

存储型 XSS 的攻击步骤:

1. 攻击者将恶意代码提交到目标网站的数据库中
2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等



### 反射型 XSS

反射型 XSS 的攻击步骤:

1. 攻击者构造出特殊的 URL,其中包含恶意代码
2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见



### DOM 型 XSS

DOM 型 XSS 的攻击步骤:

1. 攻击者构造出特殊的 URL,其中包含恶意代码
2. 用户打开带有恶意代码的 URL
3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞



### XSS的预防

通过前面介绍,看到`XSS`攻击的两大要素:

- 攻击者提交而恶意代码
- 浏览器执行恶意代码

针对第一个要素,我们在用户输入的过程中,过滤掉用户输入的恶劣代码,然后提交给后端,但是如果攻击者绕开前端请求,直接构造请求就不能预防了

而如果在后端写入数据库前,对输入进行过滤,然后把内容给前端,但是这个内容在不同地方就会有不同显示

例如:

一个正常的用户输入了 `5 < 7` 这个内容,在写入数据库前,被转义,变成了 `5 < 7`

在客户端中,一旦经过了 `escapeHTML()`,客户端显示的内容就变成了乱码( `5 < 7` )

在前端中,不同的位置所需的编码也不同。

- 当 `5 < 7` 作为 HTML 拼接页面时,可以正常显示:

```html
<div title="comment">5 &lt; 7</div>
```

- 当 `5 < 7` 通过 Ajax 返回,然后赋值给 JavaScript 的变量时,前端得到的字符串就是转义后的字符。这个内容不能直接用于 Vue 等模板的展示,也不能直接用于内容长度计算。不能用于标题、alert 等



可以看到,过滤并非可靠的,下面就要通过防止浏览器执行恶意代码:

在使用 `.innerHTML`、`.outerHTML`、`document.write()` 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 `.textContent`、`.setAttribute()` 等

如果用 `Vue/React` 技术栈,并且不使用 `v-html`/`dangerouslySetInnerHTML` 功能,就在前端 `render` 阶段避免 `innerHTML`、`outerHTML` 的 XSS 隐患

DOM 中的内联事件监听器,如 `location`、`onclick`、`onerror`、`onload`、`onmouseover` 等,`<a>` 标签的 `href` 属性,JavaScript 的 `eval()`、`setTimeout()`、`setInterval()` 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免

```js
<!-- 链接内包含恶意代码 -->
< a href=" ">1</ a>

<script>
// setTimeout()/setInterval() 中调用恶意代码
setTimeout("UNTRUSTED")
setInterval("UNTRUSTED")

// location 调用恶意代码
location.href = 'UNTRUSTED'

// eval() 中调用恶意代码
eval("UNTRUSTED")
```





## 三、CSRF

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求

利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目

一个典型的CSRF攻击有着如下的流程:

- 受害者登录a.com,并保留了登录凭证(Cookie)
- 攻击者引诱受害者访问了b.com
- b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie
- a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求
- a.com以受害者的名义执行了act=xx
- 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作

`csrf`可以通过`get`请求,即通过访问`img`的页面后,浏览器自动访问目标地址,发送请求

同样,也可以设置一个自动提交的表单发送`post`请求,如下:

```js
<form action="http://bank.example/withdraw" method=POST>
    <input type="hidden" name="account" value="xiaoming" />
    <input type="hidden" name="amount" value="10000" />
    <input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script> 
```

访问该页面后,表单会自动提交,相当于模拟用户完成了一次`POST`操作

还有一种为使用`a`标签的,需要用户点击链接才会触发

访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作

```html
< a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
    重磅消息!!
<a/>
```



### CSRF的特点

- 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生
- 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据
- 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”
- 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪



### CSRF的预防  

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性

防止`csrf`常用方案如下:

- 阻止不明外域的访问
  - 同源检测
  - Samesite Cookie
- 提交时要求附加本域才能获取的信息
  - CSRF Token
  - 双重Cookie验证



这里主要讲讲`token`这种形式,流程如下:

- 用户打开页面的时候,服务器需要给这个用户生成一个Token
- 对于GET请求,Token将附在请求地址之后。对于 POST 请求来说,要在 form 的最后加上

```html
<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>
```

- 当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性



## 四、SQL注入

Sql 注入攻击,是通过将恶意的 `Sql `查询或添加语句插入到应用的输入参数中,再在后台 `Sql `服务器上解析执行进行的攻击

 ![](https://static.vue-js.com/ead52fa0-8d1d-11eb-85f6-6fac77c0c9b3.png)

流程如下所示:

- 找出SQL漏洞的注入点

- 判断数据库的类型以及版本
-  猜解用户名和密码
- 利用工具查找Web后台管理入口
- 入侵和破坏

预防方式如下:

- 严格检查输入变量的类型和格式
- 过滤和转义特殊字符
- 对访问数据库的Web应用程序采用Web应用防火墙

上述只是列举了常见的`web`攻击方式,实际开发过程中还会遇到很多安全问题,对于这些问题, 切记不可忽视


## 参考文献

- https://tech.meituan.com/2018/09/27/fe-security.html
- https://developer.mozilla.org/zh-CN/docs/learn/Server-side/First_steps/Website_security


================================================
FILE: docs/JavaScript/single_sign.md
================================================
# 面试官:什么是单点登录?如何实现?

 ![](https://static.vue-js.com/8a25a760-8c83-11eb-85f6-6fac77c0c9b3.png)

## 一、是什么

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一

SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统

SSO 一般都需要一个独立的认证中心(passport),子系统的登录均得通过`passport`,子系统本身将不参与登录操作

当一个系统成功登录以后,`passport`将会颁发一个令牌给各个子系统,子系统可以拿着令牌会获取各自的受保护资源,为了减少频繁认证,各个子系统在被`passport`授权以后,会建立一个局部会话,在一定时间内可以无需再次向`passport`发起认证

 ![](https://static.vue-js.com/2b9b0e70-8c4b-11eb-85f6-6fac77c0c9b3.png)

上图有四个系统,分别是`Application1`、`Application2`、`Application3`、和`SSO`,当`Application1`、`Application2`、`Application3`需要登录时,将跳到`SSO`系统,`SSO`系统完成登录,其他的应用系统也就随之登录了

#### 举个例子

淘宝、天猫都属于阿里旗下,当用户登录淘宝后,再打开天猫,系统便自动帮用户登录了天猫,这种现象就属于单点登录


## 二、如何实现

### 同域名下的单点登录

`cookie`的`domain`属性设置为当前域的父域,并且父域的`cookie`会被子域所共享。`path`属性默认为`web`应用的上下文路径

利用 `Cookie` 的这个特点,没错,我们只需要将` Cookie `的` domain`属性设置为父域的域名(主域名),同时将 `Cookie `的` path `属性设置为根路径,将 `Session ID`(或 `Token`)保存到父域中。这样所有的子域应用就都可以访问到这个` Cookie `

不过这要求应用系统的域名需建立在一个共同的主域名之下,如 `tieba.baidu.com` 和 `map.baidu.com`,它们都建立在 `baidu.com `这个主域名之下,那么它们就可以通过这种方式来实现单点登录



### 不同域名下的单点登录(一)

如果是不同域的情况下,`Cookie`是不共享的,这里我们可以部署一个认证中心,用于专门处理登录请求的独立的 `Web `服务

用户统一在认证中心进行登录,登录成功后,认证中心记录用户的登录状态,并将 `token` 写入 `Cookie`(注意这个 `Cookie `是认证中心的,应用系统是访问不到的)

应用系统检查当前请求有没有 `Token`,如果没有,说明用户在当前系统中尚未登录,那么就将页面跳转至认证中心

由于这个操作会将认证中心的 `Cookie` 自动带过去,因此,认证中心能够根据 `Cookie` 知道用户是否已经登录过了

如果认证中心发现用户尚未登录,则返回登录页面,等待用户登录

如果发现用户已经登录过了,就不会让用户再次登录了,而是会跳转回目标 `URL `,并在跳转前生成一个 `Token`,拼接在目标` URL` 的后面,回传给目标应用系统

应用系统拿到 `Token `之后,还需要向认证中心确认下 `Token` 的合法性,防止用户伪造。确认无误后,应用系统记录用户的登录状态,并将 `Token `写入` Cookie`,然后给本次访问放行。(注意这个 `Cookie` 是当前应用系统的)当用户再次访问当前应用系统时,就会自动带上这个 `Token`,应用系统验证 Token 发现用户已登录,于是就不会有认证中心什么事了

此种实现方式相对复杂,支持跨域,扩展性好,是单点登录的标准做法



### 不同域名下的单点登录(二)

可以选择将 `Session ID` (或 `Token` )保存到浏览器的 `LocalStorage` 中,让前端在每次向后端发送请求时,主动将` LocalStorage `的数据传递给服务端

这些都是由前端来控制的,后端需要做的仅仅是在用户登录成功后,将 `Session ID `(或 `Token `)放在响应体中传递给前端

单点登录完全可以在前端实现。前端拿到 `Session ID `(或 `Token` )后,除了将它写入自己的 `LocalStorage` 中之外,还可以通过特殊手段将它写入多个其他域下的 `LocalStorage` 中

关键代码如下:

```js
// 获取 token
var token = result.data.token;
 
// 动态创建一个不可见的iframe,在iframe中加载一个跨域HTML
var iframe = document.createElement("iframe");
iframe.src = "http://app1.com/localstorage.html";
document.body.append(iframe);
// 使用postMessage()方法将token传递给iframe
setTimeout(function () {
    iframe.contentWindow.postMessage(token, "http://app1.com");
}, 4000);
setTimeout(function () {
    iframe.remove();
}, 6000);
 
// 在这个iframe所加载的HTML中绑定一个事件监听器,当事件被触发时,把接收到的token数据写入localStorage
window.addEventListener('message', function (event) {
    localStorage.setItem('token', event.data)
}, false);
```

前端通过 `iframe`+`postMessage()` 方式,将同一份 `Token` 写入到了多个域下的 `LocalStorage` 中,前端每次在向后端发送请求之前,都会主动从 `LocalStorage` 中读取` Token `并在请求中携带,这样就实现了同一份` Token` 被多个域所共享

此种实现方式完全由前端控制,几乎不需要后端参与,同样支持跨域



## 三、流程

单点登录的流程图如下所示:

 ![](https://static.vue-js.com/2422bc40-8c84-11eb-ab90-d9ae814b240d.png)

- 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数

- sso认证中心发现用户未登录,将用户引导至登录页面
- 用户输入用户名密码提交登录申请
- sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
- sso认证中心带着令牌跳转会最初的请求地址(系统1)
- 系统1拿到令牌,去sso认证中心校验令牌是否有效
- sso认证中心校验令牌,返回有效,注册系统1
- 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
- 用户访问系统2的受保护资源
- 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
- sso认证中心发现用户
Download .txt
gitextract_tq4ln75s/

├── .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
Download .txt
SYMBOL INDEX (17 symbols across 2 files)

FILE: docs/.vuepress/theme/index.js
  method alias (line 18) | alias () {

FILE: docs/.vuepress/theme/util/index.js
  function normalize (line 6) | function normalize (path) {
  function getHash (line 12) | function getHash (path) {
  function isExternal (line 19) | function isExternal (path) {
  function isMailto (line 23) | function isMailto (path) {
  function isTel (line 27) | function isTel (path) {
  function ensureExt (line 31) | function ensureExt (path) {
  function isActive (line 45) | function isActive (route, path) {
  function resolvePage (line 56) | function resolvePage (pages, rawPath, base) {
  function resolvePath (line 79) | function resolvePath (relative, base, append) {
  function resolveSidebarItems (line 124) | function resolveSidebarItems (page, regularPath, site, localePath) {
  function resolveHeaders (line 151) | function resolveHeaders (page) {
  function groupHeaders (line 168) | function groupHeaders (headers) {
  function resolveNavLinkItem (line 182) | function resolveNavLinkItem (linkItem) {
  function resolveMatchingConfig (line 193) | function resolveMatchingConfig (regularPath, config) {
  function ensureEndingSlash (line 211) | function ensureEndingSlash (path) {
  function resolveItem (line 217) | function resolveItem (item, pages, base, groupDepth = 1) {
Condensed preview — 272 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,452K chars).
[
  {
    "path": ".github/workflows/blank.yml",
    "chars": 1398,
    "preview": "# This is a basic workflow to help you get started with Actions\n\nname: CI\n\n# Controls when the action will run. \non:\n  #"
  },
  {
    "path": ".gitignore",
    "chars": 154,
    "preview": "/coverage\n/docs/.vuepress/dist\n/examples/**/build.js\n/test/e2e/reports\n/test/e2e/screenshots\n/types/typings\n/types/test/"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 37,
    "preview": "{\n    \"eggHelper.serverPort\": 35684\n}"
  },
  {
    "path": "docs/.vuepress/config.js",
    "chars": 16511,
    "preview": "module.exports = {\n  // title: \"Vue3源码解析 - vue中文社区\",\n  title: \"web前端面试 - 面试官系列\",\n  description: \"web前端面试,vue面试题,react面试题"
  },
  {
    "path": "docs/.vuepress/theme/LICENSE",
    "chars": 1091,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2018-present, Yuxi (Evan) You\n\nPermission is hereby granted, free of charge, to any"
  },
  {
    "path": "docs/.vuepress/theme/components/AlgoliaSearchBox.vue",
    "chars": 4589,
    "preview": "<template>\n  <form\n    id=\"search-form\"\n    class=\"algolia-search-wrapper search-box\"\n    role=\"search\"\n  >\n    <input\n "
  },
  {
    "path": "docs/.vuepress/theme/components/DropdownLink.vue",
    "chars": 5084,
    "preview": "<template>\n  <div\n    class=\"dropdown-wrapper\"\n    :class=\"{ open }\"\n  >\n    <button\n      class=\"dropdown-title\"\n      "
  },
  {
    "path": "docs/.vuepress/theme/components/DropdownTransition.vue",
    "chars": 561,
    "preview": "<template>\n  <transition\n    name=\"dropdown\"\n    @enter=\"setHeight\"\n    @after-enter=\"unsetHeight\"\n    @before-leave=\"se"
  },
  {
    "path": "docs/.vuepress/theme/components/Home.vue",
    "chars": 3538,
    "preview": "<template>\n  <main\n    class=\"home\"\n    aria-labelledby=\"main-title\"\n  >\n    <header class=\"hero\">\n      <img\n        v-"
  },
  {
    "path": "docs/.vuepress/theme/components/NavLink.vue",
    "chars": 1561,
    "preview": "<template>\n  <RouterLink\n    v-if=\"isInternal\"\n    class=\"nav-link\"\n    :to=\"link\"\n    :exact=\"exact\"\n    @focusout.nati"
  },
  {
    "path": "docs/.vuepress/theme/components/NavLinks.vue",
    "chars": 3804,
    "preview": "<template>\n  <nav\n    v-if=\"userLinks.length || repoLink\"\n    class=\"nav-links\"\n  >\n    <!-- user links -->\n    <div\n   "
  },
  {
    "path": "docs/.vuepress/theme/components/Navbar.vue",
    "chars": 3474,
    "preview": "<template>\n  <header class=\"navbar\">\n    <SidebarButton @toggle-sidebar=\"$emit('toggle-sidebar')\" />\n\n    <RouterLink\n  "
  },
  {
    "path": "docs/.vuepress/theme/components/Page.vue",
    "chars": 535,
    "preview": "<template>\n  <main class=\"page\">\n    <slot name=\"top\" />\n\n    <Content class=\"theme-default-content\" />\n    <PageEdit />"
  },
  {
    "path": "docs/.vuepress/theme/components/PageEdit.vue",
    "chars": 3167,
    "preview": "<template>\n  <footer class=\"page-edit\">\n    <div\n      v-if=\"editLink\"\n      class=\"edit-link\"\n    >\n      <a\n        :h"
  },
  {
    "path": "docs/.vuepress/theme/components/PageNav.vue",
    "chars": 3376,
    "preview": "<template>\n  <div\n    v-if=\"prev || next\"\n    class=\"page-nav\"\n  >\n    <p class=\"inner\">\n      <span\n        v-if=\"prev\""
  },
  {
    "path": "docs/.vuepress/theme/components/Sidebar.vue",
    "chars": 1240,
    "preview": "<template>\n  <aside class=\"sidebar\">\n    <NavLinks />\n\n    <slot name=\"top\" />\n\n    <SidebarLinks\n      :depth=\"0\"\n     "
  },
  {
    "path": "docs/.vuepress/theme/components/SidebarButton.vue",
    "chars": 990,
    "preview": "<template>\n  <div\n    class=\"sidebar-button\"\n    @click=\"$emit('toggle-sidebar')\"\n  >\n    <svg\n      class=\"icon\"\n      "
  },
  {
    "path": "docs/.vuepress/theme/components/SidebarGroup.vue",
    "chars": 2892,
    "preview": "<template>\n  <section\n    class=\"sidebar-group\"\n    :class=\"[\n      {\n        collapsable,\n        'is-sub-group': depth"
  },
  {
    "path": "docs/.vuepress/theme/components/SidebarLink.vue",
    "chars": 3291,
    "preview": "<script>\nimport { isActive, hashRE, groupHeaders } from '../util'\n\nexport default {\n  functional: true,\n\n  props: ['item"
  },
  {
    "path": "docs/.vuepress/theme/components/SidebarLinks.vue",
    "chars": 1991,
    "preview": "<template>\n  <ul\n    v-if=\"items.length\"\n    class=\"sidebar-links\"\n  >\n    <li\n      v-for=\"(item, i) in items\"\n      :k"
  },
  {
    "path": "docs/.vuepress/theme/global-components/Badge.vue",
    "chars": 805,
    "preview": "<script>\nexport default {\n  functional: true,\n  props: {\n    type: {\n      type: String,\n      default: 'tip'\n    },\n   "
  },
  {
    "path": "docs/.vuepress/theme/index.js",
    "chars": 1442,
    "preview": "const path = require('path')\n\n// Theme API.\nmodule.exports = (options, ctx) => {\n  const { themeConfig, siteConfig } = c"
  },
  {
    "path": "docs/.vuepress/theme/layouts/404.vue",
    "chars": 530,
    "preview": "<template>\n  <div class=\"theme-container\">\n    <div class=\"theme-default-content\">\n      <h1>404</h1>\n\n      <blockquote"
  },
  {
    "path": "docs/.vuepress/theme/layouts/Layout.vue",
    "chars": 3594,
    "preview": "<template>\n  <div\n    class=\"theme-container\"\n    :class=\"pageClasses\"\n    @touchstart=\"onTouchStart\"\n    @touchend=\"onT"
  },
  {
    "path": "docs/.vuepress/theme/noopModule.js",
    "chars": 18,
    "preview": "export default {}\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/arrow.styl",
    "chars": 577,
    "preview": "@require './config'\n\n.arrow\n  display inline-block\n  width 0\n  height 0\n  &.up\n    border-left 4px solid transparent\n   "
  },
  {
    "path": "docs/.vuepress/theme/styles/code.styl",
    "chars": 2861,
    "preview": "{$contentClass}\n  code\n    color lighten($textColor, 20%)\n    padding 0.25rem 0.5rem\n    margin 0\n    font-size 0.85em\n "
  },
  {
    "path": "docs/.vuepress/theme/styles/config.styl",
    "chars": 41,
    "preview": "$contentClass = '.theme-default-content'\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/custom-blocks.styl",
    "chars": 961,
    "preview": ".custom-block\n  .custom-block-title\n    font-weight 600\n    margin-bottom -0.4rem\n  &.tip, &.warning, &.danger\n    paddi"
  },
  {
    "path": "docs/.vuepress/theme/styles/index.styl",
    "chars": 3434,
    "preview": "@require './config'\n@require './code'\n@require './custom-blocks'\n@require './arrow'\n@require './wrapper'\n@require './toc"
  },
  {
    "path": "docs/.vuepress/theme/styles/mobile.styl",
    "chars": 755,
    "preview": "@require './config'\n\n$mobileSidebarWidth = $sidebarWidth * 0.82\n\n// narrow desktop / iPad\n@media (max-width: $MQNarrow)\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/toc.styl",
    "chars": 54,
    "preview": ".table-of-contents\n  .badge\n    vertical-align middle\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/wrapper.styl",
    "chars": 180,
    "preview": "$wrapper\n  max-width $contentWidth\n  margin 0 auto\n  padding 2rem 2.5rem\n  @media (max-width: $MQNarrow)\n    padding 2re"
  },
  {
    "path": "docs/.vuepress/theme/util/index.js",
    "chars": 5823,
    "preview": "export const hashRE = /#.*$/\nexport const extRE = /\\.(md|html)$/\nexport const endingSlashRE = /\\/$/\nexport const outboun"
  },
  {
    "path": "docs/JavaScript/== _===.md",
    "chars": 2174,
    "preview": "# 面试官:== 和 ===区别,分别在什么情况使用\n\n ![](https://static.vue-js.com/51b208f0-68df-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、等于操作符\n\n等于操作符用"
  },
  {
    "path": "docs/JavaScript/BOM.md",
    "chars": 3538,
    "preview": "# 面试官:说说你对BOM的理解,常见的BOM对象你了解哪些?\n\n ![](https://static.vue-js.com/3e191c40-8089-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n`BO"
  },
  {
    "path": "docs/JavaScript/Dom.md",
    "chars": 5234,
    "preview": "# 面试官:DOM常见的操作有哪些?\n\n ![](https://static.vue-js.com/a89c99a0-7fdc-11eb-ab90-d9ae814b240d.png)\n\n## 一、DOM\n\n文档对象模型 (DOM) 是 `"
  },
  {
    "path": "docs/JavaScript/ajax.md",
    "chars": 3506,
    "preview": "# 面试官:ajax原理是什么?如何实现?\n\n ![](https://static.vue-js.com/a35a2950-7b2a-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n`AJAX `全称(Asy"
  },
  {
    "path": "docs/JavaScript/array_api.md",
    "chars": 4865,
    "preview": "# 面试官:数组的常用方法有哪些?\n\n ![](https://static.vue-js.com/5842e560-67b6-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、操作方法\n\n数组基本操作可以归纳为 增、"
  },
  {
    "path": "docs/JavaScript/bind_call_apply.md",
    "chars": 2733,
    "preview": "# 面试官:bind、call、apply 区别?如何实现一个bind?\n\n ![](https://static.vue-js.com/a900e460-7be4-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、作"
  },
  {
    "path": "docs/JavaScript/cache.md",
    "chars": 3363,
    "preview": "# 面试官:Javascript本地存储的方式有哪些?区别及应用场景?\n\n ![](https://static.vue-js.com/68dccf20-849f-11eb-ab90-d9ae814b240d.png)\n\n## 一、方式\n\n"
  },
  {
    "path": "docs/JavaScript/closure.md",
    "chars": 3168,
    "preview": "# 面试官:说说你对闭包的理解?闭包使用场景\n\n ![](https://static.vue-js.com/c141a030-6a7a-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n一个函数和对其周围状态("
  },
  {
    "path": "docs/JavaScript/context_stack.md",
    "chars": 4197,
    "preview": "# 面试官:JavaScript中执行上下文和执行栈是什么?\n\n![](https://static.vue-js.com/8652b710-74c1-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、执行上下文\n\n简单"
  },
  {
    "path": "docs/JavaScript/continue_to_upload.md",
    "chars": 5802,
    "preview": "# 面试官:大文件上传如何做断点续传?\n\n ![](https://static.vue-js.com/3ccb0e90-8ba4-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n不管怎样简单的需求,在量级达到"
  },
  {
    "path": "docs/JavaScript/copy.md",
    "chars": 4151,
    "preview": "# 面试官:深拷贝浅拷贝的区别?如何实现一个深拷贝?\n\n ![](https://static.vue-js.com/cdf952e0-69b8-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、数据类型存储\n\n前面文章我"
  },
  {
    "path": "docs/JavaScript/data_type.md",
    "chars": 3850,
    "preview": "# 面试官:说说JavaScript中的数据类型?存储上的差别?\n\n ![](https://static.vue-js.com/6d133f90-6463-11eb-ab90-d9ae814b240d.png)\n\n## 前言\n\n在`Jav"
  },
  {
    "path": "docs/JavaScript/debounce_throttle.md",
    "chars": 3311,
    "preview": "# 面试官:什么是防抖和节流?有什么区别?如何实现?\n\n ![](https://static.vue-js.com/912f1a10-8787-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n本质上是优化高频率"
  },
  {
    "path": "docs/JavaScript/event_Model.md",
    "chars": 3888,
    "preview": "# 面试官:说说JavaScript中的事件模型\n\n![](https://static.vue-js.com/32a182f0-74cf-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、事件与事件流\n\n`javascr"
  },
  {
    "path": "docs/JavaScript/event_agent.md",
    "chars": 2607,
    "preview": "# 面试官:解释下什么是事件代理?应用场景?\n\n![](https://static.vue-js.com/a33f0ab0-797e-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n事件代理,俗地来讲,就是把"
  },
  {
    "path": "docs/JavaScript/event_loop.md",
    "chars": 4155,
    "preview": "# 面试官:说说你对事件循环的理解\n\n ![](https://static.vue-js.com/50f062d0-7cb8-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n首先,`JavaScript `是"
  },
  {
    "path": "docs/JavaScript/function_cache.md",
    "chars": 1952,
    "preview": "# 面试官:Javascript中如何实现函数缓存?函数缓存有哪些应用场景?\n\n ![](https://static.vue-js.com/2ae9dda0-85fa-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一"
  },
  {
    "path": "docs/JavaScript/functional_programming.md",
    "chars": 3650,
    "preview": "# 面试官:说说你对函数式编程的理解?优缺点?\n\n ![](https://static.vue-js.com/ec0f6e80-8534-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n函数式编程是一种\"编程"
  },
  {
    "path": "docs/JavaScript/inherit.md",
    "chars": 5700,
    "preview": "# 面试官:Javascript如何实现继承?\n\n![](https://static.vue-js.com/5d9c4450-72a3-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n继承(inheritan"
  },
  {
    "path": "docs/JavaScript/js_data_structure.md",
    "chars": 1816,
    "preview": "# 面试官:说说你了解的js数据结构?\n\n## 什么是数据结构?\n数据结构是计算机存储、组织数据的方式。\n数据结构意味着接口或封装:一个数据结构可被视为两个函数之间的接口,或者是由数据类型联合组成的存储内容的访问方法封装。\n\n我们每天的编码"
  },
  {
    "path": "docs/JavaScript/loss_accuracy.md",
    "chars": 3741,
    "preview": "# 面试官:说说 Javascript 数字精度丢失的问题,如何解决?\n\n ![](https://static.vue-js.com/09646a10-86f4-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、场景复现"
  },
  {
    "path": "docs/JavaScript/memory_leak.md",
    "chars": 2949,
    "preview": "# 面试官:说说 JavaScript 中内存泄漏的几种情况?\n\n  ![](https://static.vue-js.com/19f76b30-824d-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n内存"
  },
  {
    "path": "docs/JavaScript/new.md",
    "chars": 2232,
    "preview": "# 面试官:说说new操作符具体干了什么?\n\n![](https://static.vue-js.com/880d0010-7a39-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在`JavaScript`中"
  },
  {
    "path": "docs/JavaScript/prototype.md",
    "chars": 2789,
    "preview": "# 面试官:JavaScript原型,原型链 ? 有什么特点?\n\n ![](https://static.vue-js.com/4500e170-725e-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、原型\n\n`Jav"
  },
  {
    "path": "docs/JavaScript/pull_up_loading_pull_down_refresh.md",
    "chars": 5229,
    "preview": "# 面试官:如何实现上拉加载,下拉刷新?\n\n ![](https://static.vue-js.com/89cd1850-8adc-11eb-ab90-d9ae814b240d.png)\n\n## 一、前言\n\n下拉刷新和上拉加载这两种交互方"
  },
  {
    "path": "docs/JavaScript/regexp.md",
    "chars": 8929,
    "preview": "# 面试官:说说你对正则表达式的理解?应用场景?\n\n![](https://static.vue-js.com/55388a40-7f1d-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n正则表达式是一种用来匹"
  },
  {
    "path": "docs/JavaScript/scope.md",
    "chars": 2601,
    "preview": "# 面试官:说说你对作用域链的理解\n\n ![](https://static.vue-js.com/16f614a0-718f-11eb-ab90-d9ae814b240d.png)\n\n## 一、作用域\n\n作用域,即变量(变量作用域又称上下"
  },
  {
    "path": "docs/JavaScript/security.md",
    "chars": 5363,
    "preview": "# 面试官:web常见的攻击方式有哪些?如何防御?\n\n ![](https://static.vue-js.com/d0892930-8d1d-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\nWeb攻击(WebA"
  },
  {
    "path": "docs/JavaScript/single_sign.md",
    "chars": 3705,
    "preview": "# 面试官:什么是单点登录?如何实现?\n\n ![](https://static.vue-js.com/8a25a760-8c83-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n单点登录(Single Sig"
  },
  {
    "path": "docs/JavaScript/string_api.md",
    "chars": 3231,
    "preview": "# 面试官:JavaScript字符串的常用方法有哪些?\n\n ![](https://static.vue-js.com/ceb6ebc0-65c1-11eb-ab90-d9ae814b240d.png)\n\n## 一、操作方法\n\n我们也可将"
  },
  {
    "path": "docs/JavaScript/tail_recursion.md",
    "chars": 3442,
    "preview": "# 面试官:举例说明你对尾递归的理解,有哪些应用场景\n\n ![](https://static.vue-js.com/74db8fe0-815d-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、递归\n\n递归(英语:Rec"
  },
  {
    "path": "docs/JavaScript/this.md",
    "chars": 4781,
    "preview": "# 面试官:谈谈this对象的理解\n\n ![](https://static.vue-js.com/46c820d0-74b7-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、定义\n\n函数的 `this` 关键字在 "
  },
  {
    "path": "docs/JavaScript/type_conversion.md",
    "chars": 2903,
    "preview": "# 面试官:谈谈 JavaScript 中的类型转换机制\n\n ![](https://static.vue-js.com/2abd00a0-6692-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、概述\n\n前面我们讲到,"
  },
  {
    "path": "docs/JavaScript/typeof_instanceof.md",
    "chars": 3687,
    "preview": "# 面试官:typeof 与 instanceof 区别\n\n ![](https://static.vue-js.com/3fc158f0-7710-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、typeof\n\n`"
  },
  {
    "path": "docs/JavaScript/visible.md",
    "chars": 5628,
    "preview": "# 面试官:如何判断一个元素是否在可视区域中?\n\n ![](https://static.vue-js.com/d848c790-8a05-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、用途\n可视区域即我们浏览网页的设"
  },
  {
    "path": "docs/NodeJS/Buffer.md",
    "chars": 2991,
    "preview": "# 面试官:说说对 Node 中的 Buffer 的理解?应用场景?\n\n ![](https://static.vue-js.com/176d02b0-c69c-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n"
  },
  {
    "path": "docs/NodeJS/EventEmitter.md",
    "chars": 5230,
    "preview": "# 面试官:说说Node中的EventEmitter? 如何实现一个EventEmitter?\n\n ![](https://static.vue-js.com/16b10390-c83a-11eb-ab90-d9ae814b240d.png"
  },
  {
    "path": "docs/NodeJS/Stream.md",
    "chars": 2867,
    "preview": "# 面试官:说说对 Node 中的 Stream 的理解?应用场景?\n\n ![](https://static.vue-js.com/a5df3c60-c76f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么"
  },
  {
    "path": "docs/NodeJS/event_loop.md",
    "chars": 3801,
    "preview": "# 面试官:说说对Nodejs中的事件循环机制理解?\n\n ![](https://static.vue-js.com/e0faf3c0-c90e-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n在[浏览器事件"
  },
  {
    "path": "docs/NodeJS/file_upload.md",
    "chars": 3505,
    "preview": "# 面试官:如何实现文件上传?说说你的思路\n\n ![](https://static.vue-js.com/248a5580-ce60-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n文件上传在日常开发中应用很"
  },
  {
    "path": "docs/NodeJS/fs.md",
    "chars": 4615,
    "preview": "# 面试官:说说对 Node 中的 fs模块的理解? 有哪些常用方法\n\n ![](https://static.vue-js.com/a141e5c0-c46a-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么"
  },
  {
    "path": "docs/NodeJS/global.md",
    "chars": 2750,
    "preview": "# 面试官:说说 Node. js 有哪些全局对象?\n\n ![](https://static.vue-js.com/79c7b100-c2a3-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在浏览器 `Ja"
  },
  {
    "path": "docs/NodeJS/jwt.md",
    "chars": 3936,
    "preview": "# 面试官:如何实现jwt鉴权机制?说说你的思路\n\n ![](https://static.vue-js.com/efff62b0-cd88-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\nJWT(JSON"
  },
  {
    "path": "docs/NodeJS/middleware.md",
    "chars": 4812,
    "preview": "# 面试官:说说对中间件概念的理解,如何封装 node 中间件?\n\n ![](https://static.vue-js.com/614ae480-cce4-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n中间"
  },
  {
    "path": "docs/NodeJS/nodejs.md",
    "chars": 1333,
    "preview": "# 面试官:说说你对Node.js 的理解?优缺点?应用场景?\n\n ![](https://static.vue-js.com/b565d240-c1e6-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n`N"
  },
  {
    "path": "docs/NodeJS/paging.md",
    "chars": 2613,
    "preview": "# 面试官:如果让你来设计一个分页功能, 你会怎么设计? 前后端如何交互?\n\n ![](https://static.vue-js.com/54b0a390-cf14-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、"
  },
  {
    "path": "docs/NodeJS/performance.md",
    "chars": 3430,
    "preview": "# 面试官:Node性能如何进行监控以及优化?\n\n ![](https://static.vue-js.com/bb37dae0-d179-11eb-ab90-d9ae814b240d.png)\n\n## 一、 是什么\n\n`Node`作为一门"
  },
  {
    "path": "docs/NodeJS/process.md",
    "chars": 2072,
    "preview": "# 面试官:说说对 Node 中的 process 的理解?有哪些常用方法?\n\n![](https://static.vue-js.com/4f7866b0-c2b2-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什"
  },
  {
    "path": "docs/NodeJS/require_order.md",
    "chars": 2253,
    "preview": "# 面试官:说说 Node 文件查找的优先级以及 Require 方法的文件查找策略?\n\n ![](https://static.vue-js.com/15913530-c9ba-11eb-ab90-d9ae814b240d.png)\n\n\n"
  },
  {
    "path": "docs/README.md",
    "chars": 22956,
    "preview": "\n<p align=\"center\">\n<img width=\"300px\"  style=\"max-width:300px; width:300px;\" src=\"https://static.vue-js.com/21494410-8e"
  },
  {
    "path": "docs/React/Binding events.md",
    "chars": 2160,
    "preview": "# 面试官:React事件绑定的方式有哪些?区别?\n\n ![](https://static.vue-js.com/e21f5560-d8fa-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在`react`应"
  },
  {
    "path": "docs/React/Building components.md",
    "chars": 2147,
    "preview": "# 面试官:React构建组件的方式有哪些?区别?\n\n ![](https://static.vue-js.com/04355cb0-da10-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n组件就是把图形"
  },
  {
    "path": "docs/React/Fiber.md",
    "chars": 3336,
    "preview": "# 面试官:说说对Fiber架构的理解?解决了什么问题?\n\n ![](https://static.vue-js.com/554da6d0-ed24-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、问题\n\n`JavaS"
  },
  {
    "path": "docs/React/High order components.md",
    "chars": 4095,
    "preview": "# 面试官:说说对高阶组件的理解?应用场景?\n\n ![](https://static.vue-js.com/c8901850-e197-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n高阶函数(Highe"
  },
  {
    "path": "docs/React/Improve performance.md",
    "chars": 4042,
    "preview": "# 面试官:说说 React 性能优化的手段有哪些?\n\n ![](https://static.vue-js.com/a9e83b00-f270-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n`React`凭"
  },
  {
    "path": "docs/React/JSX to DOM.md",
    "chars": 5145,
    "preview": "# 面试官:说说React Jsx转换成真实DOM过程?\n\n ![](https://static.vue-js.com/1d340620-f00a-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n`reac"
  },
  {
    "path": "docs/React/React Hooks.md",
    "chars": 4174,
    "preview": "# 面试官:说说对React Hooks的理解?解决了什么问题?\n\n ![](https://static.vue-js.com/8d357c50-e12e-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n`H"
  },
  {
    "path": "docs/React/React Router model.md",
    "chars": 3416,
    "preview": "# 面试官:说说React Router有几种模式?实现原理?\n\n\n\n ![](https://static.vue-js.com/065f7a80-e978-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n在"
  },
  {
    "path": "docs/React/React Router.md",
    "chars": 5303,
    "preview": "# 面试官:说说你对React Router的理解?常用的Router组件有哪些?\n\n ![](https://static.vue-js.com/c6635670-e8ac-11eb-85f6-6fac77c0c9b3.png)\n\n## "
  },
  {
    "path": "docs/React/React refs.md",
    "chars": 2379,
    "preview": "# 面试官:说说对React refs 的理解?应用场景? \n\n ![](https://static.vue-js.com/25162040-de02-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n`Refs"
  },
  {
    "path": "docs/React/React.md",
    "chars": 2257,
    "preview": "# 面试官:说说对 React 的理解?有哪些特性?\n\n![](https://static.vue-js.com/671f5a90-d265-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\nReact,用于构"
  },
  {
    "path": "docs/React/Real DOM_Virtual DOM.md",
    "chars": 2448,
    "preview": "# 面试官:说说 Real DOM 和 Virtual DOM 的区别?优缺点?\n\n![](https://static.vue-js.com/f1d36350-d302-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、"
  },
  {
    "path": "docs/React/Redux Middleware.md",
    "chars": 3042,
    "preview": "# 面试官:说说对Redux中间件的理解?常用的中间件有哪些?实现原理?\n\n ![](https://static.vue-js.com/4520bbd0-e699-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是"
  },
  {
    "path": "docs/React/SyntheticEvent.md",
    "chars": 3374,
    "preview": "# 面试官:说说React的事件机制?\n\n ![](https://static.vue-js.com/f054f080-d86f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n`React`基于浏览器的"
  },
  {
    "path": "docs/React/animation.md",
    "chars": 4819,
    "preview": "# 面试官:在react中组件间过渡动画如何实现?\n ![](https://static.vue-js.com/294f1e00-e4b0-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n在日常开发中,页面"
  },
  {
    "path": "docs/React/capture error.md",
    "chars": 2199,
    "preview": "# 面试官:说说你在React项目是如何捕获错误的?\n\n ![](https://static.vue-js.com/8db1b5c0-f288-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n错误在我们日常编"
  },
  {
    "path": "docs/React/class_function component.md",
    "chars": 3550,
    "preview": "# 面试官:说说对React中类组件和函数组件的理解?有什么区别?\n\n ![](https://static.vue-js.com/6c196d80-de39-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、类组件\n\n类"
  },
  {
    "path": "docs/React/communication.md",
    "chars": 3040,
    "preview": "# 面试官:React中组件之间如何通信?\n\n ![](https://static.vue-js.com/767a2800-dc9f-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n我们将组件间通信可以拆"
  },
  {
    "path": "docs/React/controlled_Uncontrolled.md",
    "chars": 1673,
    "preview": "# 面试官:说说对受控组件和非受控组件的理解?应用场景?\n\n ![](https://static.vue-js.com/12990fd0-df2f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、受控组件\n\n受控组"
  },
  {
    "path": "docs/React/diff.md",
    "chars": 3602,
    "preview": "# 面试官:说说React diff的原理是什么?\n\n ![](https://static.vue-js.com/967e6150-ec91-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、是什么\n\n跟`Vue`一致"
  },
  {
    "path": "docs/React/how to use redux.md",
    "chars": 3313,
    "preview": "# 面试官:你在React项目中是如何使用Redux的? 项目结构是如何划分的?\n\n ![](https://static.vue-js.com/31a4aff0-e7dc-11eb-ab90-d9ae814b240d.png)\n\n## 一"
  },
  {
    "path": "docs/React/immutable.md",
    "chars": 4106,
    "preview": "# 面试官:说说你对immutable的理解?如何应用在react项目中?\n\n ![](https://static.vue-js.com/797e9470-ea3f-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什"
  },
  {
    "path": "docs/React/import css.md",
    "chars": 3901,
    "preview": "# 面试官:说说react中引入css的方式有哪几种?区别?\n\n ![](https://static.vue-js.com/7d825230-e217-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n组件式开"
  },
  {
    "path": "docs/React/improve_render.md",
    "chars": 2653,
    "preview": "# 面试官:说说你是如何提高组件的渲染效率的?在React中如何避免不必要的render?\n\n ![](https://static.vue-js.com/de2d7e20-ecf8-11eb-85f6-6fac77c0c9b3.png)\n"
  },
  {
    "path": "docs/React/key.md",
    "chars": 2136,
    "preview": "# 面试官:React中的key有什么作用?\n\n ![](https://static.vue-js.com/31677360-dd69-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n首先,先给出`react"
  },
  {
    "path": "docs/React/life cycle.md",
    "chars": 2893,
    "preview": "# 面试官:说说 React 生命周期有哪些不同阶段?每个阶段对应的方法是?\n\n ![](https://static.vue-js.com/5c717010-d373-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、"
  },
  {
    "path": "docs/React/redux.md",
    "chars": 3627,
    "preview": "# 说说你对Redux的理解?其工作原理?\n\n ![](https://static.vue-js.com/52394be0-e2a5-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n`React`是用于构建用"
  },
  {
    "path": "docs/React/render.md",
    "chars": 3511,
    "preview": "# 面试官:说说React render方法的原理?在什么时候会被触发?\n\n ![](https://static.vue-js.com/3d855230-ec6d-11eb-ab90-d9ae814b240d.png)\n\n## 一、原理\n"
  },
  {
    "path": "docs/React/server side rendering.md",
    "chars": 6834,
    "preview": "# 面试官:说说React服务端渲染怎么做?原理是什么?\n\n ![](https://static.vue-js.com/8c93cbe0-f3f7-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在[SSR中"
  },
  {
    "path": "docs/React/setState.md",
    "chars": 3672,
    "preview": "# 面试官:说说 React中的setState执行机制\n\n ![](https://static.vue-js.com/3acb8ca0-d825-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n一个组件的显"
  },
  {
    "path": "docs/React/state_props.md",
    "chars": 1781,
    "preview": "# 面试官:state 和 props 有什么区别?\n\n![](https://static.vue-js.com/7f272780-d440-11eb-ab90-d9ae814b240d.png)\n\n## 一、state\n\n一个组件的显示"
  },
  {
    "path": "docs/React/summary.md",
    "chars": 4497,
    "preview": "# 面试官:说说你在使用React 过程中遇到的常见问题?如何解决?\n\n\n ![](https://static.vue-js.com/7efcd400-f47d-11eb-ab90-d9ae814b240d.png)\n\n## 一、前言\n在"
  },
  {
    "path": "docs/React/super()_super(props).md",
    "chars": 2635,
    "preview": "# 面试官:super() 和 super(props) 有什么区别?\n\n![](https://static.vue-js.com/618abaf0-d71c-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、ES6 类"
  },
  {
    "path": "docs/algorithm/Algorithm.md",
    "chars": 1645,
    "preview": "# 面试官:说说你对算法的理解?应用场景?\n\n\n ![](https://static.vue-js.com/eca03690-1620-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n算法(Algorithm"
  },
  {
    "path": "docs/algorithm/BinarySearch.md",
    "chars": 3614,
    "preview": "# 面试官:说说你对二分查找的理解?如何实现?应用场景?\n\n ![](https://static.vue-js.com/d43ca230-2987-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n在计算机科"
  },
  {
    "path": "docs/algorithm/Heap.md",
    "chars": 2898,
    "preview": "# 面试官:说说你对堆的理解?如何实现?应用场景?\n\n\n ![](https://static.vue-js.com/dd12c700-1ed7-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n堆(Heap)"
  },
  {
    "path": "docs/algorithm/Linked List.md",
    "chars": 2390,
    "preview": "# 面试官:说说你对链表的理解?常见的操作有哪些?\n\n ![](https://static.vue-js.com/d6638dd0-1c76-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n链表(Linked"
  },
  {
    "path": "docs/algorithm/bubbleSort.md",
    "chars": 2704,
    "preview": "# 面试官:说说你对冒泡排序的理解?如何实现?应用场景?\n\n ![](https://static.vue-js.com/6f5e0850-2652-11ec-a752-75723a64e8f5.png)\n\n\n## 一、是什么\n\n冒泡排序("
  },
  {
    "path": "docs/algorithm/design1.md",
    "chars": 1863,
    "preview": "# 面试官:说说你对分而治之、动态规划的理解?区别?\n\n\n\n ![](https://static.vue-js.com/298437b0-29d0-11ec-a752-75723a64e8f5.png)\n\n\n## 一、分而治之\n\n分而治之"
  },
  {
    "path": "docs/algorithm/design2.md",
    "chars": 2259,
    "preview": "# 面试官:说说你对贪心算法、回溯算法的理解?应用场景?\n\n\n\n ![](https://static.vue-js.com/1d49eae0-2e8e-11ec-a752-75723a64e8f5.png)\n\n## 一、贪心算法\n\n贪心算"
  },
  {
    "path": "docs/algorithm/graph.md",
    "chars": 2322,
    "preview": "# 面试官:说说你对图的理解?相关操作有哪些?\n\n\n ![](https://static.vue-js.com/7876c2f0-2059-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n在计算机科学中,图"
  },
  {
    "path": "docs/algorithm/insertionSort.md",
    "chars": 1827,
    "preview": "# 面试官:说说你对插入排序的理解?如何实现?应用场景?\n\n ![](https://static.vue-js.com/912adc10-267f-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n插入排序"
  },
  {
    "path": "docs/algorithm/mergeSort.md",
    "chars": 2659,
    "preview": "# 面试官:说说你对归并排序的理解?如何实现?应用场景?\n\n ![](https://static.vue-js.com/fa1d5720-26ac-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n归并排序"
  },
  {
    "path": "docs/algorithm/quickSort.md",
    "chars": 1896,
    "preview": "# 面试官:说说你对快速排序的理解?如何实现?应用场景?\n\n\n\n ![](https://static.vue-js.com/bafae570-268a-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n快速排序"
  },
  {
    "path": "docs/algorithm/selectionSort.md",
    "chars": 2060,
    "preview": "# 面试官:说说你对选择排序的理解?如何实现?应用场景?\n\n ![](https://static.vue-js.com/50a05ed0-2671-11ec-a752-75723a64e8f5.png)\n\n## 一、是什么\n\n选择排序(S"
  },
  {
    "path": "docs/algorithm/set.md",
    "chars": 1885,
    "preview": "# 面试官:说说你对集合的理解?常见的操作有哪些?\n\n\n\n ![](https://static.vue-js.com/e3de7810-1d36-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n集合(Se"
  },
  {
    "path": "docs/algorithm/sort.md",
    "chars": 2342,
    "preview": "# 面试官:说说常见的排序算法有哪些?区别?\n\n\n ![](https://static.vue-js.com/63eb7920-211c-11ec-a752-75723a64e8f5.png)\n\n## 一、是什么\n\n排序是程序开发中非常常"
  },
  {
    "path": "docs/algorithm/stack_queue.md",
    "chars": 3091,
    "preview": "# 面试官:说说你对栈、队列的理解?应用场景?\n\n ![](https://static.vue-js.com/bc57f530-1b99-11ec-a752-75723a64e8f5.png)\n\n## 一、栈\n\n栈(stack)又名堆栈,"
  },
  {
    "path": "docs/algorithm/structure.md",
    "chars": 1531,
    "preview": "# 面试官:说说你对数据结构的理解?有哪些?区别?\n\n ![](https://static.vue-js.com/3d87b540-1aa6-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n数据结构是计算"
  },
  {
    "path": "docs/algorithm/time_space.md",
    "chars": 2688,
    "preview": "# 面试官:说说你对算法中时间复杂度,空间复杂度的理解?如何计算?\n\n\n\n ![](https://static.vue-js.com/07fd4050-16fc-11ec-a752-75723a64e8f5.png)\n\n## 一、前言\n\n"
  },
  {
    "path": "docs/algorithm/tree.md",
    "chars": 3223,
    "preview": "# 面试官:说说你对树的理解?相关的操作有哪些?\n\n\n\n ![](https://static.vue-js.com/5a7616f0-1dfe-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n在计算机领域"
  },
  {
    "path": "docs/applet/WebView_jscore.md",
    "chars": 2012,
    "preview": "-  # 面试官:说说微信小程序的实现原理?\n\n  \n\n   ![](https://static.vue-js.com/4407cb60-3722-11ec-a752-75723a64e8f5.png)\n\n  ## 一、背景\n\n  网页开"
  },
  {
    "path": "docs/applet/applet.md",
    "chars": 1475,
    "preview": "# 面试官:说说你对微信小程序的理解?优缺点?\n\n ![](https://static.vue-js.com/be367c80-300e-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n2017年,微信正式推"
  },
  {
    "path": "docs/applet/lifecycle.md",
    "chars": 3545,
    "preview": "# 面试官:说说微信小程序的生命周期函数有哪些?\n\n\n\n ![](https://static.vue-js.com/1df64890-30e0-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n跟`vue`、"
  },
  {
    "path": "docs/applet/login.md",
    "chars": 1667,
    "preview": "# 面试官:说说微信小程序的登录流程?\n\n\n\n ![](https://static.vue-js.com/aa3ccbd0-3428-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、背景\n\n传统的`web`开发实现登"
  },
  {
    "path": "docs/applet/navigate.md",
    "chars": 2165,
    "preview": "# 面试官:说说微信小程序中路由跳转的方式有哪些?区别?\n\n\n\n ![](https://static.vue-js.com/52bd3820-31a5-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n微信"
  },
  {
    "path": "docs/applet/optimization.md",
    "chars": 1728,
    "preview": "# 面试官:说说提高微信小程序的应用速度的手段有哪些?\n\n ![](https://static.vue-js.com/f606d530-3278-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n小程序启动"
  },
  {
    "path": "docs/applet/publish.md",
    "chars": 1696,
    "preview": "# 面试官:说说微信小程序的发布流程?\n\n ![](https://static.vue-js.com/d5cccdf0-3652-11ec-8e64-91fdec0f05a1.png)\n\n## 一、背景\n\n在中大型的公司里,人员的分工非常"
  },
  {
    "path": "docs/applet/requestPayment.md",
    "chars": 1600,
    "preview": "# 面试官:说说微信小程序的支付流程?\n\n ![](https://static.vue-js.com/2266fff0-34a0-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、前言\n\n微信小程序为电商类小程序,提"
  },
  {
    "path": "docs/css/BFC.md",
    "chars": 3431,
    "preview": "# 面试官:谈谈你对BFC的理解?\n\n![](https://static.vue-js.com/c3d68290-9511-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n我们在页面布局的时候,经常出现以下情"
  },
  {
    "path": "docs/css/animation.md",
    "chars": 5469,
    "preview": "# 面试官:css3动画有哪些?\n\n ![](https://static.vue-js.com/d12e2380-9c0a-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\nCSS动画(CSS Animat"
  },
  {
    "path": "docs/css/box.md",
    "chars": 2077,
    "preview": "# 面试官:说说你对盒子模型的理解?\n\n![](https://static.vue-js.com/8d0e9ca0-8f9b-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n当对一个文档进行布局(layout"
  },
  {
    "path": "docs/css/center.md",
    "chars": 4416,
    "preview": "# 面试官:元素水平垂直居中的方法有哪些?如果元素不定宽高呢?\n\n ![](https://static.vue-js.com/7b64c8d0-95f9-11eb-ab90-d9ae814b240d.png)\n\n## 一、背景\n\n在开发中"
  },
  {
    "path": "docs/css/column_layout.md",
    "chars": 6184,
    "preview": "# 面试官:如何实现两栏布局,右侧自适应?三栏布局中间自适应呢?\n\n ![](https://static.vue-js.com/f335d400-976e-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、背景\n\n在日常"
  },
  {
    "path": "docs/css/css3_features.md",
    "chars": 3765,
    "preview": "# 面试官:CSS3新增了哪些新特性?\n\n ![](https://static.vue-js.com/d58f6df0-9b5e-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n`css`,即层叠样式表(C"
  },
  {
    "path": "docs/css/css_performance.md",
    "chars": 2485,
    "preview": "# 面试官:如果要做优化,CSS提高性能的方法有哪些?\n\n ![](https://static.vue-js.com/c071c820-9fa3-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、前言\n\n每一个网页都"
  },
  {
    "path": "docs/css/dp_px_dpr_ppi.md",
    "chars": 2797,
    "preview": "# 面试官:说说设备像素、css像素、设备独立像素、dpr、ppi 之间的区别?\n\n ![](https://static.vue-js.com/c4d9bfd0-91f2-11eb-85f6-6fac77c0c9b3.png)\n\n## 一"
  },
  {
    "path": "docs/css/em_px_rem_vh_vw.md",
    "chars": 2216,
    "preview": "# 面试官:说说em/px/rem/vh/vw区别?\n\n![](https://static.vue-js.com/51b036e0-9131-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、介绍\n\n传统的项目开发中,我"
  },
  {
    "path": "docs/css/flexbox.md",
    "chars": 4706,
    "preview": "# 面试官:说说flexbox(弹性盒布局模型),以及适用场景?\n\n ![](https://static.vue-js.com/ef25b0a0-9837-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n"
  },
  {
    "path": "docs/css/grid.md",
    "chars": 6258,
    "preview": "# 面试官:介绍一下grid网格布局\n\n ![](https://static.vue-js.com/4d73e3d0-9a94-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、是什么\n\n`Grid` 布局即网格布局,"
  },
  {
    "path": "docs/css/hide_attributes.md",
    "chars": 2509,
    "preview": "# 面试官:css中,有哪些方式可以隐藏页面元素?区别?\n\n![](https://static.vue-js.com/ccf96f50-929a-11eb-ab90-d9ae814b240d.png)\n\n## 一、前言\n\n在平常的样式排版"
  },
  {
    "path": "docs/css/layout_painting.md",
    "chars": 3937,
    "preview": "# 面试官:怎么理解回流跟重绘?什么场景下会触发?\n\n ![](https://static.vue-js.com/1ed5d340-9cdc-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在`HTML`中,"
  },
  {
    "path": "docs/css/less_12px.md",
    "chars": 2355,
    "preview": "# 面试官:让Chrome支持小于12px 的文字方式有哪些?区别?\n\n![](https://static.vue-js.com/62945fd0-a334-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、背景\n\nCh"
  },
  {
    "path": "docs/css/responsive_layout.md",
    "chars": 3208,
    "preview": "# 面试官:什么是响应式设计?响应式设计的基本原理是什么?如何做?\n\n ![](https://static.vue-js.com/a57e2e40-9dba-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n响"
  },
  {
    "path": "docs/css/sass_less_stylus.md",
    "chars": 3967,
    "preview": "# 面试官:说说对Css预编语言的理解?有哪些区别?\n\n ![](https://static.vue-js.com/81cca1c0-a42c-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、是什么\n\n`Css` 作"
  },
  {
    "path": "docs/css/selector.md",
    "chars": 3634,
    "preview": "# 面试官:css选择器有哪些?优先级?哪些属性可以继承?\n\n ![](https://static.vue-js.com/f7dcd330-8fe1-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、选择器\nCSS选"
  },
  {
    "path": "docs/css/single_multi_line.md",
    "chars": 3839,
    "preview": "# 面试官:如何实现单行/多行文本溢出的省略样式?\n\n ![](https://static.vue-js.com/ada8d840-a0e9-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、前言\n\n在日常开发展示页面"
  },
  {
    "path": "docs/css/triangle.md",
    "chars": 2902,
    "preview": "# 面试官:CSS如何画一个三角形?原理是什么?\n\n![](https://static.vue-js.com/bd310120-a279-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、前言\n\n在前端开发的时候,我们有"
  },
  {
    "path": "docs/css/visual_scrolling.md",
    "chars": 3882,
    "preview": "# 面试官:如何使用css完成视差滚动效果?\n\n ![](https://static.vue-js.com/1b2d33e0-a18d-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n视差滚动(Paralla"
  },
  {
    "path": "docs/design/Factory  Pattern.md",
    "chars": 3911,
    "preview": "# 面试官:说说你对工厂模式的理解?应用场景?\n\n\n ![](https://static.vue-js.com/27a84d10-3bea-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n工厂模式是用来创建对"
  },
  {
    "path": "docs/design/Observer  Pattern.md",
    "chars": 4571,
    "preview": "# 面试官:说说你对发布订阅、观察者模式的理解?区别?\n\n ![](https://static.vue-js.com/342739f0-3fb1-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、观察者模式\n\n观察者模"
  },
  {
    "path": "docs/design/Proxy Pattern.md",
    "chars": 3506,
    "preview": "# 面试官:说说你对代理模式的理解?应用场景?\n\n ![](https://static.vue-js.com/899a6ef0-3d6a-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n代理模式(Proxy"
  },
  {
    "path": "docs/design/Singleton Pattern.md",
    "chars": 3393,
    "preview": "# 面试官:说说你对单例模式的理解?如何实现?\n\n![](https://static.vue-js.com/7df7d830-3b2b-11ec-8e64-91fdec0f05a1.png)\n\n\n\n  ## 一、是什么\n\n  单例模式(S"
  },
  {
    "path": "docs/design/Strategy Pattern.md",
    "chars": 4032,
    "preview": "# 面试官:说说你对策略模式的理解?应用场景?\n\n ![](https://static.vue-js.com/e4aad950-3cb2-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n策略模式(Stra"
  },
  {
    "path": "docs/design/design.md",
    "chars": 1870,
    "preview": "# 面试官:说说对设计模式的理解?常见的设计模式有哪些?\n\n![](https://static.vue-js.com/065bc170-37ce-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n在软件工程"
  },
  {
    "path": "docs/es6/array.md",
    "chars": 5356,
    "preview": "# 面试官:ES6中数组新增了哪些扩展?\n\n ![](https://static.vue-js.com/a156b8d0-53c5-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、扩展运算符的应用\n\nES6通过扩展元素"
  },
  {
    "path": "docs/es6/decorator.md",
    "chars": 4019,
    "preview": "# 面试官:你是怎么理解ES6中 Decorator 的?使用场景?\n\n ![](https://static.vue-js.com/7df43560-5ba5-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、介绍\n\nD"
  },
  {
    "path": "docs/es6/function.md",
    "chars": 3169,
    "preview": "#  面试官:对象新增了哪些扩展?\n\n![](https://static.vue-js.com/54a04a10-5569-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、参数\n\n`ES6`允许为函数的参数设置默认"
  },
  {
    "path": "docs/es6/generator.md",
    "chars": 5655,
    "preview": "# 面试官:你是怎么理解ES6中 Generator的?使用场景?\n\n ![](https://static.vue-js.com/7db499b0-5947-11eb-ab90-d9ae814b240d.png)\n\n## 一、介绍\n\nGe"
  },
  {
    "path": "docs/es6/module.md",
    "chars": 5039,
    "preview": "# 面试官:你是怎么理解ES6中Module的?使用场景?\n\n ![](https://static.vue-js.com/b6d19be0-5adb-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、介绍\n\n模块,("
  },
  {
    "path": "docs/es6/object.md",
    "chars": 4529,
    "preview": "#  面试官:对象新增了哪些扩展?\n\n![](https://static.vue-js.com/4da4dd40-5427-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、属性的简写\n\nES6中,当对象键名与对应值"
  },
  {
    "path": "docs/es6/promise.md",
    "chars": 7461,
    "preview": "#  面试官:你是怎么理解ES6中 Promise的?使用场景?\n\n![](https://static.vue-js.com/f033b160-5811-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、介绍\n\n`P"
  },
  {
    "path": "docs/es6/proxy.md",
    "chars": 6760,
    "preview": "# 面试官:你是怎么理解ES6中Proxy的?使用场景?\n\n ![](https://static.vue-js.com/6f656e30-59f5-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、介绍\n\n**定义:"
  },
  {
    "path": "docs/es6/set_map.md",
    "chars": 6190,
    "preview": "# 面试官:你是怎么理解ES6新增Set、Map两种数据结构的?\n\n ![](https://static.vue-js.com/2b947d00-560c-11eb-85f6-6fac77c0c9b3.png)\n\n如果要用一句来描述,我们"
  },
  {
    "path": "docs/es6/var_let_const.md",
    "chars": 3754,
    "preview": "# 面试官:说说var、let、const之间的区别\n\n![](https://static.vue-js.com/d2aba2e0-50f7-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、var\n\n在ES5中,顶层对"
  },
  {
    "path": "docs/git/Git.md",
    "chars": 1588,
    "preview": "# 面试官:说说你对Git的理解?\n\n\n\n ![](https://static.vue-js.com/213eba50-f79c-11eb-bc6f-3f06e1491664.png)\n\n## 一、是什么\n\ngit,是一个分布式版本控制软"
  },
  {
    "path": "docs/git/HEAD_tree_index.md",
    "chars": 1615,
    "preview": "# 面试官:说说Git 中 HEAD、工作树和索引之间的区别?\n\n ![](https://static.vue-js.com/2de056a0-fa40-11eb-991d-334fd31f0201.png)\n\n## 一、HEAD\n\n在`"
  },
  {
    "path": "docs/git/Version control.md",
    "chars": 1850,
    "preview": "# 面试官:说说你对版本管理的理解?常用的版本管理工具有哪些?\n\n\n\n ![](https://static.vue-js.com/f0e8a2d0-f5ac-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n版"
  },
  {
    "path": "docs/git/command.md",
    "chars": 2997,
    "preview": "# 面试官:说说Git常用的命令有哪些?\n\n ![](https://static.vue-js.com/f66b3290-f7af-11eb-bc6f-3f06e1491664.png)\n\n\n\n## 一、前言\n\n`git `的操作可以通过"
  },
  {
    "path": "docs/git/conflict.md",
    "chars": 2255,
    "preview": "# 面试官:说说 git 发生冲突的场景?如何解决?\n\n ![](https://static.vue-js.com/8aeccc40-fdb3-11eb-bc6f-3f06e1491664.png)\n\n## 一、是什么\n\n一般情况下,出现"
  },
  {
    "path": "docs/git/fork_clone_branch.md",
    "chars": 2273,
    "preview": "# 面试官:说说Git中 fork, clone,branch这三个概念,有什么区别?\n\n ![](https://static.vue-js.com/9c4eb9a0-f7ad-11eb-bc6f-3f06e1491664.png)\n\n\n"
  },
  {
    "path": "docs/git/git pull _git fetch.md",
    "chars": 1927,
    "preview": "# 说说对git pull 和 git fetch 的理解?有什么区别?\n\n ![](https://static.vue-js.com/cc90c050-fac2-11eb-991d-334fd31f0201.png)\n\n\n\n## 一、是"
  },
  {
    "path": "docs/git/git rebase_ git merge.md",
    "chars": 2275,
    "preview": "# 面试官:说说你对git rebase 和 git merge的理解?区别?\n\n\n\n ![](https://static.vue-js.com/77590970-fdd4-11eb-bc6f-3f06e1491664.png)\n\n## "
  },
  {
    "path": "docs/git/git reset_ git revert.md",
    "chars": 2124,
    "preview": "# 面试官:说说你对git reset 和 git revert 的理解?区别?\n\n![](https://static.vue-js.com/046b4440-ff74-11eb-bc6f-3f06e1491664.png)\n\n\n## 一"
  },
  {
    "path": "docs/git/git stash.md",
    "chars": 2281,
    "preview": "# 面试官:说说你对git stash 的理解?应用场景?\n\n ![](https://static.vue-js.com/83ddf210-fd6f-11eb-bc6f-3f06e1491664.png)\n\n\n\n## 一、是什么\n\nsta"
  },
  {
    "path": "docs/http/1.0_1.1_2.0.md",
    "chars": 2683,
    "preview": "# 面试官:说说 HTTP1.0/1.1/2.0 的区别?\n\n ![](https://static.vue-js.com/e167a580-b93a-11eb-ab90-d9ae814b240d.png)\n\n## 一、HTTP1.0\n`H"
  },
  {
    "path": "docs/http/CDN.md",
    "chars": 1672,
    "preview": "# 面试官:如何理解CDN?说说实现原理?\n\n![](https://static.vue-js.com/437ae0f0-b86b-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\nCDN (全称 Conten"
  },
  {
    "path": "docs/http/DNS.md",
    "chars": 1921,
    "preview": "# 面试官:DNS协议 是什么?说说DNS 完整的查询过程?\n\n ![](https://static.vue-js.com/88081710-b78f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\nDN"
  },
  {
    "path": "docs/http/GET_POST.md",
    "chars": 2030,
    "preview": "# 面试官:说一下 GET 和 POST 的区别?\n\n ![](https://static.vue-js.com/6e8d19e0-bc3d-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n`GET`和`"
  },
  {
    "path": "docs/http/HTTPS.md",
    "chars": 2901,
    "preview": "# 面试官:为什么说HTTPS比HTTP安全? HTTPS是如何保证安全的?\n\n ![](https://static.vue-js.com/b5512250-b2ff-11eb-ab90-d9ae814b240d.png)\n\n## 一、安"
  },
  {
    "path": "docs/http/HTTP_HTTPS.md",
    "chars": 1723,
    "preview": "# 面试官:什么是HTTP? HTTP 和 HTTPS 的区别?\n\n ![](https://static.vue-js.com/f50c71f0-b20b-11eb-ab90-d9ae814b240d.png)\n\n## 一、HTTP\n\n`"
  },
  {
    "path": "docs/http/OSI.md",
    "chars": 2165,
    "preview": "# 面试官:如何理解OSI七层模型?\n\n ![](https://static.vue-js.com/e2e1b910-b61e-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\nOSI (Open System"
  },
  {
    "path": "docs/http/TCP_IP.md",
    "chars": 2611,
    "preview": "# 面试官:如何理解TCP/IP协议?\n\n ![](https://static.vue-js.com/4f69a930-b647-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\nTCP/IP,**传输控制"
  },
  {
    "path": "docs/http/UDP_TCP.md",
    "chars": 2559,
    "preview": "# 面试官:如何理解UDP 和 TCP? 区别? 应用场景?\n\n ![](https://static.vue-js.com/85ad65b0-b393-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、UDP\n\nUDP"
  },
  {
    "path": "docs/http/WebSocket.md",
    "chars": 2320,
    "preview": "# 面试官:说说对WebSocket的理解?应用场景?\n\n ![](https://static.vue-js.com/a358a8c0-c0f1-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\nWebSock"
  },
  {
    "path": "docs/http/after_url.md",
    "chars": 1623,
    "preview": "# 面试官:说说地址栏输入 URL 敲下回车后发生了什么?\n\n ![](https://static.vue-js.com/11bf1f20-bdf4-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、简单分析\n\n简单的"
  }
]

// ... and 72 more files (download for full content)

About this extraction

This page contains the full source code of the febobo/web-interview GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 272 files (1.3 MB), approximately 548.7k tokens, and a symbol index with 17 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!