[
  {
    "path": ".github/workflows/blank.yml",
    "content": "# 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  # Triggers the workflow on push or pull request events but only for the master branch\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  # This workflow contains a single job called \"build\"\n  build:\n    # The type of runner that the job will run on\n    runs-on: ubuntu-latest\n\n    # Steps represent a sequence of tasks that will be executed as part of the job\n    steps:\n      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n      - uses: actions/checkout@v2\n\n      - name: use Node.js\n        # 使用action库  actions/setup-node安装node\n        uses: actions/setup-node@v1\n        with:\n          node-version: 10.x\n      # 安装依赖\n      - name: npm install\n        run: npm install\n      # 打包\n      - name: npm build\n        run: npm run build\n      - name: deploy\n        uses: easingthemes/ssh-deploy@v2.1.1\n        env:\n          # 私钥\n          SSH_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}\n          SOURCE: \"./dist\"\n          REMOTE_HOST: ${{ secrets.HOST }}\n          REMOTE_USER: \"root\"\n          TARGET: ${{ secrets.PATH }}\n\n\n\n\n          \n"
  },
  {
    "path": ".gitignore",
    "content": "/coverage\n/docs/.vuepress/dist\n/examples/**/build.js\n/test/e2e/reports\n/test/e2e/screenshots\n/types/typings\n/types/test/*.js\n*.log\n.DS_Store\nnode_modules\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"eggHelper.serverPort\": 35684\n}"
  },
  {
    "path": "docs/.vuepress/config.js",
    "content": "module.exports = {\n  // title: \"Vue3源码解析 - vue中文社区\",\n  title: \"web前端面试 - 面试官系列\",\n  description: \"web前端面试,vue面试题,react面试题,js面试题,大厂面试题,阿里面试题,京东面试题\",\n  base: '/interview/',\n  head: [\n    [\"link\", { rel: \"icon\", href: \"/onepunch.jpeg\" }],\n    [\n      \"meta\",\n      {\n        name: \"keywords\",\n        content:\n          \"web前端面试,vue面试题,react面试题,js面试题,大厂面试题,阿里面试题,京东面试题\",\n      },\n    ],\n    // [\n    //   \"script\",\n    //   { src: \"https://hm.baidu.com/hm.js?db1f163122162bcdb6d04f76b5c1df17\" },\n    // ],\n  ],\n  themeConfig: {\n    repo: \"febobo/web-interview\",\n    // 自定义仓库链接文字。默认从 `themeConfig.repo` 中自动推断为\n    // \"GitHub\"/\"GitLab\"/\"Bitbucket\" 其中之一，或是 \"Source\"。\n    repoLabel: \"Github\",\n\n    // 以下为可选的编辑链接选项\n\n    // 假如你的文档仓库和项目本身不在一个仓库：\n    docsRepo: \"febobo/web-interview\",\n    docsDir: \"docs\",\n    docsBranch: \"master\",\n    // 默认是 false, 设置为 true 来启用\n    editLinks: false,\n    // 默认为 \"Edit this page\"\n    editLinkText: \"帮助我们改善此页面！\",\n    // displayAllHeaders: true,\n    sidebar: [\n      {\n        title: \"Vue系列  ( 已完结..)\",\n        collapsable: false,\n        children: [\n          [\"vue/vue\", \"说说你对vue的理解?\"],\n          [\"vue/spa\", \"说说你对SPA（单页应用）的理解?\"],\n          [\"vue/show_if\", \"Vue中的v-show和v-if怎么理解？\"],\n          [\"vue/new_vue\", \"Vue实例挂载的过程中发生了什么?\"],\n          [\"vue/lifecycle\", \"说说你对Vue生命周期的理解?\"],\n          [\"vue/if_for\", \"为什么Vue中的v-if和v-for不建议一起用?\"],\n          [\"vue/first_page_time\", \"SPA（单页应用）首屏加载速度慢怎么解决？\"],\n          [\"vue/data\", \"为什么data属性是一个函数而不是一个对象？\"],\n          [\"vue/data_object_add_attrs\", \"Vue中给对象添加新属性界面不刷新?\"],\n          [\"vue/components_plugin\", \"Vue中组件和插件有什么区别？\"],\n          [\"vue/communication\", \"Vue组件间通信方式都有哪些?\"],\n          [\"vue/bind\", \"说说你对双向绑定的理解?\"],\n          [\"vue/nexttick\", \"说说你对nexttick的理解?\"],\n          [\"vue/mixin\", \"说说你对vue的mixin的理解，有什么应用场景？\"],\n          [\"vue/slot\", \"说说你对slot的理解？slot使用场景有哪些？\"],\n          [\"vue/observable\", \"Vue.observable你有了解过吗？说说看\"],\n          [\"vue/key\", \"你知道vue中key的原理吗？说说你对它的理解？\"],\n          [\"vue/keepalive\", \"怎么缓存当前的组件？缓存后怎么更新？说说你对keep-alive的理解是什么？\"],\n          [\"vue/modifier\", \"Vue常用的修饰符有哪些？有什么应用场景？\"],\n          [\"vue/directive\", \"你有写过自定义指令吗？自定义指令的应用场景有哪些？\"],\n          [\"vue/filter\", \"Vue中的过滤器了解吗？过滤器的应用场景有哪些？\"],\n          [\"vue/vnode\", \"什么是虚拟DOM？如何实现一个虚拟DOM？说说你的思路\"],\n          [\"vue/diff\", \"你了解vue的diff算法吗？说说看\"],\n          [\"vue/axios\", \"Vue项目中有封装过axios吗？主要是封装哪方面的？\"],\n          [\"vue/axiosCode\", \"你了解axios的原理吗？有看过它的源码吗？\"],\n          [\"vue/ssr\", \"SSR解决了什么问题？有做过SSR吗？你是怎么做的？\"],\n          [\"vue/structure\", \"说下你的vue项目的目录结构，如果是大型项目你该怎么划分结构和划分组件呢？\"],\n          [\"vue/permission\", \"vue要做权限管理该怎么做？如果控制到按钮级别的权限怎么做？\"],\n          [\"vue/cors\", \"Vue项目中你是如何解决跨域的呢？\"],\n          [\"vue/404\", \"vue项目本地开发完成后部署到服务器后报404是什么原因呢？\"],\n          [\"vue/error\", \"你是怎么处理vue项目中的错误的？\"],\n          [\"vue/vue3_vue2\", \"Vue3有了解过吗？能说说跟Vue2的区别吗？\"]\n        ],\n      },\n      {\n        title: \"Vue3系列  ( 已完结..)\",\n        collapsable: false,\n        children: [\n          [\"vue3/goal\", \"Vue3.0的设计目标是什么？做了哪些优化?\"],\n          [\"vue3/performance\", \"Vue3.0 性能提升主要是通过哪几方面体现的？\"],\n          [\"vue3/proxy\", \"Vue3.0里为什么要用 Proxy API 替代 defineProperty API ？\"],\n          [\"vue3/composition\", \"Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同？\"],\n          [\"vue3/treeshaking\", \"说说Vue 3.0中Treeshaking特性？举例说明一下？\"],\n          [\"vue3/modal_component\", \"用Vue3.0 写过组件吗？如果想实现一个 Modal你会怎么设计？\"],\n        ],\n      },\n      {\n        title: \"ES6系列  ( 已完结..)\",\n        collapsable: false,\n        children: [\n          [\"es6/var_let_const\", \"说说var、let、const之间的区别\"],\n          [\"es6/array\", \"ES6中数组新增了哪些扩展?\"],\n          [\"es6/object\", \"ES6中对象新增了哪些扩展?\"],\n          [\"es6/function\", \"ES6中函数新增了哪些扩展?\"],\n          [\"es6/set_map\", \"ES6中新增的Set、Map两种数据结构怎么理解?\"],\n          [\"es6/promise\", \"你是怎么理解ES6中 Promise的？使用场景？\"],\n          [\"es6/generator\", \"怎么理解ES6中 Generator的？使用场景？\"],\n          [\"es6/proxy\", \"你是怎么理解ES6中Proxy的？使用场景?\"],\n          [\"es6/module\", \"你是怎么理解ES6中Module的？使用场景？\"],\n          [\"es6/decorator\", \"你是怎么理解ES6中 Decorator 的？使用场景？\"],\n        ],\n      },\n      {\n        title: \"JavaScript系列  ( 已完结..)\",\n        collapsable: false,\n        children: [\n          [\"JavaScript/data_type\", \"说说JavaScript中的数据类型？存储上的差别？\"],\n          [\"JavaScript/array_api\", \"数组的常用方法有哪些？\"],\n          [\"JavaScript/string_api\", \"JavaScript字符串的常用方法有哪些？\"],\n          [\"JavaScript/type_conversion\", \"谈谈 JavaScript 中的类型转换机制\"],\n          [\"JavaScript/== _===\", \"== 和 ===区别，分别在什么情况使用\"],\n          [\"JavaScript/copy\", \"深拷贝浅拷贝的区别？如何实现一个深拷贝？\"],\n          [\"JavaScript/closure\", \"说说你对闭包的理解？闭包使用场景\"],\n          [\"JavaScript/scope\", \"说说你对作用域链的理解\"],\n          [\"JavaScript/prototype\", \"JavaScript原型，原型链 ? 有什么特点？\"],\n          [\"JavaScript/inherit\", \"Javascript如何实现继承？\"],\n          [\"JavaScript/this\", \"谈谈this对象的理解\"],\n          [\"JavaScript/context_stack\", \"JavaScript中执行上下文和执行栈是什么？\"],\n          [\"JavaScript/event_Model\", \"说说JavaScript中的事件模型\"],\n          [\"JavaScript/typeof_instanceof\", \"typeof 与 instanceof 区别\"],\n          [\"JavaScript/event_agent\", \"解释下什么是事件代理？应用场景？\"],\n          [\"JavaScript/new\", \"说说new操作符具体干了什么？\"],\n          [\"JavaScript/ajax\", \"ajax原理是什么？如何实现？\"],\n          [\"JavaScript/bind_call_apply\", \"bind、call、apply 区别？如何实现一个bind?\"],\n          [\"JavaScript/regexp\", \"说说你对正则表达式的理解？应用场景？\"],\n          [\"JavaScript/event_loop\", \"说说你对事件循环的理解\"],\n          [\"JavaScript/Dom\", \"DOM常见的操作有哪些？\"],\n          [\"JavaScript/BOM\", \"说说你对BOM的理解，常见的BOM对象你了解哪些？\"],\n          [\"JavaScript/tail_recursion\", \"举例说明你对尾递归的理解，有哪些应用场景\"],\n          [\"JavaScript/memory_leak\", \"说说 JavaScript 中内存泄漏的几种情况？\"],\n          [\"JavaScript/cache\", \"Javascript本地存储的方式有哪些？区别及应用场景？\"],\n          [\"JavaScript/functional_programming\", \"说说你对函数式编程的理解？优缺点？\"],\n          [\"JavaScript/function_cache\", \"Javascript中如何实现函数缓存？函数缓存有哪些应用场景？\"],\n          [\"JavaScript/loss_accuracy\", \"说说 Javascript 数字精度丢失的问题，如何解决？\"],\n          [\"JavaScript/debounce_throttle\", \"什么是防抖和节流？有什么区别？如何实现？\"],\n          [\"JavaScript/visible\", \"如何判断一个元素是否在可视区域中？\"],\n          [\"JavaScript/continue_to_upload\", \"大文件上传如何做断点续传？\"],\n          [\"JavaScript/pull_up_loading_pull_down_refresh\", \"如何实现上拉加载，下拉刷新？\"],\n          [\"JavaScript/single_sign\", \"什么是单点登录？如何实现？\"],\n          [\"JavaScript/security\", \"web常见的攻击方式有哪些？如何防御？\"],\n        ],\n      },\n      {\n        title: \"CSS系列  ( 已完结..)\",\n        collapsable: false,\n        children: [\n          [\"css/box\", \"说说你对盒子模型的理解?\"],\n          [\"css/selector\", \"css选择器有哪些？优先级？哪些属性可以继承？\"],\n          [\"css/em_px_rem_vh_vw\", \"说说em/px/rem/vh/vw区别?\"],\n          [\"css/dp_px_dpr_ppi\", \"说说设备像素、css像素、设备独立像素、dpr、ppi 之间的区别？\"],\n          [\"css/hide_attributes\", \"css中，有哪些方式可以隐藏页面元素？区别?\"],\n          [\"css/BFC\", \"谈谈你对BFC的理解？\"],\n          [\"css/center\", \"元素水平垂直居中的方法有哪些？如果元素不定宽高呢？\"],\n          [\"css/column_layout\", \"如何实现两栏布局，右侧自适应？三栏布局中间自适应呢？\"],\n          [\"css/flexbox\", \"说说flexbox（弹性盒布局模型）,以及适用场景？\"],\n          [\"css/grid\", \"介绍一下grid网格布局\"],\n          [\"css/css3_features\", \"CSS3新增了哪些新特性？\"],\n          [\"css/animation\", \"css3动画有哪些？\"],\n          [\"css/layout_painting\", \"怎么理解回流跟重绘？什么场景下会触发？\"],\n          [\"css/responsive_layout\", \"什么是响应式设计？响应式设计的基本原理是什么？如何做？\"],\n          [\"css/css_performance\", \"如果要做优化，CSS提高性能的方法有哪些？\"],\n          [\"css/single_multi_line\", \"如何实现单行／多行文本溢出的省略样式？\"],\n          [\"css/visual_scrolling\", \"如何使用css完成视差滚动效果?\"],\n          [\"css/triangle\", \"CSS如何画一个三角形？原理是什么？\"],\n          [\"css/less_12px\", \"让Chrome支持小于12px 的文字方式有哪些？区别？\"],\n          [\"css/sass_less_stylus\", \"说说对Css预编语言的理解？有哪些区别?\"],\n        ],\n      },\n      {\n        title: \"Webpack系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"webpack/webpack\", \"说说你对webpack的理解？解决了什么问题？\"],\n          [\"webpack/build_process\", \"说说webpack的构建流程?\"],\n          [\"webpack/Loader\", \"说说webpack中常见的Loader？解决了什么问题？\"],\n          [\"webpack/Plugin\", \"说说webpack中常见的Plugin？解决了什么问题？\"],\n          [\"webpack/Loader_Plugin\", \"说说Loader和Plugin的区别？编写Loader，Plugin的思路？\"],\n          [\"webpack/HMR\", \"说说webpack的热更新是如何做到的？原理是什么？\"],\n          [\"webpack/proxy\", \"说说webpack proxy工作原理？为什么能解决跨域?\"],\n          [\"webpack/performance\", \"说说如何借助webpack来优化前端性能？\"],\n          [\"webpack/improve_build\", \"如何提高webpack的构建速度？\"],\n          [\"webpack/Rollup_Parcel_snowpack_Vite\", \"与webpack类似的工具还有哪些？区别？\"],\n        ]\n      },\n      {\n        title: \"HTTP系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"http/HTTP_HTTPS\", \"什么是HTTP? HTTP 和 HTTPS 的区别?\"],\n          [\"http/HTTPS\", \"为什么说HTTPS比HTTP安全? HTTPS是如何保证安全的？\"],\n          [\"http/UDP_TCP\", \"如何理解UDP 和 TCP? 区别? 应用场景?\"],\n          [\"http/OSI\", \"如何理解OSI七层模型?\"],\n          [\"http/TCP_IP\", \"如何理解TCP/IP协议?\"],\n          [\"http/DNS\", \"DNS协议 是什么？说说DNS 完整的查询过程?\"],\n          [\"http/CDN\", \"如何理解CDN？说说实现原理？\"],\n          [\"http/1.0_1.1_2.0\", \"说说 HTTP1.0/1.1/2.0 的区别?\"],\n          [\"http/status\", \"说说 HTTP 常见的状态码有哪些，适用场景？\"],\n          [\"http/GET_POST\", \"说一下 GET 和 POST 的区别？\"],\n          [\"http/headers\", \"说说 HTTP 常见的请求头有哪些? 作用？\"],\n          [\"http/after_url\", \"说说地址栏输入 URL 敲下回车后发生了什么？\"],\n          [\"http/handshakes_waves\", \"说说TCP为什么需要三次握手和四次挥手？\"],\n          [\"http/WebSocket\", \"说说对WebSocket的理解？应用场景？\"]\n        ]\n      },\n      {\n        title: \"NodeJS系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"NodeJS/nodejs\", \"说说你对 Node.js 的理解？优缺点？应用场景？\"],\n          [\"NodeJS/global\", \"说说 Node.js 有哪些全局对象？\"],\n          [\"NodeJS/process\", \"说说对 Node 中的 process 的理解？有哪些常用方法？\"],\n          [\"NodeJS/fs\", \"说说对 Node 中的 fs模块的理解? 有哪些常用方法\"],\n          [\"NodeJS/Buffer\", \"说说对 Node 中的 Buffer 的理解？应用场景？\"],\n          [\"NodeJS/Stream\", \"说说对 Node 中的 Stream 的理解？应用场景？\"],\n          [\"NodeJS/EventEmitter\", \"说说Node中的EventEmitter? 如何实现一个EventEmitter?\"],\n          [\"NodeJS/event_loop\", \"说说对 Nodejs 中的事件循环机制理解?\"],\n          [\"NodeJS/require_order\", \"说说 Node 文件查找的优先级以及 Require 方法的文件查找策略?\"],\n          [\"NodeJS/middleware\", \"说说对中间件概念的理解，如何封装 node 中间件？\"],\n          [\"NodeJS/jwt\", \"如何实现jwt鉴权机制？说说你的思路\"],\n          [\"NodeJS/file_upload\", \"如何实现文件上传？说说你的思路\"],\n          [\"NodeJS/paging\", \"如果让你来设计一个分页功能, 你会怎么设计? 前后端如何交互?\"],\n          [\"NodeJS/performance\", \"Node性能如何进行监控以及优化？\"],\n        ]\n      },\n      {\n        title: \"React系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"React/React\", \"说说对React的理解？有哪些特性？\"],\n          [\"React/Real DOM_Virtual DOM\", \"说说 Real DOM和 Virtual DOM 的区别？优缺点？\"],\n          [\"React/life cycle\", \"说说 React 生命周期有哪些不同阶段？每个阶段对应的方法是？\"],\n          [\"React/state_props\", \"state 和 props有什么区别？\"],\n          [\"React/super()_super(props)\", \"super()和super(props)有什么区别？\"],\n          [\"React/setState\", \"说说 React中的setState执行机制\"],\n          [\"React/SyntheticEvent\", \"说说React的事件机制？\"],\n          [\"React/Binding events\", \"React事件绑定的方式有哪些？区别？\"],\n          [\"React/Building components\", \"React构建组件的方式有哪些？区别？\"],\n          [\"React/communication\", \"React中组件之间如何通信？\"],\n          [\"React/key\", \"React中的key有什么作用？\"],\n          [\"React/React refs\", \"说说对React refs 的理解？应用场景？\"],\n          [\"React/class_function component\", \"说说对React中类组件和函数组件的理解？有什么区别？\"],\n          [\"React/controlled_Uncontrolled\", \"说说对受控组件和非受控组件的理解？应用场景？\"],\n          [\"React/High order components\", \"说说对高阶组件的理解？应用场景?\"],\n          [\"React/React Hooks\", \"说说对React Hooks的理解？解决了什么问题？\"],\n          [\"React/import css\", \"说说react中引入css的方式有哪几种？区别？\"],\n          [\"React/animation\", \"在react中组件间过渡动画如何实现？\"],\n          [\"React/redux\", \"说说你对Redux的理解？其工作原理？\"],\n          [\"React/Redux Middleware\", \"说说对Redux中间件的理解？常用的中间件有哪些？实现原理？\"],\n          [\"React/how to use redux\", \"你在React项目中是如何使用Redux的? 项目结构是如何划分的？\"],\n          [\"React/React Router\", \"说说你对React Router的理解？常用的Router组件有哪些？\"],\n          [\"React/React Router model\", \"说说React Router有几种模式？实现原理？？\"],\n          [\"React/immutable\", \"说说你对immutable的理解？如何应用在react项目中？\"],\n          [\"React/render\", \"说说React render方法的原理？在什么时候会被触发？\"],\n          [\"React/improve_render\", \"说说你是如何提高组件的渲染效率的？在React中如何避免不必要的render？\"],\n          [\"React/diff\", \"说说React diff的原理是什么？\"],\n          [\"React/Fiber\", \"说说对Fiber架构的理解？解决了什么问题？\"],\n          [\"React/JSX to DOM\", \"说说React Jsx转换成真实DOM过程？\"],\n          [\"React/Improve performance\", \"说说 React 性能优化的手段有哪些？\"],\n          [\"React/capture error\", \"说说你在React项目是如何捕获错误的？\"],\n          [\"React/server side rendering\", \"说说React服务端渲染怎么做？原理是什么？\"],\n          [\"React/summary\", \"说说你在使用React 过程中遇到的常见问题？如何解决?\"]\n        ]\n      },\n      {\n        title: \"版本控制系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"git/Version control\", \"说说你对版本管理的理解？常用的版本管理工具有哪些？\"],\n          [\"git/Git\", \"说说你对Git的理解？\"],\n          [\"git/fork_clone_branch\", \"说说Git中 fork, clone,branch这三个概念，有什么区别?\"],\n          [\"git/command\", \"说说Git常用的命令有哪些？\"],\n          [\"git/HEAD_tree_index\", \"说说Git 中 HEAD、工作树和索引之间的区别？\"],\n          [\"git/git pull _git fetch\", \"说说对git pull 和 git fetch 的理解？有什么区别？\"],\n          [\"git/git stash\", \"说说你对git stash 的理解？应用场景？\"],\n          [\"git/git rebase_ git merge\", \"说说你对git rebase 和 git merge的理解？区别？\"],\n          [\"git/conflict\", \"说说 git 发生冲突的场景？如何解决？\"],\n          [\"git/git reset_ git revert\", \"说说你对git reset 和 git revert 的理解？区别？\"],\n        ]\n      },\n      {\n        title: \"操作系统系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"linux/linux\", \"说说你对操作系统的理解？核心概念有哪些？\"],\n          [\"linux/thread_process\", \"说说什么是进程？什么是线程？区别？\"],\n          [\"linux/file\", \"说说 linux系统下 文件操作常用的命令有哪些？\"],\n          [\"linux/vim\", \"说说 linux 系统下 文本编辑常用的命令有哪些？\"],\n          [\"linux/linux users\", \"说说你对 linux 用户管理的理解？相关的命令有哪些？\"],\n          [\"linux/redirect_pipe\", \"说说你对输入输出重定向和管道的理解？应用场景？\"],\n          [\"linux/shell\", \"说说你对 shell 的理解？常见的命令？\"],\n        ]\n      },\n      {\n        title: \"TypeScript 系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"typescript/typescript_javascript\", \"说说你对 TypeScript 的理解？与 JavaScript 的区别？\"],\n          [\"typescript/data_type\", \"说说 typescript 的数据类型有哪些？\"],\n          [\"typescript/enum\", \"说说你对 TypeScript 中枚举类型的理解？应用场景？\"],\n          [\"typescript/interface\", \"说说你对 TypeScript 中接口的理解？应用场景？\"],\n          [\"typescript/class\", \"说说你对 TypeScript 中类的理解？应用场景？\"],\n          [\"typescript/function\", \"说说你对 TypeScript 中函数的理解？与 JavaScript 函数的区别？\"],\n          [\"typescript/generic\", \"说说你对 TypeScript 中泛型的理解？应用场景？\"],\n          [\"typescript/high type\", \"说说你对 TypeScript 中高级类型的理解？有哪些？\"],\n          [\"typescript/decorator\", \"说说你对 TypeScript 装饰器的理解？应用场景？\"],\n          [\"typescript/namespace_module\", \"说说对 TypeScript 中命名空间与模块的理解？区别？\"],\n          [\"typescript/react\", \"说说如何在 React 项目中应用 TypeScript？\"],\n          [\"typescript/vue\", \"说说如何在Vue项目中应用TypeScript？\"]\n        ]\n      },\n      {\n        title: \"算法与数据结构系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"algorithm/Algorithm\", \"说说你对算法的理解？应用场景？\"],\n          [\"algorithm/time_space\", \"说说你对算法中时间复杂度，空间复杂度的理解？如何计算？\"],\n          [\"algorithm/structure\", \"说说你对数据结构的理解？有哪些？区别？\"],\n          [\"algorithm/stack_queue\", \"说说你对栈、队列的理解？应用场景？\"],\n          [\"algorithm/Linked List\", \"说说你对链表的理解？常见的操作有哪些？\"],\n          [\"algorithm/set\", \"说说你对集合的理解？常见的操作有哪些？\"],\n          [\"algorithm/tree\", \"说说你对树的理解？相关的操作有哪些？\"],\n          [\"algorithm/Heap\", \"说说你对堆的理解？如何实现？应用场景？\"],\n          [\"algorithm/graph\", \"说说你对图的理解？相关操作有哪些？\"],\n          [\"algorithm/sort\", \"说说常见的排序算法有哪些？区别？\"],\n          [\"algorithm/bubbleSort\", \"说说你对冒泡排序的理解？如何实现？应用场景？\"],\n          [\"algorithm/selectionSort\", \"说说你对选择排序的理解？如何实现？应用场景？\"],\n          [\"algorithm/insertionSort\", \"说说你对插入排序的理解？如何实现？应用场景？\"],\n          [\"algorithm/mergeSort\", \"说说你对归并排序的理解？如何实现？应用场景？\"],\n          [\"algorithm/quickSort\", \"说说你对快速排序的理解？如何实现？应用场景？\"],\n          [\"algorithm/BinarySearch\", \"说说你对二分查找的理解？如何实现？应用场景？\"],\n          [\"algorithm/design1\", \"说说说你对分而治之、动态规划的理解？区别？\"],\n          [\"algorithm/design2\", \"说说你对贪心算法、回溯算法的理解？应用场景？\"],\n        ]\n      },\n      {\n        title: \"小程序系列  ( 已完结..)\",\n        collapsable: false,\n        children:[\n          [\"applet/applet\", \"说说你对微信小程序的理解？优缺点？\"],\n          [\"applet/lifecycle\", \"说说微信小程序的生命周期函数有哪些？\"],\n          [\"applet/navigate\", \"说说微信小程序中路由跳转的方式有哪些？区别？\"],\n          [\"applet/optimization\", \"说说提高微信小程序的应用速度的手段有哪些？\"],\n          [\"applet/login\", \"说说微信小程序的登录流程？\"],\n          [\"applet/publish\", \"说说微信小程序的发布流程？\"],\n          [\"applet/requestPayment\", \"说说微信小程序的支付流程？\"],\n          [\"applet/WebView_jscore\", \"说说微信小程序的实现原理？\"],\n        ]\n      },\n      {\n        title: \"设计模式系列  ( 进行中..)\",\n        collapsable: false,\n        children:[\n          [\"design/design\", \"说说对设计模式的理解？常见的设计模式有哪些？\"],\n          [\"design/Singleton Pattern\", \"说说你对单例模式的理解？如何实现？\"],\n          [\"design/Factory  Pattern\", \"说说你对工厂模式的理解？应用场景？\"],\n          [\"design/Strategy Pattern\", \"说说你对策略模式的理解？应用场景？\"],\n          [\"design/Proxy Pattern\", \"说说你对代理模式的理解？应用场景？\"],\n          [\"design/Observer  Pattern\", \"说说你对发布订阅、观察者模式的理解？区别？\"],\n        ]\n      },\n    ],\n  },\n  markdown: {\n    lineNumbers: true,\n  },\n};\n"
  },
  {
    "path": "docs/.vuepress/theme/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018-present, Yuxi (Evan) You\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "docs/.vuepress/theme/components/AlgoliaSearchBox.vue",
    "content": "<template>\n  <form\n    id=\"search-form\"\n    class=\"algolia-search-wrapper search-box\"\n    role=\"search\"\n  >\n    <input\n      id=\"algolia-search-input\"\n      class=\"search-query\"\n      :placeholder=\"placeholder\"\n    >\n  </form>\n</template>\n\n<script>\nexport default {\n  name: 'AlgoliaSearchBox',\n\n  props: ['options'],\n\n  data () {\n    return {\n      placeholder: undefined\n    }\n  },\n\n  watch: {\n    $lang (newValue) {\n      this.update(this.options, newValue)\n    },\n\n    options (newValue) {\n      this.update(newValue, this.$lang)\n    }\n  },\n\n  mounted () {\n    this.initialize(this.options, this.$lang)\n    this.placeholder = this.$site.themeConfig.searchPlaceholder || ''\n  },\n\n  methods: {\n    initialize (userOptions, lang) {\n      Promise.all([\n        import(/* webpackChunkName: \"docsearch\" */ 'docsearch.js/dist/cdn/docsearch.min.js'),\n        import(/* webpackChunkName: \"docsearch\" */ 'docsearch.js/dist/cdn/docsearch.min.css')\n      ]).then(([docsearch]) => {\n        docsearch = docsearch.default\n        const { algoliaOptions = {}} = userOptions\n        docsearch(Object.assign(\n          {},\n          userOptions,\n          {\n            inputSelector: '#algolia-search-input',\n            // #697 Make docsearch work well at i18n mode.\n            algoliaOptions: Object.assign({\n              'facetFilters': [`lang:${lang}`].concat(algoliaOptions.facetFilters || [])\n            }, algoliaOptions),\n            handleSelected: (input, event, suggestion) => {\n              const { pathname, hash } = new URL(suggestion.url)\n              const routepath = pathname.replace(this.$site.base, '/')\n              this.$router.push(`${routepath}${hash}`)\n            }\n          }\n        ))\n      })\n    },\n\n    update (options, lang) {\n      this.$el.innerHTML = '<input id=\"algolia-search-input\" class=\"search-query\">'\n      this.initialize(options, lang)\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n.algolia-search-wrapper\n  & > span\n    vertical-align middle\n  .algolia-autocomplete\n    line-height normal\n    .ds-dropdown-menu\n      background-color #fff\n      border 1px solid #999\n      border-radius 4px\n      font-size 16px\n      margin 6px 0 0\n      padding 4px\n      text-align left\n      &:before\n        border-color #999\n      [class*=ds-dataset-]\n        border none\n        padding 0\n      .ds-suggestions\n        margin-top 0\n      .ds-suggestion\n        border-bottom 1px solid $borderColor\n    .algolia-docsearch-suggestion--highlight\n      color #2c815b\n    .algolia-docsearch-suggestion\n      border-color $borderColor\n      padding 0\n      .algolia-docsearch-suggestion--category-header\n        padding 5px 10px\n        margin-top 0\n        background $accentColor\n        color #fff\n        font-weight 600\n        .algolia-docsearch-suggestion--highlight\n          background rgba(255, 255, 255, 0.6)\n      .algolia-docsearch-suggestion--wrapper\n        padding 0\n      .algolia-docsearch-suggestion--title\n        font-weight 600\n        margin-bottom 0\n        color $textColor\n      .algolia-docsearch-suggestion--subcategory-column\n        vertical-align top\n        padding 5px 7px 5px 5px\n        border-color $borderColor\n        background #f1f3f5\n        &:after\n          display none\n      .algolia-docsearch-suggestion--subcategory-column-text\n        color #555\n    .algolia-docsearch-footer\n      border-color $borderColor\n    .ds-cursor .algolia-docsearch-suggestion--content\n      background-color #e7edf3 !important\n      color $textColor\n\n@media (min-width: $MQMobile)\n  .algolia-search-wrapper\n    .algolia-autocomplete\n      .algolia-docsearch-suggestion\n        .algolia-docsearch-suggestion--subcategory-column\n          float none\n          width 150px\n          min-width 150px\n          display table-cell\n        .algolia-docsearch-suggestion--content\n          float none\n          display table-cell\n          width 100%\n          vertical-align top\n        .ds-dropdown-menu\n          min-width 515px !important\n\n@media (max-width: $MQMobile)\n  .algolia-search-wrapper\n    .ds-dropdown-menu\n      min-width calc(100vw - 4rem) !important\n      max-width calc(100vw - 4rem) !important\n    .algolia-docsearch-suggestion--wrapper\n      padding 5px 7px 5px 5px !important\n    .algolia-docsearch-suggestion--subcategory-column\n      padding 0 !important\n      background white !important\n    .algolia-docsearch-suggestion--subcategory-column-text:after\n      content \" > \"\n      font-size 10px\n      line-height 14.4px\n      display inline-block\n      width 5px\n      margin -3px 3px 0\n      vertical-align middle\n\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/DropdownLink.vue",
    "content": "<template>\n  <div\n    class=\"dropdown-wrapper\"\n    :class=\"{ open }\"\n  >\n    <button\n      class=\"dropdown-title\"\n      type=\"button\"\n      :aria-label=\"dropdownAriaLabel\"\n      @click=\"setOpen(!open)\"\n    >\n      <span class=\"title\">{{ item.text }}</span>\n      <span\n        class=\"arrow\"\n        :class=\"open ? 'down' : 'right'\"\n      />\n    </button>\n\n    <DropdownTransition>\n      <ul\n        v-show=\"open\"\n        class=\"nav-dropdown\"\n      >\n        <li\n          v-for=\"(subItem, index) in item.items\"\n          :key=\"subItem.link || index\"\n          class=\"dropdown-item\"\n        >\n          <h4 v-if=\"subItem.type === 'links'\">\n            {{ subItem.text }}\n          </h4>\n\n          <ul\n            v-if=\"subItem.type === 'links'\"\n            class=\"dropdown-subitem-wrapper\"\n          >\n            <li\n              v-for=\"childSubItem in subItem.items\"\n              :key=\"childSubItem.link\"\n              class=\"dropdown-subitem\"\n            >\n              <NavLink\n                :item=\"childSubItem\"\n                @focusout=\"\n                  isLastItemOfArray(childSubItem, subItem.items) &&\n                    isLastItemOfArray(subItem, item.items) &&\n                    setOpen(false)\n                \"\n              />\n            </li>\n          </ul>\n\n          <NavLink\n            v-else\n            :item=\"subItem\"\n            @focusout=\"isLastItemOfArray(subItem, item.items) && setOpen(false)\"\n          />\n        </li>\n      </ul>\n    </DropdownTransition>\n  </div>\n</template>\n\n<script>\nimport NavLink from '@theme/components/NavLink.vue'\nimport DropdownTransition from '@theme/components/DropdownTransition.vue'\nimport last from 'lodash/last'\n\nexport default {\n  name: 'DropdownLink',\n\n  components: {\n    NavLink,\n    DropdownTransition\n  },\n\n  props: {\n    item: {\n      required: true\n    }\n  },\n\n  data () {\n    return {\n      open: false\n    }\n  },\n\n  computed: {\n    dropdownAriaLabel () {\n      return this.item.ariaLabel || this.item.text\n    }\n  },\n\n  watch: {\n    $route () {\n      this.open = false\n    }\n  },\n\n  methods: {\n    setOpen (value) {\n      this.open = value\n    },\n\n    isLastItemOfArray (item, array) {\n      return last(array) === item\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n.dropdown-wrapper\n  cursor pointer\n  .dropdown-title\n    display block\n    font-size 0.9rem\n    font-family inherit\n    cursor inherit\n    padding inherit\n    line-height 1.4rem\n    background transparent\n    border none\n    font-weight 500\n    color $textColor\n    &:hover\n      border-color transparent\n    .arrow\n      vertical-align middle\n      margin-top -1px\n      margin-left 0.4rem\n  .nav-dropdown\n    .dropdown-item\n      color inherit\n      line-height 1.7rem\n      h4\n        margin 0.45rem 0 0\n        border-top 1px solid #eee\n        padding 0.45rem 1.5rem 0 1.25rem\n      .dropdown-subitem-wrapper\n        padding 0\n        list-style none\n        .dropdown-subitem\n          font-size 0.9em\n      a\n        display block\n        line-height 1.7rem\n        position relative\n        border-bottom none\n        font-weight 400\n        margin-bottom 0\n        padding 0 1.5rem 0 1.25rem\n        &:hover\n          color $accentColor\n        &.router-link-active\n          color $accentColor\n          &::after\n            content \"\"\n            width 0\n            height 0\n            border-left 5px solid $accentColor\n            border-top 3px solid transparent\n            border-bottom 3px solid transparent\n            position absolute\n            top calc(50% - 2px)\n            left 9px\n      &:first-child h4\n        margin-top 0\n        padding-top 0\n        border-top 0\n\n@media (max-width: $MQMobile)\n  .dropdown-wrapper\n    &.open .dropdown-title\n      margin-bottom 0.5rem\n    .dropdown-title\n      font-weight 600\n      font-size inherit\n      &:hover\n        color $accentColor\n    .nav-dropdown\n      transition height .1s ease-out\n      overflow hidden\n      .dropdown-item\n        h4\n          border-top 0\n          margin-top 0\n          padding-top 0\n        h4, & > a\n          font-size 15px\n          line-height 2rem\n        .dropdown-subitem\n          font-size 14px\n          padding-left 1rem\n\n@media (min-width: $MQMobile)\n  .dropdown-wrapper\n    height 1.8rem\n    &:hover .nav-dropdown,\n    &.open .nav-dropdown\n      // override the inline style.\n      display block !important\n    &.open:blur\n      display none\n    .dropdown-title .arrow\n      // make the arrow always down at desktop\n      border-left 4px solid transparent\n      border-right 4px solid transparent\n      border-top 6px solid $arrowBgColor\n      border-bottom 0\n    .nav-dropdown\n      display none\n      // Avoid height shaked by clicking\n      height auto !important\n      box-sizing border-box;\n      max-height calc(100vh - 2.7rem)\n      overflow-y auto\n      position absolute\n      top 100%\n      right 0\n      background-color #fff\n      padding 0.6rem 0\n      border 1px solid #ddd\n      border-bottom-color #ccc\n      text-align left\n      border-radius 0.25rem\n      white-space nowrap\n      margin 0\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/DropdownTransition.vue",
    "content": "<template>\n  <transition\n    name=\"dropdown\"\n    @enter=\"setHeight\"\n    @after-enter=\"unsetHeight\"\n    @before-leave=\"setHeight\"\n  >\n    <slot />\n  </transition>\n</template>\n\n<script>\nexport default {\n  name: 'DropdownTransition',\n\n  methods: {\n    setHeight (items) {\n      // explicitly set height so that it can be transitioned\n      items.style.height = items.scrollHeight + 'px'\n    },\n\n    unsetHeight (items) {\n      items.style.height = ''\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n.dropdown-enter, .dropdown-leave-to\n  height 0 !important\n\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/Home.vue",
    "content": "<template>\n  <main\n    class=\"home\"\n    aria-labelledby=\"main-title\"\n  >\n    <header class=\"hero\">\n      <img\n        v-if=\"data.heroImage\"\n        :src=\"$withBase(data.heroImage)\"\n        :alt=\"data.heroAlt || 'hero'\"\n      >\n\n      <h1\n        v-if=\"data.heroText !== null\"\n        id=\"main-title\"\n      >\n        {{ data.heroText || $title || 'Hello' }}\n      </h1>\n\n      <p\n        v-if=\"data.tagline !== null\"\n        class=\"description\"\n      >\n        {{ data.tagline || $description || 'Welcome to your VuePress site' }}\n      </p>\n\n      <p\n        v-if=\"data.actionText && data.actionLink\"\n        class=\"action\"\n      >\n        <NavLink\n          class=\"action-button\"\n          :item=\"actionLink\"\n        />\n      </p>\n    </header>\n\n    <div\n      v-if=\"data.features && data.features.length\"\n      class=\"features\"\n    >\n      <div\n        v-for=\"(feature, index) in data.features\"\n        :key=\"index\"\n        class=\"feature\"\n      >\n        <h2>{{ feature.title }}</h2>\n        <p>{{ feature.details }}</p>\n      </div>\n    </div>\n\n    <Content class=\"theme-default-content custom\" />\n\n    <div\n      v-if=\"data.footer\"\n      class=\"footer\"\n    >\n      {{ data.footer }}\n    </div>\n  </main>\n</template>\n\n<script>\nimport NavLink from '@theme/components/NavLink.vue'\n\nexport default {\n  name: 'Home',\n\n  components: { NavLink },\n\n  computed: {\n    data () {\n      return this.$page.frontmatter\n    },\n\n    actionLink () {\n      return {\n        link: this.data.actionLink,\n        text: this.data.actionText\n      }\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n.home\n  padding $navbarHeight 2rem 0\n  max-width $homePageWidth\n  margin 0px auto\n  display block\n  .hero\n    text-align center\n    img\n      max-width: 100%\n      max-height 280px\n      display block\n      margin 3rem auto 1.5rem\n    h1\n      font-size 3rem\n    h1, .description, .action\n      margin 1.8rem auto\n    .description\n      max-width 35rem\n      font-size 1.6rem\n      line-height 1.3\n      color lighten($textColor, 40%)\n    .action-button\n      display inline-block\n      font-size 1.2rem\n      color #fff\n      background-color $accentColor\n      padding 0.8rem 1.6rem\n      border-radius 4px\n      transition background-color .1s ease\n      box-sizing border-box\n      border-bottom 1px solid darken($accentColor, 10%)\n      &:hover\n        background-color lighten($accentColor, 10%)\n  .features\n    border-top 1px solid $borderColor\n    padding 1.2rem 0\n    margin-top 2.5rem\n    display flex\n    flex-wrap wrap\n    align-items flex-start\n    align-content stretch\n    justify-content space-between\n  .feature\n    flex-grow 1\n    flex-basis 30%\n    max-width 30%\n    h2\n      font-size 1.4rem\n      font-weight 500\n      border-bottom none\n      padding-bottom 0\n      color lighten($textColor, 10%)\n    p\n      color lighten($textColor, 25%)\n  .footer\n    padding 2.5rem\n    border-top 1px solid $borderColor\n    text-align center\n    color lighten($textColor, 25%)\n\n@media (max-width: $MQMobile)\n  .home\n    .features\n      flex-direction column\n    .feature\n      max-width 100%\n      padding 0 2.5rem\n\n@media (max-width: $MQMobileNarrow)\n  .home\n    padding-left 1.5rem\n    padding-right 1.5rem\n    .hero\n      img\n        max-height 210px\n        margin 2rem auto 1.2rem\n      h1\n        font-size 2rem\n      h1, .description, .action\n        margin 1.2rem auto\n      .description\n        font-size 1.2rem\n      .action-button\n        font-size 1rem\n        padding 0.6rem 1.2rem\n    .feature\n      h2\n        font-size 1.25rem\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/NavLink.vue",
    "content": "<template>\n  <RouterLink\n    v-if=\"isInternal\"\n    class=\"nav-link\"\n    :to=\"link\"\n    :exact=\"exact\"\n    @focusout.native=\"focusoutAction\"\n  >\n    {{ item.text }}\n  </RouterLink>\n  <a\n    v-else\n    :href=\"link\"\n    class=\"nav-link external\"\n    :target=\"target\"\n    :rel=\"rel\"\n    @focusout=\"focusoutAction\"\n  >\n    {{ item.text }}\n    <OutboundLink v-if=\"isBlankTarget\" />\n  </a>\n</template>\n\n<script>\nimport { isExternal, isMailto, isTel, ensureExt } from '../util'\n\nexport default {\n  name: 'NavLink',\n\n  props: {\n    item: {\n      required: true\n    }\n  },\n\n  computed: {\n    link () {\n      return ensureExt(this.item.link)\n    },\n\n    exact () {\n      if (this.$site.locales) {\n        return Object.keys(this.$site.locales).some(rootLink => rootLink === this.link)\n      }\n      return this.link === '/'\n    },\n\n    isNonHttpURI () {\n      return isMailto(this.link) || isTel(this.link)\n    },\n\n    isBlankTarget () {\n      return this.target === '_blank'\n    },\n\n    isInternal () {\n      return !isExternal(this.link) && !this.isBlankTarget\n    },\n\n    target () {\n      if (this.isNonHttpURI) {\n        return null\n      }\n      if (this.item.target) {\n        return this.item.target\n      }\n      return isExternal(this.link) ? '_blank' : ''\n    },\n\n    rel () {\n      if (this.isNonHttpURI) {\n        return null\n      }\n      if (this.item.rel) {\n        return this.item.rel\n      }\n      return this.isBlankTarget ? 'noopener noreferrer' : ''\n    }\n  },\n\n  methods: {\n    focusoutAction () {\n      this.$emit('focusout')\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/NavLinks.vue",
    "content": "<template>\n  <nav\n    v-if=\"userLinks.length || repoLink\"\n    class=\"nav-links\"\n  >\n    <!-- user links -->\n    <div\n      v-for=\"item in userLinks\"\n      :key=\"item.link\"\n      class=\"nav-item\"\n    >\n      <DropdownLink\n        v-if=\"item.type === 'links'\"\n        :item=\"item\"\n      />\n      <NavLink\n        v-else\n        :item=\"item\"\n      />\n    </div>\n\n    <!-- repo link -->\n    <a\n      v-if=\"repoLink\"\n      :href=\"repoLink\"\n      class=\"repo-link\"\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n    >\n      {{ repoLabel }}\n      <OutboundLink />\n    </a>\n  </nav>\n</template>\n\n<script>\nimport DropdownLink from '@theme/components/DropdownLink.vue'\nimport { resolveNavLinkItem } from '../util'\nimport NavLink from '@theme/components/NavLink.vue'\n\nexport default {\n  name: 'NavLinks',\n\n  components: {\n    NavLink,\n    DropdownLink\n  },\n\n  computed: {\n    userNav () {\n      return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || []\n    },\n\n    nav () {\n      const { locales } = this.$site\n      if (locales && Object.keys(locales).length > 1) {\n        const currentLink = this.$page.path\n        const routes = this.$router.options.routes\n        const themeLocales = this.$site.themeConfig.locales || {}\n        const languageDropdown = {\n          text: this.$themeLocaleConfig.selectText || 'Languages',\n          ariaLabel: this.$themeLocaleConfig.ariaLabel || 'Select language',\n          items: Object.keys(locales).map(path => {\n            const locale = locales[path]\n            const text = themeLocales[path] && themeLocales[path].label || locale.lang\n            let link\n            // Stay on the current page\n            if (locale.lang === this.$lang) {\n              link = currentLink\n            } else {\n              // Try to stay on the same page\n              link = currentLink.replace(this.$localeConfig.path, path)\n              // fallback to homepage\n              if (!routes.some(route => route.path === link)) {\n                link = path\n              }\n            }\n            return { text, link }\n          })\n        }\n        return [...this.userNav, languageDropdown]\n      }\n      return this.userNav\n    },\n\n    userLinks () {\n      return (this.nav || []).map(link => {\n        return Object.assign(resolveNavLinkItem(link), {\n          items: (link.items || []).map(resolveNavLinkItem)\n        })\n      })\n    },\n\n    repoLink () {\n      const { repo } = this.$site.themeConfig\n      if (repo) {\n        return /^https?:/.test(repo)\n          ? repo\n          : `https://github.com/${repo}`\n      }\n      return null\n    },\n\n    repoLabel () {\n      if (!this.repoLink) return\n      if (this.$site.themeConfig.repoLabel) {\n        return this.$site.themeConfig.repoLabel\n      }\n\n      const repoHost = this.repoLink.match(/^https?:\\/\\/[^/]+/)[0]\n      const platforms = ['GitHub', 'GitLab', 'Bitbucket']\n      for (let i = 0; i < platforms.length; i++) {\n        const platform = platforms[i]\n        if (new RegExp(platform, 'i').test(repoHost)) {\n          return platform\n        }\n      }\n\n      return 'Source'\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n.nav-links\n  display inline-block\n  a\n    line-height 1.4rem\n    color inherit\n    &:hover, &.router-link-active\n      color $accentColor\n  .nav-item\n    position relative\n    display inline-block\n    margin-left 1.5rem\n    line-height 2rem\n    &:first-child\n      margin-left 0\n  .repo-link\n    margin-left 1.5rem\n\n@media (max-width: $MQMobile)\n  .nav-links\n    .nav-item, .repo-link\n      margin-left 0\n\n@media (min-width: $MQMobile)\n  .nav-links a\n    &:hover, &.router-link-active\n      color $textColor\n  .nav-item > a:not(.external)\n    &:hover, &.router-link-active\n      margin-bottom -2px\n      border-bottom 2px solid lighten($accentColor, 8%)\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/Navbar.vue",
    "content": "<template>\n  <header class=\"navbar\">\n    <SidebarButton @toggle-sidebar=\"$emit('toggle-sidebar')\" />\n\n    <RouterLink\n      :to=\"$localePath\"\n      class=\"home-link\"\n    >\n      <img\n        v-if=\"$site.themeConfig.logo\"\n        class=\"logo\"\n        :src=\"$withBase($site.themeConfig.logo)\"\n        :alt=\"$siteTitle\"\n      >\n      <span\n        v-if=\"$siteTitle\"\n        ref=\"siteName\"\n        class=\"site-name\"\n        :class=\"{ 'can-hide': $site.themeConfig.logo }\"\n      >{{ $siteTitle }}</span>\n    </RouterLink>\n\n    <div\n      class=\"links\"\n      :style=\"linksWrapMaxWidth ? {\n        'max-width': linksWrapMaxWidth + 'px'\n      } : {}\"\n    >\n      <AlgoliaSearchBox\n        v-if=\"isAlgoliaSearch\"\n        :options=\"algolia\"\n      />\n      <SearchBox v-else-if=\"$site.themeConfig.search !== false && $page.frontmatter.search !== false\" />\n      <NavLinks class=\"can-hide\" />\n    </div>\n  </header>\n</template>\n\n<script>\nimport AlgoliaSearchBox from '@AlgoliaSearchBox'\nimport SearchBox from '@SearchBox'\nimport SidebarButton from '@theme/components/SidebarButton.vue'\nimport NavLinks from '@theme/components/NavLinks.vue'\n\nexport default {\n  name: 'Navbar',\n\n  components: {\n    SidebarButton,\n    NavLinks,\n    SearchBox,\n    AlgoliaSearchBox\n  },\n\n  data () {\n    return {\n      linksWrapMaxWidth: null\n    }\n  },\n\n  computed: {\n    algolia () {\n      return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}\n    },\n\n    isAlgoliaSearch () {\n      return this.algolia && this.algolia.apiKey && this.algolia.indexName\n    }\n  },\n\n  mounted () {\n    const MOBILE_DESKTOP_BREAKPOINT = 719 // refer to config.styl\n    const NAVBAR_VERTICAL_PADDING = parseInt(css(this.$el, 'paddingLeft')) + parseInt(css(this.$el, 'paddingRight'))\n    const handleLinksWrapWidth = () => {\n      if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {\n        this.linksWrapMaxWidth = null\n      } else {\n        this.linksWrapMaxWidth = this.$el.offsetWidth - NAVBAR_VERTICAL_PADDING\n          - (this.$refs.siteName && this.$refs.siteName.offsetWidth || 0)\n      }\n    }\n    handleLinksWrapWidth()\n    window.addEventListener('resize', handleLinksWrapWidth, false)\n  }\n}\n\nfunction css (el, property) {\n  // NOTE: Known bug, will return 'auto' if style value is 'auto'\n  const win = el.ownerDocument.defaultView\n  // null means not to return pseudo styles\n  return win.getComputedStyle(el, null)[property]\n}\n</script>\n\n<style lang=\"stylus\">\n$navbar-vertical-padding = 0.7rem\n$navbar-horizontal-padding = 1.5rem\n\n.navbar\n  padding $navbar-vertical-padding $navbar-horizontal-padding\n  line-height $navbarHeight - 1.4rem\n  a, span, img\n    display inline-block\n  .logo\n    height $navbarHeight - 1.4rem\n    min-width $navbarHeight - 1.4rem\n    margin-right 0.8rem\n    vertical-align top\n  .site-name\n    font-size 1.3rem\n    font-weight 600\n    color $textColor\n    position relative\n  .links\n    padding-left 1.5rem\n    box-sizing border-box\n    background-color white\n    white-space nowrap\n    font-size 0.9rem\n    position absolute\n    right $navbar-horizontal-padding\n    top $navbar-vertical-padding\n    display flex\n    .search-box\n      flex: 0 0 auto\n      vertical-align top\n\n@media (max-width: $MQMobile)\n  .navbar\n    padding-left 4rem\n    .can-hide\n      display none\n    .links\n      padding-left 1.5rem\n    .site-name\n      width calc(100vw - 9.4rem)\n      overflow hidden\n      white-space nowrap\n      text-overflow ellipsis\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/Page.vue",
    "content": "<template>\n  <main class=\"page\">\n    <slot name=\"top\" />\n\n    <Content class=\"theme-default-content\" />\n    <PageEdit />\n\n    <PageNav v-bind=\"{ sidebarItems }\" />\n\n    <slot name=\"bottom\" />\n  </main>\n</template>\n\n<script>\nimport PageEdit from '@theme/components/PageEdit.vue'\nimport PageNav from '@theme/components/PageNav.vue'\n\nexport default {\n  components: { PageEdit, PageNav },\n  props: ['sidebarItems']\n}\n</script>\n\n<style lang=\"stylus\">\n@require '../styles/wrapper.styl'\n\n.page\n  padding-bottom 2rem\n  display block\n\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/PageEdit.vue",
    "content": "<template>\n  <footer class=\"page-edit\">\n    <div\n      v-if=\"editLink\"\n      class=\"edit-link\"\n    >\n      <a\n        :href=\"editLink\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n      >{{ editLinkText }}</a>\n      <OutboundLink />\n    </div>\n\n    <div\n      v-if=\"lastUpdated\"\n      class=\"last-updated\"\n    >\n      <span class=\"prefix\">{{ lastUpdatedText }}:</span>\n      <span class=\"time\">{{ lastUpdated }}</span>\n    </div>\n  </footer>\n</template>\n\n<script>\nimport isNil from 'lodash/isNil'\nimport { endingSlashRE, outboundRE } from '../util'\n\nexport default {\n  name: 'PageEdit',\n\n  computed: {\n    lastUpdated () {\n      return this.$page.lastUpdated\n    },\n\n    lastUpdatedText () {\n      if (typeof this.$themeLocaleConfig.lastUpdated === 'string') {\n        return this.$themeLocaleConfig.lastUpdated\n      }\n      if (typeof this.$site.themeConfig.lastUpdated === 'string') {\n        return this.$site.themeConfig.lastUpdated\n      }\n      return 'Last Updated'\n    },\n\n    editLink () {\n      const showEditLink = isNil(this.$page.frontmatter.editLink)\n        ? this.$site.themeConfig.editLinks\n        : this.$page.frontmatter.editLink\n\n      const {\n        repo,\n        docsDir = '',\n        docsBranch = 'master',\n        docsRepo = repo\n      } = this.$site.themeConfig\n\n      if (showEditLink && docsRepo && this.$page.relativePath) {\n        return this.createEditLink(\n          repo,\n          docsRepo,\n          docsDir,\n          docsBranch,\n          this.$page.relativePath\n        )\n      }\n      return null\n    },\n\n    editLinkText () {\n      return (\n        this.$themeLocaleConfig.editLinkText\n        || this.$site.themeConfig.editLinkText\n        || `Edit this page`\n      )\n    }\n  },\n\n  methods: {\n    createEditLink (repo, docsRepo, docsDir, docsBranch, path) {\n      const bitbucket = /bitbucket.org/\n      if (bitbucket.test(repo)) {\n        const base = outboundRE.test(docsRepo) ? docsRepo : repo\n        return (\n          base.replace(endingSlashRE, '')\n          + `/src`\n          + `/${docsBranch}/`\n          + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')\n          + path\n          + `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`\n        )\n      }\n\n      const base = outboundRE.test(docsRepo)\n        ? docsRepo\n        : `https://github.com/${docsRepo}`\n      return (\n        base.replace(endingSlashRE, '')\n        + `/edit`\n        + `/${docsBranch}/`\n        + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')\n        + path\n      )\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n@require '../styles/wrapper.styl'\n\n.page-edit\n  @extend $wrapper\n  padding-top 1rem\n  padding-bottom 1rem\n  overflow auto\n\n  .edit-link\n    display inline-block\n    a\n      color lighten($textColor, 25%)\n      margin-right 0.25rem\n  .last-updated\n    float right\n    font-size 0.9em\n    .prefix\n      font-weight 500\n      color lighten($textColor, 25%)\n    .time\n      font-weight 400\n      color #aaa\n\n@media (max-width: $MQMobile)\n  .page-edit\n    .edit-link\n      margin-bottom 0.5rem\n    .last-updated\n      font-size 0.8em\n      float none\n      text-align left\n\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/PageNav.vue",
    "content": "<template>\n  <div\n    v-if=\"prev || next\"\n    class=\"page-nav\"\n  >\n    <p class=\"inner\">\n      <span\n        v-if=\"prev\"\n        class=\"prev\"\n      >\n        ←\n        <a\n          v-if=\"prev.type === 'external'\"\n          class=\"prev\"\n          :href=\"prev.path\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          {{ prev.title || prev.path }}\n\n          <OutboundLink />\n        </a>\n\n        <RouterLink\n          v-else\n          class=\"prev\"\n          :to=\"prev.path\"\n        >\n          {{ prev.title || prev.path }}\n        </RouterLink>\n      </span>\n\n      <span\n        v-if=\"next\"\n        class=\"next\"\n      >\n        <a\n          v-if=\"next.type === 'external'\"\n          :href=\"next.path\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          {{ next.title || next.path }}\n\n          <OutboundLink />\n        </a>\n\n        <RouterLink\n          v-else\n          :to=\"next.path\"\n        >\n          {{ next.title || next.path }}\n        </RouterLink>\n        →\n      </span>\n    </p>\n  </div>\n</template>\n\n<script>\nimport { resolvePage } from '../util'\nimport isString from 'lodash/isString'\nimport isNil from 'lodash/isNil'\n\nexport default {\n  name: 'PageNav',\n\n  props: ['sidebarItems'],\n\n  computed: {\n    prev () {\n      return resolvePageLink(LINK_TYPES.PREV, this)\n    },\n\n    next () {\n      return resolvePageLink(LINK_TYPES.NEXT, this)\n    }\n  }\n}\n\nfunction resolvePrev (page, items) {\n  return find(page, items, -1)\n}\n\nfunction resolveNext (page, items) {\n  return find(page, items, 1)\n}\n\nconst LINK_TYPES = {\n  NEXT: {\n    resolveLink: resolveNext,\n    getThemeLinkConfig: ({ nextLinks }) => nextLinks,\n    getPageLinkConfig: ({ frontmatter }) => frontmatter.next\n  },\n  PREV: {\n    resolveLink: resolvePrev,\n    getThemeLinkConfig: ({ prevLinks }) => prevLinks,\n    getPageLinkConfig: ({ frontmatter }) => frontmatter.prev\n  }\n}\n\nfunction resolvePageLink (\n  linkType,\n  { $themeConfig, $page, $route, $site, sidebarItems }\n) {\n  const { resolveLink, getThemeLinkConfig, getPageLinkConfig } = linkType\n\n  // Get link config from theme\n  const themeLinkConfig = getThemeLinkConfig($themeConfig)\n\n  // Get link config from current page\n  const pageLinkConfig = getPageLinkConfig($page)\n\n  // Page link config will overwrite global theme link config if defined\n  const link = isNil(pageLinkConfig) ? themeLinkConfig : pageLinkConfig\n\n  if (link === false) {\n    return\n  } else if (isString(link)) {\n    return resolvePage($site.pages, link, $route.path)\n  } else {\n    return resolveLink($page, sidebarItems)\n  }\n}\n\nfunction find (page, items, offset) {\n  const res = []\n  flatten(items, res)\n  for (let i = 0; i < res.length; i++) {\n    const cur = res[i]\n    if (cur.type === 'page' && cur.path === decodeURIComponent(page.path)) {\n      return res[i + offset]\n    }\n  }\n}\n\nfunction flatten (items, res) {\n  for (let i = 0, l = items.length; i < l; i++) {\n    if (items[i].type === 'group') {\n      flatten(items[i].children || [], res)\n    } else {\n      res.push(items[i])\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n@require '../styles/wrapper.styl'\n\n.page-nav\n  @extend $wrapper\n  padding-top 1rem\n  padding-bottom 0\n  .inner\n    min-height 2rem\n    margin-top 0\n    border-top 1px solid $borderColor\n    padding-top 1rem\n    overflow auto // clear float\n  .next\n    float right\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/Sidebar.vue",
    "content": "<template>\n  <aside class=\"sidebar\">\n    <NavLinks />\n\n    <slot name=\"top\" />\n\n    <SidebarLinks\n      :depth=\"0\"\n      :items=\"items\"\n    />\n    <slot name=\"bottom\" />\n  </aside>\n</template>\n\n<script>\nimport SidebarLinks from '@theme/components/SidebarLinks.vue'\nimport NavLinks from '@theme/components/NavLinks.vue'\n\nexport default {\n  name: 'Sidebar',\n\n  components: { SidebarLinks, NavLinks },\n\n  props: ['items']\n}\n</script>\n\n<style lang=\"stylus\">\n.sidebar\n  ul\n    padding 0\n    margin 0\n    list-style-type none\n  a\n    display inline-block\n  .nav-links\n    display none\n    border-bottom 1px solid $borderColor\n    padding 0.5rem 0 0.75rem 0\n    a\n      font-weight 600\n    .nav-item, .repo-link\n      display block\n      line-height 1.25rem\n      font-size 1.1em\n      padding 0.5rem 0 0.5rem 1.5rem\n  & > .sidebar-links\n    padding 1.5rem 0\n    & > li > a.sidebar-link\n      font-size 1.1em\n      line-height 1.7\n      font-weight bold\n    & > li:not(:first-child)\n      margin-top .75rem\n\n@media (max-width: $MQMobile)\n  .sidebar\n    .nav-links\n      display block\n      .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after\n        top calc(1rem - 2px)\n    & > .sidebar-links\n      padding 1rem 0\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/SidebarButton.vue",
    "content": "<template>\n  <div\n    class=\"sidebar-button\"\n    @click=\"$emit('toggle-sidebar')\"\n  >\n    <svg\n      class=\"icon\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      aria-hidden=\"true\"\n      role=\"img\"\n      viewBox=\"0 0 448 512\"\n    >\n      <path\n        fill=\"currentColor\"\n        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\"\n        class=\"\"\n      />\n    </svg>\n  </div>\n</template>\n\n<style lang=\"stylus\">\n.sidebar-button\n  cursor pointer\n  display none\n  width 1.25rem\n  height 1.25rem\n  position absolute\n  padding 0.6rem\n  top 0.6rem\n  left 1rem\n  .icon\n    display block\n    width 1.25rem\n    height 1.25rem\n\n@media (max-width: $MQMobile)\n  .sidebar-button\n    display block\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/SidebarGroup.vue",
    "content": "<template>\n  <section\n    class=\"sidebar-group\"\n    :class=\"[\n      {\n        collapsable,\n        'is-sub-group': depth !== 0\n      },\n      `depth-${depth}`\n    ]\"\n  >\n    <RouterLink\n      v-if=\"item.path\"\n      class=\"sidebar-heading clickable\"\n      :class=\"{\n        open,\n        'active': isActive($route, item.path)\n      }\"\n      :to=\"item.path\"\n      @click.native=\"$emit('toggle')\"\n    >\n      <span>{{ item.title }}</span>\n      <span\n        v-if=\"collapsable\"\n        class=\"arrow\"\n        :class=\"open ? 'down' : 'right'\"\n      />\n    </RouterLink>\n\n    <p\n      v-else\n      class=\"sidebar-heading\"\n      :class=\"{ open }\"\n      @click=\"$emit('toggle')\"\n    >\n      <span>{{ item.title }}</span>\n      <span\n        v-if=\"collapsable\"\n        class=\"arrow\"\n        :class=\"open ? 'down' : 'right'\"\n      />\n    </p>\n\n    <DropdownTransition>\n      <SidebarLinks\n        v-if=\"open || !collapsable\"\n        class=\"sidebar-group-items\"\n        :items=\"item.children\"\n        :sidebar-depth=\"item.sidebarDepth\"\n        :depth=\"depth + 1\"\n      />\n    </DropdownTransition>\n  </section>\n</template>\n\n<script>\nimport { isActive } from '../util'\nimport DropdownTransition from '@theme/components/DropdownTransition.vue'\n\nexport default {\n  name: 'SidebarGroup',\n\n  components: {\n    DropdownTransition\n  },\n\n  props: [\n    'item',\n    'open',\n    'collapsable',\n    'depth'\n  ],\n\n  // ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components\n  beforeCreate () {\n    this.$options.components.SidebarLinks = require('@theme/components/SidebarLinks.vue').default\n  },\n\n  methods: { isActive }\n}\n</script>\n\n<style lang=\"stylus\">\n.sidebar-group\n  .sidebar-group\n    padding-left 0.5em\n  &:not(.collapsable)\n    .sidebar-heading:not(.clickable)\n      cursor auto\n      color inherit\n  // refine styles of nested sidebar groups\n  &.is-sub-group\n    padding-left 0\n    & > .sidebar-heading\n      font-size 0.95em\n      line-height 1.4\n      font-weight normal\n      padding-left 2rem\n      &:not(.clickable)\n        opacity 0.5\n    & > .sidebar-group-items\n      padding-left 1rem\n      & > li > .sidebar-link\n        font-size: 0.95em;\n        border-left none\n  &.depth-2\n    & > .sidebar-heading\n      border-left none\n\n.sidebar-heading\n  color $textColor\n  transition color .15s ease\n  cursor pointer\n  font-size 1.1em\n  font-weight bold\n  // text-transform uppercase\n  padding 0.35rem 1.5rem 0.35rem 1.25rem\n  width 100%\n  box-sizing border-box\n  margin 0\n  border-left 0.25rem solid transparent\n  &.open, &:hover\n    color inherit\n  .arrow\n    position relative\n    top -0.12em\n    left 0.5em\n  &.clickable\n    &.active\n      font-weight 600\n      color $accentColor\n      border-left-color $accentColor\n    &:hover\n      color $accentColor\n\n.sidebar-group-items\n  transition height .1s ease-out\n  font-size 0.95em\n  overflow hidden\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/SidebarLink.vue",
    "content": "<script>\nimport { isActive, hashRE, groupHeaders } from '../util'\n\nexport default {\n  functional: true,\n\n  props: ['item', 'sidebarDepth'],\n\n  render (h,\n    {\n      parent: {\n        $page,\n        $site,\n        $route,\n        $themeConfig,\n        $themeLocaleConfig\n      },\n      props: {\n        item,\n        sidebarDepth\n      }\n    }) {\n    // use custom active class matching logic\n    // due to edge case of paths ending with / + hash\n    const selfActive = isActive($route, item.path)\n    // for sidebar: auto pages, a hash link should be active if one of its child\n    // matches\n    const active = item.type === 'auto'\n      ? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))\n      : selfActive\n    const link = item.type === 'external'\n      ? renderExternal(h, item.path, item.title || item.path)\n      : renderLink(h, item.path, item.title || item.path, active)\n\n    const maxDepth = [\n      $page.frontmatter.sidebarDepth,\n      sidebarDepth,\n      $themeLocaleConfig.sidebarDepth,\n      $themeConfig.sidebarDepth,\n      1\n    ].find(depth => depth !== undefined)\n\n    const displayAllHeaders = $themeLocaleConfig.displayAllHeaders\n      || $themeConfig.displayAllHeaders\n\n    if (item.type === 'auto') {\n      return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]\n    } else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {\n      const children = groupHeaders(item.headers)\n      return [link, renderChildren(h, children, item.path, $route, maxDepth)]\n    } else {\n      return link\n    }\n  }\n}\n\nfunction renderLink (h, to, text, active, level) {\n  const component = {\n    props: {\n      to,\n      activeClass: '',\n      exactActiveClass: ''\n    },\n    class: {\n      active,\n      'sidebar-link': true\n    }\n  }\n\n  if (level > 2) {\n    component.style = {\n      'padding-left': level + 'rem'\n    }\n  }\n\n  return h('RouterLink', component, text)\n}\n\nfunction renderChildren (h, children, path, route, maxDepth, depth = 1) {\n  if (!children || depth > maxDepth) return null\n  return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {\n    const active = isActive(route, path + '#' + c.slug)\n    return h('li', { class: 'sidebar-sub-header' }, [\n      renderLink(h, path + '#' + c.slug, c.title, active, c.level - 1),\n      renderChildren(h, c.children, path, route, maxDepth, depth + 1)\n    ])\n  }))\n}\n\nfunction renderExternal (h, to, text) {\n  return h('a', {\n    attrs: {\n      href: to,\n      target: '_blank',\n      rel: 'noopener noreferrer'\n    },\n    class: {\n      'sidebar-link': true\n    }\n  }, [text, h('OutboundLink')])\n}\n</script>\n\n<style lang=\"stylus\">\n.sidebar .sidebar-sub-headers\n  padding-left 1rem\n  font-size 0.95em\n\na.sidebar-link\n  font-size 1em\n  font-weight 400\n  display inline-block\n  color $textColor\n  border-left 0.25rem solid transparent\n  padding 0.35rem 1rem 0.35rem 1.25rem\n  line-height 1.4\n  width: 100%\n  box-sizing: border-box\n  &:hover\n    color $accentColor\n  &.active\n    font-weight 600\n    color $accentColor\n    border-left-color $accentColor\n  .sidebar-group &\n    padding-left 2rem\n  .sidebar-sub-headers &\n    padding-top 0.25rem\n    padding-bottom 0.25rem\n    border-left none\n    &.active\n      font-weight 500\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/components/SidebarLinks.vue",
    "content": "<template>\n  <ul\n    v-if=\"items.length\"\n    class=\"sidebar-links\"\n  >\n    <li\n      v-for=\"(item, i) in items\"\n      :key=\"i\"\n    >\n      <SidebarGroup\n        v-if=\"item.type === 'group'\"\n        :item=\"item\"\n        :open=\"i === openGroupIndex\"\n        :collapsable=\"item.collapsable || item.collapsible\"\n        :depth=\"depth\"\n        @toggle=\"toggleGroup(i)\"\n      />\n      <SidebarLink\n        v-else\n        :sidebar-depth=\"sidebarDepth\"\n        :item=\"item\"\n      />\n    </li>\n  </ul>\n</template>\n\n<script>\nimport SidebarGroup from '@theme/components/SidebarGroup.vue'\nimport SidebarLink from '@theme/components/SidebarLink.vue'\nimport { isActive } from '../util'\n\nexport default {\n  name: 'SidebarLinks',\n\n  components: { SidebarGroup, SidebarLink },\n\n  props: [\n    'items',\n    'depth',  // depth of current sidebar links\n    'sidebarDepth' // depth of headers to be extracted\n  ],\n\n  data () {\n    return {\n      openGroupIndex: 0\n    }\n  },\n\n  watch: {\n    '$route' () {\n      this.refreshIndex()\n    }\n  },\n\n  created () {\n    this.refreshIndex()\n  },\n\n  methods: {\n    refreshIndex () {\n      const index = resolveOpenGroupIndex(\n        this.$route,\n        this.items\n      )\n      if (index > -1) {\n        this.openGroupIndex = index\n      }\n    },\n\n    toggleGroup (index) {\n      this.openGroupIndex = index === this.openGroupIndex ? -1 : index\n    },\n\n    isActive (page) {\n      return isActive(this.$route, page.regularPath)\n    }\n  }\n}\n\nfunction resolveOpenGroupIndex (route, items) {\n  for (let i = 0; i < items.length; i++) {\n    const item = items[i]\n    if (descendantIsActive(route, item)) {\n      return i\n    }\n  }\n  return -1\n}\n\nfunction descendantIsActive (route, item) {\n  if (item.type === 'group') {\n    return item.children.some(child => {\n      if (child.type === 'group') {\n        return descendantIsActive(route, child)\n      } else {\n        return child.type === 'page' && isActive(route, child.path)\n      }\n    })\n  }\n  return false\n}\n</script>\n"
  },
  {
    "path": "docs/.vuepress/theme/global-components/Badge.vue",
    "content": "<script>\nexport default {\n  functional: true,\n  props: {\n    type: {\n      type: String,\n      default: 'tip'\n    },\n    text: String,\n    vertical: {\n      type: String,\n      default: 'top'\n    }\n  },\n  render (h, { props, slots }) {\n    return h('span', {\n      class: ['badge', props.type],\n      style: {\n        verticalAlign: props.vertical\n      }\n    }, props.text || slots().default)\n  }\n}\n</script>\n\n<style lang=\"stylus\" scoped>\n.badge\n  display inline-block\n  font-size 14px\n  height 18px\n  line-height 18px\n  border-radius 3px\n  padding 0 6px\n  color white\n  background-color #42b983\n  &.tip, &.green\n    background-color $badgeTipColor\n  &.error\n    background-color $badgeErrorColor\n  &.warning, &.warn, &.yellow\n    background-color $badgeWarningColor\n  & + &\n    margin-left 5px\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/index.js",
    "content": "const path = require('path')\n\n// Theme API.\nmodule.exports = (options, ctx) => {\n  const { themeConfig, siteConfig } = ctx\n\n  // resolve algolia\n  const isAlgoliaSearch = (\n    themeConfig.algolia\n    || Object\n        .keys(siteConfig.locales && themeConfig.locales || {})\n        .some(base => themeConfig.locales[base].algolia)\n  )\n\n  const enableSmoothScroll = themeConfig.smoothScroll === true\n\n  return {\n    alias () {\n      return {\n        '@AlgoliaSearchBox': isAlgoliaSearch\n          ? path.resolve(__dirname, 'components/AlgoliaSearchBox.vue')\n          : path.resolve(__dirname, 'noopModule.js')\n      }\n    },\n\n    plugins: [\n      ['@vuepress/active-header-links', options.activeHeaderLinks],\n      '@vuepress/search',\n      '@vuepress/plugin-nprogress',\n      ['container', {\n        type: 'tip',\n        defaultTitle: {\n          '/': 'TIP',\n          '/zh/': '提示'\n        }\n      }],\n      ['container', {\n        type: 'warning',\n        defaultTitle: {\n          '/': 'WARNING',\n          '/zh/': '注意'\n        }\n      }],\n      ['container', {\n        type: 'danger',\n        defaultTitle: {\n          '/': 'WARNING',\n          '/zh/': '警告'\n        }\n      }],\n      ['container', {\n        type: 'details',\n        before: info => `<details class=\"custom-block details\">${info ? `<summary>${info}</summary>` : ''}\\n`,\n        after: () => '</details>\\n'\n      }],\n      ['smooth-scroll', enableSmoothScroll]\n    ]\n  }\n}\n"
  },
  {
    "path": "docs/.vuepress/theme/layouts/404.vue",
    "content": "<template>\n  <div class=\"theme-container\">\n    <div class=\"theme-default-content\">\n      <h1>404</h1>\n\n      <blockquote>{{ getMsg() }}</blockquote>\n\n      <RouterLink to=\"/\">\n        Take me home.\n      </RouterLink>\n    </div>\n  </div>\n</template>\n\n<script>\nconst msgs = [\n  `There's nothing here.`,\n  `How did we get here?`,\n  `That's a Four-Oh-Four.`,\n  `Looks like we've got some broken links.`\n]\n\nexport default {\n  methods: {\n    getMsg () {\n      return msgs[Math.floor(Math.random() * msgs.length)]\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "docs/.vuepress/theme/layouts/Layout.vue",
    "content": "<template>\n  <div\n    class=\"theme-container\"\n    :class=\"pageClasses\"\n    @touchstart=\"onTouchStart\"\n    @touchend=\"onTouchEnd\"\n  >\n    <Navbar\n      v-if=\"shouldShowNavbar\"\n      @toggle-sidebar=\"toggleSidebar\"\n    />\n\n    <div\n      class=\"sidebar-mask\"\n      @click=\"toggleSidebar(false)\"\n    />\n\n    <Sidebar\n      :items=\"sidebarItems\"\n      @toggle-sidebar=\"toggleSidebar\"\n    >\n      <template #top>\n        <slot name=\"sidebar-top\" />\n      </template>\n      <template #bottom>\n        <slot name=\"sidebar-bottom\" />\n      </template>\n    </Sidebar>\n\n    <Home v-if=\"$page.frontmatter.home\" />\n\n    <Page\n      v-else\n      :sidebar-items=\"sidebarItems\"\n    >\n      <template #top>\n        <slot name=\"page-top\" />\n      </template>\n      <template #bottom>\n        <slot name=\"page-bottom\" />\n      </template>\n    </Page>\n    <div id=\"ad\">\n      <a href=\"javascript:viod(0)\" target=\"_blank\" rel=\"sponsored noopener\">\n        关注JS每日一题回复1加入语音社群\n        <img src=\"https://static.vue-js.com/b4b71a30-443b-11eb-85f6-6fac77c0c9b3.png\"></a> \n        <!-- <a href=\"javascript:viod(0)\" target=\"_blank\" rel=\"sponsored noopener\">\n        拉你进500人前端群\n        <img src=\"http://static.vue-js.com/Fr6pvXKNio5Pe4kEHk0ufXdZ1sHT\"></a> -->\n    </div>\n  </div>\n</template>\n\n<script>\nimport Home from '@theme/components/Home.vue'\nimport Navbar from '@theme/components/Navbar.vue'\nimport Page from '@theme/components/Page.vue'\nimport Sidebar from '@theme/components/Sidebar.vue'\nimport { resolveSidebarItems } from '../util'\n\nexport default {\n  name: 'Layout',\n\n  components: {\n    Home,\n    Page,\n    Sidebar,\n    Navbar\n  },\n\n  data () {\n    return {\n      isSidebarOpen: false\n    }\n  },\n\n  computed: {\n    shouldShowNavbar () {\n      const { themeConfig } = this.$site\n      const { frontmatter } = this.$page\n      if (\n        frontmatter.navbar === false\n        || themeConfig.navbar === false) {\n        return false\n      }\n      return (\n        this.$title\n        || themeConfig.logo\n        || themeConfig.repo\n        || themeConfig.nav\n        || this.$themeLocaleConfig.nav\n      )\n    },\n\n    shouldShowSidebar () {\n      const { frontmatter } = this.$page\n      return (\n        !frontmatter.home\n        && frontmatter.sidebar !== false\n        && this.sidebarItems.length\n      )\n    },\n\n    sidebarItems () {\n      return resolveSidebarItems(\n        this.$page,\n        this.$page.regularPath,\n        this.$site,\n        this.$localePath\n      )\n    },\n\n    pageClasses () {\n      const userPageClass = this.$page.frontmatter.pageClass\n      return [\n        {\n          'no-navbar': !this.shouldShowNavbar,\n          'sidebar-open': this.isSidebarOpen,\n          'no-sidebar': !this.shouldShowSidebar\n        },\n        userPageClass\n      ]\n    }\n  },\n\n  mounted () {\n    this.$router.afterEach(() => {\n      this.isSidebarOpen = false\n    })\n  },\n\n  methods: {\n    toggleSidebar (to) {\n      this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen\n      this.$emit('toggle-sidebar', this.isSidebarOpen)\n    },\n\n    // side swipe\n    onTouchStart (e) {\n      this.touchStart = {\n        x: e.changedTouches[0].clientX,\n        y: e.changedTouches[0].clientY\n      }\n    },\n\n    onTouchEnd (e) {\n      const dx = e.changedTouches[0].clientX - this.touchStart.x\n      const dy = e.changedTouches[0].clientY - this.touchStart.y\n      if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) {\n        if (dx > 0 && this.touchStart.x <= 80) {\n          this.toggleSidebar(true)\n        } else {\n          this.toggleSidebar(false)\n        }\n      }\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "docs/.vuepress/theme/noopModule.js",
    "content": "export default {}\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/arrow.styl",
    "content": "@require './config'\n\n.arrow\n  display inline-block\n  width 0\n  height 0\n  &.up\n    border-left 4px solid transparent\n    border-right 4px solid transparent\n    border-bottom 6px solid $arrowBgColor\n  &.down\n    border-left 4px solid transparent\n    border-right 4px solid transparent\n    border-top 6px solid $arrowBgColor\n  &.right\n    border-top 4px solid transparent\n    border-bottom 4px solid transparent\n    border-left 6px solid $arrowBgColor\n  &.left\n    border-top 4px solid transparent\n    border-bottom 4px solid transparent\n    border-right 6px solid $arrowBgColor\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/code.styl",
    "content": "{$contentClass}\n  code\n    color lighten($textColor, 20%)\n    padding 0.25rem 0.5rem\n    margin 0\n    font-size 0.85em\n    background-color rgba(27,31,35,0.05)\n    border-radius 3px\n    .token\n      &.deleted\n        color #EC5975\n      &.inserted\n        color $accentColor\n\n{$contentClass}\n  pre, pre[class*=\"language-\"]\n    line-height 1.4\n    padding 1.25rem 1.5rem\n    margin 0.85rem 0\n    background-color $codeBgColor\n    border-radius 6px\n    overflow auto\n    code\n      color #fff\n      padding 0\n      background-color transparent\n      border-radius 0\n\ndiv[class*=\"language-\"]\n  position relative\n  background-color $codeBgColor\n  border-radius 6px\n  .highlight-lines\n    user-select none\n    padding-top 1.3rem\n    position absolute\n    top 0\n    left 0\n    width 100%\n    line-height 1.4\n    .highlighted\n      background-color rgba(0, 0, 0, 66%)\n  pre, pre[class*=\"language-\"]\n    background transparent\n    position relative\n    z-index 1\n  &::before\n    position absolute\n    z-index 3\n    top 0.8em\n    right 1em\n    font-size 0.75rem\n    color rgba(255, 255, 255, 0.4)\n  &:not(.line-numbers-mode)\n    .line-numbers-wrapper\n      display none\n  &.line-numbers-mode\n    .highlight-lines .highlighted\n        position relative\n        &:before\n          content ' '\n          position absolute\n          z-index 3\n          left 0\n          top 0\n          display block\n          width $lineNumbersWrapperWidth\n          height 100%\n          background-color rgba(0, 0, 0, 66%)\n    pre\n      padding-left $lineNumbersWrapperWidth + 1 rem\n      vertical-align middle\n    .line-numbers-wrapper\n      position absolute\n      top 0\n      width $lineNumbersWrapperWidth\n      text-align center\n      color rgba(255, 255, 255, 0.3)\n      padding 1.25rem 0\n      line-height 1.4\n      br\n        user-select none\n      .line-number\n        position relative\n        z-index 4\n        user-select none\n        font-size 0.85em\n    &::after\n      content ''\n      position absolute\n      z-index 2\n      top 0\n      left 0\n      width $lineNumbersWrapperWidth\n      height 100%\n      border-radius 6px 0 0 6px\n      border-right 1px solid rgba(0, 0, 0, 66%)\n      background-color $codeBgColor\n\n\nfor lang in $codeLang\n  div{'[class~=\"language-' + lang + '\"]'}\n    &:before\n      content ('' + lang)\n\ndiv[class~=\"language-javascript\"]\n  &:before\n    content \"js\"\n\ndiv[class~=\"language-typescript\"]\n  &:before\n    content \"ts\"\n\ndiv[class~=\"language-markup\"]\n  &:before\n    content \"html\"\n\ndiv[class~=\"language-markdown\"]\n  &:before\n    content \"md\"\n\ndiv[class~=\"language-json\"]:before\n  content \"json\"\n\ndiv[class~=\"language-ruby\"]:before\n  content \"rb\"\n\ndiv[class~=\"language-python\"]:before\n  content \"py\"\n\ndiv[class~=\"language-bash\"]:before\n  content \"sh\"\n\ndiv[class~=\"language-php\"]:before\n  content \"php\"\n\n@import '~prismjs/themes/prism-tomorrow.css'\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/config.styl",
    "content": "$contentClass = '.theme-default-content'\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/custom-blocks.styl",
    "content": ".custom-block\n  .custom-block-title\n    font-weight 600\n    margin-bottom -0.4rem\n  &.tip, &.warning, &.danger\n    padding .1rem 1.5rem\n    border-left-width .5rem\n    border-left-style solid\n    margin 1rem 0\n  &.tip\n    background-color #f3f5f7\n    border-color #42b983\n  &.warning\n    background-color rgba(255,229,100,.3)\n    border-color darken(#ffe564, 35%)\n    color darken(#ffe564, 70%)\n    .custom-block-title\n      color darken(#ffe564, 50%)\n    a\n      color $textColor\n  &.danger\n    background-color #ffe6e6\n    border-color darken(red, 20%)\n    color darken(red, 70%)\n    .custom-block-title\n      color darken(red, 40%)\n    a\n      color $textColor\n  &.details\n    display block\n    position relative\n    border-radius 2px\n    margin 1.6em 0\n    padding 1.6em\n    background-color #eee\n    h4\n      margin-top 0\n    figure, p\n      &:last-child\n        margin-bottom 0\n        padding-bottom 0\n    summary\n      outline none\n      cursor pointer\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/index.styl",
    "content": "@require './config'\n@require './code'\n@require './custom-blocks'\n@require './arrow'\n@require './wrapper'\n@require './toc'\n\nhtml, body\n  padding 0\n  margin 0\n  background-color #fff\n\nbody\n  font-family -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif\n  -webkit-font-smoothing antialiased\n  -moz-osx-font-smoothing grayscale\n  font-size 16px\n  color $textColor\n\n#ad\n  width: 125px;\n  position: fixed;\n  z-index: $z-header -1;\n  right: 10px;\n  top: 120px;\n  padding: 10px;\n  background-color: #fff;\n  border-radius: 3px;\n  font-size: 13px;\n  text-align: center;\n  img\n    width:  100%;\n    \n.page\n  padding-left $sidebarWidth\n\n.navbar\n  position fixed\n  z-index 20\n  top 0\n  left 0\n  right 0\n  height $navbarHeight\n  background-color #fff\n  box-sizing border-box\n  border-bottom 1px solid $borderColor\n\n.sidebar-mask\n  position fixed\n  z-index 9\n  top 0\n  left 0\n  width 100vw\n  height 100vh\n  display none\n\n.sidebar\n  font-size 16px\n  background-color #fff\n  width $sidebarWidth\n  position fixed\n  z-index 10\n  margin 0\n  top $navbarHeight\n  left 0\n  bottom 0\n  box-sizing border-box\n  border-right 1px solid $borderColor\n  overflow-y auto\n\n{$contentClass}:not(.custom)\n  @extend $wrapper\n  > *:first-child\n    margin-top $navbarHeight\n\n  a:hover\n    text-decoration underline\n\n  p.demo\n    padding 1rem 1.5rem\n    border 1px solid #ddd\n    border-radius 4px\n\n  img\n    max-width 100%\n\n{$contentClass}.custom\n  padding 0\n  margin 0\n\n  img\n    max-width 100%\n\na\n  font-weight 500\n  color $accentColor\n  text-decoration none\n\np a code\n  font-weight 400\n  color $accentColor\n\nkbd\n  background #eee\n  border solid 0.15rem #ddd\n  border-bottom solid 0.25rem #ddd\n  border-radius 0.15rem\n  padding 0 0.15em\n\nblockquote\n  font-size 1rem\n  color #999;\n  border-left .2rem solid #dfe2e5\n  margin 1rem 0\n  padding .25rem 0 .25rem 1rem\n\n  & > p\n    margin 0\n\nul, ol\n  padding-left 1.2em\n\nstrong\n  font-weight 600\n\nh1, h2, h3, h4, h5, h6\n  font-weight 600\n  line-height 1.25\n\n  {$contentClass}:not(.custom) > &\n    margin-top (0.5rem - $navbarHeight)\n    padding-top ($navbarHeight + 1rem)\n    margin-bottom 0\n\n    &:first-child\n      margin-top -1.5rem\n      margin-bottom 1rem\n\n      + p, + pre, + .custom-block\n        margin-top 2rem\n\n  &:hover .header-anchor\n    opacity: 1\n\nh1\n  font-size 2.2rem\n\nh2\n  font-size 1.65rem\n  padding-bottom .3rem\n  border-bottom 1px solid $borderColor\n\nh3\n  font-size 1.35rem\n\na.header-anchor\n  font-size 0.85em\n  float left\n  margin-left -0.87em\n  padding-right 0.23em\n  margin-top 0.125em\n  opacity 0\n\n  &:hover\n    text-decoration none\n\ncode, kbd, .line-number\n  font-family source-code-pro, Menlo, Monaco, Consolas, \"Courier New\", monospace\n\np, ul, ol\n  line-height 1.7\n\nhr\n  border 0\n  border-top 1px solid $borderColor\n\ntable\n  border-collapse collapse\n  margin 1rem 0\n  display: block\n  overflow-x: auto\n\ntr\n  border-top 1px solid #dfe2e5\n\n  &:nth-child(2n)\n    background-color #f6f8fa\n\nth, td\n  border 1px solid #dfe2e5\n  padding .6em 1em\n\n.theme-container\n  &.sidebar-open\n    .sidebar-mask\n      display: block\n\n  &.no-navbar\n    {$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6\n      margin-top 1.5rem\n      padding-top 0\n\n    .sidebar\n      top 0\n\n\n@media (min-width: ($MQMobile + 1px))\n  .theme-container.no-sidebar\n    .sidebar\n      display none\n\n    .page\n      padding-left 0\n\n@require 'mobile.styl'\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/mobile.styl",
    "content": "@require './config'\n\n$mobileSidebarWidth = $sidebarWidth * 0.82\n\n// narrow desktop / iPad\n@media (max-width: $MQNarrow)\n  .sidebar\n    font-size 15px\n    width $mobileSidebarWidth\n  .page\n    padding-left $mobileSidebarWidth\n\n// wide mobile\n@media (max-width: $MQMobile)\n  .sidebar\n    top 0\n    padding-top $navbarHeight\n    transform translateX(-100%)\n    transition transform .2s ease\n  .page\n    padding-left 0\n  .theme-container\n    &.sidebar-open\n      .sidebar\n        transform translateX(0)\n    &.no-navbar\n      .sidebar\n        padding-top: 0\n  #ad\n    display: none\n\n// narrow mobile\n@media (max-width: $MQMobileNarrow)\n  h1\n    font-size 1.9rem\n  {$contentClass}\n    div[class*=\"language-\"]\n      margin 0.85rem -1.5rem\n      border-radius 0\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/toc.styl",
    "content": ".table-of-contents\n  .badge\n    vertical-align middle\n"
  },
  {
    "path": "docs/.vuepress/theme/styles/wrapper.styl",
    "content": "$wrapper\n  max-width $contentWidth\n  margin 0 auto\n  padding 2rem 2.5rem\n  @media (max-width: $MQNarrow)\n    padding 2rem\n  @media (max-width: $MQMobileNarrow)\n    padding 1.5rem\n\n"
  },
  {
    "path": "docs/.vuepress/theme/util/index.js",
    "content": "export const hashRE = /#.*$/\nexport const extRE = /\\.(md|html)$/\nexport const endingSlashRE = /\\/$/\nexport const outboundRE = /^[a-z]+:/i\n\nexport function normalize (path) {\n  return decodeURI(path)\n    .replace(hashRE, '')\n    .replace(extRE, '')\n}\n\nexport function getHash (path) {\n  const match = path.match(hashRE)\n  if (match) {\n    return match[0]\n  }\n}\n\nexport function isExternal (path) {\n  return outboundRE.test(path)\n}\n\nexport function isMailto (path) {\n  return /^mailto:/.test(path)\n}\n\nexport function isTel (path) {\n  return /^tel:/.test(path)\n}\n\nexport function ensureExt (path) {\n  if (isExternal(path)) {\n    return path\n  }\n  const hashMatch = path.match(hashRE)\n  const hash = hashMatch ? hashMatch[0] : ''\n  const normalized = normalize(path)\n\n  if (endingSlashRE.test(normalized)) {\n    return path\n  }\n  return normalized + '.html' + hash\n}\n\nexport function isActive (route, path) {\n  const routeHash = decodeURIComponent(route.hash)\n  const linkHash = getHash(path)\n  if (linkHash && routeHash !== linkHash) {\n    return false\n  }\n  const routePath = normalize(route.path)\n  const pagePath = normalize(path)\n  return routePath === pagePath\n}\n\nexport function resolvePage (pages, rawPath, base) {\n  if (isExternal(rawPath)) {\n    return {\n      type: 'external',\n      path: rawPath\n    }\n  }\n  if (base) {\n    rawPath = resolvePath(rawPath, base)\n  }\n  const path = normalize(rawPath)\n  for (let i = 0; i < pages.length; i++) {\n    if (normalize(pages[i].regularPath) === path) {\n      return Object.assign({}, pages[i], {\n        type: 'page',\n        path: ensureExt(pages[i].path)\n      })\n    }\n  }\n  console.error(`[vuepress] No matching page found for sidebar item \"${rawPath}\"`)\n  return {}\n}\n\nfunction resolvePath (relative, base, append) {\n  const firstChar = relative.charAt(0)\n  if (firstChar === '/') {\n    return relative\n  }\n\n  if (firstChar === '?' || firstChar === '#') {\n    return base + relative\n  }\n\n  const stack = base.split('/')\n\n  // remove trailing segment if:\n  // - not appending\n  // - appending to trailing slash (last segment is empty)\n  if (!append || !stack[stack.length - 1]) {\n    stack.pop()\n  }\n\n  // resolve relative path\n  const segments = relative.replace(/^\\//, '').split('/')\n  for (let i = 0; i < segments.length; i++) {\n    const segment = segments[i]\n    if (segment === '..') {\n      stack.pop()\n    } else if (segment !== '.') {\n      stack.push(segment)\n    }\n  }\n\n  // ensure leading slash\n  if (stack[0] !== '') {\n    stack.unshift('')\n  }\n\n  return stack.join('/')\n}\n\n/**\n * @param { Page } page\n * @param { string } regularPath\n * @param { SiteData } site\n * @param { string } localePath\n * @returns { SidebarGroup }\n */\nexport function resolveSidebarItems (page, regularPath, site, localePath) {\n  const { pages, themeConfig } = site\n\n  const localeConfig = localePath && themeConfig.locales\n    ? themeConfig.locales[localePath] || themeConfig\n    : themeConfig\n\n  const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar\n  if (pageSidebarConfig === 'auto') {\n    return resolveHeaders(page)\n  }\n\n  const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar\n  if (!sidebarConfig) {\n    return []\n  } else {\n    const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig)\n    return config\n      ? config.map(item => resolveItem(item, pages, base))\n      : []\n  }\n}\n\n/**\n * @param { Page } page\n * @returns { SidebarGroup }\n */\nfunction resolveHeaders (page) {\n  const headers = groupHeaders(page.headers || [])\n  return [{\n    type: 'group',\n    collapsable: false,\n    title: page.title,\n    path: null,\n    children: headers.map(h => ({\n      type: 'auto',\n      title: h.title,\n      basePath: page.path,\n      path: page.path + '#' + h.slug,\n      children: h.children || []\n    }))\n  }]\n}\n\nexport function groupHeaders (headers) {\n  // group h3s under h2\n  headers = headers.map(h => Object.assign({}, h))\n  let lastH2\n  headers.forEach(h => {\n    if (h.level === 2) {\n      lastH2 = h\n    } else if (lastH2) {\n      (lastH2.children || (lastH2.children = [])).push(h)\n    }\n  })\n  return headers.filter(h => h.level === 2)\n}\n\nexport function resolveNavLinkItem (linkItem) {\n  return Object.assign(linkItem, {\n    type: linkItem.items && linkItem.items.length ? 'links' : 'link'\n  })\n}\n\n/**\n * @param { Route } route\n * @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config\n * @returns { base: string, config: SidebarConfig }\n */\nexport function resolveMatchingConfig (regularPath, config) {\n  if (Array.isArray(config)) {\n    return {\n      base: '/',\n      config: config\n    }\n  }\n  for (const base in config) {\n    if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {\n      return {\n        base,\n        config: config[base]\n      }\n    }\n  }\n  return {}\n}\n\nfunction ensureEndingSlash (path) {\n  return /(\\.html|\\/)$/.test(path)\n    ? path\n    : path + '/'\n}\n\nfunction resolveItem (item, pages, base, groupDepth = 1) {\n  if (typeof item === 'string') {\n    return resolvePage(pages, item, base)\n  } else if (Array.isArray(item)) {\n    return Object.assign(resolvePage(pages, item[0], base), {\n      title: item[1]\n    })\n  } else {\n    if (groupDepth > 3) {\n      console.error(\n        '[vuepress] detected a too deep nested sidebar group.'\n      )\n    }\n    const children = item.children || []\n    if (children.length === 0 && item.path) {\n      return Object.assign(resolvePage(pages, item.path, base), {\n        title: item.title\n      })\n    }\n    return {\n      type: 'group',\n      path: item.path,\n      title: item.title,\n      sidebarDepth: item.sidebarDepth,\n      children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)),\n      collapsable: item.collapsable !== false\n    }\n  }\n}\n"
  },
  {
    "path": "docs/JavaScript/== _===.md",
    "content": "# 面试官：== 和 ===区别，分别在什么情况使用\n\n ![](https://static.vue-js.com/51b208f0-68df-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、等于操作符\n\n等于操作符用两个等于号（ == ）表示，如果操作数相等，则会返回 `true`\n\n前面文章，我们提到在`JavaScript`中存在隐式转换。等于操作符（==）在比较中会先进行类型转换，再确定操作数是否相等\n\n遵循以下规则：\n\n如果任一操作数是布尔值，则将其转换为数值再比较是否相等\n\n```js\nlet result1 = (true == 1); // true\n```\n\n如果一个操作数是字符串，另一个操作数是数值，则尝试将字符串转换为数值，再比较是否相等\n\n```js\nlet result1 = (\"55\" == 55); // true\n```\n\n如果一个操作数是对象，另一个操作数不是，则调用对象的 `valueOf() `方法取得其原始值，再根据前面的规则进行比较\n\n```js\nlet obj = {valueOf:function(){return 1}}\nlet result1 = (obj == 1); // true\n```\n\n`null `和` undefined `相等\n\n```js\nlet result1 = (null == undefined ); // true\n```\n\n如果有任一操作数是 `NaN` ，则相等操作符返回 `false` \n\n```js\nlet result1 = (NaN == NaN ); // false\n```\n\n如果两个操作数都是对象，则比较它们是不是同一个对象。如果两个操作数都指向同一个对象，则相等操作符返回` true `\n\n```\nlet obj1 = {name:\"xxx\"}\nlet obj2 = {name:\"xxx\"}\nlet result1 = (obj1 == obj2 ); // false\n```\n\n下面进一步做个小结：\n\n- 两个都为简单类型，字符串和布尔值都会转换成数值，再比较\n- 简单类型与引用类型比较，对象转化成其原始类型的值，再比较\n\n- 两个都为引用类型，则比较它们是否指向同一个对象\n\n- null 和 undefined 相等\n- 存在 NaN 则返回 false\n\n\n\n## 二、全等操作符\n\n全等操作符由 3 个等于号（ === ）表示，只有两个操作数在不转换的前提下相等才返回 `true`。即类型相同，值也需相同\n\n```js\nlet result1 = (\"55\" === 55); // false，不相等，因为数据类型不同\nlet result2 = (55 === 55); // true，相等，因为数据类型相同值也相同\n```\n\n`undefined` 和 `null` 与自身严格相等\n\n```js\nlet result1 = (null === null)  //true\nlet result2 = (undefined === undefined)  //true\n```\n\n\n\n## 三、区别\n\n相等操作符（==）会做类型转换，再进行值的比较，全等运算符不会做类型转换\n\n```js\nlet result1 = (\"55\" === 55); // false，不相等，因为数据类型不同\nlet result2 = (55 === 55); // true，相等，因为数据类型相同值也相同\n```\n\n`null` 和 `undefined` 比较，相等操作符（==）为`true`，全等为`false`\n\n```js\nlet result1 = (null == undefined ); // true\nlet result2 = (null  === undefined); // false\n```\n\n\n\n### 小结\n\n相等运算符隐藏的类型转换，会带来一些违反直觉的结果\n\n```js\n'' == '0' // false\n0 == '' // true\n0 == '0' // true\n\nfalse == 'false' // false\nfalse == '0' // true\n\nfalse == undefined // false\nfalse == null // false\nnull == undefined // true\n\n' \\t\\r\\n' == 0 // true\n```\n\n但在比较`null`的情况的时候，我们一般使用相等操作符`==`\n\n```js\nconst obj = {};\n\nif(obj.x == null){\n  console.log(\"1\");  //执行\n}\n```\n\n等同于下面写法\n\n```js\nif(obj.x === null || obj.x === undefined) {\n    ...\n}\n```\n\n使用相等操作符（==）的写法明显更加简洁了\n\n所以，除了在比较对象属性为`null`或者`undefined`的情况下，我们可以使用相等操作符（==），其他情况建议一律使用全等操作符（===）\n\n\n\n"
  },
  {
    "path": "docs/JavaScript/BOM.md",
    "content": "# 面试官：说说你对BOM的理解，常见的BOM对象你了解哪些？\n\n ![](https://static.vue-js.com/3e191c40-8089-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n`BOM` (Browser Object Model)，浏览器对象模型，提供了独立于内容与浏览器窗口进行交互的对象\n\n其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退，前进，刷新，浏览器的窗口发生变化，滚动条的滚动，以及获取客户的一些信息如：浏览器品牌版本，屏幕分辨率\n\n浏览器的全部内容可以看成`DOM`，整个浏览器可以看成`BOM`。区别如下：\n\n![](https://static.vue-js.com/482f33e0-8089-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 二、window\n\n`Bom`的核心对象是`window`，它表示浏览器的一个实例\n\n在浏览器中，`window`对象有双重角色，即是浏览器窗口的一个接口，又是全局对象\n\n因此所有在全局作用域中声明的变量、函数都会变成`window`对象的属性和方法\n\n```js\nvar name = 'js每日一题';\nfunction lookName(){\n  alert(this.name);\n}\n\nconsole.log(window.name);  //js每日一题\nlookName();                //js每日一题\nwindow.lookName();         //js每日一题\n```\n\n关于窗口控制方法如下：\n\n- `moveBy(x,y)`：从当前位置水平移动窗体x个像素，垂直移动窗体y个像素，x为负数，将向左移动窗体，y为负数，将向上移动窗体\n- `moveTo(x,y)`：移动窗体左上角到相对于屏幕左上角的(x,y)点\n- `resizeBy(w,h)`：相对窗体当前的大小，宽度调整w个像素，高度调整h个像素。如果参数为负值，将缩小窗体，反之扩大窗体\n- `resizeTo(w,h)`：把窗体宽度调整为w个像素，高度调整为h个像素\n- `scrollTo(x,y)`：如果有滚动条，将横向滚动条移动到相对于窗体宽度为x个像素的位置，将纵向滚动条移动到相对于窗体高度为y个像素的位置\n- `scrollBy(x,y)`： 如果有滚动条，将横向滚动条向左移动x个像素，将纵向滚动条向下移动y个像素\n\n `window.open()` 既可以导航到一个特定的`url`，也可以打开一个新的浏览器窗口\n\n如果 `window.open()` 传递了第二个参数，且该参数是已有窗口或者框架的名称，那么就会在目标窗口加载第一个参数指定的URL\n\n```js\nwindow.open('htttp://www.vue3js.cn','topFrame')\n==> < a href=\" \" target=\"topFrame\"></ a>\n```\n\n`window.open()` 会返回新窗口的引用，也就是新窗口的 `window` 对象\n\n```js\nconst myWin = window.open('http://www.vue3js.cn','myWin')\n```\n\n`window.close()` 仅用于通过 `window.open()` 打开的窗口\n\n新创建的 `window` 对象有一个 `opener` 属性，该属性指向打开他的原始窗口对象\n\n\n\n\n\n## 三、location\n\n`url`地址如下：\n\n```js\nhttp://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents\n```\n\n`location`属性描述如下：\n\n| 属性名   | 例子                                                   | 说明                                |\n| -------- | ------------------------------------------------------ | ----------------------------------- |\n| hash     | \"#contents\"                                            | utl中#后面的字符，没有则返回空串    |\n| host     | www.wrox.com:80                                        | 服务器名称和端口号                  |\n| hostname | www.wrox.com                                           | 域名，不带端口号                    |\n| href     | http://www.wrox.com:80/WileyCDA/?q=javascript#contents | 完整url                             |\n| pathname | \"/WileyCDA/\"                                           | 服务器下面的文件路径                |\n| port     | 80                                                     | url的端口号，没有则为空             |\n| protocol | http:                                                  | 使用的协议                          |\n| search   | ?q=javascript                                          | url的查询字符串，通常为？后面的内容 |\n\n除了 `hash `之外，只要修改` location `的一个属性，就会导致页面重新加载新` URL`\n\n`location.reload()`，此方法可以重新刷新当前页面。这个方法会根据最有效的方式刷新页面，如果页面自上一次请求以来没有改变过，页面就会从浏览器缓存中重新加载\n\n如果要强制从服务器中重新加载，传递一个参数`true`即可\n\n\n\n## 四、navigator\n\n`navigator` 对象主要用来获取浏览器的属性，区分浏览器类型。属性较多，且兼容性比较复杂\n\n下表列出了`navigator`对象接口定义的属性和方法：\n\n ![](https://static.vue-js.com/6797ab40-8089-11eb-ab90-d9ae814b240d.png)\n\n ![](https://static.vue-js.com/74096620-8089-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 五、screen\n\n保存的纯粹是客户端能力信息，也就是浏览器窗口外面的客户端显示器的信息，比如像素宽度和像素高度\n\n ![](https://static.vue-js.com/7d6b21e0-8089-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 六、history\n\n`history`对象主要用来操作浏览器`URL`的历史记录，可以通过参数向前，向后，或者向指定`URL`跳转\n\n常用的属性如下：\n\n- `history.go()`\n\n接收一个整数数字或者字符串参数：向最近的一个记录中包含指定字符串的页面跳转，\n\n```js\nhistory.go('maixaofei.com')\n```\n\n当参数为整数数字的时候，正数表示向前跳转指定的页面，负数为向后跳转指定的页面\n\n```js\nhistory.go(3) //向前跳转三个记录\nhistory.go(-1) //向后跳转一个记录\n```\n\n- `history.forward()`：向前跳转一个页面\n- `history.back()`：向后跳转一个页面\n- `history.length`：获取历史记录数"
  },
  {
    "path": "docs/JavaScript/Dom.md",
    "content": "# 面试官：DOM常见的操作有哪些？\n\n ![](https://static.vue-js.com/a89c99a0-7fdc-11eb-ab90-d9ae814b240d.png)\n\n## 一、DOM\n\n文档对象模型 (DOM) 是 `HTML` 和 `XML` 文档的编程接口\n\n它提供了对文档的结构化的表述，并定义了一种方式可以使从程序中对该结构进行访问，从而改变文档的结构，样式和内容\n\n任何 `HTML `或` XML `文档都可以用 `DOM `表示为一个由节点构成的层级结构\n\n节点分很多类型，每种类型对应着文档中不同的信息和（或）标记，也都有自己不同的特性、数据和方法，而且与其他类型有某种关系，如下所示：\n\n```html\n<html>\n    <head>\n        <title>Page</title>\n    </head>\n    <body>\n        <p>Hello World!</p >\n    </body>\n</html>\n```\n\n`DOM`像原子包含着亚原子微粒那样，也有很多类型的`DOM`节点包含着其他类型的节点。接下来我们先看看其中的三种：\n\n```html\n<div>\n    <p title=\"title\">\n        content\n    </p >\n</div>\n```\n\n上述结构中，`div`、`p`就是元素节点，`content`就是文本节点，`title`就是属性节点\n\n\n\n## 二、操作\n\n日常前端开发，我们都离不开`DOM`操作\n\n在以前，我们使用`Jquery`，`zepto`等库来操作`DOM`，之后在`vue`，`Angular`，`React`等框架出现后，我们通过操作数据来控制`DOM`（绝大多数时候），越来越少的去直接操作`DOM`\n\n但这并不代表原生操作不重要。相反，`DOM`操作才能有助于我们理解框架深层的内容\n\n下面就来分析`DOM`常见的操作，主要分为：\n\n- 创建节点\n- 查询节点\n- 更新节点\n- 添加节点\n- 删除节点\n\n\n\n### 创建节点\n\n#### createElement\n\n创建新元素，接受一个参数，即要创建元素的标签名\n\n```js\nconst divEl = document.createElement(\"div\");\n```\n\n\n\n#### createTextNode\n\n创建一个文本节点\n\n```js\nconst textEl = document.createTextNode(\"content\");\n```\n\n\n\n#### createDocumentFragment\n\n用来创建一个文档碎片，它表示一种轻量级的文档，主要是用来存储临时节点，然后把文档碎片的内容一次性添加到`DOM`中\n\n```js\nconst fragment = document.createDocumentFragment();\n```\n\n当请求把一个`DocumentFragment` 节点插入文档树时，插入的不是 `DocumentFragment `自身，而是它的所有子孙节点\n\n\n\n#### createAttribute\n\n创建属性节点，可以是自定义属性\n\n```js\nconst dataAttribute = document.createAttribute('custom');\nconsle.log(dataAttribute);\n```\n\n\n\n### 获取节点\n\n#### querySelector\n\n传入任何有效的` css` 选择器，即可选中单个 `DOM `元素（首个）：\n\n```js\ndocument.querySelector('.element')\ndocument.querySelector('#element')\ndocument.querySelector('div')\ndocument.querySelector('[name=\"username\"]')\ndocument.querySelector('div + p > span')\n```\n\n如果页面上没有指定的元素时，返回 `null`\n\n\n\n#### querySelectorAll\n\n返回一个包含节点子树内所有与之相匹配的`Element`节点列表，如果没有相匹配的，则返回一个空节点列表\n\n```js\nconst notLive = document.querySelectorAll(\"p\");\n```\n\n需要注意的是，该方法返回的是一个 `NodeList `的静态实例，它是一个静态的“快照”，而非“实时”的查询\n\n\n\n\n\n关于获取`DOM`元素的方法还有如下，就不一一述说\n\n```js\ndocument.getElementById('id属性值');返回拥有指定id的对象的引用\ndocument.getElementsByClassName('class属性值');返回拥有指定class的对象集合\ndocument.getElementsByTagName('标签名');返回拥有指定标签名的对象集合\ndocument.getElementsByName('name属性值'); 返回拥有指定名称的对象结合\ndocument/element.querySelector('CSS选择器');  仅返回第一个匹配的元素\ndocument/element.querySelectorAll('CSS选择器');   返回所有匹配的元素\ndocument.documentElement;  获取页面中的HTML标签\ndocument.body; 获取页面中的BODY标签\ndocument.all[''];  获取页面中的所有元素节点的对象集合型\n```\n\n除此之外，每个`DOM`元素还有`parentNode`、`childNodes`、`firstChild`、`lastChild`、`nextSibling`、`previousSibling`属性，关系图如下图所示\n\n ![](https://static.vue-js.com/c100f450-7fdc-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 更新节点\n\n#### innerHTML\n\n不但可以修改一个`DOM`节点的文本内容，还可以直接通过`HTML`片段修改`DOM`节点内部的子树\n\n```js\n// 获取<p id=\"p\">...</p >\nvar p = document.getElementById('p');\n// 设置文本为abc:\np.innerHTML = 'ABC'; // <p id=\"p\">ABC</p >\n// 设置HTML:\np.innerHTML = 'ABC <span style=\"color:red\">RED</span> XYZ';\n// <p>...</p >的内部结构已修改\n```\n\n\n\n#### innerText、textContent\n\n自动对字符串进行`HTML`编码，保证无法设置任何`HTML`标签\n\n```\n// 获取<p id=\"p-id\">...</p >\nvar p = document.getElementById('p-id');\n// 设置文本:\np.innerText = '<script>alert(\"Hi\")</script>';\n// HTML被自动编码，无法设置一个<script>节点:\n// <p id=\"p-id\">&lt;script&gt;alert(\"Hi\")&lt;/script&gt;</p >\n```\n\n两者的区别在于读取属性时，`innerText`不返回隐藏元素的文本，而`textContent`返回所有文本\n\n\n\n#### style\n\n`DOM`节点的`style`属性对应所有的`CSS`，可以直接获取或设置。遇到`-`需要转化为驼峰命名\n\n```js\n// 获取<p id=\"p-id\">...</p >\nconst p = document.getElementById('p-id');\n// 设置CSS:\np.style.color = '#ff0000';\np.style.fontSize = '20px'; // 驼峰命名\np.style.paddingTop = '2em';\n```\n\n\n\n\n\n### 添加节点\n\n#### innerHTML\n\n如果这个DOM节点是空的，例如，`<div></div>`，那么，直接使用`innerHTML = '<span>child</span>'`就可以修改`DOM`节点的内容，相当于添加了新的`DOM`节点\n\n如果这个DOM节点不是空的，那就不能这么做，因为`innerHTML`会直接替换掉原来的所有子节点\n\n\n\n#### appendChild\n\n把一个子节点添加到父节点的最后一个子节点\n\n举个例子\n\n```js\n<!-- HTML结构 -->\n<p id=\"js\">JavaScript</p >\n<div id=\"list\">\n    <p id=\"java\">Java</p >\n    <p id=\"python\">Python</p >\n    <p id=\"scheme\">Scheme</p >\n</div>\n```\n\n添加一个`p`元素\n\n```js\nconst js = document.getElementById('js')\njs.innerHTML = \"JavaScript\"\nconst list = document.getElementById('list');\nlist.appendChild(js);\n```\n\n现在`HTML`结构变成了下面\n\n```js\n<!-- HTML结构 -->\n<div id=\"list\">\n    <p id=\"java\">Java</p >\n    <p id=\"python\">Python</p >\n    <p id=\"scheme\">Scheme</p >\n    <p id=\"js\">JavaScript</p >  <!-- 添加元素 -->\n</div>\n```\n\n上述代码中，我们是获取`DOM`元素后再进行添加操作，这个`js`节点是已经存在当前文档树中，因此这个节点首先会从原先的位置删除，再插入到新的位置\n\n如果动态添加新的节点，则先创建一个新的节点，然后插入到指定的位置\n\n```js\nconst list = document.getElementById('list'),\nconst haskell = document.createElement('p');\nhaskell.id = 'haskell';\nhaskell.innerText = 'Haskell';\nlist.appendChild(haskell);\n```\n\n\n\n#### insertBefore\n\n把子节点插入到指定的位置，使用方法如下：\n\n```js\nparentElement.insertBefore(newElement, referenceElement)\n```\n\n子节点会插入到`referenceElement`之前\n\n\n\n#### setAttribute\n\n在指定元素中添加一个属性节点，如果元素中已有该属性改变属性值\n\n```js\nconst div = document.getElementById('id')\ndiv.setAttribute('class', 'white');//第一个参数属性名，第二个参数属性值。\n```\n\n\n\n### 删除节点\n\n删除一个节点，首先要获得该节点本身以及它的父节点，然后，调用父节点的`removeChild`把自己删掉\n\n```js\n// 拿到待删除节点:\nconst self = document.getElementById('to-be-removed');\n// 拿到父节点:\nconst parent = self.parentElement;\n// 删除:\nconst removed = parent.removeChild(self);\nremoved === self; // true\n```\n\n删除后的节点虽然不在文档树中了，但其实它还在内存中，可以随时再次被添加到别的位置\n\n## 相关链接\nhttps://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model"
  },
  {
    "path": "docs/JavaScript/ajax.md",
    "content": "# 面试官：ajax原理是什么？如何实现？\n\n ![](https://static.vue-js.com/a35a2950-7b2a-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n`AJAX `全称(Async Javascript and XML)\n\n即异步的` JavaScript` 和` XML`，是一种创建交互式网页应用的网页开发技术，可以在不重新加载整个网页的情况下，与服务器交换数据，并且更新部分网页\n\n`Ajax`的原理简单来说通过`XmlHttpRequest`对象来向服务器发异步请求，从服务器获得数据，然后用`JavaScript`来操作`DOM`而更新页面\n\n流程图如下：\n\n ![](https://static.vue-js.com/af42de10-7b2a-11eb-85f6-6fac77c0c9b3.png)\n\n下面举个例子：\n\n领导想找小李汇报一下工作，就委托秘书去叫小李，自己就接着做其他事情，直到秘书告诉他小李已经到了，最后小李跟领导汇报工作\n\n`Ajax`请求数据流程与“领导想找小李汇报一下工作”类似，上述秘书就相当于`XMLHttpRequest`对象，领导相当于浏览器，响应数据相当于小李\n\n浏览器可以发送`HTTP`请求后，接着做其他事情，等收到`XHR`返回来的数据再进行操作\n\n\n\n## 二、实现过程\n\n实现 `Ajax `异步交互需要服务器逻辑进行配合，需要完成以下步骤：\n\n- 创建 `Ajax `的核心对象 `XMLHttpRequest `对象\n\n- 通过 `XMLHttpRequest` 对象的 `open()` 方法与服务端建立连接\n\n- 构建请求所需的数据内容，并通过` XMLHttpRequest` 对象的 `send()` 方法发送给服务器端\n\n- 通过 `XMLHttpRequest` 对象提供的 `onreadystatechange` 事件监听服务器端你的通信状态\n\n- 接受并处理服务端向客户端响应的数据结果\n\n- 将处理结果更新到 `HTML `页面中\n\n\n\n### 创建XMLHttpRequest对象\n\n通过`XMLHttpRequest()` 构造函数用于初始化一个 `XMLHttpRequest` 实例对象\n\n```js\nconst xhr = new XMLHttpRequest();\n```\n\n\n\n### 与服务器建立连接\n\n通过 `XMLHttpRequest` 对象的 `open()` 方法与服务器建立连接\n\n```js\nxhr.open(method, url, [async][, user][, password])\n```\n\n参数说明：\n\n- `method`：表示当前的请求方式，常见的有`GET`、`POST`\n\n- `url`：服务端地址\n\n- `async`：布尔值，表示是否异步执行操作，默认为`true`\n\n-  `user`: 可选的用户名用于认证用途；默认为`null\n\n\n- `password`: 可选的密码用于认证用途，默认为`null\n\n\n\n### 给服务端发送数据\n\n通过 `XMLHttpRequest` 对象的 `send()` 方法，将客户端页面的数据发送给服务端\n\n```js\nxhr.send([body])\n```\n\n`body`: 在 `XHR` 请求中要发送的数据体，如果不传递数据则为 `null`\n\n如果使用`GET`请求发送数据的时候，需要注意如下：\n\n- 将请求数据添加到`open()`方法中的`url`地址中\n- 发送请求数据中的`send()`方法中参数设置为`null`\n\n\n\n### 绑定onreadystatechange事件\n\n`onreadystatechange` 事件用于监听服务器端的通信状态，主要监听的属性为`XMLHttpRequest.readyState` ,\n\n关于`XMLHttpRequest.readyState`属性有五个状态，如下图显示\n\n![](https://static.vue-js.com/9782fc90-7b31-11eb-ab90-d9ae814b240d.png)\n\n只要 `readyState `属性值一变化，就会触发一次 `readystatechange` 事件\n\n`XMLHttpRequest.responseText`属性用于接收服务器端的响应结果\n\n举个例子：\n\n```js\nconst request = new XMLHttpRequest()\nrequest.onreadystatechange = function(e){\n    if(request.readyState === 4){ // 整个请求过程完毕\n        if(request.status >= 200 && request.status <= 300){\n            console.log(request.responseText) // 服务端返回的结果\n        }else if(request.status >=400){\n            console.log(\"错误信息：\" + request.status)\n        }\n    }\n}\nrequest.open('POST','http://xxxx')\nrequest.send()\n```\n\n\n\n\n\n## 三、封装\n\n通过上面对`XMLHttpRequest `对象的了解，下面来封装一个简单的`ajax`请求\n\n```js\n//封装一个ajax请求\nfunction ajax(options) {\n    //创建XMLHttpRequest对象\n    const xhr = new XMLHttpRequest()\n\n\n    //初始化参数的内容\n    options = options || {}\n    options.type = (options.type || 'GET').toUpperCase()\n    options.dataType = options.dataType || 'json'\n    const params = options.data\n\n    //发送请求\n    if (options.type === 'GET') {\n        xhr.open('GET', options.url + '?' + params, true)\n        xhr.send(null)\n    } else if (options.type === 'POST') {\n        xhr.open('POST', options.url, true)\n        xhr.send(params)\n\n    //接收请求\n    xhr.onreadystatechange = function () {\n        if (xhr.readyState === 4) {\n            let status = xhr.status\n            if (status >= 200 && status < 300) {\n                options.success && options.success(xhr.responseText, xhr.responseXML)\n            } else {\n                options.fail && options.fail(status)\n            }\n        }\n    }\n}\n```\n\n使用方式如下\n\n```js\najax({\n    type: 'post',\n    dataType: 'json',\n    data: {},\n    url: 'https://xxxx',\n    success: function(text,xml){//请求成功后的回调函数\n        console.log(text)\n    },\n    fail: function(status){////请求失败后的回调函数\n        console.log(status)\n    }\n})\n```"
  },
  {
    "path": "docs/JavaScript/array_api.md",
    "content": "# 面试官：数组的常用方法有哪些？\n\n ![](https://static.vue-js.com/5842e560-67b6-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、操作方法\n\n数组基本操作可以归纳为 增、删、改、查，需要留意的是哪些方法会对原数组产生影响，哪些方法不会\n\n下面对数组常用的操作方法做一个归纳\n\n### 增 \n\n下面前三种是对原数组产生影响的增添方法，第四种则不会对原数组产生影响\n\n- push()\n- unshift()\n- splice()\n- concat()\n\n\n#### push()\n\n`push()`方法接收任意数量的参数，并将它们添加到数组末尾，返回数组的最新长度\n\n```js\nlet colors = []; // 创建一个数组\nlet count = colors.push(\"red\", \"green\"); // 推入两项\nconsole.log(count) // 2\n```\n\n\n\n#### unshift()\n\nunshift()在数组开头添加任意多个值，然后返回新的数组长度\n\n```js\nlet colors = new Array(); // 创建一个数组\nlet count = colors.unshift(\"red\", \"green\"); // 从数组开头推入两项\nalert(count); // 2\n```\n\n\n\n#### splice\n\n传入三个参数，分别是开始位置、0（要删除的元素数量）、插入的元素，返回空数组\n\n```js\nlet colors = [\"red\", \"green\", \"blue\"];\nlet removed = colors.splice(1, 0, \"yellow\", \"orange\")\nconsole.log(colors) // red,yellow,orange,green,blue\nconsole.log(removed) // []\n```\n\n\n#### concat()\n\n首先会创建一个当前数组的副本，然后再把它的参数添加到副本末尾，最后返回这个新构建的数组，不会影响原始数组\n\n```js\nlet colors = [\"red\", \"green\", \"blue\"];\nlet colors2 = colors.concat(\"yellow\", [\"black\", \"brown\"]);\nconsole.log(colors); // [\"red\", \"green\",\"blue\"]\nconsole.log(colors2); // [\"red\", \"green\", \"blue\", \"yellow\", \"black\", \"brown\"]\n```\n\n\n\n### 删\n\n下面三种都会影响原数组，最后一项不影响原数组：\n\n- pop()\n- shift()\n- splice()\n- slice()\n\n\n\n#### pop()\n\n `pop()` 方法用于删除数组的最后一项，同时减少数组的` length` 值，返回被删除的项\n\n```js\nlet colors = [\"red\", \"green\"]\nlet item = colors.pop(); // 取得最后一项\nconsole.log(item) // green\nconsole.log(colors.length) // 1\n```\n\n\n\n#### shift()\n\n` shift() `方法用于删除数组的第一项，同时减少数组的` length` 值，返回被删除的项\n\n```js\nlet colors = [\"red\", \"green\"]\nlet item = colors.shift(); // 取得第一项\nconsole.log(item) // red\nconsole.log(colors.length) // 1\n```\n\n\n\n#### splice()\n\n传入两个参数，分别是开始位置，删除元素的数量，返回包含删除元素的数组\n\n```js\nlet colors = [\"red\", \"green\", \"blue\"];\nlet removed = colors.splice(0,1); // 删除第一项\nconsole.log(colors); // green,blue\nconsole.log(removed); // red，只有一个元素的数组\n```\n\n\n\n### slice()\n\n slice() 用于创建一个包含原有数组中一个或多个元素的新数组，不会影响原始数组\n\n```js\nlet colors = [\"red\", \"green\", \"blue\", \"yellow\", \"purple\"];\nlet colors2 = colors.slice(1);\nlet colors3 = colors.slice(1, 4);\nconsole.log(colors)   // red,green,blue,yellow,purple\nconcole.log(colors2); // green,blue,yellow,purple\nconcole.log(colors3); // green,blue,yellow\n```\n\n\n\n#### 改\n即修改原来数组的内容，常用`splice`\n#### splice() \n\n传入三个参数，分别是开始位置，要删除元素的数量，要插入的任意多个元素，返回删除元素的数组，对原数组产生影响\n\n```js\nlet colors = [\"red\", \"green\", \"blue\"];\nlet removed = colors.splice(1, 1, \"red\", \"purple\"); // 插入两个值，删除一个元素\nconsole.log(colors); // red,red,purple,blue\nconsole.log(removed); // green，只有一个元素的数组\n```\n\n\n\n#### 查\n\n即查找元素，返回元素坐标或者元素值\n\n- indexOf()\n- includes()\n- find()\n\n#### indexOf()\n\n返回要查找的元素在数组中的位置，如果没找到则返回 -1\n\n```js\nlet numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\nnumbers.indexOf(4) // 3\n```\n\n\n\n#### includes()\n\n返回要查找的元素在数组中的位置，找到返回`true`，否则`false`\n\n```js\nlet numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\nnumbers.includes(4) // true\n```\n\n#### find()\n\n返回第一个匹配的元素\n\n```js\nconst people = [\n    {\n        name: \"Matt\",\n        age: 27\n    },\n    {\n        name: \"Nicholas\",\n        age: 29\n    }\n];\npeople.find((element, index, array) => element.age < 28) // // {name: \"Matt\", age: 27}\n```\n\n\n\n## 二、排序方法\n\n数组有两个方法可以用来对元素重新排序：\n\n- reverse() \n- sort()\n\n### reverse()\n\n顾名思义，将数组元素方向反转\n\n```js\nlet values = [1, 2, 3, 4, 5];\nvalues.reverse();\nalert(values); // 5,4,3,2,1\n```\n\n\n\n### sort()\n\nsort()方法接受一个比较函数，用于判断哪个值应该排在前面\n\n```js\nfunction compare(value1, value2) {\n    if (value1 < value2) {\n        return -1;\n    } else if (value1 > value2) {\n        return 1;\n    } else {\n        return 0;\n    }\n}\nlet values = [0, 1, 5, 10, 15];\nvalues.sort(compare);\nalert(values); // 0,1,5,10,15\n```\n\n\n\n## 三、转换方法\n\n常见的转换方法有：\n\n### join()\n\njoin() 方法接收一个参数，即字符串分隔符，返回包含所有项的字符串\n\n```js\nlet colors = [\"red\", \"green\", \"blue\"];\nalert(colors.join(\",\")); // red,green,blue\nalert(colors.join(\"||\")); // red||green||blue\n```\n\n\n\n\n\n## 四、迭代方法\n\n常用来迭代数组的方法（都不改变原数组）有如下：\n\n- some()\n- every()\n- forEach()\n- filter()\n- map()\n\n\n\n### some()\n\n对数组每一项都运行传入的测试函数，如果至少有1个元素返回 true ，则这个方法返回 true\n\n```js\nlet numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\nlet someResult = numbers.some((item, index, array) => item > 2);\nconsole.log(someResult) // true\n```\n\n\n\n### every()\n\n对数组每一项都运行传入的测试函数，如果所有元素都返回 true ，则这个方法返回 true\n\n```js\nlet numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\nlet everyResult = numbers.every((item, index, array) => item > 2);\nconsole.log(everyResult) // false\n```\n\n\n\n### forEach()\n\n对数组每一项都运行传入的函数，没有返回值\n\n```js\nlet numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\nnumbers.forEach((item, index, array) => {\n    // 执行某些操作\n});\n```\n\n\n\n### filter()\n\n对数组每一项都运行传入的函数，函数返回 `true` 的项会组成数组之后返回\n\n```js\nlet numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\nlet filterResult = numbers.filter((item, index, array) => item > 2);\nconsole.log(filterResult); // 3,4,5,4,3\n```\n\n\n\n### map()\n\n对数组每一项都运行传入的函数，返回由每次函数调用的结果构成的数组\n\n```js\nlet numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\nlet mapResult = numbers.map((item, index, array) => item * 2);\nconsole.log(mapResult) // 2,4,6,8,10,8,6,4,2\n```\n"
  },
  {
    "path": "docs/JavaScript/bind_call_apply.md",
    "content": "# 面试官：bind、call、apply 区别？如何实现一个bind?\n\n ![](https://static.vue-js.com/a900e460-7be4-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、作用\n\n`call `、`apply `、`bind `作用是改变函数执行时的上下文，简而言之就是改变函数运行时的`this`指向\n\n那么什么情况下需要改变`this`的指向呢？下面举个例子\n\n```js\nvar name = \"lucy\";\nvar obj = {\n    name: \"martin\",\n    say: function () {\n        console.log(this.name);\n    }\n};\nobj.say(); // martin，this 指向 obj 对象\nsetTimeout(obj.say,0); // lucy，this 指向 window 对象\n```\n\n从上面可以看到，正常情况`say`方法输出`martin`\n\n但是我们把`say`放在`setTimeout`方法中，在定时器中是作为回调函数来执行的，因此回到主栈执行时是在全局执行上下文的环境中执行的，这时候`this`指向`window`，所以输出`lucy`\n\n我们实际需要的是`this`指向`obj`对象，这时候就需要该改变`this`指向了\n\n```js\nsetTimeout(obj.say.bind(obj),0); //martin，this指向obj对象\n```\n\n\n\n## 二、区别\n\n下面再来看看`apply`、`call`、`bind`的使用\n\n### apply\n\n`apply`接受两个参数，第一个参数是`this`的指向，第二个参数是函数接受的参数，以数组的形式传入\n\n改变`this`指向后原函数会立即执行，且此方法只是临时改变`this`指向一次\n\n```js\nfunction fn(...args){\n    console.log(this,args);\n}\nlet obj = {\n    myname:\"张三\"\n}\n\nfn.apply(obj,[1,2]); // this会变成传入的obj，传入的参数必须是一个数组；\nfn(1,2) // this指向window\n```\n\n当第一个参数为`null`、`undefined`的时候，默认指向`window`(在浏览器中)\n\n```js\nfn.apply(null,[1,2]); // this指向window\nfn.apply(undefined,[1,2]); // this指向window\n```\n\n\n\n### call\n\n`call`方法的第一个参数也是`this`的指向，后面传入的是一个参数列表\n\n跟`apply`一样，改变`this`指向后原函数会立即执行，且此方法只是临时改变`this`指向一次\n\n```js\nfunction fn(...args){\n    console.log(this,args);\n}\nlet obj = {\n    myname:\"张三\"\n}\n\nfn.call(obj,1,2); // this会变成传入的obj，传入的参数必须是一个数组；\nfn(1,2) // this指向window\n```\n\n同样的，当第一个参数为`null`、`undefined`的时候，默认指向`window`(在浏览器中)\n\n```js\nfn.call(null,[1,2]); // this指向window\nfn.call(undefined,[1,2]); // this指向window\n```\n\n\n\n### bind\n\nbind方法和call很相似，第一参数也是`this`的指向，后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)\n\n改变`this`指向后不会立即执行，而是返回一个永久改变`this`指向的函数\n\n```js\nfunction fn(...args){\n    console.log(this,args);\n}\nlet obj = {\n    myname:\"张三\"\n}\n\nconst bindFn = fn.bind(obj); // this 也会变成传入的obj ，bind不是立即执行需要执行一次\nbindFn(1,2) // this指向obj\nfn(1,2) // this指向window\n```\n\n\n### 小结\n\n从上面可以看到，`apply`、`call`、`bind`三者的区别在于：\n\n- 三者都可以改变函数的`this`对象指向\n- 三者第一个参数都是`this`要指向的对象，如果如果没有这个参数或参数为`undefined`或`null`，则默认指向全局`window`\n- 三者都可以传参，但是`apply`是数组，而`call`是参数列表，且`apply`和`call`是一次性传入参数，而`bind`可以分为多次传入\n- `bind `是返回绑定this之后的函数，`apply `、`call` 则是立即执行 \n\n\n\n## 三、实现\n\n实现`bind`的步骤，我们可以分解成为三部分：\n\n- 修改`this`指向\n- 动态传递参数\n\n```js\n// 方式一：只在bind中传递函数参数\nfn.bind(obj,1,2)()\n\n// 方式二：在bind中传递函数参数，也在返回函数中传递参数\nfn.bind(obj,1)(2)\n```\n\n- 兼容`new`关键字\n\n整体实现代码如下：\n\n```js\nFunction.prototype.myBind = function (context) {\n    // 判断调用对象是否为函数\n    if (typeof this !== \"function\") {\n        throw new TypeError(\"Error\");\n    }\n\n    // 获取参数\n    const args = [...arguments].slice(1),\n          fn = this;\n\n    return function Fn() {\n\n        // 根据调用方式，传入不同绑定值\n        return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments)); \n    }\n}\n```"
  },
  {
    "path": "docs/JavaScript/cache.md",
    "content": "# 面试官：Javascript本地存储的方式有哪些？区别及应用场景？\n\n ![](https://static.vue-js.com/68dccf20-849f-11eb-ab90-d9ae814b240d.png)\n\n## 一、方式\n\n`javaScript`本地缓存的方法我们主要讲述以下四种：\n\n- cookie\n- sessionStorage\n- localStorage\n- indexedDB\n\n\n### cookie\n\n`Cookie`，类型为「小型文本文件」，指某些网站为了辨别用户身份而储存在用户本地终端上的数据。是为了解决 `HTTP `无状态导致的问题\n\n作为一段一般不超过 4KB 的小型文本数据，它由一个名称（Name）、一个值（Value）和其它几个用于控制 `cookie `有效期、安全性、使用范围的可选属性组成\n\n但是`cookie`在每次请求中都会被发送，如果不使用 `HTTPS `并对其加密，其保存的信息很容易被窃取，导致安全风险。举个例子，在一些使用 `cookie `保持登录态的网站上，如果 `cookie `被窃取，他人很容易利用你的 `cookie `来假扮成你登录网站\n\n关于`cookie`常用的属性如下：\n\n- Expires 用于设置 Cookie 的过期时间\n\n```js\nExpires=Wed, 21 Oct 2015 07:28:00 GMT\n```\n\n- Max-Age 用于设置在 Cookie 失效之前需要经过的秒数（优先级比`Expires`高）\n\n```js\nMax-Age=604800\n```\n\n- `Domain `指定了 `Cookie` 可以送达的主机名\n- `Path `指定了一个 `URL `路径，这个路径必须出现在要请求的资源的路径中才可以发送 `Cookie` 首部\n\n```js\nPath=/docs   # /docs/Web/ 下的资源会带 Cookie 首部\n```\n\n- 标记为 `Secure `的 `Cookie `只应通过被`HTTPS`协议加密过的请求发送给服务端\n\n通过上述，我们可以看到`cookie`又开始的作用并不是为了缓存而设计出来，只是借用了`cookie`的特性实现缓存\n\n关于`cookie`的使用如下：\n\n```js\ndocument.cookie = '名字=值';\n```\n\n关于`cookie`的修改，首先要确定`domain`和`path`属性都是相同的才可以，其中有一个不同得时候都会创建出一个新的`cookie`\n\n```js\nSet-Cookie:name=aa; domain=aa.net; path=/  # 服务端设置\ndocument.cookie =name=bb; domain=aa.net; path=/  # 客户端设置\n```\n\n最后`cookie`的删除，最常用的方法就是给`cookie`设置一个过期的事件，这样`cookie`过期后会被浏览器删除\n\n\n\n### localStorage\n\n`HTML5`新方法，IE8及以上浏览器都兼容\n\n### 特点\n\n- 生命周期：持久化的本地存储，除非主动删除数据，否则数据是永远不会过期的\n- 存储的信息在同一域中是共享的\n- 当本页操作（新增、修改、删除）了`localStorage`的时候，本页面不会触发`storage`事件,但是别的页面会触发`storage`事件。\n- 大小：5M（跟浏览器厂商有关系）\n- `localStorage`本质上是对字符串的读取，如果存储内容多的话会消耗内存空间，会导致页面变卡\n- 受同源策略的限制\n\n下面再看看关于`localStorage`的使用\n\n设置\n\n```js\nlocalStorage.setItem('username','cfangxu');\n```\n\n获取\n\n```js\nlocalStorage.getItem('username')\n```\n\n获取键名\n\n```js\nlocalStorage.key(0) //获取第一个键名\n```\n\n删除\n\n```js\nlocalStorage.removeItem('username')\n```\n\n一次性清除所有存储\n\n```js\nlocalStorage.clear()\n```\n\n`localStorage` 也不是完美的，它有两个缺点：\n\n- 无法像` Cookie `一样设置过期时间\n- 只能存入字符串，无法直接存对象\n\n```js\nlocalStorage.setItem('key', {name: 'value'});\nconsole.log(localStorage.getItem('key')); // '[object, Object]'\n```\n\n\n\n### sessionStorage\n\n`sessionStorage `和 `localStorage `使用方法基本一致，唯一不同的是生命周期，一旦页面（会话）关闭，`sessionStorage` 将会删除数据\n\n\n\n### 扩展的前端存储方式\n\n`indexedDB `是一种低级API，用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索\n\n虽然 `Web Storage `对于存储较少量的数据很有用，但对于存储更大量的结构化数据来说，这种方法不太有用。`IndexedDB`提供了一个解决方案\n\n#### 优点：\n\n- 储存量理论上没有上限\n- 所有操作都是异步的，相比 `LocalStorage` 同步操作性能更高，尤其是数据量较大时\n- 原生支持储存` JS `的对象\n- 是个正经的数据库，意味着数据库能干的事它都能干\n\n#### 缺点：\n\n- 操作非常繁琐\n- 本身有一定门槛\n\n关于`indexedDB`的使用基本使用步骤如下：\n\n- 打开数据库并且开始一个事务\n\n- 创建一个 `object store`\n- 构建一个请求来执行一些数据库操作，像增加或提取数据等。\n- 通过监听正确类型的 `DOM` 事件以等待操作完成。\n- 在操作结果上进行一些操作（可以在 `request `对象中找到）\n\n关于使用`indexdb`的使用会比较繁琐，大家可以通过使用`Godb.js`库进行缓存，最大化的降低操作难度\n\n\n\n\n## 二、区别\n\n关于`cookie`、`sessionStorage`、`localStorage`三者的区别主要如下：\n\n- 存储大小：` cookie`数据大小不能超过`4k`，`sessionStorage`和`localStorage `虽然也有存储大小的限制，但比`cookie`大得多，可以达到5M或更大\n\n- 有效时间：` localStorage   `存储持久数据，浏览器关闭后数据不丢失除非主动删除数据； `sessionStorage  `数据在当前浏览器窗口关闭后自动删除；` cookie `设置的`cookie`过期时间之前一直有效，即使窗口或浏览器关闭\n\n- 数据与服务器之间的交互方式，`  cookie`的数据会自动的传递到服务器，服务器端也可以写`cookie`到客户端； `sessionStorage`和`localStorage`不会自动把数据发给服务器，仅在本地保存\n\n\n\n## 三、应用场景\n\n在了解了上述的前端的缓存方式后，我们可以看看针对不对场景的使用选择：\n\n- 标记用户与跟踪用户行为的情况，推荐使用`cookie`\n- 适合长期保存在本地的数据（令牌），推荐使用`localStorage`\n- 敏感账号一次性登录，推荐使用`sessionStorage`\n- 存储大量数据的情况、在线文档（富文本编辑器）保存编辑历史的情况，推荐使用`indexedDB`\n\n\n\n## 相关连接\n\n- https://mp.weixin.qq.com/s/mROjtpoXarN--UDfEMqwhQ\n- https://github.com/chenstarx/GoDB.js"
  },
  {
    "path": "docs/JavaScript/closure.md",
    "content": "# 面试官：说说你对闭包的理解？闭包使用场景\n\n ![](https://static.vue-js.com/c141a030-6a7a-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n一个函数和对其周围状态（lexical environment，词法环境）的引用捆绑在一起（或者说函数被引用包围），这样的组合就是闭包（closure）\n\n也就是说，闭包让你可以在一个内层函数中访问到其外层函数的作用域\n\n在 `JavaScript `中，每当创建一个函数，闭包就会在函数创建的同时被创建出来，作为函数内部与外部连接起来的一座桥梁\n\n下面给出一个简单的例子\n\n```js\nfunction init() {\n    var name = \"Mozilla\"; // name 是一个被 init 创建的局部变量\n    function displayName() { // displayName() 是内部函数，一个闭包\n        alert(name); // 使用了父函数中声明的变量\n    }\n    displayName();\n}\ninit();\n```\n\n`displayName()` 没有自己的局部变量。然而，由于闭包的特性，它可以访问到外部函数的变量\n\n\n\n## 二、使用场景\n\n任何闭包的使用场景都离不开这两点：\n\n- 创建私有变量\n- 延长变量的生命周期\n\n> 一般函数的词法环境在函数返回后就被销毁，但是闭包会保存对创建时所在词法环境的引用，即便创建时所在的执行上下文被销毁，但创建时所在词法环境依然存在，以达到延长变量的生命周期的目的\n\n\n下面举个例子：\n\n在页面上添加一些可以调整字号的按钮\n\n```js\nfunction makeSizer(size) {\n  return function() {\n    document.body.style.fontSize = size + 'px';\n  };\n}\n\nvar size12 = makeSizer(12);\nvar size14 = makeSizer(14);\nvar size16 = makeSizer(16);\n\ndocument.getElementById('size-12').onclick = size12;\ndocument.getElementById('size-14').onclick = size14;\ndocument.getElementById('size-16').onclick = size16;\n```\n\n\n\n### 柯里化函数\n\n柯里化的目的在于避免频繁调用具有相同参数函数的同时，又能够轻松的重用\n\n```js\n// 假设我们有一个求长方形面积的函数\nfunction getArea(width, height) {\n    return width * height\n}\n// 如果我们碰到的长方形的宽老是10\nconst area1 = getArea(10, 20)\nconst area2 = getArea(10, 30)\nconst area3 = getArea(10, 40)\n\n// 我们可以使用闭包柯里化这个计算面积的函数\nfunction getArea(width) {\n    return height => {\n        return width * height\n    }\n}\n\nconst getTenWidthArea = getArea(10)\n// 之后碰到宽度为10的长方形就可以这样计算面积\nconst area1 = getTenWidthArea(20)\n\n// 而且如果遇到宽度偶尔变化也可以轻松复用\nconst getTwentyWidthArea = getArea(20)\n```\n\n\n\n### 使用闭包模拟私有方法\n\n在`JavaScript`中，没有支持声明私有变量，但我们可以使用闭包来模拟私有方法\n\n\n下面举个例子：\n\n```js\nvar Counter = (function() {\n  var privateCounter = 0;\n  function changeBy(val) {\n    privateCounter += val;\n  }\n  return {\n    increment: function() {\n      changeBy(1);\n    },\n    decrement: function() {\n      changeBy(-1);\n    },\n    value: function() {\n      return privateCounter;\n    }\n  }\n})();\n\nvar Counter1 = makeCounter();\nvar Counter2 = makeCounter();\nconsole.log(Counter1.value()); /* logs 0 */\nCounter1.increment();\nCounter1.increment();\nconsole.log(Counter1.value()); /* logs 2 */\nCounter1.decrement();\nconsole.log(Counter1.value()); /* logs 1 */\nconsole.log(Counter2.value()); /* logs 0 */\n```\n\n上述通过使用闭包来定义公共函数，并令其可以访问私有函数和变量，这种方式也叫模块方式\n\n两个计数器 `Counter1` 和 `Counter2` 是维护它们各自的独立性的，每次调用其中一个计数器时，通过改变这个变量的值，会改变这个闭包的词法环境，不会影响另一个闭包中的变量\n\n\n\n### 其他\n\n例如计数器、延迟调用、回调等闭包的应用，其核心思想还是创建私有变量和延长变量的生命周期\n\n\n\n## 三、注意事项\n\n如果不是某些特定任务需要使用闭包，在其它函数中创建函数是不明智的，因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响\n\n例如，在创建新的对象或者类时，方法通常应该关联于对象的原型，而不是定义到对象的构造器中。\n\n原因在于每个对象的创建，方法都会被重新赋值\n\n```js\nfunction MyObject(name, message) {\n  this.name = name.toString();\n  this.message = message.toString();\n  this.getName = function() {\n    return this.name;\n  };\n\n  this.getMessage = function() {\n    return this.message;\n  };\n}\n```\n\n上面的代码中，我们并没有利用到闭包的好处，因此可以避免使用闭包。修改成如下：\n\n```js\nfunction MyObject(name, message) {\n  this.name = name.toString();\n  this.message = message.toString();\n}\nMyObject.prototype.getName = function() {\n  return this.name;\n};\nMyObject.prototype.getMessage = function() {\n  return this.message;\n};\n```"
  },
  {
    "path": "docs/JavaScript/context_stack.md",
    "content": "# 面试官：JavaScript中执行上下文和执行栈是什么？\n\n![](https://static.vue-js.com/8652b710-74c1-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、执行上下文\n\n简单的来说，执行上下文是一种对`Javascript`代码执行环境的抽象概念，也就是说只要有`Javascript`代码运行，那么它就一定是运行在执行上下文中\n\n执行上下文的类型分为三种：\n\n- 全局执行上下文：只有一个，浏览器中的全局对象就是 `window `对象，`this` 指向这个全局对象\n- 函数执行上下文：存在无数个，只有在函数被调用的时候才会被创建，每次调用函数都会创建一个新的执行上下文\n- Eval 函数执行上下文： 指的是运行在 `eval` 函数中的代码，很少用而且不建议使用\n\n下面给出全局上下文和函数上下文的例子：\n\n ![](https://static.vue-js.com/90dd3b60-74c1-11eb-85f6-6fac77c0c9b3.png)\n\n紫色框住的部分为全局上下文，蓝色和橘色框起来的是不同的函数上下文。只有全局上下文（的变量）能被其他任何上下文访问\n\n可以有任意多个函数上下文，每次调用函数创建一个新的上下文，会创建一个私有作用域，函数内部声明的任何变量都不能在当前函数作用域外部直接访问\n\n\n\n## 二、生命周期\n\n执行上下文的生命周期包括三个阶段：创建阶段 → 执行阶段 → 回收阶段\n\n### 创建阶段\n\n创建阶段即当函数被调用，但未执行任何其内部代码之前\n\n创建阶段做了三件事：\n\n- 确定 this 的值，也被称为 `This Binding`\n- LexicalEnvironment（词法环境） 组件被创建\n- VariableEnvironment（变量环境） 组件被创建\n\n伪代码如下：\n\n```js\nExecutionContext = {  \n  ThisBinding = <this value>,     // 确定this \n  LexicalEnvironment = { ... },   // 词法环境\n  VariableEnvironment = { ... },  // 变量环境\n}\n```\n\n\n\n#### This Binding\n\n确定`this`的值我们前面讲到，`this`的值是在执行的时候才能确认，定义的时候不能确认\n\n\n#### 词法环境\n\n词法环境有两个组成部分：\n\n- 全局环境：是一个没有外部环境的词法环境，其外部环境引用为` null`，有一个全局对象，`this` 的值指向这个全局对象\n\n- 函数环境：用户在函数中定义的变量被存储在环境记录中，包含了`arguments` 对象，外部环境的引用可以是全局环境，也可以是包含内部函数的外部函数环境\n\n伪代码如下：\n\n```js\nGlobalExectionContext = {  // 全局执行上下文\n  LexicalEnvironment: {       // 词法环境\n    EnvironmentRecord: {     // 环境记录\n      Type: \"Object\",           // 全局环境\n      // 标识符绑定在这里 \n      outer: <null>           // 对外部环境的引用\n  }  \n}\n\nFunctionExectionContext = { // 函数执行上下文\n  LexicalEnvironment: {     // 词法环境\n    EnvironmentRecord: {    // 环境记录\n      Type: \"Declarative\",      // 函数环境\n      // 标识符绑定在这里      // 对外部环境的引用\n      outer: <Global or outer function environment reference>  \n  }  \n}\n```\n\n\n\n#### 变量环境\n\n变量环境也是一个词法环境，因此它具有上面定义的词法环境的所有属性\n\n在 ES6 中，词法环境和变量环境的区别在于前者用于存储函数声明和变量（ `let` 和 `const` ）绑定，而后者仅用于存储变量（ `var` ）绑定\n\n举个例子\n\n```js\nlet a = 20;  \nconst b = 30;  \nvar c;\n\nfunction multiply(e, f) {  \n var g = 20;  \n return e * f * g;  \n}\n\nc = multiply(20, 30);\n```\n\n执行上下文如下：\n\n```js\nGlobalExectionContext = {\n\n  ThisBinding: <Global Object>,\n\n  LexicalEnvironment: {  // 词法环境\n    EnvironmentRecord: {  \n      Type: \"Object\",  \n      // 标识符绑定在这里  \n      a: < uninitialized >,  \n      b: < uninitialized >,  \n      multiply: < func >  \n    }  \n    outer: <null>  \n  },\n\n  VariableEnvironment: {  // 变量环境\n    EnvironmentRecord: {  \n      Type: \"Object\",  \n      // 标识符绑定在这里  \n      c: undefined,  \n    }  \n    outer: <null>  \n  }  \n}\n\nFunctionExectionContext = {  \n   \n  ThisBinding: <Global Object>,\n\n  LexicalEnvironment: {  \n    EnvironmentRecord: {  \n      Type: \"Declarative\",  \n      // 标识符绑定在这里  \n      Arguments: {0: 20, 1: 30, length: 2},  \n    },  \n    outer: <GlobalLexicalEnvironment>  \n  },\n\n  VariableEnvironment: {  \n    EnvironmentRecord: {  \n      Type: \"Declarative\",  \n      // 标识符绑定在这里  \n      g: undefined  \n    },  \n    outer: <GlobalLexicalEnvironment>  \n  }  \n}\n```\n\n留意上面的代码，`let`和`const`定义的变量`a`和`b`在创建阶段没有被赋值，但`var`声明的变量从在创建阶段被赋值为`undefined`\n\n这是因为，创建阶段，会在代码中扫描变量和函数声明，然后将函数声明存储在环境中\n\n但变量会被初始化为`undefined`(`var`声明的情况下)和保持`uninitialized`(未初始化状态)(使用`let`和`const`声明的情况下)\n\n这就是变量提升的实际原因\n\n\n\n### 执行阶段\n\n在这阶段，执行变量赋值、代码执行\n\n如果 `Javascript` 引擎在源代码中声明的实际位置找不到变量的值，那么将为其分配 `undefined` 值\n\n\n\n### 回收阶段\n\n执行上下文出栈等待虚拟机回收执行上下文\n\n\n\n## 二、执行栈\n\n执行栈，也叫调用栈，具有 LIFO（后进先出）结构，用于存储在代码执行期间创建的所有执行上下文\n\n ![](https://static.vue-js.com/9eda0310-74c1-11eb-ab90-d9ae814b240d.png)\n\n当`Javascript`引擎开始执行你第一行脚本代码的时候，它就会创建一个全局执行上下文然后将它压到执行栈中\n\n每当引擎碰到一个函数的时候，它就会创建一个函数执行上下文，然后将这个执行上下文压到执行栈中\n\n引擎会执行位于执行栈栈顶的执行上下文(一般是函数执行上下文)，当该函数执行结束后，对应的执行上下文就会被弹出，然后控制流程到达执行栈的下一个执行上下文\n\n举个例子：\n\n```js\nlet a = 'Hello World!';\nfunction first() {\n  console.log('Inside first function');\n  second();\n  console.log('Again inside first function');\n}\nfunction second() {\n  console.log('Inside second function');\n}\nfirst();\nconsole.log('Inside Global Execution Context');\n```\n\n转化成图的形式\n\n ![](https://static.vue-js.com/ac11a600-74c1-11eb-ab90-d9ae814b240d.png)\n\n简单分析一下流程：\n\n- 创建全局上下文请压入执行栈\n- `first`函数被调用，创建函数执行上下文并压入栈\n- 执行`first`函数过程遇到`second`函数，再创建一个函数执行上下文并压入栈\n- `second`函数执行完毕，对应的函数执行上下文被推出执行栈，执行下一个执行上下文`first`函数\n- `first`函数执行完毕，对应的函数执行上下文也被推出栈中，然后执行全局上下文\n- 所有代码执行完毕，全局上下文也会被推出栈中，程序结束\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/107552264"
  },
  {
    "path": "docs/JavaScript/continue_to_upload.md",
    "content": "# 面试官：大文件上传如何做断点续传？\n\n ![](https://static.vue-js.com/3ccb0e90-8ba4-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n不管怎样简单的需求，在量级达到一定层次时，都会变得异常复杂\n\n文件上传简单，文件变大就复杂\n\n上传大文件时，以下几个变量会影响我们的用户体验\n\n- 服务器处理数据的能力\n- 请求超时\n- 网络波动\n\n上传时间会变长，高频次文件上传失败，失败后又需要重新上传等等\n\n为了解决上述问题，我们需要对大文件上传单独处理\n\n这里涉及到分片上传及断点续传两个概念\n\n#### 分片上传\n\n分片上传，就是将所要上传的文件，按照一定的大小，将整个文件分隔成多个数据块（Part）来进行分片上传\n\n如下图\n\n ![](https://static.vue-js.com/21db7520-8ba4-11eb-85f6-6fac77c0c9b3.png)\n\n上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件\n\n大致流程如下：\n1. 将需要上传的文件按照一定的分割规则，分割成相同大小的数据块；\n2. 初始化一个分片上传任务，返回本次分片上传唯一标识；\n3. 按照一定的策略（串行或并行）发送各个分片数据块；\n4. 发送完成后，服务端根据判断数据上传是否完整，如果完整，则进行数据块合成得到原始文件\n\n#### 断点续传\n断点续传指的是在下载或上传时，将下载或上传任务人为的划分为几个部分\n\n每一个部分采用一个线程进行上传或下载，如果碰到网络故障，可以从已经上传或下载的部分开始继续上传下载未完成的部分，而没有必要从头开始上传下载。用户可以节省时间，提高速度\n\n一般实现方式有两种：\n\n- 服务器端返回，告知从哪开始\n- 浏览器端自行处理\n\n上传过程中将文件在服务器写为临时文件，等全部写完了（文件上传完），将此临时文件重命名为正式文件即可\n\n如果中途上传中断过，下次上传的时候根据当前临时文件大小，作为在客户端读取文件的偏移量，从此位置继续读取文件数据块，上传到服务器从此偏移量继续写入文件即可\n\n## 二、实现思路\n\n整体思路比较简单，拿到文件，保存文件唯一性标识，切割文件，分段上传，每次上传一段，根据唯一性标识判断文件上传进度，直到文件的全部片段上传完毕\n\n![](https://static.vue-js.com/465d2920-8ba4-11eb-85f6-6fac77c0c9b3.png)\n\n下面的内容都是伪代码\n\n读取文件内容：\n\n```js\nconst input = document.querySelector('input');\ninput.addEventListener('change', function() {\n    var file = this.files[0];\n});\n```\n\n可以使用`md5`实现文件的唯一性\n\n```js\nconst md5code = md5(file);\n```\n\n然后开始对文件进行分割\n\n```js\nvar reader = new FileReader();\nreader.readAsArrayBuffer(file);\nreader.addEventListener(\"load\", function(e) {\n    //每10M切割一段,这里只做一个切割演示，实际切割需要循环切割，\n    var slice = e.target.result.slice(0, 10*1024*1024);\n});\n```\n\nh5上传一个（一片）\n\n```js\nconst formdata = new FormData();\nformdata.append('0', slice);\n//这里是有一个坑的，部分设备无法获取文件名称，和文件类型，这个在最后给出解决方案\nformdata.append('filename', file.filename);\nvar xhr = new XMLHttpRequest();\nxhr.addEventListener('load', function() {\n    //xhr.responseText\n});\nxhr.open('POST', '');\nxhr.send(formdata);\nxhr.addEventListener('progress', updateProgress);\nxhr.upload.addEventListener('progress', updateProgress);\n\nfunction updateProgress(event) {\n    if (event.lengthComputable) {\n        //进度条\n    }\n}\n```\n\n这里给出常见的图片和视频的文件类型判断\n\n```js\nfunction checkFileType(type, file, back) {\n/**\n* type png jpg mp4 ...\n* file input.change=> this.files[0]\n* back callback(boolean)\n*/\n    var args = arguments;\n    if (args.length != 3) {\n        back(0);\n    }\n    var type = args[0]; // type = '(png|jpg)' , 'png'\n    var file = args[1];\n    var back = typeof args[2] == 'function' ? args[2] : function() {};\n    if (file.type == '') {\n        // 如果系统无法获取文件类型，则读取二进制流，对二进制进行解析文件类型\n        var imgType = [\n            'ff d8 ff', //jpg\n            '89 50 4e', //png\n\n            '0 0 0 14 66 74 79 70 69 73 6F 6D', //mp4\n            '0 0 0 18 66 74 79 70 33 67 70 35', //mp4\n            '0 0 0 0 66 74 79 70 33 67 70 35', //mp4\n            '0 0 0 0 66 74 79 70 4D 53 4E 56', //mp4\n            '0 0 0 0 66 74 79 70 69 73 6F 6D', //mp4\n\n            '0 0 0 18 66 74 79 70 6D 70 34 32', //m4v\n            '0 0 0 0 66 74 79 70 6D 70 34 32', //m4v\n\n            '0 0 0 14 66 74 79 70 71 74 20 20', //mov\n            '0 0 0 0 66 74 79 70 71 74 20 20', //mov\n            '0 0 0 0 6D 6F 6F 76', //mov\n\n            '4F 67 67 53 0 02', //ogg\n            '1A 45 DF A3', //ogg\n\n            '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)\n        ];\n        var typeName = [\n            'jpg',\n            'png',\n            'mp4',\n            'mp4',\n            'mp4',\n            'mp4',\n            'mp4',\n            'm4v',\n            'm4v',\n            'mov',\n            'mov',\n            'mov',\n            'ogg',\n            'ogg',\n            'avi',\n        ];\n        var sliceSize = /png|jpg|jpeg/.test(type) ? 3 : 12;\n        var reader = new FileReader();\n        reader.readAsArrayBuffer(file);\n        reader.addEventListener(\"load\", function(e) {\n            var slice = e.target.result.slice(0, sliceSize);\n            reader = null;\n            if (slice && slice.byteLength == sliceSize) {\n                var view = new Uint8Array(slice);\n                var arr = [];\n                view.forEach(function(v) {\n                    arr.push(v.toString(16));\n                });\n                view = null;\n                var idx = arr.join(' ').indexOf(imgType);\n                if (idx > -1) {\n                    back(typeName[idx]);\n                } else {\n                    arr = arr.map(function(v) {\n                        if (i > 3 && i < 8) {\n                            return 'x';\n                        }\n                        return v;\n                    });\n                    var idx = arr.join(' ').indexOf(imgType);\n                    if (idx > -1) {\n                        back(typeName[idx]);\n                    } else {\n                        back(false);\n                    }\n\n                }\n            } else {\n                back(false);\n            }\n\n        });\n    } else {\n        var type = file.name.match(/\\.(\\w+)$/)[1];\n        back(type);\n    }\n}\n```\n\n调用方法如下\n\n```js\ncheckFileType('(mov|mp4|avi)',file,function(fileType){\n    // fileType = mp4,\n    // 如果file的类型不在枚举之列，则返回false\n});\n```\n\n上面上传文件的一步，可以改成：\n\n```js\nformdata.append('filename', md5code+'.'+fileType);\n```\n\n有了切割上传后，也就有了文件唯一标识信息，断点续传变成了后台的一个小小的逻辑判断\n\n后端主要做的内容为：根据前端传给后台的`md5`值，到服务器磁盘查找是否有之前未完成的文件合并信息（也就是未完成的半成品文件切片），取到之后根据上传切片的数量，返回数据告诉前端开始从第几节上传\n\n如果想要暂停切片的上传，可以使用`XMLHttpRequest `的 `abort `方法\n\n\n## 三、使用场景\n\n- 大文件加速上传：当文件大小超过预期大小时，使用分片上传可实现并行上传多个 Part， 以加快上传速度\n- 网络环境较差：建议使用分片上传。当出现上传失败的时候，仅需重传失败的Part\n- 流式上传：可以在需要上传的文件大小还不确定的情况下开始上传。这种场景在视频监控等行业应用中比较常见\n\n## 小结\n当前的伪代码，只是提供一个简单的思路，想要把事情做到极致，我们还需要考虑到更多场景，比如\n\n- 切片上传失败怎么办\n- 上传过程中刷新页面怎么办\n- 如何进行并行上传\n- 切片什么时候按数量切，什么时候按大小切\n- 如何结合 Web Worker 处理大文件上传\n- 如何实现秒传\n\n人生又何尝不是如此，极致的人生体验有无限可能，越是后面才发现越是精彩 ~_~\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000009448892\n- https://baike.baidu.com/\n"
  },
  {
    "path": "docs/JavaScript/copy.md",
    "content": "# 面试官：深拷贝浅拷贝的区别？如何实现一个深拷贝？\n\n ![](https://static.vue-js.com/cdf952e0-69b8-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、数据类型存储\n\n前面文章我们讲到，`JavaScript`中存在两大数据类型：\n\n- 基本类型\n- 引用类型 \n\n基本类型数据保存在在栈内存中\n\n引用类型数据保存在堆内存中，引用数据类型的变量是一个指向堆内存中实际对象的引用，存在栈中\n\n\n\n## 二、浅拷贝\n\n浅拷贝，指的是创建新的数据，这个数据有着原始数据属性值的一份精确拷贝\n\n如果属性是基本类型，拷贝的就是基本类型的值。如果属性是引用类型，拷贝的就是内存地址\n\n即浅拷贝是拷贝一层，深层次的引用类型则共享内存地址\n\n下面简单实现一个浅拷贝\n\n```js\nfunction shallowClone(obj) {\n    const newObj = {};\n    for(let prop in obj) {\n        if(obj.hasOwnProperty(prop)){\n            newObj[prop] = obj[prop];\n        }\n    }\n    return newObj;\n}\n```\n\n在`JavaScript`中，存在浅拷贝的现象有：\n\n- `Object.assign`\n- `Array.prototype.slice()`, `Array.prototype.concat()`\n- 使用拓展运算符实现的复制\n\n\n\n\n\n### Object.assign\n\n```js\nvar obj = {\n    age: 18,\n    nature: ['smart', 'good'],\n    names: {\n        name1: 'fx',\n        name2: 'xka'\n    },\n    love: function () {\n        console.log('fx is a great girl')\n    }\n}\nvar newObj = Object.assign({}, fxObj);\n```\n\n\n\n### slice()\n\n```js\nconst fxArr = [\"One\", \"Two\", \"Three\"]\nconst fxArrs = fxArr.slice(0)\nfxArrs[1] = \"love\";\nconsole.log(fxArr) // [\"One\", \"Two\", \"Three\"]\nconsole.log(fxArrs) // [\"One\", \"love\", \"Three\"]\n```\n\n\n\n### concat()\n\n```js\nconst fxArr = [\"One\", \"Two\", \"Three\"]\nconst fxArrs = fxArr.concat()\nfxArrs[1] = \"love\";\nconsole.log(fxArr) // [\"One\", \"Two\", \"Three\"]\nconsole.log(fxArrs) // [\"One\", \"love\", \"Three\"]\n```\n\n\n\n\n\n\n\n### 拓展运算符\n\n```js\nconst fxArr = [\"One\", \"Two\", \"Three\"]\nconst fxArrs = [...fxArr]\nfxArrs[1] = \"love\";\nconsole.log(fxArr) // [\"One\", \"Two\", \"Three\"]\nconsole.log(fxArrs) // [\"One\", \"love\", \"Three\"]\n```\n\n\n\n\n\n## 三、深拷贝\n\n深拷贝开辟一个新的栈，两个对象属完成相同，但是对应两个不同的地址，修改一个对象的属性，不会改变另一个对象的属性\n\n常见的深拷贝方式有：\n\n- _.cloneDeep()\n\n- jQuery.extend()\n- JSON.stringify()\n- 手写循环递归\n\n\n\n### _.cloneDeep()\n\n```js\nconst _ = require('lodash');\nconst obj1 = {\n    a: 1,\n    b: { f: { g: 1 } },\n    c: [1, 2, 3]\n};\nconst obj2 = _.cloneDeep(obj1);\nconsole.log(obj1.b.f === obj2.b.f);// false\n```\n\n\n\n### jQuery.extend()\n\n```js\nconst $ = require('jquery');\nconst obj1 = {\n    a: 1,\n    b: { f: { g: 1 } },\n    c: [1, 2, 3]\n};\nconst obj2 = $.extend(true, {}, obj1);\nconsole.log(obj1.b.f === obj2.b.f); // false\n```\n\n\n\n\n\n### JSON.stringify()\n\n```js\nconst obj2=JSON.parse(JSON.stringify(obj1));\n```\n\n但是这种方式存在弊端，会忽略`undefined`、`symbol`和`函数`\n\n```js\nconst obj = {\n    name: 'A',\n    name1: undefined,\n    name3: function() {},\n    name4:  Symbol('A')\n}\nconst obj2 = JSON.parse(JSON.stringify(obj));\nconsole.log(obj2); // {name: \"A\"}\n```\n\n\n\n### 循环递归\n\n```js\nfunction deepClone(obj, hash = new WeakMap()) {\n  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作\n  if (obj instanceof Date) return new Date(obj);\n  if (obj instanceof RegExp) return new RegExp(obj);\n  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝\n  if (typeof obj !== \"object\") return obj;\n  // 是对象的话就要进行深拷贝\n  if (hash.get(obj)) return hash.get(obj);\n  let cloneObj = new obj.constructor();\n  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身\n  hash.set(obj, cloneObj);\n  for (let key in obj) {\n    if (obj.hasOwnProperty(key)) {\n      // 实现一个递归拷贝\n      cloneObj[key] = deepClone(obj[key], hash);\n    }\n  }\n  return cloneObj;\n}\n```\n\n\n\n\n\n\n\n## 四、区别\n\n下面首先借助两张图，可以更加清晰看到浅拷贝与深拷贝的区别\n\n ![](https://static.vue-js.com/d9862c00-69b8-11eb-ab90-d9ae814b240d.png)\n\n从上图发现，浅拷贝和深拷贝都创建出一个新的对象，但在复制对象属性的时候，行为就不一样\n\n浅拷贝只复制属性指向某个对象的指针，而不复制对象本身，新旧对象还是共享同一块内存，修改对象属性会影响原对象\n\n```js\n// 浅拷贝\nconst obj1 = {\n    name : 'init',\n    arr : [1,[2,3],4],\n};\nconst obj3=shallowClone(obj1) // 一个浅拷贝方法\nobj3.name = \"update\";\nobj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存\n\nconsole.log('obj1',obj1) // obj1 { name: 'init',  arr: [ 1, [ 5, 6, 7 ], 4 ] }\nconsole.log('obj3',obj3) // obj3 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }\n```\n\n但深拷贝会另外创造一个一模一样的对象，新对象跟原对象不共享内存，修改新对象不会改到原对象\n\n```js\n// 深拷贝\nconst obj1 = {\n    name : 'init',\n    arr : [1,[2,3],4],\n};\nconst obj4=deepClone(obj1) // 一个深拷贝方法\nobj4.name = \"update\";\nobj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存\n\nconsole.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 2, 3 ], 4 ] }\nconsole.log('obj4',obj4) // obj4 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }\n```\n\n\n\n### 小结\n\n前提为拷贝类型为引用类型的情况下：\n\n- 浅拷贝是拷贝一层，属性为对象时，浅拷贝是复制，两个对象指向同一个地址\n\n- 深拷贝是递归拷贝深层次，属性为对象时，深拷贝是新开栈，两个对象指向不同的地址"
  },
  {
    "path": "docs/JavaScript/data_type.md",
    "content": "# 面试官：说说JavaScript中的数据类型？存储上的差别？\n\n ![](https://static.vue-js.com/6d133f90-6463-11eb-ab90-d9ae814b240d.png)\n\n## 前言\n\n在`JavaScript`中，我们可以分成两种类型：\n\n- 基本类型\n- 复杂类型\n\n两种类型的区别是：存储位置不同\n\n\n\n## 一、基本类型\n\n基本类型主要为以下6种：\n\n- Number\n- String\n- Boolean\n- Undefined\n- null\n- symbol\n\n\n\n### Number\n\n数值最常见的整数类型格式则为十进制，还可以设置八进制（零开头）、十六进制（0x开头）\n\n```js\nlet intNum = 55 // 10进制的55\nlet num1 = 070 // 8进制的56\nlet hexNum1 = 0xA //16进制的10\n```\n\n浮点类型则在数值汇总必须包含小数点，还可通过科学计数法表示\n\n```js\nlet floatNum1 = 1.1;\nlet floatNum2 = 0.1;\nlet floatNum3 = .1; // 有效，但不推荐\nlet floatNum = 3.125e7; // 等于 31250000\n```\n\n在数值类型中，存在一个特殊数值`NaN`，意为“不是数值”，用于表示本来要返回数值的操作失败了（而不是抛出错误）\n\n```js\nconsole.log(0/0); // NaN\nconsole.log(-0/+0); // NaN\n```\n\n\n\n### Undefined\n\n`Undefined` 类型只有一个值，就是特殊值 `undefined`。当使用 `var `或 `let `声明了变量但没有初始化时，就相当于给变量赋予了 `undefined `值\n\n```js\nlet message;\nconsole.log(message == undefined); // true\n```\n\n包含` undefined` 值的变量跟未定义变量是有区别的\n\n```js\nlet message; // 这个变量被声明了，只是值为 undefined\n\nconsole.log(message); // \"undefined\"\nconsole.log(age); // 没有声明过这个变量，报错\n```\n\n\n\n### String\n\n字符串可以使用双引号（\"）、单引号（'）或反引号（`）标示\n\n```js\nlet firstName = \"John\";\nlet lastName = 'Jacob';\nlet lastName = `Jingleheimerschmidt`\n```\n\n字符串是不可变的，意思是一旦创建，它们的值就不能变了\n\n```js\nlet lang = \"Java\";\nlang = lang + \"Script\";  // 先销毁再创建\n```\n\n\n\n### Null\n\n`Null `类型同样只有一个值，即特殊值 `null`\n\n逻辑上讲， null 值表示一个空对象指针，这也是给`typeof `传一个 `null` 会返回 `\"object\"` 的原因\n\n```js\nlet car = null;\nconsole.log(typeof car); // \"object\"\n```\n\n`undefined` 值是由 `null `值派生而来\n\n```js\nconsole.log(null == undefined); // true\n```\n\n只要变量要保存对象，而当时又没有那个对象可保存，就可用 `null `来填充该变量\n\n\n\n### Boolean\n\n`Boolean `（布尔值）类型有两个字面值： `true` 和` false`\n\n通过`Boolean`可以将其他类型的数据转化成布尔值\n\n规则如下：\n\n```js\n数据类型      \t\t\t\t转换为 true 的值      \t\t\t\t转换为 false 的值\n String        \t\t\t\t 非空字符串          \t\t\t\t\t\"\" \n Number \t\t\t\t非零数值（包括无穷值）\t\t\t\t\t\t0 、 NaN \n Object \t\t\t\t\t 任意对象 \t\t\t\t\t\t\t   null\nUndefined \t\t\t\t\tN/A （不存在） \t\t\t\t\t\tundefined\n```\n\n\n\n### Symbol\n\nSymbol （符号）是原始值，且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符，不会发生属性冲突的危险\n\n```js\nlet genericSymbol = Symbol();\nlet otherGenericSymbol = Symbol();\nconsole.log(genericSymbol == otherGenericSymbol); // false\n\nlet fooSymbol = Symbol('foo');\nlet otherFooSymbol = Symbol('foo');\nconsole.log(fooSymbol == otherFooSymbol); // false\n```\n\n\n\n\n\n## 二、引用类型\n\n复杂类型统称为`Object`，我们这里主要讲述下面三种：\n\n- Object\n- Array\n- Function\n\n\n\n### Object\n\n创建`object`常用方式为对象字面量表示法，属性名可以是字符串或数值\n\n```js\nlet person = {\n    name: \"Nicholas\",\n    \"age\": 29,\n    5: true\n};\n```\n\n\n\n### Array\n\n`JavaScript`数组是一组有序的数据，但跟其他语言不同的是，数组中每个槽位可以存储任意类型的数据。并且，数组也是动态大小的，会随着数据添加而自动增长\n\n```js\nlet colors = [\"red\", 2, {age: 20 }]\ncolors.push(2)\n```\n\n\n\n### Function \n\n函数实际上是对象，每个函数都是 `Function`类型的实例，而 `Function `也有属性和方法，跟其他引用类型一样\n\n函数存在三种常见的表达方式：\n\n- 函数声明\n\n```js\n// 函数声明\nfunction sum (num1, num2) {\n    return num1 + num2;\n}\n```\n\n- 函数表达式\n\n```js\nlet sum = function(num1, num2) {\n    return num1 + num2;\n};\n```\n\n- 箭头函数\n\n函数声明和函数表达式两种方式\n\n```js\nlet sum = (num1, num2) => {\n    return num1 + num2;\n};\n```\n\n\n\n### 其他引用类型\n\n除了上述说的三种之外，还包括`Date`、`RegExp`、`Map`、`Set`等......\n\n\n\n## 三、存储区别\n\n基本数据类型和引用数据类型存储在内存中的位置不同：\n\n- 基本数据类型存储在栈中\n\n- 引用类型的对象存储于堆中\n\n当我们把变量赋值给一个变量时，解析器首先要确认的就是这个值是基本类型值还是引用类型值\n\n下面来举个例子\n\n### 基本类型\n\n```js\nlet a = 10;\nlet b = a; // 赋值操作\nb = 20;\nconsole.log(a); // 10值\n```\n\n`a`的值为一个基本类型，是存储在栈中，将`a`的值赋给`b`，虽然两个变量的值相等，但是两个变量保存了两个不同的内存地址\n\n下图演示了基本类型赋值的过程：\n\n ![](https://static.vue-js.com/906ffb90-6463-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### 引用类型\n\n```js\nvar obj1 = {}\nvar obj2 = obj1;\nobj2.name = \"Xxx\";\nconsole.log(obj1.name); // xxx\n```\n\n引用类型数据存放在堆中，每个堆内存对象都有对应的引用地址指向它，引用地址存放在栈中。\n\n`obj1`是一个引用类型，在赋值操作过程汇总，实际是将堆内存对象在栈内存的引用地址复制了一份给了`obj2`，实际上他们共同指向了同一个堆内存对象，所以更改`obj2`会对`obj1`产生影响\n\n下图演示这个引用类型赋值过程\n\n ![](https://static.vue-js.com/a34bdd10-6463-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 小结\n\n- 声明变量时不同的内存地址分配：\n  - 简单类型的值存放在栈中，在栈中存放的是对应的值\n  - 引用类型对应的值存储在堆中，在栈中存放的是指向堆内存的地址\n- 不同的类型数据导致赋值变量时的不同：\n  - 简单类型赋值，是生成相同的值，两个对象对应不同的地址\n  - 复杂类型赋值，是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象\n"
  },
  {
    "path": "docs/JavaScript/debounce_throttle.md",
    "content": "# 面试官：什么是防抖和节流？有什么区别？如何实现？\n\n ![](https://static.vue-js.com/912f1a10-8787-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n本质上是优化高频率执行代码的一种手段\n\n如：浏览器的 `resize`、`scroll`、`keypress`、`mousemove` 等事件在触发时，会不断地调用绑定在事件上的回调函数，极大地浪费资源，降低前端性能\n\n为了优化体验，需要对这类事件进行调用次数的限制，对此我们就可以采用 **防抖（debounce）** 和 **节流（throttle）** 的方式来减少调用频率\n\n#### 定义\n- 节流: n 秒内只运行一次，若在 n 秒内重复触发，只有一次生效\n- 防抖: n 秒后在执行该事件，若在 n 秒内被重复触发，则重新计时\n\n一个经典的比喻:\n\n想象每天上班大厦底下的电梯。把电梯完成一次运送，类比为一次函数的执行和响应\n\n假设电梯有两种运行策略 `debounce` 和 `throttle`，超时设定为15秒，不考虑容量限制\n\n电梯第一个人进来后，15秒后准时运送一次，这是节流\n\n电梯第一个人进来后，等待15秒。如果过程中又有人进来，15秒等待重新计时，直到15秒后开始运送，这是防抖\n\n## 代码实现\n\n### 节流\n\n完成节流可以使用时间戳与定时器的写法\n\n使用时间戳写法，事件会立即执行，停止触发后没有办法再次执行\n\n```js\nfunction throttled1(fn, delay = 500) {\n    let oldtime = Date.now()\n    return function (...args) {\n        let newtime = Date.now()\n        if (newtime - oldtime >= delay) {\n            fn.apply(null, args)\n            oldtime = Date.now()\n        }\n    }\n}\n\n```\n\n使用定时器写法，`delay`毫秒后第一次执行，第二次事件停止触发后依然会再一次执行\n\n```js\nfunction throttled2(fn, delay = 500) {\n    let timer = null\n    return function (...args) {\n        if (!timer) {\n            timer = setTimeout(() => {\n                fn.apply(this, args)\n                timer = null\n            }, delay);\n        }\n    }\n}\n```\n\n可以将时间戳写法的特性与定时器写法的特性相结合，实现一个更加精确的节流。实现如下\n\n```js\nfunction throttled(fn, delay) {\n    let timer = null\n    let starttime = Date.now()\n    return function () {\n        let curTime = Date.now() // 当前时间\n        let remaining = delay - (curTime - starttime)  // 从上一次到现在，还剩下多少多余时间\n        let context = this\n        let args = arguments\n        clearTimeout(timer)\n        if (remaining <= 0) {\n            fn.apply(context, args)\n            starttime = Date.now()\n        } else {\n            timer = setTimeout(fn, remaining);\n        }\n    }\n}\n```\n\n### 防抖\n\n简单版本的实现\n\n```js\nfunction debounce(func, wait) {\n    let timeout;\n\n    return function () {\n        let context = this; // 保存this指向\n        let args = arguments; // 拿到event对象\n\n        clearTimeout(timeout)\n        timeout = setTimeout(function(){\n            func.apply(context, args)\n        }, wait);\n    }\n}\n```\n\n防抖如果需要立即执行，可加入第三个参数用于判断，实现如下：\n\n```js\nfunction debounce(func, wait, immediate) {\n\n    let timeout;\n\n    return function () {\n        let context = this;\n        let args = arguments;\n\n        if (timeout) clearTimeout(timeout); // timeout 不为null\n        if (immediate) {\n            let callNow = !timeout; // 第一次会立即执行，以后只有事件执行后才会再次触发\n            timeout = setTimeout(function () {\n                timeout = null;\n            }, wait)\n            if (callNow) {\n                func.apply(context, args)\n            }\n        }\n        else {\n            timeout = setTimeout(function () {\n                func.apply(context, args)\n            }, wait);\n        }\n    }\n}\n```\n\n## 二、区别\n\n相同点：\n\n- 都可以通过使用 `setTimeout` 实现\n- 目的都是，降低回调执行频率。节省计算资源\n\n不同点：\n\n- 函数防抖，在一段连续操作结束后，处理回调，利用`clearTimeout `和 `setTimeout`实现。函数节流，在一段连续操作中，每一段时间只执行一次，频率较高的事件中使用来提高性能\n- 函数防抖关注一定时间连续触发的事件，只在最后执行一次，而函数节流一段时间内只执行一次\n\n例如，都设置时间频率为500ms，在2秒时间内，频繁触发函数，节流，每隔 500ms 就执行一次。防抖，则不管调动多少次方法，在2s后，只会执行一次\n\n如下图所示：\n\n ![](https://static.vue-js.com/a2c81b50-8787-11eb-ab90-d9ae814b240d.png)\n\n\n## 三、应用场景\n\n防抖在连续的事件，只需触发一次回调的场景有：\n\n- 搜索框搜索输入。只需用户最后一次输入完，再发送请求\n- 手机号、邮箱验证输入检测\n- 窗口大小`resize`。只需窗口调整完成后，计算窗口大小。防止重复渲染。\n\n节流在间隔一段时间执行一次回调的场景有：\n\n- 滚动加载，加载更多或滚到底部监听\n- 搜索框，搜索联想功能"
  },
  {
    "path": "docs/JavaScript/event_Model.md",
    "content": "# 面试官：说说JavaScript中的事件模型\n\n![](https://static.vue-js.com/32a182f0-74cf-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、事件与事件流\n\n`javascript`中的事件，可以理解就是在`HTML`文档或者浏览器中发生的一种交互操作，使得网页具备互动性， 常见的有加载事件、鼠标事件、自定义事件等\n\n由于`DOM`是一个树结构，如果在父子节点绑定事件时候，当触发子节点的时候，就存在一个顺序问题，这就涉及到了事件流的概念\n\n事件流都会经历三个阶段：\n\n- 事件捕获阶段(capture phase)\n- 处于目标阶段(target phase)\n- 事件冒泡阶段(bubbling phase)\n\n ![](https://static.vue-js.com/3e9a6450-74cf-11eb-85f6-6fac77c0c9b3.png)\n\n事件冒泡是一种从下往上的传播方式，由最具体的元素（触发节点）然后逐渐向上传播到最不具体的那个节点，也就是`DOM`中最高层的父节点\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\">\n        <title>Event Bubbling</title>\n    </head>\n    <body>\n        <button id=\"clickMe\">Click Me</button>\n    </body>\n</html>\n```\n\n然后，我们给`button`和它的父元素，加入点击事件\n\n```js\nvar button = document.getElementById('clickMe');\n\nbutton.onclick = function() {\n  console.log('1.Button');\n};\ndocument.body.onclick = function() {\n  console.log('2.body');\n};\ndocument.onclick = function() {\n  console.log('3.document');\n};\nwindow.onclick = function() {\n  console.log('4.window');\n};\n```\n\n点击按钮，输出如下\n\n```js\n1.button\n2.body\n3.document\n4.window\n```\n\n点击事件首先在`button`元素上发生，然后逐级向上传播\n\n事件捕获与事件冒泡相反，事件最开始由不太具体的节点最早接受事件, 而最具体的节点（触发节点）最后接受事件\n\n\n\n## 二、事件模型\n\n事件模型可以分为三种：\n\n- 原始事件模型（DOM0级）\n- 标准事件模型（DOM2级）\n- IE事件模型（基本不用）\n\n\n\n### 原始事件模型\n\n事件绑定监听函数比较简单, 有两种方式：\n\n- HTML代码中直接绑定\n\n```js\n<input type=\"button\" onclick=\"fun()\">\n```\n\n- 通过`JS`代码绑定\n\n```js\nvar btn = document.getElementById('.btn');\nbtn.onclick = fun;\n```\n\n#### 特性\n\n- 绑定速度快\n\n`DOM0`级事件具有很好的跨浏览器优势，会以最快的速度绑定，但由于绑定速度太快，可能页面还未完全加载出来，以至于事件可能无法正常运行\n\n- 只支持冒泡，不支持捕获\n\n- 同一个类型的事件只能绑定一次\n\n```js\n<input type=\"button\" id=\"btn\" onclick=\"fun1()\">\n\nvar btn = document.getElementById('.btn');\nbtn.onclick = fun2;\n```\n\n如上，当希望为同一个元素绑定多个同类型事件的时候（上面的这个`btn`元素绑定2个点击事件），是不被允许的，后绑定的事件会覆盖之前的事件\n\n删除 `DOM0` 级事件处理程序只要将对应事件属性置为`null`即可\n\n```js\nbtn.onclick = null;\n```\n\n\n\n\n\n### 标准事件模型\n\n在该事件模型中，一次事件共有三个过程:\n\n- 事件捕获阶段：事件从`document`一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数，如果有则执行\n- 事件处理阶段：事件到达目标元素, 触发目标元素的监听函数\n- 事件冒泡阶段：事件从目标元素冒泡到`document`, 依次检查经过的节点是否绑定了事件监听函数，如果有则执行\n\n事件绑定监听函数的方式如下:\n\n```\naddEventListener(eventType, handler, useCapture)\n```\n\n事件移除监听函数的方式如下:\n\n```\nremoveEventListener(eventType, handler, useCapture)\n```\n\n参数如下：\n\n- `eventType`指定事件类型(不要加on)\n- `handler`是事件处理函数\n- `useCapture`是一个`boolean`用于指定是否在捕获阶段进行处理，一般设置为`false`与IE浏览器保持一致\n\n举个例子：\n\n```js\nvar btn = document.getElementById('.btn');\nbtn.addEventListener(‘click’, showMessage, false);\nbtn.removeEventListener(‘click’, showMessage, false);\n```\n\n#### 特性\n\n- 可以在一个`DOM`元素上绑定多个事件处理器，各自并不会冲突\n\n```js\nbtn.addEventListener(‘click’, showMessage1, false);\nbtn.addEventListener(‘click’, showMessage2, false);\nbtn.addEventListener(‘click’, showMessage3, false);\n```\n\n- 执行时机\n\n当第三个参数(`useCapture`)设置为`true`就在捕获过程中执行，反之在冒泡过程中执行处理函数\n\n下面举个例子：\n\n```js\n<div id='div'>\n    <p id='p'>\n        <span id='span'>Click Me!</span>\n    </p >\n</div>\n```\n\n设置点击事件\n\n```js\nvar div = document.getElementById('div');\nvar p = document.getElementById('p');\n\nfunction onClickFn (event) {\n    var tagName = event.currentTarget.tagName;\n    var phase = event.eventPhase;\n    console.log(tagName, phase);\n}\n\ndiv.addEventListener('click', onClickFn, false);\np.addEventListener('click', onClickFn, false);\n```\n\n上述使用了`eventPhase`，返回一个代表当前执行阶段的整数值。1为捕获阶段、2为事件对象触发阶段、3为冒泡阶段\n\n点击`Click Me!`，输出如下\n\n```js\nP 3\nDIV 3\n```\n\n可以看到，`p`和`div`都是在冒泡阶段响应了事件，由于冒泡的特性，裹在里层的`p`率先做出响应\n\n如果把第三个参数都改为`true`\n\n```js\ndiv.addEventListener('click', onClickFn, true);\np.addEventListener('click', onClickFn, true);\n```\n\n输出如下\n\n```js\nDIV 1\nP 1\n```\n\n两者都是在捕获阶段响应事件，所以`div`比`p`标签先做出响应\n\n\n\n### IE事件模型\n\nIE事件模型共有两个过程:\n\n- 事件处理阶段：事件到达目标元素, 触发目标元素的监听函数。\n- 事件冒泡阶段：事件从目标元素冒泡到`document`, 依次检查经过的节点是否绑定了事件监听函数，如果有则执行\n\n事件绑定监听函数的方式如下:\n\n```\nattachEvent(eventType, handler)\n```\n\n事件移除监听函数的方式如下:\n\n```\ndetachEvent(eventType, handler)\n```\n\n举个例子：\n\n```js\nvar btn = document.getElementById('.btn');\nbtn.attachEvent(‘onclick’, showMessage);\nbtn.detachEvent(‘onclick’, showMessage);\n```"
  },
  {
    "path": "docs/JavaScript/event_agent.md",
    "content": "# 面试官：解释下什么是事件代理？应用场景？\n\n![](https://static.vue-js.com/a33f0ab0-797e-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n事件代理，俗地来讲，就是把一个元素响应事件（`click`、`keydown`......）的函数委托到另一个元素\n\n前面讲到，事件流的都会经过三个阶段： 捕获阶段 -> 目标阶段 -> 冒泡阶段，而事件委托就是在冒泡阶段完成\n\n事件委托，会把一个或者一组元素的事件委托到它的父层或者更外层元素上，真正绑定事件的是外层元素，而不是目标元素\n\n当事件响应到目标元素上时，会通过事件冒泡机制从而触发它的外层元素的绑定事件上，然后在外层元素上去执行函数\n\n下面举个例子：\n\n比如一个宿舍的同学同时快递到了，一种笨方法就是他们一个个去领取\n\n较优方法就是把这件事情委托给宿舍长，让一个人出去拿好所有快递，然后再根据收件人一一分发给每个同学\n\n在这里，取快递就是一个事件，每个同学指的是需要响应事件的 `DOM `元素，而出去统一领取快递的宿舍长就是代理的元素\n\n所以真正绑定事件的是这个元素，按照收件人分发快递的过程就是在事件执行中，需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个\n\n\n\n## 二、应用场景\n\n如果我们有一个列表，列表之中有大量的列表项，我们需要在点击列表项的时候响应一个事件\n\n```js\n<ul id=\"list\">\n  <li>item 1</li>\n  <li>item 2</li>\n  <li>item 3</li>\n  ......\n  <li>item n</li>\n</ul>\n```\n\n如果给每个列表项一一都绑定一个函数，那对于内存消耗是非常大的\n\n```js\n// 获取目标元素\nconst lis = document.getElementsByTagName(\"li\")\n// 循环遍历绑定事件\nfor (let i = 0; i < lis.length; i++) {\n    lis[i].onclick = function(e){\n        console.log(e.target.innerHTML)\n    }\n}\n```\n\n这时候就可以事件委托，把点击事件绑定在父级元素`ul`上面，然后执行事件的时候再去匹配目标元素\n\n```js\n// 给父层元素绑定事件\ndocument.getElementById('list').addEventListener('click', function (e) {\n    // 兼容性处理\n    var event = e || window.event;\n    var target = event.target || event.srcElement;\n    // 判断是否匹配目标元素\n    if (target.nodeName.toLocaleLowerCase === 'li') {\n        console.log('the content is: ', target.innerHTML);\n    }\n});\n```\n\n还有一种场景是上述列表项并不多，我们给每个列表项都绑定了事件\n\n但是如果用户能够随时动态的增加或者去除列表项元素，那么在每一次改变的时候都需要重新给新增的元素绑定事件，给即将删去的元素解绑事件\n\n如果用了事件委托就没有这种麻烦了，因为事件是绑定在父层的，和目标元素的增减是没有关系的，执行到目标元素是在真正响应执行事件函数的过程中去匹配的\n\n举个例子：\n\n下面`html`结构中，点击`input`可以动态添加元素\n\n```html\n<input type=\"button\" name=\"\" id=\"btn\" value=\"添加\" />\n<ul id=\"ul1\">\n    <li>item 1</li>\n    <li>item 2</li>\n    <li>item 3</li>\n    <li>item 4</li>\n</ul>\n```\n\n使用事件委托\n\n```js\nconst oBtn = document.getElementById(\"btn\");\nconst oUl = document.getElementById(\"ul1\");\nconst num = 4;\n\n//事件委托，添加的子元素也有事件\noUl.onclick = function (ev) {\n    ev = ev || window.event;\n    const target = ev.target || ev.srcElement;\n    if (target.nodeName.toLowerCase() == 'li') {\n        console.log('the content is: ', target.innerHTML);\n    }\n\n};\n\n//添加新节点\noBtn.onclick = function () {\n    num++;\n    const oLi = document.createElement('li');\n    oLi.innerHTML = `item ${num}`;\n    oUl.appendChild(oLi);\n};\n```\n\n可以看到，使用事件委托，在动态绑定事件的情况下是可以减少很多重复工作的\n\n\n\n## 三、总结\n\n适合事件委托的事件有：`click`，`mousedown`，`mouseup`，`keydown`，`keyup`，`keypress`\n\n从上面应用场景中，我们就可以看到使用事件委托存在两大优点：\n\n- 减少整个页面所需的内存，提升整体性能\n- 动态绑定，减少重复工作\n\n但是使用事件委托也是存在局限性：\n\n- `focus`、`blur `这些事件没有事件冒泡机制，所以无法进行委托绑定事件\n\n- `mousemove`、`mouseout `这样的事件，虽然有事件冒泡，但是只能不断通过位置去计算定位，对性能消耗高，因此也是不适合于事件委托的\n\n如果把所有事件都用事件代理，可能会出现事件误判，即本不该被触发的事件被绑定上了事件"
  },
  {
    "path": "docs/JavaScript/event_loop.md",
    "content": "# 面试官：说说你对事件循环的理解\n\n ![](https://static.vue-js.com/50f062d0-7cb8-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n首先，`JavaScript `是一门单线程的语言，意味着同一时间内只能做一件事，但是这并不意味着单线程就是阻塞，而实现单线程非阻塞的方法就是事件循环\n\n在`JavaScript`中，所有的任务都可以分为\n\n- 同步任务：立即执行的任务，同步任务一般会直接进入到主线程中执行\n\n- 异步任务：异步执行的任务，比如`ajax`网络请求，`setTimeout `定时函数等\n\n同步任务与异步任务的运行流程图如下：\n\n ![](https://static.vue-js.com/61efbc20-7cb8-11eb-85f6-6fac77c0c9b3.png)\n\n从上面我们可以看到，同步任务进入主线程，即主执行栈，异步任务进入任务队列，主线程内的任务执行完毕为空，会去任务队列读取对应的任务，推入主线程执行。上述过程的不断重复就事件循环\n\n\n\n## 二、宏任务与微任务\n\n如果将任务划分为同步任务和异步任务并不是那么的准确，举个例子：\n\n```js\nconsole.log(1)\n\nsetTimeout(()=>{\n    console.log(2)\n}, 0)\n\nnew Promise((resolve, reject)=>{\n    console.log('new Promise')\n    resolve()\n}).then(()=>{\n    console.log('then')\n})\n\nconsole.log(3)\n```\n\n如果按照上面流程图来分析代码，我们会得到下面的执行步骤：\n\n- `console.log(1) `，同步任务，主线程中执行\n- `setTimeout()` ，异步任务，放到 `Event Table`，0 毫秒后`console.log(2) `回调推入 `Event Queue` 中\n- `new Promise` ，同步任务，主线程直接执行\n- `.then` ，异步任务，放到 `Event Table`\n- `console.log(3)`，同步任务，主线程执行\n\n所以按照分析，它的结果应该是 `1` => `'new Promise'` => `3` => `2` => `'then'`\n\n但是实际结果是：`1`=>`'new Promise'`=> `3` => `'then'` => `2`\n\n出现分歧的原因在于异步任务执行顺序，事件队列其实是一个“先进先出”的数据结构，排在前面的事件会优先被主线程读取\n\n例子中 `setTimeout`回调事件是先进入队列中的，按理说应该先于 `.then` 中的执行，但是结果却偏偏相反\n\n原因在于异步任务还可以细分为微任务与宏任务\n\n### 微任务\n\n一个需要异步执行的函数，执行时机是在主函数执行结束之后、当前宏任务结束之前\n\n常见的微任务有：\n\n- Promise.then\n\n- MutaionObserver\n\n- Object.observe（已废弃；Proxy 对象替代）\n\n- process.nextTick（Node.js）\n\n  \n\n### 宏任务\n\n宏任务的时间粒度比较大，执行的时间间隔是不能精确控制的，对一些高实时性的需求就不太符合\n\n常见的宏任务有：\n\n- script (可以理解为外层同步代码) \n- setTimeout/setInterval \n- UI rendering/UI事件 \n- postMessage、MessageChannel \n- setImmediate、I/O（Node.js）\n\n\n\n这时候，事件循环，宏任务，微任务的关系如图所示\n\n ![](https://static.vue-js.com/6e80e5e0-7cb8-11eb-85f6-6fac77c0c9b3.png)\n\n按照这个流程，它的执行机制是：\n\n- 执行一个宏任务，如果遇到微任务就将它放到微任务的事件队列中\n- 当前宏任务执行完成后，会查看微任务的事件队列，然后将里面的所有微任务依次执行完\n\n\n\n回到上面的题目\n\n```js\nconsole.log(1)\nsetTimeout(()=>{\n    console.log(2)\n}, 0)\nnew Promise((resolve, reject)=>{\n    console.log('new Promise')\n    resolve()\n}).then(()=>{\n    console.log('then')\n})\nconsole.log(3)\n```\n\n流程如下\n\n```js\n// 遇到 console.log(1) ，直接打印 1\n// 遇到定时器，属于新的宏任务，留着后面执行\n// 遇到 new Promise，这个是直接执行的，打印 'new Promise'\n// .then 属于微任务，放入微任务队列，后面再执行\n// 遇到 console.log(3) 直接打印 3\n// 好了本轮宏任务执行完毕，现在去微任务列表查看是否有微任务，发现 .then 的回调，执行它，打印 'then'\n// 当一次宏任务执行完，再去执行新的宏任务，这里就剩一个定时器的宏任务了，执行它，打印 2\n```\n\n\n\n## 三、async与await\n\n`async` 是异步的意思，`await `则可以理解为 `async wait`。所以可以理解` async `就是用来声明一个异步方法，而 `await `是用来等待异步方法执行\n\n### async\n\n`async`函数返回一个`promise`对象，下面两种方法是等效的\n\n```js\nfunction f() {\n    return Promise.resolve('TEST');\n}\n\n// asyncF is equivalent to f!\nasync function asyncF() {\n    return 'TEST';\n}\n```\n\n### await\n\n正常情况下，`await`命令后面是一个 `Promise `对象，返回该对象的结果。如果不是 `Promise `对象，就直接返回对应的值\n\n```js\nasync function f(){\n    // 等同于\n    // return 123\n    return await 123\n}\nf().then(v => console.log(v)) // 123\n```\n\n不管`await`后面跟着的是什么，`await`都会阻塞后面的代码\n\n```js\nasync function fn1 (){\n    console.log(1)\n    await fn2()\n    console.log(2) // 阻塞\n}\n\nasync function fn2 (){\n    console.log('fn2')\n}\n\nfn1()\nconsole.log(3)\n```\n\n上面的例子中，`await` 会阻塞下面的代码（即加入微任务队列），先执行 `async `外面的同步代码，同步代码执行完，再回到 `async` 函数中，再执行之前阻塞的代码\n\n所以上述输出结果为：`1`，`fn2`，`3`，`2`\n\n\n\n## 四、流程分析\n\n通过对上面的了解，我们对`JavaScript`对各种场景的执行顺序有了大致的了解\n\n这里直接上代码：\n\n```js\nasync function async1() {\n    console.log('async1 start')\n    await async2()\n    console.log('async1 end')\n}\nasync function async2() {\n    console.log('async2')\n}\nconsole.log('script start')\nsetTimeout(function () {\n    console.log('settimeout')\n})\nasync1()\nnew Promise(function (resolve) {\n    console.log('promise1')\n    resolve()\n}).then(function () {\n    console.log('promise2')\n})\nconsole.log('script end')\n```\n\n分析过程：\n\n1. 执行整段代码，遇到 `console.log('script start')` 直接打印结果，输出 `script start`\n2. 遇到定时器了，它是宏任务，先放着不执行\n3. 遇到 `async1()`，执行 `async1` 函数，先打印 `async1 start`，下面遇到` await `怎么办？先执行 `async2`，打印 `async2`，然后阻塞下面代码（即加入微任务列表），跳出去执行同步代码\n4. 跳到 `new Promise` 这里，直接执行，打印 `promise1`，下面遇到 `.then()`，它是微任务，放到微任务列表等待执行\n5. 最后一行直接打印 `script end`，现在同步代码执行完了，开始执行微任务，即 `await `下面的代码，打印 `async1 end`\n6. 继续执行下一个微任务，即执行 `then` 的回调，打印 `promise2`\n7. 上一个宏任务所有事都做完了，开始下一个宏任务，就是定时器，打印 `settimeout`\n\n所以最后的结果是：`script start`、`async1 start`、`async2`、`promise1`、`script end`、`async1 end`、`promise2`、`settimeout`\n\n"
  },
  {
    "path": "docs/JavaScript/function_cache.md",
    "content": "# 面试官：Javascript中如何实现函数缓存？函数缓存有哪些应用场景？\n\n ![](https://static.vue-js.com/2ae9dda0-85fa-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n函数缓存，就是将函数运算过的结果进行缓存\n\n本质上就是用空间（缓存存储）换时间（计算过程）\n\n常用于缓存数据计算结果和缓存对象\n\n```js\nconst add = (a,b) => a+b;\nconst calc = memoize(add); // 函数缓存\ncalc(10,20);// 30\ncalc(10,20);// 30 缓存\n```\n\n缓存只是一个临时的数据存储，它保存数据，以便将来对该数据的请求能够更快地得到处理\n\n\n\n## 二、如何实现\n\n实现函数缓存主要依靠闭包、柯里化、高阶函数，这里再简单复习下：\n\n### 闭包\n\n闭包可以理解成，函数 + 函数体内可访问的变量总和\n\n```js\n(function() {\n    var a = 1;\n    function add() {\n        const b = 2\n        let sum = b + a\n        console.log(sum); // 3\n    }\n    add()\n})()\n```\n\n`add `函数本身，以及其内部可访问的变量，即 `a = 1 `，这两个组合在⼀起就形成了闭包\n\n\n\n### 柯里化\n\n把接受多个参数的函数转换成接受一个单一参数的函数\n\n```js\n// 非函数柯里化\nvar add = function (x,y) {\n    return x+y;\n}\nadd(3,4) //7\n\n// 函数柯里化\nvar add2 = function (x) {\n    //**返回函数**\n    return function (y) {\n        return x+y;\n    }\n}\nadd2(3)(4) //7\n```\n\n将一个二元函数拆分成两个一元函数\n\n\n\n### 高阶函数\n\n通过接收其他函数作为参数或返回其他函数的函数\n\n```js\nfunction foo(){\n  var a = 2;\n\n  function bar() {\n    console.log(a);\n  }\n  return bar;\n}\nvar baz = foo();\nbaz();//2\n```\n\n函数 `foo` 如何返回另一个函数 `bar`，`baz` 现在持有对 `foo` 中定义的`bar` 函数的引用。由于闭包特性，`a`的值能够得到\n\n\n\n下面再看看如何实现函数缓存，实现原理也很简单，把参数和对应的结果数据存在一个对象中，调用时判断参数对应的数据是否存在，存在就返回对应的结果数据，否则就返回计算结果\n\n如下所示\n\n```js\nconst memoize = function (func, content) {\n  let cache = Object.create(null)\n  content = content || this\n  return (...key) => {\n    if (!cache[key]) {\n      cache[key] = func.apply(content, key)\n    }\n    return cache[key]\n  }\n}\n```\n\n调用方式也很简单\n\n```js\nconst calc = memoize(add);\nconst num1 = calc(100,200)\nconst num2 = calc(100,200) // 缓存得到的结果\n```\n\n过程分析：\n\n- 在当前函数作用域定义了一个空对象，用于缓存运行结果\n- 运用柯里化返回一个函数，返回的函数由于闭包特性，可以访问到`cache`\n- 然后判断输入参数是不是在`cache`的中。如果已经存在，直接返回`cache`的内容，如果没有存在，使用函数`func`对输入参数求值，然后把结果存储在`cache`中\n\n\n\n## 三、应用场景\n\n虽然使用缓存效率是非常高的，但并不是所有场景都适用，因此千万不要极端的将所有函数都添加缓存\n\n以下几种情况下，适合使用缓存：\n\n- 对于昂贵的函数调用，执行复杂计算的函数\n- 对于具有有限且高度重复输入范围的函数\n- 对于具有重复输入值的递归函数\n- 对于纯函数，即每次使用特定输入调用时返回相同输出的函数\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/112505577"
  },
  {
    "path": "docs/JavaScript/functional_programming.md",
    "content": "# 面试官：说说你对函数式编程的理解？优缺点？\n\n ![](https://static.vue-js.com/ec0f6e80-8534-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n函数式编程是一种\"编程范式\"（programming paradigm），一种编写程序的方法论\n\n主要的编程范式有三种：命令式编程，声明式编程和函数式编程\n\n相比命令式编程，函数式编程更加强调程序执行的结果而非执行的过程，倡导利用若干简单的执行单元让计算结果不断渐进，逐层推导复杂的运算，而非设计一个复杂的执行过程\n\n举个例子，将数组每个元素进行平方操作，命令式编程与函数式编程如下\n\n```js\n// 命令式编程\nvar array = [0, 1, 2, 3]\nfor(let i = 0; i < array.length; i++) {\n    array[i] = Math.pow(array[i], 2)\n}\n\n// 函数式方式\n[0, 1, 2, 3].map(num => Math.pow(num, 2))\n```\n\n简单来讲，就是要把过程逻辑写成函数，定义好输入参数，只关心它的输出结果\n\n即是一种描述集合和集合之间的转换关系，输入通过函数都会返回有且只有一个输出值\n\n ![](https://static.vue-js.com/f9f83900-8534-11eb-85f6-6fac77c0c9b3.png)\n\n可以看到，函数实际上是一个关系，或者说是一种映射，而这种映射关系是可以组合的，一旦我们知道一个函数的输出类型可以匹配另一个函数的输入，那他们就可以进行组合\n\n\n## 二、概念\n\n### 纯函数\n\n函数式编程旨在尽可能的提高代码的无状态性和不变性。要做到这一点，就要学会使用无副作用的函数，也就是纯函数\n\n纯函数是对给定的输入返还相同输出的函数，并且要求你所有的数据都是不可变的，即纯函数=无状态+数据不可变\n\n ![](https://static.vue-js.com/04f50720-8535-11eb-ab90-d9ae814b240d.png)\n\n举一个简单的例子\n\n```js\nlet double = value=>value*2;\n```\n\n特性：\n\n- 函数内部传入指定的值，就会返回确定唯一的值\n- 不会造成超出作用域的变化，例如修改全局变量或引用传递的参数\n\n优势：\n\n- 使用纯函数，我们可以产生可测试的代码\n\n```js\ntest('double(2) 等于 4', () => {\n  expect(double(2)).toBe(4);\n})\n```\n\n- 不依赖外部环境计算，不会产生副作用，提高函数的复用性\n\n- 可读性更强 ，函数不管是否是纯函数  都会有一个语义化的名称，更便于阅读\n\n- 可以组装成复杂任务的可能性。符合模块化概念及单一职责原则\n\n\n\n### 高阶函数\n\n在我们的编程世界中，我们需要处理的其实也只有“数据”和“关系”，而关系就是函数\n\n编程工作也就是在找一种映射关系，一旦关系找到了，问题就解决了，剩下的事情，就是让数据流过这种关系，然后转换成另一个数据，如下图所示\n\n ![](https://static.vue-js.com/104af1c0-8535-11eb-ab90-d9ae814b240d.png)\n\n在这里，就是高阶函数的作用。高级函数，就是以函数作为输入或者输出的函数被称为高阶函数\n\n通过高阶函数抽象过程，注重结果，如下面例子\n\n```js\nconst forEach = function(arr,fn){\n    for(let i=0;i<arr.length;i++){\n        fn(arr[i]);\n    }\n}\nlet arr = [1,2,3];\nforEach(arr,(item)=>{\n    console.log(item);\n})\n```\n\n上面通过高阶函数 `forEach`来抽象循环如何做的逻辑，直接关注做了什么\n\n高阶函数存在缓存的特性，主要是利用闭包作用\n\n```js\nconst once = (fn)=>{\n    let done = false;\n    return function(){\n        if(!done){\n            fn.apply(this,fn);\n        }else{\n            console.log(\"该函数已经执行\");\n        }\n        done = true;\n    }\n}\n```\n\n### 柯里化\n\n柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程\n\n一个二元函数如下：\n\n```js\nlet fn = (x,y)=>x+y;\n```\n\n转化成柯里化函数如下：\n\n```js\nconst curry = function(fn){\n    return function(x){\n        return function(y){\n            return fn(x,y);\n        }\n    }\n}\nlet myfn = curry(fn);\nconsole.log( myfn(1)(2) );\n```\n\n上面的`curry`函数只能处理二元情况，下面再来实现一个实现多参数的情况\n\n```js\n// 多参数柯里化；\nconst curry = function(fn){\n    return function curriedFn(...args){\n        if(args.length<fn.length){\n            return function(){\n                return curriedFn(...args.concat([...arguments]));\n            }\n        }\n        return fn(...args);\n    }\n}\nconst fn = (x,y,z,a)=>x+y+z+a;\nconst myfn = curry(fn);\nconsole.log(myfn(1)(2)(3)(1));\n```\n\n关于柯里化函数的意义如下：\n\n- 让纯函数更纯，每次接受一个参数，松散解耦\n- 惰性执行\n\n\n\n### 组合与管道\n\n组合函数，目的是将多个函数组合成一个函数\n\n举个简单的例子：\n\n```js\nfunction afn(a){\n    return a*2;\n}\nfunction bfn(b){\n    return b*3;\n}\nconst compose = (a,b)=>c=>a(b(c));\nlet myfn =  compose(afn,bfn);\nconsole.log( myfn(2));\n```\n\n可以看到`compose`实现一个简单的功能：形成了一个新的函数，而这个函数就是一条从 `bfn -> afn` 的流水线\n\n下面再来看看如何实现一个多函数组合：\n\n```js\nconst compose = (...fns)=>val=>fns.reverse().reduce((acc,fn)=>fn(acc),val);\n```\n\n`compose`执行是从右到左的。而管道函数，执行顺序是从左到右执行的\n\n```js\nconst pipe = (...fns)=>val=>fns.reduce((acc,fn)=>fn(acc),val);\n```\n\n组合函数与管道函数的意义在于：可以把很多小函数组合起来完成更复杂的逻辑\n\n## 三、优缺点\n\n#### 优点\n\n- 更好的管理状态：因为它的宗旨是无状态，或者说更少的状态，能最大化的减少这些未知、优化代码、减少出错情况\n\n- 更简单的复用：固定输入->固定输出，没有其他外部变量影响，并且无副作用。这样代码复用时，完全不需要考虑它的内部实现和外部影响\n\n- 更优雅的组合：往大的说，网页是由各个组件组成的。往小的说，一个函数也可能是由多个小函数组成的。更强的复用性，带来更强大的组合性\n\n- 隐性好处。减少代码量，提高维护性\n\n#### 缺点：\n\n- 性能：函数式编程相对于指令式编程，性能绝对是一个短板，因为它往往会对一个方法进行过度包装，从而产生上下文切换的性能开销\n\n- 资源占用：在 JS 中为了实现对象状态的不可变，往往会创建新的对象，因此，它对垃圾回收所产生的压力远远超过其他编程方式\n\n- 递归陷阱：在函数式编程中，为了实现迭代，通常会采用递归操作\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/81302150"
  },
  {
    "path": "docs/JavaScript/inherit.md",
    "content": "# 面试官：Javascript如何实现继承？\n\n![](https://static.vue-js.com/5d9c4450-72a3-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n继承（inheritance）是面向对象软件技术当中的一个概念。\n\n如果一个类别B“继承自”另一个类别A，就把这个B称为“A的子类”，而把A称为“B的父类别”也可以称“A是B的超类”\n\n- 继承的优点\n\n继承可以使得子类具有父类别的各种属性和方法，而不需要再次编写相同的代码\n\n在子类别继承父类别的同时，可以重新定义某些属性，并重写某些方法，即覆盖父类别的原有属性和方法，使其获得与父类别不同的功能\n\n虽然`JavaScript`并不是真正的面向对象语言，但它天生的灵活性，使应用场景更加丰富\n\n关于继承，我们举个形象的例子：\n\n定义一个类（Class）叫汽车，汽车的属性包括颜色、轮胎、品牌、速度、排气量等\n\n```js\nclass Car{\n    constructor(color,speed){\n        this.color = color\n        this.speed = speed\n        // ...\n    }\n}\n```\n\n由汽车这个类可以派生出“轿车”和“货车”两个类，在汽车的基础属性上，为轿车添加一个后备厢、给货车添加一个大货箱\n\n```js\n// 货车\nclass Truck extends Car{\n    constructor(color,speed){\n        super(color,speed)\n        this.Container = true // 货箱\n    }\n}\n```\n\n这样轿车和货车就是不一样的，但是二者都属于汽车这个类，汽车、轿车继承了汽车的属性，而不需要再次在“轿车”中定义汽车已经有的属性\n\n在“轿车”继承“汽车”的同时，也可以重新定义汽车的某些属性，并重写或覆盖某些属性和方法，使其获得与“汽车”这个父类不同的属性和方法\n\n```js\nclass Truck extends Car{\n    constructor(color,speed){\n        super(color,speed)\n        this.color = \"black\" //覆盖\n        this.Container = true // 货箱\n    }\n}\n```\n\n从这个例子中就能详细说明汽车、轿车以及卡车之间的继承关系\n\n\n\n## 二、实现方式\n\n下面给出`JavaScripy`常见的继承方式：\n\n- 原型链继承\n\n- 构造函数继承（借助 call）\n- 组合继承\n- 原型式继承\n- 寄生式继承\n- 寄生组合式继承\n\n\n\n### 原型链继承\n\n原型链继承是比较常见的继承方式之一，其中涉及的构造函数、原型和实例，三者之间存在着一定的关系，即每一个构造函数都有一个原型对象，原型对象又包含一个指向构造函数的指针，而实例则包含一个原型对象的指针\n\n举个例子\n\n```js\n function Parent() {\n    this.name = 'parent1';\n    this.play = [1, 2, 3]\n  }\n  function Child() {\n    this.type = 'child2';\n  }\n  Child1.prototype = new Parent();\n  console.log(new Child())\n```\n\n上面代码看似没问题，实际存在潜在问题\n\n```js\nvar s1 = new Child2();\nvar s2 = new Child2();\ns1.play.push(4);\nconsole.log(s1.play, s2.play); // [1,2,3,4]\n```\n\n改变`s1`的`play`属性，会发现`s2`也跟着发生变化了，这是因为两个实例使用的是同一个原型对象，内存空间是共享的\n\n\n\n### 构造函数继承\n\n借助 `call `调用`Parent`函数\n\n```js\nfunction Parent(){\n    this.name = 'parent1';\n}\n\nParent.prototype.getName = function () {\n    return this.name;\n}\n\nfunction Child(){\n    Parent1.call(this);\n    this.type = 'child'\n}\n\nlet child = new Child();\nconsole.log(child);  // 没问题\nconsole.log(child.getName());  // 会报错\n```\n\n可以看到，父类原型对象中一旦存在父类之前自己定义的方法，那么子类将无法继承这些方法\n\n相比第一种原型链继承方式，父类的引用属性不会被共享，优化了第一种继承方式的弊端，但是只能继承父类的实例属性和方法，不能继承原型属性或者方法\n\n\n\n### 组合继承\n\n前面我们讲到两种继承方式，各有优缺点。组合继承则将前两种方式继承起来\n\n```js\nfunction Parent3 () {\n    this.name = 'parent3';\n    this.play = [1, 2, 3];\n}\n\nParent3.prototype.getName = function () {\n    return this.name;\n}\nfunction Child3() {\n    // 第二次调用 Parent3()\n    Parent3.call(this);\n    this.type = 'child3';\n}\n\n// 第一次调用 Parent3()\nChild3.prototype = new Parent3();\n// 手动挂上构造器，指向自己的构造函数\nChild3.prototype.constructor = Child3;\nvar s3 = new Child3();\nvar s4 = new Child3();\ns3.play.push(4);\nconsole.log(s3.play, s4.play);  // 不互相影响\nconsole.log(s3.getName()); // 正常输出'parent3'\nconsole.log(s4.getName()); // 正常输出'parent3'\n```\n\n这种方式看起来就没什么问题，方式一和方式二的问题都解决了，但是从上面代码我们也可以看到` Parent3` 执行了两次，造成了多构造一次的性能开销\n\n\n\n### 原型式继承\n\n这里主要借助`Object.create`方法实现普通对象的继承\n\n同样举个例子\n\n```js\nlet parent4 = {\n    name: \"parent4\",\n    friends: [\"p1\", \"p2\", \"p3\"],\n    getName: function() {\n      return this.name;\n    }\n  };\n\n  let person4 = Object.create(parent4);\n  person4.name = \"tom\";\n  person4.friends.push(\"jerry\");\n\n  let person5 = Object.create(parent4);\n  person5.friends.push(\"lucy\");\n\n  console.log(person4.name); // tom\n  console.log(person4.name === person4.getName()); // true\n  console.log(person5.name); // parent4\n  console.log(person4.friends); // [\"p1\", \"p2\", \"p3\",\"jerry\",\"lucy\"]\n  console.log(person5.friends); // [\"p1\", \"p2\", \"p3\",\"jerry\",\"lucy\"]\n```\n\n这种继承方式的缺点也很明显，因为`Object.create `方法实现的是浅拷贝，多个实例的引用类型属性指向相同的内存，存在篡改的可能\n\n\n\n### 寄生式继承\n\n寄生式继承在上面继承基础上进行优化，利用这个浅拷贝的能力再进行增强，添加一些方法\n\n```js\nlet parent5 = {\n    name: \"parent5\",\n    friends: [\"p1\", \"p2\", \"p3\"],\n    getName: function() {\n        return this.name;\n    }\n};\n\nfunction clone(original) {\n    let clone = Object.create(original);\n    clone.getFriends = function() {\n        return this.friends;\n    };\n    return clone;\n}\n\nlet person5 = clone(parent5);\n\nconsole.log(person5.getName()); // parent5\nconsole.log(person5.getFriends()); // [\"p1\", \"p2\", \"p3\"]\n```\n\n其优缺点也很明显，跟上面讲的原型式继承一样\n\n\n\n### 寄生组合式继承\n\n寄生组合式继承，借助解决普通对象的继承问题的` Object.create` 方法，在前面几种继承方式的优缺点基础上进行改造，这也是所有继承方式里面相对最优的继承方式\n\n```js\nfunction clone (parent, child) {\n    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程\n    child.prototype = Object.create(parent.prototype);\n    child.prototype.constructor = child;\n}\n\nfunction Parent6() {\n    this.name = 'parent6';\n    this.play = [1, 2, 3];\n}\nParent6.prototype.getName = function () {\n    return this.name;\n}\nfunction Child6() {\n    Parent6.call(this);\n    this.friends = 'child5';\n}\n\nclone(Parent6, Child6);\n\nChild6.prototype.getFriends = function () {\n    return this.friends;\n}\n\nlet person6 = new Child6();\nconsole.log(person6); //{friends:\"child5\",name:\"child5\",play:[1,2,3],__proto__:Parent6}\nconsole.log(person6.getName()); // parent6\nconsole.log(person6.getFriends()); // child5\n```\n\n可以看到 person6 打印出来的结果，属性都得到了继承，方法也没问题\n\n\n\n文章一开头，我们是使用`ES6` 中的`extends `关键字直接实现 `JavaScript `的继承\n\n```js\nclass Person {\n  constructor(name) {\n    this.name = name\n  }\n  // 原型方法\n  // 即 Person.prototype.getName = function() { }\n  // 下面可以简写为 getName() {...}\n  getName = function () {\n    console.log('Person:', this.name)\n  }\n}\nclass Gamer extends Person {\n  constructor(name, age) {\n    // 子类中存在构造函数，则需要在使用“this”之前首先调用 super()。\n    super(name)\n    this.age = age\n  }\n}\nconst asuna = new Gamer('Asuna', 20)\nasuna.getName() // 成功访问到父类的方法\n```\n\n利用`babel`工具进行转换，我们会发现`extends`实际采用的也是寄生组合继承方式，因此也证明了这种方式是较优的解决继承的方式\n\n\n\n## 三、总结\n\n下面以一张图作为总结：\n\n ![](https://static.vue-js.com/0df74700-731c-11eb-ab90-d9ae814b240d.png)\n\n通过`Object.create` 来划分不同的继承方式，最后的寄生式组合继承方式是通过组合继承改造之后的最优继承方式，而 `extends` 的语法糖和寄生组合继承的方式基本类似\n\n## 相关链接\nhttps://zh.wikipedia.org/wiki/%E7%BB%A7%E6%89%BF"
  },
  {
    "path": "docs/JavaScript/js_data_structure.md",
    "content": "# 面试官：说说你了解的js数据结构？\n\n## 什么是数据结构？\n数据结构是计算机存储、组织数据的方式。\n数据结构意味着接口或封装：一个数据结构可被视为两个函数之间的接口，或者是由数据类型联合组成的存储内容的访问方法封装。\n\n我们每天的编码中都会用到数据结构\n数组是最简单的内存数据结构\n下面是常见的数据结构：\n1. 数组（Array）\n2. 栈（Stack）\n3. 队列（Queue）\n4. 链表（Linked List）\n5. 字典\n6. 散列表（Hash table）\n7. 树（Tree）\n8. 图（Graph）\n9. 堆（Heap）\n\n\n\n## 数组（Array）\n数组是最最基本的数据结构，很多语言都内置支持数组。\n数组是使用一块连续的内存空间保存数据，保存的数据的个数在分配内存的时候就是确定的。\n\n在日常生活中，人们经常使用列表：待办事项列表、购物清单等。\n\n而计算机程序也在使用列表，在下面的条件下，选择列表作为数据结构就显得尤为有用：\n数据结构较为简单\n不需要在一个长序列中查找元素，或者对其进行排序\n反之，如果数据结构非常复杂，列表的作用就没有那么大了。\n\n\n## 栈（Stack）\n栈是一种遵循后进先出（LIFO）原则的有序集合\n在栈里，新元素都接近栈顶，旧元素都接近栈底。\n每次加入新的元素和拿走元素都在顶部操作\n![](https://upload-images.jianshu.io/upload_images/13253432-ddcb884374470d2c?imageMogr2/auto-orient/strip|imageView2/2/format/webp)\n\n\n## 队列（Queue）\n队列是遵循先进先出（FIFO，也称为先来先服务）原则的一组有序的项\n队列在尾部添加新元素，并从顶部移除元素\n最新添加的元素必须排在队列的末尾\n![](https://upload-images.jianshu.io/upload_images/13253432-55ad7c7db40d3796?imageMogr2/auto-orient/strip|imageView2/2/format/webp)\n\n\n## 链表（Linked List）\n链表也是一种列表，已经设计了数组，为什么还需要链表呢？\nJavaScript中数组的主要问题时，它们被实现成了对象，\n与其他语言（比如C++和Java）的数组相对，效率很低。\n如果你发现数组在实际使用时很慢，就可以考虑使用链表来代替它。\n\n使用条件：\n链表几乎可以用在任何可以使用一维数组的情况中。\n如果需要随机访问，数组仍然是更好的选择。\n![](https://raw.githubusercontent.com/zoro-web/blog/master/img/lian.jpg)\n\n## 字典\n字典是一种以键-值对存储数据的数据结构，js中的Object类就是以字典的形式设计的。JavaScript可以通过实现字典类，让这种字典类型的对象使用起来更加简单，字典可以实现对象拥有的常见功能，并相应拓展自己想要的功能，而对象在JavaScript编写中随处可见，所以字典的作用也异常明显了。\n\n\n## 散列表\n也称为哈希表，特点是在散列表上插入、删除和取用数据都非常快。\n为什么要设计这种数据结构呢？\n用数组或链表存储数据，如果想要找到其中一个数据，需要从头进行遍历，因为不知道这个数据存储到了数组的哪个位置。\n\n散列表在JavaScript中可以基础数组去进行设计。\n数组的长度是预先设定的，所有元素根据和该元素对应的键，保存在数组的特定位置，这里的键和对象的键是类型的概念。\n使用散列表存储数组时，通过一个散列函数将键映射为一个数字，这个数字的范围是0到散列表的长度。\n\n即使使用一个高效的散列函数，依然存在将两个键映射为同一个值得可能，这种现象叫做碰撞。常见碰撞的处理方法有：开链法和线性探测法（具体概念有兴趣的可以网上自信了解）\n使用条件：\n可以用于数据的插入、删除和取用，不适用于查找数据\n![](https://raw.githubusercontent.com/zoro-web/blog/master/img/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20170820211406.png)\n\n\n"
  },
  {
    "path": "docs/JavaScript/loss_accuracy.md",
    "content": "# 面试官：说说 Javascript 数字精度丢失的问题，如何解决？\n\n ![](https://static.vue-js.com/09646a10-86f4-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、场景复现\n\n一个经典的面试题\n\n```js\n0.1 + 0.2 === 0.3 // false\n```\n为什么是`false`呢?\n\n先看下面这个比喻\n\n比如一个数 1÷3=0.33333333...... \n\n3会一直无限循环，数学可以表示，但是计算机要存储，方便下次取出来再使用，但0.333333...... 这个数无限循环，再大的内存它也存不下，所以不能存储一个相对于数学来说的值，只能存储一个近似值，当计算机存储后再取出时就会出现精度丢失问题\n\n## 二、浮点数\n\n“浮点数”是一种表示数字的标准，整数也可以用浮点数的格式来存储\n\n我们也可以理解成，浮点数就是小数\n\n在`JavaScript`中，现在主流的数值类型是`Number`，而`Number`采用的是`IEEE754`规范中64位双精度浮点数编码\n\n这样的存储结构优点是可以归一化处理整数和小数，节省存储空间\n\n对于一个整数，可以很轻易转化成十进制或者二进制。但是对于一个浮点数来说，因为小数点的存在，小数点的位置不是固定的。解决思路就是使用科学计数法，这样小数点位置就固定了\n\n而计算机只能用二进制（0或1）表示，二进制转换为科学记数法的公式如下：\n\n ![](https://static.vue-js.com/1b4b1620-86f4-11eb-ab90-d9ae814b240d.png)\n\n其中，`a`的值为0或者1，e为小数点移动的位置\n\n举个例子：\n\n27.0转化成二进制为11011.0 ，科学计数法表示为：\n\n ![](https://static.vue-js.com/37007090-86f4-11eb-ab90-d9ae814b240d.png)\n\n前面讲到，`javaScript`存储方式是双精度浮点数，其长度为8个字节，即64位比特\n\n64位比特又可分为三个部分：\n\n- 符号位S：第 1 位是正负数符号位（sign），0代表正数，1代表负数\n- 指数位E：中间的 11 位存储指数（exponent），用来表示次方数，可以为正负数。在双精度浮点数中，指数的固定偏移量为1023\n- 尾数位M：最后的 52 位是尾数（mantissa），超出的部分自动进一舍零\n\n如下图所示：\n\n ![](https://static.vue-js.com/430d0100-86f4-11eb-85f6-6fac77c0c9b3.png)\n\n举个例子：\n\n27.5 转换为二进制11011.1\n\n11011.1转换为科学记数法 ![[公式]](https://www.zhihu.com/equation?tex=1.10111%2A2%5E4)\n\n符号位为1(正数)，指数位为4+，1023+4，即1027\n\n因为它是十进制的需要转换为二进制，即 `10000000011`，小数部分为`10111`，补够52位即： 1011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`\n\n所以27.5存储为计算机的二进制标准形式（符号位+指数位+小数部分 (阶数)），既下面所示\n\n0+10000000011+011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`\n\n\n## 二、问题分析\n\n再回到问题上\n\n```js\n0.1 + 0.2 === 0.3 // false\n```\n\n通过上面的学习，我们知道，在`javascript`语言中，0.1 和 0.2 都转化成二进制后再进行运算\n\n```js\n// 0.1 和 0.2 都转化成二进制后再进行运算\n0.00011001100110011001100110011001100110011001100110011010 +\n0.0011001100110011001100110011001100110011001100110011010 =\n0.0100110011001100110011001100110011001100110011001100111\n\n// 转成十进制正好是 0.30000000000000004\n```\n\n所以输出`false`\n\n再来一个问题，那么为什么`x=0.1`得到`0.1`？\n\n主要是存储二进制时小数点的偏移量最大为52位，最多可以表达的位数是`2^53=9007199254740992`，对应科学计数尾数是 `9.007199254740992`，这也是 JS 最多能表示的精度\n\n它的长度是 16，所以可以使用 `toPrecision(16)` 来做精度运算，超过的精度会自动做凑整处理\n\n```js\n.10000000000000000555.toPrecision(16)\n// 返回 0.1000000000000000，去掉末尾的零后正好为 0.1\n```\n\n但看到的 `0.1` 实际上并不是 `0.1`。不信你可用更高的精度试试：\n\n```js\n0.1.toPrecision(21) = 0.100000000000000005551\n```\n\n如果整数大于 `9007199254740992` 会出现什么情况呢？\n\n由于指数位最大值是1023，所以最大可以表示的整数是 `2^1024 - 1`，这就是能表示的最大整数。但你并不能这样计算这个数字，因为从 `2^1024` 开始就变成了 `Infinity`\n\n```\n> Math.pow(2, 1023)\n8.98846567431158e+307\n\n> Math.pow(2, 1024)\nInfinity\n```\n\n那么对于 `(2^53, 2^63)` 之间的数会出现什么情况呢？\n\n- `(2^53, 2^54)` 之间的数会两个选一个，只能精确表示偶数\n- `(2^54, 2^55)` 之间的数会四个选一个，只能精确表示4个倍数\n- ... 依次跳过更多2的倍数\n\n要想解决大数的问题你可以引用第三方库 `bignumber.js`，原理是把所有数字当作字符串，重新实现了计算逻辑，缺点是性能比原生差很多\n\n### 小结\n\n计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式，然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法\n\n因为存储时有位数限制（64位），并且某些十进制的浮点数在转换为二进制数时会出现无限循环，会造成二进制的舍入操作(0舍1入)，当再转换为十进制时就造成了计算误差\n\n\n## 三、解决方案\n\n理论上用有限的空间来存储无限的小数是不可能保证精确的，但我们可以处理一下得到我们期望的结果\n\n当你拿到 `1.4000000000000001` 这样的数据要展示时，建议使用 `toPrecision` 凑整并 `parseFloat` 转成数字后再显示，如下：\n\n```\nparseFloat(1.4000000000000001.toPrecision(12)) === 1.4  // True\n```\n\n封装成方法就是：\n\n```js\nfunction strip(num, precision = 12) {\n  return +parseFloat(num.toPrecision(precision));\n}\n```\n\n对于运算类操作，如 `+-*/`，就不能使用 `toPrecision` 了。正确的做法是把小数转成整数后再运算。以加法为例：\n\n```js\n/**\n * 精确加法\n */\nfunction add(num1, num2) {\n  const num1Digits = (num1.toString().split('.')[1] || '').length;\n  const num2Digits = (num2.toString().split('.')[1] || '').length;\n  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));\n  return (num1 * baseNum + num2 * baseNum) / baseNum;\n}\n```\n\n最后还可以使用第三方库，如`Math.js`、`BigDecimal.js`\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/100353781\n- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt"
  },
  {
    "path": "docs/JavaScript/memory_leak.md",
    "content": "# 面试官：说说 JavaScript 中内存泄漏的几种情况？\n\n  ![](https://static.vue-js.com/19f76b30-824d-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n内存泄漏（Memory leak）是在计算机科学中，由于疏忽或错误造成程序未能释放已经不再使用的内存\n\n并非指内存在物理上的消失，而是应用程序分配某段内存后，由于设计错误，导致在释放该段内存之前就失去了对该段内存的控制，从而造成了内存的浪费\n\n程序的运行需要内存。只要程序提出要求，操作系统或者运行时就必须供给内存\n\n对于持续运行的服务进程，必须及时释放不再用到的内存。否则，内存占用越来越高，轻则影响系统性能，重则导致进程崩溃\n\n ![](https://static.vue-js.com/56d4bd90-821c-11eb-ab90-d9ae814b240d.png)\n\n在`C`语言中，因为是手动管理内存，内存泄露是经常出现的事情。\n\n```clang\nchar * buffer;\nbuffer = (char*) malloc(42);\n\n// Do something with buffer\n\nfree(buffer);\n```\n\n上面是 C 语言代码，`malloc`方法用来申请内存，使用完毕之后，必须自己用`free`方法释放内存。\n\n这很麻烦，所以大多数语言提供自动内存管理，减轻程序员的负担，这被称为\"垃圾回收机制\"\n\n\n## 二、垃圾回收机制\n\nJavascript 具有自动垃圾回收机制（GC：Garbage Collecation），也就是说，执行环境会负责管理代码执行过程中使用的内存\n\n原理：垃圾收集器会定期（周期性）找出那些不在继续使用的变量，然后释放其内存\n\n通常情况下有两种实现方式：\n\n- 标记清除\n- 引用计数\n\n\n\n### 标记清除\n\n`JavaScript`最常用的垃圾收回机制\n\n当变量进入执行环境是，就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放，当变量离开环境时，则将其标记为“离开环境“\n\n垃圾回收程序运行的时候，会标记内存中存储的所有变量。然后，它会将所有在上下文中的变量，以及被在上下文中的变量引用的变量的标记去掉\n\n在此之后再被加上标记的变量就是待删除的了，原因是任何在上下文中的变量都访问不到它们了\n\n随后垃圾回收程序做一次内存清理，销毁带标记的所有值并收回它们的内存\n\n举个例子：\n\n```js\nvar m = 0,n = 19 // 把 m,n,add() 标记为进入环境。\nadd(m, n) // 把 a, b, c标记为进入环境。\nconsole.log(n) // a,b,c标记为离开环境，等待垃圾回收。\nfunction add(a, b) {\n  a++\n  var c = a + b\n  return c\n}\n```\n\n\n\n### 引用计数\n\n语言引擎有一张\"引用表\"，保存了内存里面所有的资源（通常是各种值）的引用次数。如果一个值的引用次数是`0`，就表示这个值不再用到了，因此可以将这块内存释放\n\n如果一个值不再需要了，引用数却不为`0`，垃圾回收机制无法释放这块内存，从而导致内存泄漏\n\n```javascript\nconst arr = [1, 2, 3, 4];\nconsole.log('hello world');\n```\n\n上面代码中，数组`[1, 2, 3, 4]`是一个值，会占用内存。变量`arr`是仅有的对这个值的引用，因此引用次数为`1`。尽管后面的代码没有用到`arr`，它还是会持续占用内存\n\n如果需要这块内存被垃圾回收机制释放，只需要设置如下：\n\n```js\narr = null\n```\n\n通过设置`arr`为`null`，就解除了对数组`[1,2,3,4]`的引用，引用次数变为 0，就被垃圾回收了\n\n\n\n### 小结\n\n有了垃圾回收机制，不代表不用关注内存泄露。那些很占空间的值，一旦不再用到，需要检查是否还存在对它们的引用。如果是的话，就必须手动解除引用\n\n\n\n## 三、常见内存泄露情况\n\n意外的全局变量\n\n```js\nfunction foo(arg) {\n    bar = \"this is a hidden global variable\";\n}\n```\n\n另一种意外的全局变量可能由 `this` 创建：\n\n```js\nfunction foo() {\n    this.variable = \"potential accidental global\";\n}\n// foo 调用自己，this 指向了全局对象（window）\nfoo();\n```\n\n上述使用严格模式，可以避免意外的全局变量\n\n定时器也常会造成内存泄露\n\n```js\nvar someResource = getData();\nsetInterval(function() {\n    var node = document.getElementById('Node');\n    if(node) {\n        // 处理 node 和 someResource\n        node.innerHTML = JSON.stringify(someResource));\n    }\n}, 1000);\n```\n\n如果`id`为Node的元素从`DOM`中移除，该定时器仍会存在，同时，因为回调函数中包含对`someResource`的引用，定时器外面的`someResource`也不会被释放\n\n包括我们之前所说的闭包，维持函数内局部变量，使其得不到释放\n\n```js\nfunction bindEvent() {\n  var obj = document.createElement('XXX');\n  var unused = function () {\n    console.log(obj, '闭包内引用obj obj不会被释放');\n  };\n  obj = null; // 解决方法\n}\n```\n\n没有清理对`DOM`元素的引用同样造成内存泄露\n\n```js\nconst refA = document.getElementById('refA');\ndocument.body.removeChild(refA); // dom删除了\nconsole.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收\nrefA = null;\nconsole.log(refA, 'refA'); // 解除引用\n```\n\n包括使用事件监听`addEventListener`监听的时候，在不监听的情况下使用`removeEventListener`取消对事件监听\n\n\n## 参考文献\n\n- http://www.ruanyifeng.com/blog/2017/04/memory-leak.html\n- https://zh.wikipedia.org/wiki\n"
  },
  {
    "path": "docs/JavaScript/new.md",
    "content": "# 面试官：说说new操作符具体干了什么？\n\n![](https://static.vue-js.com/880d0010-7a39-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在`JavaScript`中，`new`操作符用于创建一个给定构造函数的实例对象\n\n例子\n```js\nfunction Person(name, age){\n    this.name = name;\n    this.age = age;\n}\nPerson.prototype.sayName = function () {\n    console.log(this.name)\n}\nconst person1 = new Person('Tom', 20)\nconsole.log(person1)  // Person {name: \"Tom\", age: 20}\nt.sayName() // 'Tom'\n```\n\n从上面可以看到：\n\n- `new` 通过构造函数 `Person` 创建出来的实例可以访问到构造函数中的属性\n- `new` 通过构造函数 `Person` 创建出来的实例可以访问到构造函数原型链中的属性（即实例与构造函数通过原型链连接了起来）\n\n现在在构建函数中显式加上返回值，并且这个返回值是一个原始类型\n\n```js\nfunction Test(name) {\n  this.name = name\n  return 1\n}\nconst t = new Test('xxx')\nconsole.log(t.name) // 'xxx'\n```\n\n可以发现，构造函数中返回一个原始值，然而这个返回值并没有作用\n\n下面在构造函数中返回一个对象\n\n```js\nfunction Test(name) {\n  this.name = name\n  console.log(this) // Test { name: 'xxx' }\n  return { age: 26 }\n}\nconst t = new Test('xxx')\nconsole.log(t) // { age: 26 }\nconsole.log(t.name) // 'undefined'\n```\n\n从上面可以发现，构造函数如果返回值为一个对象，那么这个返回值会被正常使用\n\n\n\n## 二、流程\n\n从上面介绍中，我们可以看到`new`关键字主要做了以下的工作：\n\n- 创建一个新的对象`obj`\n- 将对象与构建函数通过原型链连接起来\n- 将构建函数中的`this`绑定到新建的对象`obj`上\n\n- 根据构建函数返回类型作判断，如果是原始值则被忽略，如果是返回对象，需要正常处理\n\n举个例子：\n\n```js\nfunction Person(name, age){\n    this.name = name;\n    this.age = age;\n}\nconst person1 = new Person('Tom', 20)\nconsole.log(person1)  // Person {name: \"Tom\", age: 20}\nt.sayName() // 'Tom'\n```\n\n流程图如下：\n\n ![](https://static.vue-js.com/b429b990-7a39-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 三、手写new操作符\n\n现在我们已经清楚地掌握了`new`的执行过程\n\n那么我们就动手来实现一下`new`\n\n```js\nfunction mynew(Func, ...args) {\n    // 1.创建一个新对象\n    const obj = {}\n    // 2.新对象原型指向构造函数原型对象\n    obj.__proto__ = Func.prototype\n    // 3.将构建函数的this指向新对象\n    let result = Func.apply(obj, args)\n    // 4.根据返回值判断\n    return result instanceof Object ? result : obj\n}\n```\n\n测试一下\n\n```js\nfunction mynew(func, ...args) {\n    const obj = {}\n    obj.__proto__ = func.prototype\n    let result = func.apply(obj, args)\n    return result instanceof Object ? result : obj\n}\nfunction Person(name, age) {\n    this.name = name;\n    this.age = age;\n}\nPerson.prototype.say = function () {\n    console.log(this.name)\n}\n\nlet p = mynew(Person, \"huihui\", 123)\nconsole.log(p) // Person {name: \"huihui\", age: 123}\np.say() // huihui\n```\n\n可以发现，代码虽然很短，但是能够模拟实现`new`\n\n"
  },
  {
    "path": "docs/JavaScript/prototype.md",
    "content": "# 面试官：JavaScript原型，原型链 ? 有什么特点？\n\n ![](https://static.vue-js.com/4500e170-725e-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、原型\n\n`JavaScript` 常被描述为一种基于原型的语言——每个对象拥有一个原型对象\n\n当试图访问一个对象的属性时，它不仅仅在该对象上搜寻，还会搜寻该对象的原型，以及该对象的原型的原型，依次层层向上搜索，直到找到一个名字匹配的属性或到达原型链的末尾\n\n准确地说，这些属性和方法定义在Object的构造器函数（constructor functions）之上的`prototype`属性上，而非实例对象本身\n\n下面举个例子：\n\n函数可以有属性。 每个函数都有一个特殊的属性叫作原型`prototype` \n\n```js\nfunction doSomething(){}\nconsole.log( doSomething.prototype );\n```\n\n控制台输出\n\n```js\n{\n    constructor: ƒ doSomething(),\n    __proto__: {\n        constructor: ƒ Object(),\n        hasOwnProperty: ƒ hasOwnProperty(),\n        isPrototypeOf: ƒ isPrototypeOf(),\n        propertyIsEnumerable: ƒ propertyIsEnumerable(),\n        toLocaleString: ƒ toLocaleString(),\n        toString: ƒ toString(),\n        valueOf: ƒ valueOf()\n    }\n}\n```\n\n上面这个对象，就是大家常说的原型对象\n\n可以看到，原型对象有一个自有属性`constructor`，这个属性指向该函数，如下图关系展示\n\n ![](https://static.vue-js.com/56d87250-725e-11eb-ab90-d9ae814b240d.png)\n\n\n\n\n\n## 二、原型链\n\n原型对象也可能拥有原型，并从中继承方法和属性，一层一层、以此类推。这种关系常被称为原型链 (prototype chain)，它解释了为何一个对象会拥有定义在其他对象中的属性和方法\n\n在对象实例和它的构造器之间建立一个链接（它是`__proto__`属性，是从构造函数的`prototype`属性派生的），之后通过上溯原型链，在构造器中找到这些属性和方法\n\n下面举个例子：\n\n```js\nfunction Person(name) {\n    this.name = name;\n    this.age = 18;\n    this.sayName = function() {\n        console.log(this.name);\n    }\n}\n// 第二步 创建实例\nvar person = new Person('person')\n```\n\n根据代码，我们可以得到下图\n\n ![](https://static.vue-js.com/60825aa0-725e-11eb-85f6-6fac77c0c9b3.png)\n\n下面分析一下：\n\n- 构造函数`Person`存在原型对象`Person.prototype`\n- 构造函数生成实例对象`person`，`person`的`__proto__`指向构造函数`Person`原型对象\n- `Person.prototype.__proto__` 指向内置对象，因为 `Person.prototype` 是个对象，默认是由 `Object `函数作为类创建的，而 `Object.prototype` 为内置对象\n\n- `Person.__proto__` 指向内置匿名函数 `anonymous`，因为 Person 是个函数对象，默认由 Function 作为类创建\n\n- `Function.prototype` 和 `Function.__proto__ `同时指向内置匿名函数 `anonymous`，这样原型链的终点就是 `null`\n\n\n\n## 三、总结\n\n下面首先要看几个概念：\n\n`__proto__`作为不同对象之间的桥梁，用来指向创建它的构造函数的原型对象的\n\n ![](https://static.vue-js.com/6a742160-725e-11eb-ab90-d9ae814b240d.png)\n\n每个对象的`__proto__`都是指向它的构造函数的原型对象`prototype`的\n\n```js\nperson1.__proto__ === Person.prototype\n```\n\n构造函数是一个函数对象，是通过 `Function `构造器产生的\n\n```js\nPerson.__proto__ === Function.prototype\n```\n\n原型对象本身是一个普通对象，而普通对象的构造函数都是`Object`\n\n```js\nPerson.prototype.__proto__ === Object.prototype\n```\n\n刚刚上面说了，所有的构造器都是函数对象，函数对象都是 `Function `构造产生的\n\n```js\nObject.__proto__ === Function.prototype\n```\n\n`Object `的原型对象也有`__proto__`属性指向`null`，`null`是原型链的顶端\n\n```js\nObject.prototype.__proto__ === null\n```\n\n下面作出总结：\n\n- 一切对象都是继承自`Object`对象，`Object` 对象直接继承根源对象` null`\n\n- 一切的函数对象（包括 `Object` 对象），都是继承自 `Function` 对象\n\n- `Object` 对象直接继承自 `Function` 对象\n\n- `Function`对象的`__proto__`会指向自己的原型对象，最终还是继承自`Object`对象\n\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6870732239556640775#heading-7\n- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain"
  },
  {
    "path": "docs/JavaScript/pull_up_loading_pull_down_refresh.md",
    "content": "# 面试官：如何实现上拉加载，下拉刷新？\n\n ![](https://static.vue-js.com/89cd1850-8adc-11eb-ab90-d9ae814b240d.png)\n\n## 一、前言\n\n下拉刷新和上拉加载这两种交互方式通常出现在移动端中\n\n本质上等同于PC网页中的分页，只是交互形式不同\n\n开源社区也有很多优秀的解决方案，如`iscroll`、`better-scroll`、`pulltorefresh.js`库等等\n\n这些第三方库使用起来非常便捷\n\n我们通过原生的方式实现一次上拉加载，下拉刷新，有助于对第三方库有更好的理解与使用\n\n## 二、实现原理\n\n上拉加载及下拉刷新都依赖于用户交互\n\n最重要的是要理解在什么场景，什么时机下触发交互动作\n\n### 上拉加载\n\n首先可以看一张图\n\n ![](https://static.vue-js.com/df498a00-8ae3-11eb-ab90-d9ae814b240d.png)\n\n上拉加载的本质是页面触底，或者快要触底时的动作\n\n判断页面触底我们需要先了解一下下面几个属性\n\n- `scrollTop`：滚动视窗的高度距离`window`顶部的距离，它会随着往上滚动而不断增加，初始值是0，它是一个变化的值\n\n- `clientHeight`:它是一个定值，表示屏幕可视区域的高度；\n- `scrollHeight`：页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示`body`所有元素的总长度(包括body元素自身的padding)\n\n综上我们得出一个触底公式：\n```js\nscrollTop + clientHeight >= scrollHeight\n```\n\n简单实现\n```js\nlet clientHeight  = document.documentElement.clientHeight; //浏览器高度\nlet scrollHeight = document.body.scrollHeight;\nlet scrollTop = document.documentElement.scrollTop;\n \nlet distance = 50;  //距离视窗还用50的时候，开始触发；\n\nif ((scrollTop + clientHeight) >= (scrollHeight - distance)) {\n    console.log(\"开始加载数据\");\n}\n```\n\n\n### 下拉刷新\n下拉刷新的本质是页面本身置于顶部时，用户下拉时需要触发的动作\n\n关于下拉刷新的原生实现，主要分成三步：\n\n- 监听原生`touchstart`事件，记录其初始位置的值，`e.touches[0].pageY`；\n- 监听原生`touchmove`事件，记录并计算当前滑动的位置值与初始位置值的差值，大于`0`表示向下拉动，并借助CSS3的`translateY`属性使元素跟随手势向下滑动对应的差值，同时也应设置一个允许滑动的最大值；\n- 监听原生`touchend`事件，若此时元素滑动达到最大值，则触发`callback`，同时将`translateY`重设为`0`，元素回到初始位置\n\n举个例子：\n\n`Html`结构如下：\n\n```js\n<main>\n    <p class=\"refreshText\"></p >\n    <ul id=\"refreshContainer\">\n        <li>111</li>\n        <li>222</li>\n        <li>333</li>\n        <li>444</li>\n        <li>555</li>\n        ...\n    </ul>\n</main>\n```\n\n监听`touchstart`事件，记录初始的值\n\n```js\nvar _element = document.getElementById('refreshContainer'),\n    _refreshText = document.querySelector('.refreshText'),\n    _startPos = 0,  // 初始的值\n    _transitionHeight = 0; // 移动的距离\n\n_element.addEventListener('touchstart', function(e) {\n    _startPos = e.touches[0].pageY; // 记录初始位置\n    _element.style.position = 'relative';\n    _element.style.transition = 'transform 0s';\n}, false);\n```\n\n监听`touchmove`移动事件，记录滑动差值\n\n```js\n_element.addEventListener('touchmove', function(e) {\n    // e.touches[0].pageY 当前位置\n    _transitionHeight = e.touches[0].pageY - _startPos; // 记录差值\n\n    if (_transitionHeight > 0 && _transitionHeight < 60) { \n        _refreshText.innerText = '下拉刷新'; \n        _element.style.transform = 'translateY('+_transitionHeight+'px)';\n\n        if (_transitionHeight > 55) {\n            _refreshText.innerText = '释放更新';\n        }\n    }                \n}, false);\n```\n\n最后，就是监听`touchend`离开的事件\n\n```js\n_element.addEventListener('touchend', function(e) {\n    _element.style.transition = 'transform 0.5s ease 1s';\n    _element.style.transform = 'translateY(0px)';\n    _refreshText.innerText = '更新中...';\n    // todo...\n\n}, false);\n```\n\n从上面可以看到，在下拉到松手的过程中，经历了三个阶段：\n\n- 当前手势滑动位置与初始位置差值大于零时，提示正在进行下拉刷新操作\n- 下拉到一定值时，显示松手释放后的操作提示\n- 下拉到达设定最大值松手时，执行回调，提示正在进行更新操作\n\n\n\n## 三、案例\n\n在实际开发中，我们更多的是使用第三方库，下面以`better-scroll`进行举例：\n\nHTML结构\n\n```js\n<div id=\"position-wrapper\">\n    <div>\n        <p class=\"refresh\">下拉刷新</p >\n        <div class=\"position-list\">\n   <!--列表内容-->\n        </div>\n        <p class=\"more\">查看更多</p >\n    </div>\n</div>\n```\n\n实例化上拉下拉插件，通过`use`来注册插件\n\n```js\nimport BScroll from \"@better-scroll/core\";\nimport PullDown from \"@better-scroll/pull-down\";\nimport PullUp from '@better-scroll/pull-up';\nBScroll.use(PullDown);\nBScroll.use(PullUp);\n```\n\n实例化`BetterScroll`，并传入相关的参数\n\n```js\nlet pageNo = 1,pageSize = 10,dataList = [],isMore = true;  \nvar scroll= new BScroll(\"#position-wrapper\",{\n    scrollY:true,//垂直方向滚动\n    click:true,//默认会阻止浏览器的原生click事件，如果需要点击，这里要设为true\n    pullUpLoad:true,//上拉加载更多\n    pullDownRefresh:{\n        threshold:50,//触发pullingDown事件的位置\n        stop:0//下拉回弹后停留的位置\n    }\n});\n//监听下拉刷新\nscroll.on(\"pullingDown\",pullingDownHandler);\n//监测实时滚动\nscroll.on(\"scroll\",scrollHandler);\n//上拉加载更多\nscroll.on(\"pullingUp\",pullingUpHandler);\n\nasync function pullingDownHandler(){\n    dataList=[];\n    pageNo=1;\n    isMore=true;\n    $(\".more\").text(\"查看更多\");\n    await getlist();//请求数据\n    scroll.finishPullDown();//每次下拉结束后，需要执行这个操作\n    scroll.refresh();//当滚动区域的dom结构有变化时，需要执行这个操作\n}\nasync function pullingUpHandler(){\n    if(!isMore){\n        $(\".more\").text(\"没有更多数据了\");\n        scroll.finishPullUp();//每次上拉结束后，需要执行这个操作\n        return;\n    }\n    pageNo++;\n    await this.getlist();//请求数据\n    scroll.finishPullUp();//每次上拉结束后，需要执行这个操作\n    scroll.refresh();//当滚动区域的dom结构有变化时，需要执行这个操作    \n}\nfunction scrollHandler(){\n    if(this.y>50) $('.refresh').text(\"松手开始加载\");\n    else $('.refresh').text(\"下拉刷新\");\n}\nfunction getlist(){\n    //返回的数据\n    let result=....;\n    dataList=dataList.concat(result);\n    //判断是否已加载完\n    if(result.length<pageSize) isMore=false;\n    //将dataList渲染到html内容中\n}    \n```\n\n注意点：\n\n使用`better-scroll `实现下拉刷新、上拉加载时要注意以下几点：\n\n- `wrapper`里必须只有一个子元素\n- 子元素的高度要比`wrapper`要高\n- 使用的时候，要确定`DOM`元素是否已经生成，必须要等到`DOM`渲染完成后，再`new BScroll()`\n- 滚动区域的`DOM`元素结构有变化后，需要执行刷新 `refresh() `\n- 上拉或者下拉，结束后，需要执行`finishPullUp()`或者`finishPullDown()`，否则将不会执行下次操作\n- `better-scroll`，默认会阻止浏览器的原生`click`事件，如果滚动内容区要添加点击事件，需要在实例化属性里设置`click:true`\n\n### 小结\n\n下拉刷新、上拉加载原理本身都很简单，真正复杂的是封装过程中，要考虑的兼容性、易用性、性能等诸多细节\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000014423308\n- https://github.com/ustbhuangyi/better-scroll\n"
  },
  {
    "path": "docs/JavaScript/regexp.md",
    "content": "# 面试官：说说你对正则表达式的理解？应用场景？\n\n![](https://static.vue-js.com/55388a40-7f1d-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n正则表达式是一种用来匹配字符串的强有力的武器\n\n它的设计思想是用一种描述性的语言定义一个规则，凡是符合规则的字符串，我们就认为它“匹配”了，否则，该字符串就是不合法的\n\n在 `JavaScript`中，正则表达式也是对象，构建正则表达式有两种方式：\n\n1. 字面量创建，其由包含在斜杠之间的模式组成\n\n```js\nconst re = /\\d+/g;\n```\n\n2. 调用`RegExp`对象的构造函数\n\n```js\nconst re = new RegExp(\"\\\\d+\",\"g\");\n\nconst rul = \"\\\\d+\"\nconst re1 = new RegExp(rul,\"g\");\n```\n\n使用构建函数创建，第一个参数可以是一个变量，遇到特殊字符`\\`需要使用`\\\\`进行转义\n\n\n\n## 二、匹配规则\n\n常见的校验规则如下：\n\n| 规则        | 描述                                                  |\n| ----------- | ----------------------------------------------------- |\n| \\           | 转义                                                  |\n| ^           | 匹配输入的开始                                        |\n| $           | 匹配输入的结束                                        |\n| *           | 匹配前一个表达式 0 次或多次                           |\n| +           | 匹配前面一个表达式 1 次或者多次。等价于 `{1,}`        |\n| ?           | 匹配前面一个表达式 0 次或者 1 次。等价于`{0,1}`       |\n| .           | 默认匹配除换行符之外的任何单个字符                    |\n| x(?=y)      | 匹配'x'仅仅当'x'后面跟着'y'。这种叫做先行断言         |\n| (?<=y)x     | 匹配'x'仅当'x'前面是'y'.这种叫做后行断言              |\n| x(?!y)      | 仅仅当'x'后面不跟着'y'时匹配'x'，这被称为正向否定查找 |\n| (?<!*y*)*x* | 仅仅当'x'前面不是'y'时匹配'x'，这被称为反向否定查找   |\n| x\\|y        | 匹配‘x’或者‘y’                                        |\n| {n}         | n 是一个正整数，匹配了前面一个字符刚好出现了 n 次     |\n| {n,}        | n是一个正整数，匹配前一个字符至少出现了n次            |\n| {n,m}       | n 和 m 都是整数。匹配前面的字符至少n次，最多m次       |\n| [xyz\\]      | 一个字符集合。匹配方括号中的任意字符                  |\n| [^xyz\\]     | 匹配任何没有包含在方括号中的字符                      |\n| \\b          | 匹配一个词的边界，例如在字母和空格之间                |\n| \\B          | 匹配一个非单词边界                                    |\n| \\d          | 匹配一个数字                                          |\n| \\D          | 匹配一个非数字字符                                    |\n| \\f          | 匹配一个换页符                                        |\n| \\n          | 匹配一个换行符                                        |\n| \\r          | 匹配一个回车符                                        |\n| \\s          | 匹配一个空白字符，包括空格、制表符、换页符和换行符    |\n| \\S          | 匹配一个非空白字符                                    |\n| \\w          | 匹配一个单字字符（字母、数字或者下划线）              |\n| \\W          | 匹配一个非单字字符                                    |\n\n### 正则表达式标记\n\n| 标志 | 描述                                                      |\n| :--- | :-------------------------------------------------------- |\n| `g`  | 全局搜索。                                                |\n| `i`  | 不区分大小写搜索。                                        |\n| `m`  | 多行搜索。                                                |\n| `s`  | 允许 `.` 匹配换行符。                                     |\n| `u`  | 使用`unicode`码的模式进行匹配。                           |\n| `y`  | 执行“粘性(`sticky`)”搜索,匹配从目标字符串的当前位置开始。 |\n\n使用方法如下：\n\n```js\nvar re = /pattern/flags;\nvar re = new RegExp(\"pattern\", \"flags\");\n```\n\n在了解下正则表达式基本的之外，还可以掌握几个正则表达式的特性：\n\n### 贪婪模式\n\n在了解贪婪模式前，首先举个例子：\n\n```js\nconst reg = /ab{1,3}c/\n```\n\n在匹配过程中，尝试可能的顺序是从多往少的方向去尝试。首先会尝试`bbb`，然后再看整个正则是否能匹配。不能匹配时，吐出一个`b`，即在`bb`的基础上，再继续尝试，以此重复\n\n如果多个贪婪量词挨着，则深度优先搜索\n\n```js\nconst string = \"12345\";\nconst regx = /(\\d{1,3})(\\d{1,3})/;\nconsole.log( string.match(reg) );\n// => [\"12345\", \"123\", \"45\", index: 0, input: \"12345\"]\n```\n\n其中，前面的`\\d{1,3}`匹配的是\"123\"，后面的`\\d{1,3}`匹配的是\"45\"\n\n### 懒惰模式\n\n惰性量词就是在贪婪量词后面加个问号。表示尽可能少的匹配\n\n```js\nvar string = \"12345\";\nvar regex = /(\\d{1,3}?)(\\d{1,3})/;\nconsole.log( string.match(regex) );\n// => [\"1234\", \"1\", \"234\", index: 0, input: \"12345\"]\n```\n\n其中`\\d{1,3}?`只匹配到一个字符\"1\"，而后面的`\\d{1,3}`匹配了\"234\"\n\n### 分组\n\n分组主要是用过`()`进行实现，比如`beyond{3}`，是匹配`d`字母3次。而`(beyond){3}`是匹配`beyond`三次\n\n在`()`内使用`|`达到或的效果，如`(abc | xxx)`可以匹配`abc`或者`xxx`\n\n反向引用，巧用`$`分组捕获\n\n```js\nlet str = \"John Smith\";\n\n// 交换名字和姓氏\nconsole.log(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John\n```\n\n\n\n\n\n## 三、匹配方法\n\n正则表达式常被用于某些方法，我们可以分成两类：\n\n- 字符串（str）方法：`match`、`matchAll`、`search`、`replace`、`split`\n- 正则对象下（regexp）的方法：`test`、`exec`\n\n| 方法     | 描述                                                         |\n| :------- | :----------------------------------------------------------- |\n| exec     | 一个在字符串中执行查找匹配的RegExp方法，它返回一个数组（未匹配到则返回 null）。 |\n| test     | 一个在字符串中测试是否匹配的RegExp方法，它返回 true 或 false。 |\n| match    | 一个在字符串中执行查找匹配的String方法，它返回一个数组，在未匹配到时会返回 null。 |\n| matchAll | 一个在字符串中执行查找所有匹配的String方法，它返回一个迭代器（iterator）。 |\n| search   | 一个在字符串中测试匹配的String方法，它返回匹配到的位置索引，或者在失败时返回-1。 |\n| replace  | 一个在字符串中执行查找匹配的String方法，并且使用替换字符串替换掉匹配到的子字符串。 |\n| split    | 一个使用正则表达式或者一个固定字符串分隔一个字符串，并将分隔后的子字符串存储到数组中的 `String` 方法。 |\n\n\n\n### str.match(regexp)\n\n`str.match(regexp)` 方法在字符串 `str` 中找到匹配 `regexp` 的字符\n\n如果 `regexp` 不带有 `g` 标记，则它以数组的形式返回第一个匹配项，其中包含分组和属性 `index`（匹配项的位置）、`input`（输入字符串，等于 `str`）\n\n```js\nlet str = \"I love JavaScript\";\n\nlet result = str.match(/Java(Script)/);\n\nconsole.log( result[0] );     // JavaScript（完全匹配）\nconsole.log( result[1] );     // Script（第一个分组）\nconsole.log( result.length ); // 2\n\n// 其他信息：\nconsole.log( result.index );  // 7（匹配位置）\nconsole.log( result.input );  // I love JavaScript（源字符串）\n```\n\n如果 `regexp` 带有 `g` 标记，则它将所有匹配项的数组作为字符串返回，而不包含分组和其他详细信息\n\n```js\nlet str = \"I love JavaScript\";\n\nlet result = str.match(/Java(Script)/g);\n\nconsole.log( result[0] ); // JavaScript\nconsole.log( result.length ); // 1\n```\n\n如果没有匹配项，则无论是否带有标记 `g` ，都将返回 `null`\n\n```js\nlet str = \"I love JavaScript\";\n\nlet result = str.match(/HTML/);\n\nconsole.log(result); // null\n```\n\n\n\n### str.matchAll(regexp)\n\n返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器\n\n```js\nconst regexp = /t(e)(st(\\d?))/g;\nconst str = 'test1test2';\n\nconst array = [...str.matchAll(regexp)];\n\nconsole.log(array[0]);\n// expected output: Array [\"test1\", \"e\", \"st1\", \"1\"]\n\nconsole.log(array[1]);\n// expected output: Array [\"test2\", \"e\", \"st2\", \"2\"]\n```\n\n\n\n\n\n\n\n### str.search(regexp)\n\n返回第一个匹配项的位置，如果未找到，则返回 `-1`\n\n```js\nlet str = \"A drop of ink may make a million think\";\n\nconsole.log( str.search( /ink/i ) ); // 10（第一个匹配位置）\n```\n\n这里需要注意的是，`search` 仅查找第一个匹配项\n\n\n\n\n\n\n\n## str.replace(regexp)\n\n替换与正则表达式匹配的子串，并返回替换后的字符串。在不设置全局匹配`g`的时候，只替换第一个匹配成功的字符串片段\n\n```js\nconst reg1=/javascript/i;\nconst reg2=/javascript/ig;\nconsole.log('hello Javascript Javascript Javascript'.replace(reg1,'js'));\n//hello js Javascript Javascript\nconsole.log('hello Javascript Javascript Javascript'.replace(reg2,'js'));\n//hello js js js\n```\n\n\n\n### str.split(regexp)\n\n使用正则表达式（或子字符串）作为分隔符来分割字符串\n\n```js\nconsole.log('12, 34, 56'.split(/,\\s*/)) // 数组 ['12', '34', '56']\n```\n\n\n\n### regexp.exec(str)\n\n`regexp.exec(str)` 方法返回字符串 `str` 中的 `regexp` 匹配项，与以前的方法不同，它是在正则表达式而不是字符串上调用的\n\n根据正则表达式是否带有标志 `g`，它的行为有所不同\n\n如果没有 `g`，那么 `regexp.exec(str)` 返回的第一个匹配与 `str.match(regexp)` 完全相同\n\n如果有标记 `g`，调用 `regexp.exec(str)` 会返回第一个匹配项，并将紧随其后的位置保存在属性`regexp.lastIndex` 中。 下一次同样的调用会从位置 `regexp.lastIndex` 开始搜索，返回下一个匹配项，并将其后的位置保存在 `regexp.lastIndex` 中\n\n```js\nlet str = 'More about JavaScript at https://javascript.info';\nlet regexp = /javascript/ig;\n\nlet result;\n\nwhile (result = regexp.exec(str)) {\n  console.log( `Found ${result[0]} at position ${result.index}` );\n  // Found JavaScript at position 11\n  // Found javascript at position 33\n}\n```\n\n\n\n### regexp.test(str)\n\n查找匹配项，然后返回 `true/false` 表示是否存在\n\n```js\nlet str = \"I love JavaScript\";\n\n// 这两个测试相同\nconsole.log( /love/i.test(str) ); // true\n```\n\n\n\n\n\n## 四、应用场景\n\n通过上面的学习，我们对正则表达式有了一定的了解\n\n下面再来看看正则表达式一些案例场景：\n\n验证QQ合法性（5~15位、全是数字、不以0开头）：\n\n```js\nconst reg = /^[1-9][0-9]{4,14}$/\nconst isvalid = patrn.exec(s)\n```\n\n校验用户账号合法性（只能输入5-20个以字母开头、可带数字、“_”、“.”的字串）：\n\n```js\nvar patrn=/^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){4,19}$/;\nconst isvalid = patrn.exec(s)\n```\n\n将`url`参数解析为对象\n\n```js\nconst protocol = '(?<protocol>https?:)';\nconst host = '(?<host>(?<hostname>[^/#?:]+)(?::(?<port>\\\\d+))?)';\nconst path = '(?<pathname>(?:\\\\/[^/#?]+)*\\\\/?)';\nconst search = '(?<search>(?:\\\\?[^#]*)?)';\nconst hash = '(?<hash>(?:#.*)?)';\nconst reg = new RegExp(`^${protocol}\\/\\/${host}${path}${search}${hash}$`);\nfunction execURL(url){\n    const result = reg.exec(url);\n    if(result){\n        result.groups.port = result.groups.port || '';\n        return result.groups;\n    }\n    return {\n        protocol:'',host:'',hostname:'',port:'',\n        pathname:'',search:'',hash:'',\n    };\n}\n\nconsole.log(execURL('https://localhost:8080/?a=b#xxxx'));\nprotocol: \"https:\"\nhost: \"localhost:8080\"\nhostname: \"localhost\"\nport: \"8080\"\npathname: \"/\"\nsearch: \"?a=b\"\nhash: \"#xxxx\"\n```\n\n再将上面的`search`和`hash`进行解析\n\n```js\nfunction execUrlParams(str){\n    str = str.replace(/^[#?&]/,'');\n    const result = {};\n    if(!str){ //如果正则可能配到空字符串，极有可能造成死循环，判断很重要\n        return result; \n    }\n    const reg = /(?:^|&)([^&=]*)=?([^&]*?)(?=&|$)/y\n    let exec = reg.exec(str);\n    while(exec){\n        result[exec[1]] = exec[2];\n        exec = reg.exec(str);\n    }\n    return result;\n}\nconsole.log(execUrlParams('#'));// {}\nconsole.log(execUrlParams('##'));//{'#':''}\nconsole.log(execUrlParams('?q=3606&src=srp')); //{q: \"3606\", src: \"srp\"}\nconsole.log(execUrlParams('test=a=b=c&&==&a='));//{test: \"a=b=c\", \"\": \"=\", a: \"\"}\n```\n\n## 参考文献\n\n- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions"
  },
  {
    "path": "docs/JavaScript/scope.md",
    "content": "# 面试官：说说你对作用域链的理解\n\n ![](https://static.vue-js.com/16f614a0-718f-11eb-ab90-d9ae814b240d.png)\n\n## 一、作用域\n\n作用域，即变量（变量作用域又称上下文）和函数生效（能被访问）的区域或集合\n\n换句话说，作用域决定了代码区块中变量和其他资源的可见性\n\n举个例子\n\n```js\nfunction myFunction() {\n    let inVariable = \"函数内部变量\";\n}\nmyFunction();//要先执行这个函数，否则根本不知道里面是啥\nconsole.log(inVariable); // Uncaught ReferenceError: inVariable is not defined\n```\n\n上述例子中，函数`myFunction`内部创建一个`inVariable`变量，当我们在全局访问这个变量的时候，系统会报错\n\n这就说明我们在全局是无法获取到（闭包除外）函数内部的变量\n\n\n\n我们一般将作用域分成：\n\n- 全局作用域\n- 函数作用域\n\n- 块级作用域\n\n\n\n### 全局作用域\n\n任何不在函数中或是大括号中声明的变量，都是在全局作用域下，全局作用域下声明的变量可以在程序的任意位置访问\n\n```js\n// 全局变量\nvar greeting = 'Hello World!';\nfunction greet() {\n  console.log(greeting);\n}\n// 打印 'Hello World!'\ngreet();\n```\n\n\n### 函数作用域\n\n函数作用域也叫局部作用域，如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问，不能在函数以外去访问\n\n```js\nfunction greet() {\n  var greeting = 'Hello World!';\n  console.log(greeting);\n}\n// 打印 'Hello World!'\ngreet();\n// 报错： Uncaught ReferenceError: greeting is not defined\nconsole.log(greeting);\n```\n可见上述代码中在函数内部声明的变量或函数，在函数外部是无法访问的，这说明在函数内部定义的变量或者方法只是函数作用域\n\n\n\n### 块级作用域\n\nES6引入了`let`和`const`关键字,和`var`关键字不同，在大括号中使用`let`和`const`声明的变量存在于块级作用域中。在大括号之外不能访问这些变量\n\n```js\n{\n  // 块级作用域中的变量\n  let greeting = 'Hello World!';\n  var lang = 'English';\n  console.log(greeting); // Prints 'Hello World!'\n}\n// 变量 'English'\nconsole.log(lang);\n// 报错：Uncaught ReferenceError: greeting is not defined\nconsole.log(greeting);\n```\n\n\n\n## 二、词法作用域\n\n词法作用域，又叫静态作用域，变量被创建时就确定好了，而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了，`JavaScript` 遵循的就是词法作用域\n\n```js\nvar a = 2;\nfunction foo(){\n    console.log(a)\n}\nfunction bar(){\n    var a = 3;\n    foo();\n}\nbar()\n```\n\n上述代码改变成一张图\n\n ![](https://static.vue-js.com/29fab3d0-718f-11eb-85f6-6fac77c0c9b3.png)\n\n由于`JavaScript`遵循词法作用域，相同层级的 `foo` 和 `bar` 就没有办法访问到彼此块作用域中的变量，所以输出2\n\n\n\n## 三、作用域链\n\n当在`Javascript`中使用一个变量的时候，首先`Javascript`引擎会尝试在当前作用域下去寻找该变量，如果没找到，再到它的上层作用域寻找，以此类推直到找到该变量或是已经到了全局作用域\n\n如果在全局作用域里仍然找不到该变量，它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错\n\n这里拿《你不知道的Javascript(上)》中的一张图解释：\n\n把作用域比喻成一个建筑，这份建筑代表程序中的嵌套作用域链，第一层代表当前的执行作用域，顶层代表全局作用域\n\n ![](https://static.vue-js.com/33f9c100-718f-11eb-85f6-6fac77c0c9b3.png)\n\n变量的引用会顺着当前楼层进行查找，如果找不到，则会往上一层找，一旦到达顶层，查找的过程都会停止\n\n下面代码演示下：\n\n```js\nvar sex = '男';\nfunction person() {\n    var name = '张三';\n    function student() {\n        var age = 18;\n        console.log(name); // 张三\n        console.log(sex); // 男 \n    }\n    student();\n    console.log(age); // Uncaught ReferenceError: age is not defined\n}\nperson();\n```\n\n上述代码主要主要做了以下工作：\n\n- `student`函数内部属于最内层作用域，找不到`name`，向上一层作用域`person`函数内部找，找到了输出“张三”\n- `student`内部输出`sex`时找不到，向上一层作用域`person`函数找，还找不到继续向上一层找，即全局作用域，找到了输出“男”\n- 在`person`函数内部输出`age`时找不到，向上一层作用域找，即全局作用域，还是找不到则报错\n"
  },
  {
    "path": "docs/JavaScript/security.md",
    "content": "# 面试官：web常见的攻击方式有哪些？如何防御？\n\n ![](https://static.vue-js.com/d0892930-8d1d-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\nWeb攻击（WebAttack）是针对用户上网行为或网站服务器等设备进行攻击的行为\n\n如植入恶意代码，修改网站权限，获取网站用户隐私信息等等\n\nWeb应用程序的安全性是任何基于Web业务的重要组成部分\n\n确保Web应用程序安全十分重要，即使是代码中很小的 bug 也有可能导致隐私信息被泄露\n\n站点安全就是为保护站点不受未授权的访问、使用、修改和破坏而采取的行为或实践\n\n我们常见的Web攻击方式有\n- XSS (Cross Site Scripting) 跨站脚本攻击\n- CSRF（Cross-site request forgery）跨站请求伪造\n- SQL注入攻击\n\n\n## 二、XSS\n\nXSS，跨站脚本攻击，允许攻击者将恶意代码植入到提供给其它用户使用的页面中\n\n`XSS`涉及到三方，即攻击者、客户端与`Web`应用\n\n`XSS`的攻击目标是为了盗取存储在客户端的`cookie`或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后，攻击者甚至可以假冒合法用户与网站进行交互\n\n举个例子：\n\n一个搜索页面，根据`url`参数决定关键词的内容\n\n```html\n<input type=\"text\" value=\"<%= getParameter(\"keyword\") %>\">\n<button>搜索</button>\n<div>\n  您搜索的关键词是：<%= getParameter(\"keyword\") %>\n</div>\n```\n\n这里看似并没有问题，但是如果不按套路出牌呢？\n\n用户输入`\"><script>alert('XSS');</script>`，拼接到 HTML 中返回给浏览器。形成了如下的 HTML：\n\n```html\n<input type=\"text\" value=\"\"><script>alert('XSS');</script>\">\n<button>搜索</button>\n<div>\n  您搜索的关键词是：\"><script>alert('XSS');</script>\n</div>\n```\n\n浏览器无法分辨出 `<script>alert('XSS');</script>` 是恶意代码，因而将其执行，试想一下，如果是获取`cookie`发送对黑客服务器呢？\n\n根据攻击的来源，`XSS`攻击可以分成：\n\n- 存储型\n- 反射型\n- DOM 型\n\n\n\n### 存储型\n\n存储型 XSS 的攻击步骤：\n\n1. 攻击者将恶意代码提交到目标网站的数据库中\n2. 用户打开目标网站时，网站服务端将恶意代码从数据库取出，拼接在 HTML 中返回给浏览器\n3. 用户浏览器接收到响应后解析执行，混在其中的恶意代码也被执行\n4. 恶意代码窃取用户数据并发送到攻击者的网站，或者冒充用户的行为，调用目标网站接口执行攻击者指定的操作\n\n这种攻击常见于带有用户保存数据的网站功能，如论坛发帖、商品评论、用户私信等\n\n\n\n### 反射型 XSS\n\n反射型 XSS 的攻击步骤：\n\n1. 攻击者构造出特殊的 URL，其中包含恶意代码\n2. 用户打开带有恶意代码的 URL 时，网站服务端将恶意代码从 URL 中取出，拼接在 HTML 中返回给浏览器\n3. 用户浏览器接收到响应后解析执行，混在其中的恶意代码也被执行\n4. 恶意代码窃取用户数据并发送到攻击者的网站，或者冒充用户的行为，调用目标网站接口执行攻击者指定的操作\n\n反射型 XSS 跟存储型 XSS 的区别是：存储型 XSS 的恶意代码存在数据库里，反射型 XSS 的恶意代码存在 URL 里。\n\n反射型 XSS 漏洞常见于通过 URL 传递参数的功能，如网站搜索、跳转等。\n\n由于需要用户主动打开恶意的 URL 才能生效，攻击者往往会结合多种手段诱导用户点击。\n\nPOST 的内容也可以触发反射型 XSS，只不过其触发条件比较苛刻（需要构造表单提交页面，并引导用户点击），所以非常少见\n\n\n\n### DOM 型 XSS\n\nDOM 型 XSS 的攻击步骤：\n\n1. 攻击者构造出特殊的 URL，其中包含恶意代码\n2. 用户打开带有恶意代码的 URL\n3. 用户浏览器接收到响应后解析执行，前端 JavaScript 取出 URL 中的恶意代码并执行\n4. 恶意代码窃取用户数据并发送到攻击者的网站，或者冒充用户的行为，调用目标网站接口执行攻击者指定的操作\n\nDOM 型 XSS 跟前两种 XSS 的区别：DOM 型 XSS 攻击中，取出和执行恶意代码由浏览器端完成，属于前端 JavaScript 自身的安全漏洞，而其他两种 XSS 都属于服务端的安全漏洞\n\n\n\n### XSS的预防\n\n通过前面介绍，看到`XSS`攻击的两大要素：\n\n- 攻击者提交而恶意代码\n- 浏览器执行恶意代码\n\n针对第一个要素，我们在用户输入的过程中，过滤掉用户输入的恶劣代码，然后提交给后端，但是如果攻击者绕开前端请求，直接构造请求就不能预防了\n\n而如果在后端写入数据库前，对输入进行过滤，然后把内容给前端，但是这个内容在不同地方就会有不同显示\n\n例如：\n\n一个正常的用户输入了 `5 < 7` 这个内容，在写入数据库前，被转义，变成了 `5 < 7`\n\n在客户端中，一旦经过了 `escapeHTML()`，客户端显示的内容就变成了乱码( `5 < 7` )\n\n在前端中，不同的位置所需的编码也不同。\n\n- 当 `5 < 7` 作为 HTML 拼接页面时，可以正常显示：\n\n```html\n<div title=\"comment\">5 &lt; 7</div>\n```\n\n- 当 `5 < 7` 通过 Ajax 返回，然后赋值给 JavaScript 的变量时，前端得到的字符串就是转义后的字符。这个内容不能直接用于 Vue 等模板的展示，也不能直接用于内容长度计算。不能用于标题、alert 等\n\n\n\n可以看到，过滤并非可靠的，下面就要通过防止浏览器执行恶意代码：\n\n在使用 `.innerHTML`、`.outerHTML`、`document.write()` 时要特别小心，不要把不可信的数据作为 HTML 插到页面上，而应尽量使用 `.textContent`、`.setAttribute()` 等\n\n如果用 `Vue/React` 技术栈，并且不使用 `v-html`/`dangerouslySetInnerHTML` 功能，就在前端 `render` 阶段避免 `innerHTML`、`outerHTML` 的 XSS 隐患\n\nDOM 中的内联事件监听器，如 `location`、`onclick`、`onerror`、`onload`、`onmouseover` 等，`<a>` 标签的 `href` 属性，JavaScript 的 `eval()`、`setTimeout()`、`setInterval()` 等，都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API，很容易产生安全隐患，请务必避免\n\n```js\n<!-- 链接内包含恶意代码 -->\n< a href=\" \">1</ a>\n\n<script>\n// setTimeout()/setInterval() 中调用恶意代码\nsetTimeout(\"UNTRUSTED\")\nsetInterval(\"UNTRUSTED\")\n\n// location 调用恶意代码\nlocation.href = 'UNTRUSTED'\n\n// eval() 中调用恶意代码\neval(\"UNTRUSTED\")\n```\n\n\n\n\n\n## 三、CSRF\n\nCSRF（Cross-site request forgery）跨站请求伪造：攻击者诱导受害者进入第三方网站，在第三方网站中，向被攻击网站发送跨站请求\n\n利用受害者在被攻击网站已经获取的注册凭证，绕过后台的用户验证，达到冒充用户对被攻击的网站执行某项操作的目\n\n一个典型的CSRF攻击有着如下的流程：\n\n- 受害者登录a.com，并保留了登录凭证（Cookie）\n- 攻击者引诱受害者访问了b.com\n- b.com 向 a.com 发送了一个请求：a.com/act=xx。浏览器会默认携带a.com的Cookie\n- a.com接收到请求后，对请求进行验证，并确认是受害者的凭证，误以为是受害者自己发送的请求\n- a.com以受害者的名义执行了act=xx\n- 攻击完成，攻击者在受害者不知情的情况下，冒充受害者，让a.com执行了自己定义的操作\n\n`csrf`可以通过`get`请求，即通过访问`img`的页面后，浏览器自动访问目标地址，发送请求\n\n同样，也可以设置一个自动提交的表单发送`post`请求，如下：\n\n```js\n<form action=\"http://bank.example/withdraw\" method=POST>\n    <input type=\"hidden\" name=\"account\" value=\"xiaoming\" />\n    <input type=\"hidden\" name=\"amount\" value=\"10000\" />\n    <input type=\"hidden\" name=\"for\" value=\"hacker\" />\n</form>\n<script> document.forms[0].submit(); </script> \n```\n\n访问该页面后，表单会自动提交，相当于模拟用户完成了一次`POST`操作\n\n还有一种为使用`a`标签的，需要用户点击链接才会触发\n\n访问该页面后，表单会自动提交，相当于模拟用户完成了一次POST操作\n\n```html\n< a href=\"http://test.com/csrf/withdraw.php?amount=1000&for=hacker\" taget=\"_blank\">\n    重磅消息！！\n<a/>\n```\n\n\n\n### CSRF的特点\n\n- 攻击一般发起在第三方网站，而不是被攻击的网站。被攻击的网站无法防止攻击发生\n- 攻击利用受害者在被攻击网站的登录凭证，冒充受害者提交操作；而不是直接窃取数据\n- 整个过程攻击者并不能获取到受害者的登录凭证，仅仅是“冒用”\n- 跨站请求可以用各种方式：图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中，难以进行追踪\n\n\n\n### CSRF的预防  \n\nCSRF通常从第三方网站发起，被攻击的网站无法防止攻击发生，只能通过增强自己网站针对CSRF的防护能力来提升安全性\n\n防止`csrf`常用方案如下：\n\n- 阻止不明外域的访问\n  - 同源检测\n  - Samesite Cookie\n- 提交时要求附加本域才能获取的信息\n  - CSRF Token\n  - 双重Cookie验证\n\n\n\n这里主要讲讲`token`这种形式，流程如下：\n\n- 用户打开页面的时候，服务器需要给这个用户生成一个Token\n- 对于GET请求，Token将附在请求地址之后。对于 POST 请求来说，要在 form 的最后加上\n\n```html\n<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>\n```\n\n- 当用户从客户端得到了Token，再次提交给服务器的时候，服务器需要判断Token的有效性\n\n\n\n## 四、SQL注入\n\nSql 注入攻击，是通过将恶意的 `Sql `查询或添加语句插入到应用的输入参数中，再在后台 `Sql `服务器上解析执行进行的攻击\n\n ![](https://static.vue-js.com/ead52fa0-8d1d-11eb-85f6-6fac77c0c9b3.png)\n\n流程如下所示：\n\n- 找出SQL漏洞的注入点\n\n- 判断数据库的类型以及版本\n-  猜解用户名和密码\n- 利用工具查找Web后台管理入口\n- 入侵和破坏\n\n预防方式如下：\n\n- 严格检查输入变量的类型和格式\n- 过滤和转义特殊字符\n- 对访问数据库的Web应用程序采用Web应用防火墙\n\n上述只是列举了常见的`web`攻击方式，实际开发过程中还会遇到很多安全问题，对于这些问题， 切记不可忽视\n\n\n## 参考文献\n\n- https://tech.meituan.com/2018/09/27/fe-security.html\n- https://developer.mozilla.org/zh-CN/docs/learn/Server-side/First_steps/Website_security\n"
  },
  {
    "path": "docs/JavaScript/single_sign.md",
    "content": "# 面试官：什么是单点登录？如何实现？\n\n ![](https://static.vue-js.com/8a25a760-8c83-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n单点登录（Single Sign On），简称为 SSO，是目前比较流行的企业业务整合的解决方案之一\n\nSSO的定义是在多个应用系统中，用户只需要登录一次就可以访问所有相互信任的应用系统\n\nSSO 一般都需要一个独立的认证中心（passport），子系统的登录均得通过`passport`，子系统本身将不参与登录操作\n\n当一个系统成功登录以后，`passport`将会颁发一个令牌给各个子系统，子系统可以拿着令牌会获取各自的受保护资源，为了减少频繁认证，各个子系统在被`passport`授权以后，会建立一个局部会话，在一定时间内可以无需再次向`passport`发起认证\n\n ![](https://static.vue-js.com/2b9b0e70-8c4b-11eb-85f6-6fac77c0c9b3.png)\n\n上图有四个系统，分别是`Application1`、`Application2`、`Application3`、和`SSO`，当`Application1`、`Application2`、`Application3`需要登录时，将跳到`SSO`系统，`SSO`系统完成登录，其他的应用系统也就随之登录了\n\n#### 举个例子\n\n淘宝、天猫都属于阿里旗下，当用户登录淘宝后，再打开天猫，系统便自动帮用户登录了天猫，这种现象就属于单点登录\n\n\n## 二、如何实现\n\n### 同域名下的单点登录\n\n`cookie`的`domain`属性设置为当前域的父域，并且父域的`cookie`会被子域所共享。`path`属性默认为`web`应用的上下文路径\n\n利用 `Cookie` 的这个特点，没错，我们只需要将` Cookie `的` domain`属性设置为父域的域名（主域名），同时将 `Cookie `的` path `属性设置为根路径，将 `Session ID`（或 `Token`）保存到父域中。这样所有的子域应用就都可以访问到这个` Cookie `\n\n不过这要求应用系统的域名需建立在一个共同的主域名之下，如 `tieba.baidu.com` 和 `map.baidu.com`，它们都建立在 `baidu.com `这个主域名之下，那么它们就可以通过这种方式来实现单点登录\n\n\n\n### 不同域名下的单点登录(一)\n\n如果是不同域的情况下，`Cookie`是不共享的，这里我们可以部署一个认证中心，用于专门处理登录请求的独立的 `Web `服务\n\n用户统一在认证中心进行登录，登录成功后，认证中心记录用户的登录状态，并将 `token` 写入 `Cookie`（注意这个 `Cookie `是认证中心的，应用系统是访问不到的）\n\n应用系统检查当前请求有没有 `Token`，如果没有，说明用户在当前系统中尚未登录，那么就将页面跳转至认证中心\n\n由于这个操作会将认证中心的 `Cookie` 自动带过去，因此，认证中心能够根据 `Cookie` 知道用户是否已经登录过了\n\n如果认证中心发现用户尚未登录，则返回登录页面，等待用户登录\n\n如果发现用户已经登录过了，就不会让用户再次登录了，而是会跳转回目标 `URL `，并在跳转前生成一个 `Token`，拼接在目标` URL` 的后面，回传给目标应用系统\n\n应用系统拿到 `Token `之后，还需要向认证中心确认下 `Token` 的合法性，防止用户伪造。确认无误后，应用系统记录用户的登录状态，并将 `Token `写入` Cookie`，然后给本次访问放行。（注意这个 `Cookie` 是当前应用系统的）当用户再次访问当前应用系统时，就会自动带上这个 `Token`，应用系统验证 Token 发现用户已登录，于是就不会有认证中心什么事了\n\n此种实现方式相对复杂，支持跨域，扩展性好，是单点登录的标准做法\n\n\n\n### 不同域名下的单点登录(二)\n\n可以选择将 `Session ID` （或 `Token` ）保存到浏览器的 `LocalStorage` 中，让前端在每次向后端发送请求时，主动将` LocalStorage `的数据传递给服务端\n\n这些都是由前端来控制的，后端需要做的仅仅是在用户登录成功后，将 `Session ID `（或 `Token `）放在响应体中传递给前端\n\n单点登录完全可以在前端实现。前端拿到 `Session ID `（或 `Token` ）后，除了将它写入自己的 `LocalStorage` 中之外，还可以通过特殊手段将它写入多个其他域下的 `LocalStorage` 中\n\n关键代码如下：\n\n```js\n// 获取 token\nvar token = result.data.token;\n \n// 动态创建一个不可见的iframe，在iframe中加载一个跨域HTML\nvar iframe = document.createElement(\"iframe\");\niframe.src = \"http://app1.com/localstorage.html\";\ndocument.body.append(iframe);\n// 使用postMessage()方法将token传递给iframe\nsetTimeout(function () {\n    iframe.contentWindow.postMessage(token, \"http://app1.com\");\n}, 4000);\nsetTimeout(function () {\n    iframe.remove();\n}, 6000);\n \n// 在这个iframe所加载的HTML中绑定一个事件监听器，当事件被触发时，把接收到的token数据写入localStorage\nwindow.addEventListener('message', function (event) {\n    localStorage.setItem('token', event.data)\n}, false);\n```\n\n前端通过 `iframe`+`postMessage()` 方式，将同一份 `Token` 写入到了多个域下的 `LocalStorage` 中，前端每次在向后端发送请求之前，都会主动从 `LocalStorage` 中读取` Token `并在请求中携带，这样就实现了同一份` Token` 被多个域所共享\n\n此种实现方式完全由前端控制，几乎不需要后端参与，同样支持跨域\n\n\n\n## 三、流程\n\n单点登录的流程图如下所示：\n\n ![](https://static.vue-js.com/2422bc40-8c84-11eb-ab90-d9ae814b240d.png)\n\n- 用户访问系统1的受保护资源，系统1发现用户未登录，跳转至sso认证中心，并将自己的地址作为参数\n\n- sso认证中心发现用户未登录，将用户引导至登录页面\n- 用户输入用户名密码提交登录申请\n- sso认证中心校验用户信息，创建用户与sso认证中心之间的会话，称为全局会话，同时创建授权令牌\n- sso认证中心带着令牌跳转会最初的请求地址（系统1）\n- 系统1拿到令牌，去sso认证中心校验令牌是否有效\n- sso认证中心校验令牌，返回有效，注册系统1\n- 系统1使用该令牌创建与用户的会话，称为局部会话，返回受保护资源\n- 用户访问系统2的受保护资源\n- 系统2发现用户未登录，跳转至sso认证中心，并将自己的地址作为参数\n- sso认证中心发现用户已登录，跳转回系统2的地址，并附上令牌\n- 系统2拿到令牌，去sso认证中心校验令牌是否有效\n- sso认证中心校验令牌，返回有效，注册系统2\n- 系统2使用该令牌创建与用户的局部会话，返回受保护资源\n\n用户登录成功之后，会与`sso`认证中心及各个子系统建立会话，用户与`sso`认证中心建立的会话称为全局会话\n\n用户与各个子系统建立的会话称为局部会话，局部会话建立之后，用户访问子系统受保护资源将不再通过`sso`认证中心\n\n全局会话与局部会话有如下约束关系：\n\n- 局部会话存在，全局会话一定存在\n- 全局会话存在，局部会话不一定存在\n- 全局会话销毁，局部会话必须销毁\n\n\n\n## 参考文献\n\n- https://blog.csdn.net/weixin_36380516/article/details/109006828\n- https://baike.baidu.com/item/%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95\n- https://juejin.cn/post/6844903664985866253\n"
  },
  {
    "path": "docs/JavaScript/string_api.md",
    "content": "# 面试官：JavaScript字符串的常用方法有哪些？\n\n ![](https://static.vue-js.com/ceb6ebc0-65c1-11eb-ab90-d9ae814b240d.png)\n\n## 一、操作方法\n\n我们也可将字符串常用的操作方法归纳为增、删、改、查，需要知道字符串的特点是一旦创建了，就不可变\n\n\n\n### 增\n\n这里增的意思并不是说直接增添内容，而是创建字符串的一个副本，再进行操作\n\n除了常用`+`以及`${}`进行字符串拼接之外，还可通过`concat`\n\n#### concat\n\n用于将一个或多个字符串拼接成一个新字符串\n\n```js\nlet stringValue = \"hello \";\nlet result = stringValue.concat(\"world\");\nconsole.log(result); // \"hello world\"\nconsole.log(stringValue); // \"hello\"\n```\n\n\n\n### 删\n\n这里的删的意思并不是说删除原字符串的内容，而是创建字符串的一个副本，再进行操作\n\n常见的有：\n\n- slice()\n- substr()\n- substring()\n\n这三个方法都返回调用它们的字符串的一个子字符串，而且都接收一或两个参数。\n\n```js\nlet stringValue = \"hello world\";\nconsole.log(stringValue.slice(3)); // \"lo world\"\nconsole.log(stringValue.substring(3)); // \"lo world\"\nconsole.log(stringValue.substr(3)); // \"lo world\"\nconsole.log(stringValue.slice(3, 7)); // \"lo w\"\nconsole.log(stringValue.substring(3,7)); // \"lo w\"\nconsole.log(stringValue.substr(3, 7)); // \"lo worl\"\n```\n\n\n\n### 改\n\n这里改的意思也不是改变原字符串，而是创建字符串的一个副本，再进行操作\n\n常见的有：\n\n- trim()、trimLeft()、trimRight()\n\n- repeat()\n- padStart()、padEnd()\n- toLowerCase()、 toUpperCase()\n\n\n\n#### trim()、trimLeft()、trimRight()\n\n删除前、后或前后所有空格符，再返回新的字符串\n\n```js\nlet stringValue = \" hello world \";\nlet trimmedStringValue = stringValue.trim();\nconsole.log(stringValue); // \" hello world \"\nconsole.log(trimmedStringValue); // \"hello world\"\n```\n\n\n\n#### repeat()\n\n接收一个整数参数，表示要将字符串复制多少次，然后返回拼接所有副本后的结果\n\n```js\nlet stringValue = \"na \";\nlet copyResult = stringValue.repeat(2) // na na \n```\n\n\n\n#### padEnd()\n\n复制字符串，如果小于指定长度，则在相应一边填充字符，直至满足长度条件\n\n```js\nlet stringValue = \"foo\";\nconsole.log(stringValue.padStart(6)); // \" foo\"\nconsole.log(stringValue.padStart(9, \".\")); // \"......foo\"\n```\n\n\n\n### toLowerCase()、 toUpperCase()\n\n大小写转化\n\n```js\nlet stringValue = \"hello world\";\nconsole.log(stringValue.toUpperCase()); // \"HELLO WORLD\"\nconsole.log(stringValue.toLowerCase()); // \"hello world\"\n```\n\n\n\n### 查\n\n除了通过索引的方式获取字符串的值，还可通过：\n\n- chatAt()\n\n- indexOf()\n\n- startWith()\n\n- includes()\n\n  \n\n#### charAt()\n\n返回给定索引位置的字符，由传给方法的整数参数指定\n\n```js\nlet message = \"abcde\";\nconsole.log(message.charAt(2)); // \"c\"\n```\n\n\n\n#### indexOf()\n\n从字符串开头去搜索传入的字符串，并返回位置（如果没找到，则返回 -1 ）\n\n```js\nlet stringValue = \"hello world\";\nconsole.log(stringValue.indexOf(\"o\")); // 4\n```\n\n\n\n#### startWith()、includes()\n\n从字符串中搜索传入的字符串，并返回一个表示是否包含的布尔值\n\n```js\nlet message = \"foobarbaz\";\nconsole.log(message.startsWith(\"foo\")); // true\nconsole.log(message.startsWith(\"bar\")); // false\nconsole.log(message.includes(\"bar\")); // true\nconsole.log(message.includes(\"qux\")); // false\n```\n\n\n\n\n\n## 二、转换方法\n\n### split\n\n把字符串按照指定的分割符，拆分成数组中的每一项\n\n```js\nlet str = \"12+23+34\"\nlet arr = str.split(\"+\") // [12,23,34]\n```\n\n\n\n## 三、模板匹配方法\n\n针对正则表达式，字符串设计了几个方法：\n\n- match()\n- search()\n- replace()\n\n\n\n### match()\n\n接收一个参数，可以是一个正则表达式字符串，也可以是一个` RegExp `对象，返回数组\n\n```js\nlet text = \"cat, bat, sat, fat\";\nlet pattern = /.at/;\nlet matches = text.match(pattern);\nconsole.log(matches[0]); // \"cat\"\n```\n\n\n\n### search()\n\n接收一个参数，可以是一个正则表达式字符串，也可以是一个` RegExp `对象，找到则返回匹配索引，否则返回 -1\n\n```js\nlet text = \"cat, bat, sat, fat\";\nlet pos = text.search(/at/);\nconsole.log(pos); // 1\n```\n\n\n\n### replace()\n\n接收两个参数，第一个参数为匹配的内容，第二个参数为替换的元素（可用函数）\n\n```js\nlet text = \"cat, bat, sat, fat\";\nlet result = text.replace(\"at\", \"ond\");\nconsole.log(result); // \"cond, bat, sat, fat\"\n```"
  },
  {
    "path": "docs/JavaScript/tail_recursion.md",
    "content": "# 面试官：举例说明你对尾递归的理解，有哪些应用场景\n\n ![](https://static.vue-js.com/74db8fe0-815d-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、递归\n\n递归（英语：Recursion）\n\n在数学与计算机科学中，是指在函数的定义中使用函数自身的方法\n\n在函数内部，可以调用其他函数。如果一个函数在内部调用自身本身，这个函数就是递归函数\n\n其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解\n\n一般来说，递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时，递归前进；当边界条件满足时，递归返回\n\n下面实现一个函数 `pow(x, n)`，它可以计算 `x` 的 `n` 次方\n\n使用迭代的方式，如下：\n\n```js\nfunction pow(x, n) {\n  let result = 1;\n\n  // 再循环中，用 x 乘以 result n 次\n  for (let i = 0; i < n; i++) {\n    result *= x;\n  }\n  return result;\n}\n```\n\n使用递归的方式，如下：\n\n```js\nfunction pow(x, n) {\n  if (n == 1) {\n    return x;\n  } else {\n    return x * pow(x, n - 1);\n  }\n}\n```\n\n`pow(x, n)` 被调用时，执行分为两个分支：\n\n```js\n             if n==1  = x\n             /\npow(x, n) =\n             \\\n              else     = x * pow(x, n - 1)\n```\n\n也就是说`pow` 递归地调用自身 直到 `n == 1`\n\n ![](https://static.vue-js.com/8002c960-815d-11eb-ab90-d9ae814b240d.png)\n\n为了计算 `pow(2, 4)`，递归变体经过了下面几个步骤：\n\n1. `pow(2, 4) = 2 * pow(2, 3)`\n2. `pow(2, 3) = 2 * pow(2, 2)`\n3. `pow(2, 2) = 2 * pow(2, 1)`\n4. `pow(2, 1) = 2`\n\n因此，递归将函数调用简化为一个更简单的函数调用，然后再将其简化为一个更简单的函数，以此类推，直到结果\n\n\n\n## 二、尾递归\n\n尾递归，即在函数尾位置调用自身（或是一个尾调用本身的其他函数等等）。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用，即在尾部直接调用自身的递归函数\n\n尾递归在普通尾调用的基础上，多出了2个特征：\n\n- 在尾部调用的是函数自身\n- 可通过优化，使得计算仅占用常量栈空间\n\n在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储，递归次数过多容易造成栈溢出\n\n这时候，我们就可以使用尾递归，即一个函数中所有递归形式的调用都出现在函数的末尾，对于尾递归来说，由于只存在一个调用记录，所以永远不会发生\"栈溢出\"错误\n\n实现一下阶乘，如果用普通的递归，如下：\n\n```js\nfunction factorial(n) {\n  if (n === 1) return 1;\n  return n * factorial(n - 1);\n}\n\nfactorial(5) // 120\n```\n\n如果`n`等于5，这个方法要执行5次，才返回最终的计算表达式，这样每次都要保存这个方法，就容易造成栈溢出，复杂度为`O(n)`\n\n如果我们使用尾递归，则如下：\n\n```js\nfunction factorial(n, total) {\n  if (n === 1) return total;\n  return factorial(n - 1, n * total);\n}\n\nfactorial(5, 1) // 120\n```\n\n可以看到，每一次返回的就是一个新的函数，不带上一个函数的参数，也就不需要储存上一个函数了。尾递归只需要保存一个调用栈，复杂度 O(1)\n\n\n\n## 二、应用场景\n\n数组求和\n\n```js\nfunction sumArray(arr, total) {\n    if(arr.length === 1) {\n        return total\n    }\n    return sum(arr, total + arr.pop())\n}\n```\n\n使用尾递归优化求斐波那契数列\n\n```js\nfunction factorial2 (n, start = 1, total = 1) {\n    if(n <= 2){\n        return total\n    }\n    return factorial2 (n -1, total, total + start)\n}\n```\n\n数组扁平化\n\n```js\nlet a = [1,2,3, [1,2,3, [1,2,3]]]\n// 变成\nlet a = [1,2,3,1,2,3,1,2,3]\n// 具体实现\nfunction flat(arr = [], result = []) {\n    arr.forEach(v => {\n        if(Array.isArray(v)) {\n            result = result.concat(flat(v, []))\n        }else {\n            result.push(v)\n        }\n    })\n    return result\n}\n```\n\n数组对象格式化\n\n```js\nlet obj = {\n    a: '1',\n    b: {\n        c: '2',\n        D: {\n            E: '3'\n        }\n    }\n}\n// 转化为如下：\nlet obj = {\n    a: '1',\n    b: {\n        c: '2',\n        d: {\n            e: '3'\n        }\n    }\n}\n\n// 代码实现\nfunction keysLower(obj) {\n    let reg = new RegExp(\"([A-Z]+)\", \"g\");\n    for (let key in obj) {\n        if (obj.hasOwnProperty(key)) {\n            let temp = obj[key];\n            if (reg.test(key.toString())) {\n                // 将修改后的属性名重新赋值给temp，并在对象obj内添加一个转换后的属性\n                temp = obj[key.replace(reg, function (result) {\n                    return result.toLowerCase()\n                })] = obj[key];\n                // 将之前大写的键属性删除\n                delete obj[key];\n            }\n            // 如果属性是对象或者数组，重新执行函数\n            if (typeof temp === 'object' || Object.prototype.toString.call(temp) === '[object Array]') {\n                keysLower(temp);\n            }\n        }\n    }\n    return obj;\n};\n```\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/%E5%B0%BE%E8%B0%83%E7%94%A8\n"
  },
  {
    "path": "docs/JavaScript/this.md",
    "content": "# 面试官：谈谈this对象的理解\n\n ![](https://static.vue-js.com/46c820d0-74b7-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、定义\n\n函数的 `this` 关键字在 `JavaScript` 中的表现略有不同，此外，在严格模式和非严格模式之间也会有一些差别\n\n在绝大多数情况下，函数的调用方式决定了 `this` 的值（运行时绑定）\n\n`this` 关键字是函数运行时自动生成的一个内部对象，只能在函数内部使用，总指向调用它的对象\n\n举个例子：\n\n```js\nfunction baz() {\n    // 当前调用栈是：baz\n    // 因此，当前调用位置是全局作用域\n    \n    console.log( \"baz\" );\n    bar(); // <-- bar的调用位置\n}\n\nfunction bar() {\n    // 当前调用栈是：baz --> bar\n    // 因此，当前调用位置在baz中\n    \n    console.log( \"bar\" );\n    foo(); // <-- foo的调用位置\n}\n\nfunction foo() {\n    // 当前调用栈是：baz --> bar --> foo\n    // 因此，当前调用位置在bar中\n    \n    console.log( \"foo\" );\n}\n\nbaz(); // <-- baz的调用位置\n```\n\n同时，`this`在函数执行过程中，`this`一旦被确定了，就不可以再更改\n\n```js\nvar a = 10;\nvar obj = {\n  a: 20\n}\n\nfunction fn() {\n  this = obj; // 修改this，运行后会报错\n  console.log(this.a);\n}\n\nfn();\n```\n\n\n\n\n\n## 二、绑定规则\n\n根据不同的使用场合，`this`有不同的值，主要分为下面几种情况：\n\n- 默认绑定\n- 隐式绑定\n- new绑定\n\n- 显示绑定\n\n\n\n### 默认绑定\n\n全局环境中定义`person`函数，内部使用`this`关键字\n\n```js\nvar name = 'Jenny';\nfunction person() {\n    return this.name;\n}\nconsole.log(person());  //Jenny\n```\n\n上述代码输出`Jenny`，原因是调用函数的对象在游览器中位`window`，因此`this`指向`window`，所以输出`Jenny`\n\n注意：\n\n严格模式下，不能将全局对象用于默认绑定，this会绑定到`undefined`，只有函数运行在非严格模式下，默认绑定才能绑定到全局对象\n\n\n\n### 隐式绑定\n\n函数还可以作为某个对象的方法调用，这时`this`就指这个上级对象\n\n```js\nfunction test() {\n  console.log(this.x);\n}\n\nvar obj = {};\nobj.x = 1;\nobj.m = test;\n\nobj.m(); // 1\n```\n\n这个函数中包含多个对象，尽管这个函数是被最外层的对象所调用，`this`指向的也只是它上一级的对象\n\n```js\nvar o = {\n    a:10,\n    b:{\n        fn:function(){\n            console.log(this.a); //undefined\n        }\n    }\n}\no.b.fn();\n```\n\n上述代码中，`this`的上一级对象为`b`，`b`内部并没有`a`变量的定义，所以输出`undefined`\n\n这里再举一种特殊情况\n\n```js\nvar o = {\n    a:10,\n    b:{\n        a:12,\n        fn:function(){\n            console.log(this.a); //undefined\n            console.log(this); //window\n        }\n    }\n}\nvar j = o.b.fn;\nj();\n```\n\n此时`this`指向的是`window`，这里的大家需要记住，`this`永远指向的是最后调用它的对象，虽然`fn`是对象`b`的方法，但是`fn`赋值给`j`时候并没有执行，所以最终指向`window`\n\n\n\n### new绑定\n\n通过构建函数`new`关键字生成一个实例对象，此时`this`指向这个实例对象\n\n```js\nfunction test() {\n　this.x = 1;\n}\n\nvar obj = new test();\nobj.x // 1\n```\n\n上述代码之所以能过输出1，是因为`new`关键字改变了`this`的指向\n\n这里再列举一些特殊情况：\n\n`new`过程遇到`return`一个对象，此时`this`指向为返回的对象\n\n```js\nfunction fn()  \n{  \n    this.user = 'xxx';  \n    return {};  \n}\nvar a = new fn();  \nconsole.log(a.user); //undefined\n```\n\n如果返回一个简单类型的时候，则`this`指向实例对象\n\n```js\nfunction fn()  \n{  \n    this.user = 'xxx';  \n    return 1;\n}\nvar a = new fn;  \nconsole.log(a.user); //xxx\n```\n\n注意的是`null`虽然也是对象，但是此时`new`仍然指向实例对象\n\n```js\nfunction fn()  \n{  \n    this.user = 'xxx';  \n    return null;\n}\nvar a = new fn;  \nconsole.log(a.user); //xxx\n```\n\n\n\n### 显示修改\n\n`apply()、call()、bind()`是函数的一个方法，作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此，这时`this`指的就是这第一个参数\n\n```js\nvar x = 0;\nfunction test() {\n　console.log(this.x);\n}\n\nvar obj = {};\nobj.x = 1;\nobj.m = test;\nobj.m.apply(obj) // 1\n```\n\n关于`apply、call、bind`三者的区别，我们后面再详细说\n\n\n## 三、箭头函数\n\n在 ES6 的语法中还提供了箭头函语法，让我们在代码书写时就能确定 `this` 的指向（编译时绑定）\n\n举个例子：\n\n```js\nconst obj = {\n  sayThis: () => {\n    console.log(this);\n  }\n};\n\nobj.sayThis(); // window 因为 JavaScript 没有块作用域，所以在定义 sayThis 的时候，里面的 this 就绑到 window 上去了\nconst globalSay = obj.sayThis;\nglobalSay(); // window 浏览器中的 global 对象\n```\n\n虽然箭头函数的`this`能够在编译的时候就确定了`this`的指向，但也需要注意一些潜在的坑\n\n下面举个例子：\n\n绑定事件监听\n\n```js\nconst button = document.getElementById('mngb');\nbutton.addEventListener('click', ()=> {\n    console.log(this === window) // true\n    this.innerHTML = 'clicked button'\n})\n```\n\n上述可以看到，我们其实是想要`this`为点击的`button`，但此时`this`指向了`window`\n\n包括在原型上添加方法时候，此时`this`指向`window`\n\n```js\nCat.prototype.sayName = () => {\n    console.log(this === window) //true\n    return this.name\n}\nconst cat = new Cat('mm');\ncat.sayName()\n```\n\n同样的，箭头函数不能作为构建函数\n\n\n\n## 四、优先级\n\n### 隐式绑定 VS 显式绑定\n\n```js\nfunction foo() {\n    console.log( this.a );\n}\n\nvar obj1 = {\n    a: 2,\n    foo: foo\n};\n\nvar obj2 = {\n    a: 3,\n    foo: foo\n};\n\nobj1.foo(); // 2\nobj2.foo(); // 3\n\nobj1.foo.call( obj2 ); // 3\nobj2.foo.call( obj1 ); // 2\n```\n\n显然，显示绑定的优先级更高\n\n### new绑定 VS 隐式绑定\n\n```js\nfunction foo(something) {\n    this.a = something;\n}\n\nvar obj1 = {\n    foo: foo\n};\n\nvar obj2 = {};\n\nobj1.foo( 2 );\nconsole.log( obj1.a ); // 2\n\nobj1.foo.call( obj2, 3 );\nconsole.log( obj2.a ); // 3\n\nvar bar = new obj1.foo( 4 );\nconsole.log( obj1.a ); // 2\nconsole.log( bar.a ); // 4\n```\n\n可以看到，new绑定的优先级`>`隐式绑定\n\n### `new`绑定 VS 显式绑定\n\n因为`new`和`apply、call`无法一起使用，但硬绑定也是显式绑定的一种，可以替换测试\n\n```js\nfunction foo(something) {\n    this.a = something;\n}\n\nvar obj1 = {};\n\nvar bar = foo.bind( obj1 );\nbar( 2 );\nconsole.log( obj1.a ); // 2\n\nvar baz = new bar( 3 );\nconsole.log( obj1.a ); // 2\nconsole.log( baz.a ); // 3\n```\n\n`bar`被绑定到obj1上，但是`new bar(3)` 并没有像我们预计的那样把`obj1.a`修改为3。但是，`new`修改了绑定调用`bar()`中的`this`\n\n我们可认为`new`绑定优先级`>`显式绑定\n\n综上，new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级\n\n\n\n## 相关链接\n\n- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this"
  },
  {
    "path": "docs/JavaScript/type_conversion.md",
    "content": "# 面试官：谈谈 JavaScript 中的类型转换机制\n\n ![](https://static.vue-js.com/2abd00a0-6692-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、概述\n\n前面我们讲到，`JS `中有六种简单数据类型：`undefined`、`null`、`boolean`、`string`、`number`、`symbol`，以及引用类型：`object`\n\n但是我们在声明的时候只有一种数据类型，只有到运行期间才会确定当前类型\n\n```js\nlet x = y ? 1 : a;\n```\n\n上面代码中，`x`的值在编译阶段是无法获取的，只有等到程序运行时才能知道\n\n虽然变量的数据类型是不确定的，但是各种运算符对数据类型是有要求的，如果运算子的类型与预期不符合，就会触发类型转换机制\n\n常见的类型转换有：\n\n- 强制转换（显示转换）\n- 自动转换（隐式转换）\n\n\n\n## 二、显示转换\n\n显示转换，即我们很清楚可以看到这里发生了类型的转变，常见的方法有：\n\n- Number()\n- parseInt()\n- String()\n- Boolean()\n\n\n\n### Number()\n\n将任意类型的值转化为数值\n\n先给出类型转换规则：\n\n ![](https://static.vue-js.com/915b7300-6692-11eb-ab90-d9ae814b240d.png)\n\n实践一下：\n\n```js\nNumber(324) // 324\n\n// 字符串：如果可以被解析为数值，则转换为相应的数值\nNumber('324') // 324\n\n// 字符串：如果不可以被解析为数值，返回 NaN\nNumber('324abc') // NaN\n\n// 空字符串转为0\nNumber('') // 0\n\n// 布尔值：true 转成 1，false 转成 0\nNumber(true) // 1\nNumber(false) // 0\n\n// undefined：转成 NaN\nNumber(undefined) // NaN\n\n// null：转成0\nNumber(null) // 0\n\n// 对象：通常转换成NaN(除了只包含单个数值的数组)\nNumber({a: 1}) // NaN\nNumber([1, 2, 3]) // NaN\nNumber([5]) // 5\n```\n\n从上面可以看到，`Number`转换的时候是很严格的，只要有一个字符无法转成数值，整个字符串就会被转为`NaN`\n\n\n\n### parseInt()\n\n`parseInt`相比`Number`，就没那么严格了，`parseInt`函数逐个解析字符，遇到不能转换的字符就停下来\n\n```js\nparseInt('32a3') //32\n```\n\n\n\n### String()\n\n可以将任意类型的值转化成字符串\n\n给出转换规则图：\n\n   ![](https://static.vue-js.com/48dd8eb0-6692-11eb-85f6-6fac77c0c9b3.png)\n\n实践一下：\n\n```js\n// 数值：转为相应的字符串\nString(1) // \"1\"\n\n//字符串：转换后还是原来的值\nString(\"a\") // \"a\"\n\n//布尔值：true转为字符串\"true\"，false转为字符串\"false\"\nString(true) // \"true\"\n\n//undefined：转为字符串\"undefined\"\nString(undefined) // \"undefined\"\n\n//null：转为字符串\"null\"\nString(null) // \"null\"\n\n//对象\nString({a: 1}) // \"[object Object]\"\nString([1, 2, 3]) // \"1,2,3\"\n```\n\n\n\n### Boolean()\n\n可以将任意类型的值转为布尔值，转换规则如下：\n\n ![](https://static.vue-js.com/53bdad10-6692-11eb-ab90-d9ae814b240d.png)\n\n实践一下：\n\n```js\nBoolean(undefined) // false\nBoolean(null) // false\nBoolean(0) // false\nBoolean(NaN) // false\nBoolean('') // false\nBoolean({}) // true\nBoolean([]) // true\nBoolean(new Boolean(false)) // true\n```\n\n\n\n## 三、隐式转换\n\n在隐式转换中，我们可能最大的疑惑是 ：何时发生隐式转换？\n\n我们这里可以归纳为两种情况发生隐式转换的场景：\n\n- 比较运算（`==`、`!=`、`>`、`<`）、`if`、`while`需要布尔值地方\n- 算术运算（`+`、`-`、`*`、`/`、`%`）\n\n除了上面的场景，还要求运算符两边的操作数不是同一类型\n\n\n\n### 自动转换为布尔值\n\n在需要布尔值的地方，就会将非布尔值的参数自动转为布尔值，系统内部会调用`Boolean`函数\n\n可以得出个小结：\n\n- undefined \n- null \n- false \n- +0 \n- -0\n-  NaN\n-  \"\"\n\n除了上面几种会被转化成`false`，其他都换被转化成`true`\n\n\n\n### 自动转换成字符串\n\n遇到预期为字符串的地方，就会将非字符串的值自动转为字符串\n\n具体规则是：先将复合类型的值转为原始类型的值，再将原始类型的值转为字符串\n\n常发生在`+`运算中，一旦存在字符串，则会进行字符串拼接操作\n\n```js\n'5' + 1 // '51'\n'5' + true // \"5true\"\n'5' + false // \"5false\"\n'5' + {} // \"5[object Object]\"\n'5' + [] // \"5\"\n'5' + function (){} // \"5function (){}\"\n'5' + undefined // \"5undefined\"\n'5' + null // \"5null\"\n```\n\n\n\n### 自动转换成数值\n\n除了`+`有可能把运算子转为字符串，其他运算符都会把运算子自动转成数值\n\n```js\n'5' - '2' // 3\n'5' * '2' // 10\ntrue - 1  // 0\nfalse - 1 // -1\n'1' - 1   // 0\n'5' * []    // 0\nfalse / '5' // 0\n'abc' - 1   // NaN\nnull + 1 // 1\nundefined + 1 // NaN\n```\n\n`null`转为数值时，值为`0` 。`undefined`转为数值时，值为`NaN`"
  },
  {
    "path": "docs/JavaScript/typeof_instanceof.md",
    "content": "# 面试官：typeof 与 instanceof 区别\n\n ![](https://static.vue-js.com/3fc158f0-7710-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、typeof\n\n`typeof` 操作符返回一个字符串，表示未经计算的操作数的类型\n\n使用方法如下：\n\n```js\ntypeof operand\ntypeof(operand)\n```\n\n`operand`表示对象或原始值的表达式，其类型将被返回\n\n举个例子\n\n```js\ntypeof 1 // 'number'\ntypeof '1' // 'string'\ntypeof undefined // 'undefined'\ntypeof true // 'boolean'\ntypeof Symbol() // 'symbol'\ntypeof null // 'object'\ntypeof [] // 'object'\ntypeof {} // 'object'\ntypeof console // 'object'\ntypeof console.log // 'function'\n```\n\n从上面例子，前6个都是基础数据类型。虽然`typeof null`为`object`，但这只是` JavaScript` 存在的一个悠久 `Bug`，不代表`null `就是引用数据类型，并且`null `本身也不是对象\n\n所以，`null `在 `typeof `之后返回的是有问题的结果，不能作为判断` null `的方法。如果你需要在 `if` 语句中判断是否为 `null`，直接通过`===null`来判断就好\n\n同时，可以发现引用类型数据，用`typeof`来判断的话，除了`function`会被识别出来之外，其余的都输出`object`\n\n如果我们想要判断一个变量是否存在，可以使用`typeof`：(不能使用`if(a)`， 若`a`未声明，则报错)\n\n```js\nif(typeof a != 'undefined'){\n    //变量存在\n}\n```\n\n\n\n## 二、instanceof\n\n`instanceof` 运算符用于检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上\n\n使用如下：\n\n```js\nobject instanceof constructor\n```\n\n`object`为实例对象，`constructor`为构造函数\n\n构造函数通过`new`可以实例对象，`instanceof `能判断这个对象是否是之前那个构造函数生成的对象\n\n```js\n// 定义构建函数\nlet Car = function() {}\nlet benz = new Car()\nbenz instanceof Car // true\nlet car = new String('xxx')\ncar instanceof String // true\nlet str = 'xxx'\nstr instanceof String // false\n```\n\n关于`instanceof`的实现原理，可以参考下面：\n\n```js\nfunction myInstanceof(left, right) {\n    // 这里先用typeof来判断基础数据类型，如果是，直接返回false\n    if(typeof left !== 'object' || left === null) return false;\n    // getProtypeOf是Object对象自带的API，能够拿到参数的原型对象\n    let proto = Object.getPrototypeOf(left);\n    while(true) {                  \n        if(proto === null) return false;\n        if(proto === right.prototype) return true;//找到相同原型对象，返回true\n        proto = Object.getPrototypeof(proto);\n    }\n}\n```\n\n也就是顺着原型链去找，直到找到相同的原型对象，返回`true`，否则为`false`\n\n\n\n## 三、区别\n\n`typeof`与`instanceof`都是判断数据类型的方法，区别如下：\n\n- `typeof`会返回一个变量的基本类型，`instanceof`返回的是一个布尔值\n\n- `instanceof` 可以准确地判断复杂引用数据类型，但是不能正确判断基础数据类型\n- 而` typeof` 也存在弊端，它虽然可以判断基础数据类型（`null` 除外），但是引用数据类型中，除了` function` 类型以外，其他的也无法判断\n\n可以看到，上述两种方法都有弊端，并不能满足所有场景的需求\n\n如果需要通用检测数据类型，可以采用`Object.prototype.toString`，调用该方法，统一返回格式`“[object Xxx]” `的字符串\n\n如下\n\n```js\nObject.prototype.toString({})       // \"[object Object]\"\nObject.prototype.toString.call({})  // 同上结果，加上call也ok\nObject.prototype.toString.call(1)    // \"[object Number]\"\nObject.prototype.toString.call('1')  // \"[object String]\"\nObject.prototype.toString.call(true)  // \"[object Boolean]\"\nObject.prototype.toString.call(function(){})  // \"[object Function]\"\nObject.prototype.toString.call(null)   //\"[object Null]\"\nObject.prototype.toString.call(undefined) //\"[object Undefined]\"\nObject.prototype.toString.call(/123/g)    //\"[object RegExp]\"\nObject.prototype.toString.call(new Date()) //\"[object Date]\"\nObject.prototype.toString.call([])       //\"[object Array]\"\nObject.prototype.toString.call(document)  //\"[object HTMLDocument]\"\nObject.prototype.toString.call(window)   //\"[object Window]\"\n```\n\n了解了`toString`的基本用法，下面就实现一个全局通用的数据类型判断方法\n\n```js\nfunction getType(obj){\n  let type  = typeof obj;\n  if (type !== \"object\") {    // 先进行typeof判断，如果是基础数据类型，直接返回\n    return type;\n  }\n  // 对于typeof返回结果是object的，再进行如下的判断，正则返回结果\n  return Object.prototype.toString.call(obj).replace(/^\\[object (\\S+)\\]$/, '$1'); \n}\n```\n\n使用如下\n\n```js\ngetType([])     // \"Array\" typeof []是object，因此toString返回\ngetType('123')  // \"string\" typeof 直接返回\ngetType(window) // \"Window\" toString返回\ngetType(null)   // \"Null\"首字母大写，typeof null是object，需toString来判断\ngetType(undefined)   // \"undefined\" typeof 直接返回\ngetType()            // \"undefined\" typeof 直接返回\ngetType(function(){}) // \"function\" typeof能判断，因此首字母小写\ngetType(/123/g)      //\"RegExp\" toString返回\n```"
  },
  {
    "path": "docs/JavaScript/visible.md",
    "content": "# 面试官：如何判断一个元素是否在可视区域中？\n\n ![](https://static.vue-js.com/d848c790-8a05-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、用途\n可视区域即我们浏览网页的设备肉眼可见的区域，如下图\n\n ![](https://static.vue-js.com/9c5bbb10-8a56-11eb-85f6-6fac77c0c9b3.png)\n\n在日常开发中，我们经常需要判断目标元素是否在视窗之内或者和视窗的距离小于一个值（例如 100 px），从而实现一些常用的功能，例如：\n\n- 图片的懒加载\n- 列表的无限滚动\n- 计算广告元素的曝光情况\n- 可点击链接的预加载\n\n\n## 二、实现方式\n\n判断一个元素是否在可视区域，我们常用的有三种办法：\n\n- offsetTop、scrollTop\n\n- getBoundingClientRect \n- Intersection Observer\n\n\n\n### offsetTop、scrollTop\n\n`offsetTop`，元素的上外边框至包含元素的上内边框之间的像素距离，其他`offset`属性如下图所示：\n\n ![](https://static.vue-js.com/b4b63ca0-8a54-11eb-85f6-6fac77c0c9b3.png)\n\n下面再来了解下`clientWidth`、`clientHeight`：\n\n- `clientWidth`：元素内容区宽度加上左右内边距宽度，即`clientWidth = content + padding`\n- `clientHeight`：元素内容区高度加上上下内边距高度，即`clientHeight = content + padding`\n\n这里可以看到`client`元素都不包括外边距\n\n最后，关于`scroll`系列的属性如下：\n\n- `scrollWidth` 和 `scrollHeight` 主要用于确定元素内容的实际大小\n\n- `scrollLeft` 和 `scrollTop` 属性既可以确定元素当前滚动的状态，也可以设置元素的滚动位置\n\n- - 垂直滚动 `scrollTop > 0`\n  - 水平滚动 `scrollLeft > 0`\n\n- 将元素的 `scrollLeft` 和 `scrollTop` 设置为 0，可以重置元素的滚动位置\n\n#### 注意\n\n- 上述属性都是只读的，每次访问都要重新开始\n\n\n\n下面再看看如何实现判断：\n\n公式如下：\n```js\nel.offsetTop - document.documentElement.scrollTop <= viewPortHeight\n```\n代码实现：\n```js\nfunction isInViewPortOfOne (el) {\n    // viewPortHeight 兼容所有浏览器写法\n    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight \n    const offsetTop = el.offsetTop\n    const scrollTop = document.documentElement.scrollTop\n    const top = offsetTop - scrollTop\n    return top <= viewPortHeight\n}\n```\n\n### getBoundingClientRect \n\n返回值是一个 `DOMRect`对象，拥有`left`, `top`, `right`, `bottom`, `x`, `y`, `width`, 和 `height`属性\n\n```js\nconst target = document.querySelector('.target');\nconst clientRect = target.getBoundingClientRect();\nconsole.log(clientRect);\n\n// {\n//   bottom: 556.21875,\n//   height: 393.59375,\n//   left: 333,\n//   right: 1017,\n//   top: 162.625,\n//   width: 684\n// }\n```\n\n属性对应的关系图如下所示：\n\n ![](https://static.vue-js.com/e34ac5d0-8a05-11eb-85f6-6fac77c0c9b3.png)\n\n当页面发生滚动的时候，`top`与`left`属性值都会随之改变\n\n如果一个元素在视窗之内的话，那么它一定满足下面四个条件：\n\n- top 大于等于 0\n- left 大于等于 0\n- bottom 小于等于视窗高度\n- right 小于等于视窗宽度\n\n实现代码如下：\n\n```js\nfunction isInViewPort(element) {\n  const viewWidth = window.innerWidth || document.documentElement.clientWidth;\n  const viewHeight = window.innerHeight || document.documentElement.clientHeight;\n  const {\n    top,\n    right,\n    bottom,\n    left,\n  } = element.getBoundingClientRect();\n\n  return (\n    top >= 0 &&\n    left >= 0 &&\n    right <= viewWidth &&\n    bottom <= viewHeight\n  );\n}\n```\n\n\n\n### Intersection Observer\n\n`Intersection Observer` 即重叠观察者，从这个命名就可以看出它用于判断两个元素是否重叠，因为不用进行事件的监听，性能方面相比`getBoundingClientRect `会好很多\n\n\n\n使用步骤主要分为两步：创建观察者和传入被观察者\n\n#### 创建观察者\n\n```js\nconst options = {\n  // 表示重叠面积占被观察者的比例，从 0 - 1 取值，\n  // 1 表示完全被包含\n  threshold: 1.0, \n  root:document.querySelector('#scrollArea') // 必须是目标元素的父级元素\n};\n\nconst callback = (entries, observer) => { ....}\n\nconst observer = new IntersectionObserver(callback, options);\n```\n\n通过`new IntersectionObserver`创建了观察者 `observer`，传入的参数 `callback` 在重叠比例超过 `threshold` 时会被执行`\n\n关于`callback`回调函数常用属性如下：\n\n```js\n// 上段代码中被省略的 callback\nconst callback = function(entries, observer) { \n    entries.forEach(entry => {\n        entry.time;               // 触发的时间\n        entry.rootBounds;         // 根元素的位置矩形，这种情况下为视窗位置\n        entry.boundingClientRect; // 被观察者的位置举行\n        entry.intersectionRect;   // 重叠区域的位置矩形\n        entry.intersectionRatio;  // 重叠区域占被观察者面积的比例（被观察者不是矩形时也按照矩形计算）\n        entry.target;             // 被观察者\n    });\n};\n```\n\n#### 传入被观察者\n\n通过 `observer.observe(target)` 这一行代码即可简单的注册被观察者\n\n```js\nconst target = document.querySelector('.target');\nobserver.observe(target);\n```\n\n\n\n### 三、案例分析\n\n实现：创建了一个十万个节点的长列表，当节点滚入到视窗中时，背景就会从红色变为黄色\n\n`Html`结构如下：\n\n```js\n<div class=\"container\"></div>\n```\n\n`css`样式如下：\n\n```css\n.container {\n    display: flex;\n    flex-wrap: wrap;\n}\n.target {\n    margin: 5px;\n    width: 20px;\n    height: 20px;\n    background: red;\n}\n```\n\n往`container`插入1000个元素\n\n```js\nconst $container = $(\".container\");\n\n// 插入 100000 个 <div class=\"target\"></div>\nfunction createTargets() {\n  const htmlString = new Array(100000)\n    .fill('<div class=\"target\"></div>')\n    .join(\"\");\n  $container.html(htmlString);\n}\n```\n\n这里，首先使用`getBoundingClientRect `方法进行判断元素是否在可视区域\n\n```js\nfunction isInViewPort(element) {\n    const viewWidth = window.innerWidth || document.documentElement.clientWidth;\n    const viewHeight =\n          window.innerHeight || document.documentElement.clientHeight;\n    const { top, right, bottom, left } = element.getBoundingClientRect();\n\n    return top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight;\n}\n```\n\n然后开始监听`scroll`事件，判断页面上哪些元素在可视区域中，如果在可视区域中则将背景颜色设置为`yellow`\n\n```js\n$(window).on(\"scroll\", () => {\n    console.log(\"scroll !\");\n    $targets.each((index, element) => {\n        if (isInViewPort(element)) {\n            $(element).css(\"background-color\", \"yellow\");\n        }\n    });\n});\n```\n\n通过上述方式，可以看到可视区域颜色会变成黄色了，但是可以明显看到有卡顿的现象，原因在于我们绑定了`scroll`事件，`scroll`事件伴随了大量的计算，会造成资源方面的浪费\n\n下面通过`Intersection Observer`的形式同样实现相同的功能\n\n首先创建一个观察者\n\n```js\nconst observer = new IntersectionObserver(getYellow, { threshold: 1.0 });\n```\n\n`getYellow`回调函数实现对背景颜色改变，如下：\n\n```js\nfunction getYellow(entries, observer) {\n    entries.forEach(entry => {\n        $(entry.target).css(\"background-color\", \"yellow\");\n    });\n}\n```\n\n最后传入观察者，即`.target`元素\n\n```js\n$targets.each((index, element) => {\n    observer.observe(element);\n});\n```\n\n可以看到功能同样完成，并且页面不会出现卡顿的情况\n\n## 参考文献\n\n- https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect\n- https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API"
  },
  {
    "path": "docs/NodeJS/Buffer.md",
    "content": "# 面试官：说说对 Node 中的 Buffer 的理解？应用场景？\n\n ![](https://static.vue-js.com/176d02b0-c69c-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n在`Node`应用中，需要处理网络协议、操作数据库、处理图片、接收上传文件等，在网络流和文件的操作中，要处理大量二进制数据，而`Buffer`就是在内存中开辟一片区域（初次初始化为8KB），用来存放二进制数据\n\n在上述操作中都会存在数据流动，每个数据流动的过程中，都会有一个最小或最大数据量\n\n如果数据到达的速度比进程消耗的速度快，那么少数早到达的数据会处于等待区等候被处理。反之，如果数据到达的速度比进程消耗的数据慢，那么早先到达的数据需要等待一定量的数据到达之后才能被处理\n\n这里的等待区就指的缓冲区（Buffer），它是计算机中的一个小物理单位，通常位于计算机的 `RAM` 中\n\n简单来讲，`Nodejs`不能控制数据传输的速度和到达时间，只能决定何时发送数据，如果还没到发送时间，则将数据放在`Buffer`中，即在`RAM`中，直至将它们发送完毕\n\n上面讲到了`Buffer`是用来存储二进制数据，其的形式可以理解成一个数组，数组中的每一项，都可以保存8位二进制：`00000000`，也就是一个字节\n\n例如：\n\n```js\nconst buffer = Buffer.from(\"why\")\n```\n\n其存储过程如下图所示：\n\n ![](https://static.vue-js.com/20371250-c69c-11eb-ab90-d9ae814b240d.png)\n\n\n## 二、使用方法\n\n`Buffer` 类在全局作用域中，无须`require`导入\n\n创建`Buffer`的方法有很多种，我们讲讲下面的两种常见的形式：\n\n- Buffer.from()\n\n- Buffer.alloc() \n\n### Buffer.from()\n\n```js\nconst b1 = Buffer.from('10');\nconst b2 = Buffer.from('10', 'utf8');\nconst b3 = Buffer.from([10]);\nconst b4 = Buffer.from(b3);\n\nconsole.log(b1, b2, b3, b4); // <Buffer 31 30> <Buffer 31 30> <Buffer 0a> <Buffer 0a>\n```\n\n### Buffer.alloc() \n\n```js\nconst bAlloc1 = Buffer.alloc(10); // 创建一个大小为 10 个字节的缓冲区\nconst bAlloc2 = Buffer.alloc(10, 1); // 建一个长度为 10 的 Buffer,其中全部填充了值为 `1` 的字节\nconsole.log(bAlloc1); // <Buffer 00 00 00 00 00 00 00 00 00 00>\nconsole.log(bAlloc2); // <Buffer 01 01 01 01 01 01 01 01 01 01>\n```\n\n在上面创建`buffer`后，则能够`toString`的形式进行交互，默认情况下采取`utf8`字符编码形式，如下\n\n```js\nconst buffer = Buffer.from(\"你好\");\nconsole.log(buffer);\n// <Buffer e4 bd a0 e5 a5 bd>\nconst str = buffer.toString();\nconsole.log(str);\n// 你好\n```\n\n如果编码与解码不是相同的格式则会出现乱码的情况，如下：\n\n```js\nconst buffer = Buffer.from(\"你好\",\"utf-8 \");\nconsole.log(buffer);\n// <Buffer e4 bd a0 e5 a5 bd>\nconst str = buffer.toString(\"ascii\");\nconsole.log(str); \n// d= e%=\n```\n\n当设定的范围导致字符串被截断的时候，也会存在乱码情况，如下：\n\n```js\nconst buf = Buffer.from('Node.js 技术栈', 'UTF-8');\n\nconsole.log(buf)          // <Buffer 4e 6f 64 65 2e 6a 73 20 e6 8a 80 e6 9c af e6 a0 88>\nconsole.log(buf.length)   // 17\n\nconsole.log(buf.toString('UTF-8', 0, 9))  // Node.js �\nconsole.log(buf.toString('UTF-8', 0, 11)) // Node.js 技\n```\n\n所支持的字符集有如下：\n\n- ascii：仅支持 7 位 ASCII 数据，如果设置去掉高位的话，这种编码是非常快的\n- utf8：多字节编码的 Unicode 字符，许多网页和其他文档格式都使用 UTF-8\n- utf16le：2 或 4 个字节，小字节序编码的 Unicode 字符，支持代理对（U+10000至 U+10FFFF）\n- ucs2，utf16le 的别名\n- base64：Base64 编码\n- latin：一种把 Buffer 编码成一字节编码的字符串的方式\n- binary：latin1 的别名，\n- hex：将每个字节编码为两个十六进制字符\n\n\n\n## 三、应用场景\n\n`Buffer`的应用场景常常与流的概念联系在一起，例如有如下：\n\n- I/O操作\n- 加密解密\n- zlib.js\n\n\n\n### I/O操作\n\n通过流的形式，将一个文件的内容读取到另外一个文件\n\n```js\nconst fs = require('fs');\n\nconst inputStream = fs.createReadStream('input.txt'); // 创建可读流\nconst outputStream = fs.createWriteStream('output.txt'); // 创建可写流\n\ninputStream.pipe(outputStream); // 管道读写\n```\n\n\n\n### 加解密\n\n在一些加解密算法中会遇到使用 `Buffer`，例如 `crypto.createCipheriv` 的第二个参数 `key` 为 `string` 或 `Buffer` 类型\n\n\n\n### zlib.js\n\n`zlib.js` 为 `Node.js` 的核心库之一，其利用了缓冲区（`Buffer`）的功能来操作二进制数据流，提供了压缩或解压功能\n\n\n\n## 参考文献\n- http://nodejs.cn/api/buffer.html \n- https://segmentfault.com/a/1190000019894714\n"
  },
  {
    "path": "docs/NodeJS/EventEmitter.md",
    "content": "# 面试官：说说Node中的EventEmitter? 如何实现一个EventEmitter?\n\n ![](https://static.vue-js.com/16b10390-c83a-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n我们了解到，`Node `采用了事件驱动机制，而`EventEmitter `就是`Node`实现事件驱动的基础\n\n在`EventEmitter`的基础上，`Node `几乎所有的模块都继承了这个类，这些模块拥有了自己的事件，可以绑定／触发监听器，实现了异步操作\n\n`Node.js` 里面的许多对象都会分发事件，比如 fs.readStream 对象会在文件被打开的时候触发一个事件\n\n这些产生事件的对象都是 events.EventEmitter 的实例，这些对象有一个 eventEmitter.on() 函数，用于将一个或多个函数绑定到命名事件上\n\n\n## 二、使用方法\n\n`Node `的`events`模块只提供了一个`EventEmitter`类，这个类实现了`Node`异步事件驱动架构的基本模式——观察者模式\n\n在这种模式中，被观察者(主体)维护着一组其他对象派来(注册)的观察者，有新的对象对主体感兴趣就注册观察者，不感兴趣就取消订阅，主体有更新的话就依次通知观察者们\n\n基本代码如下所示：\n\n```js\nconst EventEmitter = require('events')\n\nclass MyEmitter extends EventEmitter {}\nconst myEmitter = new MyEmitter()\n\nfunction callback() {\n    console.log('触发了event事件！')\n}\nmyEmitter.on('event', callback)\nmyEmitter.emit('event')\nmyEmitter.removeListener('event', callback);\n```\n\n通过实例对象的`on`方法注册一个名为`event`的事件，通过`emit`方法触发该事件，而`removeListener`用于取消事件的监听\n\n关于其常见的方法如下：\n\n- emitter.addListener/on(eventName, listener) ：添加类型为 eventName 的监听事件到事件数组尾部\n- emitter.prependListener(eventName, listener)：添加类型为 eventName 的监听事件到事件数组头部  \n- emitter.emit(eventName[, ...args])：触发类型为 eventName 的监听事件 \n- emitter.removeListener/off(eventName, listener)：移除类型为 eventName 的监听事件   \n- emitter.once(eventName, listener)：添加类型为 eventName 的监听事件，以后只能执行一次并删除           \n- emitter.removeAllListeners([eventName])： 移除全部类型为 eventName 的监听事件\n\n\n\n## 三、实现过程\n\n通过上面的方法了解，`EventEmitter`是一个构造函数，内部存在一个包含所有事件的对象\n\n```js\nclass EventEmitter {\n    constructor() {\n        this.events = {};\n    }\n}\n```\n\n其中`events`存放的监听事件的函数的结构如下：\n\n```js\n{\n  \"event1\": [f1,f2,f3]，\n  \"event2\": [f4,f5]，\n  ...\n}\n```\n\n然后开始一步步实现实例方法，首先是`emit`，第一个参数为事件的类型，第二个参数开始为触发事件函数的参数，实现如下：\n\n```js\nemit(type, ...args) {\n    this.events[type].forEach((item) => {\n        Reflect.apply(item, this, args);\n    });\n}\n```\n\n当实现了`emit`方法之后，然后实现`on`、`addListener`、`prependListener`这三个实例方法，都是添加事件监听触发函数，实现也是大同小异\n\n```js\non(type, handler) {\n    if (!this.events[type]) {\n        this.events[type] = [];\n    }\n    this.events[type].push(handler);\n}\n\naddListener(type,handler){\n    this.on(type,handler)\n}\n\nprependListener(type, handler) {\n    if (!this.events[type]) {\n        this.events[type] = [];\n    }\n    this.events[type].unshift(handler);\n}\n```\n\n紧接着就是实现事件监听的方法`removeListener/on`\n\n```js\nremoveListener(type, handler) {\n    if (!this.events[type]) {\n        return;\n    }\n    this.events[type] = this.events[type].filter(item => item !== handler);\n}\n\noff(type,handler){\n    this.removeListener(type,handler)\n}\n```\n\n最后再来实现`once`方法， 再传入事件监听处理函数的时候进行封装，利用闭包的特性维护当前状态，通过`fired`属性值判断事件函数是否执行过\n\n```js\nonce(type, handler) {\n    this.on(type, this._onceWrap(type, handler, this));\n  }\n\n  _onceWrap(type, handler, target) {\n    const state = { fired: false, handler, type , target};\n    const wrapFn = this._onceWrapper.bind(state);\n    state.wrapFn = wrapFn;\n    return wrapFn;\n  }\n\n  _onceWrapper(...args) {\n    if (!this.fired) {\n      this.fired = true;\n      Reflect.apply(this.handler, this.target, args);\n      this.target.off(this.type, this.wrapFn);\n    }\n }\n```\n\n完整代码如下：\n\n```js\nclass EventEmitter {\n    constructor() {\n        this.events = {};\n    }\n\n    on(type, handler) {\n        if (!this.events[type]) {\n            this.events[type] = [];\n        }\n        this.events[type].push(handler);\n    }\n\n    addListener(type,handler){\n        this.on(type,handler)\n    }\n\n    prependListener(type, handler) {\n        if (!this.events[type]) {\n            this.events[type] = [];\n        }\n        this.events[type].unshift(handler);\n    }\n\n    removeListener(type, handler) {\n        if (!this.events[type]) {\n            return;\n        }\n        this.events[type] = this.events[type].filter(item => item !== handler);\n    }\n\n    off(type,handler){\n        this.removeListener(type,handler)\n    }\n\n    emit(type, ...args) {\n        this.events[type].forEach((item) => {\n            Reflect.apply(item, this, args);\n        });\n    }\n\n    once(type, handler) {\n        this.on(type, this._onceWrap(type, handler, this));\n    }\n\n    _onceWrap(type, handler, target) {\n        const state = { fired: false, handler, type , target};\n        const wrapFn = this._onceWrapper.bind(state);\n        state.wrapFn = wrapFn;\n        return wrapFn;\n    }\n\n    _onceWrapper(...args) {\n        if (!this.fired) {\n            this.fired = true;\n            Reflect.apply(this.handler, this.target, args);\n            this.target.off(this.type, this.wrapFn);\n        }\n    }\n}\n```\n\n测试代码如下：\n\n```js\nconst ee = new EventEmitter();\n\n// 注册所有事件\nee.once('wakeUp', (name) => { console.log(`${name} 1`); });\nee.on('eat', (name) => { console.log(`${name} 2`) });\nee.on('eat', (name) => { console.log(`${name} 3`) });\nconst meetingFn = (name) => { console.log(`${name} 4`) };\nee.on('work', meetingFn);\nee.on('work', (name) => { console.log(`${name} 5`) });\n\nee.emit('wakeUp', 'xx');\nee.emit('wakeUp', 'xx');         // 第二次没有触发\nee.emit('eat', 'xx');\nee.emit('work', 'xx');\nee.off('work', meetingFn);        // 移除事件\nee.emit('work', 'xx');           // 再次工作\n```\n\n\n\n## 参考文献\n- http://nodejs.cn/api/events.html#events_class_eventemitter\n- https://segmentfault.com/a/1190000015762318\n- https://juejin.cn/post/6844903781230968845\n- https://vue3js.cn/interview\n"
  },
  {
    "path": "docs/NodeJS/Stream.md",
    "content": "# 面试官：说说对 Node 中的 Stream 的理解？应用场景？\n\n ![](https://static.vue-js.com/a5df3c60-c76f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n流（Stream），是一个数据传输手段，是端到端信息交换的一种方式，而且是有顺序的,是逐块读取数据、处理内容，用于顺序读取输入或写入输出\n\n`Node.js`中很多对象都实现了流，总之它是会冒数据（以 `Buffer` 为单位）\n\n它的独特之处在于，它不像传统的程序那样一次将一个文件读入内存，而是逐块读取数据、处理其内容，而不是将其全部保存在内存中\n\n流可以分成三部分：`source`、`dest`、`pipe`\n\n在`source`和`dest`之间有一个连接的管道`pipe`,它的基本语法是`source.pipe(dest)`，`source`和`dest`就是通过pipe连接，让数据从`source`流向了`dest`，如下图所示：\n\n ![](https://static.vue-js.com/aec05670-c76f-11eb-ab90-d9ae814b240d.png)\n\n\n\n\n\n## 二、种类\n\n在`NodeJS`，几乎所有的地方都使用到了流的概念，分成四个种类：\n\n- 可写流：可写入数据的流。例如 fs.createWriteStream()  可以使用流将数据写入文件\n\n- 可读流： 可读取数据的流。例如fs.createReadStream() 可以从文件读取内容\n\n- 双工流： 既可读又可写的流。例如 net.Socket\n\n- 转换流： 可以在数据写入和读取时修改或转换数据的流。例如，在文件压缩操作中，可以向文件写入压缩数据，并从文件中读取解压数据\n\n\n在`NodeJS`中`HTTP`服务器模块中，`request` 是可读流，`response` 是可写流。还有`fs` 模块，能同时处理可读和可写文件流\n\n可读流和可写流都是单向的，比较容易理解，而另外两个是双向的\n\n### 双工流\n\n之前了解过`websocket`通信，是一个全双工通信，发送方和接受方都是各自独立的方法，发送和接收都没有任何关系\n\n如下图所示：\n\n ![](https://static.vue-js.com/b7ac6d00-c76f-11eb-ab90-d9ae814b240d.png)\n\n基本代码如下：\n\n```js\nconst { Duplex } = require('stream');\n\nconst myDuplex = new Duplex({\n  read(size) {\n    // ...\n  },\n  write(chunk, encoding, callback) {\n    // ...\n  }\n});\n```\n\n\n\n### 双工流\n\n双工流的演示图如下所示：\n\n ![](https://static.vue-js.com/c02883b0-c76f-11eb-ab90-d9ae814b240d.png)\n\n除了上述压缩包的例子，还比如一个 `babel`，把`es6`转换为，我们在左边写入 `es6`，从右边读取 `es5`\n\n基本代码如下所示：\n\n```js\nconst { Transform } = require('stream');\n\nconst myTransform = new Transform({\n  transform(chunk, encoding, callback) {\n    // ...\n  }\n});\n```\n\n\n\n## 三、应用场景\n\n`stream`的应用场景主要就是处理`IO`操作，而`http`请求和文件操作都属于`IO`操作\n\n试想一下，如果一次`IO`操作过大，硬件的开销就过大，而将此次大的`IO`操作进行分段操作，让数据像水管一样流动，直到流动完成\n\n常见的场景有：\n\n- get请求返回文件给客户端\n- 文件操作\n- 一些打包工具的底层操作\n\n\n\n### get请求返回文件给客户端\n\n使用`stream`流返回文件，`res`也是一个`stream`对象，通过`pipe`管道将文件数据返回\n\n```js\nconst server = http.createServer(function (req, res) {\n    const method = req.method; // 获取请求方法\n    if (method === 'GET') { // get 请求\n        const fileName = path.resolve(__dirname, 'data.txt');\n        let stream = fs.createReadStream(fileName);\n        stream.pipe(res); // 将 res 作为 stream 的 dest\n    }\n});\nserver.listen(8000);\n```\n\n\n\n### 文件操作\n\n创建一个可读数据流`readStream`，一个可写数据流`writeStream`，通过`pipe`管道把数据流转过去\n\n```js\nconst fs = require('fs')\nconst path = require('path')\n\n// 两个文件名\nconst fileName1 = path.resolve(__dirname, 'data.txt')\nconst fileName2 = path.resolve(__dirname, 'data-bak.txt')\n// 读取文件的 stream 对象\nconst readStream = fs.createReadStream(fileName1)\n// 写入文件的 stream 对象\nconst writeStream = fs.createWriteStream(fileName2)\n// 通过 pipe执行拷贝，数据流转\nreadStream.pipe(writeStream)\n// 数据读取完成监听，即拷贝完成\nreadStream.on('end', function () {\n    console.log('拷贝完成')\n})\n\n```\n\n\n\n\n\n### 一些打包工具的底层操作\n\n目前一些比较火的前端打包构建工具，都是通过`node.js`编写的，打包和构建的过程肯定是文件频繁操作的过程，离不来`stream`，如`gulp`\n\n\n\n\n\n## 参考文献\n\n- https://xie.infoq.cn/article/1a9695020828460eb3c4ff1fa\n- https://juejin.cn/post/6844903891083984910\n"
  },
  {
    "path": "docs/NodeJS/event_loop.md",
    "content": "# 面试官：说说对Nodejs中的事件循环机制理解?\n\n ![](https://static.vue-js.com/e0faf3c0-c90e-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n在[浏览器事件循环](https://github.com/febobo/web-interview/issues/73)中，我们了解到`javascript`在浏览器中的事件循环机制，其是根据`HTML5`定义的规范来实现\n\n而在`NodeJS`中，事件循环是基于`libuv`实现，`libuv`是一个多平台的专注于异步IO的库，如下图最右侧所示：\n\n ![](https://static.vue-js.com/ea690b90-c90e-11eb-85f6-6fac77c0c9b3.png)\n\n上图`EVENT_QUEUE` 给人看起来只有一个队列，但`EventLoop`存在6个阶段，每个阶段都有对应的一个先进先出的回调队列\n\n\n## 二、流程\n\n上节讲到事件循环分成了六个阶段，对应如下：\n\n ![](https://static.vue-js.com/f2e34d80-c90e-11eb-ab90-d9ae814b240d.png)\n\n- timers阶段：这个阶段执行timer（setTimeout、setInterval）的回调\n- 定时器检测阶段(timers)：本阶段执行 timer 的回调，即 setTimeout、setInterval 里面的回调函数\n- I/O事件回调阶段(I/O callbacks)：执行延迟到下一个循环迭代的 I/O 回调，即上一轮循环中未被执行的一些I/O回调\n- 闲置阶段(idle, prepare)：仅系统内部使用\n- 轮询阶段(poll)：检索新的 I/O 事件;执行与 I/O 相关的回调（几乎所有情况下，除了关闭的回调函数，那些由计时器和 setImmediate() 调度的之外），其余情况 node 将在适当的时候在此阻塞\n- 检查阶段(check)：setImmediate() 回调函数在这里执行\n- 关闭事件回调阶段(close callback)：一些关闭的回调函数，如：socket.on('close', ...)\n\n每个阶段对应一个队列，当事件循环进入某个阶段时, 将会在该阶段内执行回调，直到队列耗尽或者回调的最大数量已执行, 那么将进入下一个处理阶段\n\n除了上述6个阶段，还存在`process.nextTick`，其不属于事件循环的任何一个阶段，它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调，类似插队\n\n流程图如下所示：\n\n ![](https://static.vue-js.com/fbe731d0-c90e-11eb-ab90-d9ae814b240d.png)\n\n在`Node`中，同样存在宏任务和微任务，与浏览器中的事件循环相似\n\n微任务对应有：\n\n- next tick queue：process.nextTick\n- other queue：Promise的then回调、queueMicrotask\n\n宏任务对应有：\n\n- timer queue：setTimeout、setInterval\n- poll queue：IO事件\n- check queue：setImmediate\n- close queue：close事件\n\n其执行顺序为：\n\n- next tick microtask queue\n- other microtask queue\n- timer queue\n- poll queue\n- check queue\n- close queue\n\n\n\n## 三、题目\n\n通过上面的学习，下面开始看看题目\n\n```js\nasync function async1() {\n    console.log('async1 start')\n    await async2()\n    console.log('async1 end')\n}\n\nasync function async2() {\n    console.log('async2')\n}\n\nconsole.log('script start')\n\nsetTimeout(function () {\n    console.log('setTimeout0')\n}, 0)\n\nsetTimeout(function () {\n    console.log('setTimeout2')\n}, 300)\n\nsetImmediate(() => console.log('setImmediate'));\n\nprocess.nextTick(() => console.log('nextTick1'));\n\nasync1();\n\nprocess.nextTick(() => console.log('nextTick2'));\n\nnew Promise(function (resolve) {\n    console.log('promise1')\n    resolve();\n    console.log('promise2')\n}).then(function () {\n    console.log('promise3')\n})\n\nconsole.log('script end')\n```\n\n分析过程：\n\n- 先找到同步任务，输出script start\n- 遇到第一个 setTimeout，将里面的回调函数放到 timer 队列中\n- 遇到第二个 setTimeout，300ms后将里面的回调函数放到 timer 队列中\n- 遇到第一个setImmediate，将里面的回调函数放到 check 队列中\n- 遇到第一个 nextTick，将其里面的回调函数放到本轮同步任务执行完毕后执行\n\n- 执行 async1函数，输出 async1 start\n- 执行 async2 函数，输出 async2，async2 后面的输出 async1 end进入微任务，等待下一轮的事件循环\n- 遇到第二个，将其里面的回调函数放到本轮同步任务执行完毕后执行\n- 遇到 new Promise，执行里面的立即执行函数，输出 promise1、promise2\n- then里面的回调函数进入微任务队列\n- 遇到同步任务，输出 script end\n- 执行下一轮回到函数，先依次输出 nextTick 的函数，分别是 nextTick1、nextTick2\n- 然后执行微任务队列，依次输出 async1 end、promise3\n- 执行timer 队列，依次输出 setTimeout0\n- 接着执行 check  队列，依次输出 setImmediate\n- 300ms后，timer 队列存在任务，执行输出 setTimeout2\n\n执行结果如下：\n\n```\nscript start\nasync1 start\nasync2\npromise1\npromise2\nscript end\nnextTick1\nnextTick2\nasync1 end\npromise3\nsetTimeout0\nsetImmediate\nsetTimeout2\n```\n\n最后有一道是关于`setTimeout`与`setImmediate`的输出顺序\n\n```js\nsetTimeout(() => {\n  console.log(\"setTimeout\");\n}, 0);\n\nsetImmediate(() => {\n  console.log(\"setImmediate\");\n});\n```\n\n输出情况如下：\n\n```js\n情况一：\nsetTimeout\nsetImmediate\n\n情况二：\nsetImmediate\nsetTimeout\n```\n\n分析下流程：\n\n- 外层同步代码一次性全部执行完，遇到异步API就塞到对应的阶段\n- 遇到`setTimeout`，虽然设置的是0毫秒触发，但实际上会被强制改成1ms，时间到了然后塞入`times`阶段\n- 遇到`setImmediate`塞入`check`阶段\n- 同步代码执行完毕，进入Event Loop\n- 先进入`times`阶段，检查当前时间过去了1毫秒没有，如果过了1毫秒，满足`setTimeout`条件，执行回调，如果没过1毫秒，跳过\n- 跳过空的阶段，进入check阶段，执行`setImmediate`回调\n\n这里的关键在于这1ms，如果同步代码执行时间较长，进入`Event Loop`的时候1毫秒已经过了，`setTimeout`先执行，如果1毫秒还没到，就先执行了`setImmediate`\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000012258592\n- https://juejin.cn/post/6844904100195205133\n- https://vue3js.cn/interview/\n"
  },
  {
    "path": "docs/NodeJS/file_upload.md",
    "content": "# 面试官：如何实现文件上传？说说你的思路\n\n ![](https://static.vue-js.com/248a5580-ce60-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n文件上传在日常开发中应用很广泛，我们发微博、发微信朋友圈都会用到了图片上传功能\n\n因为浏览器限制，浏览器不能直接操作文件系统的，需要通过浏览器所暴露出来的统一接口，由用户主动授权发起来访问文件动作，然后读取文件内容进指定内存里，最后执行提交请求操作，将内存里的文件内容数据上传到服务端，服务端解析前端传来的数据信息后存入文件里\n\n对于文件上传，我们需要设置请求头为`content-type:multipart/form-data`\n\n> multipart互联网上的混合资源，就是资源由多种元素组成，form-data表示可以使用HTML Forms 和 POST 方法上传文件\n\n结构如下：\n\n```http\nPOST /t2/upload.do HTTP/1.1\nUser-Agent: SOHUWapRebot\nAccept-Language: zh-cn,zh;q=0.5\nAccept-Charset: GBK,utf-8;q=0.7,*;q=0.7\nConnection: keep-alive\nContent-Length: 60408\nContent-Type:multipart/form-data; boundary=ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC\nHost: w.sohu.com\n\n--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC\nContent-Disposition: form-data; name=\"city\"\n\nSanta colo\n--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC\nContent-Disposition: form-data;name=\"desc\"\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n \n...\n--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC\nContent-Disposition: form-data;name=\"pic\"; filename=\"photo.jpg\"\nContent-Type: application/octet-stream\nContent-Transfer-Encoding: binary\n \n... binary data of the jpg ...\n--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC--\n```\n\n`boundary`表示分隔符，如果要上传多个表单项，就要使用`boundary`分割，每个表单项由`———XXX`开始，以`———XXX`结尾\n\n而`xxx`是即时生成的字符串，用以确保整个分隔符不会在文件或表单项的内容中出现\n\n每个表单项必须包含一个 `Content-Disposition` 头，其他的头信息则为可选项， 比如 `Content-Type` \n\n`Content-Disposition` 包含了 `type `和 一个名字为` name `的 `parameter`，`type` 是 `form-data`，`name `参数的值则为表单控件（也即 field）的名字，如果是文件，那么还有一个 `filename `参数，值就是文件名\n\n```kotlin\nContent-Disposition: form-data; name=\"user\"; filename=\"logo.png\"\n```\n\n至于使用`multipart/form-data`，是因为文件是以二进制的形式存在，其作用是专门用于传输大型二进制数据，效率高\n\n\n\n### 二、如何实现\n\n关于文件的上传的上传，我们可以分成两步骤：\n\n- 文件的上传\n- 文件的解析\n\n\n\n### 文件上传\n\n传统前端文件上传的表单结构如下：\n\n```html\n<form action=\"http://localhost:8080/api/upload\" method=\"post\" enctype=\"multipart/form-data\">\n    <input type=\"file\" name=\"file\" id=\"file\" value=\"\" multiple=\"multiple\" />\n    <input type=\"submit\" value=\"提交\"/>\n</form>\n```\n\n`action` 就是我们的提交到的接口，`enctype=\"multipart/form-data\"` 就是指定上传文件格式，`input` 的 `name` 属性一定要等于`file`\n\n\n\n### 文件解析\n\n在服务器中，这里采用`koa2`中间件的形式解析上传的文件数据，分别有下面两种形式：\n\n- koa-body\n- koa-multer\n\n\n#### koa-body\n\n安装依赖\n\n```cmd\nnpm install koa-body\n```\n\n引入`koa-body`中间件\n\n```js\nconst koaBody = require('koa-body');\napp.use(koaBody({\n    multipart: true,\n    formidable: {\n        maxFileSize: 200*1024*1024    // 设置上传文件大小最大限制，默认2M\n    }\n}));\n```\n\n获取上传的文件\n\n```js\nconst file = ctx.request.files.file; // 获取上传文件\n```\n\n获取文件数据后，可以通过`fs`模块将文件保存到指定目录\n\n```js\nrouter.post('/uploadfile', async (ctx, next) => {\n  // 上传单个文件\n  const file = ctx.request.files.file; // 获取上传文件\n  // 创建可读流\n  const reader = fs.createReadStream(file.path);\n  let filePath = path.join(__dirname, 'public/upload/') + `/${file.name}`;\n  // 创建可写流\n  const upStream = fs.createWriteStream(filePath);\n  // 可读流通过管道写入可写流\n  reader.pipe(upStream);\n  return ctx.body = \"上传成功！\";\n});\n```\n\n\n\n#### koa-multer\n\n安装依赖：\n\n```cmd \nnpm install koa-multer\n```\n\n使用 `multer` 中间件实现文件上传\n\n```js\nconst storage = multer.diskStorage({\n  destination: (req, file, cb) => {\n    cb(null, \"./upload/\")\n  },\n  filename: (req, file, cb) => {\n    cb(null, Date.now() + path.extname(file.originalname))\n  }\n})\n\nconst upload = multer({\n  storage\n});\n\nconst fileRouter = new Router();\n\nfileRouter.post(\"/upload\", upload.single('file'), (ctx, next) => {\n  console.log(ctx.req.file); // 获取文件\n})\n\napp.use(fileRouter.routes());\n```\n\n\n\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000037411957\n- https://www.jianshu.com/p/29e38bcc8a1d"
  },
  {
    "path": "docs/NodeJS/fs.md",
    "content": "# 面试官：说说对 Node 中的 fs模块的理解? 有哪些常用方法\n\n ![](https://static.vue-js.com/a141e5c0-c46a-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\nfs（filesystem），该模块提供本地文件的读写能力，基本上是`POSIX`文件操作命令的简单包装\n\n可以说，所有与文件的操作都是通过`fs`核心模块实现\n\n导入模块如下：\n\n```js\nconst fs = require('fs');\n```\n\n这个模块对所有文件系统操作提供异步（不具有`sync` 后缀）和同步（具有 `sync` 后缀）两种操作方式，而供开发者选择\n\n\n\n### 二、文件知识\n\n在计算机中有关于文件的知识：\n\n- 权限位 mode\n- 标识位 flag\n- 文件描述为 fd\n\n\n\n### 权限位 mode\n\n ![](https://static.vue-js.com/4f4d41a0-c46b-11eb-ab90-d9ae814b240d.png)\n\n针对文件所有者、文件所属组、其他用户进行权限分配，其中类型又分成读、写和执行，具备权限位4、2、1，不具备权限为0\n\n如在`linux`查看文件权限位：\n\n```js\ndrwxr-xr-x 1 PandaShen 197121 0 Jun 28 14:41 core\n-rw-r--r-- 1 PandaShen 197121 293 Jun 23 17:44 index.md\n```\n\n在开头前十位中，`d`为文件夹，`-`为文件，后九位就代表当前用户、用户所属组和其他用户的权限位，按每三位划分，分别代表读（r）、写（w）和执行（x），- 代表没有当前位对应的权限\n\n\n\n### 标识位\n\n标识位代表着对文件的操作方式，如可读、可写、即可读又可写等等，如下表所示：\n\n| 符号 | 含义                                                     |\n| ---- | -------------------------------------------------------- |\n| r    | 读取文件，如果文件不存在则抛出异常。                     |\n| r+   | 读取并写入文件，如果文件不存在则抛出异常。               |\n| rs   | 读取并写入文件，指示操作系统绕开本地文件系统缓存。       |\n| w    | 写入文件，文件不存在会被创建，存在则清空后写入。         |\n| wx   | 写入文件，排它方式打开。                                 |\n| w+   | 读取并写入文件，文件不存在则创建文件，存在则清空后写入。 |\n| wx+  | 和 w+ 类似，排他方式打开。                               |\n| a    | 追加写入，文件不存在则创建文件。                         |\n| ax   | 与 a 类似，排他方式打开。                                |\n| a+   | 读取并追加写入，不存在则创建。                           |\n| ax+  | 与 a+ 类似，排他方式打开。                               |\n\n\n\n### 文件描述为 fd\n\n操作系统会为每个打开的文件分配一个名为文件描述符的数值标识，文件操作使用这些文件描述符来识别与追踪每个特定的文件\n\n`Window `系统使用了一个不同但概念类似的机制来追踪资源，为方便用户，`NodeJS `抽象了不同操作系统间的差异，为所有打开的文件分配了数值的文件描述符\n\n在 `NodeJS `中，每操作一个文件，文件描述符是递增的，文件描述符一般从 `3` 开始，因为前面有 `0`、`1`、`2`三个比较特殊的描述符，分别代表 `process.stdin`（标准输入）、`process.stdout`（标准输出）和 `process.stderr`（错误输出）\n\n\n\n## 三、方法\n\n下面针对`fs`模块常用的方法进行展开：\n\n- 文件读取\n- 文件写入\n- 文件追加写入\n- 文件拷贝\n- 创建目录\n\n\n\n### 文件读取\n\n####  fs.readFileSync\n\n同步读取，参数如下：\n\n- 第一个参数为读取文件的路径或文件描述符\n- 第二个参数为 options，默认值为 null，其中有 encoding（编码，默认为 null）和 flag（标识位，默认为 r），也可直接传入 encoding\n\n结果为返回文件的内容\n\n```js\nconst fs = require(\"fs\");\n\nlet buf = fs.readFileSync(\"1.txt\");\nlet data = fs.readFileSync(\"1.txt\", \"utf8\");\n\nconsole.log(buf); // <Buffer 48 65 6c 6c 6f>\nconsole.log(data); // Hello\n```\n\n\n\n#### fs.readFile\n\n异步读取方法 `readFile` 与 `readFileSync` 的前两个参数相同，最后一个参数为回调函数，函数内有两个参数 `err`（错误）和 `data`（数据），该方法没有返回值，回调函数在读取文件成功后执行\n\n```js\nconst fs = require(\"fs\");\n\nfs.readFile(\"1.txt\", \"utf8\", (err, data) => {\n   if(!err){\n       console.log(data); // Hello\n   }\n});\n```\n\n\n\n### 文件写入\n\n#### writeFileSync\n\n同步写入，有三个参数：\n\n- 第一个参数为写入文件的路径或文件描述符\n\n- 第二个参数为写入的数据，类型为 String 或 Buffer\n\n- 第三个参数为 options，默认值为 null，其中有 encoding（编码，默认为 utf8）、 flag（标识位，默认为 w）和 mode（权限位，默认为 0o666），也可直接传入 encoding\n\n```js\nconst fs = require(\"fs\");\n\nfs.writeFileSync(\"2.txt\", \"Hello world\");\nlet data = fs.readFileSync(\"2.txt\", \"utf8\");\n\nconsole.log(data); // Hello world\n```\n\n\n\n#### writeFile\n\n异步写入，`writeFile` 与 `writeFileSync` 的前三个参数相同，最后一个参数为回调函数，函数内有一个参数 `err`（错误），回调函数在文件写入数据成功后执行\n\n```js\nconst fs = require(\"fs\");\n\nfs.writeFile(\"2.txt\", \"Hello world\", err => {\n    if (!err) {\n        fs.readFile(\"2.txt\", \"utf8\", (err, data) => {\n            console.log(data); // Hello world\n        });\n    }\n});\n```\n\n\n\n### 文件追加写入\n\n#### appendFileSync\n\n参数如下：\n\n- 第一个参数为写入文件的路径或文件描述符\n- 第二个参数为写入的数据，类型为 String 或 Buffer\n- 第三个参数为 options，默认值为 null，其中有 encoding（编码，默认为 utf8）、 flag（标识位，默认为 a）和 mode（权限位，默认为 0o666），也可直接传入 encoding\n\n```js\nconst fs = require(\"fs\");\n\nfs.appendFileSync(\"3.txt\", \" world\");\nlet data = fs.readFileSync(\"3.txt\", \"utf8\");\n```\n\n\n\n#### appendFile\n\n异步追加写入方法 `appendFile` 与 `appendFileSync` 的前三个参数相同，最后一个参数为回调函数，函数内有一个参数 `err`（错误），回调函数在文件追加写入数据成功后执行\n\n```js\nconst fs = require(\"fs\");\n\nfs.appendFile(\"3.txt\", \" world\", err => {\n    if (!err) {\n        fs.readFile(\"3.txt\", \"utf8\", (err, data) => {\n            console.log(data); // Hello world\n        });\n    }\n});\n```\n\n\n\n### 文件拷贝\n\n#### copyFileSync\n\n同步拷贝\n\n```js\nconst fs = require(\"fs\");\n\nfs.copyFileSync(\"3.txt\", \"4.txt\");\nlet data = fs.readFileSync(\"4.txt\", \"utf8\");\n\nconsole.log(data); // Hello world\n```\n\n\n\n#### copyFile\n\n异步拷贝\n\n```js\nconst fs = require(\"fs\");\n\nfs.copyFile(\"3.txt\", \"4.txt\", () => {\n    fs.readFile(\"4.txt\", \"utf8\", (err, data) => {\n        console.log(data); // Hello world\n    });\n});\n```\n\n\n\n### 创建目录\n\n#### mkdirSync\n\n同步创建，参数为一个目录的路径，没有返回值，在创建目录的过程中，必须保证传入的路径前面的文件目录都存在，否则会抛出异常\n\n```js\n// 假设已经有了 a 文件夹和 a 下的 b 文件夹\nfs.mkdirSync(\"a/b/c\")\n```\n\n\n\n#### mkdir\n\n异步创建，第二个参数为回调函数\n\n```js\nfs.mkdir(\"a/b/c\", err => {\n    if (!err) console.log(\"创建成功\");\n});\n```\n\n\n\n\n\n## 参考文献\n\n- http://nodejs.cn/api/fs.html\n\n- https://segmentfault.com/a/1190000019913303"
  },
  {
    "path": "docs/NodeJS/global.md",
    "content": "# 面试官：说说 Node. js 有哪些全局对象？\n\n ![](https://static.vue-js.com/79c7b100-c2a3-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在浏览器 `JavaScript` 中，通常` window` 是全局对象， 而 `Nodejs `中的全局对象是 `global`\n\n在`NodeJS`里，是不可能在最外层定义一个变量，因为所有的用户代码都是当前模块的，只在当前模块里可用，但可以通过`exports`对象的使用将其传递给模块外部\n\n所以，在`NodeJS`中，用`var`声明的变量并不属于全局的变量，只在当前模块生效\n\n像上述的`global`全局对象则在全局作用域中，任何全局变量、函数、对象都是该对象的一个属性值\n\n\n\n## 二、有哪些\n\n将全局对象分成两类：\n\n- 真正的全局对象\n\n- 模块级别的全局变量\n\n\n\n### 真正的全局对象\n\n下面给出一些常见的全局对象：\n\n- Class:Buffer\n- process\n\n- console\n- clearInterval、setInterval\n- clearTimeout、setTimeout\n\n- global\n\n\n\n#### Class:Buffer\n\n可以处理二进制以及非`Unicode`编码的数据\n\n在`Buffer`类实例化中存储了原始数据。`Buffer`类似于一个整数数组，在V8堆原始存储空间给它分配了内存\n\n一旦创建了`Buffer`实例，则无法改变大小\n\n\n\n#### process\n\n进程对象，提供有关当前进程的信息和控制\n\n包括在执行`node`程序进程时，如果需要传递参数，我们想要获取这个参数需要在`process`内置对象中\n\n启动进程：\n\n```cmd\n node index.js 参数1 参数2 参数3\n```\n\nindex.js文件如下：\n\n```js\nprocess.argv.forEach((val, index) => {\n  console.log(`${index}: ${val}`);\n});\n```\n\n输出如下：\n\n```js\n/usr/local/bin/node\n/Users/mjr/work/node/process-args.js\n参数1\n参数2\n参数3\n```\n\n除此之外，还包括一些其他信息如版本、操作系统等\n\n![](https://static.vue-js.com/85f473a0-c2a3-11eb-ab90-d9ae814b240d.png)\n\n\n\n#### console\n\n用来打印`stdout`和`stderr`\n\n最常用的输入内容的方式：console.log\n\n```js\nconsole.log(\"hello\");\n```\n\n清空控制台：console.clear\n\n```js\nconsole.clear\n```\n\n打印函数的调用栈：console.trace\n\n```js\nfunction test() {\n    demo();\n}\n\nfunction demo() {\n    foo();\n}\n\nfunction foo() {\n    console.trace();\n}\n\ntest();\n```\n\n ![](https://static.vue-js.com/91b6dbb0-c2a3-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n#### clearInterval、setInterval\n\n设置定时器与清除定时器\n\n```js\nsetInterval(callback, delay[, ...args])\n```\n\n`callback`每`delay`毫秒重复执行一次\n\n`clearInterval`则为对应发取消定时器的方法\n\n\n\n#### clearTimeout、setTimeout\n\n设置延时器与清除延时器\n\n```js\nsetTimeout(callback,delay[,...args])\n```\n\n`callback`在`delay`毫秒后执行一次\n\n`clearTimeout`则为对应取消延时器的方法\n\n\n\n#### global\n\n全局命名空间对象，墙面讲到的`process`、`console`、`setTimeout`等都有放到`global`中\n\n```js\nconsole.log(process === global.process) // true\n```\n\n\n\n\n\n### 模块级别的全局对象\n\n这些全局对象是模块中的变量，只是每个模块都有，看起来就像全局变量，像在命令交互中是不可以使用，包括：\n\n- __dirname\n- __filename\n- exports\n- module\n- require\n\n\n\n#### __dirname\n\n获取当前文件所在的路径，不包括后面的文件名\n\n从 `/Users/mjr` 运行 `node example.js`：\n\n```js\nconsole.log(__dirname);\n// 打印: /Users/mjr\n```\n\n\n\n#### __filename\n\n获取当前文件所在的路径和文件名称，包括后面的文件名称\n\n从 `/Users/mjr` 运行 `node example.js`：\n\n```js\nconsole.log(__filename);\n// 打印: /Users/mjr/example.js\n```\n\n\n\n#### exports\n\n`module.exports` 用于指定一个模块所导出的内容，即可以通过 `require()` 访问的内容\n\n```js\nexports.name = name;\nexports.age = age;\nexports.sayHello = sayHello;\n```\n\n\n\n#### module\n\n对当前模块的引用，通过`module.exports` 用于指定一个模块所导出的内容，即可以通过 `require()` 访问的内容\n\n\n\n#### require\n\n用于引入模块、 `JSON`、或本地文件。 可以从 `node_modules` 引入模块。\n\n可以使用相对路径引入本地模块或` JSON `文件，路径会根据`__dirname`定义的目录名或当前工作目录进行处理\n\n\n\n\n\n## 参考文献\n\n- http://nodejs.cn/api/globals.html\n- https://vue3js.cn/interview\n"
  },
  {
    "path": "docs/NodeJS/jwt.md",
    "content": "# 面试官：如何实现jwt鉴权机制？说说你的思路\n\n ![](https://static.vue-js.com/efff62b0-cd88-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\nJWT（JSON Web Token），本质就是一个字符串书写规范，如下图，作用是用来在用户和服务器之间传递安全可靠的信息\n\n![](https://static.vue-js.com/052904c0-cd89-11eb-ab90-d9ae814b240d.png)\n\n在目前前后端分离的开发过程中，使用`token`鉴权机制用于身份验证是最常见的方案，流程如下：\n\n- 服务器当验证用户账号和密码正确的时候，给用户颁发一个令牌，这个令牌作为后续用户访问一些接口的凭证\n- 后续访问会根据这个令牌判断用户时候有权限进行访问\n\n`Token`，分成了三部分，头部（Header）、载荷（Payload）、签名（Signature），并以`.`进行拼接。其中头部和载荷都是以`JSON`格式存放数据，只是进行了编码\n\n ![](https://static.vue-js.com/1175f990-cd89-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### header\n\n每个JWT都会带有头部信息，这里主要声明使用的算法。声明算法的字段名为`alg`，同时还有一个`typ`的字段，默认`JWT`即可。以下示例中算法为HS256\n\n```json\n{  \"alg\": \"HS256\",  \"typ\": \"JWT\" } \n```\n\n因为JWT是字符串，所以我们还需要对以上内容进行Base64编码，编码后字符串如下：\n\n```tex\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9        \n```\n\n\n\n### payload\n\n载荷即消息体，这里会存放实际的内容，也就是`Token`的数据声明，例如用户的`id`和`name`，默认情况下也会携带令牌的签发时间`iat`，通过还可以设置过期时间，如下：\n\n```json\n{\n  \"sub\": \"1234567890\",\n  \"name\": \"John Doe\",\n  \"iat\": 1516239022\n}\n```\n\n同样进行Base64编码后，字符串如下：\n\n```tex\neyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ\n```\n\n\n\n### Signature\n\n签名是对头部和载荷内容进行签名，一般情况，设置一个`secretKey`，对前两个的结果进行`HMACSHA25`算法，公式如下：\n\n```js\nSignature = HMACSHA256(base64Url(header)+.+base64Url(payload),secretKey)\n```\n\n一旦前面两部分数据被篡改，只要服务器加密用的密钥没有泄露，得到的签名肯定和之前的签名不一致\n\n\n\n## 二、如何实现\n\n`Token`的使用分成了两部分：\n\n- 生成token：登录成功的时候，颁发token\n- 验证token：访问某些资源或者接口时，验证token\n\n\n\n### 生成 token\n\n借助第三方库`jsonwebtoken`，通过`jsonwebtoken` 的 `sign` 方法生成一个 `token`：\n\n- 第一个参数指的是 Payload\n\n- 第二个是秘钥，服务端特有\n\n- 第三个参数是 option，可以定义 token 过期时间\n\n```js\nconst crypto = require(\"crypto\"),\n  jwt = require(\"jsonwebtoken\");\n// TODO:使用数据库\n// 这里应该是用数据库存储，这里只是演示用\nlet userList = [];\n\nclass UserController {\n  // 用户登录\n  static async login(ctx) {\n    const data = ctx.request.body;\n    if (!data.name || !data.password) {\n      return ctx.body = {\n        code: \"000002\", \n        message: \"参数不合法\"\n      }\n    }\n    const result = userList.find(item => item.name === data.name && item.password === crypto.createHash('md5').update(data.password).digest('hex'))\n    if (result) {\n      // 生成token\n      const token = jwt.sign(  \n        {\n          name: result.name\n        },\n        \"test_token\", // secret\n        { expiresIn: 60 * 60 } // 过期时间：60 * 60 s\n      );\n      return ctx.body = {\n        code: \"0\",\n        message: \"登录成功\",\n        data: {\n          token\n        }\n      };\n    } else {\n      return ctx.body = {\n        code: \"000002\",\n        message: \"用户名或密码错误\"\n      };\n    }\n  }\n}\n\nmodule.exports = UserController;\n```\n\n在前端接收到`token`后，一般情况会通过`localStorage`进行缓存，然后将`token`放到`HTTP `请求头`Authorization` 中，关于`Authorization` 的设置，前面要加上 Bearer ，注意后面带有空格\n\n```js\naxios.interceptors.request.use(config => {\n  const token = localStorage.getItem('token');\n  config.headers.common['Authorization'] = 'Bearer ' + token; // 留意这里的 Authorization\n  return config;\n})\n```\n\n\n\n### 校验token\n\n使用 `koa-jwt` 中间件进行验证，方式比较简单\n\n```js\n/ 注意：放在路由前面\napp.use(koajwt({\n  secret: 'test_token'\n}).unless({ // 配置白名单\n  path: [/\\/api\\/register/, /\\/api\\/login/]\n}))\n```\n\n- secret 必须和 sign 时候保持一致\n- 可以通过 unless 配置接口白名单，也就是哪些 URL 可以不用经过校验，像登陆/注册都可以不用校验\n- 校验的中间件需要放在需要校验的路由前面，无法对前面的 URL 进行校验\n\n获取`token`用户的信息方法如下：\n\n```js\nrouter.get('/api/userInfo',async (ctx,next) =>{\n    const authorization =  ctx.header.authorization // 获取jwt\n    const token = authorization.replace('Beraer ','')\n    const result = jwt.verify(token,'test_token')\n    ctx.body = result\n```\n\n\n\n注意：上述的`HMA256`加密算法为单秘钥的形式，一旦泄露后果非常的危险\n\n在分布式系统中，每个子系统都要获取到秘钥，那么这个子系统根据该秘钥可以发布和验证令牌，但有些服务器只需要验证令牌\n\n这时候可以采用非对称加密，利用私钥发布令牌，公钥验证令牌，加密算法可以选择`RS256`\n\n\n## 三、优缺点\n\n优点：\n\n- json具有通用性，所以可以跨语言\n- 组成简单，字节占用小，便于传输\n- 服务端无需保存会话信息，很容易进行水平扩展\n- 一处生成，多处使用，可以在分布式系统中，解决单点登录问题\n- 可防护CSRF攻击\n\n缺点：\n\n- payload部分仅仅是进行简单编码，所以只能用于存储逻辑必需的非敏感信息\n- 需要保护好加密密钥，一旦泄露后果不堪设想\n- 为避免token被劫持，最好使用https协议\n\n\n\n## 参考文献\n\n- http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html\n- https://blog.wangjunfeng.com/post/golang-jwt/\n- https://vue3js.cn/interview/"
  },
  {
    "path": "docs/NodeJS/middleware.md",
    "content": "# 面试官：说说对中间件概念的理解，如何封装 node 中间件？\n\n ![](https://static.vue-js.com/614ae480-cce4-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n中间件（Middleware）是介于应用系统和系统软件之间的一类软件，它使用系统软件所提供的基础服务（功能），衔接网络上应用系统的各个部分或不同的应用，能够达到资源共享、功能共享的目的\n\n在`NodeJS`中，中间件主要是指封装`http`请求细节处理的方法\n\n例如在`express`、`koa`等`web`框架中，中间件的本质为一个回调函数，参数包含请求对象、响应对象和执行下一个中间件的函数\n\n ![](https://static.vue-js.com/6a6ed3f0-cce4-11eb-85f6-6fac77c0c9b3.png)\n\n在这些中间件函数中，我们可以执行业务逻辑代码，修改请求和响应对象、返回响应数据等操作\n\n\n\n## 二、封装\n\n`koa`是基于`NodeJS`当前比较流行的`web`框架，本身支持的功能并不多，功能都可以通过中间件拓展实现。通过添加不同的中间件，实现不同的需求，从而构建一个 `Koa` 应用\n\n`Koa` 中间件采用的是洋葱圈模型，每次执行下一个中间件传入两个参数：\n\n- ctx ：封装了request 和  response 的变量\n- next ：进入下一个要执行的中间件的函数\n\n ![](https://static.vue-js.com/7507b020-cce4-11eb-ab90-d9ae814b240d.png)\n\n\n\n下面就针对`koa`进行中间件的封装：\n\n`Koa `的中间件就是函数，可以是` async` 函数，或是普通函数\n\n```js\n// async 函数\napp.use(async (ctx, next) => {\n  const start = Date.now();\n  await next();\n  const ms = Date.now() - start;\n  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);\n});\n\n// 普通函数\napp.use((ctx, next) => {\n  const start = Date.now();\n  return next().then(() => {\n    const ms = Date.now() - start;\n    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);\n  });\n});\n```\n\n下面则通过中间件封装`http`请求过程中几个常用的功能：\n\n### token校验\n\n```js\nmodule.exports = (options) => async (ctx, next) {\n  try {\n    // 获取 token\n    const token = ctx.header.authorization\n    if (token) {\n      try {\n          // verify 函数验证 token，并获取用户相关信息\n          await verify(token)\n      } catch (err) {\n        console.log(err)\n      }\n    }\n    // 进入下一个中间件\n    await next()\n  } catch (err) {\n    console.log(err)\n  }\n}\n```\n\n### 日志模块\n\n```js\nconst fs = require('fs')\nmodule.exports = (options) => async (ctx, next) => {\n  const startTime = Date.now()\n  const requestTime = new Date()\n  await next()\n  const ms = Date.now() - startTime;\n  let logout = `${ctx.request.ip} -- ${requestTime} -- ${ctx.method} -- ${ctx.url} -- ${ms}ms`;\n  // 输出日志文件\n  fs.appendFileSync('./log.txt', logout + '\\n')\n}\n```\n\n`Koa`存在很多第三方的中间件，如`koa-bodyparser`、`koa-static`等\n\n下面再来看看它们的大体的简单实现：\n\n### koa-bodyparser\n\n`koa-bodyparser` 中间件是将我们的 `post` 请求和表单提交的查询字符串转换成对象，并挂在 `ctx.request.body` 上，方便我们在其他中间件或接口处取值\n\n```js\n// 文件：my-koa-bodyparser.js\nconst querystring = require(\"querystring\");\n\nmodule.exports = function bodyParser() {\n    return async (ctx, next) => {\n        await new Promise((resolve, reject) => {\n            // 存储数据的数组\n            let dataArr = [];\n\n            // 接收数据\n            ctx.req.on(\"data\", data => dataArr.push(data));\n\n            // 整合数据并使用 Promise 成功\n            ctx.req.on(\"end\", () => {\n                // 获取请求数据的类型 json 或表单\n                let contentType = ctx.get(\"Content-Type\");\n\n                // 获取数据 Buffer 格式\n                let data = Buffer.concat(dataArr).toString();\n\n                if (contentType === \"application/x-www-form-urlencoded\") {\n                    // 如果是表单提交，则将查询字符串转换成对象赋值给 ctx.request.body\n                    ctx.request.body = querystring.parse(data);\n                } else if (contentType === \"applaction/json\") {\n                    // 如果是 json，则将字符串格式的对象转换成对象赋值给 ctx.request.body\n                    ctx.request.body = JSON.parse(data);\n                }\n\n                // 执行成功的回调\n                resolve();\n            });\n        });\n\n        // 继续向下执行\n        await next();\n    };\n};\n```\n\n\n\n### koa-static\n\n `koa-static` 中间件的作用是在服务器接到请求时，帮我们处理静态文件\n\n```js\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst mime = require(\"mime\");\nconst { promisify } = require(\"util\");\n\n// 将 stat 和 access 转换成 Promise\nconst stat = promisify(fs.stat);\nconst access = promisify(fs.access)\n\nmodule.exports = function (dir) {\n    return async (ctx, next) => {\n        // 将访问的路由处理成绝对路径，这里要使用 join 因为有可能是 /\n        let realPath = path.join(dir, ctx.path);\n\n        try {\n            // 获取 stat 对象\n            let statObj = await stat(realPath);\n\n            // 如果是文件，则设置文件类型并直接响应内容，否则当作文件夹寻找 index.html\n            if (statObj.isFile()) {\n                ctx.set(\"Content-Type\", `${mime.getType()};charset=utf8`);\n                ctx.body = fs.createReadStream(realPath);\n            } else {\n                let filename = path.join(realPath, \"index.html\");\n\n                // 如果不存在该文件则执行 catch 中的 next 交给其他中间件处理\n                await access(filename);\n\n                // 存在设置文件类型并响应内容\n                ctx.set(\"Content-Type\", \"text/html;charset=utf8\");\n                ctx.body = fs.createReadStream(filename);\n            }\n        } catch (e) {\n            await next();\n        }\n    }\n}\n```\n\n\n\n\n\n## 三、总结\n\n在实现中间件时候，单个中间件应该足够简单，职责单一，中间件的代码编写应该高效，必要的时候通过缓存重复获取数据\n\n`koa`本身比较简洁，但是通过中间件的机制能够实现各种所需要的功能，使得`web`应用具备良好的可拓展性和组合性\n\n通过将公共逻辑的处理编写在中间件中，可以不用在每一个接口回调中做相同的代码编写，减少了冗杂代码，过程就如装饰者模式\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000017897279\n- https://www.jianshu.com/p/81b6ebc0dd85\n- https://baike.baidu.com/item/%E4%B8%AD%E9%97%B4%E4%BB%B6"
  },
  {
    "path": "docs/NodeJS/nodejs.md",
    "content": "# 面试官：说说你对Node.js 的理解？优缺点？应用场景？\n\n ![](https://static.vue-js.com/b565d240-c1e6-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n`Node.js` 是一个开源与跨平台的 `JavaScript` 运行时环境\n\n在浏览器外运行 V8 JavaScript 引擎（Google Chrome 的内核），利用事件驱动、非阻塞和异步输入输出模型等技术提高性能\n\n可以理解为 `Node.js` 就是一个服务器端的、非阻塞式I/O的、事件驱动的`JavaScript`运行环境\n\n### 非阻塞异步 \n\n`Nodejs`采用了非阻塞型`I/O`机制，在做`I/O`操作的时候不会造成任何的阻塞，当完成之后，以时间的形式通知执行操作\n\n例如在执行了访问数据库的代码之后，将立即转而执行其后面的代码，把数据库返回结果的处理代码放在回调函数中，从而提高了程序的执行效率\n\n\n\n### 事件驱动\n\n事件驱动就是当进来一个新的请求的时，请求将会被压入一个事件队列中，然后通过一个循环来检测队列中的事件状态变化，如果检测到有状态变化的事件，那么就执行该事件对应的处理代码，一般都是回调函数\n\n比如读取一个文件，文件读取完毕后，就会触发对应的状态，然后通过对应的回调函数来进行处理\n\n ![](https://static.vue-js.com/a7729590-c1e8-11eb-ab90-d9ae814b240d.png)\n\n\n\n\n\n\n\n## 二、优缺点\n\n优点：\n\n- 处理高并发场景性能更佳\n- 适合I/O密集型应用，值的是应用在运行极限时，CPU占用率仍然比较低，大部分时间是在做 I/O硬盘内存读写操作\n\n因为`Nodejs`是单线程，带来的缺点有：\n\n- 不适合CPU密集型应用\n- 只支持单核CPU，不能充分利用CPU\n- 可靠性低，一旦代码某个环节崩溃，整个系统都崩溃\n\n\n\n\n\n## 三、应用场景\n\n借助`Nodejs`的特点和弊端，其应用场景分类如下：\n\n- 善于`I/O`，不善于计算。因为Nodejs是一个单线程，如果计算（同步）太多，则会阻塞这个线程\n- 大量并发的I/O，应用程序内部并不需要进行非常复杂的处理\n- 与 websocket 配合，开发长连接的实时交互应用程序\n\n具体场景可以表现为如下：\n\n- 第一大类：用户表单收集系统、后台管理系统、实时交互系统、考试系统、联网软件、高并发量的web应用程序\n- 第二大类：基于web、canvas等多人联网游戏\n- 第三大类：基于web的多人实时聊天客户端、聊天室、图文直播\n- 第四大类：单页面浏览器应用程序\n- 第五大类：操作数据库、为前端和移动端提供基于`json`的API\n\n其实，`Nodejs`能实现几乎一切的应用，只考虑适不适合使用它\n\n\n\n## 参考文献\n\n- http://nodejs.cn/\n- https://segmentfault.com/a/1190000019854308\n- https://segmentfault.com/a/1190000005173218"
  },
  {
    "path": "docs/NodeJS/paging.md",
    "content": "# 面试官：如果让你来设计一个分页功能, 你会怎么设计? 前后端如何交互?\n\n ![](https://static.vue-js.com/54b0a390-cf14-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n在我们做数据查询的时候，如果数据量很大，比如几万条数据，放在一个页面显示的话显然不友好，这时候就需要采用分页显示的形式，如每次只显示10条数据\n\n![](https://static.vue-js.com/6070e8c0-cf14-11eb-85f6-6fac77c0c9b3.png)\n\n要实现分页功能，实际上就是从结果集中显示第1~10条记录作为第1页，显示第11~20条记录作为第2页，以此类推\n\n因此，分页实际上就是从结果集中截取出第M~N条记录\n\n\n## 二、如何实现\n\n前端实现分页功能，需要后端返回必要的数据，如总的页数，总的数据量，当前页，当前的数据\n\n```js\n{\n \"totalCount\": 1836,   // 总的条数\n \"totalPages\": 92,  // 总页数\n \"currentPage\": 1   // 当前页数\n \"data\": [     // 当前页的数据\n   {\n ...\n   }\n]\n```\n\n后端采用`mysql`作为数据的持久性存储\n\n前端向后端发送目标的页码`page`以及每页显示数据的数量`pageSize`，默认情况每次取10条数据，则每一条数据的起始位置`start`为：\n\n```js\nconst start = (page - 1) * pageSize\n```\n\n当确定了`limit`和`start`的值后，就能够确定`SQL`语句：\n\n```JS\nconst sql = `SELECT * FROM record limit ${pageSize} OFFSET ${start};`\n```\n\n上诉`SQL`语句表达的意思为：截取从`start`到`start`+`pageSize`之间（左闭右开）的数据\n\n关于查询数据总数的`SQL`语句为，`record`为表名：\n\n```mysql\nSELECT COUNT(*) FROM record\n```\n\n因此后端的处理逻辑为：\n\n- 获取用户参数页码数page和每页显示的数目 pageSize ，其中page 是必须传递的参数，pageSize为可选参数，默认为10\n- 编写 SQL 语句，利用 limit 和 OFFSET 关键字进行分页查询\n- 查询数据库，返回总数据量、总页数、当前页、当前页数据给前端\n\n代码如下所示：\n\n```js\nrouter.all('/api', function (req, res, next) {\n  var param = '';\n  // 获取参数\n  if (req.method == \"POST\") {\n    param = req.body;\n  } else {\n    param = req.query || req.params;\n  }\n  if (param.page == '' || param.page == null || param.page == undefined) {\n    res.end(JSON.stringify({ msg: '请传入参数page', status: '102' }));\n    return;\n  }\n  const pageSize = param.pageSize || 10;\n  const start = (param.page - 1) * pageSize;\n  const sql = `SELECT * FROM record limit ${pageSize} OFFSET ${start};`\n  pool.getConnection(function (err, connection) {\n    if (err) throw err;\n    connection.query(sql, function (err, results) {\n      connection.release();\n      if (err) {\n        throw err\n      } else {\n        // 计算总页数\n        var allCount = results[0][0]['COUNT(*)'];\n        var allPage = parseInt(allCount) / 20;\n        var pageStr = allPage.toString();\n        // 不能被整除\n        if (pageStr.indexOf('.') > 0) {\n          allPage = parseInt(pageStr.split('.')[0]) + 1;\n        }\n        var list = results[1];\n        res.end(JSON.stringify({ msg: '操作成功', status: '200', totalPages: allPage, currentPage: param.page, totalCount: allCount, data: list }));\n      }\n    })\n  })\n});\n```\n\n\n\n## 三、总结\n\n通过上面的分析，可以看到分页查询的关键在于，要首先确定每页显示的数量`pageSize`，然后根据当前页的索引`pageIndex`（从1开始），确定`LIMIT`和`OFFSET`应该设定的值：\n\n- LIMIT 总是设定为 pageSize\n- OFFSET 计算公式为 pageSize * (pageIndex - 1)\n\n确定了这两个值，就能查询出第 `N`页的数据\n\n\n## 参考文献\n\n- https://www.liaoxuefeng.com/wiki/1177760294764384/1217864791925600\n- https://vue3js.cn/interview/\n"
  },
  {
    "path": "docs/NodeJS/performance.md",
    "content": "# 面试官：Node性能如何进行监控以及优化？\n\n ![](https://static.vue-js.com/bb37dae0-d179-11eb-ab90-d9ae814b240d.png)\n\n## 一、 是什么\n\n`Node`作为一门服务端语言，性能方面尤为重要，其衡量指标一般有如下：\n\n- CPU\n- 内存\n- I/O\n- 网络\n\n\n### CPU\n\n主要分成了两部分：\n\n- CPU负载：在某个时间段内，占用以及等待CPU的进程总数\n- CPU使用率：CPU时间占用状况，等于 1 - 空闲CPU时间(idle time) / CPU总时间\n\n这两个指标都是用来评估系统当前CPU的繁忙程度的量化指标\n\n`Node`应用一般不会消耗很多的`CPU`，如果`CPU`占用率高，则表明应用存在很多同步操作，导致异步任务回调被阻塞\n\n\n\n### 内存指标\n\n内存是一个非常容易量化的指标。 内存占用率是评判一个系统的内存瓶颈的常见指标。 对于Node来说，内部内存堆栈的使用状态也是一个可以量化的指标\n\n```js\n// /app/lib/memory.js\nconst os = require('os');\n// 获取当前Node内存堆栈情况\nconst { rss, heapUsed, heapTotal } = process.memoryUsage();\n// 获取系统空闲内存\nconst sysFree = os.freemem();\n// 获取系统总内存\nconst sysTotal = os.totalmem();\n\nmodule.exports = {\n  memory: () => {\n    return {\n      sys: 1 - sysFree / sysTotal,  // 系统内存占用率\n      heap: heapUsed / headTotal,   // Node堆内存占用率\n      node: rss / sysTotal,         // Node占用系统内存的比例\n    }\n  }\n}\n```\n\n- rss：表示node进程占用的内存总量。\n- heapTotal：表示堆内存的总量。\n- heapUsed：实际堆内存的使用量。\n- external ：外部程序的内存使用量，包含Node核心的C++程序的内存使用量\n\n在`Node`中，一个进程的最大内存容量为1.5GB。因此我们需要减少内存泄露\n\n\n\n### 磁盘 I/O\n\n硬盘的` IO` 开销是非常昂贵的，硬盘 IO 花费的 CPU 时钟周期是内存的 164000 倍\n\n内存 `IO `比磁盘` IO` 快非常多，所以使用内存缓存数据是有效的优化方法。常用的工具如 `redis`、`memcached `等\n\n并不是所有数据都需要缓存，访问频率高，生成代价比较高的才考虑是否缓存，也就是说影响你性能瓶颈的考虑去缓存，并且而且缓存还有缓存雪崩、缓存穿透等问题要解决\n\n\n\n## 二、如何监控\n\n关于性能方面的监控，一般情况都需要借助工具来实现\n\n这里采用`Easy-Monitor 2.0`，其是轻量级的 `Node.js` 项目内核性能监控 + 分析工具，在默认模式下，只需要在项目入口文件 `require` 一次，无需改动任何业务代码即可开启内核级别的性能监控分析\n\n使用方法如下：\n\n在你的项目入口文件中按照如下方式引入，当然请传入你的项目名称：\n\n```js\nconst easyMonitor = require('easy-monitor');\neasyMonitor('你的项目名称');\n```\n\n打开你的浏览器，访问 `http://localhost:12333` ，即可看到进程界面\n\n关于定制化开发、通用配置项以及如何动态更新配置项详见官方文档\n\n\n\n## 三、如何优化\n\n关于`Node`的性能优化的方式有：\n\n- 使用最新版本Node.js\n- 正确使用流 Stream\n- 代码层面优化\n- 内存管理优化\n\n\n\n### 使用最新版本Node.js\n\n每个版本的性能提升主要来自于两个方面：\n\n- V8 的版本更新\n- Node.js 内部代码的更新优化\n\n\n\n### 正确使用流 Stream\n\n在`Node`中，很多对象都实现了流，对于一个大文件可以通过流的形式发送，不需要将其完全读入内存\n\n```js\nconst http = require('http');\nconst fs = require('fs');\n\n// bad\nhttp.createServer(function (req, res) {\n    fs.readFile(__dirname + '/data.txt', function (err, data) {\n        res.end(data);\n    });\n});\n\n// good\nhttp.createServer(function (req, res) {\n    const stream = fs.createReadStream(__dirname + '/data.txt');\n    stream.pipe(res);\n});\n```\n\n\n\n### 代码层面优化\n\n合并查询，将多次查询合并一次，减少数据库的查询次数\n\n```js\n// bad\nfor user_id in userIds \n     let account = user_account.findOne(user_id)\n\n// good\nconst user_account_map = {}   // 注意这个对象将会消耗大量内存。\nuser_account.find(user_id in user_ids).forEach(account){\n    user_account_map[account.user_id] =  account\n}\nfor user_id in userIds \n    var account = user_account_map[user_id]\n```\n\n\n\n### 内存管理优化\n\n在 V8 中，主要将内存分为新生代和老生代两代：\n\n- 新生代：对象的存活时间较短。新生对象或只经过一次垃圾回收的对象\n- 老生代：对象存活时间较长。经历过一次或多次垃圾回收的对象\n\n若新生代内存空间不够，直接分配到老生代\n\n通过减少内存占用，可以提高服务器的性能。如果有内存泄露，也会导致大量的对象存储到老生代中，服务器性能会大大降低\n\n如下面情况：\n\n```js\nconst buffer = fs.readFileSync(__dirname + '/source/index.htm');\n\napp.use(\n    mount('/', async (ctx) => {\n        ctx.status = 200;\n        ctx.type = 'html';\n        ctx.body = buffer;\n        leak.push(fs.readFileSync(__dirname + '/source/index.htm'));\n    })\n);\n\nconst leak = [];\n```\n\n`leak`的内存非常大，造成内存泄露，应当避免这样的操作，通过减少内存使用，是提高服务性能的手段之一\n\n而节省内存最好的方式是使用池，其将频用、可复用对象存储起来，减少创建和销毁操作\n\n例如有个图片请求接口，每次请求，都需要用到类。若每次都需要重新new这些类，并不是很合适，在大量请求时，频繁创建和销毁这些类，造成内存抖动\n\n使用对象池的机制，对这种频繁需要创建和销毁的对象保存在一个对象池中。每次用到该对象时，就取对象池空闲的对象，并对它进行初始化操作，从而提高框架的性能\n\n\n\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000039327565\n- https://zhuanlan.zhihu.com/p/50055740\n- https://segmentfault.com/a/1190000010231628\n"
  },
  {
    "path": "docs/NodeJS/process.md",
    "content": "# 面试官：说说对 Node 中的 process 的理解？有哪些常用方法？\n\n![](https://static.vue-js.com/4f7866b0-c2b2-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n`process` 对象是一个全局变量，提供了有关当前 `Node.js `进程的信息并对其进行控制，作为一个全局变量\n\n我们都知道，进程计算机系统进行资源分配和调度的基本单位，是操作系统结构的基础，是线程的容器\n\n当我们启动一个`js`文件，实际就是开启了一个服务进程，每个进程都拥有自己的独立空间地址、数据栈，像另一个进程无法访问当前进程的变量、数据结构，只有数据通信后，进程之间才可以数据共享\n\n由于`JavaScript`是一个单线程语言，所以通过`node xxx`启动一个文件后，只有一条主线程\n\n\n\n\n\n## 二、属性与方法\n\n关于`process`常见的属性有如下：\n\n- process.env：环境变量，例如通过 `process.env.NODE_ENV 获取不同环境项目配置信息\n- process.nextTick：这个在谈及 `EventLoop` 时经常为会提到\n- process.pid：获取当前进程id\n- process.ppid：当前进程对应的父进程\n- process.cwd()：获取当前进程工作目录，\n- process.platform：获取当前进程运行的操作系统平台\n- process.uptime()：当前进程已运行时间，例如：pm2 守护进程的 uptime 值\n- 进程事件： process.on(‘uncaughtException’,cb) 捕获异常信息、 process.on(‘exit’,cb）进程推出监听\n- 三个标准流： process.stdout 标准输出、 process.stdin 标准输入、 process.stderr 标准错误输出\n- process.title 指定进程名称，有的时候需要给进程指定一个名称\n\n\n\n下面再稍微介绍下某些方法的使用：\n\n### process.cwd()\n\n返回当前 `Node `进程执行的目录\n\n一个` Node` 模块 `A` 通过 NPM 发布，项目 `B` 中使用了模块 `A`。在 `A` 中需要操作 `B` 项目下的文件时，就可以用 `process.cwd()` 来获取 `B` 项目的路径\n\n\n\n### process.argv\n\n在终端通过 Node 执行命令的时候，通过 `process.argv` 可以获取传入的命令行参数，返回值是一个数组：\n\n- 0: Node 路径（一般用不到，直接忽略）\n- 1: 被执行的 JS 文件路径（一般用不到，直接忽略）\n- 2~n: 真实传入命令的参数\n\n所以，我们只要从 `process.argv[2]` 开始获取就好了\n\n```js\nconst args = process.argv.slice(2);\n```\n\n\n\n### process.env\n\n返回一个对象，存储当前环境相关的所有信息，一般很少直接用到。\n\n一般我们会在 `process.env` 上挂载一些变量标识当前的环境。比如最常见的用 `process.env.NODE_ENV` 区分 `development` 和 `production`\n\n在 `vue-cli` 的源码中也经常会看到 `process.env.VUE_CLI_DEBUG` 标识当前是不是 `DEBUG` 模式\n\n\n\n### process.nextTick()\n\n我们知道`NodeJs`是基于事件轮询，在这个过程中，同一时间只会处理一件事情\n\n在这种处理模式下，`process.nextTick()`就是定义出一个动作，并且让这个动作在下一个事件轮询的时间点上执行\n\n例如下面例子将一个`foo`函数在下一个时间点调用\n\n```js\nfunction foo() {\n    console.error('foo');\n}\n\nprocess.nextTick(foo);\nconsole.error('bar');\n```\n\n输出结果为`bar`、`foo`\n\n虽然下述方式也能实现同样效果：\n\n```js\nsetTimeout(foo, 0);\nconsole.log('bar');\n```\n\n两者区别在于：\n\n- process.nextTick()会在这一次event loop的call stack清空后（下一次event loop开始前）再调用callback\n- setTimeout()是并不知道什么时候call stack清空的，所以何时调用callback函数是不确定的\n\n\n\n\n\n### 参考文献\n\n- http://nodejs.cn/api/process.html\n- https://vue3js.cn/interview/\n"
  },
  {
    "path": "docs/NodeJS/require_order.md",
    "content": "# 面试官：说说 Node 文件查找的优先级以及 Require 方法的文件查找策略?\n\n ![](https://static.vue-js.com/15913530-c9ba-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、模块规范\n\n`NodeJS`对`CommonJS`进行了支持和实现，让我们在开发`node`的过程中可以方便的进行模块化开发：\n\n- 在Node中每一个js文件都是一个单独的模块\n- 模块中包括CommonJS规范的核心变量：exports、module.exports、require\n- 通过上述变量进行模块化开发\n\n而模块化的核心是导出与导入，在`Node`中通过`exports`与`module.exports`负责对模块中的内容进行导出，通过`require`函数导入其他模块（自定义模块、系统模块、第三方库模块）中的内容\n\n\n\n## 二、查找策略\n\n`require`方法接收一下几种参数的传递：\n\n- 原生模块：http、fs、path等\n- 相对路径的文件模块：./mod或../mod\n- 绝对路径的文件模块：/pathtomodule/mod\n- 目录作为模块：./dirname\n- 非原生模块的文件模块：mod\n\n`require`参数较为简单，但是内部的加载却是十分复杂的，其加载优先级也各自不同，如下图：\n\n ![](https://static.vue-js.com/33ae8ef0-c9ba-11eb-85f6-6fac77c0c9b3.png)\n\n从上图可以看见，文件模块存在缓存区，寻找模块路径的时候都会优先从缓存中加载已经存在的模块\n\n\n\n### 原生模块\n\n而像原生模块这些，通过`require `方法在解析文件名之后，优先检查模块是否在原生模块列表中，如果在则从原生模块中加载\n\n\n\n### 绝对路径、相对路径\n\n如果`require`绝对路径的文件，则直接查找对应的路径，速度最快\n\n相对路径的模块则相对于当前调用`require`的文件去查找\n\n如果按确切的文件名没有找到模块，则 `NodeJs` 会尝试带上 `.js`、`.json `或 `.node `拓展名再加载\n\n\n### 目录作为模块\n\n默认情况是根据根目录中`package.json`文件的`main`来指定目录模块，如：\n\n```json\n{ \"name\" : \"some-library\",\n  \"main\" : \"main.js\" }\n```\n\n如果这是在` ./some-library node_modules `目录中，则 `require('./some-library')` 会试图加载 `./some-library/main.js`\n\n如果目录里没有 `package.json`文件，或者 `main`入口不存在或无法解析，则会试图加载目录下的 `index.js` 或 `index.node` 文件\n\n\n\n\n\n### 非原生模块\n\n在每个文件中都存在`module.paths`，表示模块的搜索路径，`require`就是根据其来寻找文件\n\n在`window`下输出如下：\n\n```js\n[ 'c:\\\\nodejs\\\\node_modules',\n'c:\\\\node_modules' ]\n```\n\n可以看出`module path`的生成规则为：从当前文件目录开始查找`node_modules`目录；然后依次进入父目录，查找父目录下的`node_modules`目录，依次迭代，直到根目录下的`node_modules`目录\n\n当都找不到的时候，则会从系统`NODE_PATH`环境变量查找\n\n#### 举个例子：\n\n如果在`/home/ry/projects/foo.js`文件里调用了 `require('bar.js')`，则 Node.js 会按以下顺序查找：\n\n- /home/ry/projects/node_modules/bar.js\n- /home/ry/node_modules/bar.js\n- /home/node_modules/bar.js\n- /node_modules/bar.js\n\n这使得程序本地化它们的依赖，避免它们产生冲突\n\n\n\n## 三、总结\n\n通过上面模块的文件查找策略之后，总结下文件查找的优先级：\n\n- 缓存的模块优先级最高\n\n- 如果是内置模块，则直接返回，优先级仅次缓存的模块\n- 如果是绝对路径 / 开头，则从根目录找\n- 如果是相对路径 ./开头，则从当前require文件相对位置找\n- 如果文件没有携带后缀，先从js、json、node按顺序查找\n- 如果是目录，则根据 package.json的main属性值决定目录下入口文件，默认情况为 index.js\n- 如果文件为第三方模块，则会引入 node_modules 文件，如果不在当前仓库文件中，则自动从上级递归查找，直到根目录\n\n\n\n## 参考文献\n\n- http://nodejs.cn/api/modules.html#modules_file_modules\n- https://blog.csdn.net/qq_36801250/article/details/106352686\n- https://www.cnblogs.com/samve/p/10805908.html"
  },
  {
    "path": "docs/README.md",
    "content": "\n<p align=\"center\">\n<img width=\"300px\"  style=\"max-width:300px; width:300px;\" src=\"https://static.vue-js.com/21494410-8e11-11eb-ab90-d9ae814b240d.png\"/>\n</p>\n<p align=\"center\">\n  <a href=\"https://github.com/febobo/web-interview/stargazers\"><img src=\"https://img.shields.io/redmine/plugin/stars/redmine_xlsx_format_issue_exporter.svg\" alt=\"Statr\"></a>\n<a href=\"https://github.com/febobo/web-interview/issues\"><img src=\"https://img.shields.io/github/languages/top/badges/shields.svg?label=vue\" alt=\"vue\"></a>\n<a href=\"https://github.com/febobo/web-interview/issues\"><img src=\"https://img.shields.io/github/languages/top/badges/shields.svg?label=es6\" alt=\"es6\"></a>\n  <a href=\"https://github.com/febobo/web-interview/issues\"><img src=\"https://img.shields.io/github/languages/top/badges/shields.svg?label=css\" alt=\"css\"></a>\n  <a href=\"https://github.com/febobo/web-interview/issues\"><img src=\"https://img.shields.io/github/languages/top/badges/shields.svg?label=javascript\" alt=\"javascript\"></a>\n</p>\n\n本仓库为语音打卡社群(JS每日一题)维护的前端面试题库，包含不限于Vue面试题，React面试题，JS面试题，HTTP面试题，工程化面试题，CSS面试题，算法面试题，大厂面试题，高频面试题\n\n同时我们提供更好阅读体验的在线版本，[点这里](https://vue3js.cn/interview)   \n点击[@AD](https://vue3js.cn/assets/img/18141744641588_.pic.jpg)添加好友，获取最新面试真题，加前端500人大群\n\n避免失联可以先点star\n\nPS: 仓库每周一更，想要及时收到推送，交流想法的同学，可以点击[@公众号:JS每日一题](https://static.vue-js.com/b4b71a30-443b-11eb-85f6-6fac77c0c9b3.png)关注我们\n\n##  🤠 为什么要做这样的一个仓库\n最开始是没有想要弄仓库的，我们通过微信群语音的形式进行每天一次的打卡，次日再通过[公众号](https://static.vue-js.com/b4b71a30-443b-11eb-85f6-6fac77c0c9b3.png)图文形式的题解推送至群内供大家复盘总结\n\n随着时间的推移，题也积累的越来越多，再去通过公众号检索信息效率会明显降低\n\n这个时候我们就想着通过开源的形式，将我们总结好的文章以多种形态的呈现方式帮助到更多需要的人\n\n## 🤡 这个仓库主要内容是?\n\n仓库目前主题以前端面试题为主，后续可能会扩展更多的分支\n\n在这个信息爆炸，前端生态百花齐放的时代，我们每找寻一个答案都要换一种形式，这简直太奢侈了\n\n我们的目标是做最全最好最有质量前端面试仓库，用心收录大厂面试题，高频面试题，知识点面试题，用心做好每一道题值得参考的题解\n\n## 🤡 你们的本质就是面试题吗？\n\n不是，一直在强调语音社群，这个仓库也仅且是一个附属品\n\n我们只是以面试题的形式作为切入点，根据面试题去反推学习更具体的知识点，绝不仅限于应对面试，要真真切切在学习过程中得到蜕变\n\n社群内每天的打卡都是一次思维整理输出的训练，将自己想要表达的内容放入框架中再进行输出，让我们的表达更简洁更系统\n\n它的背后是思考，总结，更是坚持\n\n在过程中看清自已，在结果中遇见自己，我们都值得有更美好的未来\n\n再晒晒平常我们的样子\n\n<p>\n<img width=\"600px\"  style=\"max-width:600px; width:600px;\" src=\"https://user-images.githubusercontent.com/9276376/102876713-90c0f700-4480-11eb-880f-613f8f0f5042.png\"/>\n</p>\n\n如果看到这里你也有兴趣，欢迎扫描下方二维码加入我们，持续成长\n\n<p>\n  <img  width=\"200px\" style=\"max-width:200px; width:200px; height: 200px\" src=\"https://static.vue-js.com/b4b71a30-443b-11eb-85f6-6fac77c0c9b3.png\"/>\n</p>\n\n\n\n## 🤡 内容\n\n点击[@公众号:JS每日一题](https://static.vue-js.com/b4b71a30-443b-11eb-85f6-6fac77c0c9b3.png)扫码关注，跟踪我们最新动态\n\n\n\n<h3 style=\"text-align: center\" align=\"center\">Vue系列</h3>\n\n- [面试官：说说你对vue的理解?](https://github.com/febobo/web-interview/issues/1)\n- [面试官：说说你对双向绑定的理解?](https://github.com/febobo/web-interview/issues/2)\n- [面试官：说说你对SPA（单页应用）的理解?](https://github.com/febobo/web-interview/issues/3)\n- [面试官：Vue中的v-show和v-if怎么理解？](https://github.com/febobo/web-interview/issues/4)\n- [面试官：Vue实例挂载的过程中发生了什么?](https://github.com/febobo/web-interview/issues/5)\n- [面试官：说说你对Vue生命周期的理解?](https://github.com/febobo/web-interview/issues/6)\n- [面试官：为什么Vue中的v-if和v-for不建议一起用?](https://github.com/febobo/web-interview/issues/7)\n- [面试官：SPA（单页应用）首屏加载速度慢怎么解决？?](https://github.com/febobo/web-interview/issues/8)\n- [面试官：为什么data属性是一个函数而不是一个对象？](https://github.com/febobo/web-interview/issues/9)\n- [面试官：Vue中给对象添加新属性界面不刷新?](https://github.com/febobo/web-interview/issues/10)\n- [面试官：Vue中组件和插件有什么区别](https://github.com/febobo/web-interview/issues/11)\n- [面试官：Vue组件间通信方式都有哪些?](https://github.com/febobo/web-interview/issues/12)\n- [面试官：说说你对nexttick的理解?](https://github.com/febobo/web-interview/issues/14)\n- [面试官：说说你对vue的mixin的理解，有什么应用场景？](https://github.com/febobo/web-interview/issues/15)\n- [面试官：说说你对slot的理解？slot使用场景有哪些？](https://github.com/febobo/web-interview/issues/16)\n- [面试官：Vue.observable你有了解过吗？说说看](https://github.com/febobo/web-interview/issues/17)\n- [面试官：你知道vue中key的原理吗？说说你对它的理解？](https://github.com/febobo/web-interview/issues/18)\n- [面试官：怎么缓存当前的组件？缓存后怎么更新？说说你对keep-alive的理解是什么？](https://github.com/febobo/web-interview/issues/19)\n- [面试官：Vue常用的修饰符有哪些？有什么应用场景？](https://github.com/febobo/web-interview/issues/20)\n- [面试官：你有写过自定义指令吗？自定义指令的应用场景有哪些？](https://github.com/febobo/web-interview/issues/21)\n- [面试官：Vue中的过滤器了解吗？过滤器的应用场景有哪些？](https://github.com/febobo/web-interview/issues/22)\n- [面试官：什么是虚拟DOM？如何实现一个虚拟DOM？说说你的思路](https://github.com/febobo/web-interview/issues/23)\n- [面试官：了解过vue中的diff算法吗？说说看](https://github.com/febobo/web-interview/issues/24)\n- [面试官：Vue项目中有封装过axios吗？怎么封装的？](https://github.com/febobo/web-interview/issues/25)\n- [面试官：你了解Axios的原理吗？有看过它的源码吗？](https://github.com/febobo/web-interview/issues/26)\n- [面试官：SSR解决了什么问题？有做过SSR吗？你是怎么做的？](https://github.com/febobo/web-interview/issues/27)\n- [面试官：说下你的Vue项目的目录结构，如果是大型项目你该怎么划分结构和划分组件呢？](https://github.com/febobo/web-interview/issues/28)\n- [面试官：Vue要做权限管理该怎么做？控制到按钮级别的权限怎么做？](https://github.com/febobo/web-interview/issues/29)\n- [面试官：跨域是什么？Vue项目中你是如何解决跨域的呢？](https://github.com/febobo/web-interview/issues/30)\n- [面试官：Vue项目如何部署？有遇到布署服务器后刷新404问题吗？](https://github.com/febobo/web-interview/issues/31)\n- [面试官：你是怎么处理vue项目中的错误的？](https://github.com/febobo/web-interview/issues/32)\n- [面试官：Vue3有了解过吗？能说说跟Vue2的区别吗？](https://github.com/febobo/web-interview/issues/33)\n\n<h3 style=\"text-align: center\" align=\"center\">Vue3系列</h3>\n\n- [面试官：Vue3.0的设计目标是什么？做了哪些优化?](https://github.com/febobo/web-interview/issues/45)\n- [面试官：Vue3.0 性能提升主要是通过哪几方面体现的？](https://github.com/febobo/web-interview/issues/46)\n- [面试官：Vue3.0里为什么要用 Proxy API 替代 defineProperty API ？](https://github.com/febobo/web-interview/issues/47)\n- [面试官：Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同？](https://github.com/febobo/web-interview/issues/48)\n- [面试官：说说Vue 3.0中Treeshaking特性？举例说明一下？](https://github.com/febobo/web-interview/issues/67)\n- [面试官：用Vue3.0 写过组件吗？如果想实现一个 Modal你会怎么设计？](https://github.com/febobo/web-interview/issues/50)\n\n<h3 style=\"text-align: center\" align=\"center\">ES6系列</h3>\n\n- [面试官：说说var、let、const之间的区别](https://github.com/febobo/web-interview/issues/34)\n- [面试官：ES6中数组新增了哪些扩展?](https://github.com/febobo/web-interview/issues/35)\n- [面试官：ES6中对象新增了哪些扩展?](https://github.com/febobo/web-interview/issues/36)\n- [面试官：ES6中函数新增了哪些扩展?](https://github.com/febobo/web-interview/issues/37)\n- [面试官：ES6中新增的Set、Map两种数据结构怎么理解?](https://github.com/febobo/web-interview/issues/38)\n- [面试官：你是怎么理解ES6中 Promise的？使用场景？](https://github.com/febobo/web-interview/issues/40)\n- [面试官：怎么理解ES6中 Generator的？使用场景？](https://github.com/febobo/web-interview/issues/41)\n- [面试官：你是怎么理解ES6中Proxy的？使用场景?](https://github.com/febobo/web-interview/issues/42)\n- [面试官：你是怎么理解ES6中Module的？使用场景？](https://github.com/febobo/web-interview/issues/43)\n- [面试官：你是怎么理解ES6中 Decorator 的？使用场景？](https://github.com/febobo/web-interview/issues/44)\n\n<h3 style=\"text-align: center\" align=\"center\">Javascript系列</h3>\n\n- [面试官：说说Javascript中的数据类型？区别？](https://github.com/febobo/web-interview/issues/51)\n- [面试官：Javscript数组的常用方法有哪些？](https://github.com/febobo/web-interview/issues/52)\n- [面试官：Javascript字符串的常用方法有哪些？](https://github.com/febobo/web-interview/issues/53)\n- [面试官：谈谈 Javascript 中的类型转换机制](https://github.com/febobo/web-interview/issues/54)\n- [面试官：== 和 ===区别，分别在什么情况使用](https://github.com/febobo/web-interview/issues/55)\n- [面试官：深拷贝浅拷贝的区别？如何实现一个深拷贝？](https://github.com/febobo/web-interview/issues/56)\n- [面试官：说说你对闭包的理解](https://github.com/febobo/web-interview/issues/57)\n- [面试官：说说你对作用域链的理解](https://github.com/febobo/web-interview/issues/58)\n- [面试官：JavaScript原型，原型链 ? 有什么特点？](https://github.com/febobo/web-interview/issues/59)\n- [面试官：Javascript如何实现继承？](https://github.com/febobo/web-interview/issues/60)\n- [面试官：谈谈this对象的理解](https://github.com/febobo/web-interview/issues/62)\n- [面试官：JavaScript中执行上下文和执行栈是什么？](https://github.com/febobo/web-interview/issues/63)\n- [面试官：说说JavaScript中的事件模型](https://github.com/febobo/web-interview/issues/64)\n- [面试官：typeof 与 instanceof 区别](https://github.com/febobo/web-interview/issues/65)\n- [面试官：解释下什么是事件代理？应用场景？](https://github.com/febobo/web-interview/issues/66)\n- [面试官：说说new操作符具体干了什么？](https://github.com/febobo/web-interview/issues/69)\n- [面试官：ajax原理是什么？如何实现？](https://github.com/febobo/web-interview/issues/70)\n- [面试官：bind、call、apply 区别？如何实现一个bind?](https://github.com/febobo/web-interview/issues/71)\n- [面试官：说说你对正则表达式的理解？应用场景？](https://github.com/febobo/web-interview/issues/72)\n- [面试官：说说你对事件循环的理解](https://github.com/febobo/web-interview/issues/73)\n- [面试官：DOM常见的操作有哪些？](https://github.com/febobo/web-interview/issues/75)\n- [面试官：说说你对BOM的理解，常见的BOM对象你了解哪些？](https://github.com/febobo/web-interview/issues/76)\n- [面试官：举例说明你对尾递归的理解，有哪些应用场景](https://github.com/febobo/web-interview/issues/77)\n- [面试官：说说 JavaScript 中内存泄漏的几种情况？](https://github.com/febobo/web-interview/issues/78)\n- [面试官：Javascript本地存储的方式有哪些？区别及应用场景？](https://github.com/febobo/web-interview/issues/79)\n- [面试官：说说你对函数式编程的理解？优缺点？](https://github.com/febobo/web-interview/issues/80)\n- [面试官：Javascript中如何实现函数缓存？函数缓存有哪些应用场景？](https://github.com/febobo/web-interview/issues/81)\n- [面试官：说说 Javascript 数字精度丢失的问题，如何解决？](https://github.com/febobo/web-interview/issues/82)\n- [面试官：什么是防抖和节流？有什么区别？如何实现？](https://github.com/febobo/web-interview/issues/83)\n- [面试官：如何判断一个元素是否在可视区域中？](https://github.com/febobo/web-interview/issues/84)\n- [面试官：大文件上传如何做断点续传？](https://github.com/febobo/web-interview/issues/89)\n- [面试官：如何实现上拉加载，下拉刷新？](https://github.com/febobo/web-interview/issues/90)\n- [面试官：什么是单点登录？如何实现？](https://github.com/febobo/web-interview/issues/91)\n- [面试官：web常见的攻击方式有哪些？如何防御？](https://github.com/febobo/web-interview/issues/92)\n\n\n\n<h3 style=\"text-align: center\">CSS系列</h3>\n\n- [面试官：说说你对盒子模型的理解?](https://github.com/febobo/web-interview/issues/93)\n- [面试官：css选择器有哪些？优先级？哪些属性可以继承？](https://github.com/febobo/web-interview/issues/95)\n- [面试官：说说em/px/rem/vh/vw区别?](https://github.com/febobo/web-interview/issues/96)\n- [面试官：说说设备像素、css像素、设备独立像素、dpr、ppi 之间的区别？](https://github.com/febobo/web-interview/issues/97)\n- [面试官：css中，有哪些方式可以隐藏页面元素？区别?](https://github.com/febobo/web-interview/issues/98)\n- [面试官：谈谈你对BFC的理解？](https://github.com/febobo/web-interview/issues/99)\n- [面试官：元素水平垂直居中的方法有哪些？如果元素不定宽高呢？](https://github.com/febobo/web-interview/issues/102)\n- [面试官：如何实现两栏布局，右侧自适应？三栏布局中间自适应呢？](https://github.com/febobo/web-interview/issues/103)\n- [面试官：说说flexbox（弹性盒布局模型）,以及适用场景？](https://github.com/febobo/web-interview/issues/104)\n- [面试官：介绍一下grid网格布局](https://github.com/febobo/web-interview/issues/105)\n- [面试官：CSS3新增了哪些新特性？](https://github.com/febobo/web-interview/issues/106)\n- [面试官：css3动画有哪些？](https://github.com/febobo/web-interview/issues/109)\n- [面试官：怎么理解回流跟重绘？什么场景下会触发？](https://github.com/febobo/web-interview/issues/107)\n- [面试官：什么是响应式设计？响应式设计的基本原理是什么？如何做？](https://github.com/febobo/web-interview/issues/108)\n- [面试官：如果要做优化，CSS提高性能的方法有哪些？](https://github.com/febobo/web-interview/issues/114)\n- [面试官：如何实现单行／多行文本溢出的省略样式？](https://github.com/febobo/web-interview/issues/115)\n- [面试官：如何使用css完成视差滚动效果?](https://github.com/febobo/web-interview/issues/116)\n- [面试官：CSS如何画一个三角形？原理是什么？](https://github.com/febobo/web-interview/issues/117)\n- [面试官：让Chrome支持小于12px 的文字方式有哪些？区别？](https://github.com/febobo/web-interview/issues/118)\n- [面试官：说说对Css预编语言的理解？有哪些区别?](https://github.com/febobo/web-interview/issues/119)\n\n<h3 style=\"text-align: center\">webpack系列</h3>\n\n- [面试官：说说你对webpack的理解？解决了什么问题？](https://github.com/febobo/web-interview/issues/121)\n- [面试官：说说webpack的构建流程?](https://github.com/febobo/web-interview/issues/122)\n- [面试官：说说webpack中常见的Loader？解决了什么问题？](https://github.com/febobo/web-interview/issues/123)\n- [面试官：说说webpack中常见的Plugin？解决了什么问题？](https://github.com/febobo/web-interview/issues/124)\n- [面试官：说说Loader和Plugin的区别？编写Loader，Plugin的思路？](https://github.com/febobo/web-interview/issues/125)\n- [面试官：说说webpack的热更新是如何做到的？原理是什么？](https://github.com/febobo/web-interview/issues/126)\n- [面试官：说说webpack proxy工作原理？为什么能解决跨域?](https://github.com/febobo/web-interview/issues/130)\n- [面试官：说说如何借助webpack来优化前端性能？](https://github.com/febobo/web-interview/issues/131)\n- [面试官：如何提高webpack的构建速度？](https://github.com/febobo/web-interview/issues/132)\n- [面试官：与webpack类似的工具还有哪些？区别？](https://github.com/febobo/web-interview/issues/133)\n\n<h3 style=\"text-align: center\">HTTP系列</h3>\n\n- [面试官：什么是HTTP? HTTP 和 HTTPS 的区别?](https://github.com/febobo/web-interview/issues/134)\n- [面试官：为什么说HTTPS比HTTP安全? HTTPS是如何保证安全的？](https://github.com/febobo/web-interview/issues/135)\n- [面试官：如何理解UDP 和 TCP? 区别? 应用场景?](https://github.com/febobo/web-interview/issues/136)\n- [面试官：如何理解OSI七层模型?](https://github.com/febobo/web-interview/issues/139)\n- [面试官：如何理解TCP/IP协议?](https://github.com/febobo/web-interview/issues/140)\n- [面试官：DNS协议 是什么？说说DNS 完整的查询过程?](https://github.com/febobo/web-interview/issues/141)\n- [面试官：如何理解CDN？说说实现原理？](https://github.com/febobo/web-interview/issues/142)\n- [面试官：说说 HTTP1.0/1.1/2.0 的区别?](https://github.com/febobo/web-interview/issues/143)\n- [面试官：说说HTTP 常见的状态码有哪些，适用场景？](https://github.com/febobo/web-interview/issues/144)\n- [面试官：说一下 GET 和 POST 的区别？](https://github.com/febobo/web-interview/issues/145)\n- [面试官：说说 HTTP 常见的请求头有哪些? 作用？](https://github.com/febobo/web-interview/issues/149)\n- [面试官：说说地址栏输入 URL 敲下回车后发生了什么？](https://github.com/febobo/web-interview/issues/150)\n- [面试官：说说TCP为什么需要三次握手和四次挥手？](https://github.com/febobo/web-interview/issues/151)\n- [面试官：说说对WebSocket的理解？应用场景？](https://github.com/febobo/web-interview/issues/152)\n\n<h3 style=\"text-align: center\">NodeJS系列</h3>\n\n- [面试官：说说你对Node.js 的理解？优缺点？应用场景？](https://github.com/febobo/web-interview/issues/153)\n- [面试官：说说 Node. js 有哪些全局对象？](https://github.com/febobo/web-interview/issues/154)\n- [面试官：说说对 Node 中的 process 的理解？有哪些常用方法？](https://github.com/febobo/web-interview/issues/155)\n- [面试官：说说对 Node 中的 fs模块的理解? 有哪些常用方法](https://github.com/febobo/web-interview/issues/156)\n- [面试官：说说对 Node 中的 Buffer 的理解？应用场景？](https://github.com/febobo/web-interview/issues/164)\n- [面试官：说说对 Node 中的 Stream 的理解？应用场景？](https://github.com/febobo/web-interview/issues/165)\n- [面试官：说说Node中的EventEmitter? 如何实现一个EventEmitter?](https://github.com/febobo/web-interview/issues/166)\n- [面试官：说说对Nodejs中的事件循环机制理解?](https://github.com/febobo/web-interview/issues/167)\n- [面试官：说说 Node 文件查找的优先级以及 Require 方法的文件查找策略?](https://github.com/febobo/web-interview/issues/168)\n- [面试官：说说对中间件概念的理解，如何封装 node 中间件？](https://github.com/febobo/web-interview/issues/169)\n- [面试官：如何实现jwt鉴权机制？说说你的思路](https://github.com/febobo/web-interview/issues/170)\n- [面试官：如何实现文件上传？说说你的思路](https://github.com/febobo/web-interview/issues/171)\n- [面试官：如果让你来设计一个分页功能, 你会怎么设计? 前后端如何交互?](https://github.com/febobo/web-interview/issues/172)\n- [面试官：Node性能如何进行监控以及优化？](https://github.com/febobo/web-interview/issues/173)\n\n<h3 style=\"text-align: center\">React系列</h3>\n\n- [面试官：说说对React的理解？有哪些特性？](https://github.com/febobo/web-interview/issues/180)\n- [面试官：说说 Real DOM和 Virtual DOM 的区别？优缺点？](https://github.com/febobo/web-interview/issues/181)\n- [面试官：说说 React 生命周期有哪些不同阶段？每个阶段对应的方法是？](https://github.com/febobo/web-interview/issues/182)\n- [面试官：state 和 props有什么区别？](https://github.com/febobo/web-interview/issues/183)\n- [面试官：super()和super(props)有什么区别？](https://github.com/febobo/web-interview/issues/184)\n- [面试官：说说React中的setState执行机制](https://github.com/febobo/web-interview/issues/185)\n- [面试官：说说React的事件机制？](https://github.com/febobo/web-interview/issues/186)\n- [面试官：React事件绑定的方式有哪些？区别？](https://github.com/febobo/web-interview/issues/187)\n- [面试官：React构建组件的方式有哪些？区别？](https://github.com/febobo/web-interview/issues/188)\n- [面试官：React中组件之间如何通信？](https://github.com/febobo/web-interview/issues/189)\n- [面试官：React中的key有什么作用？](https://github.com/febobo/web-interview/issues/191)\n- [面试官：说说对React refs 的理解？应用场景？](https://github.com/febobo/web-interview/issues/192)\n- [面试官：说说对React中类组件和函数组件的理解？有什么区别？](https://github.com/febobo/web-interview/issues/193)\n- [面试官：说说对受控组件和非受控组件的理解？应用场景？](https://github.com/febobo/web-interview/issues/207)\n- [面试官：说说对高阶组件的理解？应用场景?](https://github.com/febobo/web-interview/issues/194)\n- [面试官：说说对React Hooks的理解？解决了什么问题？](https://github.com/febobo/web-interview/issues/195)\n- [面试官：说说react中引入css的方式有哪几种？区别？](https://github.com/febobo/web-interview/issues/196)\n- [面试官：在react中组件间过渡动画如何实现？](https://github.com/febobo/web-interview/issues/197)\n- [面试官：说说你对Redux的理解？其工作原理？](https://github.com/febobo/web-interview/issues/198)\n- [面试官：说说对Redux中间件的理解？常用的中间件有哪些？实现原理？](https://github.com/febobo/web-interview/issues/199)\n- [面试官：你在React项目中是如何使用Redux的? 项目结构是如何划分的？](https://github.com/febobo/web-interview/issues/201)\n- [面试官：说说你对React Router的理解？常用的Router组件有哪些？](https://github.com/febobo/web-interview/issues/202)\n- [面试官：说说React Router有几种模式？实现原理？](https://github.com/febobo/web-interview/issues/203)\n- [面试官：说说你对immutable的理解？如何应用在react项目中？](https://github.com/febobo/web-interview/issues/204)\n- [面试官：说说React render方法的原理？在什么时候会被触发？](https://github.com/febobo/web-interview/issues/205)\n- [面试官：说说你是如何提高组件的渲染效率的？在React中如何避免不必要的render？](https://github.com/febobo/web-interview/issues/210)\n- [面试官：说说React diff的原理是什么？](https://github.com/febobo/web-interview/issues/208)\n- [面试官：说说对Fiber架构的理解？解决了什么问题？](https://github.com/febobo/web-interview/issues/209)\n- [面试官：说说React Jsx转换成真实DOM过程？](https://github.com/febobo/web-interview/issues/206)\n- [面试官：说说 React 性能优化的手段有哪些？ ](https://github.com/febobo/web-interview/issues/211)\n- [面试官：说说你在React项目是如何捕获错误的？](https://github.com/febobo/web-interview/issues/216)\n- [面试官：说说React服务端渲染怎么做？原理是什么？](https://github.com/febobo/web-interview/issues/217)\n- [面试官：说说你在使用React 过程中遇到的常见问题？如何解决?](https://github.com/febobo/web-interview/issues/218)\n\n<h3 style=\"text-align: center\">版本控制系列</h3>\n\n- [面试官：说说你对版本管理的理解？常用的版本管理工具有哪些？](https://github.com/febobo/web-interview/issues/219)\n- [面试官：说说你对Git的理解？](https://github.com/febobo/web-interview/issues/220)\n- [面试官：说说Git中 fork, clone,branch这三个概念，有什么区别?](https://github.com/febobo/web-interview/issues/221)\n- [面试官：说说Git常用的命令有哪些？](https://github.com/febobo/web-interview/issues/222)\n- [面试官：说说Git 中 HEAD、工作树和索引之间的区别？](https://github.com/febobo/web-interview/issues/223)\n- [面试官：说说对git pull 和 git fetch 的理解？有什么区别？](https://github.com/febobo/web-interview/issues/224)\n- [面试官：说说你对git stash 的理解？应用场景？](https://github.com/febobo/web-interview/issues/227)\n- [面试官：说说你对git rebase 和 git merge的理解？区别？](https://github.com/febobo/web-interview/issues/228)\n- [面试官：说说 git 发生冲突的场景？如何解决？](https://github.com/febobo/web-interview/issues/229)\n- [面试官：说说你对git reset 和 git revert 的理解？区别？](https://github.com/febobo/web-interview/issues/230)\n\n<h3 style=\"text-align: center\">操作系统系列</h3>\n\n- [面试官：说说你对操作系统的理解？核心概念有哪些？](https://github.com/febobo/web-interview/issues/231)\n- [面试官：说说什么是进程？什么是线程？区别？](https://github.com/febobo/web-interview/issues/232)\n- [面试官：说说 linux系统下 文件操作常用的命令有哪些？](https://github.com/febobo/web-interview/issues/233)\n- [面试官：说说 linux 系统下 文本编辑常用的命令有哪些？](https://github.com/febobo/web-interview/issues/234)\n- [面试官：说说你对 linux 用户管理的理解？相关的命令有哪些？](https://github.com/febobo/web-interview/issues/235)\n- [面试官：说说你对输入输出重定向和管道的理解？应用场景？](https://github.com/febobo/web-interview/issues/236)\n- [面试官：说说你对 shell 的理解？常见的命令？](https://github.com/febobo/web-interview/issues/237)\n\n<h3 style=\"text-align: center\">typescript系列</h3>\n\n- [面试官：说说你对 typescript 的理解？与 javascript 的区别？](https://github.com/febobo/web-interview/issues/245)\n- [面试官：说说 typescript 的数据类型有哪些？](https://github.com/febobo/web-interview/issues/246)\n- [面试官：说说你对 TypeScript 中枚举类型的理解？应用场景？](https://github.com/febobo/web-interview/issues/247)\n- [面试官：说说你对 TypeScript 中接口的理解？应用场景？](https://github.com/febobo/web-interview/issues/248)\n- [面试官：说说你对 TypeScript 中类的理解？应用场景？](https://github.com/febobo/web-interview/issues/249)\n- [面试官：说说你对 TypeScript 中函数的理解？与 JavaScript 函数的区别？](https://github.com/febobo/web-interview/issues/255)\n- [面试官：说说你对 TypeScript 中泛型的理解？应用场景？](https://github.com/febobo/web-interview/issues/250)\n- [面试官：说说你对 TypeScript 中高级类型的理解？有哪些？](https://github.com/febobo/web-interview/issues/251)\n- [面试官：说说你对 TypeScript 装饰器的理解？应用场景？](https://github.com/febobo/web-interview/issues/252)\n- [面试官：说说对 TypeScript 中命名空间与模块的理解？区别？](https://github.com/febobo/web-interview/issues/253)\n- [面试官：说说如何在React项目中应用TypeScript？](https://github.com/febobo/web-interview/issues/255)\n- [面试官：说说如何在Vue项目中应用TypeScript？](https://github.com/febobo/web-interview/issues/257)\n\n<h3 style=\"text-align: center\">算法系列</h3>\n\n- [面试官：说说你对算法的理解？应用场景？](https://github.com/febobo/web-interview/issues/258)\n- [面试官：说说你对算法中时间复杂度，空间复杂度的理解？如何计算？](https://github.com/febobo/web-interview/issues/259)\n- [面试官：说说你对数据结构的理解？有哪些？区别？](https://github.com/febobo/web-interview/issues/260)\n- [面试官：说说你对栈、队列的理解？应用场景？](https://github.com/febobo/web-interview/issues/261)\n- [面试官：说说你对链表的理解？常见的操作有哪些？](https://github.com/febobo/web-interview/issues/262)\n- [面试官：说说你对集合的理解？常见的操作有哪些？](https://github.com/febobo/web-interview/issues/263)\n- [面试官：说说你对树的理解？相关的操作有哪些？](https://github.com/febobo/web-interview/issues/264)\n- [面试官：说说你对堆的理解？如何实现？应用场景？](https://github.com/febobo/web-interview/issues/265)\n- [面试官：说说你对图的理解？相关操作有哪些？](https://github.com/febobo/web-interview/issues/266)\n- [面试官：说说常见的排序算法有哪些？区别？](https://github.com/febobo/web-interview/issues/267)\n- [面试官：说说你对冒泡排序的理解？如何实现？应用场景？](https://github.com/febobo/web-interview/issues/271)\n- [面试官：说说你对选择排序的理解？如何实现？应用场景？](https://github.com/febobo/web-interview/issues/272)\n- [面试官：说说你对插入排序的理解？如何实现？应用场景？](https://github.com/febobo/web-interview/issues/273)\n- [面试官：说说你对归并排序的理解？如何实现？应用场景？](https://github.com/febobo/web-interview/issues/274)\n- [面试官：说说你对快速排序的理解？如何实现？应用场景？](https://github.com/febobo/web-interview/issues/275)\n- [面试官：说说你对二分查找的理解？如何实现？应用场景？](https://github.com/febobo/web-interview/issues/276)\n- [面试官：说说你对分而治之、动态规划的理解？区别？](https://github.com/febobo/web-interview/issues/277)\n- [面试官：说说你对贪心算法、回溯算法的理解？应用场景？](https://github.com/febobo/web-interview/issues/278)\n\n<h3 style=\"text-align: center\">小程序系列</h3>\n\n- [面试官：说说你对微信小程序的理解？优缺点？](https://github.com/febobo/web-interview/issues/282)\n- [面试官：说说微信小程序的生命周期函数有哪些？](https://github.com/febobo/web-interview/issues/283)\n- [面试官：说说微信小程序中路由跳转的方式有哪些？区别？](https://github.com/febobo/web-interview/issues/284)\n- [面试官：说说提高微信小程序的应用速度的手段有哪些？](https://github.com/febobo/web-interview/issues/285)\n- [面试官：说说微信小程序的登录流程？](https://github.com/febobo/web-interview/issues/286)\n- [面试官：说说微信小程序的发布流程？](https://github.com/febobo/web-interview/issues/287)\n- [面试官：说说微信小程序的支付流程？](https://github.com/febobo/web-interview/issues/288)\n- [面试官：说说微信小程序的实现原理？](https://github.com/febobo/web-interview/issues/289)\n\n<h3 style=\"text-align: center\">设计模式系列</h3>\n\n- [面试官：说说对设计模式的理解？常见的设计模式有哪些？](https://github.com/febobo/web-interview/issues/290)\n- [面试官：说说你对单例模式的理解？如何实现？](https://github.com/febobo/web-interview/issues/291)\n- [面试官：说说你对工厂模式的理解？应用场景？](https://github.com/febobo/web-interview/issues/292)\n- [面试官：说说你对策略模式的理解？应用场景？](https://github.com/febobo/web-interview/issues/293)\n- [面试官：说说你对代理模式的理解？应用场景？](https://github.com/febobo/web-interview/issues/294)\n- [面试官：说说你对发布订阅、观察者模式的理解？区别？](https://github.com/febobo/web-interview/issues/295)\n\n## 👧 更多系列\n\n### 面试官手写系列/精选33道\n筹备中..\n### 面试官浏览器系列/精选33道\n筹备中..\n### 面试官前端构建系列/精选33道\n筹备中..\n\n## 声明\n\n- 添加本仓库地址可随意转载仓库内所有内容\n- 本仓库永不收取任何费用，现在不会，未来也不会，也不会授权任何人/机构进行收费\n\n<p align=\"center\">\n<img width=\"300px\"  style=\"max-width:300px; width:300px;\" src=\"https://static.vue-js.com/21494410-8e11-11eb-ab90-d9ae814b240d.png\"/>\n</p>\n"
  },
  {
    "path": "docs/React/Binding events.md",
    "content": "# 面试官：React事件绑定的方式有哪些？区别？\n\n ![](https://static.vue-js.com/e21f5560-d8fa-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在`react`应用中，事件名都是用小驼峰格式进行书写，例如`onclick`要改写成`onClick`\n\n最简单的事件绑定如下：\n\n```jsx\nclass ShowAlert extends React.Component {\n  showAlert() {\n    console.log(\"Hi\");\n  }\n\n  render() {\n    return <button onClick={this.showAlert}>show</button>;\n  }\n}\n```\n\n从上面可以看到，事件绑定的方法需要使用`{}`包住\n\n上述的代码看似没有问题，但是当将处理函数输出代码换成`console.log(this)`的时候，点击按钮，则会发现控制台输出`undefined`\n\n\n\n## 二、如何绑定\n\n为了解决上面正确输出`this`的问题，常见的绑定方式有如下：\n\n- render方法中使用bind\n- render方法中使用箭头函数\n- constructor中bind\n- 定义阶段使用箭头函数绑定\n\n\n\n### render方法中使用bind\n\n如果使用一个类组件，在其中给某个组件/元素一个`onClick`属性，它现在并会自定绑定其`this`到当前组件，解决这个问题的方法是在事件函数后使用`.bind(this)`将`this`绑定到当前组件中\n\n```jsx\nclass App extends React.Component {\n  handleClick() {\n    console.log('this > ', this);\n  }\n  render() {\n    return (\n      <div onClick={this.handleClick.bind(this)}>test</div>\n    )\n  }\n}\n```\n\n这种方式在组件每次`render`渲染的时候，都会重新进行`bind`的操作，影响性能\n\n\n\n### render方法中使用箭头函数\n\n通过`ES6`的上下文来将`this`的指向绑定给当前组件，同样再每一次`render`的时候都会生成新的方法，影响性能\n\n```jsx\nclass App extends React.Component {\n  handleClick() {\n    console.log('this > ', this);\n  }\n  render() {\n    return (\n      <div onClick={e => this.handleClick(e)}>test</div>\n    )\n  }\n}\n```\n\n\n\n## constructor中bind\n\n在`constructor`中预先`bind`当前组件，可以避免在`render`操作中重复绑定\n\n```jsx\nclass App extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleClick = this.handleClick.bind(this);\n  }\n  handleClick() {\n    console.log('this > ', this);\n  }\n  render() {\n    return (\n      <div onClick={this.handleClick}>test</div>\n    )\n  }\n}\n```\n\n\n\n### 定义阶段使用箭头函数绑定\n\n跟上述方式三一样，能够避免在`render`操作中重复绑定，实现也非常的简单，如下：\n\n```jsx\nclass App extends React.Component {\n  constructor(props) {\n    super(props);\n  }\n  handleClick = () => {\n    console.log('this > ', this);\n  }\n  render() {\n    return (\n      <div onClick={this.handleClick}>test</div>\n    )\n  }\n}\n```\n\n\n\n## 三、区别\n\n上述四种方法的方式，区别主要如下：\n\n- 编写方面：方式一、方式二写法简单，方式三的编写过于冗杂\n- 性能方面：方式一和方式二在每次组件render的时候都会生成新的方法实例，性能问题欠缺。若该函数作为属性值传给子组件的时候，都会导致额外的渲染。而方式三、方式四只会生成一个方法实例\n\n综合上述，方式四是最优的事件绑定方式\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000011317515\n- https://vue3js.cn/interview/"
  },
  {
    "path": "docs/React/Building components.md",
    "content": "# 面试官：React构建组件的方式有哪些？区别？\n\n ![](https://static.vue-js.com/04355cb0-da10-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念（组件）来实现开发的模式\n\n在`React`中，一个类、一个函数都可以视为一个组件\n\n在[之前文章](https://mp.weixin.qq.com/s/Wi0r38LBopsyQ9HesMID0g)中，我们了解到组件所存在的优势：\n\n- 降低整个系统的耦合度，在保持接口不变的情况下，我们可以替换不同的组件快速完成需求，例如输入框，可以替换为日历、时间、范围等组件作具体的实现\n- 调试方便，由于整个系统是通过组件组合起来的，在出现问题的时候，可以用排除法直接移除组件，或者根据报错的组件快速定位问题，之所以能够快速定位，是因为每个组件之间低耦合，职责单一，所以逻辑会比分析整个系统要简单\n- 提高可维护性，由于每个组件的职责单一，并且组件在系统中是被复用的，所以对代码进行优化可获得系统的整体升级\n\n\n\n## 二、如何构建\n\n在`React`目前来讲，组件的创建主要分成了三种方式：\n\n- 函数式创建\n- 通过 React.createClass 方法创建\n- 继承 React.Component 创建\n\n\n\n### 函数式创建\n\n在`React Hooks`出来之前，函数式组件可以视为无状态组件，只负责根据传入的`props`来展示视图，不涉及对`state`状态的操作\n\n大多数组件可以写为无状态组件，通过简单组合构建其他组件\n\n在`React`中，通过函数简单创建组件的示例如下：\n\n```jsx\nfunction HelloComponent(props, /* context */) {\n  return <div>Hello {props.name}</div>\n}\n```\n\n\n\n\n\n### 通过 React.createClass 方法创建\n\n`React.createClass`是react刚开始推荐的创建组件的方式，目前这种创建方式已经不怎么用了\n\n像上述通过函数式创建的组件的方式，最终会通过`babel`转化成`React.createClass`这种形式，转化成如下：\n\n```jsx\nfunction HelloComponent(props) /* context */{\n  return React.createElement(\n    \"div\",\n    null,\n    \"Hello \",\n    props.name\n  );\n}\n```\n\n由于上述的编写方式过于冗杂，目前基本上不使用上\n\n\n\n### 继承 React.Component 创建\n\n同样在`react hooks`出来之前，有状态的组件只能通过继承`React.Component`这种形式进行创建\n\n有状态的组件也就是组件内部存在维护的数据，在类创建的方式中通过`this.state`进行访问\n\n当调用`this.setState`修改组件的状态时，组价会再次会调用`render()`方法进行重新渲染\n\n通过继承`React.Component`创建一个时钟示例如下：\n\n```jsx\nclass Timer extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = { seconds: 0 };\n  }\n\n  tick() {\n    this.setState(state => ({\n      seconds: state.seconds + 1\n    }));\n  }\n\n  componentDidMount() {\n    this.interval = setInterval(() => this.tick(), 1000);\n  }\n\n  componentWillUnmount() {\n    clearInterval(this.interval);\n  }\n\n  render() {\n    return (\n      <div>\n        Seconds: {this.state.seconds}\n      </div>\n    );\n  }\n}\n```\n\n\n\n## 三、区别\n\n由于`React.createClass `创建的方式过于冗杂，并不建议使用\n\n而像函数式创建和类组件创建的区别主要在于需要创建的组件是否需要为有状态组件：\n\n- 对于一些无状态的组件创建，建议使用函数式创建的方式\n\n- 由于`react hooks`的出现，函数式组件创建的组件通过使用`hooks`方法也能使之成为有状态组件，再加上目前推崇函数式编程，所以这里建议都使用函数式的方式来创建组件\n\n在考虑组件的选择原则上，能用无状态组件则用无状态组件\n\n\n\n## 参考文献\n\n- https://react.docschina.org/\n\n"
  },
  {
    "path": "docs/React/Fiber.md",
    "content": "# 面试官：说说对Fiber架构的理解？解决了什么问题？\n\n ![](https://static.vue-js.com/554da6d0-ed24-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、问题\n\n`JavaScript `引擎和页面渲染引擎两个线程是互斥的，当其中一个线程执行时，另一个线程只能挂起等待\n\n如果 `JavaScript` 线程长时间地占用了主线程，那么渲染层面的更新就不得不长时间地等待，界面长时间不更新，会导致页面响应度变差，用户可能会感觉到卡顿\n\n而这也正是 `React 15` 的 `Stack Reconciler `所面临的问题，当 `React `在渲染组件时，从开始到渲染完成整个过程是一气呵成的，无法中断\n\n如果组件较大，那么`js`线程会一直执行，然后等到整棵`VDOM`树计算完成后，才会交给渲染的线程\n\n这就会导致一些用户交互、动画等任务无法立即得到处理，导致卡顿的情况\n\n ![](https://static.vue-js.com/5eb3a850-ed24-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 二、是什么\n\nReact Fiber 是 Facebook 花费两年余时间对 React 做出的一个重大改变与优化，是对 React 核心算法的一次重新实现。从Facebook在 React Conf 2017 会议上确认，React Fiber 在React 16 版本发布\n\n在`react`中，主要做了以下的操作：\n\n- 为每个增加了优先级，优先级高的任务可以中断低优先级的任务。然后再重新，注意是重新执行优先级低的任务\n- 增加了异步任务，调用requestIdleCallback api，浏览器空闲的时候执行\n- dom diff树变成了链表，一个dom对应两个fiber（一个链表），对应两个队列，这都是为找到被中断的任务，重新执行\n\n从架构角度来看，`Fiber` 是对 `React `核心算法（即调和过程）的重写\n\n从编码角度来看，`Fiber `是 `React `内部所定义的一种数据结构，它是 `Fiber `树结构的节点单位，也就是 `React 16` 新架构下的虚拟`DOM`\n\n一个 `fiber `就是一个 `JavaScript `对象，包含了元素的信息、该元素的更新操作队列、类型，其数据结构如下：\n\n```js\ntype Fiber = {\n  // 用于标记fiber的WorkTag类型，主要表示当前fiber代表的组件类型如FunctionComponent、ClassComponent等\n  tag: WorkTag,\n  // ReactElement里面的key\n  key: null | string,\n  // ReactElement.type，调用`createElement`的第一个参数\n  elementType: any,\n  // The resolved function/class/ associated with this fiber.\n  // 表示当前代表的节点类型\n  type: any,\n  // 表示当前FiberNode对应的element组件实例\n  stateNode: any,\n\n  // 指向他在Fiber节点树中的`parent`，用来在处理完这个节点之后向上返回\n  return: Fiber | null,\n  // 指向自己的第一个子节点\n  child: Fiber | null,\n  // 指向自己的兄弟结构，兄弟节点的return指向同一个父节点\n  sibling: Fiber | null,\n  index: number,\n\n  ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,\n\n  // 当前处理过程中的组件props对象\n  pendingProps: any,\n  // 上一次渲染完成之后的props\n  memoizedProps: any,\n\n  // 该Fiber对应的组件产生的Update会存放在这个队列里面\n  updateQueue: UpdateQueue<any> | null,\n\n  // 上一次渲染的时候的state\n  memoizedState: any,\n\n  // 一个列表，存放这个Fiber依赖的context\n  firstContextDependency: ContextDependency<mixed> | null,\n\n  mode: TypeOfMode,\n\n  // Effect\n  // 用来记录Side Effect\n  effectTag: SideEffectTag,\n\n  // 单链表用来快速查找下一个side effect\n  nextEffect: Fiber | null,\n\n  // 子树中第一个side effect\n  firstEffect: Fiber | null,\n  // 子树中最后一个side effect\n  lastEffect: Fiber | null,\n\n  // 代表任务在未来的哪个时间点应该被完成，之后版本改名为 lanes\n  expirationTime: ExpirationTime,\n\n  // 快速确定子树中是否有不在等待的变化\n  childExpirationTime: ExpirationTime,\n\n  // fiber的版本池，即记录fiber更新过程，便于恢复\n  alternate: Fiber | null,\n}\n```\n\n\n\n## 三、如何解决\n\n`Fiber`把渲染更新过程拆分成多个子任务，每次只做一小部分，做完看是否还有剩余时间，如果有继续下一个任务；如果没有，挂起当前任务，将时间控制权交给主线程，等主线程不忙的时候在继续执行\n\n即可以中断与恢复，恢复后也可以复用之前的中间状态，并给不同的任务赋予不同的优先级，其中每个任务更新单元为 `React Element` 对应的 `Fiber `节点\n\n实现的上述方式的是`requestIdleCallback`方法\n\n`window.requestIdleCallback()`方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作，而不会影响延迟关键事件，如动画和输入响应\n\n首先 React 中任务切割为多个步骤，分批完成。在完成一部分任务之后，将控制权交回给浏览器，让浏览器有时间再进行页面的渲染。等浏览器忙完之后有剩余时间，再继续之前 React 未完成的任务，是一种合作式调度。\n\n该实现过程是基于 `Fiber `节点实现，作为静态的数据结构来说，每个 `Fiber` 节点对应一个 `React element`，保存了该组件的类型（函数组件/类组件/原生组件等等）、对应的 DOM 节点等信息。\n\n作为动态的工作单元来说，每个 `Fiber` 节点保存了本次更新中该组件改变的状态、要执行的工作。\n\n每个 Fiber 节点有个对应的 `React element`，多个 `Fiber `节点根据如下三个属性构建一颗树：\n\n```javascript\n// 指向父级Fiber节点\nthis.return = null\n// 指向子Fiber节点\nthis.child = null\n// 指向右边第一个兄弟Fiber节点\nthis.sibling = null\n```\n通过这些属性就能找到下一个执行目标\n\n\n## 参考文献\n\n- https://juejin.cn/post/6926432527980691470\n- https://zhuanlan.zhihu.com/p/137234573\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/React/High order components.md",
    "content": "# 面试官：说说对高阶组件的理解？应用场景?\n\n ![](https://static.vue-js.com/c8901850-e197-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n高阶函数（Higher-order function），至少满足下列一个条件的函数\n\n- 接受一个或多个函数作为输入\n- 输出一个函数\n\n在`React`中，高阶组件即接受一个或多个组件作为参数并且返回一个组件，本质也就是一个函数，并不是一个组件\n\n```jsx\nconst EnhancedComponent = highOrderComponent(WrappedComponent);\n```\n\n上述代码中，该函数接受一个组件`WrappedComponent`作为参数，返回加工过的新组件`EnhancedComponent`\n\n高阶组件的这种实现方式，本质上是一个装饰者设计模式\n\n\n## 二、如何编写\n\n最基本的高阶组件的编写模板如下：\n\n```jsx\nimport React, { Component } from 'react';\n\nexport default (WrappedComponent) => {\n  return class EnhancedComponent extends Component {\n    // do something\n    render() {\n      return <WrappedComponent />;\n    }\n  }\n}\n```\n\n通过对传入的原始组件 `WrappedComponent` 做一些你想要的操作（比如操作 props，提取 state，给原始组件包裹其他元素等），从而加工出想要的组件 `EnhancedComponent`\n\n把通用的逻辑放在高阶组件中，对组件实现一致的处理，从而实现代码的复用\n\n所以，高阶组件的主要功能是封装并分离组件的通用逻辑，让通用逻辑在组件间更好地被复用\n\n但在使用高阶组件的同时，一般遵循一些约定，如下：\n\n- props 保持一致\n- 你不能在函数式（无状态）组件上使用 ref 属性，因为它没有实例\n- 不要以任何方式改变原始组件 WrappedComponent\n- 透传不相关 props 属性给被包裹的组件 WrappedComponent\n- 不要再 render() 方法中使用高阶组件\n- 使用  compose 组合高阶组件\n- 包装显示名字以便于调试\n\n这里需要注意的是，高阶组件可以传递所有的`props`，但是不能传递`ref`\n\n如果向一个高阶组件添加`refe`引用，那么`ref` 指向的是最外层容器组件实例的，而不是被包裹的组件，如果需要传递`refs`的话，则使用`React.forwardRef`，如下：\n\n```jsx\nfunction withLogging(WrappedComponent) {\n    class Enhance extends WrappedComponent {\n        componentWillReceiveProps() {\n            console.log('Current props', this.props);\n            console.log('Next props', nextProps);\n        }\n        render() {\n            const {forwardedRef, ...rest} = this.props;\n            // 把 forwardedRef 赋值给 ref\n            return <WrappedComponent {...rest} ref={forwardedRef} />;\n        }\n    };\n\n    // React.forwardRef 方法会传入 props 和 ref 两个参数给其回调函数\n    // 所以这边的 ref 是由 React.forwardRef 提供的\n    function forwardRef(props, ref) {\n        return <Enhance {...props} forwardRef={ref} />\n    }\n\n    return React.forwardRef(forwardRef);\n}\nconst EnhancedComponent = withLogging(SomeComponent);\n```\n\n\n## 三、应用场景\n\n通过上面的了解，高阶组件能够提高代码的复用性和灵活性，在实际应用中，常常用于与核心业务无关但又在多个模块使用的功能，如权限控制、日志记录、数据校验、异常处理、统计上报等\n\n举个例子，存在一个组件，需要从缓存中获取数据，然后渲染。一般情况，我们会如下编写：\n\n```jsx\nimport React, { Component } from 'react'\n\nclass MyComponent extends Component {\n\n  componentWillMount() {\n      let data = localStorage.getItem('data');\n      this.setState({data});\n  }\n  \n  render() {\n    return <div>{this.state.data}</div>\n  }\n}\n```\n\n上述代码当然可以实现该功能，但是如果还有其他组件也有类似功能的时候，每个组件都需要重复写`componentWillMount`中的代码，这明显是冗杂的\n\n下面就可以通过高价组件来进行改写，如下：\n\n```jsx\nimport React, { Component } from 'react'\n\nfunction withPersistentData(WrappedComponent) {\n  return class extends Component {\n    componentWillMount() {\n      let data = localStorage.getItem('data');\n        this.setState({data});\n    }\n    \n    render() {\n      // 通过{...this.props} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent\n      return <WrappedComponent data={this.state.data} {...this.props} />\n    }\n  }\n}\n\nclass MyComponent2 extends Component {  \n  render() {\n    return <div>{this.props.data}</div>\n  }\n}\n\nconst MyComponentWithPersistentData = withPersistentData(MyComponent2)\n```\n\n再比如组件渲染性能监控，如下：\n\n```jsx\nclass Home extends React.Component {\n    render() {\n        return (<h1>Hello World.</h1>);\n    }\n}\nfunction withTiming(WrappedComponent) {\n    return class extends WrappedComponent {\n        constructor(props) {\n            super(props);\n            this.start = 0;\n            this.end = 0;\n        }\n        componentWillMount() {\n            super.componentWillMount && super.componentWillMount();\n            this.start = Date.now();\n        }\n        componentDidMount() {\n            super.componentDidMount && super.componentDidMount();\n            this.end = Date.now();\n            console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);\n        }\n        render() {\n            return super.render();\n        }\n    };\n}\n\nexport default withTiming(Home);\n```\n\n\n## 参考文献\n\n- https://zh-hans.reactjs.org/docs/higher-order-components.html#gatsby-focus-wrapper\n- https://zh.wikipedia.org/wiki/%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0\n- https://segmentfault.com/a/1190000010307650\n- https://zhuanlan.zhihu.com/p/61711492\n"
  },
  {
    "path": "docs/React/Improve performance.md",
    "content": "# 面试官：说说 React 性能优化的手段有哪些？\n\n ![](https://static.vue-js.com/a9e83b00-f270-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n`React`凭借`virtual DOM`和`diff`算法拥有高效的性能，但是某些情况下，性能明显可以进一步提高\n\n在前面文章中，我们了解到类组件通过调用`setState`方法， 就会导致`render`，父组件一旦发生`render`渲染，子组件一定也会执行`render`渲染\n\n当我们想要更新一个子组件的时候，如下图绿色部分：\n\n ![](https://static.vue-js.com/b41f6f30-f270-11eb-ab90-d9ae814b240d.png)\n\n理想状态只调用该路径下的组件`render`：\n\n ![](https://static.vue-js.com/bc0f2460-f270-11eb-85f6-6fac77c0c9b3.png)\n\n但是`react`的默认做法是调用所有组件的`render`，再对生成的虚拟`DOM`进行对比（黄色部分），如不变则不进行更新\n\n ![](https://static.vue-js.com/c2f0c4f0-f270-11eb-85f6-6fac77c0c9b3.png)\n\n从上图可见，黄色部分`diff`算法对比是明显的性能浪费的情况\n\n\n\n\n\n## 二、如何做\n\n在[React中如何避免不必要的render](https://mp.weixin.qq.com/s/h4NX4Plr6TCjoIhlawiJTg)中，我们了解到如何避免不必要的`render`来应付上面的问题，主要手段是通过`shouldComponentUpdate`、`PureComponent`、`React.memo`，这三种形式这里就不再复述\n\n除此之外， 常见性能优化常见的手段有如下：\n\n- 避免使用内联函数\n- 使用 React Fragments 避免额外标记\n- 使用 Immutable\n\n- 懒加载组件\n\n- 事件绑定方式\n\n- 服务端渲染\n\n\n\n#### 避免使用内联函数\n\n如果我们使用内联函数，则每次调用`render`函数时都会创建一个新的函数实例，如下：\n\n```jsx\nimport React from \"react\";\n\nexport default class InlineFunctionComponent extends React.Component {\n  render() {\n    return (\n      <div>\n        <h1>Welcome Guest</h1>\n        <input type=\"button\" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value=\"Click For Inline Function\" />\n      </div>\n    )\n  }\n}\n```\n\n我们应该在组件内部创建一个函数，并将事件绑定到该函数本身。这样每次调用 `render` 时就不会创建单独的函数实例，如下：\n\n```jsx\nimport React from \"react\";\n\nexport default class InlineFunctionComponent extends React.Component {\n  \n  setNewStateData = (event) => {\n    this.setState({\n      inputValue: e.target.value\n    })\n  }\n  \n  render() {\n    return (\n      <div>\n        <h1>Welcome Guest</h1>\n        <input type=\"button\" onClick={this.setNewStateData} value=\"Click For Inline Function\" />\n      </div>\n    )\n  }\n}\n```\n\n\n\n\n\n#### 使用 React Fragments 避免额外标记\n\n用户创建新组件时，每个组件应具有单个父标签。父级不能有两个标签，所以顶部要有一个公共标签，所以我们经常在组件顶部添加额外标签`div`\n\n这个额外标签除了充当父标签之外，并没有其他作用，这时候则可以使用`fragement`\n\n其不会向组件引入任何额外标记，但它可以作为父级标签的作用，如下所示：\n\n```jsx\nexport default class NestedRoutingComponent extends React.Component {\n    render() {\n        return (\n            <>\n                <h1>This is the Header Component</h1>\n                <h2>Welcome To Demo Page</h2>\n            </>\n        )\n    }\n}\n```\n\n\n\n### 事件绑定方式\n\n在[事件绑定方式](https://mp.weixin.qq.com/s/VfQ34ZEPXUXsimzMaJ_41A)中，我们了解到四种事假绑定的方式\n\n从性能方面考虑，在`render`方法中使用`bind`和`render`方法中使用箭头函数这两种形式在每次组件`render`的时候都会生成新的方法实例，性能欠缺\n\n而`constructor`中`bind`事件与定义阶段使用箭头函数绑定这两种形式只会生成一个方法实例，性能方面会有所改善\n\n\n\n### 使用 Immutable\n\n在[理解Immutable中](https://mp.weixin.qq.com/s/laYJ_KNa8M5JNBnIolMDAA)，我们了解到使用 `Immutable`可以给 `React` 应用带来性能的优化，主要体现在减少渲染的次数\n\n在做`react`性能优化的时候，为了避免重复渲染，我们会在`shouldComponentUpdate()`中做对比，当返回`true`执行`render`方法\n\n`Immutable`通过`is`方法则可以完成对比，而无需像一样通过深度比较的方式比较\n\n\n\n### 懒加载组件\n\n从工程方面考虑，`webpack`存在代码拆分能力，可以为应用创建多个包，并在运行时动态加载，减少初始包的大小\n\n而在`react`中使用到了`Suspense `和 `lazy`组件实现代码拆分功能，基本使用如下：\n\n```jsx\nconst johanComponent = React.lazy(() => import(/* webpackChunkName: \"johanComponent\" */ './myAwesome.component'));\n \nexport const johanAsyncComponent = props => (\n  <React.Suspense fallback={<Spinner />}>\n    <johanComponent {...props} />\n  </React.Suspense>\n);\n```\n\n\n\n### 服务端渲染\n\n采用服务端渲染端方式，可以使用户更快的看到渲染完成的页面\n\n服务端渲染，需要起一个`node`服务，可以使用`express`、`koa`等，调用`react`的`renderToString`方法，将根组件渲染成字符串，再输出到响应中\n\n例如：\n\n```js\nimport { renderToString } from \"react-dom/server\";\nimport MyPage from \"./MyPage\";\napp.get(\"/\", (req, res) => {\n  res.write(\"<!DOCTYPE html><html><head><title>My Page</title></head><body>\");\n  res.write(\"<div id='content'>\");  \n  res.write(renderToString(<MyPage/>));\n  res.write(\"</div></body></html>\");\n  res.end();\n});\n```\n\n客户端使用render方法来生成HTML\n\n```jsx\nimport ReactDOM from 'react-dom';\nimport MyPage from \"./MyPage\";\nReactDOM.render(<MyPage />, document.getElementById('app'));\n```\n\n\n\n### 其他\n\n除此之外，还存在的优化手段有组件拆分、合理使用`hooks`等性能优化手段...\n\n\n\n### 三、总结\n\n通过上面初步学习，我们了解到`react`常见的性能优化可以分成三个层面：\n\n- 代码层面\n- 工程层面\n- 框架机制层面\n\n通过这三个层面的优化结合，能够使基于`react`项目的性能更上一层楼\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/108666350\n- https://segmentfault.com/a/1190000007811296"
  },
  {
    "path": "docs/React/JSX to DOM.md",
    "content": "# 面试官：说说React Jsx转换成真实DOM过程？\n\n ![](https://static.vue-js.com/1d340620-f00a-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n`react`通过将组件编写的`JSX`映射到屏幕，以及组件中的状态发生了变化之后 `React`会将这些「变化」更新到屏幕上\n\n在前面文章了解中，`JSX`通过`babel`最终转化成`React.createElement`这种形式，例如：\n\n```jsx\n<div>\n  < img src=\"avatar.png\" className=\"profile\" />\n  <Hello />\n</div>\n```\n\n会被`bebel`转化成如下：\n\n```jsx\nReact.createElement(\n  \"div\",\n  null,\n  React.createElement(\"img\", {\n    src: \"avatar.png\",\n    className: \"profile\"\n  }),\n  React.createElement(Hello, null)\n);\n```\n\n在转化过程中，`babel`在编译时会判断 JSX 中组件的首字母：\n\n- 当首字母为小写时，其被认定为原生 `DOM` 标签，`createElement` 的第一个变量被编译为字符串\n\n- 当首字母为大写时，其被认定为自定义组件，createElement 的第一个变量被编译为对象\n\n最终都会通过`RenderDOM.render(...)`方法进行挂载，如下：\n\n```jsx\nReactDOM.render(<App />,  document.getElementById(\"root\"));\n```\n\n\n\n## 二、过程\n\n在`react`中，节点大致可以分成四个类别：\n\n- 原生标签节点\n- 文本节点\n- 函数组件\n- 类组件\n\n如下所示：\n\n```jsx\nclass ClassComponent extends Component {\n  static defaultProps = {\n    color: \"pink\"\n  };\n  render() {\n    return (\n      <div className=\"border\">\n        <h3>ClassComponent</h3>\n        <p className={this.props.color}>{this.props.name}</p >\n      </div>\n    );\n  }\n}\n\nfunction FunctionComponent(props) {\n  return (\n    <div className=\"border\">\n      FunctionComponent\n      <p>{props.name}</p >\n    </div>\n  );\n}\n\nconst jsx = (\n  <div className=\"border\">\n    <p>xx</p >\n    < a href=\" \">xxx</ a>\n    <FunctionComponent name=\"函数组件\" />\n    <ClassComponent name=\"类组件\" color=\"red\" />\n  </div>\n);\n```\n\n这些类别最终都会被转化成`React.createElement`这种形式\n\n`React.createElement`其被调用时会传⼊标签类型`type`，标签属性`props`及若干子元素`children`，作用是生成一个虚拟`Dom`对象，如下所示：\n\n```js\nfunction createElement(type, config, ...children) {\n    if (config) {\n        delete config.__self;\n        delete config.__source;\n    }\n    // ! 源码中做了详细处理，⽐如过滤掉key、ref等\n    const props = {\n        ...config,\n        children: children.map(child =>\n   typeof child === \"object\" ? child : createTextNode(child)\n  )\n    };\n    return {\n        type,\n        props\n    };\n}\nfunction createTextNode(text) {\n    return {\n        type: TEXT,\n        props: {\n            children: [],\n            nodeValue: text\n        }\n    };\n}\nexport default {\n    createElement\n};\n```\n\n`createElement`会根据传入的节点信息进行一个判断：\n\n- 如果是原生标签节点， type 是字符串，如div、span\n- 如果是文本节点， type就没有，这里是 TEXT\n- 如果是函数组件，type 是函数名\n- 如果是类组件，type 是类名\n\n虚拟`DOM`会通过`ReactDOM.render`进行渲染成真实`DOM`，使用方法如下：\n\n```jsx\nReactDOM.render(element, container[, callback])\n```\n\n当首次调用时，容器节点里的所有 `DOM` 元素都会被替换，后续的调用则会使用 `React` 的 `diff`算法进行高效的更新\n\n如果提供了可选的回调函数`callback`，该回调将在组件被渲染或更新之后被执行\n\n`render`大致实现方法如下：\n\n```js\nfunction render(vnode, container) {\n    console.log(\"vnode\", vnode); // 虚拟DOM对象\n    // vnode _> node\n    const node = createNode(vnode, container);\n    container.appendChild(node);\n}\n\n// 创建真实DOM节点\nfunction createNode(vnode, parentNode) {\n    let node = null;\n    const {type, props} = vnode;\n    if (type === TEXT) {\n        node = document.createTextNode(\"\");\n    } else if (typeof type === \"string\") {\n        node = document.createElement(type);\n    } else if (typeof type === \"function\") {\n        node = type.isReactComponent\n            ? updateClassComponent(vnode, parentNode)\n        : updateFunctionComponent(vnode, parentNode);\n    } else {\n        node = document.createDocumentFragment();\n    }\n    reconcileChildren(props.children, node);\n    updateNode(node, props);\n    return node;\n}\n\n// 遍历下子vnode，然后把子vnode->真实DOM节点，再插入父node中\nfunction reconcileChildren(children, node) {\n    for (let i = 0; i < children.length; i++) {\n        let child = children[i];\n        if (Array.isArray(child)) {\n            for (let j = 0; j < child.length; j++) {\n                render(child[j], node);\n            }\n        } else {\n            render(child, node);\n        }\n    }\n}\nfunction updateNode(node, nextVal) {\n    Object.keys(nextVal)\n        .filter(k => k !== \"children\")\n        .forEach(k => {\n        if (k.slice(0, 2) === \"on\") {\n            let eventName = k.slice(2).toLocaleLowerCase();\n            node.addEventListener(eventName, nextVal[k]);\n        } else {\n            node[k] = nextVal[k];\n        }\n    });\n}\n\n// 返回真实dom节点\n// 执行函数\nfunction updateFunctionComponent(vnode, parentNode) {\n    const {type, props} = vnode;\n    let vvnode = type(props);\n    const node = createNode(vvnode, parentNode);\n    return node;\n}\n\n// 返回真实dom节点\n// 先实例化，再执行render函数\nfunction updateClassComponent(vnode, parentNode) {\n    const {type, props} = vnode;\n    let cmp = new type(props);\n    const vvnode = cmp.render();\n    const node = createNode(vvnode, parentNode);\n    return node;\n}\nexport default {\n    render\n};\n```\n\n\n\n\n\n## 三、总结\n\n在`react`源码中，虚拟`Dom`转化成真实`Dom`整体流程如下图所示：\n\n ![](https://static.vue-js.com/28824fa0-f00a-11eb-ab90-d9ae814b240d.png)\n\n其渲染流程如下所示：\n\n- 使用React.createElement或JSX编写React组件，实际上所有的 JSX 代码最后都会转换成React.createElement(...) ，Babel帮助我们完成了这个转换的过程。\n- createElement函数对key和ref等特殊的props进行处理，并获取defaultProps对默认props进行赋值，并且对传入的孩子节点进行处理，最终构造成一个虚拟DOM对象\n- ReactDOM.render将生成好的虚拟DOM渲染到指定容器上，其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化，最终转换为真实DOM\n\n\n\n\n\n## 参考文献\n\n- https://bbs.huaweicloud.com/blogs/265503)\n- https://huang-qing.github.io/react/2019/05/29/React-VirDom/\n- https://segmentfault.com/a/1190000018891454\n"
  },
  {
    "path": "docs/React/React Hooks.md",
    "content": "# 面试官：说说对React Hooks的理解？解决了什么问题？\n\n ![](https://static.vue-js.com/8d357c50-e12e-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n`Hook` 是 React 16.8 的新增特性。它可以让你在不编写 `class` 的情况下使用 `state` 以及其他的 `React` 特性\n\n至于为什么引入`hook`，官方给出的动机是解决长时间使用和维护`react`过程中常遇到的问题，例如：\n\n- 难以重用和共享组件中的与状态相关的逻辑\n- 逻辑复杂的组件难以开发与维护，当我们的组件需要处理多个互不相关的 local state 时，每个生命周期函数中可能会包含着各种互不相关的逻辑在里面\n- 类组件中的this增加学习成本，类组件在基于现有工具的优化上存在些许问题\n- 由于业务变动，函数组件不得不改为类组件等等\n\n在以前，函数组件也被称为无状态的组件，只负责渲染的一些工作\n\n因此，现在的函数组件也可以是有状态的组件，内部也可以维护自身的状态以及做一些逻辑方面的处理\n\n\n## 二、有哪些\n\n上面讲到，`Hooks`让我们的函数组件拥有了类组件的特性，例如组件内的状态、生命周期\n\n最常见的`hooks`有如下：\n\n- useState\n- useEffect\n- 其他\n\n\n### useState\n\n首先给出一个例子，如下：\n\n```js\nimport React, { useState } from 'react';\n\nfunction Example() {\n  // 声明一个叫 \"count\" 的 state 变量\n  const [count, setCount] = useState(0);\n\n  return (\n    <div>\n      <p>You clicked {count} times</p >\n      <button onClick={() => setCount(count + 1)}>\n        Click me\n      </button>\n    </div>\n  );\n}\n```\n\n在函数组件中通过`useState`实现函数内部维护`state`，参数为`state`默认的值，返回值是一个数组，第一个值为当前的`state`，第二个值为更新`state`的函数\n\n该函数组件等价于的类组件如下：\n\n```js\nclass Example extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      count: 0\n    };\n  }\n\n  render() {\n    return (\n      <div>\n        <p>You clicked {this.state.count} times</p >\n        <button onClick={() => this.setState({ count: this.state.count + 1 })}>\n          Click me\n        </button>\n      </div>\n    );\n  }\n}\n```\n\n从上述两种代码分析，可以看出两者区别：\n\n- state声明方式：在函数组件中通过 useState 直接获取，类组件通过constructor 构造函数中设置\n- state读取方式：在函数组件中直接使用变量，类组件通过`this.state.count`的方式获取\n\n- state更新方式：在函数组件中通过 setCount 更新，类组件通过this.setState()\n\n总的来讲，useState 使用起来更为简洁，减少了`this`指向不明确的情况\n\n\n\n### useEffect\n\n`useEffect`可以让我们在函数组件中进行一些带有副作用的操作\n\n同样给出一个计时器示例：\n\n```js\nclass Example extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      count: 0\n    };\n  }\n\n  componentDidMount() {\n    document.title = `You clicked ${this.state.count} times`;\n  }\n  componentDidUpdate() {\n    document.title = `You clicked ${this.state.count} times`;\n  }\n\n  render() {\n    return (\n      <div>\n        <p>You clicked {this.state.count} times</p >\n        <button onClick={() => this.setState({ count: this.state.count + 1 })}>\n          Click me\n        </button>\n      </div>\n    );\n  }\n}\n```\n\n从上面可以看见，组件在加载和更新阶段都执行同样操作\n\n而如果使用`useEffect`后，则能够将相同的逻辑抽离出来，这是类组件不具备的方法\n\n对应的`useEffect`示例如下：\n\n```jsx\nimport React, { useState, useEffect } from 'react';\nfunction Example() {\n  const [count, setCount] = useState(0);\n \n  useEffect(() => {    document.title = `You clicked ${count} times`;  });\n  return (\n    <div>\n      <p>You clicked {count} times</p >\n      <button onClick={() => setCount(count + 1)}>\n        Click me\n      </button>\n    </div>\n  );\n}\n```\n\n`useEffect`第一个参数接受一个回调函数，默认情况下，`useEffect`会在第一次渲染和更新之后都会执行，相当于在`componentDidMount`和`componentDidUpdate`两个生命周期函数中执行回调\n\n如果某些特定值在两次重渲染之间没有发生变化，你可以跳过对 effect 的调用，这时候只需要传入第二个参数，如下：\n\n```js\nuseEffect(() => {\n  document.title = `You clicked ${count} times`;\n}, [count]); // 仅在 count 更改时更新\n```\n\n上述传入第二个参数后，如果 `count` 的值是 `5`，而且我们的组件重渲染的时候 `count` 还是等于 `5`，React 将对前一次渲染的 `[5]` 和后一次渲染的 `[5]` 进行比较，如果是相等则跳过`effects`执行\n\n回调函数中可以返回一个清除函数，这是`effect`可选的清除机制，相当于类组件中`componentwillUnmount`生命周期函数，可做一些清除副作用的操作，如下：\n\n```jsx\nuseEffect(() => {\n    function handleStatusChange(status) {\n        setIsOnline(status.isOnline);\n    }\n\n    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);\n    return () => {\n        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);\n    };\n});\n```\n\n所以， `useEffect`相当于`componentDidMount`，`componentDidUpdate` 和 `componentWillUnmount` 这三个生命周期函数的组合\n\n\n\n### 其它 hooks\n\n在组件通信过程中可以使用`useContext`，`refs`学习中我们也用到了`useRef`获取`DOM`结构......\n\n还有很多额外的`hooks`，如：\n\n- useReducer\n- useCallback\n- useMemo\n- useRef\n\n\n\n## 三、解决什么\n\n通过对上面的初步认识，可以看到`hooks`能够更容易解决状态相关的重用的问题：\n\n- 每调用useHook一次都会生成一份独立的状态\n\n- 通过自定义hook能够更好的封装我们的功能\n\n编写`hooks`为函数式编程，每个功能都包裹在函数中，整体风格更清爽，更优雅\n\n`hooks`的出现，使函数组件的功能得到了扩充，拥有了类组件相似的功能，在我们日常使用中，使用`hooks`能够解决大多数问题，并且还拥有代码复用机制，因此优先考虑`hooks`\n\n\n## 参考文献\n\n- https://zh-hans.reactjs.org/docs/hooks-state.html\n- https://zh-hans.reactjs.org/docs/hooks-effect.html\n- https://www.cnblogs.com/lalalagq/p/9898531.html\n"
  },
  {
    "path": "docs/React/React Router model.md",
    "content": "# 面试官：说说React Router有几种模式？实现原理？\n\n\n\n ![](https://static.vue-js.com/065f7a80-e978-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n在单页应用中，一个`web`项目只有一个`html`页面，一旦页面加载完成之后，就不用因为用户的操作而进行页面的重新加载或者跳转，其特性如下：\n\n- 改变 url 且不让浏览器像服务器发送请求\n\n- 在不刷新页面的前提下动态改变浏览器地址栏中的URL地址\n\n其中主要分成了两种模式：\n\n- hash 模式：在url后面加上#，如http://127.0.0.1:5500/home/#/page1\n- history 模式：允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录\n\n\n## 二、使用\n\n`React Router`对应的`hash`模式和`history`模式对应的组件为：\n\n- HashRouter\n- BrowserRouter\n\n\n这两个组件的使用都十分的简单，作为最顶层组件包裹其他组件，如下所示\n\n```jsx\n// 1.import { BrowserRouter as Router } from \"react-router-dom\";\n// 2.import { HashRouter as Router } from \"react-router-dom\";\n\nimport React from 'react';\nimport {\n  BrowserRouter as Router,\n  // HashRouter as Router  \n  Switch,\n  Route,\n} from \"react-router-dom\";\nimport Home from './pages/Home';\nimport Login from './pages/Login';\nimport Backend from './pages/Backend';\nimport Admin from './pages/Admin';\n\n\nfunction App() {\n  return (\n    <Router>\n        <Route path=\"/login\" component={Login}/>\n        <Route path=\"/backend\" component={Backend}/>\n        <Route path=\"/admin\" component={Admin}/>\n        <Route path=\"/\" component={Home}/>\n    </Router>\n  );\n}\n\nexport default App;\n```\n\n\n\n## 三、实现原理\n\n路由描述了 `URL` 与 `UI `之间的映射关系，这种映射是单向的，即 URL 变化引起 UI 更新（无需刷新页面）\n\n下面以`hash`模式为例子，改变`hash`值并不会导致浏览器向服务器发送请求，浏览器不发出请求，也就不会刷新页面\n\n`hash` 值改变，触发全局 `window` 对象上的 `hashchange` 事件。所以 `hash` 模式路由就是利用 `hashchange` 事件监听 `URL` 的变化，从而进行 `DOM` 操作来模拟页面跳转\n\n`react-router`也是基于这个特性实现路由的跳转\n\n下面以`HashRouter`组件分析进行展开：\n\n\n## HashRouter\n\n`HashRouter`包裹了整应用，\n\n通过`window.addEventListener('hashChange',callback)`监听`hash`值的变化，并传递给其嵌套的组件\n\n然后通过`context`将`location`数据往后代组件传递，如下：\n\n```jsx\nimport React, { Component } from 'react';\nimport { Provider } from './context'\n// 该组件下Api提供给子组件使用\nclass HashRouter extends Component {\n  constructor() {\n    super()\n    this.state = {\n      location: {\n        pathname: window.location.hash.slice(1) || '/'\n      }\n    }\n  }\n  // url路径变化 改变location\n  componentDidMount() {\n    window.location.hash = window.location.hash || '/'\n    window.addEventListener('hashchange', () => {\n      this.setState({\n        location: {\n          ...this.state.location,\n          pathname: window.location.hash.slice(1) || '/'\n        }\n      }, () => console.log(this.state.location))\n    })\n  }\n  render() {\n    let value = {\n      location: this.state.location\n    }\n    return (\n      <Provider value={value}>\n        {\n          this.props.children\n        }\n      </Provider>\n    );\n  }\n}\n\nexport default HashRouter;\n\n```\n\n\n### Router\n\n`Router`组件主要做的是通过`BrowserRouter`传过来的当前值，通过`props`传进来的`path`与`context`传进来的`pathname`进行匹配，然后决定是否执行渲染组件\n\n```js\nimport React, { Component } from 'react';\nimport { Consumer } from './context'\nconst { pathToRegexp } = require(\"path-to-regexp\");\nclass Route extends Component {\n  render() {\n    return (\n      <Consumer>\n        {\n          state => {\n            console.log(state)\n            let {path, component: Component} = this.props\n            let pathname = state.location.pathname\n            let reg = pathToRegexp(path, [], {end: false})\n            // 判断当前path是否包含pathname\n            if(pathname.match(reg)) {\n              return <Component></Component>\n            }\n            return null\n          }\n        }\n      </Consumer>\n    );\n  }\n}\nexport default Route;\n\n```\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6870376090297171975#heading-9\n\n- https://segmentfault.com/a/1190000023560665"
  },
  {
    "path": "docs/React/React Router.md",
    "content": "# 面试官：说说你对React Router的理解？常用的Router组件有哪些？\n\n ![](https://static.vue-js.com/c6635670-e8ac-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n`react-router`等前端路由的原理大致相同，可以实现无刷新的条件下切换显示不同的页面\n\n路由的本质就是页面的`URL`发生改变时，页面的显示结果可以根据`URL`的变化而变化，但是页面不会刷新\n\n因此，可以通过前端路由可以实现单页(SPA)应用\n\n`react-router`主要分成了几个不同的包：\n\n- react-router: 实现了路由的核心功能\n- react-router-dom： 基于 react-router，加入了在浏览器运行环境下的一些功能\n- react-router-native：基于 react-router，加入了 react-native 运行环境下的一些功能\n\n- react-router-config: 用于配置静态路由的工具库\n\n\n\n\n\n## 二、有哪些\n\n这里主要讲述的是`react-router-dom`的常用`API`，主要是提供了一些组件：\n\n- BrowserRouter、HashRouter\n- Route\n- Link、NavLink\n- switch\n- redirect\n\n\n\n### BrowserRouter、HashRouter\n\n`Router`中包含了对路径改变的监听，并且会将相应的路径传递给子组件\n\n`BrowserRouter`是`history`模式，`HashRouter`模式\n\n使用两者作为最顶层组件包裹其他组件\n\n```jsx\nimport { BrowserRouter as Router } from \"react-router-dom\";\n\nexport default function App() {\n  return (\n    <Router>\n      <main>\n        <nav>\n          <ul>\n            <li>\n              < a href=\" \">Home</ a>\n            </li>\n            <li>\n              < a href=\"/about\">About</ a>\n            </li>\n            <li>\n              < a href=\"/contact\">Contact</ a>\n            </li>\n          </ul>\n        </nav>\n      </main>\n    </Router>\n  );\n}\n```\n\n\n\n### Route\n\n`Route`用于路径的匹配，然后进行组件的渲染，对应的属性如下：\n\n- path 属性：用于设置匹配到的路径\n- component 属性：设置匹配到路径后，渲染的组件\n- render 属性：设置匹配到路径后，渲染的内容\n- exact 属性：开启精准匹配，只有精准匹配到完全一致的路径，才会渲染对应的组件\n\n```jsx\nimport { BrowserRouter as Router, Route } from \"react-router-dom\";\n\nexport default function App() {\n  return (\n    <Router>\n      <main>\n        <nav>\n          <ul>\n            <li>\n              < a href=\"/\">Home</ a>\n            </li>\n            <li>\n              < a href=\"/about\">About</ a>\n            </li>\n            <li>\n              < a href=\"/contact\">Contact</ a>\n            </li>\n          </ul>\n        </nav>\n        <Route path=\"/\" render={() => <h1>Welcome!</h1>} />\n      </main>\n    </Router>\n  );\n}\n```\n\n\n\n\n\n### Link、NavLink\n\n通常路径的跳转是使用`Link`组件，最终会被渲染成`a`元素，其中属性`to`代替`a`标题的`href`属性\n\n`NavLink`是在`Link`基础之上增加了一些样式属性，例如组件被选中时，发生样式变化，则可以设置`NavLink`的一下属性：\n\n- activeStyle：活跃时（匹配时）的样式\n- activeClassName：活跃时添加的class\n\n如下：\n\n```js\n<NavLink to=\"/\" exact activeStyle={{color: \"red\"}}>首页</NavLink>\n<NavLink to=\"/about\" activeStyle={{color: \"red\"}}>关于</NavLink>\n<NavLink to=\"/profile\" activeStyle={{color: \"red\"}}>我的</NavLink>\n```\n\n如果需要实现`js`实现页面的跳转，那么可以通过下面的形式：\n\n通过`Route`作为顶层组件包裹其他组件后,页面组件就可以接收到一些路由相关的东西，比如`props.history`\n\n```jsx\nconst Contact = ({ history }) => (\n  <Fragment>\n    <h1>Contact</h1>\n    <button onClick={() => history.push(\"/\")}>Go to home</button>\n    <FakeText />\n  </Fragment>\n);\n```\n\n`props `中接收到的`history`对象具有一些方便的方法，如`goBack`，`goForward`,`push`\n\n\n\n### redirect\n\n用于路由的重定向，当这个组件出现时，就会执行跳转到对应的`to`路径中，如下例子：\n\n```js\nconst About = ({\n  match: {\n    params: { name },\n  },\n}) => (\n  // props.match.params.name\n  <Fragment>\n    {name !== \"tom\" ? <Redirect to=\"/\" /> : null}\n    <h1>About {name}</h1>\n    <FakeText />\n  </Fragment>\n)\n```\n\n上述组件当接收到的路由参数`name` 不等于 `tom` 的时候，将会自动重定向到首页\n\n\n\n\n\n### switch\n\n`swich`组件的作用适用于当匹配到第一个组件的时候，后面的组件就不应该继续匹配\n\n如下例子：\n\n```jsx\n<Switch>\n  <Route exact path=\"/\" component={Home} />\n  <Route path=\"/about\" component={About} />\n  <Route path=\"/profile\" component={Profile} />\n  <Route path=\"/:userid\" component={User} />\n  <Route component={NoMatch} />\n</Switch>\n```\n\n如果不使用`switch`组件进行包裹\n\n\n\n\n\n除了一些路由相关的组件之外，`react-router`还提供一些`hooks`，如下：\n\n- useHistory\n- useParams\n- useLocation\n\n\n\n### useHistory\n\n`useHistory`可以让组件内部直接访问`history`，无须通过`props`获取\n\n```js\nimport { useHistory } from \"react-router-dom\";\n\nconst Contact = () => {\n  const history = useHistory();\n  return (\n    <Fragment>\n      <h1>Contact</h1>\n      <button onClick={() => history.push(\"/\")}>Go to home</button>\n    </Fragment>\n  );\n};\n```\n\n\n\n### useParams\n\n\n\n```jsx\nconst About = () => {\n  const { name } = useParams();\n  return (\n    // props.match.params.name\n    <Fragment>\n      {name !== \"John Doe\" ? <Redirect to=\"/\" /> : null}\n      <h1>About {name}</h1>\n      <Route component={Contact} />\n    </Fragment>\n  );\n};\n```\n\n\n\n### useLocation\n\n`useLocation` 会返回当前 `URL `的 `location `对象\n\n```jsx\nimport { useLocation } from \"react-router-dom\";\n\nconst Contact = () => {\n  const { pathname } = useLocation();\n\n  return (\n    <Fragment>\n      <h1>Contact</h1>\n      <p>Current URL: {pathname}</p >\n    </Fragment>\n  );\n};\n```\n\n\n\n\n\n## 三、参数传递\n\n这些路由传递参数主要分成了三种形式：\n\n- 动态路由的方式\n- search传递参数\n- to传入对象\n\n\n\n### 动态路由\n\n动态路由的概念指的是路由中的路径并不会固定\n\n例如将`path`在`Route`匹配时写成`/detail/:id`，那么 `/detail/abc`、`/detail/123`都可以匹配到该`Route`\n\n```jsx\n<NavLink to=\"/detail/abc123\">详情</NavLink>\n\n<Switch>\n    ... 其他Route\n    <Route path=\"/detail/:id\" component={Detail}/>\n    <Route component={NoMatch} />\n</Switch>\n```\n\n获取参数方式如下：\n\n```jsx\nconsole.log(props.match.params.xxx)\n```\n\n\n\n### search传递参数\n\n在跳转的路径中添加了一些query参数；\n\n```jsx\n<NavLink to=\"/detail2?name=why&age=18\">详情2</NavLink>\n\n<Switch>\n  <Route path=\"/detail2\" component={Detail2}/>\n</Switch>\n```\n\n获取形式如下：\n\n```js\nconsole.log(props.location.search)\n```\n\n\n\n\n\n### to传入对象\n\n传递方式如下：\n\n```jsx\n<NavLink to={{\n    pathname: \"/detail2\", \n    query: {name: \"kobe\", age: 30},\n    state: {height: 1.98, address: \"洛杉矶\"},\n    search: \"?apikey=123\"\n  }}>\n  详情2\n</NavLink>\n```\n\n获取参数的形式如下：\n\n```js\nconsole.log(props.location)\n```\n\n\n\n\n\n## 参考文献\n\n- http://react-guide.github.io/react-router-cn/docs/API.html#route"
  },
  {
    "path": "docs/React/React refs.md",
    "content": "# 面试官：说说对React refs 的理解？应用场景？ \n\n ![](https://static.vue-js.com/25162040-de02-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n`Refs` 在计算机中称为弹性文件系统（英语：Resilient File System，简称ReFS）\n\n`React` 中的 `Refs`提供了一种方式，允许我们访问 `DOM `节点或在 `render `方法中创建的 `React `元素\n\n本质为`ReactDOM.render()`返回的组件实例，如果是渲染组件则返回的是组件实例，如果渲染`dom`则返回的是具体的`dom`节点\n\n\n## 二、如何使用\n\n创建`ref`的形式有三种：\n\n- 传入字符串，使用时通过 this.refs.传入的字符串的格式获取对应的元素\n- 传入对象，对象是通过 React.createRef()  方式创建出来，使用时获取到创建的对象中存在 current 属性就是对应的元素\n- 传入函数，该函数会在 DOM 被挂载时进行回调，这个函数会传入一个 元素对象，可以自己保存，使用时，直接拿到之前保存的元素对象即可\n- 传入hook，hook是通过 useRef() 方式创建，使用时通过生成hook对象的 current 属性就是对应的元素\n\n\n\n### 传入字符串\n\n只需要在对应元素或组件中`ref`属性\n\n```jsx\nclass MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n    this.myRef = React.createRef();\n  }\n  render() {\n    return <div ref=\"myref\" />;\n  }\n}\n```\n\n访问当前节点的方式如下：\n\n```js\nthis.refs.myref.innerHTML = \"hello\";\n```\n\n\n### 传入对象\n\n`refs`通过`React.createRef()`创建，然后将`ref`属性添加到`React`元素中，如下：\n\n```jsx\nclass MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n    this.myRef = React.createRef();\n  }\n  render() {\n    return <div ref={this.myRef} />;\n  }\n}\n```\n\n当 `ref` 被传递给 `render` 中的元素时，对该节点的引用可以在 `ref` 的 `current` 属性中访问\n\n```js\nconst node = this.myRef.current;\n```\n\n\n### 传入函数\n\n当`ref`传入为一个函数的时候，在渲染过程中，回调函数参数会传入一个元素对象，然后通过实例将对象进行保存\n\n```jsx\nclass MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n    this.myRef = React.createRef();\n  }\n  render() {\n    return <div ref={element => this.myref = element} />;\n  }\n}\n```\n\n获取`ref`对象只需要通过先前存储的对象即可\n\n```js\nconst node = this.myref \n```\n\n\n### 传入hook\n\n通过`useRef`创建一个`ref`，整体使用方式与`React.createRef`一致\n\n```jsx\nfunction App(props) {\n  const myref = useRef()\n  return (\n    <>\n      <div ref={myref}></div>\n    </>\n  )\n}\n```\n\n获取`ref`属性也是通过`hook`对象的`current`属性\n\n```js\nconst node = myref.current;\n```\n\n上述三种情况都是`ref`属性用于原生`HTML`元素上，如果`ref`设置的组件为一个类组件的时候，`ref`对象接收到的是组件的挂载实例\n\n注意的是，不能在函数组件上使用`ref`属性，因为他们并没有实例\n\n\n## 三、应用场景\n\n在某些情况下，我们会通过使用`refs`来更新组件，但这种方式并不推荐，更多情况我们是通过`props`与`state`的方式进行去重新渲染子元素\n\n过多使用`refs`，会使组件的实例或者是`DOM`结构暴露，违反组件封装的原则\n\n例如，避免在 `Dialog` 组件里暴露 `open()` 和 `close()` 方法，最好传递 `isOpen` 属性\n\n但下面的场景使用`refs`非常有用：\n\n- 对Dom元素的焦点控制、内容选择、控制\n- 对Dom元素的内容设置及媒体播放\n- 对Dom元素的操作和对组件实例的操作\n- 集成第三方 DOM 库\n\n\n## 参考文献\n\n- https://zh-hans.reactjs.org/docs/refs-and-the-dom.html\n- https://segmentfault.com/a/1190000020842342\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/React/React.md",
    "content": "# 面试官：说说对 React 的理解？有哪些特性？\n\n![](https://static.vue-js.com/671f5a90-d265-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\nReact，用于构建用户界面的 JavaScript 库，只提供了 UI 层面的解决方案\n\n遵循组件设计模式、声明式编程范式和函数式编程概念，以使前端应用程序更高效\n\n使用虚拟 `DOM` 来有效地操作 `DOM`，遵循从高阶组件到低阶组件的单向数据流\n\n帮助我们将界面成了各个独立的小块，每一个块就是组件，这些组件之间可以组合、嵌套，构成整体页面\n\n`react` 类组件使用一个名为 `render()` 的方法或者函数组件`return`，接收输入的数据并返回需要展示的内容\n\n```jsx\nclass HelloMessage extends React.Component {\n  render() {\n    return <div>Hello {this.props.name}</div>;\n  }\n}\n\nReactDOM.render(\n  <HelloMessage name=\"Taylor\" />,\n  document.getElementById(\"hello-example\")\n);\n```\n\n上述这种类似 `XML` 形式就是 `JSX`，最终会被 `babel` 编译为合法的 `JS` 语句调用\n\n被传入的数据可在组件中通过 `this.props` 在 `render()` 访问\n\n## 二、特性\n\n`React` 特性有很多，如：\n\n- JSX 语法\n- 单向数据绑定\n- 虚拟 DOM\n- 声明式编程\n- Component\n\n着重介绍下声明式编程及 Component\n\n### 声明式编程\n\n声明式编程是一种编程范式，它关注的是你要做什么，而不是如何做\n\n它表达逻辑而不显式地定义步骤。这意味着我们需要根据逻辑的计算来声明要显示的组件\n\n如实现一个标记的地图：\n\n通过命令式创建地图、创建标记、以及在地图上添加的标记的步骤如下：\n\n```js\n// 创建地图\nconst map = new Map.map(document.getElementById(\"map\"), {\n  zoom: 4,\n  center: { lat, lng },\n});\n\n// 创建标记\nconst marker = new Map.marker({\n  position: { lat, lng },\n  title: \"Hello Marker\",\n});\n\n// 地图上添加标记\nmarker.setMap(map);\n```\n\n而用 `React` 实现上述功能则如下：\n\n```jsx\n<Map zoom={4} center={(lat, lng)}>\n  <Marker position={(lat, lng)} title={\"Hello Marker\"} />\n</Map>\n```\n\n声明式编程方式使得 `React` 组件很容易使用，最终的代码简单易于维护\n\n### Component\n\n在 `React` 中，一切皆为组件。通常将应用程序的整个逻辑分解为小的单个部分。 我们将每个单独的部分称为组件\n\n组件可以是一个函数或者是一个类，接受数据输入，处理它并返回在 `UI` 中呈现的 `React` 元素\n\n函数式组件如下：\n\n```jsx\nconst Header = () => {\n  return (\n    <Jumbotron style={{ backgroundColor: \"orange\" }}>\n      <h1>TODO App</h1>\n    </Jumbotron>\n  );\n};\n```\n\n类组件（有状态组件）如下：\n\n```jsx\nclass Dashboard extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {};\n  }\n  render() {\n    return (\n      <div className=\"dashboard\">\n        <ToDoForm />\n        <ToDolist />\n      </div>\n    );\n  }\n}\n```\n\n一个组件该有的特点如下：\n\n- 可组合：每个组件易于和其它组件一起使用，或者嵌套在另一个组件内部\n- 可重用：每个组件都是具有独立功能的，它可以被使用在多个 UI 场景\n- 可维护：每个小的组件仅仅包含自身的逻辑，更容易被理解和维护\n\n## 三、优势\n\n通过上面的初步了解，可以感受到 `React` 存在的优势：\n\n- 高效灵活\n- 声明式的设计，简单使用\n- 组件式开发，提高代码复用率\n- 单向响应的数据流会比双向绑定的更安全，速度更快\n\n## 参考文献\n\n- [https://segmentfault.com/a/1190000015924762](https://segmentfault.com/a/1190000015924762)\n- [https://react.docschina.org/](https://react.docschina.org/)\n"
  },
  {
    "path": "docs/React/Real DOM_Virtual DOM.md",
    "content": "# 面试官：说说 Real DOM 和 Virtual DOM 的区别？优缺点？\n\n![](https://static.vue-js.com/f1d36350-d302-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\nReal DOM，真实 `DOM`，意思为文档对象模型，是一个结构化文本的抽象，在页面渲染出的每一个结点都是一个真实 `DOM` 结构，如下：\n\n![](https://static.vue-js.com/fc7ba8d0-d302-11eb-85f6-6fac77c0c9b3.png)\n\n`Virtual Dom`，本质上是以 `JavaScript` 对象形式存在的对 `DOM` 的描述\n\n创建虚拟 `DOM` 目的就是为了更好将虚拟的节点渲染到页面视图中，虚拟 `DOM` 对象的节点与真实 `DOM` 的属性一一照应\n\n在 `React` 中，`JSX` 是其一大特性，可以让你在 `JS` 中通过使用 `XML` 的方式去直接声明界面的 `DOM` 结构\n\n```jsx\n// 创建 h1 标签，右边千万不能加引号\nconst vDom = <h1>Hello World</h1>; \n// 找到 <div id=\"root\"></div> 节点\nconst root = document.getElementById(\"root\"); \n// 把创建的 h1 标签渲染到 root 节点上\nReactDOM.render(vDom, root); \n```\n\n上述中，`ReactDOM.render()` 用于将你创建好的虚拟 `DOM` 节点插入到某个真实节点上，并渲染到页面上\n\n`JSX` 实际是一种语法糖，在使用过程中会被 `babel` 进行编译转化成 `JS` 代码，上述 `VDOM` 转化为如下：\n\n```jsx\nconst vDom = React.createElement(\n  'h1'，\n  { className: 'hClass', id: 'hId' },\n  'hello world'\n)\n```\n\n可以看到，`JSX` 就是为了简化直接调用 `React.createElement()` 方法：\n\n- 第一个参数是标签名，例如 h1、span、table...\n\n- 第二个参数是个对象，里面存着标签的一些属性，例如 id、class 等\n\n- 第三个参数是节点中的文本\n\n通过 `console.log(VDOM)`，则能够得到虚拟 `VDOM` 消息\n\n![](https://static.vue-js.com/1716b9a0-d303-11eb-ab90-d9ae814b240d.png)\n\n所以可以得到，`JSX` 通过 `babel` 的方式转化成 `React.createElement` 执行，返回值是一个对象，也就是虚拟 `DOM`\n\n## 二、区别\n\n两者的区别如下：\n\n- 虚拟 DOM 不会进行排版与重绘操作，而真实 DOM 会频繁重排与重绘\n- 虚拟 DOM 的总损耗是“虚拟 DOM 增删改+真实 DOM 差异增删改+排版与重绘”，真实 DOM 的总损耗是“真实 DOM 完全增删改+排版与重绘”\n\n拿[以前文章](https://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484516&idx=1&sn=965a4ce32bf93adb9ed112922c5cb8f5&chksm=fc10c632cb674f2484fdf914d76fba55afcefca3b5adcbe6cf4b0c7fd36e29d0292e8cefceb5&scene=178&cur_album_id=1711105826272116736#rd)举过的例子：\n\n传统的原生 `api` 或 `jQuery` 去操作 `DOM` 时，浏览器会从构建 `DOM` 树开始从头到尾执行一遍流程\n\n当你在一次操作时，需要更新 10 个 `DOM` 节点，浏览器没这么智能，收到第一个更新 `DOM` 请求后，并不知道后续还有 9 次更新操作，因此会马上执行流程，最终执行 10 次流程\n\n而通过 `VNode`，同样更新 10 个 `DOM` 节点，虚拟 `DOM` 不会立即操作 `DOM`，而是将这 10 次更新的 `diff` 内容保存到本地的一个 `js` 对象中，最终将这个 `js` 对象一次性 `attach` 到 `DOM` 树上，避免大量的无谓计算\n\n## 三、优缺点\n\n真实 `DOM` 的优势：\n\n- 易用\n\n缺点：\n\n- 效率低，解析速度慢，内存占用量过高\n- 性能差：频繁操作真实 DOM，易于导致重绘与回流\n\n使用虚拟 `DOM` 的优势如下：\n\n- 简单方便：如果使用手动操作真实 `DOM` 来完成页面，繁琐又容易出错，在大规模应用下维护起来也很困难\n\n- 性能方面：使用 Virtual DOM，能够有效避免真实 DOM 数频繁更新，减少多次引起重绘与回流，提高性能\n- 跨平台：React 借助虚拟 DOM，带来了跨平台的能力，一套代码多端运行\n\n缺点：\n\n- 在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化\n- 首次渲染大量 DOM 时，由于多了一层虚拟 DOM 的计算，速度比正常稍慢\n\n## 参考文献\n\n- [https://juejin.cn/post/6844904052971536391](https://juejin.cn/post/6844904052971536391)\n- [https://www.html.cn/qa/other/22832.html](https://www.html.cn/qa/other/22832.html)\n"
  },
  {
    "path": "docs/React/Redux Middleware.md",
    "content": "# 面试官：说说对Redux中间件的理解？常用的中间件有哪些？实现原理？\n\n ![](https://static.vue-js.com/4520bbd0-e699-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n中间件（Middleware）是介于应用系统和系统软件之间的一类软件，它使用系统软件所提供的基础服务（功能），衔接网络上应用系统的各个部分或不同的应用，能够达到资源共享、功能共享的目的\n\n在上篇文章中，了解到了`Redux`整个工作流程，当`action`发出之后，`reducer`立即算出`state`，整个过程是一个同步的操作\n\n那么如果需要支持异步操作，或者支持错误处理、日志监控，这个过程就可以用上中间件\n\n`Redux`中，中间件就是放在就是在`dispatch`过程，在分发`action`进行拦截处理，如下图：\n\n ![](https://static.vue-js.com/57edf750-e699-11eb-ab90-d9ae814b240d.png)\n\n其本质上一个函数，对`store.dispatch`方法进行了改造，在发出 `Action `和执行 `Reducer `这两步之间，添加了其他功能\n\n\n## 二、常用的中间件\n\n有很多优秀的`redux`中间件，如：\n\n- redux-thunk：用于异步操作\n- redux-logger：用于日志记录\n\n上述的中间件都需要通过`applyMiddlewares`进行注册，作用是将所有的中间件组成一个数组，依次执行\n\n然后作为第二个参数传入到`createStore`中\n\n```js\nconst store = createStore(\n  reducer,\n  applyMiddleware(thunk, logger)\n);\n```\n\n### redux-thunk\n\n`redux-thunk`是官网推荐的异步处理中间件\n\n默认情况下的`dispatch(action)`，`action`需要是一个`JavaScript`的对象\n\n`redux-thunk`中间件会判断你当前传进来的数据类型，如果是一个函数，将会给函数传入参数值（dispatch，getState）\n\n- dispatch函数用于我们之后再次派发action\n- getState函数考虑到我们之后的一些操作需要依赖原来的状态，用于让我们可以获取之前的一些状态\n\n所以`dispatch`可以写成下述函数的形式：\n\n```js\nconst getHomeMultidataAction = () => {\n  return (dispatch) => {\n    axios.get(\"http://xxx.xx.xx.xx/test\").then(res => {\n      const data = res.data.data;\n      dispatch(changeBannersAction(data.banner.list));\n      dispatch(changeRecommendsAction(data.recommend.list));\n    })\n  }\n}\n```\n\n\n\n### redux-logger\n\n\n如果想要实现一个日志功能，则可以使用现成的`redux-logger`\n\n```js\n\nimport { applyMiddleware, createStore } from 'redux';\nimport createLogger from 'redux-logger';\nconst logger = createLogger();\n\nconst store = createStore(\n  reducer,\n  applyMiddleware(logger)\n);\n```\n\n这样我们就能简单通过中间件函数实现日志记录的信息\n\n\n\n## 三、实现原理\n\n首先看看`applyMiddlewares`的源码\n\n```js\nexport default function applyMiddleware(...middlewares) {\n  return (createStore) => (reducer, preloadedState, enhancer) => {\n    var store = createStore(reducer, preloadedState, enhancer);\n    var dispatch = store.dispatch;\n    var chain = [];\n\n    var middlewareAPI = {\n      getState: store.getState,\n      dispatch: (action) => dispatch(action)\n    };\n    chain = middlewares.map(middleware => middleware(middlewareAPI));\n    dispatch = compose(...chain)(store.dispatch);\n\n    return {...store, dispatch}\n  }\n}\n```\n\n所有中间件被放进了一个数组`chain`，然后嵌套执行，最后执行`store.dispatch`。可以看到，中间件内部（`middlewareAPI`）可以拿到`getState`和`dispatch`这两个方法\n\n在上面的学习中，我们了解到了`redux-thunk`的基本使用\n\n内部会将`dispatch`进行一个判断，然后执行对应操作，原理如下：\n\n```js\nfunction patchThunk(store) {\n    let next = store.dispatch;\n\n    function dispatchAndThunk(action) {\n        if (typeof action === \"function\") {\n            action(store.dispatch, store.getState);\n        } else {\n            next(action);\n        }\n    }\n\n    store.dispatch = dispatchAndThunk;\n}\n```\n\n实现一个日志输出的原理也非常简单，如下：\n\n```js\nlet next = store.dispatch;\n\nfunction dispatchAndLog(action) {\n  console.log(\"dispatching:\", addAction(10));\n  next(addAction(5));\n  console.log(\"新的state:\", store.getState());\n}\n\nstore.dispatch = dispatchAndLog;\n```\n\n\n## 参考文献\n\n- http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html"
  },
  {
    "path": "docs/React/SyntheticEvent.md",
    "content": "# 面试官：说说React的事件机制？\n\n ![](https://static.vue-js.com/f054f080-d86f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n`React`基于浏览器的事件机制自身实现了一套事件机制，包括事件注册、事件的合成、事件冒泡、事件派发等\n\n在`React`中这套事件机制被称之为合成事件\n\n#### 合成事件（SyntheticEvent）\n\n合成事件是 `React `模拟原生 `DOM `事件所有能力的一个事件对象，即浏览器原生事件的跨浏览器包装器\n\n根据 `W3C `规范来定义合成事件，兼容所有浏览器，拥有与浏览器原生事件相同的接口，例如：\n\n```jsx\nconst button = <button onClick={handleClick}>按钮</button>\n```\n\n如果想要获得原生`DOM`事件，可以通过`e.nativeEvent`属性获取\n\n```js\nconst handleClick = (e) => console.log(e.nativeEvent);;\nconst button = <button onClick={handleClick}>按钮</button>\n```\n\n从上面可以看到`React`事件和原生事件也非常的相似，但也有一定的区别：\n\n- 事件名称命名方式不同\n\n```jsx\n// 原生事件绑定方式\n<button onclick=\"handleClick()\">按钮命名</button>\n      \n// React 合成事件绑定方式\nconst button = <button onClick={handleClick}>按钮命名</button>\n```\n\n- 事件处理函数书写不同\n\n```jsx\n// 原生事件 事件处理函数写法\n<button onclick=\"handleClick()\">按钮命名</button>\n      \n// React 合成事件 事件处理函数写法\nconst button = <button onClick={handleClick}>按钮命名</button>\n```\n\n虽然`onclick`看似绑定到`DOM`元素上，但实际并不会把事件代理函数直接绑定到真实的节点上，而是把所有的事件绑定到结构的最外层，使用一个统一的事件去监听\n\n这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时，只是在这个统一的事件监听器上插入或删除一些对象\n\n当事件发生时，首先被这个统一的事件监听器处理，然后在映射里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制，效率也有很大提升\n\n\n## 二、执行顺序\n\n关于`React `合成事件与原生事件执行顺序，可以看看下面一个例子：\n\n```jsx\nimport  React  from 'react';\nclass App extends React.Component{\n\n  constructor(props) {\n    super(props);\n    this.parentRef = React.createRef();\n    this.childRef = React.createRef();\n  }\n  componentDidMount() {\n    console.log(\"React componentDidMount！\");\n    this.parentRef.current?.addEventListener(\"click\", () => {\n      console.log(\"原生事件：父元素 DOM 事件监听！\");\n    });\n    this.childRef.current?.addEventListener(\"click\", () => {\n      console.log(\"原生事件：子元素 DOM 事件监听！\");\n    });\n    document.addEventListener(\"click\", (e) => {\n      console.log(\"原生事件：document DOM 事件监听！\");\n    });\n  }\n  parentClickFun = () => {\n    console.log(\"React 事件：父元素事件监听！\");\n  };\n  childClickFun = () => {\n    console.log(\"React 事件：子元素事件监听！\");\n  };\n  render() {\n    return (\n      <div ref={this.parentRef} onClick={this.parentClickFun}>\n        <div ref={this.childRef} onClick={this.childClickFun}>\n          分析事件执行顺序\n        </div>\n      </div>\n    );\n  }\n}\nexport default App;\n```\n\n输出顺序为：\n\n```tex\n原生事件：子元素 DOM 事件监听！ \n原生事件：父元素 DOM 事件监听！ \nReact 事件：子元素事件监听！ \nReact 事件：父元素事件监听！ \n原生事件：document DOM 事件监听！ \n```\n\n可以得出以下结论：\n\n- React 所有事件都挂载在 document 对象上\n- 当真实 DOM 元素触发事件，会冒泡到 document 对象后，再处理 React 事件\n- 所以会先执行原生事件，然后处理 React 事件\n- 最后真正执行 document 上挂载的事件\n\n对应过程如图所示：\n\n ![](https://static.vue-js.com/08e22ff0-d870-11eb-ab90-d9ae814b240d.png)\n\n所以想要阻止不同时间段的冒泡行为，对应使用不同的方法，对应如下：\n\n- 阻止合成事件间的冒泡，用e.stopPropagation()\n- 阻止合成事件与最外层 document 上的事件间的冒泡，用e.nativeEvent.stopImmediatePropagation()\n\n- 阻止合成事件与除最外层document上的原生事件上的冒泡，通过判断e.target来避免\n\n```js\ndocument.body.addEventListener('click', e => {   \n    if (e.target && e.target.matches('div.code')) {  \n        return;    \n    }    \n    this.setState({   active: false,    });   }); \n}\n```\n\n\n## 三、总结\n\n`React`事件机制总结如下：\n\n- React 上注册的事件最终会绑定在document这个 DOM 上，而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上，其他节点没有绑定事件)\n- React 自身实现了一套事件冒泡机制，所以这也就是为什么我们 event.stopPropagation()无效的原因。\n- React 通过队列的形式，从触发的组件向父组件回溯，然后调用他们 JSX 中定义的 callback\n- React 有一套自己的合成事件 SyntheticEvent\n\n\n## 参考文献\n- https://zh-hans.reactjs.org/docs/events.html\n- https://segmentfault.com/a/1190000015725214?utm_source=sf-similar-article\n- https://segmentfault.com/a/1190000038251163\n"
  },
  {
    "path": "docs/React/animation.md",
    "content": "# 面试官：在react中组件间过渡动画如何实现？\n ![](https://static.vue-js.com/294f1e00-e4b0-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n在日常开发中，页面切换时的转场动画是比较基础的一个场景\n\n当一个组件在显示与消失过程中存在过渡动画，可以很好的增加用户的体验\n\n在`react`中实现过渡动画效果会有很多种选择，如`react-transition-group`，`react-motion`，`Animated`，以及原生的`CSS`都能完成切换动画\n\n\n## 二、如何实现\n\n在`react`中，`react-transition-group`是一种很好的解决方案，其为元素添加`enter`，`enter-active`，`exit`，`exit-active`这一系列勾子\n\n可以帮助我们方便的实现组件的入场和离场动画\n\n其主要提供了三个主要的组件：\n\n- CSSTransition：在前端开发中，结合 CSS 来完成过渡动画效果\n- SwitchTransition：两个组件显示和隐藏切换时，使用该组件\n- TransitionGroup：将多个动画组件包裹在其中，一般用于列表中元素的动画\n\n### CSSTransition\n\n其实现动画的原理在于，当`CSSTransition`的`in`属性置为`true`时，`CSSTransition`首先会给其子组件加上`xxx-enter`、`xxx-enter-active`的`class`执行动画\n\n当动画执行结束后，会移除两个`class`，并且添加`-enter-done`的`class`\n\n所以可以利用这一点，通过`css`的`transition`属性，让元素在两个状态之间平滑过渡，从而得到相应的动画效果\n\n当`in`属性置为`false`时，`CSSTransition`会给子组件加上`xxx-exit`和`xxx-exit-active`的`class`，然后开始执行动画，当动画结束后，移除两个`class`，然后添加`-enter-done`的`class`\n\n如下例子：\n\n```jsx\nexport default class App2 extends React.PureComponent {\n\n  state = {show: true};\n\n  onToggle = () => this.setState({show: !this.state.show});\n\n  render() {\n    const {show} = this.state;\n    return (\n      <div className={'container'}>\n        <div className={'square-wrapper'}>\n          <CSSTransition\n            in={show}\n            timeout={500}\n            classNames={'fade'}\n            unmountOnExit={true}\n          >\n            <div className={'square'} />\n          </CSSTransition>\n        </div>\n        <Button onClick={this.onToggle}>toggle</Button>\n      </div>\n    );\n  }\n}\n```\n\n对应`css`样式如下：\n\n```css\n.fade-enter {\n  opacity: 0;\n  transform: translateX(100%);\n}\n\n.fade-enter-active {\n  opacity: 1;\n  transform: translateX(0);\n  transition: all 500ms;\n}\n\n.fade-exit {\n  opacity: 1;\n  transform: translateX(0);\n}\n\n.fade-exit-active {\n  opacity: 0;\n  transform: translateX(-100%);\n  transition: all 500ms;\n}\n```\n\n\n\n### SwitchTransition\n\n`SwitchTransition`可以完成两个组件之间切换的炫酷动画\n\n比如有一个按钮需要在`on`和`off`之间切换，我们希望看到`on`先从左侧退出，`off`再从右侧进入\n\n`SwitchTransition`中主要有一个属性`mode`，对应两个值：\n\n- in-out：表示新组件先进入，旧组件再移除；\n- out-in：表示就组件先移除，新组建再进入\n\n`SwitchTransition`组件里面要有`CSSTransition`，不能直接包裹你想要切换的组件\n\n里面的`CSSTransition`组件不再像以前那样接受`in`属性来判断元素是何种状态，取而代之的是`key`属性\n\n下面给出一个按钮入场和出场的示例，如下：\n\n```jsx\nimport { SwitchTransition, CSSTransition } from \"react-transition-group\";\n\nexport default class SwitchAnimation extends PureComponent {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      isOn: true\n    }\n  }\n\n  render() {\n    const {isOn} = this.state;\n\n    return (\n      <SwitchTransition mode=\"out-in\">\n        <CSSTransition classNames=\"btn\"\n                       timeout={500}\n                       key={isOn ? \"on\" : \"off\"}>\n          {\n          <button onClick={this.btnClick.bind(this)}>\n            {isOn ? \"on\": \"off\"}\n          </button>\n        }\n        </CSSTransition>\n      </SwitchTransition>\n    )\n  }\n\n  btnClick() {\n    this.setState({isOn: !this.state.isOn})\n  }\n}\n```\n\n`css`文件对应如下：\n\n```css\n.btn-enter {\n  transform: translate(100%, 0);\n  opacity: 0;\n}\n\n.btn-enter-active {\n  transform: translate(0, 0);\n  opacity: 1;\n  transition: all 500ms;\n}\n\n.btn-exit {\n  transform: translate(0, 0);\n  opacity: 1;\n}\n\n.btn-exit-active {\n  transform: translate(-100%, 0);\n  opacity: 0;\n  transition: all 500ms;\n}\n```\n\n\n\n### TransitionGroup\n\n当有一组动画的时候，就可将这些`CSSTransition`放入到一个`TransitionGroup`中来完成动画\n\n同样`CSSTransition`里面没有`in`属性，用到了`key`属性\n\n`TransitionGroup`在感知`children`发生变化的时候，先保存移除的节点，当动画结束后才真正移除\n\n其处理方式如下：\n\n- 插入的节点，先渲染dom，然后再做动画\n\n- 删除的节点，先做动画，然后再删除dom\n\n如下：\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { CSSTransition, TransitionGroup } from 'react-transition-group';\n\nexport default class GroupAnimation extends PureComponent {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      friends: []\n    }\n  }\n\n  render() {\n    return (\n      <div>\n        <TransitionGroup>\n          {\n            this.state.friends.map((item, index) => {\n              return (\n                <CSSTransition classNames=\"friend\" timeout={300} key={index}>\n                  <div>{item}</div>\n                </CSSTransition>\n              )\n            })\n          }\n        </TransitionGroup>\n        <button onClick={e => this.addFriend()}>+friend</button>\n      </div>\n    )\n  }\n\n  addFriend() {\n    this.setState({\n      friends: [...this.state.friends, \"coderwhy\"]\n    })\n  }\n}\n```\n\n对应`css`如下：\n\n```css\n.friend-enter {\n    transform: translate(100%, 0);\n    opacity: 0;\n}\n\n.friend-enter-active {\n    transform: translate(0, 0);\n    opacity: 1;\n    transition: all 500ms;\n}\n\n.friend-exit {\n    transform: translate(0, 0);\n    opacity: 1;\n}\n\n.friend-exit-active {\n    transform: translate(-100%, 0);\n    opacity: 0;\n    transition: all 500ms;\n}\n```\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000018861018\n- https://mp.weixin.qq.com/s/14HneI7SpfrRHKtqgosIiA\n"
  },
  {
    "path": "docs/React/capture error.md",
    "content": "# 面试官：说说你在React项目是如何捕获错误的？\n\n ![](https://static.vue-js.com/8db1b5c0-f288-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n错误在我们日常编写代码是非常常见的\n\n举个例子，在`react`项目中去编写组件内`JavaScript`代码错误会导致 `React` 的内部状态被破坏，导致整个应用崩溃，这是不应该出现的现象\n\n作为一个框架，`react`也有自身对于错误的处理的解决方案\n\n\n## 二、如何做\n\n为了解决出现的错误导致整个应用崩溃的问题，`react16`引用了**错误边界**新的概念\n\n错误边界是一种 `React` 组件，这种组件可以捕获发生在其子组件树任何位置的 `JavaScript` 错误，并打印这些错误，同时展示降级 `UI`，而并不会渲染那些发生崩溃的子组件树\n\n错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误\n\n形成错误边界组件的两个条件：\n\n- 使用了  static getDerivedStateFromError()\n- 使用了 componentDidCatch()\n\n抛出错误后，请使用 `static getDerivedStateFromError()` 渲染备用 UI ，使用 `componentDidCatch()` 打印错误信息，如下：\n\n```jsx\nclass ErrorBoundary extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = { hasError: false };\n  }\n\n  static getDerivedStateFromError(error) {\n    // 更新 state 使下一次渲染能够显示降级后的 UI\n    return { hasError: true };\n  }\n\n  componentDidCatch(error, errorInfo) {\n    // 你同样可以将错误日志上报给服务器\n    logErrorToMyService(error, errorInfo);\n  }\n\n  render() {\n    if (this.state.hasError) {\n      // 你可以自定义降级后的 UI 并渲染\n      return <h1>Something went wrong.</h1>;\n    }\n\n    return this.props.children; \n  }\n}\n```\n\n然后就可以把自身组件的作为错误边界的子组件，如下：\n\n```jsx\n<ErrorBoundary>\n  <MyWidget />\n</ErrorBoundary>\n```\n\n下面这些情况无法捕获到异常：\n\n- 事件处理\n- 异步代码\n- 服务端渲染\n- 自身抛出来的错误\n\n在`react 16`版本之后，会把渲染期间发生的所有错误打印到控制台\n\n除了错误信息和 JavaScript 栈外，React 16 还提供了组件栈追踪。现在你可以准确地查看发生在组件树内的错误信息：\n\n ![](https://static.vue-js.com/7b2b51d0-f289-11eb-ab90-d9ae814b240d.png)\n\n可以看到在错误信息下方文字中存在一个组件栈，便于我们追踪错误\n\n对于错误边界无法捕获的异常，如事件处理过程中发生问题并不会捕获到，是因为其不会在渲染期间触发，并不会导致渲染时候问题\n\n这种情况可以使用`js`的`try...catch...`语法，如下：\n\n```jsx\nclass MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = { error: null };\n    this.handleClick = this.handleClick.bind(this);\n  }\n\n  handleClick() {\n    try {\n      // 执行操作，如有错误则会抛出\n    } catch (error) {\n      this.setState({ error });\n    }\n  }\n\n  render() {\n    if (this.state.error) {\n      return <h1>Caught an error.</h1>\n    }\n    return <button onClick={this.handleClick}>Click Me</button>\n  }\n}\n```\n\n\n除此之外还可以通过监听`onerror`事件\n\n```js\nwindow.addEventListener('error', function(event) { ... })\n```\n\n\n## 参考文献\n\n- https://zh-hans.reactjs.org/docs/error-boundaries.html"
  },
  {
    "path": "docs/React/class_function component.md",
    "content": "# 面试官：说说对React中类组件和函数组件的理解？有什么区别？\n\n ![](https://static.vue-js.com/6c196d80-de39-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、类组件\n\n类组件，顾名思义，也就是通过使用`ES6`类的编写形式去编写组件，该类必须继承`React.Component`\n\n如果想要访问父组件传递过来的参数，可通过`this.props`的方式去访问\n\n在组件中必须实现`render`方法，在`return`中返回`React`对象，如下：\n\n```jsx\nclass Welcome extends React.Component {\n  constructor(props) {\n    super(props)\n  }\n  render() {\n    return <h1>Hello, {this.props.name}</h1>\n  }\n}\n```\n\n\n\n\n\n## 二、函数组件\n\n函数组件，顾名思义，就是通过函数编写的形式去实现一个`React`组件，是`React`中定义组件最简单的方式\n\n```jsx\nfunction Welcome(props) {\n  return <h1>Hello, {props.name}</h1>;\n}\n```\n\n函数第一个参数为`props`用于接收父组件传递过来的参数\n\n\n\n## 三、区别\n\n针对两种`React`组件，其区别主要分成以下几大方向：\n\n- 编写形式\n- 状态管理\n- 生命周期\n\n- 调用方式\n\n- 获取渲染的值\n\n\n\n### 编写形式\n\n两者最明显的区别在于编写形式的不同，同一种功能的实现可以分别对应类组件和函数组件的编写形式\n\n函数组件：\n\n```jsx\nfunction Welcome(props) {\n  return <h1>Hello, {props.name}</h1>;\n}\n```\n\n类组件：\n\n```jsx\nclass Welcome extends React.Component {\n  constructor(props) {\n    super(props)\n  }\n  render() {\n    return <h1>Hello, {this.props.name}</h1>\n  }\n}\n```\n\n\n\n### 状态管理\n\n在`hooks`出来之前，函数组件就是无状态组件，不能保管组件的状态，不像类组件中调用`setState`\n\n如果想要管理`state`状态，可以使用`useState`，如下：\n\n```jsx\nconst FunctionalComponent = () => {\n    const [count, setCount] = React.useState(0);\n\n    return (\n        <div>\n            <p>count: {count}</p >\n            <button onClick={() => setCount(count + 1)}>Click</button>\n        </div>\n    );\n};\n\n```\n\n在使用`hooks`情况下，一般如果函数组件调用`state`，则需要创建一个类组件或者`state`提升到你的父组件中，然后通过`props`对象传递到子组件\n\n\n\n### 生命周期\n\n在函数组件中，并不存在生命周期，这是因为这些生命周期钩子都来自于继承的`React.Component`\n\n所以，如果用到生命周期，就只能使用类组件\n\n但是函数组件使用`useEffect`也能够完成替代生命周期的作用，这里给出一个简单的例子：\n\n```jsx\nconst FunctionalComponent = () => {\n    useEffect(() => {\n        console.log(\"Hello\");\n    }, []);\n    return <h1>Hello, World</h1>;\n};\n```\n\n上述简单的例子对应类组件中的`componentDidMount`生命周期\n\n如果在`useEffect`回调函数中`return `一个函数，则`return`函数会在组件卸载的时候执行，正如`componentWillUnmount`\n\n```jsx\nconst FunctionalComponent = () => {\n React.useEffect(() => {\n   return () => {\n     console.log(\"Bye\");\n   };\n }, []);\n return <h1>Bye, World</h1>;\n};\n\n```\n\n\n\n\n\n### 调用方式\n\n如果是一个函数组件，调用则是执行函数即可：\n\n```jsx\n// 你的代码 \nfunction SayHi() { \n    return <p>Hello, React</p > \n} \n// React内部 \nconst result = SayHi(props) // » <p>Hello, React</p >\n```\n\n如果是一个类组件，则需要将组件进行实例化，然后调用实例对象的`render`方法：\n\n```jsx\n// 你的代码 \nclass SayHi extends React.Component { \n    render() { \n        return <p>Hello, React</p > \n    } \n} \n// React内部 \nconst instance = new SayHi(props) // » SayHi {} \nconst result = instance.render() // » <p>Hello, React</p >\n```\n\n\n\n### 获取渲染的值\n\n首先给出一个示例\n\n函数组件对应如下：\n\n```jsx\nfunction ProfilePage(props) {\n  const showMessage = () => {\n    alert('Followed ' + props.user);\n  }\n\n  const handleClick = () => {\n    setTimeout(showMessage, 3000);\n  }\n\n  return (\n    <button onClick={handleClick}>Follow</button>\n  )\n}\n```\n\n类组件对应如下：\n\n```jsx\nclass ProfilePage extends React.Component {\n  showMessage() {\n    alert('Followed ' + this.props.user);\n  }\n\n  handleClick() {\n    setTimeout(this.showMessage.bind(this), 3000);\n  }\n\n  render() {\n    return <button onClick={this.handleClick.bind(this)}>Follow</button>\n  }\n}\n```\n\n两者看起来实现功能是一致的，但是在类组件中，输出`this.props.user`，`Props `在 `React `中是不可变的所以它永远不会改变，但是 `this` 总是可变的，以便您可以在 `render` 和生命周期函数中读取新版本\n\n因此，如果我们的组件在请求运行时更新。`this.props` 将会改变。`showMessage `方法从“最新”的 `props` 中读取 `user`\n\n而函数组件，本身就不存在`this`，`props`并不发生改变，因此同样是点击，`alert`的内容仍旧是之前的内容\n\n\n\n### 小结\n\n两种组件都有各自的优缺点\n\n函数组件语法更短、更简单，这使得它更容易开发、理解和测试\n\n而类组件也会因大量使用 `this `而让人感到困惑\n\n\n\n## 参考文献\n\n- https://zh-hans.reactjs.org/docs/components-and-props.html#function-and-class-components\n- https://juejin.cn/post/6844903806140973069"
  },
  {
    "path": "docs/React/communication.md",
    "content": "# 面试官：React中组件之间如何通信？\n\n ![](https://static.vue-js.com/767a2800-dc9f-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n我们将组件间通信可以拆分为两个词：\n\n- 组件\n- 通信\n\n回顾[Vue系列](https://mp.weixin.qq.com/s/uFjMz6BByA5eknBgkvgdeQ)的文章，组件是`vue`中最强大的功能之一，同样组件化是`React`的核心思想\n\n相比`vue`，`React`的组件更加灵活和多样，按照不同的方式可以分成很多类型的组件\n\n而通信指的是发送者通过某种媒体以某种格式来传递信息到收信者以达到某个目的，广义上，任何信息的交通都是通信\n\n组件间通信即指组件通过某种方式来传递信息以达到某个目的\n\n\n## 二、如何通信\n\n组件传递的方式有很多种，根据传送者和接收者可以分为如下：\n\n- 父组件向子组件传递\n- 子组件向父组件传递\n- 兄弟组件之间的通信\n- 父组件向后代组件传递\n- 非关系组件传递\n\n\n### 父组件向子组件传递\n\n由于`React`的数据流动为单向的，父组件向子组件传递是最常见的方式\n\n父组件在调用子组件的时候，只需要在子组件标签内传递参数，子组件通过`props`属性就能接收父组件传递过来的参数\n\n```jsx\nfunction EmailInput(props) {\n  return (\n    <label>\n      Email: <input value={props.email} />\n    </label>\n  );\n}\n\nconst element = <EmailInput email=\"123124132@163.com\" />;\n```\n\n\n### 子组件向父组件传递\n\n子组件向父组件通信的基本思路是，父组件向子组件传一个函数，然后通过这个函数的回调，拿到子组件传过来的值\n\n父组件对应代码如下：\n\n```jsx\nclass Parents extends Component {\n  constructor() {\n    super();\n    this.state = {\n      price: 0\n    };\n  }\n\n  getItemPrice(e) {\n    this.setState({\n      price: e\n    });\n  }\n\n  render() {\n    return (\n      <div>\n        <div>price: {this.state.price}</div>\n        {/* 向子组件中传入一个函数  */}\n        <Child getPrice={this.getItemPrice.bind(this)} />\n      </div>\n    );\n  }\n}\n```\n\n子组件对应代码如下：\n\n```jsx\nclass Child extends Component {\n  clickGoods(e) {\n    // 在此函数中传入值\n    this.props.getPrice(e);\n  }\n\n  render() {\n    return (\n      <div>\n        <button onClick={this.clickGoods.bind(this, 100)}>goods1</button>\n        <button onClick={this.clickGoods.bind(this, 1000)}>goods2</button>\n      </div>\n    );\n  }\n}\n```\n\n\n\n### 兄弟组件之间的通信\n\n如果是兄弟组件之间的传递，则父组件作为中间层来实现数据的互通，通过使用父组件传递\n\n```jsx\nclass Parent extends React.Component {\n  constructor(props) {\n    super(props)\n    this.state = {count: 0}\n  }\n  setCount = () => {\n    this.setState({count: this.state.count + 1})\n  }\n  render() {\n    return (\n      <div>\n        <SiblingA\n          count={this.state.count}\n        />\n        <SiblingB\n          onClick={this.setCount}\n        />\n      </div>\n    );\n  }\n}\n```\n\n\n\n### 父组件向后代组件传递\n\n父组件向后代组件传递数据是一件最普通的事情，就像全局数据一样\n\n使用`context`提供了组件之间通讯的一种方式，可以共享数据，其他数据都能读取对应的数据\n\n通过使用`React.createContext`创建一个`context`\n\n```js\n const PriceContext = React.createContext('price')\n```\n\n`context`创建成功后，其下存在`Provider`组件用于创建数据源，`Consumer`组件用于接收数据，使用实例如下：\n\n`Provider`组件通过`value`属性用于给后代组件传递数据：\n\n```jsx\n<PriceContext.Provider value={100}>\n</PriceContext.Provider>\n```\n\n如果想要获取`Provider`传递的数据，可以通过`Consumer`组件或者或者使用`contextType`属性接收，对应分别如下：\n\n```jsx\nclass MyClass extends React.Component {\n  static contextType = PriceContext;\n  render() {\n    let price = this.context;\n    /* 基于这个值进行渲染工作 */\n  }\n}\n```\n\n`Consumer`组件：\n\n````jsx\n<PriceContext.Consumer>\n    { /*这里是一个函数*/ }\n    {\n        price => <div>price：{price}</div>\n    }\n</PriceContext.Consumer>\n````\n\n\n\n### 非关系组件传递\n\n如果组件之间关系类型比较复杂的情况，建议将数据进行一个全局资源管理，从而实现通信，例如`redux`。关于`redux`的使用后续再详细介绍\n\n\n## 三、总结\n\n由于`React`是单向数据流，主要思想是组件不会改变接收的数据，只会监听数据的变化，当数据发生变化时它们会使用接收到的新值，而不是去修改已有的值\n\n因此，可以看到通信过程中，数据的存储位置都是存放在上级位置中\n\n## 参考文献\n\n- https://react.docschina.org/docs/context.html"
  },
  {
    "path": "docs/React/controlled_Uncontrolled.md",
    "content": "# 面试官：说说对受控组件和非受控组件的理解？应用场景？\n\n ![](https://static.vue-js.com/12990fd0-df2f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、受控组件\n\n受控组件，简单来讲，就是受我们控制的组件，组件的状态全程响应外部数据\n\n举个简单的例子：\n\n```jsx\nclass TestComponent extends React.Component {\n  constructor (props) {\n    super(props);\n    this.state = { username: 'lindaidai' };\n  }\n  render () {\n    return <input name=\"username\" value={this.state.username} />\n  }\n}\n```\n\n这时候当我们在输入框输入内容的时候，会发现输入的内容并无法显示出来，也就是`input`标签是一个可读的状态\n\n这是因为`value`被`this.state.username`所控制住。当用户输入新的内容时，`this.state.username`并不会自动更新，这样的话`input`内的内容也就不会变了\n\n如果想要解除被控制，可以为`input`标签设置`onChange`事件，输入的时候触发事件函数，在函数内部实现`state`的更新，从而导致`input`框的内容页发现改变\n\n因此，受控组件我们一般需要初始状态和一个状态更新事件函数\n\n\n\n## 二、非受控组件\n\n非受控组件，简单来讲，就是不受我们控制的组件\n\n一般情况是在初始化的时候接受外部数据，然后自己在内部存储其自身状态\n\n当需要时，可以使用` ref ` 查询 `DOM `并查找其当前值，如下：\n\n```jsx\nimport React, { Component } from 'react';\n\nexport class UnControll extends Component {\n  constructor (props) {\n    super(props);\n    this.inputRef = React.createRef();\n  }\n  handleSubmit = (e) => {\n    console.log('我们可以获得input内的值为', this.inputRef.current.value);\n    e.preventDefault();\n  }\n  render () {\n    return (\n      <form onSubmit={e => this.handleSubmit(e)}>\n        <input defaultValue=\"lindaidai\" ref={this.inputRef} />\n        <input type=\"submit\" value=\"提交\" />\n      </form>\n    )\n  }\n}\n```\n\n关于`refs`的详情使用可以参考[之前文章](https://mp.weixin.qq.com/s/ZBKWcslVBi0IKQgz7lYzbA)\n\n\n\n## 三、应用场景\n\n大部分时候推荐使用受控组件来实现表单，因为在受控组件中，表单数据由`React`组件负责处理\n\n如果选择非受控组件的话，控制能力较弱，表单数据就由`DOM`本身处理，但更加方便快捷，代码量少\n\n针对两者的区别，其应用场景如下图所示：\n\n ![](https://static.vue-js.com/f28aed20-df2f-11eb-ab90-d9ae814b240d.png)\n\n\n\n\n\n## 参考文献\n\n- http://meloguo.com/2018/10/08/受控与非受控组件/\n- https://zhuanlan.zhihu.com/p/37579677\n\n"
  },
  {
    "path": "docs/React/diff.md",
    "content": "# 面试官：说说React diff的原理是什么？\n\n ![](https://static.vue-js.com/967e6150-ec91-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、是什么\n\n跟`Vue`一致，`React`通过引入`Virtual DOM`的概念，极大地避免无效的`Dom`操作，使我们的页面的构建效率提到了极大的提升\n\n而`diff`算法就是更高效地通过对比新旧`Virtual DOM`来找出真正的`Dom`变化之处\n\n传统diff算法通过循环递归对节点进行依次对比，效率低下，算法复杂度达到 O(n^3)，`react`将算法进行一个优化，复杂度姜维`O(n)`，两者效率差距如下图：\n\n ![](https://static.vue-js.com/a43c9960-ec91-11eb-ab90-d9ae814b240d.png)\n\n\n## 二、原理\n\n`react`中`diff`算法主要遵循三个层级的策略：\n\n- tree层级\n\n- conponent 层级\n\n- element 层级\n\n\n### tree层级\n\n`DOM`节点跨层级的操作不做优化，只会对相同层级的节点进行比较\n\n ![](https://static.vue-js.com/ae71d1c0-ec91-11eb-85f6-6fac77c0c9b3.png)\n\n只有删除、创建操作，没有移动操作，如下图：\n\n ![](https://static.vue-js.com/b85f2bb0-ec91-11eb-ab90-d9ae814b240d.png)\n\n`react`发现新树中，R节点下没有了A，那么直接删除A，在D节点下创建A以及下属节点\n\n上述操作中，只有删除和创建操作\n\n\n### conponent层级\n\n如果是同一个类的组件，则会继续往下`diff`运算，如果不是一个类的组件，那么直接删除这个组件下的所有子节点，创建新的\n\n ![](https://static.vue-js.com/c1fcdf00-ec91-11eb-ab90-d9ae814b240d.png)\n\n当`component D `换成了`component G` 后，即使两者的结构非常类似，也会将`D`删除再重新创建`G`\n\n\n\n### element层级\n\n对于比较同一层级的节点们，每个节点在对应的层级用唯一的`key`作为标识\n\n提供了 3 种节点操作，分别为 `INSERT_MARKUP `(插入)、`MOVE_EXISTING` (移动)和 `REMOVE_NODE` (删除)\n\n如下场景：\n\n ![](https://static.vue-js.com/cae1c9a0-ec91-11eb-ab90-d9ae814b240d.png)\n\n通过`key`可以准确地发现新旧集合中的节点都是相同的节点，因此无需进行节点删除和创建，只需要将旧集合中节点的位置进行移动，更新为新集合中节点的位置\n\n流程如下表：\n\n ![](https://static.vue-js.com/d34c5420-ec91-11eb-85f6-6fac77c0c9b3.png)\n\n- index： 新集合的遍历下标。\n- oldIndex：当前节点在老集合中的下标\n- maxIndex：在新集合访问过的节点中，其在老集合的最大下标\n\n如果当前节点在新集合中的位置比老集合中的位置靠前的话，是不会影响后续节点操作的，这里这时候被动字节不用动\n\n操作过程中只比较oldIndex和maxIndex，规则如下：\n\n- 当oldIndex>maxIndex时，将oldIndex的值赋值给maxIndex\n- 当oldIndex=maxIndex时，不操作\n- 当oldIndex<maxIndex时，将当前节点移动到index的位置\n\n`diff`过程如下：\n\n- 节点B：此时 maxIndex=0，oldIndex=1；满足 maxIndex< oldIndex，因此B节点不动，此时maxIndex= Math.max(oldIndex, maxIndex)，就是1\n- 节点A：此时maxIndex=1，oldIndex=0；不满足maxIndex< oldIndex，因此A节点进行移动操作，此时maxIndex= Math.max(oldIndex, maxIndex)，还是1\n- 节点D：此时maxIndex=1, oldIndex=3；满足maxIndex< oldIndex，因此D节点不动，此时maxIndex= Math.max(oldIndex, maxIndex)，就是3\n- 节点C：此时maxIndex=3，oldIndex=2；不满足maxIndex< oldIndex，因此C节点进行移动操作，当前已经比较完了\n\n当ABCD节点比较完成后，`diff`过程还没完，还会整体遍历老集合中节点，看有没有没用到的节点，有的话，就删除\n\n\n\n## 三、注意事项\n\n对于简单列表渲染而言，不使用`key`比使用`key`的性能，例如：\n\n将一个[1,2,3,4,5]，渲染成如下的样子：\n\n```html\n<div>1</div>\n<div>2</div>\n<div>3</div>\n<div>4</div>\n<div>5</div>\n```\n\n后续更改成[1,3,2,5,4]，使用`key`与不使用`key`作用如下：\n\n```html\n1.加key\n<div key='1'>1</div>             <div key='1'>1</div>     \n<div key='2'>2</div>             <div key='3'>3</div>  \n<div key='3'>3</div>  ========>  <div key='2'>2</div>  \n<div key='4'>4</div>             <div key='5'>5</div>  \n<div key='5'>5</div>             <div key='4'>4</div>  \n操作：节点2移动至下标为2的位置，节点4移动至下标为4的位置。\n\n2.不加key\n<div>1</div>             <div>1</div>     \n<div>2</div>             <div>3</div>  \n<div>3</div>  ========>  <div>2</div>  \n<div>4</div>             <div>5</div>  \n<div>5</div>             <div>4</div>  \n操作：修改第1个到第5个节点的innerText\n```\n\n如果我们对这个集合进行增删的操作改成[1,3,2,5,6]\n\n```html\n1.加key\n<div key='1'>1</div>             <div key='1'>1</div>     \n<div key='2'>2</div>             <div key='3'>3</div>  \n<div key='3'>3</div>  ========>  <div key='2'>2</div>  \n<div key='4'>4</div>             <div key='5'>5</div>  \n<div key='5'>5</div>             <div key='6'>6</div>  \n操作：节点2移动至下标为2的位置，新增节点6至下标为4的位置，删除节点4。\n\n2.不加key\n<div>1</div>             <div>1</div>     \n<div>2</div>             <div>3</div>  \n<div>3</div>  ========>  <div>2</div>  \n<div>4</div>             <div>5</div>  \n<div>5</div>             <div>6</div> \n操作：修改第1个到第5个节点的innerText\n```\n\n由于`dom`节点的移动操作开销是比较昂贵的，没有`key`的情况下要比有`key`的性能更好\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/140489744\n\n- https://zhuanlan.zhihu.com/p/20346379\n"
  },
  {
    "path": "docs/React/how to use redux.md",
    "content": "# 面试官：你在React项目中是如何使用Redux的? 项目结构是如何划分的？\n\n ![](https://static.vue-js.com/31a4aff0-e7dc-11eb-ab90-d9ae814b240d.png)\n\n## 一、背景\n\n在前面文章了解中，我们了解到`redux`是用于数据状态管理，而`react`是一个视图层面的库\n\n如果将两者连接在一起，可以使用官方推荐`react-redux`库，其具有高效且灵活的特性\n\n`react-redux`将组件分成：\n\n- 容器组件：存在逻辑处理\n- UI 组件：只负责现显示和交互，内部不处理逻辑，状态由外部控制\n\n通过`redux`将整个应用状态存储到`store`中，组件可以派发`dispatch`行为`action`给`store`\n\n其他组件通过订阅`store`中的状态`state`来更新自身的视图\n\n\n## 二、如何做\n\n使用`react-redux`分成了两大核心：\n\n- Provider\n- connection\n\n### Provider\n\n在`redux`中存在一个`store`用于存储`state`，如果将这个`store`存放在顶层元素中，其他组件都被包裹在顶层元素之上\n\n那么所有的组件都能够受到`redux`的控制，都能够获取到`redux`中的数据\n\n使用方式如下：\n\n```js\n<Provider store = {store}>\n    <App />\n<Provider>\n```\n\n\n\n### connection\n\n`connect`方法将`store`上的`getState `和 `dispatch `包装成组件的`props`\n\n导入`conect`如下：\n\n```js\nimport { connect } from \"react-redux\";\n```\n\n用法如下：\n\n```js\nconnect(mapStateToProps, mapDispatchToProps)(MyComponent)\n```\n\n可以传递两个参数：\n\n- mapStateToProps\n\n- mapDispatchToProps\n\n\n\n### mapStateToProps\n\n把`redux`中的数据映射到`react`中的`props`中去\n\n如下：\n\n```jsx\nconst mapStateToProps = (state) => {\n    return {\n        // prop : state.xxx  | 意思是将state中的某个数据映射到props中\n        foo: state.bar\n    }\n}\n```\n\n组件内部就能够通过`props`获取到`store`中的数据\n\n```cons\nclass Foo extends Component {\n    constructor(props){\n        super(props);\n    }\n    render(){\n        return(\n         // 这样子渲染的其实就是state.bar的数据了\n            <div>this.props.foo</div>\n        )\n    }\n}\nFoo = connect()(Foo)\nexport default Foo\n```\n\n\n### mapDispatchToProps\n\n将`redux`中的`dispatch`映射到组件内部的`props`中\n\n```jsx\nconst mapDispatchToProps = (dispatch) => { // 默认传递参数就是dispatch\n  return {\n    onClick: () => {\n      dispatch({\n        type: 'increatment'\n      });\n    }\n  };\n}\n\n```\n\n```js\nclass Foo extends Component {\n    constructor(props){\n        super(props);\n    }\n    render(){\n        return(\n         \n             <button onClick = {this.props.onClick}>点击increase</button>\n        )\n    }\n}\nFoo = connect()(Foo);\nexport default Foo;\n```\n\n\n### 小结\n\n整体流程图大致如下所示：\n\n ![](https://static.vue-js.com/3e47db10-e7dc-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 三、项目结构\n\n可以根据项目具体情况进行选择，以下列出两种常见的组织结构\n\n#### 按角色组织（MVC）\n\n角色如下：\n\n- reducers \n- actions\n- components \n- containers \n\n参考如下：\n\n```js\nreducers/\n  todoReducer.js\n  filterReducer.js\nactions/\n  todoAction.js\n  filterActions.js\ncomponents/\n  todoList.js\n  todoItem.js\n  filter.js\ncontainers/\n  todoListContainer.js\n  todoItemContainer.js\n  filterContainer.js\n```\n\n#### 按功能组织\n\n使用`redux`使用功能组织项目，也就是把完成同一应用功能的代码放在一个目录下，一个应用功能包含多个角色的代码\n\n`Redux`中，不同的角色就是`reducer`、`actions`和视图，而应用功能对应的就是用户界面的交互模块\n\n参考如下：\n\n```js\ntodoList/\n  actions.js\n  actionTypes.js\n  index.js\n  reducer.js\n  views/\n    components.js\n    containers.js\nfilter/\n  actions.js\n  actionTypes.js\n  index.js\n  reducer.js\n  views/\n    components.js\n    container.js\n```\n\n每个功能模块对应一个目录，每个目录下包含同样的角色文件：\n\n- actionTypes.js 定义action类型\n- actions.js 定义action构造函数\n- reducer.js  定义这个功能模块如果响应actions.js定义的动作\n- views 包含功能模块中所有的React组件，包括展示组件和容器组件\n- index.js 把所有的角色导入，统一导出\n\n其中`index`模块用于导出对外的接口\n\n```js\nimport * as actions from './actions.js';\nimport reducer from './reducer.js';\nimport view from './views/container.js';\n\nexport { actions, reducer, view };\n```\n\n导入方法如下：\n\n```js\nimport { actions, reducer, view as TodoList } from './xxxx'\n```\n\n\n## 参考文献\n\n- https://www.redux.org.cn/docs/basics/UsageWithReact.html\n- https://segmentfault.com/a/1190000010384268"
  },
  {
    "path": "docs/React/immutable.md",
    "content": "# 面试官：说说你对immutable的理解？如何应用在react项目中？\n\n ![](https://static.vue-js.com/797e9470-ea3f-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\nImmutable，不可改变的，在计算机中，即指一旦创建，就不能再被更改的数据\n\n对 `Immutable `对象的任何修改或添加删除操作都会返回一个新的 `Immutable `对象\n\n`Immutable` 实现的原理是 `Persistent Data Structure`（持久化数据结构）:\n\n- 用一种数据结构来保存数据\n- 当数据被修改时，会返回一个对象，但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费\n\n也就是使用旧数据创建新数据时，要保证旧数据同时可用且不变，同时为了避免 `deepCopy `把所有节点都复制一遍带来的性能损耗，`Immutable` 使用了 `Structural Sharing`（结构共享）\n\n如果对象树中一个节点发生变化，只修改这个节点和受它影响的父节点，其它节点则进行共享\n\n如下图所示：\n\n![](https://pic4.zhimg.com/80/2b4c801a7b40eefcd4ee6767fb984fdf_720w.gif)\n\n\n\n## 二、如何使用\n\n使用`Immutable`对象最主要的库是`immutable.js`\n\nimmutable.js 是一个完全独立的库，无论基于什么框架都可以用它\n\n其出现场景在于弥补 Javascript 没有不可变数据结构的问题，通过 structural sharing来解决的性能问题\n\n内部提供了一套完整的 Persistent Data Structure，还有很多易用的数据类型，如`Collection`、`List`、`Map`、`Set`、`Record`、`Seq`，其中：\n\n- List: 有序索引集，类似 JavaScript 中的 Array\n\n- Map: 无序索引集，类似 JavaScript 中的 Object\n\n- Set: 没有重复值的集合\n\n\n\n主要的方法如下：\n\n- fromJS()：将一个js数据转换为Immutable类型的数据\n\n```js\nconst obj = Immutable.fromJS({a:'123',b:'234'})\n```\n\n- toJS()：将一个Immutable数据转换为JS类型的数据\n- is()：对两个对象进行比较\n\n```js\nimport { Map, is } from 'immutable'\nconst map1 = Map({ a: 1, b: 1, c: 1 })\nconst map2 = Map({ a: 1, b: 1, c: 1 })\nmap1 === map2   //false\nObject.is(map1, map2) // false\nis(map1, map2) // true\n```\n\n- get(key)：对数据或对象取值\n\n- getIn([]) ：对嵌套对象或数组取值，传参为数组，表示位置\n\n```js\nlet abs = Immutable.fromJS({a: {b:2}});\nabs.getIn(['a', 'b']) // 2\nabs.getIn(['a', 'c']) // 子级没有值\n\nlet arr = Immutable.fromJS([1 ,2, 3, {a: 5}]);\narr.getIn([3, 'a']); // 5\narr.getIn([3, 'c']); // 子级没有值\n```\n\n- \n\n如下例子：使用方法如下：\n\n```js\nimport Immutable from 'immutable';\nfoo = Immutable.fromJS({a: {b: 1}});\nbar = foo.setIn(['a', 'b'], 2);   // 使用 setIn 赋值\nconsole.log(foo.getIn(['a', 'b']));  // 使用 getIn 取值，打印 1\nconsole.log(foo === bar);  //  打印 false\n```\n\n如果换到原生的`js`，则对应如下：\n\n```js\nlet foo = {a: {b: 1}};\nlet bar = foo;\nbar.a.b = 2;\nconsole.log(foo.a.b);  // 打印 2\nconsole.log(foo === bar);  //  打印 true\n```\n\n\n\n## 三、在React中应用\n\n使用 `Immutable `可以给 `React` 应用带来性能的优化，主要体现在减少渲染的次数\n\n在做`react`性能优化的时候，为了避免重复渲染，我们会在`shouldComponentUpdate()`中做对比，当返回`true`执行`render`方法\n\n`Immutable`通过`is`方法则可以完成对比，而无需像一样通过深度比较的方式比较\n\n在使用`redux`过程中也可以结合`Immutable`，不使用`Immutable`前修改一个数据需要做一个深拷贝\n\n```jsx\nimport '_' from 'lodash';\n\nconst Component = React.createClass({\n  getInitialState() {\n    return {\n      data: { times: 0 }\n    }\n  },\n  handleAdd() {\n    let data = _.cloneDeep(this.state.data);\n    data.times = data.times + 1;\n    this.setState({ data: data });\n  }\n}\n```\n\n使用 Immutable 后：\n\n```jsx\ngetInitialState() {\n  return {\n    data: Map({ times: 0 })\n  }\n},\n  handleAdd() {\n    this.setState({ data: this.state.data.update('times', v => v + 1) });\n    // 这时的 times 并不会改变\n    console.log(this.state.data.get('times'));\n  }\n```\n\n同理，在`redux`中也可以将数据进行`fromJS`处理\n\n```js\nimport * as constants from './constants'\nimport {fromJS} from 'immutable'\nconst defaultState = fromJS({ //将数据转化成immutable数据\n    home:true,\n    focused:false,\n    mouseIn:false,\n    list:[],\n    page:1,\n    totalPage:1\n})\nexport default(state=defaultState,action)=>{\n    switch(action.type){\n        case constants.SEARCH_FOCUS:\n            return state.set('focused',true) //更改immutable数据\n        case constants.CHANGE_HOME_ACTIVE:\n            return state.set('home',action.value)\n        case constants.SEARCH_BLUR:\n            return state.set('focused',false)\n        case constants.CHANGE_LIST:\n            // return state.set('list',action.data).set('totalPage',action.totalPage)\n            //merge效率更高，执行一次改变多个数据\n            return state.merge({\n                list:action.data,\n                totalPage:action.totalPage\n            })\n        case constants.MOUSE_ENTER:\n            return state.set('mouseIn',true)\n        case constants.MOUSE_LEAVE:\n            return state.set('mouseIn',false)\n        case constants.CHANGE_PAGE:\n            return state.set('page',action.page)\n        default:\n            return state\n    }\n}\n```\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/20295971?spm=a2c4e.11153940.blogcont69516.18.4f275a00EzBHjr&columnSlug=purerender\n- https://www.jianshu.com/p/7bf04638e82a\n"
  },
  {
    "path": "docs/React/import css.md",
    "content": "# 面试官：说说react中引入css的方式有哪几种？区别？\n\n ![](https://static.vue-js.com/7d825230-e217-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n组件式开发选择合适的`css`解决方案尤为重要\n\n通常会遵循以下规则：\n\n- 可以编写局部css，不会随意污染其他组件内的原生；\n- 可以编写动态的css，可以获取当前组件的一些状态，根据状态的变化生成不同的css样式；\n- 支持所有的css特性：伪类、动画、媒体查询等；\n- 编写起来简洁方便、最好符合一贯的css风格特点\n\n在这一方面，`vue`使用`css`起来更为简洁：\n\n- 通过 style 标签编写样式\n- scoped 属性决定编写的样式是否局部有效\n- lang 属性设置预处理器\n- 内联样式风格的方式来根据最新状态设置和改变css\n\n而在`react`中，引入`CSS`就不如`Vue`方便简洁，其引入`css`的方式有很多种，各有利弊\n\n\n## 二、方式\n\n常见的`CSS`引入方式有以下：\n\n- 在组件内直接使用\n- 组件中引入 .css 文件\n- 组件中引入 .module.css 文件\n- CSS in JS\n\n\n### 在组件内直接使用\n\n直接在组件中书写`css`样式，通过`style`属性直接引入，如下：\n\n```js\nimport React, { Component } from \"react\";\n\nconst div1 = {\n  width: \"300px\",\n  margin: \"30px auto\",\n  backgroundColor: \"#44014C\",  //驼峰法\n  minHeight: \"200px\",\n  boxSizing: \"border-box\"\n};\n\nclass Test extends Component {\n  constructor(props, context) {\n    super(props);\n  }\n \n  render() {\n    return (\n     <div>\n       <div style={div1}>123</div>\n       <div style={{backgroundColor:\"red\"}}>\n     </div>\n    );\n  }\n}\n\nexport default Test;\n```\n\n上面可以看到，`css`属性需要转换成驼峰写法\n\n这种方式优点：\n\n- 内联样式, 样式之间不会有冲突\n- 可以动态获取当前state中的状态\n\n缺点：\n\n- 写法上都需要使用驼峰标识\n\n- 某些样式没有提示\n\n- 大量的样式, 代码混乱\n\n- 某些样式无法编写(比如伪类/伪元素)\n\n \n\n### 组件中引入css文件\n\n将`css`单独写在一个`css`文件中，然后在组件中直接引入\n\n`App.css`文件：\n\n```css\n.title {\n  color: red;\n  font-size: 20px;\n}\n\n.desc {\n  color: green;\n  text-decoration: underline;\n}\n```\n\n组件中引入：\n\n```js\nimport React, { PureComponent } from 'react';\n\nimport Home from './Home';\n\nimport './App.css';\n\nexport default class App extends PureComponent {\n  render() {\n    return (\n      <div className=\"app\">\n        <h2 className=\"title\">我是App的标题</h2>\n        <p className=\"desc\">我是App中的一段文字描述</p >\n        <Home/>\n      </div>\n    )\n  }\n}\n```\n\n这种方式存在不好的地方在于样式是全局生效，样式之间会互相影响\n\n\n\n### 组件中引入 .module.css 文件\n\n将`css`文件作为一个模块引入，这个模块中的所有`css`，只作用于当前组件。不会影响当前组件的后代组件\n\n这种方式是`webpack`特工的方案，只需要配置`webpack`配置文件中`modules:true`即可\n\n```jsx\nimport React, { PureComponent } from 'react';\n\nimport Home from './Home';\n\nimport './App.module.css';\n\nexport default class App extends PureComponent {\n  render() {\n    return (\n      <div className=\"app\">\n        <h2 className=\"title\">我是App的标题</h2>\n        <p className=\"desc\">我是App中的一段文字描述</p >\n        <Home/>\n      </div>\n    )\n  }\n}\n```\n\n这种方式能够解决局部作用域问题，但也有一定的缺陷：\n\n- 引用的类名，不能使用连接符(.xxx-xx)，在 JavaScript 中是不识别的\n- 所有的 className 都必须使用 {style.className} 的形式来编写\n- 不方便动态来修改某些样式，依然需要使用内联样式的方式；\n\n\n\n### CSS in JS\n\nCSS-in-JS， 是指一种模式，其中` CSS `由 `JavaScript `生成而不是在外部文件中定义\n\n此功能并不是 React 的一部分，而是由第三方库提供，例如：\n\n- styled-components\n- emotion\n- glamorous\n\n\n\n下面主要看看`styled-components`的基本使用\n\n本质是通过函数的调用，最终创建出一个组件：\n\n- 这个组件会被自动添加上一个不重复的class\n- styled-components会给该class添加相关的样式\n\n基本使用如下：\n\n创建一个`style.js`文件用于存放样式组件：\n\n```js\nexport const SelfLink = styled.div`\n  height: 50px;\n  border: 1px solid red;\n  color: yellow;\n`;\n\nexport const SelfButton = styled.div`\n  height: 150px;\n  width: 150px;\n  color: ${props => props.color};\n  background-image: url(${props => props.src});\n  background-size: 150px 150px;\n`;\n```\n\n引入样式组件也很简单：\n\n```jsx\nimport React, { Component } from \"react\";\n\nimport { SelfLink, SelfButton } from \"./style\";\n\nclass Test extends Component {\n  constructor(props, context) {\n    super(props);\n  }  \n \n  render() {\n    return (\n     <div>\n       <SelfLink title=\"People's Republic of China\">app.js</SelfLink>\n       <SelfButton color=\"palevioletred\" style={{ color: \"pink\" }} src={fist}>\n          SelfButton\n        </SelfButton>\n     </div>\n    );\n  }\n}\n\nexport default Test;\n```\n\n\n\n## 三、区别\n\n通过上面四种样式的引入，可以看到：\n\n- 在组件内直接使用`css`该方式编写方便，容易能够根据状态修改样式属性，但是大量的演示编写容易导致代码混乱\n- 组件中引入 .css 文件符合我们日常的编写习惯，但是作用域是全局的，样式之间会层叠\n- 引入.module.css 文件能够解决局部作用域问题，但是不方便动态修改样式，需要使用内联的方式进行样式的编写\n\n- 通过css in js 这种方法，可以满足大部分场景的应用，可以类似于预处理器一样样式嵌套、定义、修改状态等\n\n至于使用`react`用哪种方案引入`css`，并没有一个绝对的答案，可以根据各自情况选择合适的方案\n\n## 参考文献\n\n- https://zh-hans.reactjs.org/docs/faq-styling.html#gatsby-focus-wrapper\n- https://mp.weixin.qq.com/s/oywTpNKEikMXn8QTBgITow"
  },
  {
    "path": "docs/React/improve_render.md",
    "content": "# 面试官：说说你是如何提高组件的渲染效率的？在React中如何避免不必要的render？\n\n ![](https://static.vue-js.com/de2d7e20-ecf8-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n`react` 基于虚拟 `DOM` 和高效 `Diff `算法的完美配合，实现了对 `DOM `最小粒度的更新，大多数情况下，`React `对 `DOM `的渲染效率足以我们的业务日常\n\n复杂业务场景下，性能问题依然会困扰我们。此时需要采取一些措施来提升运行性能，避免不必要的渲染则是业务中常见的优化手段之一\n\n\n## 二、如何做\n\n在之前文章中，我们了解到`render`的触发时机，简单来讲就是类组件通过调用`setState`方法， 就会导致`render`，父组件一旦发生`render`渲染，子组件一定也会执行`render`渲染\n\n从上面可以看到，父组件渲染导致子组件渲染，子组件并没有发生任何改变，这时候就可以从避免无谓的渲染，具体实现的方式有如下：\n\n- shouldComponentUpdate\n- PureComponent\n- React.memo\n\n\n### shouldComponentUpdate\n\n通过`shouldComponentUpdate`生命周期函数来比对 `state `和 `props`，确定是否要重新渲染\n\n默认情况下返回`true`表示重新渲染，如果不希望组件重新渲染，返回 `false` 即可\n\n\n### PureComponent\n\n跟`shouldComponentUpdate `原理基本一致，通过对 `props` 和 `state`的浅比较结果来实现 `shouldComponentUpdate`，源码大致如下：\n\n```js\nif (this._compositeType === CompositeTypes.PureClass) {\n    shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);\n}\n```\n\n`shallowEqual`对应方法大致如下：\n\n```js\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\n\n/**\n * is 方法来判断两个值是否是相等的值，为何这么写可以移步 MDN 的文档\n * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is\n */\nfunction is(x: mixed, y: mixed): boolean {\n  if (x === y) {\n    return x !== 0 || y !== 0 || 1 / x === 1 / y;\n  } else {\n    return x !== x && y !== y;\n  }\n}\n\nfunction shallowEqual(objA: mixed, objB: mixed): boolean {\n  // 首先对基本类型进行比较\n  if (is(objA, objB)) {\n    return true;\n  }\n\n  if (typeof objA !== 'object' || objA === null ||\n      typeof objB !== 'object' || objB === null) {\n    return false;\n  }\n\n  const keysA = Object.keys(objA);\n  const keysB = Object.keys(objB);\n\n  // 长度不相等直接返回false\n  if (keysA.length !== keysB.length) {\n    return false;\n  }\n\n  // key相等的情况下，再去循环比较\n  for (let i = 0; i < keysA.length; i++) {\n    if (\n      !hasOwnProperty.call(objB, keysA[i]) ||\n      !is(objA[keysA[i]], objB[keysA[i]])\n    ) {\n      return false;\n    }\n  }\n\n  return true;\n}\n```\n\n当对象包含复杂的数据结构时，对象深层的数据已改变却没有触发 `render`\n\n注意：在`react`中，是不建议使用深层次结构的数据\n\n\n### React.memo\n\n`React.memo`用来缓存组件的渲染，避免不必要的更新，其实也是一个高阶组件，与 `PureComponent` 十分类似。但不同的是， `React.memo` 只能用于函数组件\n\n```jsx\nimport { memo } from 'react';\n\nfunction Button(props) {\n  // Component code\n}\n\nexport default memo(Button);\n```\n\n如果需要深层次比较，这时候可以给`memo`第二个参数传递比较函数\n\n```jsx\nfunction arePropsEqual(prevProps, nextProps) {\n  // your code\n  return prevProps === nextProps;\n}\n\nexport default memo(Button, arePropsEqual);\n```\n\n\n## 三、总结\n\n在实际开发过程中，前端性能问题是一个必须考虑的问题，随着业务的复杂，遇到性能问题的概率也在增高\n\n除此之外，建议将页面进行更小的颗粒化，如果一个过大，当状态发生修改的时候，就会导致整个大组件的渲染，而对组件进行拆分后，粒度变小了，也能够减少子组件不必要的渲染\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903781679759367#heading-12"
  },
  {
    "path": "docs/React/key.md",
    "content": "# 面试官：React中的key有什么作用？\n\n ![](https://static.vue-js.com/31677360-dd69-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n首先，先给出`react`组件中进行列表渲染的一个示例：\n\n```jsx\nconst data = [\n  { id: 0, name: 'abc' },\n  { id: 1, name: 'def' },\n  { id: 2, name: 'ghi' },\n  { id: 3, name: 'jkl' }\n];\n\nconst ListItem = (props) => {\n  return <li>{props.name}</li>;\n};\n\nconst List = () => {\n  return (\n    <ul>\n      {data.map((item) => (\n        <ListItem name={item.name}></ListItem>\n      ))}\n    </ul>\n  );\n};\n```\n\n然后在输出就可以看到`react`所提示的警告信息：\n\n```tex\nEach child in a list should have a unique \"key\" prop.\n```\n\n根据意思就可以得到渲染列表的每一个子元素都应该需要一个唯一的`key`值\n\n在这里可以使用列表的`id`属性作为`key`值以解决上面这个警告\n\n```jsx\nconst List = () => {\n  return (\n    <ul>\n      {data.map((item) => (\n        <ListItem name={item.name} key={item.id}></ListItem>\n      ))}\n    </ul>\n  );\n};\n```\n\n\n\n## 二、作用\n\n跟`Vue`一样，`React` 也存在 `Diff`算法，而元素`key`属性的作用是用于判断元素是新创建的还是被移动的元素，从而减少不必要的元素渲染\n\n因此`key`的值需要为每一个元素赋予一个确定的标识\n\n如果列表数据渲染中，在数据后面插入一条数据，`key`作用并不大，如下：\n\n```jsx\nthis.state = {\n    numbers:[111,222,333]\n}\n\ninsertMovie() {\n  const newMovies = [...this.state.numbers, 444];\n  this.setState({\n    movies: newMovies\n  })\n}\n\n<ul>\n    {\n        this.state.movies.map((item, index) => {\n            return <li>{item}</li>\n        })\n    }\n</ul>\n```\n\n前面的元素在`diff`算法中，前面的元素由于是完全相同的，并不会产生删除创建操作，在最后一个比较的时候，则需要插入到新的`DOM`树中\n\n因此，在这种情况下，元素有无`key`属性意义并不大\n\n下面再来看看在前面插入数据时，使用`key`与不使用`key`的区别：\n\n```js\ninsertMovie() {\n  const newMovies = [000 ,...this.state.numbers];\n  this.setState({\n    movies: newMovies\n  })\n}\n```\n\n当拥有`key`的时候，`react`根据`key`属性匹配原有树上的子元素以及最新树上的子元素，像上述情况只需要将000元素插入到最前面位置\n\n当没有`key`的时候，所有的`li`标签都需要进行修改\n\n同样，并不是拥有`key`值代表性能越高，如果说只是文本内容改变了，不写`key`反而性能和效率更高\n\n主要是因为不写`key`是将所有的文本内容替换一下，节点不会发生变化\n\n而写`key`则涉及到了节点的增和删，发现旧`key`不存在了，则将其删除，新`key`在之前没有，则插入，这就增加性能的开销\n\n\n\n## 三、总结\n\n良好使用`key`属性是性能优化的非常关键的一步，注意事项为：\n\n- key 应该是唯一的\n- key不要使用随机值（随机数在下一次 render 时，会重新生成一个数字）\n\n- 使用 index 作为 key值，对性能没有优化\n\n`react`判断`key`的流程具体如下图：\n\n ![](https://static.vue-js.com/3b9afe10-dd69-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 参考文献\n\n- https://zh-hans.reactjs.org/docs/lists-and-keys.html#gatsby-focus-wrapper\n- https://segmentfault.com/a/1190000017511836\n"
  },
  {
    "path": "docs/React/life cycle.md",
    "content": "# 面试官：说说 React 生命周期有哪些不同阶段？每个阶段对应的方法是？\n\n ![](https://static.vue-js.com/5c717010-d373-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、是什么\n\n在[以前文章](https://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484176&idx=1&sn=5623421ed2678046ed9e438aadf6e26f&chksm=fc10c146cb67485015f24f7e9f5862c4c685fc33485fe30e1b375a534b4031978439c554e0c0&scene=178&cur_album_id=1711105826272116736#rd)中，我们了解到生命周期定义\n\n生命周期`（Life Cycle）`的概念应用很广泛，特别是在经济、环境、技术、社会等诸多领域经常出现，其基本涵义可以通俗地理解为“从摇篮到坟墓”`（Cradle-to-Grave）`的整个过程\n\n跟`Vue`一样，`React`整个组件生命周期包括从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程\n\n\n## 二、流程\n\n这里主要讲述`react16.4`之后的生命周期，可以分成三个阶段：\n\n- 创建阶段\n- 更新阶段\n- 卸载阶段\n\n\n### 创建阶段\n\n创建阶段主要分成了以下几个生命周期方法：\n\n- constructor\n- getDerivedStateFromProps\n- render\n- componentDidMount\n\n\n#### constructor\n\n实例过程中自动调用的方法，在方法内部通过`super`关键字获取来自父组件的`props`\n\n在该方法中，通常的操作为初始化`state`状态或者在`this`上挂载方法\n\n\n### getDerivedStateFromProps\n\n该方法是新增的生命周期方法，是一个静态的方法，因此不能访问到组件的实例\n\n执行时机：组件创建和更新阶段，不论是`props`变化还是`state`变化，也会调用\n\n在每次`render`方法前调用，第一个参数为即将更新的`props`，第二个参数为上一个状态的`state`，可以比较`props` 和 `state`来加一些限制条件，防止无用的state更新\n\n该方法需要返回一个新的对象作为新的`state`或者返回`null`表示`state`状态不需要更新\n\n\n### render\n\n类组件必须实现的方法，用于渲染`DOM`结构，可以访问组件`state`与`prop`属性\n\n注意： 不要在 `render` 里面 `setState`, 否则会触发死循环导致内存崩溃\n\n\n### componentDidMount\n\n组件挂载到真实`DOM`节点后执行，其在`render`方法之后执行\n\n此方法多用于执行一些数据获取，事件监听等操作\n\n\n\n### 更新阶段\n\n该阶段的函数主要为如下方法：\n\n- getDerivedStateFromProps\n- shouldComponentUpdate\n- render\n- getSnapshotBeforeUpdate\n- componentDidUpdate\n\n\n\n### getDerivedStateFromProps\n\n该方法介绍同上\n\n\n## shouldComponentUpdate\n\n用于告知组件本身基于当前的`props`和`state`是否需要重新渲染组件，默认情况返回`true`\n\n执行时机：到新的props或者state时都会调用，通过返回true或者false告知组件更新与否\n\n一般情况，不建议在该周期方法中进行深层比较，会影响效率\n\n同时也不能调用`setState`，否则会导致无限循环调用更新\n\n\n\n### render\n\n介绍如上\n\n\n### getSnapshotBeforeUpdate\n\n该周期函数在`render`后执行，执行之时`DOM`元素还没有被更新\n\n该方法返回的一个`Snapshot`值，作为`componentDidUpdate`第三个参数传入\n\n```jsx\ngetSnapshotBeforeUpdate(prevProps, prevState) {\n    console.log('#enter getSnapshotBeforeUpdate');\n    return 'foo';\n}\n\ncomponentDidUpdate(prevProps, prevState, snapshot) {\n    console.log('#enter componentDidUpdate snapshot = ', snapshot);\n}\n```\n\n此方法的目的在于获取组件更新前的一些信息，比如组件的滚动位置之类的，在组件更新后可以根据这些信息恢复一些UI视觉上的状态\n\n\n\n### componentDidUpdate\n\n执行时机：组件更新结束后触发\n\n在该方法中，可以根据前后的`props`和`state`的变化做相应的操作，如获取数据，修改`DOM`样式等\n\n\n\n### 卸载阶段\n\n## componentWillUnmount\n\n此方法用于组件卸载前，清理一些注册是监听事件，或者取消订阅的网络请求等\n\n一旦一个组件实例被卸载，其不会被再次挂载，而只可能是被重新创建\n\n\n\n## 三、总结\n\n新版生命周期整体流程如下图所示：\n\n ![](https://static.vue-js.com/66c999c0-d373-11eb-85f6-6fac77c0c9b3.png)\n\n旧的生命周期流程图如下：\n\n![](https://static.vue-js.com/d379e420-d374-11eb-ab90-d9ae814b240d.png)\n\n通过两个图的对比，可以发现新版的生命周期减少了以下三种方法：\n\n- componentWillMount\n- componentWillReceiveProps\n- componentWillUpdate\n\n其实这三个方法仍然存在，只是在前者加上了`UNSAFE_`前缀，如`UNSAFE_componentWillMount`，并不像字面意思那样表示不安全，而是表示这些生命周期的代码可能在未来的 `react `版本可能废除\n\n同时也新增了两个生命周期函数：\n\n- getDerivedStateFromProps\n- getSnapshotBeforeUpdate\n\n\n## 参考文献\n\n- https://github.com/pomelovico/keep/issues/23\n- https://segmentfault.com/a/1190000020268993"
  },
  {
    "path": "docs/React/redux.md",
    "content": "# 说说你对Redux的理解？其工作原理？\n\n ![](https://static.vue-js.com/52394be0-e2a5-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n`React`是用于构建用户界面的，帮助我们解决渲染`DOM`的过程\n\n而在整个应用中会存在很多个组件，每个组件的`state`是由自身进行管理，包括组件定义自身的`state`、组件之间的通信通过`props`传递、使用`Context`实现数据共享\n\n如果让每个组件都存储自身相关的状态，理论上来讲不会影响应用的运行，但在开发及后续维护阶段，我们将花费大量精力去查询状态的变化过程\n\n这种情况下，如果将所有的状态进行集中管理，当需要更新状态的时候，仅需要对这个管理集中处理，而不用去关心状态是如何分发到每一个组件内部的\n\n`redux`就是一个实现上述集中管理的容器，遵循三大基本原则：\n\n- 单一数据源\n- state 是只读的\n- 使用纯函数来执行修改\n\n注意的是，`redux`并不是只应用在`react`中，还与其他界面库一起使用，如`Vue`\n\n\n## 二、工作原理\n\n`redux `要求我们把数据都放在 `store `公共存储空间\n\n一个组件改变了 `store` 里的数据内容，其他组件就能感知到 `store `的变化，再来取数据，从而间接的实现了这些数据传递的功能\n\n工作流程图如下所示：\n\n ![](https://static.vue-js.com/27b2e930-e56b-11eb-85f6-6fac77c0c9b3.png)\n\n根据流程图，可以想象，`React Components` 是借书的用户， `Action Creactor` 是借书时说的话(借什么书)， `Store` 是图书馆管理员，`Reducer` 是记录本(借什么书，还什么书，在哪儿，需要查一下)， `state` 是书籍信息\n\n整个流程就是借书的用户需要先存在，然后需要借书，需要一句话来描述借什么书，图书馆管理员听到后需要查一下记录本，了解图书的位置，最后图书馆管理员会把这本书给到这个借书人\n\n转换为代码是，`React Components` 需要获取一些数据, 然后它就告知 `Store` 需要获取数据，这就是就是 `Action Creactor` , `Store` 接收到之后去 `Reducer` 查一下， `Reducer` 会告诉 `Store` 应该给这个组件什么数据\n\n\n\n## 三、如何使用\n\n创建一个`store`的公共数据区域\n\n```js\nimport { createStore } from 'redux' // 引入一个第三方的方法\nconst store = createStore() // 创建数据的公共存储区域（管理员）\n```\n\n还需要创建一个记录本去辅助管理数据，也就是`reduecer`，本质就是一个函数，接收两个参数`state`，`action`，返回`state`\n\n```js\n// 设置默认值\nconst initialState = {\n  counter: 0\n}\n\nconst reducer = (state = initialState, action) => {\n}\n```\n\n然后就可以将记录本传递给`store`，两者建立连接。如下：\n\n```js\nconst store = createStore(reducer)\n```\n\n如果想要获取`store`里面的数据，则通过`store.getState()`来获取当前`state`\n\n```js\nconsole.log(store.getState());\n```\n\n下面再看看如何更改`store`里面数据，是通过`dispatch`来派发`action`，通常`action`中都会有`type`属性，也可以携带其他的数据\n\n```js\nstore.dispatch({\n  type: \"INCREMENT\"\n})\n\nstore.dispath({\n  type: \"DECREMENT\"\n})\n\nstore.dispatch({\n  type: \"ADD_NUMBER\",\n  number: 5\n})\n```\n\n下面再来看看修改`reducer`中的处理逻辑：\n\n```js\nconst reducer = (state = initialState, action) => {\n  switch (action.type) {\n    case \"INCREMENT\":\n      return {...state, counter: state.counter + 1};\n    case \"DECREMENT\":\n      return {...state, counter: state.counter - 1};\n    case \"ADD_NUMBER\":\n      return {...state, counter: state.counter + action.number}\n    default: \n      return state;\n  }\n}\n```\n\n注意，`reducer`是一个纯函数，不需要直接修改`state`\n\n这样派发`action`之后，既可以通过`store.subscribe`监听`store`的变化，如下：\n\n```js\nstore.subscribe(() => {\n  console.log(store.getState());\n})\n```\n\n在`React`项目中，会搭配`react-redux`进行使用\n\n完整代码如下：\n\n```js\nconst redux = require('redux');\n\nconst initialState = {\n  counter: 0\n}\n\n// 创建reducer\nconst reducer = (state = initialState, action) => {\n  switch (action.type) {\n    case \"INCREMENT\":\n      return {...state, counter: state.counter + 1};\n    case \"DECREMENT\":\n      return {...state, counter: state.counter - 1};\n    case \"ADD_NUMBER\":\n      return {...state, counter: state.counter + action.number}\n    default: \n      return state;\n  }\n}\n\n// 根据reducer创建store\nconst store = redux.createStore(reducer);\n\nstore.subscribe(() => {\n  console.log(store.getState());\n})\n\n// 修改store中的state\nstore.dispatch({\n  type: \"INCREMENT\"\n})\n// console.log(store.getState());\n\nstore.dispatch({\n  type: \"DECREMENT\"\n})\n// console.log(store.getState());\n\nstore.dispatch({\n  type: \"ADD_NUMBER\",\n  number: 5\n})\n// console.log(store.getState());\n```\n\n\n\n### 小结\n\n- createStore可以帮助创建 store\n- store.dispatch 帮助派发 action , action 会传递给 store\n- store.getState 这个方法可以帮助获取 store 里边所有的数据内容\n- store.subscrible 方法订阅 store 的改变，只要 store 发生改变， store.subscrible 这个函数接收的这个回调函数就会被执行\n\n\n## 参考文献\n\n- https://cn.redux.js.org/docs/introduction/\n- https://www.redux.org.cn/docs/basics/Actions.html\n- https://lulujianglab.com/posts/大白话解析 Redux 、 redux-thunk 、redux-saga 和 react-redux\n"
  },
  {
    "path": "docs/React/render.md",
    "content": "# 面试官：说说React render方法的原理？在什么时候会被触发？\n\n ![](https://static.vue-js.com/3d855230-ec6d-11eb-ab90-d9ae814b240d.png)\n\n## 一、原理\n\n首先，`render`函数在`react`中有两种形式：\n\n在类组件中，指的是`render`方法：\n\n```jsx\nclass Foo extends React.Component {\n    render() {\n        return <h1> Foo </h1>;\n    }\n}\n```\n\n在函数组件中，指的是函数组件本身：\n\n```js\nfunction Foo() {\n    return <h1> Foo </h1>;\n}\n```\n\n在`render`中，我们会编写`jsx`，`jsx`通过`babel`编译后就会转化成我们熟悉的`js`格式，如下：\n\n```jsx\nreturn (\n  <div className='cn'>\n    <Header> hello </Header>\n    <div> start </div>\n    Right Reserve\n  </div>\n)\n```\n\n`babel`编译后：\n\n```js\nreturn (\n  React.createElement(\n    'div',\n    {\n      className : 'cn'\n    },\n    React.createElement(\n      Header,\n      null,\n      'hello'\n    ),\n    React.createElement(\n      'div',\n      null,\n      'start'\n    ),\n    'Right Reserve'\n  )\n)\n```\n\n从名字上来看，`createElement`方法用来元素的\n\n在`react`中，这个元素就是虚拟`DOM`树的节点，接收三个参数：\n\n- type：标签\n- attributes：标签属性，若无则为null\n\n- children：标签的子节点\n\n这些虚拟`DOM`树最终会渲染成真实`DOM`\n\n在`render`过程中，`React` 将新调用的 `render `函数返回的树与旧版本的树进行比较，这一步是决定如何更新 `DOM` 的必要步骤，然后进行 `diff` 比较，更新 `DOM `树\n\n\n\n\n\n## 二、触发时机\n\n`render`的执行时机主要分成了两部分：\n\n- 类组件调用 setState 修改状态\n\n```jsx\nclass Foo extends React.Component {\n  state = { count: 0 };\n\n  increment = () => {\n    const { count } = this.state;\n\n    const newCount = count < 10 ? count + 1 : count;\n\n    this.setState({ count: newCount });\n  };\n\n  render() {\n    const { count } = this.state;\n    console.log(\"Foo render\");\n\n    return (\n      <div>\n        <h1> {count} </h1>\n        <button onClick={this.increment}>Increment</button>\n      </div>\n    );\n  }\n}\n```\n\n点击按钮，则调用`setState`方法，无论`count`发生变化辩护，控制台都会输出`Foo render`，证明`render`执行了\n\n- 函数组件通过`useState hook`修改状态\n\n```jsx\nfunction Foo() {\n  const [count, setCount] = useState(0);\n\n  function increment() {\n    const newCount = count < 10 ? count + 1 : count;\n    setCount(newCount);\n  }\n\n  console.log(\"Foo render\");\n  \n  return (\n    <div>\n      <h1> {count} </h1>\n      <button onClick={increment}>Increment</button>\n    </div>\n  );\n}\n```\n\n函数组件通过`useState`这种形式更新数据，当数组的值不发生改变了，就不会触发`render`\n\n- 类组件重新渲染\n\n```js\nclass App extends React.Component {\n  state = { name: \"App\" };\n  render() {\n    return (\n      <div className=\"App\">\n        <Foo />\n        <button onClick={() => this.setState({ name: \"App\" })}>\n          Change name\n        </button>\n      </div>\n    );\n  }\n}\n\nfunction Foo() {\n  console.log(\"Foo render\");\n\n  return (\n    <div>\n      <h1> Foo </h1>\n    </div>\n  );\n}\n```\n\n只要点击了 `App` 组件内的 `Change name` 按钮，不管 `Foo` 具体实现是什么，都会被重新`render`渲染\n\n- 函数组件重新渲染\n\n```jsx\nfunction App(){\n    const [name,setName] = useState('App')\n\n    return (\n        <div className=\"App\">\n            <Foo />\n            <button onClick={() => setName(\"aaa\")}>\n                { name }\n            </button>\n      </div>\n    )\n}\n\nfunction Foo() {\n  console.log(\"Foo render\");\n\n  return (\n    <div>\n      <h1> Foo </h1>\n    </div>\n  );\n}\n```\n\n可以发现，使用`useState`来更新状态的时候，只有首次会触发`Foo render`，后面并不会导致`Foo render`\n\n\n\n## 三、总结\n\n`render`函数里面可以编写`JSX`，转化成`createElement`这种形式，用于生成虚拟`DOM`，最终转化成真实`DOM`\n\n在` React` 中，类组件只要执行了 `setState` 方法，就一定会触发 `render` 函数执行，函数组件使用`useState`更改状态不一定导致重新`render`\n\n组件的` props` 改变了，不一定触发 `render` 函数的执行，但是如果 `props` 的值来自于父组件或者祖先组件的 `state`\n\n在这种情况下，父组件或者祖先组件的 `state` 发生了改变，就会导致子组件的重新渲染\n\n所以，一旦执行了`setState`就会执行`render`方法，`useState` 会判断当前值有无发生改变确定是否执行`render`方法，一旦父组件发生渲染，子组件也会渲染\n\n ![](https://static.vue-js.com/229784b0-ecf5-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/45091185\n- https://juejin.cn/post/6844904181493415950"
  },
  {
    "path": "docs/React/server side rendering.md",
    "content": "# 面试官：说说React服务端渲染怎么做？原理是什么？\n\n ![](https://static.vue-js.com/8c93cbe0-f3f7-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在[SSR中](https://mp.weixin.qq.com/s/vvUtC_aAprUjoJRnfFjA1A)，我们了解到`Server-Side Rendering` ，简称`SSR`，意为服务端渲染\n\n指由服务侧完成页面的 `HTML` 结构拼接的页面处理技术，发送到浏览器，然后为其绑定状态与事件，成为完全可交互页面的过程\n\n ![](https://static.vue-js.com/96dc3e20-f3f7-11eb-85f6-6fac77c0c9b3.png)\n\n其解决的问题主要有两个：\n\n- SEO，由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面\n- 加速首屏加载，解决首屏白屏问题\n\n\n## 二、如何做\n\n在`react`中，实现`SSR`主要有两种形式：\n\n- 手动搭建一个 SSR 框架\n- 使用成熟的SSR 框架，如 Next.JS\n\n\n这里主要以手动搭建一个`SSR`框架进行实现\n\n首先通过`express`启动一个`app.js`文件，用于监听3000端口的请求，当请求根目录时，返回`HTML`，如下：\n\n```js\nconst express = require('express')\nconst app = express()\napp.get('/', (req,res) => res.send(`\n<html>\n   <head>\n       <title>ssr demo</title>\n   </head>\n   <body>\n       Hello world\n   </body>\n</html>\n`))\n\napp.listen(3000, () => console.log('Exampleapp listening on port 3000!'))\n```\n\n然后再服务器中编写`react`代码，在`app.js`中进行应引用\n\n```jsx\nimport React from 'react'\n\nconst Home = () =>{\n\n    return <div>home</div>\n\n}\n\nexport default Home\n```\n\n为了让服务器能够识别`JSX`，这里需要使用`webpakc`对项目进行打包转换，创建一个配置文件`webpack.server.js`并进行相关配置，如下：\n\n```js\nconst path = require('path')    //node的path模块\nconst nodeExternals = require('webpack-node-externals')\n\nmodule.exports = {\n    target:'node',\n    mode:'development',           //开发模式\n    entry:'./app.js',             //入口\n    output: {                     //打包出口\n        filename:'bundle.js',     //打包后的文件名\n        path:path.resolve(__dirname,'build')    //存放到根目录的build文件夹\n    },\n    externals: [nodeExternals()],  //保持node中require的引用方式\n    module: {\n        rules: [{                  //打包规则\n           test:   /\\.js?$/,       //对所有js文件进行打包\n           loader:'babel-loader',  //使用babel-loader进行打包\n           exclude: /node_modules/,//不打包node_modules中的js文件\n           options: {\n               presets: ['react','stage-0',['env', { \n                                  //loader时额外的打包规则,对react,JSX，ES6进行转换\n                    targets: {\n                        browsers: ['last 2versions']   //对主流浏览器最近两个版本进行兼容\n                    }\n               }]]\n           }\n       }]\n    }\n}\n```\n\n接着借助`react-dom`提供了服务端渲染的 `renderToString`方法，负责把`React`组件解析成`html`\n\n```js\nimport express from 'express'\nimport React from 'react'//引入React以支持JSX的语法\nimport { renderToString } from 'react-dom/server'//引入renderToString方法\nimport Home from'./src/containers/Home'\n\nconst app= express()\nconst content = renderToString(<Home/>)\napp.get('/',(req,res) => res.send(`\n<html>\n   <head>\n       <title>ssr demo</title>\n   </head>\n   <body>\n        ${content}\n   </body>\n</html>\n`))\n\napp.listen(3001, () => console.log('Exampleapp listening on port 3001!'))\n```\n\n上面的过程中，已经能够成功将组件渲染到了页面上\n\n但是像一些事件处理的方法，是无法在服务端完成，因此需要将组件代码在浏览器中再执行一遍，这种服务器端和客户端共用一套代码的方式就称之为**同构**\n\n重构通俗讲就是一套React代码在服务器上运行一遍，到达浏览器又运行一遍：\n\n- 服务端渲染完成页面结构\n- 浏览器端渲染完成事件绑定\n\n浏览器实现事件绑定的方式为让浏览器去拉取`JS`文件执行，让`JS`代码来控制，因此需要引入`script`标签\n\n通过`script`标签为页面引入客户端执行的`react`代码，并通过`express`的`static`中间件为`js`文件配置路由，修改如下：\n\n```js\nimport express from 'express'\nimport React from 'react'//引入React以支持JSX的语法\nimport { renderToString } from'react-dom/server'//引入renderToString方法\nimport Home from './src/containers/Home'\n \nconst app = express()\napp.use(express.static('public'));\n//使用express提供的static中间件,中间件会将所有静态文件的路由指向public文件夹\n const content = renderToString(<Home/>)\n \napp.get('/',(req,res)=>res.send(`\n<html>\n   <head>\n       <title>ssr demo</title>\n   </head>\n   <body>\n        ${content}\n   <script src=\"/index.js\"></script>\n   </body>\n</html>\n`))\n\n app.listen(3001, () =>console.log('Example app listening on port 3001!'))\n```\n\n然后再客户端执行以下`react`代码，新建`webpack.client.js`作为客户端React代码的`webpack`配置文件如下：\n\n```js\nconst path = require('path')                    //node的path模块\n\nmodule.exports = {\n    mode:'development',                         //开发模式\n    entry:'./src/client/index.js',              //入口\n    output: {                                   //打包出口\n        filename:'index.js',                    //打包后的文件名\n        path:path.resolve(__dirname,'public')   //存放到根目录的build文件夹\n    },\n    module: {\n        rules: [{                               //打包规则\n           test:   /\\.js?$/,                    //对所有js文件进行打包\n           loader:'babel-loader',               //使用babel-loader进行打包\n           exclude: /node_modules/,             //不打包node_modules中的js文件\n           options: {\n               presets: ['react','stage-0',['env', {     \n                    //loader时额外的打包规则,这里对react,JSX进行转换\n                    targets: {\n                        browsers: ['last 2versions']   //对主流浏览器最近两个版本进行兼容\n                    }\n               }]]\n           }\n       }]\n    }\n}\n```\n\n这种方法就能够简单实现首页的`react`服务端渲染，过程对应如下图：\n\n ![](https://static.vue-js.com/a2894970-f3f7-11eb-85f6-6fac77c0c9b3.png)\n\n在做完初始渲染的时候，一个应用会存在路由的情况，配置信息如下：\n\n```js\nimport React from 'react'                   //引入React以支持JSX\nimport { Route } from 'react-router-dom'    //引入路由\nimport Home from './containers/Home'        //引入Home组件\n\nexport default (\n    <div>\n        <Route path=\"/\" exact component={Home}></Route>\n    </div>\n)\n```\n\n然后可以通过`index.js`引用路由信息，如下：\n\n```js\nimport React from 'react'\nimport ReactDom from 'react-dom'\nimport { BrowserRouter } from'react-router-dom'\nimport Router from'../Routers'\n\nconst App= () => {\n    return (\n        <BrowserRouter>\n           {Router}\n        </BrowserRouter>\n    )\n}\n\nReactDom.hydrate(<App/>, document.getElementById('root'))\n```\n\n这时候控制台会存在报错信息，原因在于每个`Route`组件外面包裹着一层`div`，但服务端返回的代码中并没有这个`div`\n\n解决方法只需要将路由信息在服务端执行一遍，使用使用`StaticRouter`来替代`BrowserRouter`，通过`context`进行参数传递\n\n```js\nimport express from 'express'\nimport React from 'react'//引入React以支持JSX的语法\nimport { renderToString } from 'react-dom/server'//引入renderToString方法\nimport { StaticRouter } from 'react-router-dom'\nimport Router from '../Routers'\n \nconst app = express()\napp.use(express.static('public'));\n//使用express提供的static中间件,中间件会将所有静态文件的路由指向public文件夹\n\napp.get('/',(req,res)=>{\n    const content  = renderToString((\n        //传入当前path\n        //context为必填参数,用于服务端渲染参数传递\n        <StaticRouter location={req.path} context={{}}>\n           {Router}\n        </StaticRouter>\n    ))\n    res.send(`\n   <html>\n       <head>\n           <title>ssr demo</title>\n       </head>\n       <body>\n       <div id=\"root\">${content}</div>\n       <script src=\"/index.js\"></script>\n       </body>\n   </html>\n    `)\n})\n\n\napp.listen(3001, () => console.log('Exampleapp listening on port 3001!'))\n```\n\n这样也就完成了路由的服务端渲染\n\n\n\n## 三、原理\n\n整体`react`服务端渲染原理并不复杂，具体如下：\n\n`node server` 接收客户端请求，得到当前的请求`url` 路径，然后在已有的路由表内查找到对应的组件，拿到需要请求的数据，将数据作为 `props`、`context`或者`store` 形式传入组件\n\n然后基于 `react` 内置的服务端渲染方法 `renderToString()`把组件渲染为 `html`字符串在把最终的 `html `进行输出前需要将数据注入到浏览器端\n\n浏览器开始进行渲染和节点对比，然后执行完成组件内事件绑定和一些交互，浏览器重用了服务端输出的 `html` 节点，整个流程结束\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/52693113\n- https://segmentfault.com/a/1190000020417285\n- https://juejin.cn/post/6844904000387563533#heading-14"
  },
  {
    "path": "docs/React/setState.md",
    "content": "# 面试官：说说 React中的setState执行机制\n\n ![](https://static.vue-js.com/3acb8ca0-d825-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n一个组件的显示形态可以由数据状态和外部参数所决定，而数据状态就是`state`\n\n当需要修改里面的值的状态需要通过调用`setState`来改变，从而达到更新组件内部数据的作用\n\n如下例子：\n\n```jsx\nimport React, { Component } from 'react'\n\nexport default class App extends Component {\n    constructor(props) {\n        super(props);\n\n        this.state = {\n            message: \"Hello World\"\n        }\n    }\n\n    render() {\n        return (\n            <div>\n                <h2>{this.state.message}</h2>\n                <button onClick={e => this.changeText()}>面试官系列</button>\n            </div>\n        )\n    }\n\n    changeText() {\n        this.setState({\n            message: \"JS每日一题\"\n        })\n    }\n}\n```\n\n通过点击按钮触发`onclick`事件，执行`this.setState`方法更新`state`状态，然后重新执行`render`函数，从而导致页面的视图更新\n\n如果直接修改`state`的状态，如下：\n\n```jsx\nchangeText() {\n    this.state.message = \"你好啊,李银河\";\n}\n```\n\n我们会发现页面并不会有任何反应，但是`state`的状态是已经发生了改变\n\n这是因为`React`并不像`vue2`中调用`Object.defineProperty`数据响应式或者`Vue3`调用`Proxy`监听数据的变化\n\n必须通过`setState`方法来告知`react`组件`state`已经发生了改变\n\n关于`state`方法的定义是从`React.Component`中继承，定义的源码如下：\n\n```js\nComponent.prototype.setState = function(partialState, callback) {\n  invariant(\n    typeof partialState === 'object' ||\n      typeof partialState === 'function' ||\n      partialState == null,\n    'setState(...): takes an object of state variables to update or a ' +\n      'function which returns an object of state variables.',\n  );\n  this.updater.enqueueSetState(this, partialState, callback, 'setState');\n};\n```\n\n从上面可以看到`setState`第一个参数可以是一个对象，或者是一个函数，而第二个参数是一个回调函数，用于可以实时的获取到更新之后的数据\n\n\n\n## 二、更新类型\n\n在使用`setState`更新数据的时候，`setState`的更新类型分成：\n\n- 异步更新\n- 同步更新\n\n### 异步更新\n\n先举出一个例子：\n\n```jsx\nchangeText() {\n  this.setState({\n    message: \"你好啊\"\n  })\n  console.log(this.state.message); // Hello World\n}\n```\n\n从上面可以看到，最终打印结果为`Hello world`，并不能在执行完`setState`之后立马拿到最新的`state`的结果\n\n如果想要立刻获取更新后的值，在第二个参数的回调中更新后会执行\n\n```jsx\nchangeText() {\n  this.setState({\n    message: \"你好啊\"\n  }, () => {\n    console.log(this.state.message); // 你好啊\n  });\n}\n```\n\n\n\n### 同步更新\n\n同样先给出一个在`setTimeout`中更新的例子：\n\n```jsx\nchangeText() {\n  setTimeout(() => {\n    this.setState({\n      message: \"你好啊\n    });\n    console.log(this.state.message); // 你好啊\n  }, 0);\n}\n```\n\n上面的例子中，可以看到更新是同步\n\n再来举一个原生`DOM`事件的例子：\n\n```jsx\ncomponentDidMount() {\n  const btnEl = document.getElementById(\"btn\");\n  btnEl.addEventListener('click', () => {\n    this.setState({\n      message: \"你好啊,李银河\"\n    });\n    console.log(this.state.message); // 你好啊,李银河\n  })\n}\n```\n\n\n\n### 小结\n\n- 在组件生命周期或React合成事件中，setState是异步\n- 在setTimeout或者原生dom事件中，setState是同步\n\n\n\n### 三、批量更新\n\n同样先给出一个例子：\n\n```jsx\nhandleClick = () => {\n    this.setState({\n        count: this.state.count + 1,\n    })\n    console.log(this.state.count) // 1\n\n    this.setState({\n        count: this.state.count + 1,\n    })\n    console.log(this.state.count) // 1\n\n    this.setState({\n        count: this.state.count + 1,\n    })\n    console.log(this.state.count) // 1\n}\n```\n\n点击按钮触发事件，打印的都是 1，页面显示 `count` 的值为 2\n\n对同一个值进行多次 `setState `， `setState` 的批量更新策略会对其进行覆盖，取最后一次的执行结果\n\n上述的例子，实际等价于如下：\n\n```js\nObject.assign(\n  previousState,\n  {index: state.count+ 1},\n  {index: state.count+ 1},\n  ...\n)\n```\n\n由于后面的数据会覆盖前面的更改，所以最终只加了一次\n\n如果是下一个`state`依赖前一个`state`的话，推荐给`setState`一个参数传入一个`function`，如下：\n\n```jsx\nonClick = () => {\n    this.setState((prevState, props) => {\n      return {count: prevState.count + 1};\n    });\n    this.setState((prevState, props) => {\n      return {count: prevState.count + 1};\n    });\n}\n```\n\n而在`setTimeout`或者原生`dom`事件中，由于是同步的操作，所以并不会进行覆盖现象\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903667426918408\n- https://juejin.cn/post/6844903636749778958\n- https://segmentfault.com/a/1190000039077904\n"
  },
  {
    "path": "docs/React/state_props.md",
    "content": "# 面试官：state 和 props 有什么区别？\n\n![](https://static.vue-js.com/7f272780-d440-11eb-ab90-d9ae814b240d.png)\n\n## 一、state\n\n一个组件的显示形态可以由数据状态和外部参数所决定，而数据状态就是 `state`，一般在 `constructor` 中初始化\n\n当需要修改里面的值的状态需要通过调用 `setState` 来改变，从而达到更新组件内部数据的作用，并且重新调用组件 `render` 方法，如下面的例子：\n\n```jsx\nclass Button extends React.Component {\n  constructor() {\n    super();\n    this.state = {\n      count: 0,\n    };\n  }\n\n  updateCount() {\n    this.setState((prevState, props) => {\n      return { count: prevState.count + 1 };\n    });\n  }\n\n  render() {\n    return (\n      <button onClick={() => this.updateCount()}>\n        Clicked {this.state.count} times\n      </button>\n    );\n  }\n}\n```\n\n`setState` 还可以接受第二个参数，它是一个函数，会在 `setState` 调用完成并且组件开始重新渲染时被调用，可以用来监听渲染是否完成\n\n```js\nthis.setState(\n  {\n    name: \"JS每日一题\",\n  },\n  () => console.log(\"setState finished\")\n);\n```\n\n## 二、props\n\n`React` 的核心思想就是组件化思想，页面会被切分成一些独立的、可复用的组件\n\n组件从概念上看就是一个函数，可以接受一个参数作为输入值，这个参数就是 `props`，所以可以把 `props` 理解为从外部传入组件内部的数据\n\n`react` 具有单向数据流的特性，所以他的主要作用是从父组件向子组件中传递数据\n\n`props` 除了可以传字符串，数字，还可以传递对象，数组甚至是回调函数，如下：\n\n```jsx\nclass Welcome extends React.Component {\n  render() {\n    return <h1>Hello {this.props.name}</h1>;\n  }\n}\n\nconst element = <Welcome name=\"Sara\" onNameChanged={this.handleName} />;\n```\n\n上述 `name` 属性与 `onNameChanged` 方法都能在子组件的 `props` 变量中访问\n\n在子组件中，`props` 在内部不可变的，如果想要改变它看，只能通过外部组件传入新的 `props` 来重新渲染子组件，否则子组件的 `props` 和展示形式不会改变\n\n## 三、区别\n\n相同点：\n\n- 两者都是 JavaScript 对象\n- 两者都是用于保存信息\n- props 和 state 都能触发渲染更新\n\n区别：\n\n- props 是外部传递给组件的，而 state 是在组件内被组件自己管理的，一般在 constructor 中初始化\n- props 在组件内部是不可修改的，但 state 在组件内部可以进行修改\n- state 是多变的、可以修改\n\n## 参考文献\n\n- [https://lucybain.com/blog/2016/react-state-vs-pros/](https://lucybain.com/blog/2016/react-state-vs-pros/)\n- [https://juejin.cn/post/6844904009203974158](https://juejin.cn/post/6844904009203974158)\n"
  },
  {
    "path": "docs/React/summary.md",
    "content": "# 面试官：说说你在使用React 过程中遇到的常见问题？如何解决?\n\n\n ![](https://static.vue-js.com/7efcd400-f47d-11eb-ab90-d9ae814b240d.png)\n\n## 一、前言\n在使用`react`开发项目过程中，每个人或多或少都会遇到一些\"奇怪\"的问题，本质上都是我们对其理解的不够透彻\n\n`react` 系列，33个工作日，33次凌晨还在亮起的台灯，到今天就圆满画上句号了，比心\n\n在系列中我们列出了很多比较经典的考题，工作中遇到的问题也往往就藏中其中，只是以不同的表现形式存在罢了\n\n今天的题解不算题解，准确来说是对整个系列的一次贯穿，总结\n\n目录:\n\n- react 有什么特性\n- 生命周期有哪些不同阶段？每个阶段对应的方法是？\n- state 和 props有什么区别？\n- super()和super(props)有什么区别？\n- setState执行机制？\n- React的事件机制？\n- 事件绑定的方式有哪些？\n- 构建组件的方式有哪些？区别？\n- 组件之间如何通信？\n- key有什么作用？\n- refs 的理解？应用场景？\n- Hooks的理解？解决了什么问题？\n- 如何引入css？\n- redux工作原理？\n- redux中间件有哪些？\n- react-router组件有哪些？\n- render触发时机？\n- 如何减少render？\n- JSX转化DOM过程？\n- 性能优化手段有哪些\n- 如何做服务端渲染？\n\n\n### react 有什么特性\n\n主要的特性分为：\n\n- JSX语法\n- 单向数据绑定\n- 虚拟DOM\n- 声明式编程\n- Component\n\n借助这些特性，`react`整体使用起来更加简单高效，组件式开发提高了代码的复用率\n\n\n### 生命周期有哪些不同阶段？每个阶段对应的方法是？\n\n主要分成了新的生命周期和旧的生命周期：\n\n- 新版生命周期整体流程如下图所示：\n\n   ![](https://static.vue-js.com/66c999c0-d373-11eb-85f6-6fac77c0c9b3.png)\n\n  旧的生命周期流程图如下：\n\n  ![](https://static.vue-js.com/d379e420-d374-11eb-ab90-d9ae814b240d.png)\n\n\n\n### state 和 props有什么区别？\n\n两者相同点：\n\n- 两者都是 JavaScript 对象\n- 两者都是用于保存信息\n- props 和 state 都能触发渲染更新\n\n区别：\n\n- props 是外部传递给组件的，而 state 是在组件内被组件自己管理的，一般在 constructor 中初始化\n- props 在组件内部是不可修改的，但 state 在组件内部可以进行修改\n- state 是多变的、可以修改\n\n\n\n### super()和super(props)有什么区别？\n\n在`React`中，类组件基于`ES6`，所以在`constructor`中必须使用`super`\n\n在调用`super`过程，无论是否传入`props`，`React`内部都会将`porps`赋值给组件实例`porps`属性中\n\n如果只调用了`super()`，那么`this.props`在`super()`和构造函数结束之间仍是`undefined`\n\n\n\n### setState执行机制？\n\n在`react`类组件的状态需要通过`setState`进行更改，在不同场景下对应不同的执行顺序：\n\n- 在组件生命周期或React合成事件中，setState是异步\n- 在setTimeout或者原生dom事件中，setState是同步\n\n当我们批量更改`state`的值的时候，`react`内部会将其进行覆盖，只取最后一次的执行结果\n\n当需要下一个`state`依赖当前`state`的时候，则可以在`setState`中传递一个回调函数进行下次更新\n\n\n\n### React的事件机制？\n\n`React`基于浏览器的事件机制自身实现了一套事件机制，包括事件注册、事件的合成、事件冒泡、事件派发等\n\n组件注册的事件最终会绑定在`document`这个 `DOM `上，而不是 `React `组件对应的 `DOM`，从而节省内存开销\n\n自身实现了一套事件冒泡机制，阻止不同时间段的冒泡行为，需要对应使用不同的方法\n\n\n\n### 事件绑定的方式有哪些？\n\n`react`常见的绑定方式有如下：\n\n- render方法中使用bind\n- render方法中使用箭头函数\n- constructor中bind\n- 定义阶段使用箭头函数绑定\n\n前两种方式在每次组件`render`的时候都会生成新的方法实例，性能问题欠缺\n\n\n\n### 构建组件的方式有哪些？区别？\n\n组件的创建主要分成了三种方式：\n\n- 函数式创建\n- 继承 React.Component 创建\n- 通过 React.createClass 方法创建\n\n如今一般都是前两种方式，对于一些无状态的组件创建，建议使用函数式创建的方式，再比如`hooks`的机制下，函数式组件能做类组件对应的事情，所以建议都使用函数式的方式来创建组件\n\n\n\n### 组件之间如何通信？\n\n组件间通信可以通过`props`、传递回调函数、`context`、`redux`等形式进行组件之间通讯\n\n\n\n### key有什么作用？\n\n使用`key`是`react`性能优化的手段，在一系列数据最前面插入元素，如果没有`key`的值，则所有的元素都需要进行更换，而有`key`的情况只需要将最新元素插入到前面，不涉及删除操作\n\n在使用`key`的时候应保证：\n\n- key 应该是唯一的\n- key不要使用随机值（随机数在下一次 render 时，会重新生成一个数字）\n- 避免使用 index 作为 key\n\n\n\n### refs 的理解？应用场景？\n\n`Refs`允许我们访问 `DOM `节点或在 `render `方法中创建的 `React `元素\n\n下面的场景使用`refs`非常有用：\n\n- 对Dom元素的焦点控制、内容选择、控制\n- 对Dom元素的内容设置及媒体播放\n- 对Dom元素的操作和对组件实例的操作\n- 集成第三方 DOM 库\n\n\n\n### Hooks的理解？解决了什么问题？\n\n`Hook` 是 React 16.8 的新增特性。它可以让你在不编写 `class` 的情况下使用 `state` 以及其他的 `React` 特性\n\n解决问题如下：\n\n- 难以重用和共享组件中的与状态相关的逻辑\n- 逻辑复杂的组件难以开发与维护，当我们的组件需要处理多个互不相关的 local state 时，每个生命周期函数中可能会包含着各种互不相关的逻辑在里面\n- 类组件中的this增加学习成本，类组件在基于现有工具的优化上存在些许问题\n- 由于业务变动，函数组件不得不改为类组件等等\n\n\n\n### 如何引入css？\n\n常见的`CSS`引入方式有以下：\n\n- 在组件内直接使用\n- 组件中引入 .css 文件\n- 组件中引入 .module.css 文件\n- CSS in JS\n\n组件内直接使用`css`会导致大量的代码，而文件中直接引入`css`文件是全局作用域，发生层叠\n\n引入`.module.css `文件能够解决局部作用域问题，但是不方便动态修改样式，需要使用内联的方式进行样式的编写\n\n`css in js `这种方法，可以满足大部分场景的应用，可以类似于预处理器一样样式嵌套、定义、修改状态等\n\n\n\n### redux工作原理？\n\n`redux `要求我们把数据都放在 `store `公共存储空间\n\n一个组件改变了 `store` 里的数据内容，其他组件就能感知到 `store `的变化，再来取数据，从而间接的实现了这些数据传递的功能\n\n工作流程图如下所示：\n\n ![](https://static.vue-js.com/27b2e930-e56b-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### redux中间件有哪些？\n\n市面上有很多优秀的`redux`中间件，如：\n\n- redux-thunk：用于异步操作\n- redux-logger：用于日志记录\n\n\n\n### react-router组件有哪些？\n\n常见的组件有：\n\n- BrowserRouter、HashRouter\n- Route\n- Link、NavLink\n- switch\n- redirect\n\n\n\n### render触发时机？\n\n在` React` 中，类组件只要执行了 `setState` 方法，就一定会触发 `render` 函数执行\n\n函数组件`useState` 会判断当前值有无发生改变确定是否执行`render`方法，一旦父组件发生渲染，子组件也会渲染\n\n\n\n### 如何减少render？\n\n父组件渲染导致子组件渲染，子组件并没有发生任何改变，这时候就可以从避免无谓的渲染，具体实现的方式有如下：\n\n- shouldComponentUpdate\n- PureComponent\n- React.memo\n\n\n\n### JSX转化DOM过程？\n\n`jsx`首先会转化成`React.createElement`这种形式，`React.createElement`作用是生成一个虚拟`Dom`对象，然后会通过`ReactDOM.render`进行渲染成真实`DOM`\n\n\n\n### 性能优化手段有哪些\n\n除了减少`render`的渲染之外，还可以通过以下手段进行优化：\n\n除此之外， 常见性能优化常见的手段有如下：\n\n- 避免使用内联函数\n- 使用 React Fragments 避免额外标记\n- 使用 Immutable\n- 懒加载组件\n- 事件绑定方式\n- 服务端渲染\n\n### 如何做服务端渲染？\n\n`node server` 接收客户端请求，得到当前的请求`url` 路径，然后在已有的路由表内查找到对应的组件，拿到需要请求的数据，将数据作为 `props`、`context`或者`store` 形式传入组件\n\n然后基于 `react` 内置的服务端渲染方法 `renderToString()`把组件渲染为 `html`字符串在把最终的 `html `进行输出前需要将数据注入到浏览器端\n\n浏览器开始进行渲染和节点对比，然后执行完成组件内事件绑定和一些交互，浏览器重用了服务端输出的 `html` 节点，整个流程结束\n\n ![](https://static.vue-js.com/a2894970-f3f7-11eb-85f6-6fac77c0c9b3.png)\n"
  },
  {
    "path": "docs/React/super()_super(props).md",
    "content": "# 面试官：super() 和 super(props) 有什么区别？\n\n![](https://static.vue-js.com/618abaf0-d71c-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、ES6 类\n\n在 `ES6` 中，通过 `extends` 关键字实现类的继承，方式如下：\n\n```js\nclass sup {\n  constructor(name) {\n    this.name = name;\n  }\n\n  printName() {\n    console.log(this.name);\n  }\n}\n\nclass sub extends sup {\n  constructor(name, age) {\n    super(name); // super代表的事父类的构造函数\n    this.age = age;\n  }\n\n  printAge() {\n    console.log(this.age);\n  }\n}\n\nlet jack = new sub(\"jack\", 20);\njack.printName(); //输出 : jack\njack.printAge(); //输出 : 20\n```\n\n在上面的例子中，可以看到通过 `super` 关键字实现调用父类，`super` 代替的是父类的构建函数，使用 `super(name)` 相当于调用 `sup.prototype.constructor.call(this,name)`\n\n如果在子类中不使用 `super`，关键字，则会引发报错，如下：\n\n![](https://static.vue-js.com/6ab40190-d71c-11eb-85f6-6fac77c0c9b3.png)\n\n报错的原因是 子类是没有自己的 `this` 对象的，它只能继承父类的 `this` 对象，然后对其进行加工\n\n而 `super()` 就是将父类中的 `this` 对象继承给子类的，没有 `super()` 子类就得不到 `this` 对象\n\n如果先调用 `this`，再初始化 `super()`，同样是禁止的行为\n\n```js\nclass sub extends sup {\n  constructor(name, age) {\n    this.age = age;\n    super(name); // super代表的事父类的构造函数\n  }\n}\n```\n\n所以在子类 `constructor` 中，必须先代用 `super` 才能引用 `this`\n\n## 二、类组件\n\n在 `React` 中，类组件是基于 `ES6` 的规范实现的，继承 `React.Component`，因此如果用到 `constructor` 就必须写 `super()` 才初始化 `this`\n\n这时候，在调用 `super()` 的时候，我们一般都需要传入 `props` 作为参数，如果不传进去，`React` 内部也会将其定义在组件实例中\n\n```js\n// React 内部\nconst instance = new YourComponent(props);\ninstance.props = props;\n```\n\n所以无论有没有 `constructor`，在 `render` 中 `this.props` 都是可以使用的，这是 `React` 自动附带的，是可以不写的：\n\n```jsx\nclass HelloMessage extends React.Component {\n  render() {\n    return <div>nice to meet you! {this.props.name}</div>;\n  }\n}\n```\n\n但是也不建议使用 `super()` 代替 `super(props)`\n\n因为在 `React` 会在类组件构造函数生成实例后再给 `this.props` 赋值，所以在不传递 `props` 在 `super` 的情况下，调用 `this.props` 为 `undefined`，如下情况：\n\n```jsx\nclass Button extends React.Component {\n  constructor(props) {\n    super(); // 没传入 props\n    console.log(props);      //  {}\n    console.log(this.props); //  undefined\n    // ...\n  }\n}\n```\n\n而传入 `props` 的则都能正常访问，确保了 `this.props` 在构造函数执行完毕之前已被赋值，更符合逻辑，如下：\n\n```jsx\nclass Button extends React.Component {\n  constructor(props) {\n    super(props); // 没传入 props\n    console.log(props);      //  {}\n    console.log(this.props); //  {}\n    // ...\n  }\n}\n```\n\n## 三、总结\n\n在 `React` 中，类组件基于 `ES6`，所以在 `constructor` 中必须使用 `super`\n\n在调用 `super` 过程，无论是否传入 `props`，`React` 内部都会将 `porps` 赋值给组件实例 `porps` 属性中\n\n如果只调用了 `super()`，那么 `this.props` 在 `super()` 和构造函数结束之间仍是 `undefined`\n\n## 参考文献\n\n- [https://overreacted.io/zh-hans/why-do-we-write-super-props/](https://overreacted.io/zh-hans/why-do-we-write-super-props/)\n- [https://segmentfault.com/q/1010000008340434](https://segmentfault.com/q/1010000008340434)\n"
  },
  {
    "path": "docs/algorithm/Algorithm.md",
    "content": "# 面试官：说说你对算法的理解？应用场景？\n\n\n ![](https://static.vue-js.com/eca03690-1620-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n算法（Algorithm）是指解题方案的准确而完整的描述，是一系列解决问题的清晰指令，算法代表着用系统的方法描述解决问题的策略机制\n\n也就是说，能够对一定规范的输入，在有限时间内获得所要求的输出\n\n如果一个算法有缺陷，或不适合于某个问题，执行这个算法将不会解决这个问题\n\n一个程序=算法+数据结构，数据结构是算法实现的基础，算法总是要依赖于某种数据结构来实现的，两者不可分割\n\n因此，算法的设计和选择要同时结合数据结构，简单地说数据结构的设计就是选择存储方式，如确定问题中的信息是用数组存储还是用普通的变量存储或其他更加复杂的数据结构\n\n针对上述，可以得出一个总结：不同的算法可能用不同的时间、空间或效率来完成同样的任务\n\n## 二、特性\n\n关于算法的五大特性，有如下：\n\n- 有限性（Finiteness）：一个算法必须保证执行有限步之后结束\n- 确切性（Definiteness）： 一个算法的每一步骤必须有确切的定义\n- 输入（Input）：一个算法有零个或多个输入，以刻画运算对象的初始情况，所谓零个输入是指算法本身给定了初始条件\n- 输出（Output）：一个算法有一个或多个输出。没有输出的算法毫无意义\n- 可行性（Effectiveness）：算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤，即每个计算步骤都可以在有限时间内完成（也称之为有效性）\n\n\n## 三、应用场景\n\n在前端领域中，数据结构与算法无法不在，例如现在的`vue`或者`react`项目，实现虚拟`DOM`或者`Fiber`结构，本质就是一种数据结构，如下一个简单的虚拟`DOM`：\n\n```js\n{\n  type: 'div',\n    props: {\n      name: 'lucifer'\n    },\n      children: [{\n        type: 'span',\n        props: {},\n        children: []\n      }]\n}\n```\n\n`vue`与`react`都能基于基于对应的数据结构实现`diff`算法，提高了整个框架的性能以及拓展性\n\n包括在前端`javascript`编译的时候，都会生成对应的抽象语法树`AST`，其本身不涉及到任何语法，因此你只要编写相应的转义规则，就可以将任何语法转义到任何语法，也是`babel`， `PostCSS`, `prettier`， `typescript` \n\n除了这些框架或者工具底层用到算法与数据结构之外，日常业务也无处不在，例如实现一个输入框携带联想功能，如下：\n\n ![](https://static.vue-js.com/682d16c0-1621-11ec-8e64-91fdec0f05a1.png)\n\n如果我们要实现这个功能， 则可以使用前缀树，如下：\n\n ![](https://static.vue-js.com/55a1ed50-1621-11ec-8e64-91fdec0f05a1.png)\n\n包括前端可能会做一些对字符串进行相似度检测，例如\"每日一题\"和\"js每日一题\"两个字符串进行相似度对比，这种情况可以通过“最小编辑距离”算法，如果`a`和`b`的编辑距离越小，我们认为越相似\n\n日常在编写任何代码的都需要一个良好的算法思维，选择好的算法或者数据结构，能让整个程序效率更高\n\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E7%AE%97%E6%B3%95/209025\n- https://lucifer.ren/blog/2019/09/18/algorthimn-fe-1/"
  },
  {
    "path": "docs/algorithm/BinarySearch.md",
    "content": "# 面试官：说说你对二分查找的理解？如何实现？应用场景？\n\n ![](https://static.vue-js.com/d43ca230-2987-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n在计算机科学中，二分查找算法，也称折半搜索算法，是一种在有序数组中查找某一特定元素的搜索算法\n\n想要应用二分查找法，则这一堆数应有如下特性：\n\n- 存储在数组中\n- 有序排序\n\n搜索过程从数组的中间元素开始，如果中间元素正好是要查找的元素，则搜索过程结束\n\n如果某一特定元素大于或者小于中间元素，则在数组大于或小于中间元素的那一半中查找，而且跟开始一样从中间元素开始比较\n\n如果在某一步骤数组为空，则代表找不到\n\n这种搜索算法每一次比较都使搜索范围缩小一半\n\n如下图所示：\n\n ![](https://static.vue-js.com/e2108520-2987-11ec-a752-75723a64e8f5.png)\n\n相比普通的顺序查找，除了数据量很少的情况下，二分查找会比顺序查找更快，区别如下所示：\n\n![](https://pic2.zhimg.com/v2-43339b963db63b33107b56503ad6b1b5_b.gif)\n\n## 二、如何实现\n\n基于二分查找的实现，如果数据是有序的，并且不存在重复项，实现代码如下：\n\n```js\nfunction BinarySearch(arr, target) {\n    if (arr.length <= 1) return -1\n    // 低位下标\n    let lowIndex = 0\n    // 高位下标\n    let highIndex = arr.length - 1\n\n    while (lowIndex <= highIndex) {\n        // 中间下标\n        const midIndex = Math.floor((lowIndex + highIndex) / 2)\n        if (target < arr[midIndex]) {\n            highIndex = midIndex - 1\n        } else if (target > arr[midIndex]) {\n            lowIndex = midIndex + 1\n        } else {\n            // target === arr[midIndex]\n            return midIndex\n        }\n    }\n    return -1\n}\n```\n\n如果数组中存在重复项，而我们需要找出第一个制定的值，实现则如下：\n\n```js\nfunction BinarySearchFirst(arr, target) {\n    if (arr.length <= 1) return -1\n    // 低位下标\n    let lowIndex = 0\n    // 高位下标\n    let highIndex = arr.length - 1\n\n    while (lowIndex <= highIndex) {\n        // 中间下标\n        const midIndex = Math.floor((lowIndex + highIndex) / 2)\n        if (target < arr[midIndex]) {\n            highIndex = midIndex - 1\n        } else if (target > arr[midIndex]) {\n            lowIndex = midIndex + 1\n        } else {\n            // 当 target 与 arr[midIndex] 相等的时候，如果 midIndex 为0或者前一个数比 target 小那么就找到了第一个等于给定值的元素，直接返回\n            if (midIndex === 0 || arr[midIndex - 1] < target) return midIndex\n            // 否则高位下标为中间下标减1，继续查找\n            highIndex = midIndex - 1\n        }\n    }\n    return -1\n}\n```\n\n实际上，除了有序的数组可以使用，还有一种特殊的数组可以应用，那就是轮转后的有序数组\n\n有序数组即一个有序数字以某一个数为轴，将其之前的所有数都轮转到数组的末尾所得\n\n例如，[4, 5, 6, 7, 0, 1, 2]就是一个轮转后的有序数组\n\n该数组的特性是存在一个分界点用来分界两个有序数组，如下：\n\n ![](https://static.vue-js.com/eeee2130-2987-11ec-8e64-91fdec0f05a1.png)\n\n分界点有如下特性：\n\n- 分界点元素 >= 第一个元素\n- 分界点元素 < 第一个元素\n\n代码实现如下：\n\n```js\nfunction search (nums, target) {\n  // 如果为空或者是空数组的情况\n  if (nums == null || !nums.length) {\n    return -1;\n  }\n  // 搜索区间是前闭后闭\n  let begin = 0,\n    end = nums.length - 1;\n  while (begin <= end) {\n    // 下面这样写是考虑大数情况下避免溢出\n    let mid = begin + ((end - begin) >> 1);\n    if (nums[mid] == target) {\n      return mid;\n    }\n    // 如果左边是有序的\n    if (nums[begin] <= nums[mid]) {\n      //同时target在[ nums[begin],nums[mid] ]中，那么就在这段有序区间查找\n      if (nums[begin] <= target && target <= nums[mid]) {\n        end = mid - 1;\n      } else {\n        //否则去反方向查找\n        begin = mid + 1;\n      }\n      //如果右侧是有序的\n    } else {\n      //同时target在[ nums[mid],nums[end] ]中，那么就在这段有序区间查找\n      if (nums[mid] <= target && target <= nums[end]) {\n        begin = mid + 1;\n      } else {\n        end = mid - 1;\n      }\n    }\n  }\n  return -1;\n};\n```\n\n对比普通的二分查找法，为了确定目标数会落在二分后的哪个部分，我们需要更多的判定条件\n\n\n\n\n\n## 三、应用场景\n\n二分查找法的`O(logn)`让它成为十分高效的算法。不过它的缺陷却也是比较明显，就在它的限定之上：\n\n- 有序：我们很难保证我们的数组都是有序的\n- 数组：数组读取效率是O(1)，可是它的插入和删除某个元素的效率却是O(n)，并且数组的存储是需要连续的内存空间，不适合大数据的情况\n\n关于二分查找的应用场景，主要如下：\n\n- 不适合数据量太小的数列；数列太小，直接顺序遍历说不定更快，也更简单\n- 每次元素与元素的比较是比较耗时的，这个比较操作耗时占整个遍历算法时间的大部分，那么使用二分查找就能有效减少元素比较的次数\n- 不适合数据量太大的数列，二分查找作用的数据结构是顺序表，也就是数组，数组是需要连续的内存空间的，系统并不一定有这么大的连续内存空间可以使用\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95#javascript_%E7%89%88%E6%9C%AC\n- https://www.cnblogs.com/ider/archive/2012/04/01/binary_search.html"
  },
  {
    "path": "docs/algorithm/Heap.md",
    "content": "# 面试官：说说你对堆的理解？如何实现？应用场景？\n\n\n ![](https://static.vue-js.com/dd12c700-1ed7-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n堆(Heap)是计算机科学中一类特殊的数据结构的统称\n\n堆通常是一个可以被看做一棵完全二叉树的数组对象，如下图：\n\n ![](https://static.vue-js.com/ea0fd1f0-1ed7-11ec-8e64-91fdec0f05a1.png)\n\n总是满足下列性质：\n\n- 堆中某个结点的值总是不大于或不小于其父结点的值\n- 堆总是一棵完全二叉树\n\n堆又可以分成最大堆和最小堆：\n\n- 最大堆：每个根结点，都有根结点的值大于两个孩子结点的值\n- 最小堆：每个根结点，都有根结点的值小于孩子结点的值\n\n\n\n## 二、操作\n\n堆的元素存储方式，按照完全二叉树的顺序存储方式存储在一个一维数组中，如下图：\n\n![](https://static.vue-js.com/ea0fd1f0-1ed7-11ec-8e64-91fdec0f05a1.png)\n\n用一维数组存储则如下：\n\n```js\n[0, 1, 2, 3, 4, 5, 6, 7, 8]\n```\n\n根据完全二叉树的特性，可以得到如下特性：\n\n- 数组零坐标代码的是堆顶元素\n- 一个节点的父亲节点的坐标等于其坐标除以2整数部分\n- 一个节点的左节点等于其本身节点坐标 * 2 + 1\n- 一个节点的右节点等于其本身节点坐标 * 2 + 2\n\n\n\n根据上述堆的特性，下面构建最小堆的构造函数和对应的属性方法：\n\n```js\nclass MinHeap {\n  constructor() {\n    // 存储堆元素\n    this.heap = []\n  }\n  // 获取父元素坐标\n  getParentIndex(i) {\n    return (i - 1) >> 1\n  }\n\n  // 获取左节点元素坐标\n  getLeftIndex(i) {\n    return i * 2 + 1\n  }\n\n // 获取右节点元素坐标\n  getRightIndex(i) {\n    return i * 2 + 2\n  }\n\n  // 交换元素\n  swap(i1, i2) {\n    const temp = this.heap[i1]\n    this.heap[i1] = this.heap[i2]\n    this.heap[i2] = temp\n  }\n\n  // 查看堆顶元素\n  peek() {\n    return this.heap[0]\n  }\n\n  // 获取堆元素的大小\n  size() {\n    return this.heap.length\n  }\n}\n```\n\n\n\n\n\n涉及到堆的操作有：\n\n- 插入\n- 删除\n\n\n\n### 插入\n\n将值插入堆的底部，即数组的尾部，当插入一个新的元素之后，堆的结构就会被破坏，因此需要堆中一个元素做上移操作\n\n将这个值和它父节点进行交换，直到父节点小于等于这个插入的值，大小为`k`的堆中插入元素的时间复杂度为`O(logk)`\n\n如下图所示，22节点是新插入的元素，然后进行上移操作：\n\n ![](https://static.vue-js.com/06893fb0-1ed8-11ec-8e64-91fdec0f05a1.png)\n\n相关代码如下：\n\n```js\n// 插入元素\ninsert(value) {\n  this.heap.push(value)\n  this.shifUp(this.heap.length - 1)\n}\n\n// 上移操作\nshiftUp(index) {\n  if (index === 0) { return }\n  const parentIndex = this.getParentIndex(index)\n  if(this.heap[parentIndex] > this.heap[index]){\n    this.swap(parentIndex, index)\n    this.shiftUp(parentIndex)\n  }\n}\n```\n\n\n### 删除\n\n常见操作是用数组尾部元素替换堆顶，这里不直接删除堆顶，因为所有的元素会向前移动一位，会破坏了堆的结构\n\n然后进行下移操作，将新的堆顶和它的子节点进行交换，直到子节点大于等于这个新的堆顶，删除堆顶的时间复杂度为`O(logk)`\n\n整体如下图操作：\n\n ![](https://static.vue-js.com/12a2a160-1ed8-11ec-a752-75723a64e8f5.png)\n\n相关代码如下：\n\n```js\n// 删除元素\npop() {\n  this.heap[0] = this.heap.pop()\n  this.shiftDown(0)\n}\n\n// 下移操作\nshiftDown(index) {\n  const leftIndex = this.getLeftIndex(index)\n  const rightIndex = this.getRightIndex(index)\n  if (this.heap[leftIndex] < this.heap[index]){\n    this.swap(leftIndex, index)\n    this.shiftDown(leftIndex)\n  }\n  if (this.heap[rightIndex] < this.heap[index]){\n    this.swap(rightIndex, index)\n    this.shiftDown(rightIndex)\n  }\n}\n```\n\n\n\n### 时间复杂度\n\n关于堆的插入和删除时间复杂度都是`Olog(n)`，原因在于包含n个节点的完全二叉树，树的高度不会超过`log2n`\n\n堆化的过程是顺着节点所在路径比较交换的，所以堆化的时间复杂度跟树的高度成正比，也就是`Olog(n)`，插入数据和删除堆顶元素的主要逻辑就是堆化\n\n\n### 三、总结\n\n- 堆是一个完全二叉树\n- 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值\n- 对于每个节点的值都大于等于子树中每个节点值的堆，叫作“大顶堆”\n- 对于每个节点的值都小于等于子树中每个节点值的堆，叫作“小顶堆”\n- 根据堆的特性，我们可以使用堆来进行排序操作，也可以使用其来求第几大或者第几小的值\n\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E5%A0%86/20606834\n- https://xlbpowder.cn/2021/02/26/%E5%A0%86%E5%92%8C%E5%A0%86%E6%8E%92%E5%BA%8F/"
  },
  {
    "path": "docs/algorithm/Linked List.md",
    "content": "# 面试官：说说你对链表的理解？常见的操作有哪些？\n\n ![](https://static.vue-js.com/d6638dd0-1c76-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n链表（Linked List）是一种物理存储单元上非连续、非顺序的存储结构，数据元素的逻辑顺序是通过链表中的指针链接次序实现的，由一系列结点（链表中每一个元素称为结点）组成\n\n每个结点包括两个部分：一个是存储数据元素的数据域，另一个是存储下一个结点地址的指针域\n\n ![](https://static.vue-js.com/e4e93490-1c76-11ec-8e64-91fdec0f05a1.png)\n\n节点用代码表示，则如下：\n\n```js\nclass Node {\n  constructor(val) {\n    this.val = val;\n    this.next = null;\n  }\n}\n```\n\n- data 表示节点存放的数据\n- next 表示下一个节点指向的内存空间\n\n相比于线性表顺序结构，操作复杂。由于不必须按顺序存储，链表在插入的时候可以达到`O(1)`的复杂度，比另一种线性表顺序表快得多，但是查找一个节点或者访问特定编号的节点则需要O(n)的时间，而线性表和顺序表相应的时间复杂度分别是`O(logn)`和`O(1)`\n\n链表的结构也十分多，常见的有四种形式：\n\n- 单链表：除了头节点和尾节点，其他节点只包含一个后继指针\n- 循环链表：跟单链表唯一的区别就在于它的尾结点又指回了链表的头结点，首尾相连，形成了一个环\n- 双向链表：每个结点具有两个方向指针，后继指针(next)指向后面的结点，前驱指针(prev)指向前面的结点，其中节点的前驱指针和尾结点的后继指针均指向空地址NULL\n- 双向循环链表：跟双向链表基本一致，不过头节点前驱指针指向尾迹诶单和尾节点的后继指针指向头节点\n\n\n\n## 二、操作\n\n关于链表的操作可以主要分成如下：\n\n- 遍历\n- 插入\n- 删除\n\n### 遍历\n\n遍历很好理解，就是根据`next`指针遍历下去，直到为`null`，如下：\n\n```js\nlet current = head\nwhile(current){\n console.log(current.val)\n  current = current.next\n}\n```\n\n### 插入\n\n向链表中间插入一个元素，可以如下图所示：\n\n ![](https://static.vue-js.com/f5fe5fd0-1c76-11ec-8e64-91fdec0f05a1.png)\n\n可以看到，插入节点可以分成如下步骤：\n\n- 存储插入位置的前一个节点\n- 存储插入位置的后一个节点\n\n- 将插入位置的前一个节点的 next 指向插入节点\n- 将插入节点的 next 指向前面存储的 next 节点\n\n相关代码如下所示：\n\n```js\nlet current = head\nwhile (current < position){\n  pervious = current;\n  current = current.next;\n}\npervious.next = node;\nnode.next = current;\n\n```\n\n如果在头节点进行插入操作的时候，会实现`previousNode`节点为`undefined`，不适合上述方式\n\n解放方式可以是在头节点前面添加一个虚拟头节点，保证插入行为一致\n\n\n\n### 删除\n\n向链表任意位置删除节点，如下图操作：\n\n ![](https://static.vue-js.com/0160cd90-1c77-11ec-a752-75723a64e8f5.png)\n\n从上图可以看到删除节点的步骤为如下：\n\n- 获取删除节点的前一个节点\n- 获取删除节点的后一个节点\n- 将前一个节点的 next 指向后一个节点\n- 向删除节点的 next 指向为null\n\n如果想要删除制定的节点，示意代码如下：\n\n```js\nwhile (current != node){\n  pervious = current;\n  current = current.next;\n  nextNode = current.next;\n}\npervious.next = nextNode\n```\n\n同样如何希望删除节点处理行为一致，可以在头节点前面添加一个虚拟头节点\n\n\n\n## 三、应用场景\n\n缓存是一种提高数据读取性能的技术，在硬件设计、软件开发中都有着非常广泛的应用，比如常见的`CPU`缓存、数据库缓存、浏览器缓存等等\n\n当缓存空间被用满时，我们可能会使用`LRU`最近最好使用策略去清楚，而实现`LRU`算法的数据结构是链表，思路如下：\n\n维护一个有序单链表，越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时，我们从链表头部开始顺序遍历链表\n\n- 如果此数据之前已经被缓存在链表中了，我们遍历得到这个数据的对应结点，并将其从原来的位置删除，并插入到链表头部\n- 如果此数据没在缓存链表中\n  - 如果此时缓存未满，可直接在链表头部插入新节点存储此数据\n  - 如果此时缓存已满，则删除链表尾部节点，再在链表头部插入新节点\n\n由于链表插入删除效率极高，达到O(1)。对于不需要搜索但变动频繁且无法预知数量上限的数据的情况的时候，都可以使用链表\n\n\n## 参考文献\n- https://zh.wikipedia.org/zh-hans/%E9%93%BE%E8%A1%A8\n- https://mah93.github.io/2019/07/19/js-linked/\n"
  },
  {
    "path": "docs/algorithm/bubbleSort.md",
    "content": "# 面试官：说说你对冒泡排序的理解？如何实现？应用场景？\n\n ![](https://static.vue-js.com/6f5e0850-2652-11ec-a752-75723a64e8f5.png)\n\n\n## 一、是什么\n\n冒泡排序（Bubble Sort），是一种计算机科学领域的较简单的排序算法\n\n冒泡排序的思想就是在每次遍历一遍未排序的数列之后，将一个数据元素浮上去（也就是排好了一个数据）\n\n如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样，故名“冒泡排序”\n\n假如我们要把 12、35、99、18、76 这 5 个数从大到小进行排序，那么数越大，越需要把它放在前面\n\n思路如下：\n\n- 从后开始遍历，首先比较 18 和 76，发现 76 比 18 大，就把两个数交换顺序，得到 12、35、99、76、18\n- 接着比较 76 和 99，发现 76 比 99 小，所以不用交换顺序\n- 接着比较 99 和 35，发现 99 比 35 大，交换顺序\n- 接着比较 99 和 12，发现 99 比 12 大，交换顺序\n\n最终第 1 趟排序的结果变成了 99、12、35、76、18，如下图所示：\n\n ![](https://static.vue-js.com/7a363770-2652-11ec-8e64-91fdec0f05a1.png)\n\n上述可以看到，经过第一趟的排序，可以得到最大的元素，接下来第二趟排序则对剩下的的4个元素进行排序，如下图所示：\n\n ![](https://static.vue-js.com/84b9ddf0-2652-11ec-a752-75723a64e8f5.png)\n\n经过第 2 趟排序，结果为 99、76、12、35、18\n\n然后开始第3趟的排序，结果为99、76、35、12、18\n\n然后第四趟排序结果为99、76、35、18、12\n\n经过 4 趟排序之后，只剩一个 12 需要排序了，这时已经没有可比较的元素了，这时排序完成\n\n## 二、如何实现\n\n如果要实现一个从小到大的排序，算法原理如下：\n\n- 首先比较相邻的元素，如果第一个元素比第二个元素大，则交换它们\n- 针对每一对相邻元素做同样的工作，从开始第一对到结尾的最后一对，这样，最后的元素回事最大的数\n- 针对所有的元素重复以上的步骤，除了最后一个\n- 持续每次对越来越少的元素重复上面的步骤，直到没有任何一对数字需要比较\n\n![](https://www.runoob.com/wp-content/uploads/2019/03/bubbleSort.gif)\n\n用代码表示则如下：\n\n```js\nfunction bubbleSort(arr) {\n    const len = arr.length;\n    for (let i = 0; i < len - 1; i++) {\n        for (let j = 0; j < len - 1 - i; j++) {\n            if (arr[j] > arr[j+1]) {        // 相邻元素两两对比\n                var temp = arr[j+1];        // 元素交换\n                arr[j+1] = arr[j];\n                arr[j] = temp;\n            }\n        }\n    }\n    return arr;\n}\n```\n\n可以看到：冒泡排序在每一轮排序中都会使一个元素排到一趟， 也就是最终需要 n-1 轮这样的排序\n\n而在每轮排序中都需要对相邻的两个元素进行比较，在最坏的情况下，每次比较之后都需要交换位置，此时时间复杂度为`O(n^2)`\n\n\n\n### 优化\n\n对冒泡排序常见的改进方法是加入一标志性变量`exchange`，用于标志某一趟排序过程中是否有数据交换\n\n如果进行某一趟排序时并没有进行数据交换，则说明数据已经按要求排列好，可立即结束排序，避免不必要的比较过程\n\n可以设置一标志性变量`pos`，用于记录每趟排序中最后一次进行交换的位置，由于`pos`位置之后的记录均已交换到位，故在进行下一趟排序时只要扫描到`pos`位置即可，如下：\n\n```js\nfunction bubbleSort1(arr){\n const i=arr.length-1;//初始时,最后位置保持不变  \n while(i>0){\n  let pos = 0;//每趟开始时,无记录交换\n  for(let j = 0; j < i; j++){\n   if(arr[j] > arr[j+1]){\n        let tmp = arr[j];\n        arr[j] = arr[j+1];\n        arr[j+1] = tmp;\n    pos = j;//记录最后交换的位置  \n   }   \n  }\n  i = pos;//为下一趟排序作准备\n }\n return arr;\n}\n```\n\n在待排序的数列有序的情况下，只需要一轮排序并且不用交换，此时情况最好，时间复杂度为`O(n)`\n\n并且从上述比较中看到，只有后一个元素比前面的元素大（小）时才会对它们交换位置并向上冒出，对于同样大小的元素，是不需要交换位置的，所以对于同样大小的元素来说，相对位置是不会改变的，因此， 冒泡排序是稳定的\n\n\n\n\n\n## 三、应用场景\n冒泡排的核心部分是双重嵌套循环，\n时间复杂度是 O(N 2 )，相比其它排序算法，这是一个相对较高的时间复杂度，一般情况不推荐使用，由于冒泡排序的简洁性，通常被用来对于程序设计入门的学生介绍算法的概念\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F/4602306\n- https://www.runoob.com/w3cnote/bubble-sort.html\n- http://data.biancheng.net/view/116.html\n- https://dsb123dsb.github.io/2017/03/07/js%E5%AE%9E%E7%8E%B0%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E4%BB%A5%E5%8F%8A%E4%BC%98%E5%8C%96/"
  },
  {
    "path": "docs/algorithm/design1.md",
    "content": "# 面试官：说说你对分而治之、动态规划的理解？区别？\n\n\n\n ![](https://static.vue-js.com/298437b0-29d0-11ec-a752-75723a64e8f5.png)\n\n\n## 一、分而治之\n\n分而治之是算法设计中的一种方法，就是把一个复杂的问题分成两个或更多的相同或相似的子问题，直到最后子问题可以简单的直接求解，原问题的解即子问题的解的合并\n\n关于分而治之的实现，都会经历三个步骤：\n\n- 分解：将原问题分解为若干个规模较小，相对独立，与原问题形式相同的子问题\n- 解决：若子问题规模较小且易于解决时，则直接解。否则，递归地解决各子问题\n- 合并：将各子问题的解合并为原问题的解\n\n实际上，关于分而治之的思想，我们在前面已经使用，例如归并排序的实现，同样经历了实现分而治之的三个步骤：\n\n- 分解：把数组从中间一分为二\n- 解决：递归地对两个子数组进行归并排序\n\n- 合并：将两个字数组合并称有序数组\n\n同样关于快速排序的实现，亦如此：\n\n- 分：选基准，按基准把数组分成两个字数组\n- 解：递归地对两个字数组进行快速排序\n- 合：对两个字数组进行合并\n\n同样二分搜索也能使用分而治之的思想去实现，代码如下：\n\n```js\nfunction binarySearch(arr,l,r,target){\n    if(l> r){\n        return -1;\n    }\n    let mid = l + Math.floor((r-l)/2)\n    if(arr[mid] === target){\n        return mid;\n    }else if(arr[mid] < target ){\n        return binarySearch(arr,mid + 1,r,target)\n    }else{\n        return binarySearch(arr,l,mid - 1,target)\n    }\n}\n```\n\n\n\n## 二、动态规划\n\n动态规划，同样是算法设计中的一种方法，是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的，通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法\n\n常常适用于有重叠子问题和最优子结构性质的问题\n\n简单来说，动态规划其实就是，给定一个问题，我们把它拆成一个个子问题，直到子问题可以直接解决\n\n然后呢，把子问题答案保存起来，以减少重复计算。再根据子问题答案反推，得出原问题解的一种方法。\n\n一般这些子问题很相似，可以通过函数关系式递推出来，例如斐波那契数列，我们可以得到公式：当 n 大于 2的时候，F(n) =  F(n-1) + F(n-2) ，\n\nf(10)= f(9)+f(8),f(9) = f(8) + f(7)...是重叠子问题，当n = 1、2的时候，对应的值为2，这时候就通过可以使用一个数组记录每一步计算的结果，以此类推，减少不必要的重复计算\n\n\n\n### 适用场景\n\n如果一个问题，可以把所有可能的答案穷举出来，并且穷举出来后，发现存在重叠子问题，就可以考虑使用动态规划\n\n比如一些求最值的场景，如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等，都是动态规划的经典应用场景\n\n关于动态规划题目解决的步骤，一般如下：\n\n- 描述最优解的结构\n- 递归定义最优解的值\n- 按自底向上的方式计算最优解的值\n- 由计算出的结果构造一个最优解\n\n\n## 三、区别\n\n动态规划算法与分治法类似，其基本思想也是将待求解问题分解成若干个子问题，先求解子问题，然后从这些子问题的解得到原问题的解\n\n与分治法不同的是，适合于用动态规划求解的问题，经分解得到子问题往往**不是互相独立**的，而分而治之的子问题是相互独立的\n\n若用分治法来解这类问题，则分解得到的子问题数目太多，有些子问题被重复计算了很多次\n\n如果我们能够保存已解决的子问题的答案，而在需要时再找出已求得的答案，这样就可以避免大量的重复计算，节省时间\n\n综上，可得：\n\n- 动态规划：有最优子结构和重叠子问题\n\n- 分而治之：各子问题独立\n\n\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/529408\n- https://juejin.cn/post/6951922898638471181"
  },
  {
    "path": "docs/algorithm/design2.md",
    "content": "# 面试官：说说你对贪心算法、回溯算法的理解？应用场景？\n\n\n\n ![](https://static.vue-js.com/1d49eae0-2e8e-11ec-a752-75723a64e8f5.png)\n\n## 一、贪心算法\n\n贪心算法，又称贪婪算法，是算法设计中的一种思想\n\n其期待每一个阶段都是局部最优的选择，从而达到全局最优，但是结果并不一定是最优的\n\n举个零钱兑换的例子，如果你有1元、2元、5元的钱币数张，用于兑换一定的金额，但是要求兑换的钱币张数最少\n\n如果现在你要兑换11元，按照贪心算法的思想，先选择面额最大的5元钱币进行兑换，那么就得到11 = 5 + 5 + 1 的选择，这种情况是最优的\n\n但是如果你手上钱币的面额为1、3、4，想要兑换6元，按照贪心算法的思路，我们会 6 = 4 + 1 + 1这样选择，这种情况结果就不是最优的选择\n\n从上面例子可以看到，贪心算法存在一些弊端，使用到贪心算法的场景，都会存在一个特性：\n\n一旦一个问题可以通过贪心法来解决，那么贪心法一般是解决这个问题的最好办法\n\n至于是否选择贪心算法，主要看是否有如下两大特性：\n\n-  贪心选择：当某一个问题的整体最优解可通过一系列局部的最优解的选择达到，并且每次做出的选择可以依赖以前做出的选择，但不需要依赖后面需要做出的选择\n- 最优子结构：如果一个问题的最优解包含其子问题的最优解，则此问题具备最优子结构的性质。问题的最优子结构性质是该问题是否可以用贪心算法求解的关键所在\n\n\n\n## 二、回溯算法\n\n回溯算法，也是算法设计中的一种思想，是一种渐进式寻找并构建问题解决方式的策略\n\n回溯算法会先从一个可能的工作开始解决问题，如果不行，就回溯并选择另一个动作，知道将问题解决\n\n使用回溯算法的问题，有如下特性：\n\n- 有很多路，例如一个矩阵的方向或者树的路径\n- 在这些的路里面，有死路也有生路，思路即不符合题目要求的路，生路则符合\n- 通常使用递归来模拟所有的路\n\n常见的伪代码如下：\n\n```js\nresult = []\nfunction backtrack(路径, 选择列表):\n  if 满足结束条件:\n    result.add(路径)\n  return\n\n  for 选择 of 选择列表:\n    做选择\n    backtrack(路径, 选择列表)\n    撤销选择\n```\n\n重点解决三个问题：\n\n- 路径：也就是已经做出的选择\n- 选择列表\n- 结束条件\n\n\n\n例如经典使用回溯算法为解决全排列的问题，如下：\n\n一个不含重复数字的数组 `nums` ，我们要返回其所有可能的全排列，解决这个问题的思路是：\n\n- 用递归模拟所有的情况\n- 遇到包含重复元素的情况则回溯\n- 收集到所有到达递归终点的情况，并返回、\n\n ![](https://static.vue-js.com/2a030f00-2e8e-11ec-8e64-91fdec0f05a1.png)\n\n用代码表示则如下：\n\n```js\nvar permute = function(nums) {\n    const res = [], path = [];\n    backtracking(nums, nums.length, []);\n    return res;\n    \n    function backtracking(n, k, used) {\n        if(path.length === k) {\n            res.push(Array.from(path));\n            return;\n        }\n        for (let i = 0; i < k; i++ ) {\n            if(used[i]) continue;\n            path.push(n[i]);\n            used[i] = true; // 同支\n            backtracking(n, k, used);\n            path.pop();\n            used[i] = false;\n        }\n    }\n};\n```\n\n\n\n\n\n## 三、总结\n\n前面也初步了解到分而治之、动态规划，现在再了解到贪心算法、回溯算法\n\n其中关于分而治之、动态规划、贪心策略三者的求解思路如下：\n\n ![](https://static.vue-js.com/504b5230-2e8e-11ec-8e64-91fdec0f05a1.png)\n\n其中三者对应的经典问题如下图：\n\n ![](https://static.vue-js.com/62cdc910-2e8e-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95\n- https://leetcode-cn.com/problems/permutations/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-hui-s-mfrp/\n- https://cloud.tencent.com/developer/article/1767046"
  },
  {
    "path": "docs/algorithm/graph.md",
    "content": "# 面试官：说说你对图的理解？相关操作有哪些？\n\n\n ![](https://static.vue-js.com/7876c2f0-2059-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n在计算机科学中，图是一种抽象的数据类型，在图中的数据元素通常称为结点，`V`是所有顶点的集合，`E`是所有边的集合\n\n如果两个顶点`v`,` w`，只能由`v`向`w`，而不能由`w`向`v`，那么我们就把这种情况叫做一个从 `v` 到 `w` 的有向边。`v `也被称做初始点，`w`也被称为终点。这种图就被称做有向图\n\n如果`v`和`w`是没有顺序的，从`v`到达`w`和从`w`到达`v`是完全相同的，这种图就被称为无向图\n\n图的结构比较复杂，任意两个顶点之间都可能存在联系，因此无法以数据元素在存储区中的物理位置来表示元素之间的关系\n\n常见表达图的方式有如下：\n\n- 邻接矩阵\n\n- 邻接表\n\n### 邻接矩阵\n\n通过使用一个二维数组`G[N][N]`进行表示`N`个点到`N-1`编号，通过邻接矩阵可以立刻看出两顶点之间是否存在一条边，只需要检查邻接矩阵行`i`和列`j`是否是非零值，对于无向图，邻接矩阵是对称的\n\n ![](https://static.vue-js.com/881d4300-2059-11ec-a752-75723a64e8f5.png)\n\n\n\n### 邻接表\n\n存储方式如下图所示：\n\n ![](https://static.vue-js.com/949fedd0-2059-11ec-a752-75723a64e8f5.png)\n\n在`javascript`中，可以使用`Object`进行表示，如下：\n\n```js\nconst graph = {\n  A: [2, 3, 5],\n  B: [2],\n  C: [0, 1, 3],\n  D: [0, 2],\n  E: [6],\n  F: [0, 6],\n  G: [4, 5]\n}\n```\n\n图的数据结构还可能包含和每条边相关联的数值（edge value），例如一个标号或一个数值（即权重，weight；表示花费、容量、长度等）\n\n\n\n\n\n## 二、操作\n\n关于的图的操作常见的有：\n\n- 深度优先遍历\n- 广度优先遍历\n\n\n\n首先构建一个图的邻接矩阵表示，如下面的图：\n\n ![](https://static.vue-js.com/a1311790-2059-11ec-8e64-91fdec0f05a1.png)\n\n用代码表示则如下：\n\n```js\nconst graph = {\n  0: [1, 4],\n  1: [2, 4],\n  2: [2, 3],\n  3: [],\n  4: [3],\n}\n```\n\n\n\n\n\n### 深度优先遍历\n\n也就是尽可能的往深处的搜索图的分支\n\n实现思路是，首先应该确定一个根节点，然后对根节点的没访问过的相邻节点进行深度优先遍历\n\n确定以 0 为根节点，然后进行深度遍历，然后遍历1，接着遍历 2，然后3，此时完成一条分支`0 - 1- 2- 3`的遍历，换一条分支，也就是4，4后面因为3已经遍历过了，所以就不访问了\n\n用代码表示则如下：\n\n```js\nconst visited = new Set()\nconst dfs = (n) => {\n  console.log(n)\n  visited.add(n) // 访问过添加记录\n  graph[n].forEach(c => {\n    if(!visited.has(c)){ // 判断是否访问呢过\n      dfs(c)\n    }\n  })\n}\n```\n\n\n\n### 广度优先遍历\n\n先访问离根节点最近的节点，然后进行入队操作，解决思路如下：\n\n- 新建一个队列，把根节点入队\n- 把队头出队并访问\n- 把队头的没访问过的相邻节点入队\n- 重复二、三步骤，知道队列为空\n\n用代码标识则如下：\n\n```js\nconst visited = new Set()\nconst dfs = (n) => {\n  visited.add(n)\n  const q = [n]\n  while(q.length){\n    const n = q.shift()\n    console.log(n)\n    graph[n].forEach(c => {\n      if(!visited.has(c)){\n        q.push(c)  \n        visited.add(c)\n      }\n    })\n  }\n}\n```\n\n\n\n## 三、总结\n\n通过上面的初步了解，可以看到图就是由顶点的有穷非空集合和顶点之间的边组成的集合，分成了无向图与有向图\n\n图的表达形式可以分成邻接矩阵和邻接表两种形式，在`javascript`中，则可以通过二维数组和对象的形式进行表达\n\n图实际是很复杂的，后续还可以延伸出无向图和带权图，对应如下图所示：\n\n ![](https://static.vue-js.com/b0d88200-2059-11ec-8e64-91fdec0f05a1.png)\n\n\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/%E5%9B%BE_(%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)\n- https://www.kancloud.cn/imnotdown1019/java_core_full/2159607\n"
  },
  {
    "path": "docs/algorithm/insertionSort.md",
    "content": "# 面试官：说说你对插入排序的理解？如何实现？应用场景？\n\n ![](https://static.vue-js.com/912adc10-267f-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n插入排序（Insertion Sort），一般也被称为直接插入排序。对于少量元素的排序，它是一个有效、简单的算法\n\n其主要的实现思想是将数据按照一定的顺序一个一个的插入到有序的表中，最终得到的序列就是已经排序好的数据\n\n插入排序的工作方式像许多人排序一手扑克牌，开始时，我们的左手为空并且桌子上的牌面向下\n\n然后，我们每次从桌子上拿走一张牌并将它插入左手中正确的位置，该正确位置需要从右到左将它与已在手中的每张牌进行比较\n\n例如一个无序数组 3、1、7、5、2、4、9、6，将其升序的结果则如下：\n\n一开始有序表中无数据，直接插入3\n\n从第二个数开始，插入一个元素1，然后和有序表中记录3比较，1<3，所以插入到记录 3 的左侧\n\n ![](https://static.vue-js.com/9d24f5f0-267f-11ec-a752-75723a64e8f5.png)\n\n向有序表插入记录 7 时，同有序表中记录 3 进行比较，3<7，所以插入到记录 3 的右侧\n\n ![](https://static.vue-js.com/a6a954e0-267f-11ec-8e64-91fdec0f05a1.png)\n\n向有序表中插入记录 5 时，同有序表中记录 7 进行比较，5<7，同时 5>3，所以插入到 3 和 7 中间\n\n ![](https://static.vue-js.com/b1981940-267f-11ec-8e64-91fdec0f05a1.png)\n\n照此规律，依次将无序表中的记录 4，9 和 6插入到有序表中\n\n ![](https://static.vue-js.com/bc2ed290-267f-11ec-a752-75723a64e8f5.png)\n\n## 二、如何实现\n\n将第一待排序序列第一个元素看做一个有序序列，把第二个元素到最后一个元素当成是未排序序列。\n\n从头到尾依次扫描未排序序列，将扫描到的每个元素插入有序序列的适当位置\n\n如果待插入的元素与有序序列中的某个元素相等，则将待插入元素插入到相等元素的后面\n\n![](https://www.runoob.com/wp-content/uploads/2019/03/insertionSort.gif)\n\n用代码表示则如下：\n\n```js\nfunction insertionSort(arr) {\n    const len = arr.length;\n    let preIndex, current;\n    for (let i = 1; i < len; i++) {\n        preIndex = i - 1;\n        current = arr[i];\n        while(preIndex >= 0 && arr[preIndex] > current) {\n            arr[preIndex+1] = arr[preIndex];\n            preIndex--;\n        }\n        arr[preIndex+1] = current;\n    }\n    return arr;\n}\n```\n\n在插入排序中，当待排序数组是有序时，是最优的情况，只需当前数跟前一个数比较一下就可以了，这时一共需要比较`N- 1`次，时间复杂度为`O(n)`\n\n最坏的情况是待排序数组是逆序的，此时需要比较次数最多，总次数记为：1+2+3+…+N-1，所以，插入排序最坏情况下的时间复杂度为`O(n^2)`\n\n通过上面了解，可以看到插入排序是一种稳定的排序方式\n\n\n\n## 三、应用场景\n\n插入排序时间复杂度是 O(n2)，适用于数据量不大，算法稳定性要求高，且数据局部或整体有序的数列排序\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F/7214992\n- http://data.biancheng.net/view/65.html"
  },
  {
    "path": "docs/algorithm/mergeSort.md",
    "content": "# 面试官：说说你对归并排序的理解？如何实现？应用场景？\n\n ![](https://static.vue-js.com/fa1d5720-26ac-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n归并排序（Merge Sort）是建立归并操作上的一种有效，稳定的排序算法，该算法是采用分治法的一个非常典型的应用\n\n将已有序的子序列合并，得到完全有序的序列，即先使每个子序列有序，再使子序列段间有序\n\n例如对于含有 `n` 个记录的无序表，首先默认表中每个记录各为一个有序表（只不过表的长度都为 1）\n\n然后进行两两合并，使 `n` 个有序表变为`n/2`  个长度为 2 或者 1 的有序表（例如 4 个小有序表合并为 2 个大的有序表）\n\n通过不断地进行两两合并，直到得到一个长度为 `n` 的有序表为止\n\n例如对无序表{49，38，65，97，76，13，27}进行归并排序分成了分、合两部分：\n\n如下图所示：\n\n ![](https://static.vue-js.com/05f14b60-26ad-11ec-a752-75723a64e8f5.png)\n\n归并合过程中，每次得到的新的子表本身有序，所以最终得到有序表\n\n上述分成两部分，则称为二路归并，如果分成三个部分则称为三路归并，以此类推\n\n\n\n## 二、如何实现\n\n关于归并排序的算法思路如下：\n\n- 分：把数组分成两半，再递归对子数组进行分操作，直至到一个个单独数字\n\n- 合：把两个数合成有序数组，再对有序数组进行合并操作，直到全部子数组合成一个完整的数组\n  - 合并操作可以新建一个数组，用于存放排序后的数组\n  - 比较两个有序数组的头部，较小者出队并且推入到上述新建的数组中\n  - 如果两个数组还有值，则重复上述第二步\n  - 如果只有一个数组有值，则将该数组的值出队并推入到上述新建的数组中\n\n![](https://www.runoob.com/wp-content/uploads/2019/03/mergeSort.gif)\n\n用代码表示则如下图所示：\n\n```js\nfunction mergeSort(arr) {  // 采用自上而下的递归方法\n    const len = arr.length;\n    if(len < 2) {\n        return arr;\n    }\n    let middle = Math.floor(len / 2),\n        left = arr.slice(0, middle),\n        right = arr.slice(middle);\n    return merge(mergeSort(left), mergeSort(right));\n}\n\nfunction merge(left, right)\n{\n    const result = [];\n\n    while (left.length && right.length) {\n        if (left[0] <= right[0]) {\n            result.push(left.shift());\n        } else {\n            result.push(right.shift());\n        }\n    }\n\n    while (left.length)\n        result.push(left.shift());\n\n    while (right.length)\n        result.push(right.shift());\n\n    return result;\n}\n```\n\n上述归并分成了分、合两部分，在处理分过程中递归调用两个分的操作，所花费的时间为2乘`T(n/2)`，合的操作时间复杂度则为`O(n)`，因此可以得到以下公式：\n\n总的执行时间 = 2 × 输入长度为`n/2`的`sort`函数的执行时间 + `merge`函数的执行时间`O(n)`\n\n当只有一个元素时，`T(1) = O(1)`\n\n如果对`T(n) = 2 * T(n/2) + O(n) `进行左右 / n的操作，得到 `T(n) / n = (n / 2) * T(n/2) + O(1)`\n\n现在令 `S(n) = T(n)/n`，则`S(1) = O(1)`，然后利用表达式带入得到`S(n) = S(n/2) + O(1)`\n\n所以可以得到：`S(n) = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S(n/2^k) + O(k) = S(1) + O(logn) = O(logn)`\n\n综上可得，`T(n) = n * log(n) = nlogn`\n\n关于归并排序的稳定性，在进行合并过程，在1个或2个元素时，1个元素不会交换，2个元素如果大小相等也不会交换，由此可见归并排序是稳定的排序算法\n\n\n\n## 三、应用场景\n\n在外排序中通常使用排序-归并的策略，外排序是指处理超过内存限度的数据的排序算法，通常将中间结果放在读写较慢的外存储器，如下分成两个阶段：\n\n- 排序阶段：读入能够放进内存中的数据量，将其排序输出到临时文件，一次进行，将带排序数据组织为多个有序的临时文件\n- 归并阶段：将这些临时文件组合为大的有序文件\n\n例如，使用100m内存对900m的数据进行排序，过程如下：\n\n- 读入100m数据内存，用常规方式排序\n- 将排序后的数据写入磁盘\n- 重复前两个步骤，得到9个100m的临时文件\n- 将100m的内存划分为10份，将9份为输入缓冲区，第10份为输出缓冲区\n- 进行九路归并排序，将结果输出到缓冲区\n  - 若输出缓冲区满，将数据写到目标文件，清空缓冲区\n  - 若缓冲区空，读入相应文件的下一份数据\n\n\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/1639015\n- https://chowdera.com/2021/09/20210920201630258d.html#_127\n- https://juejin.cn/post/6844904007899561998"
  },
  {
    "path": "docs/algorithm/quickSort.md",
    "content": "# 面试官：说说你对快速排序的理解？如何实现？应用场景？\n\n\n\n ![](https://static.vue-js.com/bafae570-268a-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n快速排序（Quick Sort）算法是在冒泡排序的基础上进行改进的一种算法，从名字上看就知道该排序算法的特点是快、效率高，是处理大数据最快的排序算法之一\n\n实现的基本思想是：通过一次排序将整个无序表分成相互独立的两部分，其中一部分中的数据都比另一部分中包含的数据的值小\n\n然后继续沿用此方法分别对两部分进行同样的操作，直到每一个小部分不可再分，所得到的整个序列就变成有序序列\n\n例如，对无序表49，38，65，97，76，13，27，49进行快速排序，大致过程为：\n\n- 首先从表中选取一个记录的关键字作为分割点（称为“枢轴”或者支点，一般选择第一个关键字），例如选取 49\n\n- 将表格中大于 49 个放置于 49 的右侧，小于 49 的放置于 49 的左侧，假设完成后的无序表为：{27，38，13，49，65，97，76，49}\n\n- 以 49 为支点，将整个无序表分割成了两个部分，分别为{27，38，13}和{65，97，76，49}，继续采用此种方法分别对两个子表进行排序\n\n- 前部分子表以 27 为支点，排序后的子表为{13，27，38}，此部分已经有序；后部分子表以 65 为支点，排序后的子表为{49，65，97，76}\n\n- 此时前半部分子表中的数据已完成排序；后部分子表继续以 65 为支点，将其分割为{49}和{97，76}，前者不需排序，后者排序后的结果为{76， 97}\n\n- 通过以上几步的排序，最后由子表{13，27，38}、{49}、{49}、{65}、{76，97}构成有序表：{13，27，38，49，49，65，76，97}\n\n  \n\n## 二、如何实现\n\n可以分成以下步骤：\n\n- 分区：从数组中选择任意一个基准，所有比基准小的元素放在基准的左边，比基准大的元素放到基准的右边\n- 递归：递归地对基准前后的子数组进行分区\n\n![](https://www.runoob.com/wp-content/uploads/2019/03/quickSort.gif)\n\n用代码表示则如下：\n\n```js\nfunction quickSort (arr) {\n  const rec = (arr) => {\n    if (arr.length <= 1) { return arr; }\n    const left = [];\n    const right = [];\n    const mid = arr[0]; // 基准元素\n    for (let i = 1; i < arr.length; i++){\n      if (arr[i] < mid) {\n        left.push(arr[i]);\n      } else {\n        right.push(arr[i]);\n      }\n    }\n    return [...rec(left), mid, ...rec(right)]\n  }\n  return res(arr)\n};\n```\n\n快速排序是冒泡排序的升级版，最坏情况下每一次基准元素都是数组中最小或者最大的元素，则快速排序就是冒泡排序\n\n这种情况时间复杂度就是冒泡排序的时间复杂度：`T[n] = n * (n-1) = n^2 + n`，也就是`O(n^2)`\n\n最好情况下是`O(nlogn)`，其中递归算法的时间复杂度公式：`T[n] = aT[n/b] + f(n)`，推导如下所示：\n\n ![](https://static.vue-js.com/b6019540-2b5e-11ec-8e64-91fdec0f05a1.png)\n\n关于上述代码实现的快速排序，可以看到是稳定的\n\n\n## 三、应用场景\n\n快速排序时间复杂度为`O(nlogn)`，是目前基于比较的内部排序中被认为最好的方法，当数据过大且数据杂乱无章时，则适合采用快速排序\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/369842\n- https://www.cnblogs.com/l199616j/p/10597245.html"
  },
  {
    "path": "docs/algorithm/selectionSort.md",
    "content": "# 面试官：说说你对选择排序的理解？如何实现？应用场景？\n\n ![](https://static.vue-js.com/50a05ed0-2671-11ec-a752-75723a64e8f5.png)\n\n## 一、是什么\n\n选择排序（Selection sort）是一种简单直观的排序算法，无论什么数据进去都是 `O(n²) `的时间复杂度，所以用到它的时候，数据规模越小越好\n\n其基本思想是：首先在未排序的数列中找到最小(or最大)元素，然后将其存放到数列的起始位置\n\n然后再从剩余未排序的元素中继续寻找最小(or最大)元素，然后放到已排序序列的末尾\n\n以此类推，直到所有元素均排序完毕\n\n举个例子，一个数组为 56、12、80、91、29，其排序过程如下：\n\n- 第一次遍历时，从下标为 1 的位置即 56 开始，找出关键字值最小的记录 12，同下标为 0 的关键字 56 交换位置。此时数组为 12、56、80、91、20\n\n ![](https://static.vue-js.com/60bd2050-2671-11ec-a752-75723a64e8f5.png)\n\n- 第二次遍历时，从下标为 2 的位置即 56 开始，找出最小值 20，同下标为 2 的关键字 56 互换位置，此时数组为12、20、80、91、56\n\n ![](https://static.vue-js.com/6b04cf40-2671-11ec-8e64-91fdec0f05a1.png)\n\n- 第三次遍历时，从下标为 3 的位置即 80 开始，找出最小值 56，同下标为 3 的关键字 80 互换位置，此时数组为 12、20、56、91、80\n\n ![](https://static.vue-js.com/757f4e00-2671-11ec-a752-75723a64e8f5.png)\n\n- 第四次遍历时，从下标为 4 的位置即 91 开始，找出最小是 80，同下标为 4 的关键字 91 互换位置，此时排序完成，变成有序数组\n\n ![](https://static.vue-js.com/757f4e00-2671-11ec-a752-75723a64e8f5.png)\n\n\n\n## 二、如何实现\n\n从上面可以看到，对于具有 `n` 个记录的无序表遍历 `n-1` 次，第` i` 次从无序表中第 `i` 个记录开始，找出后序关键字中最小的记录，然后放置在第 `i` 的位置上\n\n直至到从第`n`个和第`n-1`个元素中选出最小的放在第`n-1`个位置\n\n如下动画所示：\n\n![](https://www.runoob.com/wp-content/uploads/2019/03/selectionSort.gif)\n\n用代码表示则如下：\n\n```js\nfunction selectionSort(arr) {\n    var len = arr.length;\n    var minIndex, temp;\n    for (var i = 0; i < len - 1; i++) {\n        minIndex = i;\n        for (var j = i + 1; j < len; j++) {\n            if (arr[j] < arr[minIndex]) {     // 寻找最小的数\n                minIndex = j;                 // 将最小数的索引保存\n            }\n        }\n        temp = arr[i];\n        arr[i] = arr[minIndex];\n        arr[minIndex] = temp;\n    }\n    return arr;\n}\n```\n\n第一次内循环比较`N - 1`次，然后是`N-2`次，`N-3`次，……，最后一次内循环比较1次\n共比较的次数是 `(N - 1) + (N - 2) + ... + 1`，求等差数列和，得 `(N - 1 + 1)* N / 2 = N^2 / 2`，舍去最高项系数，其时间复杂度为 `O(N^2)`\n\n从上述也可以看到，选择排序是一种稳定的排序\n\n\n\n\n\n## 三、应用场景\n\n和冒泡排序一致，相比其它排序算法，这也是一个相对较高的时间复杂度，一般情况不推荐使用\n\n但是我们还是要掌握冒泡排序的思想及实现，这对于我们的算法思维是有很大帮助的\n\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F/9762418\n- https://zhuanlan.zhihu.com/p/29889599\n- http://data.biancheng.net/view/72.html"
  },
  {
    "path": "docs/algorithm/set.md",
    "content": "# 面试官：说说你对集合的理解？常见的操作有哪些？\n\n\n\n ![](https://static.vue-js.com/e3de7810-1d36-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n集合（Set），指具有某种特定性质的事物的总体，里面的每一项内容称作元素\n\n在数学中，我们经常会遇到集合的概念：\n\n- 有限集合：例如一个班集所有的同学构成的集合\n- 无限集合：例如全体自然数集合\n\n在计算机中集合道理也基本一致，具有三大特性：\n\n- 确定性：于一个给定的集合，集合中的元素是确定的。即一个元素，或者属于该集合，或者不属于该集合，两者必居其一\n- 无序性：在一个集合中，不考虑元素之间的顺序，只要元素完全相同，就认为是同一个集合\n- 互异性：集合中任意两个元素都是不同的\n\n\n## 二、操作\n\n在`ES6`中，集合本身是一个构建函数`Set`，用来生成 `Set` 数据结构，如下：\n\n```js\nconst s = new Set();\n```\n\n关于集合常见的方法有：\n\n- add()：增\n- delete()：删\n- has()：改\n- clear()：查\n\n\n\n### add()\n\n添加某个值，返回 `Set` 结构本身\n\n当添加实例中已经存在的元素，`set`不会进行处理添加\n\n```js\ns.add(1).add(2).add(2); // 2只被添加了一次\n```\n\n体现了集合的互异性特性\n\n### delete()\n\n删除某个值，返回一个布尔值，表示删除是否成功\n\n```js\ns.delete(1)\n```\n\n### has()\n\n返回一个布尔值，判断该值是否为`Set`的成员\n\n```js\ns.has(2)\n```\n\n### clear()\n\n清除所有成员，没有返回值\n\n```js\ns.clear()\n```\n\n\n\n关于多个集合常见的操作有：\n\n- 并集\n- 交集\n- 差集\n\n\n\n### 并集\n\n两个集合的共同元素，如下图所示：\n\n ![](https://static.vue-js.com/ed96df50-1d36-11ec-a752-75723a64e8f5.png)\n\n代码实现方式如下：\n\n```js\nlet a = new Set([1, 2, 3]);\nlet b = new Set([4, 3, 2]);\n\n// 并集\nlet union = new Set([...a, ...b]);\n// Set {1, 2, 3, 4}\n```\n\n\n\n\n\n### 交集\n\n两个集合`A` 和 `B`，即属于`A`又属于`B`的元素，如下图所示：\n\n ![](https://static.vue-js.com/f8a9cd80-1d36-11ec-a752-75723a64e8f5.png)\n\n用代码标识则如下：\n\n```js\nlet a = new Set([1, 2, 3]);\nlet b = new Set([4, 3, 2]);\n\n// 交集\nlet intersect = new Set([...a].filter(x => b.has(x)));\n// set {2, 3}\n```\n\n\n\n### 差集\n\n两个集合`A` 和 `B`，属于`A`的元素但不属于`B`的元素称为`A`相对于`B`的差集，如下图所示：\n\n ![](https://static.vue-js.com/0191c560-1d37-11ec-8e64-91fdec0f05a1.png)\n\n代码标识则如下：\n\n```js\nlet a = new Set([1, 2, 3]);\nlet b = new Set([4, 3, 2]);\n\n// （a 相对于 b 的）差集\nlet difference = new Set([...a].filter(x => !b.has(x)));\n// Set {1}\n```\n\n\n\n\n\n## 三、应用场景\n\n一般情况下，使用数组的概率会比集合概率高很多\n\n使用`set`集合的场景一般是借助其确定性，其本身只包含不同的元素\n\n所以，可以利用`Set`的一些原生方法轻松的完成数组去重，查找数组公共元素及不同元素等操作\n\n\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/%E5%B9%B6%E9%9B%86\n- https://zh.wikipedia.org/wiki/%E8%A1%A5%E9%9B%86"
  },
  {
    "path": "docs/algorithm/sort.md",
    "content": "# 面试官：说说常见的排序算法有哪些？区别？\n\n\n ![](https://static.vue-js.com/63eb7920-211c-11ec-a752-75723a64e8f5.png)\n\n## 一、是什么\n\n排序是程序开发中非常常见的操作，对一组任意的数据元素经过排序操作后，就可以把他们变成一组一定规则排序的有序序列\n\n排序算法属于算法中的一种，而且是覆盖范围极小的一种，彻底掌握排序算法对程序开发是有很大的帮助的\n\n对与排序算法的好坏衡量，主要是从时间复杂度、空间复杂度、稳定性\n\n时间复杂度、空间复杂度前面已经讲过，这里主要看看稳定性的定义\n\n稳定性指的是假定在待排序的记录序列中，存在多个具有相同的关键字的记录，若经过排序，这些记录的相对次序保持不变\n\n即在原序列中，r[i] = r[j]，且 r[i] 在 r[j] 之前，而在排序后的序列中，r[i] 仍在 r[j] 之前，则称这种排序算法是稳定的；否则称为不稳定的\n\n\n\n## 二、有哪些\n\n常见的算法排序算法有：\n\n- 冒泡排序\n- 选择排序\n- 插入排序\n- 归并排序\n- 快速排序\n\n### 冒泡排序\n\n一种简单直观的排序算法。它重复地走访过要排序的数列，一次比较两个元素，如果他们的顺序错误就把他们交换过来\n\n思路如下：\n\n- 比较相邻的元素，如果第一个比第二个大，就交换它们两个\n\n- 对每一对相邻元素作同样的工作，从开始第一对到结尾的最后一对，这样在最后的元素应该会是最大的数\n- 针对所有的元素重复以上的步骤，除了最后一个\n- 重复上述步骤，直到没有任何一堆数字需要比较\n\n![](https://pic4.zhimg.com/v2-33a947c71ad62b254cab62e5364d2813_b.webp)\n\n\n\n### 选择排序\n\n选择排序是一种简单直观的排序算法，它也是一种交换排序算法\n\n无论什么数据进去都是 `O(n²) `的时间复杂度。所以用到它的时候，数据规模越小越好\n\n唯一的好处是不占用额外的内存存储空间\n\n思路如下：\n\n- 在未排序序列中找到最小（大）元素，存放到排序序列的起始位置\n- 从剩余未排序元素中继续寻找最小（大）元素，然后放到已排序序列的末尾。\n- 重复第二步，直到所有元素均排序完毕\n\n![](https://pic1.zhimg.com/v2-1c7e20f306ddc02eb4e3a50fa7817ff4_b.webp)\n\n\n\n\n\n### 插入排序\n\n插入排序是一种简单直观的排序算法\n\n它的工作原理是通过构建有序序列，对于未排序数据，在已排序序列中从后向前扫描，找到相应位置并插入\n\n解决思路如下：\n\n- 把待排序的数组分成已排序和未排序两部分，初始的时候把第一个元素认为是已排好序的\n- 从第二个元素开始，在已排好序的子数组中寻找到该元素合适的位置并插入该位置（如果待插入的元素与有序序列中的某个元素相等，则将待插入元素插入到相等元素的后面。）\n- 重复上述过程直到最后一个元素被插入有序子数组中\n\n![](https://pic3.zhimg.com/v2-91b76e8e4dab9b0cad9a017d7dd431e2_b.webp)\n\n\n\n### 归并排序\n\n归并排序是建立在归并操作上的一种有效的排序算法\n\n该算法是采用分治法的一个非常典型的应用\n\n将已有序的子序列合并，得到完全有序的序列，即先使每个子序列有序，再使子序列段间有序\n\n解决思路如下：\n\n- 申请空间，使其大小为两个已经排序序列之和，该空间用来存放合并后的序列\n- 设定两个指针，最初位置分别为两个已经排序序列的起始位置\n- 比较两个指针所指向的元素，选择相对小的元素放入到合并空间，并移动指针到下一位置\n- 重复步骤3直到某一指针到达序列尾\n- 将另一序列剩下的所有元素直接复制到合并序列尾\n\n![](https://pic3.zhimg.com/v2-cdda3f11c6efbc01577f5c29a9066772_b.jpg)\n\n\n\n### 快速排序\n\n快速排序是对冒泡排序算法的一种改进，基本思想是通过一趟排序将要排序的数据分割成独立的两部分，其中一部分的所有数据比另一部分的所有数据要小\n\n再按这种方法对这两部分数据分别进行快速排序，整个排序过程可以递归进行，使整个数据变成有序序列\n\n解决思路如下：\n\n- 从数列中挑出一个元素，称为\"基准\"（pivot）\n- 重新排序数列，所有比基准值小的元素摆放在基准前面，所有比基准值大的元素摆在基准后面（相同的数可以到任何一边）。在这个分区结束之后，该基准就处于数列的中间位置。这个称为分区（partition）操作\n- 递归地（recursively）把小于基准值元素的子数列和大于基准值元素的子数列排序\n\n![](https://pic1.zhimg.com/v2-c411339b79f92499dcb7b5f304c826f4_b.jpg)\n\n\n\n## 三、区别\n\n除了上述的排序算法之外，还存在其他的排序算法，例如希尔排序、堆排序等等......\n\n区别如下图所示：\n\n![](https://static.vue-js.com/5c3d7b50-2131-11ec-a752-75723a64e8f5.png)\n\n\n\n## 参考文献\n\n- https://www.runoob.com/w3cnote/bubble-sort.html\n- http://www.x-lab.info/post/sort-algorithm/\n- https://zhuanlan.zhihu.com/p/42586566"
  },
  {
    "path": "docs/algorithm/stack_queue.md",
    "content": "# 面试官：说说你对栈、队列的理解？应用场景？\n\n ![](https://static.vue-js.com/bc57f530-1b99-11ec-a752-75723a64e8f5.png)\n\n## 一、栈\n\n栈（stack）又名堆栈，它是一种运算受限的线性表，限定仅在表尾进行插入和删除操作的线性表\n\n表尾这一端被称为栈顶，相反地另一端被称为栈底，向栈顶插入元素被称为进栈、入栈、压栈，从栈顶删除元素又称作出栈\n\n所以其按照先进后出的原则存储数据，先进入的数据被压入栈底，最后的数据在栈顶，需要读数据的时候从栈顶开始弹出数据，具有记忆作用\n\n关于栈的简单实现，如下：\n\n```js\nclass Stack {\n  constructor() {\n    this.items = [];\n  }\n\n  /**\n   * 添加一个（或几个）新元素到栈顶\n   * @param {*} element 新元素\n   */\n  push(element) {\n    this.items.push(element)\n  }\n\n  /**\n   * 移除栈顶的元素，同时返回被移除的元素\n   */\n  pop() {\n    return this.items.pop()\n  }\n\n  /**\n   * 返回栈顶的元素，不对栈做任何修改（这个方法不会移除栈顶的元素，仅仅返回它）\n   */\n  peek() {\n    return this.items[this.items.length - 1]\n  }\n\n  /**\n   * 如果栈里没有任何元素就返回true,否则返回false\n   */\n  isEmpty() {\n    return this.items.length === 0\n  }\n\n  /**\n   * 移除栈里的所有元素\n   */\n  clear() {\n    this.items = []\n  }\n\n  /**\n   * 返回栈里的元素个数。这个方法和数组的length属性很类似\n   */\n  size() {\n    return this.items.length\n  }\n}\n```\n\n关于栈的操作主要的方法如下：\n\n- push：入栈操作\n- pop：出栈操作\n\n\n\n\n\n## 二、队列\n\n跟栈十分相似，队列是一种特殊的线性表，特殊之处在于它只允许在表的前端（front）进行删除操作，而在表的后端（rear）进行插入操作\n\n进行插入操作的端称为队尾，进行删除操作的端称为队头，当队列中没有元素时，称为空队列\n\n在队列中插入一个队列元素称为入队，从队列中删除一个队列元素称为出队。因为队列只允许在一端插入，在另一端删除，所以只有最早进入队列的元素才能最先从队列中删除，故队列又称为先进先出\n\n简单实现一个队列的方式，如下：\n\n```js\nclass Queue {\n    constructor() {\n        this.list = []\n        this.frontIndex = 0\n        this.tailIndex = 0\n    }\n    enqueue(item) {\n        this.list[this.tailIndex++] = item\n    }\n    unqueue() {\n        const item  = this.list[this.frontIndex]\n        this.frontIndex++        \n        return item\n    }\n}\n```\n\n上述这种入队和出队操作中，头尾指针只增加不减小，致使被删元素的空间永远无法重新利用\n\n当队列中实际的元素个数远远小于向量空间的规模时，也可能由于尾指针已超越向量空间的上界而不能做入队操作，出该现象称为\"假溢\"\n\n在实际使用队列时，为了使队列空间能重复使用，往往对队列的使用方法稍加改进：\n\n无论插入或删除，一旦`rear`指针增1或`front`指针增1 时超出了所分配的队列空间，就让它指向这片连续空间的起始位置，这种队列也就是循环队列\n\n下面实现一个循环队列，如下：\n\n```js\nclass Queue {\n    constructor(size) {\n        this.size = size; // 长度需要限制, 来达到空间的利用, 代表空间的长度\n        this.list = [];\n        this.font = 0; // 指向首元素\n        this.rear = 0;  // 指向准备插入元素的位置\n    }\n    enQueue() {\n        if (this.isFull() == true) {\n            return false\n        }\n        this.rear = this.rear % this.k;\n        this._data[this.rear++] = value;\n        return true\n    }\n    deQueue() {\n        if(this.isEmpty()){\n            return false;\n        }\n        this.font++;\n        this.font = this.font % this.k;\n        return true;\n    }\n    isEmpty() {\n        return this.font == this.rear - 1;\n    }\n    isFull() {\n        return this.rear % this.k == this.font;\n    }\n}\n```\n\n上述通过求余的形式代表首尾指针增1 时超出了所分配的队列空间\n\n\n\n## 三、应用场景\n\n### 栈\n\n借助栈的先进后出的特性，可以简单实现一个逆序数处的功能，首先把所有元素依次入栈，然后把所有元素出栈并输出\n\n包括编译器的在对输入的语法进行分析的时候，例如`\"()\"`、`\"{}\"`、`\"[]\"`这些成对出现的符号，借助栈的特性，凡是遇到括号的前半部分，即把这个元素入栈，凡是遇到括号的后半部分就比对栈顶元素是否该元素相匹配，如果匹配，则前半部分出栈，否则就是匹配出错\n\n包括函数调用和递归的时候，每调用一个函数，底层都会进行入栈操作，出栈则返回函数的返回值\n\n生活中的例子，可以把乒乓球盒比喻成一个堆栈，球一个一个放进去（入栈），最先放进去的要等其后面的全部拿出来后才能出来（出栈），这种就是典型的先进后出模型\n\n### 队列\n\n当我们需要按照一定的顺序来处理数据，而该数据的数据量在不断地变化的时候，则需要队列来帮助解题\n\n队列的使用广泛应用在广度优先搜索种，例如层次遍历一个二叉树的节点值（后续将到）\n\n生活中的例子，排队买票，排在队头的永远先处理，后面的必须等到前面的全部处理完毕再进行处理，这也是典型的先进先出模型\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E6%A0%88/12808149\n- https://baike.baidu.com/item/%E9%98%9F%E5%88%97/14580481\n"
  },
  {
    "path": "docs/algorithm/structure.md",
    "content": "# 面试官：说说你对数据结构的理解？有哪些？区别？\n\n ![](https://static.vue-js.com/3d87b540-1aa6-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n数据结构是计算机存储、组织数据的方式，是指相互之间存在一种或多种特定关系的数据元素的集合\n\n前面讲到，一个程序 = 算法 + 数据结构，数据结构是实现算法的基础，选择合适的数据结构可以带来更高的运行或者存储效率\n\n数据元素相互之间的关系称为结构，根据数据元素之间关系的不同特性，通常有如下四类基本的结构：\n\n- 集合结构：该结构的数据元素间的关系是“属于同一个集合”\n- 线性结构：该结构的数据元素之间存在着一对一的关系\n- 树型结构：该结构的数据元素之间存在着一对多的关系\n- 图形结构：该结构的数据元素之间存在着多对多的关系，也称网状结构\n\n由于数据结构种类太多，逻辑结构可以再分成为：\n\n- 线性结构：有序数据元素的集合，其中数据元素之间的关系是一对一的关系，除了第一个和最后一个数据元素之外，其它数据元素都是首尾相接的\n- 非线性结构：各个数据元素不再保持在一个线性序列中，每个数据元素可能与零个或者多个其他数据元素发生关联\n\n![](https://static.vue-js.com/9aedc5d0-1aa6-11ec-8e64-91fdec0f05a1.png)\n\n## 二、有哪些\n\n常见的数据结构有如下：\n\n- 数组\n- 栈\n- 队列\n- 链表\n- 树\n- 图\n- 堆\n- 散列表\n\n\n### 数组\n\n在程序设计中，为了处理方便， 一般情况把具有相同类型的若干变量按有序的形式组织起来，这些按序排列的同类数据元素的集合称为数组\n\n\n### 栈\n\n一种特殊的线性表，只能在某一端插入和删除的特殊线性表，按照先进后出的特性存储数据\n\n先进入的数据被压入栈底，最后的数据在栈顶，需要读数据的时候从栈顶开始弹出数据\n\n\n\n### 队列\n\n跟栈基本一致，也是一种特殊的线性表，其特性是先进先出，只允许在表的前端进行删除操作，而在表的后端进行插入操作\n\n\n\n### 链表\n\n是一种物理存储单元上非连续、非顺序的存储结构，数据元素的逻辑顺序是通过链表中的指针链接次序实现的\n\n链表由一系列结点（链表中每一个元素称为结点）组成，结点可以在运行时动态生成\n\n一般情况，每个结点包括两个部分：一个是存储数据元素的数据域，另一个是存储下一个结点地址的指针域\n\n\n\n### 树\n\n树是典型的非线性结构，在树的结构中，有且仅有一个根结点，该结点没有前驱结点。在树结构中的其他结点都有且仅有一个前驱结点，而且可以有两个以上的后继结点\n\n\n\n### 图\n\n一种非线性结构。在图结结构中，数据结点一般称为顶点，而边是顶点的有序偶对。如果两个顶点之间存在一条边，那么就表示这两个顶点具有相邻关系\n\n\n\n### 堆\n\n堆是一种特殊的树形数据结构，每个结点都有一个值，特点是根结点的值最小（或最大），且根结点的两个子树也是一个堆\n\n\n\n### 散列表\n\n若结构中存在关键字和`K`相等的记录，则必定在`f(K)`的存储位置上，不需比较便可直接取得所查记录\n\n\n\n## 三、区别\n\n上述的数据结构，之前的区别可以分成线性结构和非线性结构：\n\n- 线性结构有：数组、栈、队列、链表等\n- 非线性结构有：树、图、堆等\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84\n- https://baike.baidu.com/item/数据结构/1450\n"
  },
  {
    "path": "docs/algorithm/time_space.md",
    "content": "# 面试官：说说你对算法中时间复杂度，空间复杂度的理解？如何计算？\n\n\n\n ![](https://static.vue-js.com/07fd4050-16fc-11ec-a752-75723a64e8f5.png)\n\n## 一、前言\n\n算法（Algorithm）是指用来操作数据、解决程序问题的一组方法。对于同一个问题，使用不同的算法，也许最终得到的结果是一样的，但在过程中消耗的资源和时间却会有很大的区别\n\n衡量不同算法之间的优劣主要是通过**时间**和**空间**两个维度去考量：\n\n- 时间维度：是指执行当前算法所消耗的时间，我们通常用「时间复杂度」来描述。\n- 空间维度：是指执行当前算法需要占用多少内存空间，我们通常用「空间复杂度」来描述\n\n通常会遇到一种情况，时间和空间维度不能够兼顾，需要在两者之间取得一个平衡点是我们需要考虑的\n\n一个算法通常存在最好、平均、最坏三种情况，我们一般关注的是最坏情况\n\n最坏情况是算法运行时间的上界，对于某些算法来说，最坏情况出现的比较频繁，也意味着平均情况和最坏情况一样差\n\n\n\n## 二、时间复杂度\n\n时间复杂度是指执行这个算法所需要的计算工作量，其复杂度反映了程序执行时间「随输入规模增长而增长的量级」，在很大程度上能很好地反映出算法的优劣与否\n\n一个算法花费的时间与算法中语句的「执行次数成正比」，执行次数越多，花费的时间就越多\n\n算法的复杂度通常用大O符号表述，定义为`T(n) = O(f(n))`，常见的时间复杂度有：O(1)常数型、O(log n)对数型、O(n)线性型、O(nlogn)线性对数型、O(n^2)平方型、O(n^3)立方型、O(n^k)k次方型、O(2^n)指数型，如下图所示：\n\n ![](https://static.vue-js.com/33d5ebf0-16fc-11ec-8e64-91fdec0f05a1.png)\n\n从上述可以看到，随着问题规模`n`的不断增大，上述时间复杂度不断增大，算法的执行效率越低，由小到大排序如下：\n\n```js\nΟ(1)＜Ο(log n)＜Ο(n)＜Ο(nlog n)＜Ο(n2)＜Ο(n3)＜…＜Ο(2^n)＜Ο(n!)\n```\n\n注意的是，算法复杂度只是描述算法的增长趋势，并不能说一个算法一定比另外一个算法高效，如果常数项过大的时候也会导致算法的执行时间变长\n\n关于如何计算时间复杂度，可以看看如下简单例子：\n\n```js\nfunction process(n) {\n  let a = 1\n  let b = 2\n  let sum = a + b\n  for(let i = 0; i < n; i++) {\n    sum += i\n  }\n  return sum\n}\n```\n\n该函数算法需要执行的运算次数用输入大小`n`的函数表示，即 `T(n) = 2 + n  + 1`，那么时间复杂度为`O(n + 3)`，又因为时间复杂度只关注最高数量级，且与之系数也没有关系，因此上述的时间复杂度为`O(n)`\n\n又比如下面的例子：\n\n```js\nfunction process(n) {\n let count = 0\n  for(let i = 0; i < n; i++){\n    for(let i = 0; i < n; i++){\n      count += 1\n    }\n  }\n}\n```\n\n循环里面嵌套循环，外面的循环执行一次，里面的循环执行`n`次，因此时间复杂度为 `O(n*n*1 + 2) = O(n^2)`\n\n对于顺序执行的语句，总的时间复杂度等于其中最大的时间复杂度，如下：\n\n```js\nfunction process(n) {\n  let sum = 0\n  for(let i = 0; i < n; i++) {\n    sum += i\n  }\n  for(let i = 0; i < n; i++){\n    for(let i = 0; i < n; i++){\n      sum += 1\n    }\n  }\n  return sum\n}\n```\n\n上述第一部分复杂度为`O(n)`，第二部分复杂度为`O(n^2)`，总复杂度为`max(O(n^2), O(n)) = O(n^2)`\n\n又如下一个例子：\n\n```js\nfunction process(n) {\n  let i = 1; // ①\n  while (i <= n) {\n     i = i * 2; // ②\n  }\n}\n```\n\n循环语句中以2的倍数来逼近`n`，每次都乘以2。如果用公式表示就是1 *  2 * 2 * 2 … * 2 <=n，也就是说2的`x`次方小于等于`n`时会执行循环体，记作`2^x <= n`，于是得出`x<=logn`\n\n因此循环在执行`logn`次之后，便结束，因此时间复杂度为`O(logn)`\n\n同理，如果一个`O(n)`循环里面嵌套`O(logn)`的循环，则时间复杂度为`O(nlogn)`，像`O(n^3)`无非也就是嵌套了三层`O(n)`循环\n\n\n\n## 三、空间复杂度\n\n空间复杂度主要指执行算法所需内存的大小，用于对程序运行过程中所需要的临时存储空间的度量\n\n除了需要存储空间、指令、常数、变量和输入数据外，还包括对数据进行操作的工作单元和存储计算所需信息的辅助空间\n\n下面给出空间复杂度为`O(1)`的示例，如下\n\n```js\nlet a = 1\nlet b = 2\nlet c = 3\n```\n\n上述代码的临时空间不会随着`n`的变化而变化，因此空间复杂度为`O(1)`\n\n```js\nlet arr []\nfor(i=1; i<=n; ++i){\n  arr.push(i)\n}\n```\n\n上述可以看到，随着`n`的增加，数组的占用的内存空间越大\n\n通常来说，只要算法不涉及到动态分配的空间，以及递归、栈所需的空间，空间复杂度通常为`O(1)`，一个一维数组`a[n]`，空间复杂度`O(n)`，二维数组为`O(n^2)`\n\n\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844904167824162823#heading-7\n\n- https://zhuanlan.zhihu.com/p/50479555\n\n- https://cloud.tencent.com/developer/article/1769988\n"
  },
  {
    "path": "docs/algorithm/tree.md",
    "content": "# 面试官：说说你对树的理解？相关的操作有哪些？\n\n\n\n ![](https://static.vue-js.com/5a7616f0-1dfe-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n在计算机领域，树形数据结构是一类重要的非线性数据结构，可以表示数据之间一对多的关系。以树与二叉树最为常用，直观看来，树是以分支关系定义的层次结构\n\n二叉树满足以下两个条件：\n\n- 本身是有序树\n- 树中包含的各个结点的不能超过 2，即只能是 0、1 或者 2\n\n如下图，左侧的为二叉树，而右侧的因为头结点的子结点超过2，因此不属于二叉树：\n\n ![](https://static.vue-js.com/66758800-1dfe-11ec-a752-75723a64e8f5.png)\n\n同时，二叉树可以继续进行分类，分成了满二叉树和完成二叉树：\n\n- 满二叉树：如果二叉树中除了叶子结点，每个结点的度都为 2\n\n ![](https://static.vue-js.com/759db050-1dfe-11ec-a752-75723a64e8f5.png)\n\n- 完成二叉树：如果二叉树中除去最后一层节点为满二叉树，且最后一层的结点依次从左到右分布\n\n ![](https://static.vue-js.com/84ae31f0-1dfe-11ec-8e64-91fdec0f05a1.png)\n\n## 二、操作\n\n关于二叉树的遍历，常见的有：\n\n- 前序遍历\n- 中序遍历\n- 后序遍历\n\n- 层序遍历\n\n\n\n\n\n### 前序遍历\n\n前序遍历的实现思想是：\n\n- 访问根节点\n- 访问当前节点的左子树\n- 若当前节点无左子树，则访问当前节点的右子\n\n根据遍历特性，递归版本用代码表示则如下：\n\n```js\nconst preOrder = (root) => {\n  if(!root){ return }\n  console.log(root)\n  preOrder(root.left)\n  preOrder(root.right)\n}\n```\n\n如果不使用递归版本，可以借助栈先进后出的特性实现，先将根节点压入栈，再分别压入右节点和左节点，直到栈中没有元素，如下：\n\n```js\nconst preOrder = (root) => {\n  if(!root){ return }\n  const stack = [root]\n  while (stack.length) {\n    const n = stack.pop()\n    console.log(n.val)\n    if (n.right) {\n      stack.push(n.right)\n    }\n    if (n.left) {\n      stack.push(n.left)\n    }\n  }\n}\n```\n\n\n\n\n\n### 中序遍历\n\n前序遍历的实现思想是：\n\n- 访问当前节点的左子树\n- 访问根节点\n- 访问当前节点的右子\n\n递归版本很好理解，用代码表示则如下：\n\n```js\nconst inOrder = (root) => {\n  if (!root) { return }\n  inOrder(root.left)\n  console.log(root.val)\n  inOrder(root.right)\n}\n```\n\n非递归版本也是借助栈先进后出的特性，可以一直首先一直压入节点的左元素，当左节点没有后，才开始进行出栈操作，压入右节点，然后有依次压入左节点，如下：\n\n```js\nconst inOrder = (root) => {\n  if (!root) { return }\n  const stack = [root]\n  let p = root\n  while(stack.length || p){\n    while (p) {\n      stack.push(p)\n      p = p.left\n    }\n    const n = stack.pop()\n    console.log(n.val)\n    p = n.right\n  }\n}\n```\n\n\n\n\n\n### 后序遍历\n\n前序遍历的实现思想是：\n\n- 访问当前节点的左子树\n- 访问当前节点的右子\n- 访问根节点\n\n递归版本，用代码表示则如下：\n\n```js\nconst postOrder = (root) => {\n  if (!root) { return }\n  postOrder(root.left)\n  postOrder(root.right)\n  console.log(n.val)\n }\n```\n\n后序遍历非递归版本实际根全序遍历是逆序关系，可以再多创建一个栈用来进行输出，如下：\n\n```js\nconst preOrder = (root) => {\n  if(!root){ return }\n  const stack = [root]\n  const outPut = []\n  while (stack.length) {\n    const n = stack.pop()\n    outPut.push(n.val)\n    if (n.right) {\n      stack.push(n.right)\n    }\n    if (n.left) {\n      stack.push(n.left)\n    }\n  }\n  while (outPut.length) {\n    const n = outPut.pop()\n    console.log(n.val)\n  }\n}\n```\n\n\n\n\n\n### 层序遍历\n\n按照二叉树中的层次从左到右依次遍历每层中的结点\n\n借助队列先进先出的特性，从树的根结点开始，依次将其左孩子和右孩子入队。而后每次队列中一个结点出队，都将其左孩子和右孩子入队，直到树中所有结点都出队，出队结点的先后顺序就是层次遍历的最终结果\n\n用代码表示则如下：\n\n```js\nconst levelOrder = (root) => {\n    if (!root) { return [] }\n    const queue = [[root, 0]]\n    const res = []\n    while (queue.length) {\n        const n = queue.shift()\n        const [node, leval] = n\n        if (!res[leval]) {\n            res[leval] = [node.val]\n        } else {\n            res[leval].push(node.val)\n        }\n        if (node.left) { queue.push([node.left, leval + 1]) }\n        if (node.right) { queue.push([node.right, leval + 1]) }\n    }\n    return res\n};\n```\n\n\n\n\n\n## 三、总结\n\n树是一个非常重要的非线性结构，其中二叉树以二叉树最常见，二叉树的遍历方式可以分成前序遍历、中序遍历、后序遍历\n\n同时，二叉树又分成了完成二叉树和满二叉树\n\n\n## 参考文献\n\n- https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91\n- http://data.biancheng.net/view/27.html"
  },
  {
    "path": "docs/applet/WebView_jscore.md",
    "content": "-  # 面试官：说说微信小程序的实现原理？\n\n  \n\n   ![](https://static.vue-js.com/4407cb60-3722-11ec-a752-75723a64e8f5.png)\n\n  ## 一、背景\n\n  网页开发，渲染线程和脚本是互斥的，这也是为什么长时间的脚本运行可能会导致页面失去响应的原因，本质就是我们常说的 `JS` 是单线程的\n\n  而在小程序中，选择了 `Hybrid` 的渲染方式，将视图层和逻辑层是分开的，双线程同时运行，视图层的界面使用 `WebView` 进行渲染，逻辑层运行在 `JSCore` 中\n\n   ![](https://static.vue-js.com/4e322e50-3722-11ec-8e64-91fdec0f05a1.png)\n\n  - 渲染层：界面渲染相关的任务全都在 WebView 线程里执行。一个小程序存在多个界面，所以渲染层存在多个 WebView 线程\n  - 逻辑层：采用 JsCore 线程运行 JS 脚本，在这个环境下执行的都是有关小程序业务逻辑的代码\n\n  \n\n  ## 二、通信\n\n  小程序在渲染层，宿主环境会把`wxml`转化成对应的`JS`对象\n\n  在逻辑层发生数据变更的时候，通过宿主环境提供的`setData`方法把数据从逻辑层传递到渲染层，再经过对比前后差异，把差异应用在原来的`Dom`树上，渲染出正确的视图\n\n   ![](https://static.vue-js.com/5948ed10-3722-11ec-a752-75723a64e8f5.png)\n\n  当视图存在交互的时候，例如用户点击你界面上某个按钮，这类反馈应该通知给开发者的逻辑层，需要将对应的处理状态呈现给用户\n\n  对于事件的分发处理，微信进行了特殊的处理，将所有的事件拦截后，丢到逻辑层交给`JavaScript`进行处理\n\n   ![](https://static.vue-js.com/61f9f670-3722-11ec-a752-75723a64e8f5.png)\n\n  由于小程序是基于双线程的，也就是任何在视图层和逻辑层之间的数据传递都是线程间的通信，会有一定的延时，因此在小程序中，页面更新成了异步操作\n\n  异步会使得各部分的运行时序变得复杂一些，比如在渲染首屏的时候，逻辑层与渲染层会同时开始初始化工作，但是渲染层需要有逻辑层的数据才能把界面渲染出来\n\n  如果渲染层初始化工作较快完成，就要等逻辑层的指令才能进行下一步工作\n\n  因此逻辑层与渲染层需要有一定的机制保证时序正确，在每个小程序页面的生命周期中，存在着若干次页面数据通信\n\n   ![](https://static.vue-js.com/6cb798b0-3722-11ec-a752-75723a64e8f5.png)\n\n  ## 三、运行机制\n\n  小程序启动运行两种情况：\n\n  - 冷启动（重新开始）：用户首次打开或者小程序被微信主动销毁后再次打开的情况，此时小程序需要重新加载启动，即为冷启动\n  - 热启动：用户已经打开过小程序，然后在一定时间内再次打开该小程序，此时无需重新启动，只需要将后台态的小程序切换到前台，这个过程就是热启动\n\n  #### 需要注意：\n  >  1.小程序没有重启的概念   \n  >  2.当小程序进入后台，客户端会维持一段时间的运行状态，超过一定时间后会被微信主动销毁   \n  >  3.短时间内收到系统两次以上内存警告，也会对小程序进行销毁，这也就为什么一旦页面内存溢出，页面会奔溃的本质原因了\n\n   ![](https://static.vue-js.com/968c8510-3722-11ec-a752-75723a64e8f5.png)\n\n  \n\n  开发者在后台发布新版本之后，无法立刻影响到所有现网用户，但最差情况下，也在发布之后 24 小时之内下发新版本信息到用户\n\n  每次冷启动时，都会检查是否有更新版本，如果发现有新版本，将会异步下载新版本的代码包，并同时用客户端本地的包进行启动，即新版本的小程序需要等下一次冷启动才会应用上\n\n  \n\n  ## 参考文献\n\n  - https://developers.weixin.qq.com/community/develop/article/doc/0008a4c4f28f30fe3eb863b2750813\n  - https://juejin.cn/post/6976805521407868958#heading-5\n  - https://juejin.cn/post/6844903805675388942\n  - https://juejin.cn/post/6844903999863259144#heading-1"
  },
  {
    "path": "docs/applet/applet.md",
    "content": "# 面试官：说说你对微信小程序的理解？优缺点？\n\n ![](https://static.vue-js.com/be367c80-300e-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n2017年，微信正式推出了小程序，允许外部开发者在微信内部运行自己的代码，开展业务\n\n截至目前，小程序已经成为国内前端的一个重要业务，跟 `Web` 和手机 `App` 有着同等的重要性\n\n ![](https://static.vue-js.com/ce751de0-300e-11ec-8e64-91fdec0f05a1.png)\n\n小程序是一种不需要下载安装即可使用的应用，它实现了应用“触手可及”的梦想，用户扫一扫或者搜一下即可打开应用\n\n也体现了“用完即走”的理念，用户不用关心是否安装太多应用的问题。应用将无处不在，随时可用，但又无需安装卸载\n\n注意的是，除了微信小程序，还有百度小程序、微信小程序、支付宝小程序、抖音小程序，都是每个平台自己开发的，都是有针对性平台的应用程序\n\n\n\n## 二、背景\n\n⼩程序并⾮凭空冒出来的⼀个概念，当微信中的 `WebView` 逐渐成为移动 `Web`的⼀个重要⼊⼝时，微信就有相关的 `JS-SDK`\n\n`JS-SDK` 解决了移动⽹⻚能⼒不⾜的问题，通过暴露微信的接⼝使得 `Web` 开发者能够拥有更多的能⼒，然⽽在更多的能⼒之外，`JS-SDK`的模式并没有解决使⽤移动⽹⻚遇到的体验不良的问题\n\n因此需要设计⼀个⽐较好的系统，使得所有开发者在微信中都能获得⽐较好的体验：\n- 快速的加载\n- 更强⼤的能⼒\n- 原⽣的体验\n- 易⽤且安全的微信数据开放\n- ⾼效和简单的开发\n\n这些是`JS-SDK`做不到的，需要设计一个全新的小程序系统\n\n对于小程序的开发，提供一个简单、高效的应用开发框架和丰富的组件及`API`，帮助开发者开发出具有原生体验的服务\n\n其中相比`H5`，小程序与其的区别有如下：\n- 运⾏环境：⼩程序基于浏览器内核重构的内置解析器\n- 系统权限：⼩程序能获得更多的系统权限，如⽹络通信状态、数据缓存能⼒等\n- 渲染机制：⼩程序的逻辑层和渲染层是分开的\n\n小程序可以视为只能用微信打开和浏览的`H5`，小程序和网页的技术模型是一样的，用到的 `JavaScript` 语言和 `CSS` 样式也是一样的，只是网页的 `HTML` 标签被稍微修改成了 `WXML` 标签\n\n因此可以说，小程序页面本质上就是网页\n\n其中关于微信小程序的实现原理，我们在后面的文章讲到\n\n\n\n## 三、优缺点\n\n优点：\n- 随搜随用，用完即走：使得小程序可以代替许多APP，或是做APP的整体嫁接，或是作为阉割版功能的承载体\n- 流量大，易接受：小程序借助自身平台更加容易引入更多的流量\n- 安全\n- 开发门槛低\n- 降低兼容性限制\n\n\n\n缺点：\n\n- 用户留存：及相关数据显示，小程序的平均次日留存在13%左右，但是双周留存骤降到仅有1%\n- 体积限制：微信小程序只有2M的大小，这样导致无法开发大型一些的小程序\n- 受控微信：比起APP，尤其是安卓版的高自由度，小程序要面对很多来自微信的限制，从功能接口，甚至到类别内容，都要接受微信的管控\n\n\n\n## 参考文献\n\n- https://developers.weixin.qq.com/miniprogram/dev/framework/\n- https://www.zhihu.com/question/263816362"
  },
  {
    "path": "docs/applet/lifecycle.md",
    "content": "# 面试官：说说微信小程序的生命周期函数有哪些？\n\n\n\n ![](https://static.vue-js.com/1df64890-30e0-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n跟`vue`、`react`框架一样，微信小程序框架也存在生命周期，实质也是一堆会在特定时期执行的函数\n\n小程序中，生命周期主要分成了三部分：\n\n- 应用的生命周期\n- 页面的生命周期\n- 组件的生命周期\n\n### 应用的生命周期\n\n小程序的生命周期函数是在`app.js`里面调用的，通过`App(Object)`函数用来注册一个小程序，指定其小程序的生命周期回调\n\n\n\n### 页面的生命周期\n\n页面生命周期函数就是当你每进入/切换到一个新的页面的时候，就会调用的生命周期函数，同样通过`App(Object)`函数用来注册一个页面\n\n\n\n### 组件的生命周期\n\n组件的生命周期，指的是组件自身的一些函数，这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发，通过`Component(Object)`进行注册组件\n\n\n\n\n\n## 二、有哪些\n\n### 应用的生命周期\n\n| 生命周期               | 说明                                    |\n| ---------------------- | --------------------------------------- |\n| onLaunch               | 小程序初始化完成时触发，全局只触发一次  |\n| onShow                 | 小程序启动，或从后台进入前台显示时触发  |\n| onHide                 | 小程序从前台进入后台时触发              |\n| onError                | 小程序发生脚本错误或 API 调用报错时触发 |\n| onPageNotFound         | 小程序要打开的页面不存在时触发          |\n| onUnhandledRejection() | 小程序有未处理的 Promise 拒绝时触发     |\n| onThemeChange          | 系统切换主题时触发                      |\n\n\n\n\n\n\n\n### 页面的生命周期\n\n| 生命周期 | 说明                              | 作用                           |\n| -------- | --------------------------------- | ------------------------------ |\n| onLoad   | 生命周期回调—监听页面加载         | 发送请求获取数据               |\n| onShow   | 生命周期回调—监听页面显示         | 请求数据                       |\n| onReady  | 生命周期回调—监听页面初次渲染完成 | 获取页面元素（少用）           |\n| onHide   | 生命周期回调—监听页面隐藏         | 终止任务，如定时器或者播放音乐 |\n| onUnload | 生命周期回调—监听页面卸载         | 终止任务                       |\n\n\n\n\n\n### 组件的生命周期\n\n| 生命周期 | 说明                              |\n| -------- | --------------------------------- |\n| created  | 生命周期回调—监听页面加载         |\n| attached | 生命周期回调—监听页面显示         |\n| ready    | 生命周期回调—监听页面初次渲染完成 |\n| moved    | 生命周期回调—监听页面隐藏         |\n| detached | 生命周期回调—监听页面卸载         |\n| error    | 每当组件方法抛出错误时执行        |\n\n注意的是：\n\n- 组件实例刚刚被创建好时， created 生命周期被触发，此时，组件数据 this.data 就是在 Component  构造器中定义的数据 data ， 此时不能调用 setData\n- 在组件完全初始化完毕、进入页面节点树后， attached 生命周期被触发。此时， this.data 已被初始化为组件的当前值。这个生命周期很有用，绝大多数初始化工作可以在这个时机进行\n- 在组件离开页面节点树后， detached 生命周期被触发。退出一个页面时，如果组件还在页面节点树中，则  detached 会被触发\n\n还有一些特殊的生命周期，它们并非与组件有很强的关联，但有时组件需要获知，以便组件内部处理，这样的生命周期称为“组件所在页面的生命周期”，在 `pageLifetimes` 定义段中定义，如下：\n\n| 生命周期 | 说明                       |\n| -------- | -------------------------- |\n| show     | 组件所在的页面被展示时执行 |\n| hide     | 组件所在的页面被隐藏时执行 |\n\n代码如下：\n\n```js\nComponent({\n  pageLifetimes: {\n    show: function() {\n      // 页面被展示\n    },\n    hide: function() {\n      // 页面被隐藏\n    },\n  }\n})\n```\n\n\n\n\n\n\n## 三、执行过程\n\n### 应⽤的⽣命周期执行过程：\n\n-  ⽤户⾸次打开⼩程序，触发 onLaunch（全局只触发⼀次）\n\n-  ⼩程序初始化完成后，触发onShow⽅法，监听⼩程序显示\n\n-  ⼩程序从前台进⼊后台，触发 onHide⽅法\n\n-  ⼩程序从后台进⼊前台显示，触发 onShow⽅法\n\n-  ⼩程序后台运⾏⼀定时间，或系统资源占⽤过⾼，会被销毁\n\n\n\n### ⻚⾯⽣命周期的执行过程：\n\n- ⼩程序注册完成后，加载⻚⾯，触发onLoad⽅法\n- ⻚⾯载⼊后触发onShow⽅法，显示⻚⾯\n- ⾸次显示⻚⾯，会触发onReady⽅法，渲染⻚⾯元素和样式，⼀个⻚⾯只会调⽤⼀次\n- 当⼩程序后台运⾏或跳转到其他⻚⾯时，触发onHide⽅法\n- 当⼩程序有后台进⼊到前台运⾏或重新进⼊⻚⾯时，触发onShow⽅法\n- 当使⽤重定向⽅法 wx.redirectTo() 或关闭当前⻚返回上⼀⻚wx.navigateBack()，触发onUnload\n\n\n\n当存在也应用生命周期和页面周期的时候，相关的执行顺序如下：\n\n- 打开小程序：(App)onLaunch --> (App)onShow --> (Pages)onLoad --> (Pages)onShow --> (pages)onRead\n\n- 进入下一个页面：(Pages)onHide --> (Next)onLoad --> (Next)onShow --> (Next)onReady\n\n- 返回上一个页面：(curr)onUnload --> (pre)onShow\n\n- 离开小程序：(App)onHide\n\n- 再次进入：小程序未销毁 --> (App)onShow(执行上面的顺序），小程序被销毁，（App)onLaunch重新开始执行.\n\n\n\n## 参考文献\n\n- https://developers.weixin.qq.com/miniprogram/dev/reference/api/App.html#onLaunch-Object-object\n- https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#onLoad-Object-query\n- https://developers.weixin.qq.com/miniprogram/dev/reference/api/App.html#onLaunch-Object-object"
  },
  {
    "path": "docs/applet/login.md",
    "content": "# 面试官：说说微信小程序的登录流程？\n\n\n\n ![](https://static.vue-js.com/aa3ccbd0-3428-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、背景\n\n传统的`web`开发实现登陆功能，一般的做法是输入账号密码、或者输入手机号及短信验证码进行登录\n\n服务端校验用户信息通过之后，下发一个代表登录态的 `token` 给客户端，以便进行后续的交互,每当`token`过期，用户都需要重新登录\n\n而在微信小程序中，可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识，快速建立小程序内的用户体系，从而实现登陆功能\n\n实现小程序用户体系主要涉及到`openid`和`code`的概念：\n\n- 调用`wx.login()`方法会生成`code`，将`code`作为参数传递给微信服务器指定接口，就可以获取用户的`openid`\n\n对于每个小程序，微信都会将用户的微信`ID`映射出一个小程序 `openid`，作为这个用户在这个小程序的唯一标识\n\n\n\n\n\n## 二、流程\n\n微信小程序登陆具体实现的逻辑如下图所示：\n\n ![](https://static.vue-js.com/b60638c0-3428-11ec-a752-75723a64e8f5.png)\n\n- 通过  wx.login()  获取到用户的code判断用户是否授权读取用户信息，调用wx.getUserInfo 读取用户数据\n- 由于小程序后台授权域名无法授权微信的域名，所以需要自身后端调用微信服务器获取用户信息\n- 通过 wx.request() 方法请求业务方服务器，后端把 appid , appsecret  和 code 一起发送到微信服务器。 appid 和 appsecret 都是微信提供的，可以在管理员后台找到\n- 微信服务器返回了 openid 及本次登录的会话密钥 session_key\n- 后端从数据库中查找 openid ，如果没有查到记录，说明该用户没有注册，如果有记录，则继续往下走\n- session_key 是对用户数据进行加密签名的密钥。为了自身应用安全，session_key 不应该在网络上传输\n- 然后生成 session并返回给小程序\n- 小程序把 session 存到  storage 里面\n- 下次请求时，先从 storage 里面读取，然后带给服务端\n- 服务端对比 session 对应的记录，然后校验有效期\n\n更加详细的功能图如下所示：\n\n ![](https://static.vue-js.com/c3cfbb70-3428-11ec-8e64-91fdec0f05a1.png)\n\n\n\n\n\n## 三、扩展\n\n实际业务中，我们还需要登录态是否过期，通常的做法是在登录态（临时令牌）中保存有效期数据，该有效期数据应该在服务端校验登录态时和约定的时间（如服务端本地的系统时间或时间服务器上的标准时间）做对比\n\n这种方法需要将本地存储的登录态发送到小程序的服务端，服务端判断为无效登录态时再返回需重新执行登录过程的消息给小程\n\n另一种方式可以通过调用`wx.checkSession`检查微信登陆态是否过期：\n\n- 如果过期，则发起完整的登录流程\n- 如果不过期，则继续使用本地保存的自定义登录态\n\n这种方式的好处是不需要小程序服务端来参与校验，而是在小程序端调用AP，流程如下所示：\n\n ![](https://static.vue-js.com/8b446d30-349d-11ec-a752-75723a64e8f5.png)\n\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000016750340\n- https://juejin.cn/post/6955754095860776973\n- https://www.cnblogs.com/zwh0910/p/13977278.html"
  },
  {
    "path": "docs/applet/navigate.md",
    "content": "# 面试官：说说微信小程序中路由跳转的方式有哪些？区别？\n\n\n\n ![](https://static.vue-js.com/52bd3820-31a5-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n微信小程序拥有`web`网页和`Application`共同的特征，我们的页面都不是孤立存在的，而是通过和其他页面进行交互，来共同完成系统的功能\n\n在微信小程序中，每个页面可以看成是一个` pageModel`，`pageModel `全部以栈的形式进行管理\n\n\n\n## 二、有哪些\n\n常见的微信小程序页面跳转方式有如下：\n\n- wx.navigateTo(Object)\n- wx.redirectTo(Object)\n- wx.switchTab(Object)\n- wx.navigateBack(Object)\n- wx.reLaunch(Object)\n\n\n\n### wx.navigateTo(Object)\n\n`wx.navigateTo()`用于保留当前页面、跳转到应用内的某个页面，使用 `wx.navigateBack`可以返回到原页面\n\n对于页面不是特别多的小程序，通常推荐使用 `wx.navigateTo`进行跳转， 以便返回原页面，以提高加载速度。当页面特别多时，则不推荐使用\n\n参数表如下所示：\n\n ![](https://static.vue-js.com/5e524ea0-31a5-11ec-8e64-91fdec0f05a1.png)\n\n流程图如下：\n\n ![](https://static.vue-js.com/68f033e0-31a5-11ec-8e64-91fdec0f05a1.png)\n\n\n\n### wx.redirectTo(Object)\n\n重定向，当页面过多时，被保留页面会挤占微信分配给小程序的内存，或是达到微信所限制的 10 层页面栈的情况下，我们应该考虑选择 `wx.redirectTo`\n\n`wx.redirectTo()`用于关闭当前页面，跳转到应用内的某个页面\n\n这样的跳转，可以避免跳转前页面占据运行内存，但返回时页面需要重新加载，增加了返回页面的显示时间\n\n参数表如下所示：\n\n ![](https://static.vue-js.com/76066c20-31a5-11ec-8e64-91fdec0f05a1.png)\n\n流程图如下所示：\n\n ![](https://static.vue-js.com/828c4b40-31a5-11ec-a752-75723a64e8f5.png)\n\n\n\n### wx.switchTab(Object)\n\n跳转到 `tabBar `页面，并关闭其他所有非 `tabBar` 页面\n\n参数表如下所示：\n\n ![](https://static.vue-js.com/968869d0-31a5-11ec-a752-75723a64e8f5.png)\n\n\n\n### wx.navigateBack(Object)\n\n`wx.navigateBack()` 用于关闭当前页面，并返回上一页面或多级页面，开发者可通过 `getCurrentPages()` 获取当前的页面栈，决定需要返回几层则设置对象的`delta`属性即可\n\n参数表如下：\n\n ![](https://static.vue-js.com/a28d8030-31a5-11ec-a752-75723a64e8f5.png)\n\n\n\n### wx.reLaunch(Object)\n\n关闭所有页面，打开到应用内的某个页面，返回的时候跳到首页\n\n流程图如下所示：\n\n ![](https://static.vue-js.com/accca3a0-31a5-11ec-8e64-91fdec0f05a1.png)\n\n参数表如下所示：\n\n ![](https://static.vue-js.com/b98c7e80-31a5-11ec-8e64-91fdec0f05a1.png)\n\n\n\n\n\n\n\n## 三、总结\n\n关于上述五种跳转方式，做下总结：\n\n- navigateTo 保留当前页面，跳转到应用内的某个页面，使用 wx.navigateBack 可以返回到原页\n- redirectTo 关闭当前页面，跳转到应用内的某个页面\n- switchTab 跳转到 tabBar 页面，同时关闭其他非 tabBar 页面\n- navigateBack 返回上一页面\n- reLanch 关闭所有页面，打开到应用内的某个页面\n\n其中关于它们的页面栈的关系如下：\n\n- avigateTo 新页面入栈\n\n- redirectTo 当前页面出栈，新页面入栈\n- navigateBack 页面不断出栈，直到目标返回页，新页面入栈\n- switchTab 页面全部出栈，只留下新的 Tab 页面\n- reLanch 页面全部出栈，只留下新的页面\n\n\n\n## 参考文献\n\n- https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.navigateBack.html"
  },
  {
    "path": "docs/applet/optimization.md",
    "content": "# 面试官：说说提高微信小程序的应用速度的手段有哪些？\n\n ![](https://static.vue-js.com/f606d530-3278-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n小程序启动会常常遇到如下图场景：\n\n ![](https://static.vue-js.com/03941230-3279-11ec-8e64-91fdec0f05a1.png)\n\n这是因为，小程序首次启动前，微信会在小程序启动前为小程序准备好通用的运行环境，如运行中的线程和一些基础库的初始化\n\n然后才开始进入启动状态，展示一个固定的启动界面，界面内包含小程序的图标、名称和加载提示图标。此时，微信会在背后完成几项工作：\n\n- 下载小程序代码包\n- 加载小程序代码包\n- 初始化小程序首页\n\n下载到的小程序代码包不是小程序的源代码，而是编译、压缩、打包之后的代码包\n\n整体流程如下图：\n\n ![](https://static.vue-js.com/11c0ea90-3279-11ec-a752-75723a64e8f5.png)\n\n\n\n\n\n## 二、手段\n\n围绕上图小程序的启动流程， 我们可以从加载、渲染两个纬度进行切入：\n\n\n\n### 加载\n\n提升体验最直接的方法是控制小程序包的大小，常见手段有如下：\n\n- 代码包的体积压缩可以通过勾选开发者工具中“上传代码时，压缩代码”选项\n\n- 及时清理无用的代码和资源文件\n- 减少资源包中的图片等资源的数量和大小（理论上除了小icon，其他图片资源从网络下载），图片资源压缩率有限\n\n并且可以采取分包加载的操作，将用户访问率高的页面放在主包里，将访问率低的页面放入子包里，按需加载\n\n当用户点击到子包的目录时，还是有一个代码包下载的过程，这会感觉到明显的卡顿，所以子包也不建议拆的太大，当然我们可以采用子包预加载技术，并不需要等到用户点击到子包页面后在下载子包\n\n ![](https://static.vue-js.com/2034de10-3279-11ec-8e64-91fdec0f05a1.png)\n\n\n\n### 渲染\n\n关于微信小程序首屏渲染优化的手段如下：\n\n- 请求可以在页面onLoad就加载，不需要等页面ready后在异步请求数据\n- 尽量减少不必要的https请求，可使用 getStorageSync() 及 setStorageSync() 方法将数据存储在本地\n- 可以在前置页面将一些有用的字段带到当前页，进行首次渲染（列表页的某些数据--> 详情页），没有数据的模块可以进行骨架屏的占位\n\n\n\n在微信小程序中，提高页面的多次渲染效率主要在于正确使用`setData`：\n\n- 不要过于频繁调用setData，应考虑将多次setData合并成一次setData调用\n- 数据通信的性能与数据量正相关，因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串，则不应使用`setData`来设置这些数据\n- 与界面渲染无关的数据最好不要设置在data中，可以考虑设置在page对象的其他字段下\n\n\n\n除此之外，对于一些独立的模块我们尽可能抽离出来，这是因为自定义组件的更新并不会影响页面上其他元素的更新\n\n各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立的数据、`setData`调用\n\n\n\n\n\n\n\n\n\n## 三、总结\n\n**小程序启动加载性能**：\n\n- 控制代码包的大小\n- 分包加载\n- 首屏体验（预请求，利用缓存，避免白屏，及时反馈\n\n**小程序渲染性能**：\n\n- 避免不当的使用setData\n- 使用自定义组件\n\n\n\n### 参考文献\n\n- https://juejin.cn/post/6969779451177484296\n- https://segmentfault.com/a/1190000008925450\n- https://juejin.cn/post/6844903638226173965\n- https://juejin.cn/post/6844903726939897869\n"
  },
  {
    "path": "docs/applet/publish.md",
    "content": "# 面试官：说说微信小程序的发布流程？\n\n ![](https://static.vue-js.com/d5cccdf0-3652-11ec-8e64-91fdec0f05a1.png)\n\n## 一、背景\n\n在中大型的公司里，人员的分工非常仔细，一般会有不同岗位角色的员工同时参与同一个小程序项目。为此，小程序平台设计了不同的权限管理使得项目管理者可以更加高效管理整个团队的协同工作\n\n ![](https://static.vue-js.com/e76aff50-3652-11ec-8e64-91fdec0f05a1.png)\n\n以往我们在开发完网页之后，需要把网页的代码和资源放在服务器上，让用户通过互联网来访问\n\n在小程序的平台里，开发者完成开发之后，需要在开发者工具提交小程序的代码包，然后在小程序后台发布小程序\n\n ![](https://static.vue-js.com/fe5da190-3652-11ec-8e64-91fdec0f05a1.png)\n\n\n\n\n\n## 二、流程\n\n关于发布的流程，主要分成了三个部分：\n\n- 上传代码\n- 提交审核\n- 发布版本\n\n\n\n### 上传代码\n\n在开发者工具中，可以点击代码上传功能：\n\n ![](https://static.vue-js.com/08f19bc0-3653-11ec-a752-75723a64e8f5.png)\n\n然后就可以填写版本信息：\n\n ![](https://static.vue-js.com/1d02c8f0-3653-11ec-a752-75723a64e8f5.png)\n\n然后点击上传，编译器则会提示上传代码成功\n\n\n\n### 提交审核\n\n代码上传完毕，就可以登陆微信公众号的官网首页，点击【开发管理】，查看应用详情：\n\n ![](https://static.vue-js.com/281038e0-3653-11ec-8e64-91fdec0f05a1.png)\n\n提交审核过程需要填写审核信息，如下图：\n\n ![](https://static.vue-js.com/33d97ec0-3653-11ec-a752-75723a64e8f5.png)\n\n提交审核成功之后如下图：\n\n ![](https://static.vue-js.com/3e4c3550-3653-11ec-a752-75723a64e8f5.png)\n\n### 发布版本\n\n当审核通过之后，即可提交发布\n\n ![](https://static.vue-js.com/495140d0-3653-11ec-8e64-91fdec0f05a1.png)\n\n发布成功之后则如下：\n\n ![](https://static.vue-js.com/5293b4c0-3653-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 三、扩展\n\n上述是最简单的小程序代码发布的流程，通常的流程如下：\n\n- 代码管理服务器上新建分支\n- 开发测试新需求\n- 测试完成后，将本地分支合并到 master 分支\n- 拉取 master 分支最新代码，执行 build 命令生成小程序可执行文件\n- 开发者工具点击“上传”\n- 提审\n- 发布\n\n但是面对多人协调开发的时候，有可能出现已经上线的代码还没合并到`master`的情况\n\n因此可以考虑自动化构建部署，就是将从开发到部署的一系列流程变成自动化，衔接连贯，在构建失败时能够告知开发者，构建成功后能够告知测试和实施人员，可参考如下流程图：\n\n ![](https://static.vue-js.com/602d9bf0-3653-11ec-a752-75723a64e8f5.png)\n\n\n## 参考文献\n\n- https://juejin.cn/post/6994414162700927012\n- https://www.leapcloud.cn/website/docs/doc_config/xiaochengxu/xiaochengxu.html"
  },
  {
    "path": "docs/applet/requestPayment.md",
    "content": "# 面试官：说说微信小程序的支付流程？\n\n ![](https://static.vue-js.com/2266fff0-34a0-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、前言\n\n微信小程序为电商类小程序，提供了非常完善、优秀、安全的支付功能\n\n在小程序内可调用微信的`API`完成支付功能，方便、快捷\n\n场景如下图所示：\n\n ![](https://static.vue-js.com/6e0cff40-34a0-11ec-a752-75723a64e8f5.png)\n\n ![](https://static.vue-js.com/34864830-34a0-11ec-8e64-91fdec0f05a1.png)\n\n- 用户通过分享或扫描二维码进入商户小程序，用户选择购买，完成选购流程\n- 调起微信支付控件，用户开始输入支付密码\n- 密码验证通过，支付成功。商户后台得到支付成功的通知\n- 返回商户小程序，显示购买成功\n- 微信支付公众号下发支付凭证\n\n\n\n## 二、流程\n\n以电商小程序为例\n\n支付流程图如下所示：\n\n ![](https://static.vue-js.com/76b66780-34a0-11ec-8e64-91fdec0f05a1.png)\n\n具体的做法：\n\n- 打开某小程序，点击直接下单\n- wx.login获取用户临时登录凭证code，发送到后端服务器换取openId\n- 在下单时，小程序需要将购买的商品Id，商品数量，以及用户的openId传送到服务器\n- 服务器在接收到商品Id、商品数量、openId后，生成服务期订单数据，同时经过一定的签名算法，向微信支付发送请求，获取预付单信息(prepay_id)，同时将获取的数据再次进行相应规则的签名，向小程序端响应必要的信息\n- 小程序端在获取对应的参数后，调用wx.requestPayment()发起微信支付，唤醒支付工作台，进行支付\n- 接下来的一些列操作都是由用户来操作的包括了微信支付密码，指纹等验证，确认支付之后执行鉴权调起支付\n- 鉴权调起支付：在微信后台进行鉴权，微信后台直接返回给前端支付的结果，前端收到返回数据后对支付结果进行展示\n- 推送支付结果：微信后台在给前端返回支付的结果后，也会向后台也返回一个支付结果，后台通过这个支付结果来更新订单的状态\n\n其中后端响应数据必要的信息则是`wx.requestPayment`方法所需要的参数，大致如下：\n\n```JS\nwx.requestPayment({\n  // 时间戳\n  timeStamp: '',\n  // 随机字符串\n  nonceStr: '',\n  // 统一下单接口返回的 prepay_id 参数值\n  package: '',\n  // 签名类型\n  signType: '',\n  // 签名\n  paySign: '',\n  // 调用成功回调\n  success () {},\n  // 失败回调\n  fail () {},\n  // 接口调用结束回调\n  complete () {}\n})\n```\n\n参数表如下所示：\n\n\n![](https://files.mdnice.com/user/155/48efed1f-d67f-45a7-ab2c-89a6424fafa0.png)\n\n\n\n## 三、结束\n\n小程序支付和以往的网页、APP微信支付大同小异，可以说小程序的支付变得更加简洁，不需要设置支付目录、域名授权等操作\n\n\n## 参考文献\n\n- https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml\n- https://juejin.cn/post/6844903895970349064"
  },
  {
    "path": "docs/css/BFC.md",
    "content": "# 面试官：谈谈你对BFC的理解？\n\n![](https://static.vue-js.com/c3d68290-9511-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n我们在页面布局的时候，经常出现以下情况：\n\n- 这个元素高度怎么没了？\n- 这两栏布局怎么没法自适应？\n- 这两个元素的间距怎么有点奇怪的样子？\n- ......\n\n原因是元素之间相互的影响，导致了意料之外的情况，这里就涉及到`BFC`概念\n\n`BFC`（Block Formatting Context），即块级格式化上下文，它是页面中的一块渲染区域，并且有一套属于自己的渲染规则：\n\n- 内部的盒子会在垂直方向上一个接一个的放置\n- 对于同一个BFC的俩个相邻的盒子的margin会发生重叠，与方向无关。\n- 每个元素的左外边距与包含块的左边界相接触（从左到右），即使浮动元素也是如此\n- BFC的区域不会与float的元素区域重叠\n- 计算BFC的高度时，浮动子元素也参与计算\n- BFC就是页面上的一个隔离的独立容器，容器里面的子元素不会影响到外面的元素，反之亦然\n\n`BFC`目的是形成一个相对于外界完全独立的空间，让内部的子元素不会影响到外部的元素\n\n\n## 二、触发条件\n\n触发`BFC`的条件包含不限于：\n\n- 根元素，即HTML元素\n- 浮动元素：float值为left、right\n- overflow值不为 visible，为 auto、scroll、hidden\n- display的值为inline-block、inltable-cell、table-caption、table、inline-table、flex、inline-flex、grid、inline-grid\n- position的值为absolute或fixed\n\n\n\n## 三、应用场景\n\n利用`BFC`的特性，我们将`BFC`应用在以下场景：\n\n#### 防止margin重叠（塌陷）\n\n```html\n<style>\n    p {\n        color: #f55;\n        background: #fcc;\n        width: 200px;\n        line-height: 100px;\n        text-align:center;\n        margin: 100px;\n    }\n</style>\n<body>\n    <p>Haha</p >\n    <p>Hehe</p >\n</body>\n```\n\n页面显示如下：\n\n ![](https://static.vue-js.com/d0ce3650-9511-11eb-85f6-6fac77c0c9b3.png)\n\n两个`p`元素之间的距离为`100px`，发生了`margin`重叠（塌陷），以最大的为准，如果第一个P的`margin`为80的话，两个P之间的距离还是100，以最大的为准。\n\n前面讲到，同一个`BFC`的俩个相邻的盒子的`margin`会发生重叠\n\n可以在`p`外面包裹一层容器，并触发这个容器生成一个`BFC`，那么两个`p`就不属于同一个`BFC`，则不会出现`margin`重叠\n\n```html\n<style>\n    .wrap {\n        overflow: hidden;// 新的BFC\n    }\n    p {\n        color: #f55;\n        background: #fcc;\n        width: 200px;\n        line-height: 100px;\n        text-align:center;\n        margin: 100px;\n    }\n</style>\n<body>\n    <p>Haha</p >\n    <div class=\"wrap\">\n        <p>Hehe</p >\n    </div>\n</body>\n```\n\n这时候，边距则不会重叠：\n\n ![](https://static.vue-js.com/dec44740-9511-11eb-85f6-6fac77c0c9b3.png)\n\n#### 清除内部浮动\n\n```html\n<style>\n    .par {\n        border: 5px solid #fcc;\n        width: 300px;\n    }\n \n    .child {\n        border: 5px solid #f66;\n        width:100px;\n        height: 100px;\n        float: left;\n    }\n</style>\n<body>\n    <div class=\"par\">\n        <div class=\"child\"></div>\n        <div class=\"child\"></div>\n    </div>\n</body>\n```\n\n页面显示如下：\n\n ![](https://static.vue-js.com/ec5d4410-9511-11eb-85f6-6fac77c0c9b3.png)\n\n而`BFC`在计算高度时，浮动元素也会参与，所以我们可以触发`.par`元素生成`BFC`，则内部浮动元素计算高度时候也会计算\n\n```css\n.par {\n    overflow: hidden;\n}\n```\n\n实现效果如下：\n\n ![](https://static.vue-js.com/f6487b20-9511-11eb-ab90-d9ae814b240d.png)\n\n#### 自适应多栏布局\n\n这里举个两栏的布局\n\n```html\n<style>\n    body {\n        width: 300px;\n        position: relative;\n    }\n \n    .aside {\n        width: 100px;\n        height: 150px;\n        float: left;\n        background: #f66;\n    }\n \n    .main {\n        height: 200px;\n        background: #fcc;\n    }\n</style>\n<body>\n    <div class=\"aside\"></div>\n    <div class=\"main\"></div>\n</body>\n```\n\n效果图如下：\n\n ![](https://static.vue-js.com/ffb95210-9511-11eb-ab90-d9ae814b240d.png)\n\n前面讲到，每个元素的左外边距与包含块的左边界相接触\n\n因此，虽然`.aslide`为浮动元素，但是`main`的左边依然会与包含块的左边相接触\n\n而`BFC`的区域不会与浮动盒子重叠\n\n所以我们可以通过触发`main`生成`BFC`，以此适应两栏布局\n\n```css\n.main {\n    overflow: hidden;\n}\n```\n\n这时候，新的`BFC`不会与浮动的`.aside`元素重叠。因此会根据包含块的宽度，和`.aside`的宽度，自动变窄\n\n效果如下：\n\n ![](https://static.vue-js.com/0a5f2690-9512-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 小结\n\n可以看到上面几个案例，都体现了`BFC`实际就是页面一个独立的容器，里面的子元素不影响外面的元素\n\n## 参考文献\n\n- https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Block_formatting_context\n- https://github.com/zuopf769/notebook/blob/master/fe/BFC%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90/README.md\n"
  },
  {
    "path": "docs/css/animation.md",
    "content": "# 面试官：css3动画有哪些？\n\n ![](https://static.vue-js.com/d12e2380-9c0a-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\nCSS动画（CSS Animations）是为层叠样式表建议的允许可扩展标记语言（XML）元素使用CSS的动画的模块\n\n即指元素从一种样式逐渐过渡为另一种样式的过程\n\n常见的动画效果有很多，如平移、旋转、缩放等等，复杂动画则是多个简单动画的组合\n\n`css`实现动画的方式，有如下几种：\n\n- transition 实现渐变动画\n- transform 转变动画\n- animation 实现自定义动画\n\n\n## 二、实现方式\n\n### transition 实现渐变动画\n\n`transition`的属性如下：\n\n- property:填写需要变化的css属性\n- duration:完成过渡效果需要的时间单位(s或者ms)\n- timing-function:完成效果的速度曲线\n- delay: 动画效果的延迟触发时间\n\n其中`timing-function`的值有如下：\n\n| 值                            | 描述                                                         |\n| ----------------------------- | ------------------------------------------------------------ |\n| linear                        | 匀速（等于 cubic-bezier(0,0,1,1)）                           |\n| ease                          | 从慢到快再到慢（cubic-bezier(0.25,0.1,0.25,1)）              |\n| ease-in                       | 慢慢变快（等于 cubic-bezier(0.42,0,1,1)）                    |\n| ease-out                      | 慢慢变慢（等于 cubic-bezier(0,0,0.58,1)）                    |\n| ease-in-out                   | 先变快再到慢（等于 cubic-bezier(0.42,0,0.58,1)），渐显渐隐效果 |\n| cubic-bezier(*n*,*n*,*n*,*n*) | 在 cubic-bezier 函数中定义自己的值。可能的值是 0 至 1 之间的数值 |\n\n注意：并不是所有的属性都能使用过渡的，如`display:none<->display:block`\n\n举个例子，实现鼠标移动上去发生变化动画效果\n\n```html\n<style>\n       .base {\n            width: 100px;\n            height: 100px;\n            display: inline-block;\n            background-color: #0EA9FF;\n            border-width: 5px;\n            border-style: solid;\n            border-color: #5daf34;\n            transition-property: width, height, background-color, border-width;\n            transition-duration: 2s;\n            transition-timing-function: ease-in;\n            transition-delay: 500ms;\n        }\n\n        /*简写*/\n        /*transition: all 2s ease-in 500ms;*/\n        .base:hover {\n            width: 200px;\n            height: 200px;\n            background-color: #5daf34;\n            border-width: 10px;\n            border-color: #3a8ee6;\n        }\n</style>\n<div class=\"base\"></div>\n```\n\n### transform 转变动画\n\n包含四个常用的功能：\n\n- translate：位移\n- scale：缩放\n- rotate：旋转\n- skew：倾斜\n\n一般配合`transition`过度使用\n\n注意的是，`transform`不支持`inline`元素，使用前把它变成`block`\n\n举个例子\n\n```html\n<style>\n    .base {\n        width: 100px;\n        height: 100px;\n        display: inline-block;\n        background-color: #0EA9FF;\n        border-width: 5px;\n        border-style: solid;\n        border-color: #5daf34;\n        transition-property: width, height, background-color, border-width;\n        transition-duration: 2s;\n        transition-timing-function: ease-in;\n        transition-delay: 500ms;\n    }\n    .base2 {\n        transform: none;\n        transition-property: transform;\n        transition-delay: 5ms;\n    }\n\n    .base2:hover {\n        transform: scale(0.8, 1.5) rotate(35deg) skew(5deg) translate(15px, 25px);\n    }\n</style>\n <div class=\"base base2\"></div>\n```\n\n可以看到盒子发生了旋转，倾斜，平移，放大\n\n\n\n### animation 实现自定义动画\n\n`animation`是由 8 个属性的简写，分别如下：\n\n| 属性                                   | 描述                                                         | 属性值                                        |\n| -------------------------------------- | ------------------------------------------------------------ | --------------------------------------------- |\n| animation-duration                     | 指定动画完成一个周期所需要时间，单位秒（s）或毫秒（ms），默认是 0 |                                               |\n| animation-timing-function              | 指定动画计时函数，即动画的速度曲线，默认是 \"ease\"            | linear、ease、ease-in、ease-out、ease-in-out  |\n| animation-delay                        | 指定动画延迟时间，即动画何时开始，默认是 0                   |                                               |\n| animation-iteration-count              | 指定动画播放的次数，默认是 1                                 |                                               |\n| animation-direction 指定动画播放的方向 | 默认是 normal                                                | normal、reverse、alternate、alternate-reverse |\n| animation-fill-mode                    | 指定动画填充模式。默认是 none                                | forwards、backwards、both                     |\n| animation-play-state                   | 指定动画播放状态，正在运行或暂停。默认是 running             | running、pauser                               |\n| animation-name                         | 指定 @keyframes 动画的名称                                   |                                               |\n\n`CSS` 动画只需要定义一些关键的帧，而其余的帧，浏览器会根据计时函数插值计算出来，\n\n通过 `@keyframes` 来定义关键帧\n\n因此，如果我们想要让元素旋转一圈，只需要定义开始和结束两帧即可：\n\n```css\n@keyframes rotate{\n    from{\n        transform: rotate(0deg);\n    }\n    to{\n        transform: rotate(360deg);\n    }\n}\n```\n\n`from` 表示最开始的那一帧，`to` 表示结束时的那一帧\n\n也可以使用百分比刻画生命周期\n\n```css\n@keyframes rotate{\n    0%{\n        transform: rotate(0deg);\n    }\n    50%{\n        transform: rotate(180deg);\n    }\n    100%{\n        transform: rotate(360deg);\n    }\n}\n```\n\n定义好了关键帧后，下来就可以直接用它了：\n\n```css\nanimation: rotate 2s;\n```\n\n\n\n\n\n## 三、总结\n\n| 属性               | 含义                                                         |\n| ------------------ | ------------------------------------------------------------ |\n| transition（过度） | 用于设置元素的样式过度，和animation有着类似的效果，但细节上有很大的不同 |\n| transform（变形）  | 用于元素进行旋转、缩放、移动或倾斜，和设置样式的动画并没有什么关系，就相当于color一样用来设置元素的“外表” |\n| translate（移动）  | 只是transform的一个属性值，即移动                            |\n| animation（动画）  | 用于设置动画属性，他是一个简写的属性，包含6个属性            |\n\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000022540857\n- https://zh.m.wikipedia.org/wiki/CSS%E5%8A%A8%E7%94%BB\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/css/box.md",
    "content": "# 面试官：说说你对盒子模型的理解?\n\n![](https://static.vue-js.com/8d0e9ca0-8f9b-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n当对一个文档进行布局（layout）的时候，浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型（CSS basic box model），将所有元素表示为一个个矩形的盒子（box）\n\n一个盒子由四个部分组成：`content`、`padding`、`border`、`margin`\n\n![](https://static.vue-js.com/976789a0-8f9b-11eb-85f6-6fac77c0c9b3.png)\n\n`content`，即实际内容，显示文本和图像\n\n`boreder`，即边框，围绕元素内容的内边距的一条或多条线，由粗细、样式、颜色三部分组成\n\n`padding`，即内边距，清除内容周围的区域，内边距是透明的，取值不能为负，受盒子的`background`属性影响\n\n`margin`，即外边距，在元素外创建额外的空白，空白通常指不能放其他元素的区域\n\n上述是一个从二维的角度观察盒子，下面再看看看三维图：\n\n ![](https://static.vue-js.com/b2548b00-8f9b-11eb-ab90-d9ae814b240d.png)\n\n\n\n下面来段代码：\n\n```html\n<style>\n  .box {\n    width: 200px;\n    height: 100px;\n    padding: 20px;\n  }\n</style>\n<div class=\"box\">\n  盒子模型\n</div>\n```\n\n当我们在浏览器查看元素时，却发现元素的大小变成了`240px`\n\n这是因为，在`CSS`中，盒子模型可以分成：\n\n- W3C 标准盒子模型\n- IE 怪异盒子模型\n\n默认情况下，盒子模型为`W3C` 标准盒子模型\n\n\n## 二、标准盒子模型\n\n标准盒子模型，是浏览器默认的盒子模型\n\n下面看看标准盒子模型的模型图：\n\n ![](https://static.vue-js.com/c0e1d2e0-8f9b-11eb-85f6-6fac77c0c9b3.png)\n\n从上图可以看到：\n\n- 盒子总宽度 = width + padding + border + margin;\n\n- 盒子总高度 = height + padding + border + margin\n\n也就是，`width/height` 只是内容高度，不包含 `padding` 和 `border `值\n\n所以上面问题中，设置`width`为200px，但由于存在`padding`，但实际上盒子的宽度有240px\n\n## 三、IE 怪异盒子模型\n\n同样看看IE 怪异盒子模型的模型图：\n\n ![](https://static.vue-js.com/cfbb3ef0-8f9b-11eb-ab90-d9ae814b240d.png)\n\n从上图可以看到：\n\n- 盒子总宽度 = width + margin;\n\n- 盒子总高度 = height + margin;\n\n也就是，`width/height` 包含了 `padding `和 `border `值\n\n## Box-sizing\n\nCSS 中的 box-sizing 属性定义了引擎应该如何计算一个元素的总宽度和总高度\n\n语法：\n```css\nbox-sizing: content-box|border-box|inherit:\n```\n\n- content-box 默认值，元素的 width/height 不包含padding，border，与标准盒子模型表现一致\n- border-box 元素的 width/height 包含 padding，border，与怪异盒子模型表现一致\n- inherit 指定 box-sizing 属性的值，应该从父元素继承\n\n回到上面的例子里，设置盒子为 border-box 模型\n\n```html\n<style>\n  .box {\n    width: 200px;\n    height: 100px;\n    padding: 20px;\n    box-sizing: border-box;\n  }\n</style>\n<div class=\"box\">\n  盒子模型\n</div>\n```\n这时候，就可以发现盒子的所占据的宽度为200px\n\n## 参考文献\n- https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model\n- https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-sizing\n"
  },
  {
    "path": "docs/css/center.md",
    "content": "# 面试官：元素水平垂直居中的方法有哪些？如果元素不定宽高呢？\n\n ![](https://static.vue-js.com/7b64c8d0-95f9-11eb-ab90-d9ae814b240d.png)\n\n## 一、背景\n\n在开发中经常遇到这个问题，即让某个元素的内容在水平和垂直方向上都居中，内容不仅限于文字，可能是图片或其他元素\n\n居中是一个非常基础但又是非常重要的应用场景，实现居中的方法存在很多，可以将这些方法分成两个大类：\n\n- 居中元素（子元素）的宽高已知\n- 居中元素宽高未知\n\n\n\n## 二、实现方式\n\n实现元素水平垂直居中的方式：\n\n- 利用定位+margin:auto\n\n- 利用定位+margin:负值\n\n- 利用定位+transform\n- table布局\n- flex布局\n- grid布局\n\n\n\n### 利用定位+margin:auto\n\n先上代码：\n\n```html\n<style>\n    .father{\n        width:500px;\n        height:300px;\n        border:1px solid #0a3b98;\n        position: relative;\n    }\n    .son{\n        width:100px;\n        height:40px;\n        background: #f0a238;\n        position: absolute;\n        top:0;\n        left:0;\n        right:0;\n        bottom:0;\n        margin:auto;\n    }\n</style>\n<div class=\"father\">\n    <div class=\"son\"></div>\n</div>\n```\n\n父级设置为相对定位，子级绝对定位 ，并且四个定位属性的值都设置了0，那么这时候如果子级没有设置宽高，则会被拉开到和父级一样宽高\n\n这里子元素设置了宽高，所以宽高会按照我们的设置来显示，但是实际上子级的虚拟占位已经撑满了整个父级，这时候再给它一个`margin：auto`它就可以上下左右都居中了\n\n\n\n### 利用定位+margin:负值\n\n绝大多数情况下，设置父元素为相对定位， 子元素移动自身50%实现水平垂直居中\n\n```html\n<style>\n    .father {\n        position: relative;\n        width: 200px;\n        height: 200px;\n        background: skyblue;\n    }\n    .son {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        margin-left:-50px;\n        margin-top:-50px;\n        width: 100px;\n        height: 100px;\n        background: red;\n    }\n</style>\n<div class=\"father\">\n    <div class=\"son\"></div>\n</div>\n```\n\n整个实现思路如下图所示：\n\n ![](https://static.vue-js.com/922dc300-95f9-11eb-ab90-d9ae814b240d.png)\n\n- 初始位置为方块1的位置\n- 当设置left、top为50%的时候，内部子元素为方块2的位置\n- 设置margin为负数时，使内部子元素到方块3的位置，即中间位置\n\n这种方案不要求父元素的高度，也就是即使父元素的高度变化了，仍然可以保持在父元素的垂直居中位置，水平方向上是一样的操作\n\n但是该方案需要知道子元素自身的宽高，但是我们可以通过下面`transform`属性进行移动\n\n\n\n### 利用定位+transform\n\n实现代码如下：\n\n```css\n<style>\n    .father {\n        position: relative;\n        width: 200px;\n        height: 200px;\n        background: skyblue;\n    }\n    .son {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%,-50%);\n        width: 100px;\n        height: 100px;\n        background: red;\n    }\n</style>\n<div class=\"father\">\n    <div class=\"son\"></div>\n</div>\n```\n\n`translate(-50%, -50%)`将会将元素位移自己宽度和高度的-50%\n\n这种方法其实和最上面被否定掉的margin负值用法一样，可以说是`margin`负值的替代方案，并不需要知道自身元素的宽高\n\n\n\n\n\n### table布局\n\n设置父元素为`display:table-cell`，子元素设置 `display: inline-block`。利用`vertical`和`text-align`可以让所有的行内块级元素水平垂直居中\n\n```html\n<style>\n    .father {\n        display: table-cell;\n        width: 200px;\n        height: 200px;\n        background: skyblue;\n        vertical-align: middle;\n        text-align: center;\n    }\n    .son {\n        display: inline-block;\n        width: 100px;\n        height: 100px;\n        background: red;\n    }\n</style>\n<div class=\"father\">\n    <div class=\"son\"></div>\n</div>\n```\n\n\n\n### flex弹性布局\n\n还是看看实现的整体代码：\n\n```html\n<style>\n    .father {\n        display: flex;\n        justify-content: center;\n        align-items: center;\n        width: 200px;\n        height: 200px;\n        background: skyblue;\n    }\n    .son {\n        width: 100px;\n        height: 100px;\n        background: red;\n    }\n</style>\n<div class=\"father\">\n    <div class=\"son\"></div>\n</div>\n```\n\n`css3`中了`flex`布局，可以非常简单实现垂直水平居中\n\n这里可以简单看看`flex`布局的关键属性作用：\n\n- display: flex时，表示该容器内部的元素将按照flex进行布局\n\n- align-items: center表示这些元素将相对于本容器水平居中\n- justify-content: center也是同样的道理垂直居中\n\n\n\n### grid网格布局\n\n```html\n<style>\n    .father {\n            display: grid;\n            align-items:center;\n            justify-content: center;\n            width: 200px;\n            height: 200px;\n            background: skyblue;\n\n        }\n        .son {\n            width: 10px;\n            height: 10px;\n            border: 1px solid red\n        }\n</style>\n<div class=\"father\">\n    <div class=\"son\"></div>\n</div>\n```\n\n这里看到，`gird`网格布局和`flex`弹性布局都简单粗暴\n\n\n\n### 小结\n\n上述方法中，不知道元素宽高大小仍能实现水平垂直居中的方法有：\n\n- 利用定位+margin:auto\n- 利用定位+transform\n- flex布局\n- grid布局\n\n\n\n## 三、总结\n\n根据元素标签的性质，可以分为：\n\n- 内联元素居中布局\n- 块级元素居中布局\n\n\n\n### 内联元素居中布局\n\n水平居中\n\n- 行内元素可设置：text-align: center\n- flex布局设置父元素：display: flex; justify-content: center\n\n垂直居中\n\n- 单行文本父元素确认高度：height === line-height\n- 多行文本父元素确认高度：display: table-cell; vertical-align: middle\n\n\n\n### 块级元素居中布局\n\n水平居中\n\n- 定宽: margin: 0 auto\n- 绝对定位+left:50%+margin:负自身一半\n\n垂直居中\n\n- position: absolute设置left、top、margin-left、margin-top(定高)\n- display: table-cell\n- transform: translate(x, y)\n- flex(不定高，不定宽)\n- grid(不定高，不定宽)，兼容性相对比较差\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903982960214029#heading-10\n"
  },
  {
    "path": "docs/css/column_layout.md",
    "content": "# 面试官：如何实现两栏布局，右侧自适应？三栏布局中间自适应呢？\n\n ![](https://static.vue-js.com/f335d400-976e-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、背景\n\n在日常布局中，无论是两栏布局还是三栏布局，使用的频率都非常高\n\n### 两栏布局\n\n两栏布局实现效果就是将页面分割成左右宽度不等的两列，宽度较小的列设置为固定宽度，剩余宽度由另一列撑满，\n\n比如 `Ant Design` 文档，蓝色区域为主要内容布局容器，侧边栏为次要内容布局容器\n\n> 这里称宽度较小的列父元素为次要布局容器，宽度较大的列父元素为主要布局容器\n\n ![](https://static.vue-js.com/fcb8ac50-976e-11eb-85f6-6fac77c0c9b3.png)\n\n这种布局适用于内容上具有明显主次关系的网页\n\n\n\n### 三栏布局\n\n三栏布局按照左中右的顺序进行排列，通常中间列最宽，左右两列次之\n\n大家最常见的就是`github`：\n\n ![](https://static.vue-js.com/0bf016e0-976f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 二、两栏布局\n\n两栏布局非常常见，往往是以一个定宽栏和一个自适应的栏并排展示存在\n\n实现思路也非常的简单：\n\n- 使用 float 左浮左边栏\n- 右边模块使用 margin-left 撑出内容块做内容展示\n- 为父级元素添加BFC，防止下方元素飞到上方内容\n\n代码如下：\n\n```html\n<style>\n    .box{\n        overflow: hidden; 添加BFC\n    }\n    .left {\n        float: left;\n        width: 200px;\n        background-color: gray;\n        height: 400px;\n    }\n    .right {\n        margin-left: 210px;\n        background-color: lightgray;\n        height: 200px;\n    }\n</style>\n<div class=\"box\">\n    <div class=\"left\">左边</div>\n    <div class=\"right\">右边</div>\n</div>\n```\n\n还有一种更为简单的使用则是采取：flex弹性布局\n\n\n\n### flex弹性布局\n\n```html\n<style>\n    .box{\n        display: flex;\n    }\n    .left {\n        width: 100px;\n    }\n    .right {\n        flex: 1;\n    }\n</style>\n<div class=\"box\">\n    <div class=\"left\">左边</div>\n    <div class=\"right\">右边</div>\n</div>\n```\n\n`flex`可以说是最好的方案了，代码少，使用简单\n\n注意的是，`flex`容器的一个默认属性值:`align-items: stretch;`\n\n这个属性导致了列等高的效果。 为了让两个盒子高度自动，需要设置: `align-items: flex-start`\n\n\n## 三、三栏布局\n\n实现三栏布局中间自适应的布局方式有：\n\n- 两边使用 float，中间使用 margin\n- 两边使用 absolute，中间使用 margin\n- 两边使用 float 和负 margin\n- display: table 实现\n- flex实现\n- grid网格布局\n\n\n\n### 两边使用 float，中间使用 margin\n\n需要将中间的内容放在`html`结构最后，否则右侧会臣在中间内容的下方\n\n实现代码如下：\n\n```html\n<style>\n    .wrap {\n        background: #eee;\n        overflow: hidden; <!-- 生成BFC，计算高度时考虑浮动的元素 -->\n        padding: 20px;\n        height: 200px;\n    }\n    .left {\n        width: 200px;\n        height: 200px;\n        float: left;\n        background: coral;\n    }\n    .right {\n        width: 120px;\n        height: 200px;\n        float: right;\n        background: lightblue;\n    }\n    .middle {\n        margin-left: 220px;\n        height: 200px;\n        background: lightpink;\n        margin-right: 140px;\n    }\n</style>\n<div class=\"wrap\">\n    <div class=\"left\">左侧</div>\n    <div class=\"right\">右侧</div>\n    <div class=\"middle\">中间</div>\n</div>\n```\n\n原理如下：\n\n- 两边固定宽度，中间宽度自适应。\n- 利用中间元素的margin值控制两边的间距\n- 宽度小于左右部分宽度之和时，右侧部分会被挤下去\n\n这种实现方式存在缺陷：\n\n- 主体内容是最后加载的。\n\n- 右边在主体内容之前，如果是响应式设计，不能简单的换行展示\n\n\n\n### 两边使用 absolute，中间使用 margin\n\n基于绝对定位的三栏布局：注意绝对定位的元素脱离文档流，相对于最近的已经定位的祖先元素进行定位。无需考虑HTML中结构的顺序\n\n```html\n<style>\n  .container {\n    position: relative;\n  }\n  \n  .left,\n  .right,\n  .main {\n    height: 200px;\n    line-height: 200px;\n    text-align: center;\n  }\n\n  .left {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100px;\n    background: green;\n  }\n\n  .right {\n    position: absolute;\n    top: 0;\n    right: 0;\n    width: 100px;\n    background: green;\n  }\n\n  .main {\n    margin: 0 110px;\n    background: black;\n    color: white;\n  }\n</style>\n\n<div class=\"container\">\n  <div class=\"left\">左边固定宽度</div>\n  <div class=\"right\">右边固定宽度</div>\n  <div class=\"main\">中间自适应</div>\n</div>\n```\n\n实现流程：\n\n- 左右两边使用绝对定位，固定在两侧。\n- 中间占满一行，但通过 margin和左右两边留出10px的间隔\n\n\n\n\n\n### 两边使用 float 和负 margin\n\n```html\n<style>\n  .left,\n  .right,\n  .main {\n    height: 200px;\n    line-height: 200px;\n    text-align: center;\n  }\n\n  .main-wrapper {\n    float: left;\n    width: 100%;\n  }\n\n  .main {\n    margin: 0 110px;\n    background: black;\n    color: white;\n  }\n\n  .left,\n  .right {\n    float: left;\n    width: 100px;\n    margin-left: -100%;\n    background: green;\n  }\n\n  .right {\n    margin-left: -100px; /* 同自身宽度 */\n  }\n</style>\n\n<div class=\"main-wrapper\">\n  <div class=\"main\">中间自适应</div>\n</div>\n<div class=\"left\">左边固定宽度</div>\n<div class=\"right\">右边固定宽度</div>\n```\n\n实现过程：\n\n- 中间使用了双层标签，外层是浮动的，以便左中右能在同一行展示\n- 左边通过使用负 margin-left:-100%，相当于中间的宽度，所以向上偏移到左侧\n- 右边通过使用负 margin-left:-100px，相当于自身宽度，所以向上偏移到最右侧\n\n \n\n缺点：\n\n- 增加了 .main-wrapper 一层，结构变复杂\n- 使用负 margin，调试也相对麻烦\n\n\n\n### 使用 display: table 实现\n\n`<table>` 标签用于展示行列数据，不适合用于布局。但是可以使用 `display: table` 来实现布局的效果\n\n```html\n<style>\n  .container {\n    height: 200px;\n    line-height: 200px;\n    text-align: center;\n    display: table;\n    table-layout: fixed;\n    width: 100%;\n  }\n\n  .left,\n  .right,\n  .main {\n    display: table-cell;\n  }\n\n  .left,\n  .right {\n    width: 100px;\n    background: green;\n  }\n\n  .main {\n    background: black;\n    color: white;\n    width: 100%;\n  }\n</style>\n\n<div class=\"container\">\n  <div class=\"left\">左边固定宽度</div>\n  <div class=\"main\">中间自适应</div>\n  <div class=\"right\">右边固定宽度</div>\n</div>\n```\n\n实现原理：\n\n- 层通过 display: table设置为表格，设置 table-layout: fixed`表示列宽自身宽度决定，而不是自动计算。\n- 内层的左中右通过 display: table-cell设置为表格单元。\n- 左右设置固定宽度，中间设置 width: 100% 填充剩下的宽度\n\n\n\n\n\n### 使用flex实现\n\n利用`flex`弹性布局，可以简单实现中间自适应\n\n代码如下：\n\n```html\n\n<style type=\"text/css\">\n    .wrap {\n        display: flex;\n        justify-content: space-between;\n    }\n\n    .left,\n    .right,\n    .middle {\n        height: 100px;\n    }\n\n    .left {\n        width: 200px;\n        background: coral;\n    }\n\n    .right {\n        width: 120px;\n        background: lightblue;\n    }\n\n    .middle {\n        background: #555;\n        width: 100%;\n        margin: 0 20px;\n    }\n</style>\n<div class=\"wrap\">\n    <div class=\"left\">左侧</div>\n    <div class=\"middle\">中间</div>\n    <div class=\"right\">右侧</div>\n</div>\n```\n\n实现过程：\n\n- 仅需将容器设置为`display:flex;`，\n- 盒内元素两端对其，将中间元素设置为`100%`宽度，或者设为`flex:1`，即可填充空白\n- 盒内元素的高度撑开容器的高度\n\n优点：\n\n- 结构简单直观\n- 可以结合 flex的其他功能实现更多效果，例如使用 order属性调整显示顺序，让主体内容优先加载，但展示在中间\n\n\n\n### grid网格布局\n\n代码如下：\n\n```html\n<style>\n    .wrap {\n        display: grid;\n        width: 100%;\n        grid-template-columns: 300px auto 300px;\n    }\n\n    .left,\n    .right,\n    .middle {\n        height: 100px;\n    }\n\n    .left {\n        background: coral;\n    }\n\n    .right {\n        background: lightblue;\n    }\n\n    .middle {\n        background: #555;\n    }\n</style>\n<div class=\"wrap\">\n    <div class=\"left\">左侧</div>\n    <div class=\"middle\">中间</div>\n    <div class=\"right\">右侧</div>\n</div>\n```\n\n跟`flex`弹性布局一样的简单\n\n## 参考文献\n\n- https://zhuqingguang.github.io/2017/08/16/adapting-two-layout/\n\n- https://segmentfault.com/a/1190000008705541\n"
  },
  {
    "path": "docs/css/css3_features.md",
    "content": "# 面试官：CSS3新增了哪些新特性？\n\n ![](https://static.vue-js.com/d58f6df0-9b5e-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、是什么\n\n`css`，即层叠样式表（Cascading Style Sheets）的简称，是一种标记语言，由浏览器解释执行用来使页面变得更美观\n\n`css3`是`css`的最新标准，是向后兼容的，`CSS1/2 `的特性在` CSS3` 里都是可以使用的\n\n而` CSS3` 也增加了很多新特性，为开发带来了更佳的开发体验\n\n\n## 二、选择器\n\n`css3`中新增了一些选择器，主要为如下图所示：\n\n ![](https://static.vue-js.com/e368cf20-9b5e-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 三、新样式\n\n### 边框\n\n`css3`新增了三个边框属性，分别是：\n\n- border-radius：创建圆角边框\n- box-shadow：为元素添加阴影\n\n- border-image：使用图片来绘制边框\n\n\n\n#### box-shadow\n\n设置元素阴影，设置属性如下：\n\n- 水平阴影\n- 垂直阴影\n- 模糊距离(虚实)\n- 阴影尺寸(影子大小)\n- 阴影颜色\n- 内/外阴影\n\n其中水平阴影和垂直阴影是必须设置的\n\n\n### 背景\n\n新增了几个关于背景的属性，分别是`background-clip`、`background-origin`、`background-size`和`background-break`\n\n\n\n#### background-clip\n\n用于确定背景画区，有以下几种可能的属性：\n\n- background-clip: border-box; 背景从border开始显示\n- background-clip: padding-box; 背景从padding开始显示\n- background-clip: content-box; 背景显content区域开始显示\n- background-clip: no-clip; 默认属性，等同于border-box\n\n通常情况，背景都是覆盖整个元素的，利用这个属性可以设定背景颜色或图片的覆盖范围\n\n\n\n#### background-origin\n\n当我们设置背景图片时，图片是会以左上角对齐，但是是以`border`的左上角对齐还是以`padding`的左上角或者`content`的左上角对齐? `border-origin`正是用来设置这个的\n\n- background-origin: border-box; 从border开始计算background-position\n- background-origin: padding-box; 从padding开始计算background-position\n- background-origin: content-box; 从content开始计算background-position\n\n默认情况是`padding-box`，即以`padding`的左上角为原点\n\n\n\n#### background-size\n\nbackground-size属性常用来调整背景图片的大小，主要用于设定图片本身。有以下可能的属性：\n\n- background-size: contain; 缩小图片以适合元素（维持像素长宽比）\n- background-size: cover; 扩展元素以填补元素（维持像素长宽比）\n- background-size: 100px 100px; 缩小图片至指定的大小\n- background-size: 50% 100%; 缩小图片至指定的大小，百分比是相对包 含元素的尺寸\n\n\n\n### background-break\n\n元素可以被分成几个独立的盒子（如使内联元素span跨越多行），`background-break` 属性用来控制背景怎样在这些不同的盒子中显示\n\n- background-break: continuous; 默认值。忽略盒之间的距离（也就是像元素没有分成多个盒子，依然是一个整体一样）\n- background-break: bounding-box; 把盒之间的距离计算在内；\n- background-break: each-box; 为每个盒子单独重绘背景\n\n\n\n### 文字\n\n### word-wrap\n\n语法：`word-wrap: normal|break-word`\n\n- normal：使用浏览器默认的换行\n- break-all：允许在单词内换行\n\n\n\n### text-overflow\n\n` text-overflow`设置或检索当当前行超过指定容器的边界时如何显示，属性有两个值选择：\n\n- clip：修剪文本\n- ellipsis：显示省略符号来代表被修剪的文本\n\n\n\n### text-shadow\n\n`text-shadow`可向文本应用阴影。能够规定水平阴影、垂直阴影、模糊距离，以及阴影的颜色\n\n\n\n### text-decoration\n\nCSS3里面开始支持对文字的更深层次的渲染，具体有三个属性可供设置：\n\n- text-fill-color: 设置文字内部填充颜色\n\n- text-stroke-color: 设置文字边界填充颜色\n\n- text-stroke-width: 设置文字边界宽度\n\n\n\n### 颜色\n\n`css3`新增了新的颜色表示方式`rgba`与`hsla`\n\n- rgba分为两部分，rgb为颜色值，a为透明度\n- hala分为四部分，h为色相，s为饱和度，l为亮度，a为透明度\n\n\n\n## 四、transition 过渡\n\n`transition`属性可以被指定为一个或多个` CSS `属性的过渡效果，多个属性之间用逗号进行分隔，必须规定两项内容：\n\n- 过度效果\n- 持续时间\n\n语法如下：\n\n```css\ntransition： CSS属性，花费时间，效果曲线(默认ease)，延迟时间(默认0)\n```\n\n上面为简写模式，也可以分开写各个属性\n\n```css\ntransition-property: width; \ntransition-duration: 1s;\ntransition-timing-function: linear;\ntransition-delay: 2s;\n```\n\n\n\n### 五、transform 转换\n\n`transform`属性允许你旋转，缩放，倾斜或平移给定元素\n\n`transform-origin`：转换元素的位置（围绕那个点进行转换），默认值为`(x,y,z):(50%,50%,0)`\n\n使用方式：\n\n- transform: translate(120px, 50%)：位移\n- transform: scale(2, 0.5)：缩放\n- transform: rotate(0.5turn)：旋转\n- transform: skew(30deg, 20deg)：倾斜\n\n\n\n### 六、animation 动画\n\n动画这个平常用的也很多，主要是做一个预设的动画。和一些页面交互的动画效果，结果和过渡应该一样，让页面不会那么生硬\n\nanimation也有很多的属性\n\n- animation-name：动画名称\n- animation-duration：动画持续时间\n- animation-timing-function：动画时间函数\n- animation-delay：动画延迟时间\n- animation-iteration-count：动画执行次数，可以设置为一个整数，也可以设置为infinite，意思是无限循环\n- animation-direction：动画执行方向\n- animation-paly-state：动画播放状态\n- animation-fill-mode：动画填充模式\n\n\n\n## 七、渐变\n\n颜色渐变是指在两个颜色之间平稳的过渡，`css3`渐变包括\n\n- linear-gradient：线性渐变\n\n> background-image: linear-gradient(direction, color-stop1, color-stop2, ...);\n\n- radial-gradient：径向渐变\n\n> linear-gradient(0deg, red, green); \n\n\n\n## 八、其他\n\n关于`css3`其他的新特性还包括`flex`弹性布局、`Grid`栅格布局，这两个布局在以前就已经讲过，这里就不再展示\n\n除此之外，还包括多列布局、媒体查询、混合模式等等......\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903518520901639#heading-1\n\n- https://www.w3school.com.cn/css/index.asp"
  },
  {
    "path": "docs/css/css_performance.md",
    "content": "# 面试官：如果要做优化，CSS提高性能的方法有哪些？\n\n ![](https://static.vue-js.com/c071c820-9fa3-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、前言\n\n每一个网页都离不开`css`，但是很多人又认为，`css`主要是用来完成页面布局的，像一些细节或者优化，就不需要怎么考虑，实际上这种想法是不正确的\n\n作为页面渲染和内容展现的重要环节，`css`影响着用户对整个网站的第一体验\n\n因此，在整个产品研发过程中，`css`性能优化同样需要贯穿全程\n\n\n\n## 二、实现方式\n\n\n\n实现方式有很多种，主要有如下：\n\n- 内联首屏关键CSS\n- 异步加载CSS\n- 资源压缩\n- 合理使用选择器\n- 减少使用昂贵的属性\n- 不要使用@import\n\n### 内联首屏关键CSS\n\n在打开一个页面，页面首要内容出现在屏幕的时间影响着用户的体验，而通过内联`css`关键代码能够使浏览器在下载完`html`后就能立刻渲染\n\n而如果外部引用`css`代码，在解析`html`结构过程中遇到外部`css`文件，才会开始下载`css`代码，再渲染\n\n所以，`CSS`内联使用使渲染时间提前\n\n注意：但是较大的`css`代码并不合适内联（初始拥塞窗口、没有缓存），而其余代码则采取外部引用方式\n\n\n\n### 异步加载CSS\n\n在`CSS`文件请求、下载、解析完成之前，`CSS`会阻塞渲染，浏览器将不会渲染任何已处理的内容\n\n前面加载内联代码后，后面的外部引用`css`则没必要阻塞浏览器渲染。这时候就可以采取异步加载的方案，主要有如下：\n\n- 使用javascript将link标签插到head标签最后\n\n```js\n// 创建link标签\nconst myCSS = document.createElement( \"link\" );\nmyCSS.rel = \"stylesheet\";\nmyCSS.href = \"mystyles.css\";\n// 插入到header的最后位置\ndocument.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );\n```\n\n- 设置link标签media属性为noexis，浏览器会认为当前样式表不适用当前类型，会在不阻塞页面渲染的情况下再进行下载。加载完成后，将`media`的值设为`screen`或`all`，从而让浏览器开始解析CSS\n\n```html\n<link rel=\"stylesheet\" href=\"mystyles.css\" media=\"noexist\" onload=\"this.media='all'\">\n```\n\n- 通过rel属性将link元素标记为alternate可选样式表，也能实现浏览器异步加载。同样别忘了加载完成之后，将rel设回stylesheet\n\n```html\n<link rel=\"alternate stylesheet\" href=\"mystyles.css\" onload=\"this.rel='stylesheet'\">\n```\n\n\n\n### 资源压缩\n\n利用`webpack`、`gulp/grunt`、`rollup`等模块化工具，将`css`代码进行压缩，使文件变小，大大降低了浏览器的加载时间\n\n\n\n### 合理使用选择器\n\n`css`匹配的规则是从右往左开始匹配，例如`#markdown .content h3`匹配规则如下：\n\n- 先找到h3标签元素\n- 然后去除祖先不是.content的元素\n- 最后去除祖先不是#markdown的元素\n\n如果嵌套的层级更多，页面中的元素更多，那么匹配所要花费的时间代价自然更高\n\n所以我们在编写选择器的时候，可以遵循以下规则：\n\n- 不要嵌套使用过多复杂选择器，最好不要三层以上\n- 使用id选择器就没必要再进行嵌套\n- 通配符和属性选择器效率最低，避免使用\n\n\n\n### 减少使用昂贵的属性\n\n在页面发生重绘的时候，昂贵属性如`box-shadow`/`border-radius`/`filter`/透明度/`:nth-child`等，会降低浏览器的渲染性能\n\n\n\n### 不要使用@import\n\ncss样式文件有两种引入方式，一种是`link`元素，另一种是`@import`\n\n`@import`会影响浏览器的并行下载，使得页面在加载时增加额外的延迟，增添了额外的往返耗时\n\n而且多个`@import`可能会导致下载顺序紊乱\n\n比如一个css文件`index.css`包含了以下内容：`@import url(\"reset.css\")`\n\n那么浏览器就必须先把`index.css`下载、解析和执行后，才下载、解析和执行第二个文件`reset.css`\n\n\n\n### 其他\n\n- 减少重排操作，以及减少不必要的重绘\n- 了解哪些属性可以继承而来，避免对这些属性重复编写\n- cssSprite，合成所有icon图片，用宽高加上backgroud-position的背景图方式显现出我们要的icon图，减少了http请求\n- 把小的icon图片转成base64编码\n- CSS3动画或者过渡尽量使用transform和opacity来实现动画，不要使用left和top属性\n\n\n\n## 三、总结\n\n`css`实现性能的方式可以从选择器嵌套、属性特性、减少`http`这三面考虑，同时还要注意`css`代码的加载顺序\n\n\n\n## 参考文献\n- https://www.zhihu.com/question/19886806\n- https://juejin.cn/post/6844903649605320711#heading-1\n- https://vue3js.cn/interview/"
  },
  {
    "path": "docs/css/dp_px_dpr_ppi.md",
    "content": "# 面试官：说说设备像素、css像素、设备独立像素、dpr、ppi 之间的区别？\n\n ![](https://static.vue-js.com/c4d9bfd0-91f2-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、背景\n\n在`css`中我们通常使用px作为单位，在PC浏览器中`css`的1个像素都是对应着电脑屏幕的1个物理像素\n\n这会造成一种错觉，我们会认为`css`中的像素就是设备的物理像素\n\n但实际情况却并非如此，`css`中的像素只是一个抽象的单位，在不同的设备或不同的环境中，`css`中的1px所代表的设备物理像素是不同的\n\n当我们做移动端开发时，同为1px的设置，在不同分辨率的移动设备上显示效果却有很大差异\n\n这背后就涉及了css像素、设备像素、设备独立像素、dpr、ppi的概念\n\n## 二、介绍\n\n### CSS像素\n\nCSS像素（css pixel, px）: 适用于web编程，在 CSS 中以 px 为后缀，是一个长度单位\n\n在 CSS 规范中，长度单位可以分为两类，绝对单位以及相对单位\n\npx是一个相对单位，相对的是设备像素（device pixel）\n\n一般情况，页面缩放比为1，1个CSS像素等于1个设备独立像素\n\n`CSS`像素又具有两个方面的相对性：\n\n- 在同一个设备上，每1个 CSS 像素所代表的设备像素是可以变化的（比如调整屏幕的分辨率）\n- 在不同的设备之间，每1个 CSS 像素所代表的设备像素是可以变化的（比如两个不同型号的手机）\n\n在页面进行缩放操作也会 引起`css`中`px`的变化，假设页面放大一倍，原来的 1px 的东西变成 2px，在实际宽度不变的情况下1px 变得跟原来的 2px 的长度（长宽）一样了（元素会占据更多的设备像素）\n\n假设原来需要 320px 才能填满的宽度现在只需要 160px\n\npx会受到下面的因素的影响而变化：\n\n- 每英寸像素（PPI）\n- 设备像素比（DPR）\n\n\n### 设备像素\n\n设备像素（device pixels），又称为物理像素\n\n指设备能控制显示的最小物理单位，不一定是一个小正方形区块，也没有标准的宽高，只是用于显示丰富色彩的一个“点”而已\n\n可以参考公园里的景观变色彩灯，一个彩灯(物理像素)由红、蓝、绿小灯组成，三盏小灯不同的亮度混合出各种色彩\n\n ![](https://static.vue-js.com/cffc6570-91f2-11eb-ab90-d9ae814b240d.png)\n\n从屏幕在工厂生产出的那天起，它上面设备像素点就固定不变了，单位为`pt`\n\n\n\n### 设备独立像素\n\n设备独立像素（Device Independent Pixel）：与设备无关的逻辑像素，代表可以通过程序控制使用的虚拟像素，是一个总体概念，包括了CSS像素\n\n在`javaScript`中可以通过`window.screen.width/ window.screen.height` 查看\n\n比如我们会说“电脑屏幕在 2560x1600分辨率下不适合玩游戏，我们把它调为 1440x900”，这里的“分辨率”（非严谨说法）指的就是设备独立像素\n\n一个设备独立像素里可能包含1个或者多个物理像素点，包含的越多则屏幕看起来越清晰\n\n至于为什么出现设备独立像素这种虚拟像素单位概念，下面举个例子：\n\niPhone 3GS 和 iPhone 4/4s 的尺寸都是 3.5 寸，但 iPhone 3GS 的分辨率是 320x480，iPhone 4/4s 的分辨率是 640x960\n\n这意味着，iPhone 3GS 有 320 个物理像素，iPhone 4/4s 有 640 个物理像素\n\n如果我们按照真实的物理像素进行布局，比如说我们按照 320 物理像素进行布局，到了 640 物理像素的手机上就会有一半的空白，为了避免这种问题，就产生了虚拟像素单位\n\n我们统一 iPhone 3GS 和 iPhone 4/4s 都是 320 个虚拟像素，只是在 iPhone 3GS 上，最终 1 个虚拟像素换算成 1 个物理像素，在 iphone 4s 中，1 个虚拟像素最终换算成 2 个物理像素\n\n至于 1 个虚拟像素被换算成几个物理像素，这个数值我们称之为设备像素比，也就是下面介绍的`dpr`\n\n\n### dpr\n\ndpr（device pixel ratio），设备像素比，代表设备独立像素到设备像素的转换关系，在`JavaScript`中可以通过 `window.devicePixelRatio` 获取\n\n计算公式如下：\n\n ![](https://static.vue-js.com/dd45e2b0-91f2-11eb-ab90-d9ae814b240d.png)\n\n当设备像素比为1:1时，使用1（1×1）个设备像素显示1个CSS像素\n\n当设备像素比为2:1时，使用4（2×2）个设备像素显示1个CSS像素\n\n当设备像素比为3:1时，使用9（3×3）个设备像素显示1个CSS像素\n\n如下图所示：\n\n![](https://static.vue-js.com/e63cceb0-91f2-11eb-ab90-d9ae814b240d.png)\n\n当`dpr`为3，那么`1px`的`CSS`像素宽度对应`3px`的物理像素的宽度，1px的`CSS`像素高度对应`3px`的物理像素高度\n\n\n\n### ppi\n\nppi （pixel per inch），每英寸像素，表示每英寸所包含的像素点数目，更确切的说法应该是像素密度。数值越高，说明屏幕能以更高密度显示图像\n\n计算公式如下：\n\n ![](https://static.vue-js.com/f734adf0-91f2-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 三、总结\n\n无缩放情况下，1个CSS像素等于1个设备独立像素\n\n设备像素由屏幕生产之后就不发生改变，而设备独立像素是一个虚拟单位会发生改变\n\nPC端中，1个设备独立像素 = 1个设备像素 （在100%，未缩放的情况下）\n\n在移动端中，标准屏幕（160ppi）下 1个设备独立像素 = 1个设备像素\n\n设备像素比（dpr） = 设备像素 / 设备独立像素\n\n每英寸像素（ppi），值越大，图像越清晰\n\n\n\n## 参考文献\n\n- https://developer.mozilla.org/zh-CN/docs/Glossary/CSS_pixel\n- https://hijiangtao.github.io/2017/07/09/Device-Viewport-and-Pixel-Introduction/"
  },
  {
    "path": "docs/css/em_px_rem_vh_vw.md",
    "content": "# 面试官：说说em/px/rem/vh/vw区别?\n\n![](https://static.vue-js.com/51b036e0-9131-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、介绍\n\n传统的项目开发中，我们只会用到`px`、`%`、`em`这几个单位，它可以适用于大部分的项目开发，且拥有比较良好的兼容性\n\n从`CSS3`开始，浏览器对计量单位的支持又提升到了另外一个境界，新增了`rem`、`vh`、`vw`、`vm`等一些新的计量单位\n\n利用这些新的单位开发出比较良好的响应式页面，适应多种不同分辨率的终端，包括移动设备等\n\n## 二、单位\n\n在`css`单位中，可以分为长度单位、绝对单位，如下表所指示\n\n| CSS单位      |                                        |\n| ------------ | -------------------------------------- |\n| 相对长度单位 | em、ex、ch、rem、vw、vh、vmin、vmax、% |\n| 绝对长度单位 | cm、mm、in、px、pt、pc                 |\n\n这里我们主要讲述px、em、rem、vh、vw\n\n\n\n### px\n\npx，表示像素，所谓像素就是呈现在我们显示器上的一个个小点，每个像素点都是大小等同的，所以像素为计量单位被分在了绝对长度单位中\n\n有些人会把`px`认为是相对长度，原因在于在移动端中存在设备像素比，`px`实际显示的大小是不确定的\n\n这里之所以认为`px`为绝对单位，在于`px`的大小和元素的其他属性无关\n\n### em\n\nem是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置，则相对于浏览器的默认字体尺寸（`1em = 16px`）\n\n为了简化 `font-size` 的换算，我们需要在` css `中的 `body` 选择器中声明` font-size `= `62.5%`，这就使 em 值变为 `16px*62.5% = 10px`\n\n这样 `12px = 1.2em`, `10px = 1em`, 也就是说只需要将你的原来的` px` 数值除以 10，然后换上 `em `作为单位就行了\n\n特点：\n\n- em 的值并不是固定的\n- em 会继承父级元素的字体大小\n- em 是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置，则相对于浏览器的默认字体尺寸\n- 任意浏览器的默认字体高都是 16px\n\n\n\n举个例子\n\n```html\n<div class=\"big\">\n    我是14px=1.4rem<div class=\"small\">我是12px=1.2rem</div>\n</div>\n```\n\n样式为\n\n```css\n<style>\n    html {font-size: 10px;  } /*  公式16px*62.5%=10px  */  \n    .big{font-size: 1.4rem}\n    .small{font-size: 1.2rem}\n</style>\n```\n\n这时候`.big`元素的`font-size`为14px，而`.small`元素的`font-size`为12px\n\n\n\n\n\n### rem\n\nrem，相对单位，相对的只是HTML根元素`font-size`的值\n\n同理，如果想要简化`font-size`的转化，我们可以在根元素`html`中加入`font-size: 62.5%`\n\n```css\nhtml {font-size: 62.5%;  } /*  公式16px*62.5%=10px  */ \n```\n\n这样页面中1rem=10px、1.2rem=12px、1.4rem=14px、1.6rem=16px;使得视觉、使用、书写都得到了极大的帮助\n\n特点：\n\n- rem单位可谓集相对大小和绝对大小的优点于一身\n- 和em不同的是rem总是相对于根元素，而不像em一样使用级联的方式来计算尺寸\n\n\n\n### vh、vw\n\nvw ，就是根据窗口的宽度，分成100等份，100vw就表示满宽，50vw就表示一半宽。（vw 始终是针对窗口的宽），同理，`vh`则为窗口的高度\n\n这里的窗口分成几种情况：\n\n- 在桌面端，指的是浏览器的可视区域\n\n- 移动端指的就是布局视口\n\n像`vw`、`vh`，比较容易混淆的一个单位是`%`，不过百分比宽泛的讲是相对于父元素：\n\n- 对于普通定位元素就是我们理解的父元素\n- 对于position: absolute;的元素是相对于已定位的父元素\n- 对于position: fixed;的元素是相对于 ViewPort（可视窗口）\n\n\n\n## 三、总结\n\n**px**：绝对单位，页面按精确像素展示\n\n**em**：相对单位，基准点为父节点字体的大小，如果自身定义了`font-size`按自身来计算，整个页面内`1em`不是一个固定的值\n\n**rem**：相对单位，可理解为`root em`, 相对根节点`html`的字体大小来计算\n\n**vh、vw**：主要用于页面视口大小布局，在页面布局上更加方便简单"
  },
  {
    "path": "docs/css/flexbox.md",
    "content": "# 面试官：说说flexbox（弹性盒布局模型）,以及适用场景？\n\n ![](https://static.vue-js.com/ef25b0a0-9837-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n`Flexible Box` 简称 `flex`，意为”弹性布局”，可以简便、完整、响应式地实现各种页面布局\n\n采用Flex布局的元素，称为`flex`容器`container`\n\n它的所有子元素自动成为容器成员，称为`flex`项目`item`\n\n ![](https://static.vue-js.com/fbc5f590-9837-11eb-ab90-d9ae814b240d.png)\n\n容器中默认存在两条轴，主轴和交叉轴，呈90度关系。项目默认沿主轴排列，通过`flex-direction`来决定主轴的方向\n\n每根轴都有起点和终点，这对于元素的对齐非常重要\n\n\n\n## 二、属性\n\n关于`flex`常用的属性，我们可以划分为容器属性和容器成员属性\n\n容器属性有：\n\n- flex-direction\n- flex-wrap\n- flex-flow\n- justify-content\n- align-items\n- align-content\n\n\n\n### flex-direction\n\n决定主轴的方向(即项目的排列方向)\n\n```css\n.container {   \n    flex-direction: row | row-reverse | column | column-reverse;  \n} \n```\n\n属性对应如下：\n\n- row（默认值）：主轴为水平方向，起点在左端\n- row-reverse：主轴为水平方向，起点在右端\n- column：主轴为垂直方向，起点在上沿。\n- column-reverse：主轴为垂直方向，起点在下沿\n\n如下图所示：\n\n ![](https://static.vue-js.com/0c9abc70-9838-11eb-ab90-d9ae814b240d.png)\n\n\n\n### flex-wrap\n\n弹性元素永远沿主轴排列，那么如果主轴排不下，通过`flex-wrap`决定容器内项目是否可换行\n\n```css\n.container {  \n    flex-wrap: nowrap | wrap | wrap-reverse;\n}  \n```\n\n属性对应如下：\n\n- nowrap（默认值）：不换行\n- wrap：换行，第一行在下方\n- wrap-reverse：换行，第一行在上方\n\n默认情况是不换行，但这里也不会任由元素直接溢出容器，会涉及到元素的弹性伸缩\n\n\n\n### flex-flow\n\n是`flex-direction`属性和`flex-wrap`属性的简写形式，默认值为`row nowrap`\n\n```css\n.box {\n  flex-flow: <flex-direction> || <flex-wrap>;\n}\n```\n\n\n\n### justify-content\n\n定义了项目在主轴上的对齐方式\n\n```css\n.box {\n    justify-content: flex-start | flex-end | center | space-between | space-around;\n}\n```\n\n属性对应如下：\n\n- flex-start（默认值）：左对齐\n- flex-end：右对齐\n- center：居中\n- space-between：两端对齐，项目之间的间隔都相等\n- space-around：两个项目两侧间隔相等\n\n效果图如下：\n\n ![](https://static.vue-js.com/2d5ca950-9838-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### align-items\n\n定义项目在交叉轴上如何对齐\n\n```css\n.box {\n  align-items: flex-start | flex-end | center | baseline | stretch;\n}\n```\n\n属性对应如下：\n\n- flex-start：交叉轴的起点对齐\n- flex-end：交叉轴的终点对齐\n- center：交叉轴的中点对齐\n- baseline: 项目的第一行文字的基线对齐\n- stretch（默认值）：如果项目未设置高度或设为auto，将占满整个容器的高度\n\n\n\n### align-content\n\n定义了多根轴线的对齐方式。如果项目只有一根轴线，该属性不起作用\n\n```css\n.box {\n    align-content: flex-start | flex-end | center | space-between | space-around | stretch;\n}\n```\n\n属性对应如吓：\n\n- flex-start：与交叉轴的起点对齐\n- flex-end：与交叉轴的终点对齐\n- center：与交叉轴的中点对齐\n- space-between：与交叉轴两端对齐，轴线之间的间隔平均分布\n- space-around：每根轴线两侧的间隔都相等。所以，轴线之间的间隔比轴线与边框的间隔大一倍\n- stretch（默认值）：轴线占满整个交叉轴\n\n效果图如下：\n\n ![](https://static.vue-js.com/39bcb0f0-9838-11eb-ab90-d9ae814b240d.png)\n\n\n\n容器成员属性如下：\n\n- `order`\n- `flex-grow`\n- `flex-shrink`\n- `flex-basis`\n- `flex`\n- `align-self`\n\n\n\n### order\n\n定义项目的排列顺序。数值越小，排列越靠前，默认为0\n\n```css\n.item {\n    order: <integer>;\n}\n```\n\n\n\n### flex-grow\n\n上面讲到当容器设为`flex-wrap: nowrap;`不换行的时候，容器宽度有不够分的情况，弹性元素会根据`flex-grow`来决定\n\n定义项目的放大比例（容器宽度>元素总宽度时如何伸展）\n\n默认为`0`，即如果存在剩余空间，也不放大\n\n```css\n.item {\n    flex-grow: <number>;\n}\n```\n\n如果所有项目的`flex-grow`属性都为1，则它们将等分剩余空间（如果有的话）\n\n ![](https://static.vue-js.com/48c8c5c0-9838-11eb-ab90-d9ae814b240d.png)\n\n如果一个项目的`flex-grow`属性为2，其他项目都为1，则前者占据的剩余空间将比其他项多一倍\n\n ![](https://static.vue-js.com/5b822b20-9838-11eb-ab90-d9ae814b240d.png)\n\n弹性容器的宽度正好等于元素宽度总和，无多余宽度，此时无论`flex-grow`是什么值都不会生效\n\n\n\n### flex-shrink\n\n定义了项目的缩小比例（容器宽度<元素总宽度时如何收缩），默认为1，即如果空间不足，该项目将缩小\n\n```css\n.item {\n    flex-shrink: <number>; /* default 1 */\n}\n```\n\n如果所有项目的`flex-shrink`属性都为1，当空间不足时，都将等比例缩小\n\n如果一个项目的`flex-shrink`属性为0，其他项目都为1，则空间不足时，前者不缩小\n\n ![](https://static.vue-js.com/658c5be0-9838-11eb-85f6-6fac77c0c9b3.png)\n\n在容器宽度有剩余时，`flex-shrink`也是不会生效的\n\n\n\n\n\n### flex-basis\n\n设置的是元素在主轴上的初始尺寸，所谓的初始尺寸就是元素在`flex-grow`和`flex-shrink`生效前的尺寸\n\n浏览器根据这个属性，计算主轴是否有多余空间，默认值为`auto`，即项目的本来大小，如设置了`width`则元素尺寸由`width/height`决定（主轴方向），没有设置则由内容决定\n\n ```css\n.item {\n    flex-basis: <length> | auto; /* default auto */\n}\n ```\n\n当设置为0的是，会根据内容撑开\n\n它可以设为跟`width`或`height`属性一样的值（比如350px），则项目将占据固定空间\n\n\n\n### flex\n\n`flex`属性是`flex-grow`, `flex-shrink` 和 `flex-basis`的简写，默认值为`0 1 auto`，也是比较难懂的一个复合属性\n\n ```css\n .item {\n   flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]\n }\n ```\n\n一些属性有：\n\n- flex: 1 = flex: 1 1 0%\n- flex: 2 = flex: 2 1 0%\n- flex: auto = flex: 1 1 auto\n- flex: none = flex: 0 0 auto，常用于固定尺寸不伸缩\n\n\n\n`flex:1` 和 `flex:auto` 的区别，可以归结于`flex-basis:0`和`flex-basis:auto`的区别\n\n当设置为0时（绝对弹性元素），此时相当于告诉`flex-grow`和`flex-shrink`在伸缩的时候不需要考虑我的尺寸\n\n当设置为`auto`时（相对弹性元素），此时则需要在伸缩时将元素尺寸纳入考虑\n\n注意：建议优先使用这个属性，而不是单独写三个分离的属性，因为浏览器会推算相关值\n\n\n\n### align-self\n\n允许单个项目有与其他项目不一样的对齐方式，可覆盖`align-items`属性\n\n默认值为`auto`，表示继承父元素的`align-items`属性，如果没有父元素，则等同于`stretch`\n\n```css\n.item {\n    align-self: auto | flex-start | flex-end | center | baseline | stretch;\n}\n```\n\n效果图如下：\n\n ![](https://static.vue-js.com/6f8304a0-9838-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 三、应用场景\n\n在以前的文章中，我们能够通过`flex`简单粗暴的实现元素水平垂直方向的居中，以及在两栏三栏自适应布局中通过`flex`完成，这里就不再展开代码的演示\n\n包括现在在移动端、小程序这边的开发，都建议使用`flex`进行布局\n\n\n\n## 参考文献\n- https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex\n- http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html\n"
  },
  {
    "path": "docs/css/grid.md",
    "content": "# 面试官：介绍一下grid网格布局\n\n ![](https://static.vue-js.com/4d73e3d0-9a94-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、是什么\n\n`Grid` 布局即网格布局，是一个二维的布局方式，由纵横相交的两组网格线形成的框架性布局结构，能够同时处理行与列\n\n擅长将一个页面划分为几个主要区域，以及定义这些区域的大小、位置、层次等关系\n\n ![](https://static.vue-js.com/59680a40-9a94-11eb-85f6-6fac77c0c9b3.png)\n\n这与之前讲到的`flex`一维布局不相同\n\n设置`display:grid/inline-grid`的元素就是网格布局容器，这样就能出发浏览器渲染引擎的网格布局算法\n\n```js\n<div class=\"container\">\n    <div class=\"item item-1\">\n        <p class=\"sub-item\"></p >\n </div>\n    <div class=\"item item-2\"></div>\n    <div class=\"item item-3\"></div>\n</div> \n```\n\n上述代码实例中，`.container`元素就是网格布局容器，`.item`元素就是网格的项目，由于网格元素只能是容器的顶层子元素，所以`p`元素并不是网格元素\n\n这里提一下，网格线概念，有助于下面对`grid-column`系列属性的理解\n\n网格线，即划分网格的线，如下图所示：\n\n ![](https://static.vue-js.com/61be7080-9a94-11eb-ab90-d9ae814b240d.png)\n\n上图是一个 2 x 3 的网格，共有3根水平网格线和4根垂直网格线\n\n\n## 二、属性\n\n同样，`Grid` 布局属性可以分为两大类：\n\n- 容器属性，\n- 项目属性\n\n\n\n关于容器属性有如下：\n\n### display 属性\n\n文章开头讲到，在元素上设置`display：grid` 或 `display：inline-grid` 来创建一个网格容器\n\n- display：grid 则该容器是一个块级元素\n\n- display: inline-grid 则容器元素为行内元素\n\n\n\n### grid-template-columns 属性，grid-template-rows 属性\n\n`grid-template-columns` 属性设置列宽，`grid-template-rows` 属性设置行高\n\n```css\n.wrapper {\n  display: grid;\n  /*  声明了三列，宽度分别为 200px 200px 200px */\n  grid-template-columns: 200px 200px 200px;\n  grid-gap: 5px;\n  /*  声明了两行，行高分别为 50px 50px  */\n  grid-template-rows: 50px 50px;\n}\n```\n\n以上表示固定列宽为 200px 200px 200px，行高为 50px 50px\n\n上述代码可以看到重复写单元格宽高，通过使用`repeat()`函数，可以简写重复的值\n\n- 第一个参数是重复的次数\n- 第二个参数是重复的值\n\n所以上述代码可以简写成\n\n```css\n.wrapper {\n  display: grid;\n  grid-template-columns: repeat(3,200px);\n  grid-gap: 5px;\n  grid-template-rows:repeat(2,50px);\n}\n```\n\n除了上述的`repeact`关键字，还有：\n\n- auto-fill：示自动填充，让一行（或者一列）中尽可能的容纳更多的单元格\n\n>`grid-template-columns: repeat(auto-fill, 200px)` 表示列宽是 200 px，但列的数量是不固定的，只要浏览器能够容纳得下，就可以放置元素\n\n- fr：片段，为了方便表示比例关系\n\n>`grid-template-columns: 200px 1fr 2fr` 表示第一个列宽设置为 200px，后面剩余的宽度分为两部分，宽度分别为剩余宽度的 1/3 和 2/3\n\n- minmax：产生一个长度范围，表示长度就在这个范围之中都可以应用到网格项目中。第一个参数就是最小值，第二个参数就是最大值\n\n>`minmax(100px, 1fr)`表示列宽不小于`100px`，不大于`1fr`\n\n- auto：由浏览器自己决定长度\n\n>`grid-template-columns: 100px auto 100px` 表示第一第三列为 100px，中间由浏览器决定长度\n\n\n\n### grid-row-gap 属性， grid-column-gap 属性， grid-gap 属性\n\n`grid-row-gap` 属性、`grid-column-gap` 属性分别设置行间距和列间距。`grid-gap` 属性是两者的简写形式\n\n`grid-row-gap: 10px` 表示行间距是 10px\n\n`grid-column-gap: 20px` 表示列间距是 20px\n\n`grid-gap: 10px 20px` 等同上述两个属性\n\n\n\n### grid-template-areas 属性\n\n用于定义区域，一个区域由一个或者多个单元格组成\n\n```css\n.container {\n  display: grid;\n  grid-template-columns: 100px 100px 100px;\n  grid-template-rows: 100px 100px 100px;\n  grid-template-areas: 'a b c'\n                       'd e f'\n                       'g h i';\n}\n```\n\n上面代码先划分出9个单元格，然后将其定名为`a`到`i`的九个区域，分别对应这九个单元格。\n\n多个单元格合并成一个区域的写法如下\n\n ```css\n grid-template-areas: 'a a a'\n                      'b b b'\n                      'c c c';\n ```\n\n上面代码将9个单元格分成`a`、`b`、`c`三个区域\n\n如果某些区域不需要利用，则使用\"点\"（`.`）表示\n\n\n\n### grid-auto-flow 属性\n\n划分网格以后，容器的子元素会按照顺序，自动放置在每一个网格。\n\n顺序就是由`grid-auto-flow`决定，默认为行，代表\"先行后列\"，即先填满第一行，再开始放入第二行\n\n ![](https://static.vue-js.com/70fb3240-9a94-11eb-ab90-d9ae814b240d.png)\n\n当修改成`column`后，放置变为如下：\n\n![](https://static.vue-js.com/7c26ffa0-9a94-11eb-ab90-d9ae814b240d.png)\n\n\n\n### justify-items 属性， align-items 属性， place-items 属性\n\n`justify-items` 属性设置单元格内容的水平位置（左中右），`align-items` 属性设置单元格的垂直位置（上中下）\n\n两者属性的值完成相同\n\n```css\n.container {\n  justify-items: start | end | center | stretch;\n  align-items: start | end | center | stretch;\n}\n```\n\n属性对应如下：\n\n- start：对齐单元格的起始边缘\n- end：对齐单元格的结束边缘\n- center：单元格内部居中\n- stretch：拉伸，占满单元格的整个宽度（默认值）\n\n`place-items`属性是`align-items`属性和`justify-items`属性的合并简写形式\n\n\n\n### justify-content 属性， align-content 属性， place-content 属性\n\n`justify-content`属性是整个内容区域在容器里面的水平位置（左中右），`align-content`属性是整个内容区域的垂直位置（上中下）\n\n```css\n.container {\n  justify-content: start | end | center | stretch | space-around | space-between | space-evenly;\n  align-content: start | end | center | stretch | space-around | space-between | space-evenly;  \n}\n```\n\n两个属性的写法完全相同，都可以取下面这些值：\n\n- start - 对齐容器的起始边框\n- end - 对齐容器的结束边框\n- center - 容器内部居中\n\n ![](https://static.vue-js.com/9d1ec990-9a94-11eb-ab90-d9ae814b240d.png)\n\n- space-around - 每个项目两侧的间隔相等。所以，项目之间的间隔比项目与容器边框的间隔大一倍\n\n- space-between - 项目与项目的间隔相等，项目与容器边框之间没有间隔\n\n- space-evenly - 项目与项目的间隔相等，项目与容器边框之间也是同样长度的间隔\n\n- stretch - 项目大小没有指定时，拉伸占据整个网格容器\n\n ![](https://static.vue-js.com/a620b210-9a94-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### grid-auto-columns 属性和 grid-auto-rows 属性\n\n有时候，一些项目的指定位置，在现有网格的外部，就会产生显示网格和隐式网格\n\n比如网格只有3列，但是某一个项目指定在第5行。这时，浏览器会自动生成多余的网格，以便放置项目。超出的部分就是隐式网格\n\n而`grid-auto-rows`与`grid-auto-columns`就是专门用于指定隐式网格的宽高\n\n\n\n\n\n关于项目属性，有如下：\n\n\n### grid-column-start 属性、grid-column-end 属性、grid-row-start 属性以及grid-row-end 属性\n\n指定网格项目所在的四个边框，分别定位在哪根网格线，从而指定项目的位置\n\n- grid-column-start 属性：左边框所在的垂直网格线\n- grid-column-end 属性：右边框所在的垂直网格线\n- grid-row-start 属性：上边框所在的水平网格线\n- grid-row-end 属性：下边框所在的水平网格线\n\n举个例子：\n\n```html\n<style>\n    #container{\n        display: grid;\n        grid-template-columns: 100px 100px 100px;\n        grid-template-rows: 100px 100px 100px;\n    }\n    .item-1 {\n        grid-column-start: 2;\n        grid-column-end: 4;\n    }\n</style>\n\n<div id=\"container\">\n    <div class=\"item item-1\">1</div>\n    <div class=\"item item-2\">2</div>\n    <div class=\"item item-3\">3</div>\n</div>\n```\n\n通过设置`grid-column`属性，指定1号项目的左边框是第二根垂直网格线，右边框是第四根垂直网格线\n\n ![](https://static.vue-js.com/b7925530-9a94-11eb-ab90-d9ae814b240d.png)\n\n\n\n\n\n### grid-area 属性\n\n`grid-area` 属性指定项目放在哪一个区域\n\n```css\n.item-1 {\n  grid-area: e;\n}\n```\n\n意思为将1号项目位于`e`区域\n\n与上述讲到的`grid-template-areas`搭配使用\n\n\n\n### justify-self 属性、align-self 属性以及 place-self 属性\n\n`justify-self`属性设置单元格内容的水平位置（左中右），跟`justify-items`属性的用法完全一致，但只作用于单个项目。\n\n`align-self`属性设置单元格内容的垂直位置（上中下），跟`align-items`属性的用法完全一致，也是只作用于单个项目\n\n ```css\n .item {\n   justify-self: start | end | center | stretch;\n   align-self: start | end | center | stretch;\n }\n ```\n\n这两个属性都可以取下面四个值。\n\n - start：对齐单元格的起始边缘。\n  - end：对齐单元格的结束边缘。\n - center：单元格内部居中。\n - stretch：拉伸，占满单元格的整个宽度（默认值）\n\n\n\n## 三、应用场景\n\n文章开头就讲到，`Grid`是一个强大的布局，如一些常见的 CSS 布局，如居中，两列布局，三列布局等等是很容易实现的，在以前的文章中，也有使用`Grid`布局完成对应的功能\n\n关于兼容性问题，结果如下：\n\n ![](https://static.vue-js.com/c24a2b10-9a94-11eb-85f6-6fac77c0c9b3.png)\n\n总体兼容性还不错，但在 IE 10 以下不支持\n\n目前，`Grid`布局在手机端支持还不算太友好\n\n\n\n## 参考文献\n\n- https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Grid_Layout\n- https://www.ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html\n- https://juejin.cn/post/6854573220306255880#heading-2"
  },
  {
    "path": "docs/css/hide_attributes.md",
    "content": "# 面试官：css中，有哪些方式可以隐藏页面元素？区别?\n\n![](https://static.vue-js.com/ccf96f50-929a-11eb-ab90-d9ae814b240d.png)\n\n## 一、前言\n\n在平常的样式排版中，我们经常遇到将某个模块隐藏的场景\n\n通过`css`隐藏元素的方法有很多种，它们看起来实现的效果是一致的\n\n但实际上每一种方法都有一丝轻微的不同，这些不同决定了在一些特定场合下使用哪一种方法\n\n## 二、实现方式\n\n通过`css`实现隐藏元素方法有如下：\n\n- display:none\n- visibility:hidden\n- opacity:0\n- 设置height、width模型属性为0\n- position:absolute\n- clip-path\n\n### display:none\n\n设置元素的`display`为`none`是最常用的隐藏元素的方法\n\n```css\n.hide {\n    display:none;\n}\n```\n\n将元素设置为`display:none`后，元素在页面上将彻底消失\n\n元素本身占有的空间就会被其他元素占有，也就是说它会导致浏览器的重排和重绘\n\n消失后，自身绑定的事件不会触发，也不会有过渡效果\n\n特点：元素不可见，不占据空间，无法响应点击事件\n\n### visibility:hidden\n\n设置元素的`visibility`为`hidden`也是一种常用的隐藏元素的方法\n\n从页面上仅仅是隐藏该元素，DOM结果均会存在，只是当时在一个不可见的状态，不会触发重排，但是会触发重绘\n\n```css\n.hidden{\n    visibility:hidden\n}\n```\n\n给人的效果是隐藏了，所以他自身的事件不会触发\n\n特点：元素不可见，占据页面空间，无法响应点击事件\n\n\n### opacity:0\n\n`opacity`属性表示元素的透明度，将元素的透明度设置为0后，在我们用户眼中，元素也是隐藏的\n\n不会引发重排，一般情况下也会引发重绘\n\n> 如果利用 animation 动画，对 opacity 做变化（animation会默认触发GPU加速），则只会触发 GPU 层面的 composite，不会触发重绘\n\n```css\n.transparent {\n    opacity:0;\n}\n```\n\n由于其仍然是存在于页面上的，所以他自身的的事件仍然是可以触发的，但被他遮挡的元素是不能触发其事件的\n\n需要注意的是：其子元素不能设置opacity来达到显示的效果\n\n特点：改变元素透明度，元素不可见，占据页面空间，可以响应点击事件\n\n\n\n### 设置height、width属性为0\n\n将元素的`margin`，`border`，`padding`，`height`和`width`等影响元素盒模型的属性设置成0，如果元素内有子元素或内容，还应该设置其`overflow:hidden`来隐藏其子元素\n\n```css\n.hiddenBox {\n    margin:0;     \n    border:0;\n    padding:0;\n    height:0;\n    width:0;\n    overflow:hidden;\n}\n```\n\n特点：元素不可见，不占据页面空间，无法响应点击事件\n\n\n\n### position:absolute\n\n将元素移出可视区域\n\n```css\n.hide {\n   position: absolute;\n   top: -9999px;\n   left: -9999px;\n}\n```\n\n特点：元素不可见，不影响页面布局\n\n\n### clip-path\n\n通过裁剪的形式\n\n```css\n.hide {\n  clip-path: polygon(0px 0px,0px 0px,0px 0px,0px 0px);\n}\n```\n\n特点：元素不可见，占据页面空间，无法响应点击事件\n\n\n### 小结\n\n最常用的还是`display:none`和`visibility:hidden`，其他的方式只能认为是奇招，它们的真正用途并不是用于隐藏元素，所以并不推荐使用它们\n\n\n## 三、区别\n\n关于`display: none`、`  visibility: hidden`、`opacity: 0`的区别，如下表所示：\n\n|                        | display: none | visibility: hidden | opacity: 0 |\n| :--------------------- | :------------ | :----------------- | ---------- |\n| 页面中                 | 不存在        | 存在               | 存在       |\n| 重排                   | 会            | 不会               | 不会       |\n| 重绘                   | 会            | 会                 | 不一定     |\n| 自身绑定事件           | 不触发        | 不触发             | 可触发     |\n| transition             | 不支持        | 支持               | 支持       |\n| 子元素可复原           | 不能          | 能                 | 不能       |\n| 被遮挡的元素可触发事件 | 能            | 能                 | 不能       |\n\n\n## 参考文献\n\n- https://www.cnblogs.com/a-cat/p/9039962.html"
  },
  {
    "path": "docs/css/layout_painting.md",
    "content": "# 面试官：怎么理解回流跟重绘？什么场景下会触发？\n\n ![](https://static.vue-js.com/1ed5d340-9cdc-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n在`HTML`中，每个元素都可以理解成一个盒子，在浏览器解析过程中，会涉及到回流与重绘：\n\n- 回流：布局引擎会根据各种样式计算每个盒子在页面上的大小与位置\n\n- 重绘：当计算好盒模型的位置、大小及其他属性后，浏览器根据每个盒子特性进行绘制\n\n具体的浏览器解析渲染机制如下所示：\n\n ![](https://static.vue-js.com/2b56a950-9cdc-11eb-ab90-d9ae814b240d.png)\n\n- 解析HTML，生成DOM树，解析CSS，生成CSSOM树\n\n- 将DOM树和CSSOM树结合，生成渲染树(Render Tree)\n- Layout(回流):根据生成的渲染树，进行回流(Layout)，得到节点的几何信息（位置，大小）\n- Painting(重绘):根据渲染树以及回流得到的几何信息，得到节点的绝对像素\n- Display:将像素发送给GPU，展示在页面上\n\n\n\n在页面初始渲染阶段，回流不可避免的触发，可以理解成页面一开始是空白的元素，后面添加了新的元素使页面布局发生改变\n\n当我们对 `DOM` 的修改引发了 `DOM `几何尺寸的变化（比如修改元素的宽、高或隐藏元素等）时，浏览器需要重新计算元素的几何属性，然后再将计算的结果绘制出来\n\n当我们对 `DOM `的修改导致了样式的变化（`color`或`background-color`），却并未影响其几何属性时，浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式，这里就仅仅触发了重绘\n\n\n\n## 二、如何触发\n\n要想减少回流和重绘的次数，首先要了解回流和重绘是如何触发的\n\n### 回流触发时机\n\n回流这一阶段主要是计算节点的位置和几何信息，那么当页面布局和几何信息发生变化的时候，就需要回流，如下面情况：\n\n- 添加或删除可见的DOM元素\n- 元素的位置发生变化\n- 元素的尺寸发生变化（包括外边距、内边框、边框大小、高度和宽度等）\n- 内容发生变化，比如文本变化或图片被另一个不同尺寸的图片所替代\n- 页面一开始渲染的时候（这避免不了）\n- 浏览器的窗口尺寸变化（因为回流是根据视口的大小来计算元素的位置和大小的）\n\n还有一些容易被忽略的操作：获取一些特定属性的值\n\n> offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight\n\n这些属性有一个共性，就是需要通过即时计算得到。因此浏览器为了获取这些值，也会进行回流\n\n除此还包括`getComputedStyle `方法，原理是一样的\n\n\n\n### 重绘触发时机\n\n触发回流一定会触发重绘\n\n可以把页面理解为一个黑板，黑板上有一朵画好的小花。现在我们要把这朵从左边移到了右边，那我们要先确定好右边的具体位置，画好形状（回流），再画上它原有的颜色（重绘）\n\n除此之外还有一些其他引起重绘行为：\n\n- 颜色的修改\n\n- 文本方向的修改\n- 阴影的修改\n\n\n\n### 浏览器优化机制\n\n由于每次重排都会造成额外的计算消耗，因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里，直到过了一段时间或者操作达到了一个阈值，才清空队列\n\n当你获取布局信息的操作的时候，会强制队列刷新，包括前面讲到的`offsetTop`等方法都会返回最新的数据\n\n因此浏览器不得不清空队列，触发回流重绘来返回正确的值\n\n\n\n## 三、如何减少\n\n我们了解了如何触发回流和重绘的场景，下面给出避免回流的经验：\n\n- 如果想设定元素的样式，通过改变元素的 `class` 类名 (尽可能在 DOM 树的最里层)\n- 避免设置多项内联样式\n- 应用元素的动画，使用 `position` 属性的 `fixed` 值或 `absolute` 值(如前文示例所提)\n- 避免使用 `table` 布局，`table` 中每个元素的大小以及内容的改动，都会导致整个 `table` 的重新计算\n- 对于那些复杂的动画，对其设置 `position: fixed/absolute`，尽可能地使元素脱离文档流，从而减少对其他元素的影响\n- 使用css3硬件加速，可以让`transform`、`opacity`、`filters`这些动画不会引起回流重绘\n- 避免使用 CSS 的 `JavaScript` 表达式\n\n在使用 `JavaScript` 动态插入多个节点时, 可以使用`DocumentFragment`. 创建后一次插入. 就能避免多次的渲染性能\n\n但有时候，我们会无可避免地进行回流或者重绘，我们可以更好使用它们\n\n例如，多次修改一个把元素布局的时候，我们很可能会如下操作\n\n```js\nconst el = document.getElementById('el')\nfor(let i=0;i<10;i++) {\n    el.style.top  = el.offsetTop  + 10 + \"px\";\n    el.style.left = el.offsetLeft + 10 + \"px\";\n}\n```\n\n每次循环都需要获取多次`offset`属性，比较糟糕，可以使用变量的形式缓存起来，待计算完毕再提交给浏览器发出重计算请求\n\n```js\n// 缓存offsetLeft与offsetTop的值\nconst el = document.getElementById('el')\nlet offLeft = el.offsetLeft, offTop = el.offsetTop\n\n// 在JS层面进行计算\nfor(let i=0;i<10;i++) {\n  offLeft += 10\n  offTop  += 10\n}\n\n// 一次性将计算结果应用到DOM上\nel.style.left = offLeft + \"px\"\nel.style.top = offTop  + \"px\"\n```\n\n我们还可避免改变样式，使用类名去合并样式\n\n```js\nconst container = document.getElementById('container')\ncontainer.style.width = '100px'\ncontainer.style.height = '200px'\ncontainer.style.border = '10px solid red'\ncontainer.style.color = 'red'\n```\n\n使用类名去合并样式\n\n```html\n<style>\n    .basic_style {\n        width: 100px;\n        height: 200px;\n        border: 10px solid red;\n        color: red;\n    }\n</style>\n<script>\n    const container = document.getElementById('container')\n    container.classList.add('basic_style')\n</script>\n```\n\n前者每次单独操作，都去触发一次渲染树更改（新浏览器不会），\n\n都去触发一次渲染树更改，从而导致相应的回流与重绘过程\n\n合并之后，等于我们将所有的更改一次性发出\n\n我们还可以通过通过设置元素属性`display: none`，将其从页面上去掉，然后再进行后续操作，这些后续操作也不会触发回流与重绘，这个过程称为离线操作\n\n```js\nconst container = document.getElementById('container')\ncontainer.style.width = '100px'\ncontainer.style.height = '200px'\ncontainer.style.border = '10px solid red'\ncontainer.style.color = 'red'\n```\n\n离线操作后\n\n```js\nlet container = document.getElementById('container')\ncontainer.style.display = 'none'\ncontainer.style.width = '100px'\ncontainer.style.height = '200px'\ncontainer.style.border = '10px solid red'\ncontainer.style.color = 'red'\n...（省略了许多类似的后续操作）\ncontainer.style.display = 'block'\n```\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903942137053192\n\n- https://segmentfault.com/a/1190000017329980"
  },
  {
    "path": "docs/css/less_12px.md",
    "content": "# 面试官：让Chrome支持小于12px 的文字方式有哪些？区别？\n\n![](https://static.vue-js.com/62945fd0-a334-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、背景\n\nChrome 中文版浏览器会默认设定页面的最小字号是12px，英文版没有限制\n\n原由 Chrome 团队认为汉字小于12px就会增加识别难度\n\n- 中文版浏览器\n\n与网页语言无关，取决于用户在Chrome的设置里（chrome://settings/languages）把哪种语言设置为默认显示语言\n\n- 系统级最小字号\n\n浏览器默认设定页面的最小字号，用户可以前往 chrome://settings/fonts 根据需求更改\n\n而我们在实际项目中，不能奢求用户更改浏览器设置\n\n对于文本需要以更小的字号来显示，就需要用到一些小技巧\n\n\n## 二、解决方案\n\n常见的解决方案有：\n\n- zoom\n-  -webkit-transform:scale()\n-  -webkit-text-size-adjust:none\n\n### Zoom\n\n`zoom` 的字面意思是“变焦”，可以改变页面上元素的尺寸，属于真实尺寸\n\n其支持的值类型有：\n\n- zoom:50%，表示缩小到原来的一半\n- zoom:0.5，表示缩小到原来的一半\n\n使用 `zoom` 来”支持“ 12px 以下的字体\n\n代码如下：\n\n```html\n<style type=\"text/css\">\n    .span1{\n        font-size: 12px;\n        display: inline-block;\n        zoom: 0.8;\n    }\n    .span2{\n        display: inline-block;\n        font-size: 12px;\n    }\n</style>\n<body>\n    <span class=\"span1\">测试10px</span>\n    <span class=\"span2\">测试12px</span>\n</body>\n```\n\n效果如下：\n\n ![](https://static.vue-js.com/d5243980-a334-11eb-ab90-d9ae814b240d.png)\n\n> 需要注意的是，`Zoom` 并不是标准属性，需要考虑其兼容性\n\n ![image.png](https://static.vue-js.com/3defe3c0-a343-11eb-85f6-6fac77c0c9b3.png)\n\n\n### -webkit-transform:scale()\n\n针对`chrome`浏览器,加`webkit`前缀，用`transform:scale()`这个属性进行放缩\n\n注意的是，使用`scale`属性只对可以定义宽高的元素生效，所以，下面代码中将`span`元素转为行内块元素\n\n实现代码如下：\n\n```html\n<style type=\"text/css\">\n    .span1{\n        font-size: 12px;\n        display: inline-block;\n        -webkit-transform:scale(0.8);\n    }\n    .span2{\n        display: inline-block;\n        font-size: 12px;\n    }\n</style>\n<body>\n    <span class=\"span1\">测试10px</span>\n    <span class=\"span2\">测试12px</span>\n</body>\n```\n\n效果如下：\n\n ![](https://static.vue-js.com/d5243980-a334-11eb-ab90-d9ae814b240d.png)\n\n\n### -webkit-text-size-adjust:none\n\n该属性用来设定文字大小是否根据设备(浏览器)来自动调整显示大小\n\n属性值：\n\n- percentage：字体显示的大小；\n- auto：默认，字体大小会根据设备/浏览器来自动调整；\n- none:字体大小不会自动调整\n\n```css\nhtml { -webkit-text-size-adjust: none; }\n```\n\n这样设置之后会有一个问题，就是当你放大网页时，一般情况下字体也会随着变大，而设置了以上代码后，字体只会显示你当前设置的字体大小，不会随着网页放大而变大了\n\n所以，我们不建议全局应用该属性，而是单独对某一属性使用\n\n> 需要注意的是，自从`chrome 27`之后，就取消了对这个属性的支持。同时，该属性只对英文、数字生效，对中文不生效\n\n## 三、总结\n\n`Zoom` 非标属性，有兼容问题，缩放会改变了元素占据的空间大小，触发重排\n\n`-webkit-transform:scale()` 大部分现代浏览器支持，并且对英文、数字、中文也能够生效，缩放不会改变了元素占据的空间大小，页面布局不会发生变化\n\n`-webkit-text-size-adjust`对谷歌浏览器有版本要求，在27之后，就取消了该属性的支持，并且只对英文、数字生效\n\n## 参考文献\n\n- https://developer.mozilla.org/zh-CN/docs/Web/CSS/text-size-adjust\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/css/responsive_layout.md",
    "content": "# 面试官：什么是响应式设计？响应式设计的基本原理是什么？如何做？\n\n ![](https://static.vue-js.com/a57e2e40-9dba-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n响应式网站设计（Responsive Web design）是一种网络页面设计布局，页面的设计与开发应当根据用户行为以及设备环境(系统平台、屏幕尺寸、屏幕定向等)进行相应的响应和调整\n\n描述响应式界面最著名的一句话就是“Content is like water”\n\n大白话便是“如果将屏幕看作容器，那么内容就像水一样”\n\n响应式网站常见特点：\n\n- 同时适配PC + 平板 + 手机等\n\n- 标签导航在接近手持终端设备时改变为经典的抽屉式导航\n\n- 网站的布局会根据视口来调整模块的大小和位置\n\n ![](https://static.vue-js.com/ae68be30-9dba-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 二、实现方式\n\n响应式设计的基本原理是通过媒体查询检测不同的设备屏幕尺寸做处理，为了处理移动端，页面头部必须有`meta`声明`viewport`\n\n```html\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no”>\n```\n\n属性对应如下：\n\n- width=device-width: 是自适应手机屏幕的尺寸宽度\n\n- maximum-scale:是缩放比例的最大值\n\n- inital-scale:是缩放的初始化\n\n- user-scalable:是用户的可以缩放的操作\n\n\n\n实现响应式布局的方式有如下：\n\n- 媒体查询\n- 百分比\n- vw/vh\n- rem\n\n\n\n### 媒体查询\n\n`CSS3 `中的增加了更多的媒体查询，就像`if`条件表达式一样，我们可以设置不同类型的媒体条件，并根据对应的条件，给相应符合条件的媒体调用相对应的样式表\n\n使用`@Media`查询，可以针对不同的媒体类型定义不同的样式，如：\n\n```css\n@media screen and (max-width: 1920px) { ... }\n```\n\n当视口在375px - 600px之间，设置特定字体大小18px\n\n```css\n@media screen (min-width: 375px) and (max-width: 600px) {\n  body {\n    font-size: 18px;\n  }\n}\n```\n\n通过媒体查询，可以通过给不同分辨率的设备编写不同的样式来实现响应式的布局，比如我们为不同分辨率的屏幕，设置不同的背景图片\n\n比如给小屏幕手机设置@2x图，为大屏幕手机设置@3x图，通过媒体查询就能很方便的实现\n\n\n\n### 百分比\n\n通过百分比单位 \" % \" 来实现响应式的效果\n\n 比如当浏览器的宽度或者高度发生变化时，通过百分比单位，可以使得浏览器中的组件的宽和高随着浏览器的变化而变化，从而实现响应式的效果\n\n`height`、`width`属性的百分比依托于父标签的宽高，但是其他盒子属性则不完全依赖父元素：\n\n- 子元素的top/left和bottom/right如果设置百分比，则相对于直接非static定位(默认定位)的父元素的高度/宽度\n\n- 子元素的padding如果设置百分比，不论是垂直方向或者是水平方向，都相对于直接父亲元素的width，而与父元素的height无关。\n\n- 子元素的margin如果设置成百分比，不论是垂直方向还是水平方向，都相对于直接父元素的width\n\n- border-radius不一样，如果设置border-radius为百分比，则是相对于自身的宽度\n\n可以看到每个属性都使用百分比，会照成布局的复杂度，所以不建议使用百分比来实现响应式\n\n\n\n\n\n### vw/vh\n\n`vw`表示相对于视图窗口的宽度，`vh`表示相对于视图窗口高度。 任意层级元素，在使用`vw`单位的情况下，`1vw`都等于视图宽度的百分之一\n\n与百分比布局很相似，在以前文章提过与`%`的区别，这里就不再展开述说\n\n\n\n### rem\n\n在以前也讲到，`rem`是相对于根元素`html`的`font-size`属性，默认情况下浏览器字体大小为`16px`，此时`1rem = 16px`\n\n可以利用前面提到的媒体查询，针对不同设备分辨率改变`font-size`的值，如下：\n\n```css\n@media screen and (max-width: 414px) {\n  html {\n    font-size: 18px\n  }\n}\n\n@media screen and (max-width: 375px) {\n  html {\n    font-size: 16px\n  }\n}\n\n@media screen and (max-width: 320px) {\n  html {\n    font-size: 12px\n  }\n}\n```\n\n为了更准确监听设备可视窗口变化，我们可以在`css`之前插入`script`标签，内容如下：\n\n```js\n//动态为根元素设置字体大小\nfunction init () {\n    // 获取屏幕宽度\n    var width = document.documentElement.clientWidth\n    // 设置根元素字体大小。此时为宽的10等分\n    document.documentElement.style.fontSize = width / 10 + 'px'\n}\n\n//首次加载应用，设置一次\ninit()\n// 监听手机旋转的事件的时机，重新设置\nwindow.addEventListener('orientationchange', init)\n// 监听手机窗口变化，重新设置\nwindow.addEventListener('resize', init)\n```\n\n无论设备可视窗口如何变化，始终设置`rem`为`width`的1/10，实现了百分比布局\n\n除此之外，我们还可以利用主流`UI`框架，如：`element ui`、`antd`提供的栅格布局实现响应式\n\n\n\n### 小结\n\n响应式设计实现通常会从以下几方面思考：\n\n- 弹性盒子（包括图片、表格、视频）和媒体查询等技术\n- 使用百分比布局创建流式布局的弹性UI，同时使用媒体查询限制元素的尺寸和内容变更范围\n- 使用相对单位使得内容自适应调节\n- 选择断点，针对不同断点实现不同布局和内容展示\n\n\n\n## 三、总结\n\n响应式布局优点可以看到：\n\n- 面对不同分辨率设备灵活性强\n- 能够快捷解决多设备显示适应问题\n\n缺点：\n\n- 仅适用布局、信息、框架并不复杂的部门类型网站\n- 兼容各种设备工作量大，效率低下\n- 代码累赘，会出现隐藏无用的元素，加载时间加长\n- 其实这是一种折中性质的设计解决方案，多方面因素影响而达不到最佳效果\n- 一定程度上改变了网站原有的布局结构，会出现用户混淆的情况\n\n\n## 参考文献\n- https://baike.baidu.com/item/%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BD%91%E9%A1%B5%E8%AE%BE%E8%AE%A1\n- https://juejin.cn/post/6844904082751111176\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/css/sass_less_stylus.md",
    "content": "# 面试官：说说对Css预编语言的理解？有哪些区别?\n\n ![](https://static.vue-js.com/81cca1c0-a42c-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、是什么\n\n`Css` 作为一门标记性语言，语法相对简单，对使用者的要求较低，但同时也带来一些问题\n\n需要书写大量看似没有逻辑的代码，不方便维护及扩展，不利于复用，尤其对于非前端开发工程师来讲，往往会因为缺少 `Css` 编写经验而很难写出组织良好且易于维护的 `Css` 代码\n\n`Css`预处理器便是针对上述问题的解决方案\n\n#### 预处理语言\n\n扩充了 `Css` 语言，增加了诸如变量、混合（mixin）、函数等功能，让 `Css` 更易维护、方便\n\n本质上，预处理是`Css`的超集\n\n包含一套自定义的语法及一个解析器，根据这些语法定义自己的样式规则，这些规则最终会通过解析器，编译生成对应的 `Css` 文件\n\n\n## 二、有哪些\n\n`Css`预编译语言在前端里面有三大优秀的预编处理器，分别是：\n\n- sass\n- less\n- stylus\n\n\n\n### sass\n\n2007 年诞生，最早也是最成熟的 `Css `预处理器，拥有 Ruby 社区的支持和 `Compass` 这一最强大的 `Css `框架，目前受 `LESS` 影响，已经进化到了全面兼容 `Css` 的 `Scss`\n\n文件后缀名为`.sass`与`scss`，可以严格按照 sass 的缩进方式省去大括号和分号\n\n### less\n\n2009年出现，受` SASS `的影响较大，但又使用 `Css` 的语法，让大部分开发者和设计师更容易上手，在 `Ruby `社区之外支持者远超过 `SASS`\n\n其缺点是比起 `SASS `来，可编程功能不够，不过优点是简单和兼容 `Css`，反过来也影响了 `SASS `演变到了` Scss` 的时代\n\n\n\n### stylus\n\n`Stylus `是一个`Css`的预处理框架，2010 年产生，来自 `Node.js `社区，主要用来给 `Node` 项目进行 `Css` 预处理支持\n\n所以` Stylus` 是一种新型语言，可以创建健壮的、动态的、富有表现力的` Css`。比较年轻，其本质上做的事情与` SASS/LESS `等类似\n\n\n\n\n\n## 三、区别\n\n虽然各种预处理器功能强大，但使用最多的，还是以下特性：\n\n- 变量（variables）\n- 作用域（scope）\n- 代码混合（ mixins）\n- 嵌套（nested rules）\n- 代码模块化（Modules）\n\n因此，下面就展开这些方面的区别\n\n\n\n### 基本使用\n\nless和scss\n\n```Css\n.box {\n  display: block;\n}\n```\n\nsass\n\n```Css\n.box\n  display: block\n```\n\nstylus\n\n```Css\n.box\n  display: block\n```\n\n\n\n\n\n### 嵌套\n\n三者的嵌套语法都是一致的，甚至连引用父级选择器的标记 & 也相同\n\n区别只是 Sass 和 Stylus 可以用没有大括号的方式书写\n\nless\n\n```Css\n.a {\n  &.b {\n    color: red;\n  }\n}\n```\n\n\n\n### 变量\n\n变量无疑为 Css 增加了一种有效的复用方式，减少了原来在 Css 中无法避免的重复「硬编码」\n\n`less`声明的变量必须以`@`开头，后面紧跟变量名和变量值，而且变量名和变量值需要使用冒号`:`分隔开\n\n```Css\n@red: #c00;\n\nstrong {\n  color: @red;\n}\n```\n\n`sass`声明的变量跟`less`十分的相似，只是变量名前面使用`@`开头\n\n```Css\n$red: #c00;\n\nstrong {\n  color: $red;\n}\n```\n\n`stylus`声明的变量没有任何的限定，可以使用`$`开头，结尾的分号`;`可有可无，但变量与变量值之间需要使用`=`\n\n在`stylus`中我们不建议使用`@`符号开头声明变量\n\n```Css\nred = #c00\n\nstrong\n  color: red\n```\n\n\n\n\n\n### 作用域\n\n`Css` 预编译器把变量赋予作用域，也就是存在生命周期。就像 `js `一样，它会先从局部作用域查找变量，依次向上级作用域查找\n\n`sass`中不存在全局变量\n\n```Css\n$color: black;\n.scoped {\n  $bg: blue;\n  $color: white;\n  color: $color;\n  background-color:$bg;\n}\n.unscoped {\n  color:$color;\n} \n```\n\n编译后\n\n```Css\n.scoped {\n  color:white;/*是白色*/\n  background-color:blue;\n}\n.unscoped {\n  color:white;/*白色（无全局变量概念）*/\n} \n```\n\n所以，在`sass`中最好不要定义相同的变量名\n\n\n\n`less`与`stylus`的作用域跟`javascript`十分的相似，首先会查找局部定义的变量，如果没有找到，会像冒泡一样，一级一级往下查找，直到根为止\n\n```Css\n@color: black;\n.scoped {\n  @bg: blue;\n  @color: white;\n  color: @color;\n  background-color:@bg;\n}\n.unscoped {\n  color:@color;\n} \n```\n\n编译后：\n\n```Css\n.scoped {\n  color:white;/*白色（调用了局部变量）*/\n  background-color:blue;\n}\n.unscoped {\n  color:black;/*黑色（调用了全局变量）*/\n} \n```\n\n\n\n\n\n### 混入\n\n混入（mixin）应该说是预处理器最精髓的功能之一了，简单点来说，`Mixins`可以将一部分样式抽出，作为单独定义的模块，被很多选择器重复使用\n\n可以在`Mixins`中定义变量或者默认参数\n\n在`less`中，混合的用法是指将定义好的`ClassA`中引入另一个已经定义的`Class`，也能使用够传递参数，参数变量为`@`声明\n\n```Css\n.alert {\n  font-weight: 700;\n}\n\n.highlight(@color: red) {\n  font-size: 1.2em;\n  color: @color;\n}\n\n.heads-up {\n  .alert;\n  .highlight(red);\n}\n```\n\n编译后\n\n```Css\n.alert {\n  font-weight: 700;\n}\n.heads-up {\n  font-weight: 700;\n  font-size: 1.2em;\n  color: red;\n}\n```\n\n`Sass`声明`mixins`时需要使用`@mixinn`，后面紧跟`mixin`的名，也可以设置参数，参数名为变量`$`声明的形式\n\n```Css\n@mixin large-text {\n  font: {\n    family: Arial;\n    size: 20px;\n    weight: bold;\n  }\n  color: #ff0000;\n}\n\n.page-title {\n  @include large-text;\n  padding: 4px;\n  margin-top: 10px;\n}\n```\n\n`stylus`中的混合和前两款`Css`预处理器语言的混合略有不同，他可以不使用任何符号，就是直接声明`Mixins`名，然后在定义参数和默认值之间用等号（=）来连接\n\n```Css\nerror(borderWidth= 2px) {\n  border: borderWidth solid #F00;\n  color: #F00;\n}\n.generic-error {\n  padding: 20px;\n  margin: 4px;\n  error(); /* 调用error mixins */\n}\n.login-error {\n  left: 12px;\n  position: absolute;\n  top: 20px;\n  error(5px); /* 调用error mixins，并将参数$borderWidth的值指定为5px */\n} \n```\n\n\n\n\n\n### 代码模块化\n\n模块化就是将`Css`代码分成一个个模块\n\n`scss`、`less`、`stylus`三者的使用方法都如下所示\n\n```Css\n@import './common';\n@import './github-markdown';\n@import './mixin';\n@import './variables';\n```\n\n\n\n## 参考文献\n\n- https://jelly.jd.com/article/5dcb9c73641a030153732a89\n- https://zhuanlan.zhihu.com/p/23382462\n- https://baike.baidu.com/item/Less/17570158"
  },
  {
    "path": "docs/css/selector.md",
    "content": "# 面试官：css选择器有哪些？优先级？哪些属性可以继承？\n\n ![](https://static.vue-js.com/f7dcd330-8fe1-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、选择器\nCSS选择器是CSS规则的第一部分\n\n它是元素和其他部分组合起来告诉浏览器哪个HTML元素应当是被选为应用规则中的CSS属性值的方式\n\n选择器所选择的元素，叫做“选择器的对象”\n\n我们从一个`Html`结构开始\n\n```html\n<div id=\"box\">\n    <div class=\"one\">\n        <p class=\"one_1\">\n        </p >\n        <p class=\"one_1\">\n        </p >\n    </div>\n    <div class=\"two\"></div>\n    <div class=\"two\"></div>\n    <div class=\"two\"></div>\n</div>\n```\n\n关于`css`属性选择器常用的有：\n\n- id选择器（#box），选择id为box的元素\n\n- 类选择器（.one），选择类名为one的所有元素\n- 标签选择器（div），选择标签为div的所有元素\n\n- 后代选择器（#box div），选择id为box元素内部所有的div元素\n- 子选择器（.one>one_1），选择父元素为.one的所有.one_1的元素\n- 相邻同胞选择器（.one+.two），选择紧接在.one之后的所有.two元素\n- 群组选择器（div,p），选择div、p的所有元素\n\n\n\n还有一些使用频率相对没那么多的选择器：\n\n- 伪类选择器\n\n```css\n:link ：选择未被访问的链接\n:visited：选取已被访问的链接\n:active：选择活动链接\n:hover ：鼠标指针浮动在上面的元素\n:focus ：选择具有焦点的\n:first-child：父元素的首个子元素\n```\n\n- 伪元素选择器\n\n```css\n:first-letter ：用于选取指定选择器的首字母\n:first-line ：选取指定选择器的首行\n:before : 选择器在被选元素的内容前面插入内容\n:after : 选择器在被选元素的内容后面插入内容\n```\n\n- 属性选择器\n\n```css\n[attribute] 选择带有attribute属性的元素\n[attribute=value] 选择所有使用attribute=value的元素\n[attribute~=value] 选择attribute属性包含value的元素\n[attribute|=value]：选择attribute属性以value开头的元素\n```\n\n在`CSS3`中新增的选择器有如下：\n\n- 层次选择器（p~ul），选择前面有p元素的每个ul元素\n- 伪类选择器\n\n```css\n:first-of-type 表示一组同级元素中其类型的第一个元素\n:last-of-type 表示一组同级元素中其类型的最后一个元素\n:only-of-type 表示没有同类型兄弟元素的元素\n:only-child 表示没有任何兄弟的元素\n:nth-child(n) 根据元素在一组同级中的位置匹配元素\n:nth-last-of-type(n) 匹配给定类型的元素，基于它们在一组兄弟元素中的位置，从末尾开始计数\n:last-child 表示一组兄弟元素中的最后一个元素\n:root 设置HTML文档\n:empty 指定空的元素\n:enabled 选择可用元素\n:disabled 选择被禁用元素\n:checked 选择选中的元素\n:not(selector) 选择与 <selector> 不匹配的所有元素\n```\n\n- 属性选择器\n\n```css\n[attribute*=value]：选择attribute属性值包含value的所有元素\n[attribute^=value]：选择attribute属性开头为value的所有元素\n[attribute$=value]：选择attribute属性结尾为value的所有元素\n```\n\n\n\n## 二、优先级\n\n相信大家对`CSS`选择器的优先级都不陌生：\n\n> 内联 > ID选择器 > 类选择器 > 标签选择器\n\n到具体的计算层⾯，优先级是由 A 、B、C、D 的值来决定的，其中它们的值计算规则如下：\n\n- 如果存在内联样式，那么 A = 1, 否则 A = 0\n\n- B的值等于 ID选择器出现的次数\n\n- C的值等于 类选择器 和 属性选择器 和 伪类 出现的总次数\n\n- D 的值等于 标签选择器 和 伪元素 出现的总次数\n\n这里举个例子：\n\n```css\n#nav-global > ul > li > a.nav-link\n```\n\n套用上面的算法，依次求出 `A` `B` `C` `D` 的值：\n\n- 因为没有内联样式 ，所以 A = 0\n\n- ID选择器总共出现了1次， B = 1\n\n- 类选择器出现了1次， 属性选择器出现了0次，伪类选择器出现0次，所以 C = (1 + 0 + 0) = 1\n- 标签选择器出现了3次， 伪元素出现了0次，所以 D = (3 + 0) = 3\n\n上面算出的`A` 、 `B`、`C`、`D` 可以简记作：`(0, 1, 1, 3)`\n\n知道了优先级是如何计算之后，就来看看比较规则：\n\n- 从左往右依次进行比较 ，较大者优先级更高\n- 如果相等，则继续往右移动一位进行比较\n- 如果4位全部相等，则后面的会覆盖前面的\n\n经过上面的优先级计算规则，我们知道内联样式的优先级最高，如果外部样式需要覆盖内联样式，就需要使用`!important`\n\n\n\n## 三、继承属性\n\n在`css`中，继承是指的是给父元素设置一些属性，后代元素会自动拥有这些属性\n\n关于继承属性，可以分成：\n\n- 字体系列属性\n\n```css\nfont:组合字体\nfont-family:规定元素的字体系列\nfont-weight:设置字体的粗细\nfont-size:设置字体的尺寸\nfont-style:定义字体的风格\nfont-variant:偏大或偏小的字体\n```\n\n- 文本系列属性\n\n```css\ntext-indent：文本缩进\ntext-align：文本水平对刘\nline-height：行高\nword-spacing：增加或减少单词间的空白\nletter-spacing：增加或减少字符间的空白\ntext-transform：控制文本大小写\ndirection：规定文本的书写方向\ncolor：文本颜色\n```\n\n- 元素可见性\n\n```css\nvisibility\n```\n\n- 表格布局属性\n\n```css\ncaption-side：定位表格标题位置\nborder-collapse：合并表格边框\nborder-spacing：设置相邻单元格的边框间的距离\nempty-cells：单元格的边框的出现与消失\ntable-layout：表格的宽度由什么决定\n```\n\n- 列表属性\n\n```css\nlist-style-type：文字前面的小点点样式\nlist-style-position：小点点位置\nlist-style：以上的属性可通过这属性集合\n```\n\n- 引用\n\n```css\nquotes：设置嵌套引用的引号类型\n```\n\n- 光标属性\n\n```css\ncursor：箭头可以变成需要的形状\n```\n\n继承中比较特殊的几点：\n\n- a 标签的字体颜色不能被继承\n\n- h1-h6标签字体的大下也是不能被继承的\n\n\n\n### 无继承的属性\n\n- display\n\n- 文本属性：vertical-align、text-decoration\n\n- 盒子模型的属性：宽度、高度、内外边距、边框等\n\n- 背景属性：背景图片、颜色、位置等\n\n- 定位属性：浮动、清除浮动、定位position等\n\n- 生成内容属性：content、counter-reset、counter-increment\n\n- 轮廓样式属性：outline-style、outline-width、outline-color、outline\n\n- 页面样式属性：size、page-break-before、page-break-after\n\n\n\n## 参考文献\n\n- https://www.html.cn/qa/css3/13444.html\n- https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Selectors\n"
  },
  {
    "path": "docs/css/single_multi_line.md",
    "content": "# 面试官：如何实现单行／多行文本溢出的省略样式？\n\n ![](https://static.vue-js.com/ada8d840-a0e9-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、前言\n\n在日常开发展示页面，如果一段文本的数量过长，受制于元素宽度的因素，有可能不能完全显示，为了提高用户的使用体验，这个时候就需要我们把溢出的文本显示成省略号\n\n对于文本的溢出，我们可以分成两种形式：\n\n- 单行文本溢出\n- 多行文本溢出\n\n\n\n## 二、实现方式\n\n\n\n### 单行文本溢出省略\n\n理解也很简单，即文本在一行内显示，超出部分以省略号的形式展现\n\n实现方式也很简单，涉及的`css`属性有：\n\n- text-overflow：规定当文本溢出时，显示省略符号来代表被修剪的文本\n- white-space：设置文字在一行显示，不能换行\n- overflow：文字长度超出限定宽度，则隐藏超出的内容\n\n`overflow`设为`hidden`，普通情况用在块级元素的外层隐藏内部溢出元素，或者配合下面两个属性实现文本溢出省略\n\n`white-space:nowrap`，作用是设置文本不换行，是`overflow:hidden`和`text-overflow：ellipsis`生效的基础\n\n`text-overflow`属性值有如下：\n\n- clip：当对象内文本溢出部分裁切掉\n- ellipsis：当对象内文本溢出时显示省略标记（...）\n\n`text-overflow`只有在设置了`overflow:hidden`和`white-space:nowrap`才能够生效的\n\n举个例子\n\n```html\n<style>\n    p{\n        overflow: hidden;\n        line-height: 40px;\n        width:400px;\n        height:40px;\n        border:1px solid red;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n    }\n</style>\n<p 这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本</p >\n```\n\n效果如下：\n\n ![](https://static.vue-js.com/bb3048e0-a0e9-11eb-85f6-6fac77c0c9b3.png)\n\n可以看到，设置单行文本溢出较为简单，并且省略号显示的位置较好\n\n\n\n### 多行文本溢出省略\n\n多行文本溢出的时候，我们可以分为两种情况：\n\n- 基于高度截断\n- 基于行数截断\n\n\n\n#### 基于高度截断\n\n#### 伪元素 + 定位\n\n核心的`css`代码结构如下：\n\n- position: relative：为伪元素绝对定位\n- overflow: hidden：文本溢出限定的宽度就隐藏内容）\n- position: absolute：给省略号绝对定位\n- line-height: 20px：结合元素高度,高度固定的情况下,设定行高, 控制显示行数\n- height: 40px：设定当前元素高度\n- ::after {} ：设置省略号样式\n\n代码如下所示：\n\n```html\n<style>\n    .demo {\n        position: relative;\n        line-height: 20px;\n        height: 40px;\n        overflow: hidden;\n    }\n    .demo::after {\n        content: \"...\";\n        position: absolute;\n        bottom: 0;\n        right: 0;\n        padding: 0 20px 0 10px;\n    }\n</style>\n\n<body>\n    <div class='demo'>这是一段很长的文本</div>\n</body>\n```\n\n实现原理很好理解，就是通过伪元素绝对定位到行尾并遮住文字，再通过 `overflow: hidden` 隐藏多余文字\n\n这种实现具有以下优点：\n\n- 兼容性好，对各大主流浏览器有好的支持\n- 响应式截断，根据不同宽度做出调整\n\n一般文本存在英文的时候，可以设置`word-break: break-all`使一个单词能够在换行时进行拆分\n\n\n\n#### 基于行数截断\n\n纯`css`实现也非常简单，核心的`css`代码如下：\n\n- -webkit-line-clamp: 2：用来限制在一个块元素显示的文本的行数，为了实现该效果，它需要组合其他的WebKit属性）\n- display: -webkit-box：和1结合使用，将对象作为弹性伸缩盒子模型显示 \n- -webkit-box-orient: vertical：和1结合使用 ，设置或检索伸缩盒对象的子元素的排列方式 \n- overflow: hidden：文本溢出限定的宽度就隐藏内容\n- text-overflow: ellipsis：多行文本的情况下，用省略号“…”隐藏溢出范围的文本\n\n```html\n<style>\n    p {\n        width: 400px;\n        border-radius: 1px solid red;\n        -webkit-line-clamp: 2;\n        display: -webkit-box;\n        -webkit-box-orient: vertical;\n        overflow: hidden;\n        text-overflow: ellipsis;\n    }\n</style>\n<p>\n    这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本\n    这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本\n</p >\n```\n\n可以看到，上述使用了`webkit`的`CSS`属性扩展，所以兼容浏览器范围是`PC`端的`webkit`内核的浏览器，由于移动端大多数是使用`webkit`，所以移动端常用该形式\n\n需要注意的是，如果文本为一段很长的英文或者数字，则需要添加`word-wrap: break-word`属性\n\n还能通过使用`javascript`实现配合`css`，实现代码如下所示：\n\ncss结构如下：\n\n```css\np {\n    position: relative;\n    width: 400px;\n    line-height: 20px;\n    overflow: hidden;\n\n}\n.p-after:after{\n    content: \"...\"; \n    position: absolute; \n    bottom: 0; \n    right: 0; \n    padding-left: 40px;\n    background: -webkit-linear-gradient(left, transparent, #fff 55%);\n    background: -moz-linear-gradient(left, transparent, #fff 55%);\n    background: -o-linear-gradient(left, transparent, #fff 55%);\n    background: linear-gradient(to right, transparent, #fff 55%);\n}\n```\n\njavascript代码如下：\n\n```js\n$(function(){\n //获取文本的行高，并获取文本的高度，假设我们规定的行数是五行，那么对超过行数的部分进行限制高度，并加上省略号\n   $('p').each(function(i, obj){\n        var lineHeight = parseInt($(this).css(\"line-height\"));\n        var height = parseInt($(this).height());\n        if((height / lineHeight) >3 ){\n            $(this).addClass(\"p-after\")\n            $(this).css(\"height\",\"60px\");\n        }else{\n            $(this).removeClass(\"p-after\");\n        }\n    });\n})\n```\n\n\n\n\n\n\n\n## 参考文献\n\n- https://www.zoo.team/article/text-overflow\n\n- https://segmentfault.com/a/1190000017078153"
  },
  {
    "path": "docs/css/triangle.md",
    "content": "# 面试官：CSS如何画一个三角形？原理是什么？\n\n![](https://static.vue-js.com/bd310120-a279-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、前言\n\n在前端开发的时候，我们有时候会需要用到一个三角形的形状，比如地址选择或者播放器里面播放按钮\n\n ![](https://static.vue-js.com/d6d8ff60-a279-11eb-85f6-6fac77c0c9b3.png)\n\n通常情况下，我们会使用图片或者`svg`去完成三角形效果图，但如果单纯使用`css`如何完成一个三角形呢？\n\n实现过程似乎也并不困难，通过边框就可完成\n\n\n## 二、实现过程\n\n在以前也讲过盒子模型，默认情况下是一个矩形，实现也很简单\n\n```html\n<style>\n    .border {\n        width: 50px;\n        height: 50px;\n        border: 2px solid;\n        border-color: #96ceb4 #ffeead #d9534f #ffad60;\n    }\n</style>\n<div class=\"border\"></div>\n```\n\n效果如下图所示：\n\n ![](https://static.vue-js.com/e3f244e0-a279-11eb-ab90-d9ae814b240d.png)\n\n将`border`设置`50px`，效果图如下所示：\n\n ![](https://static.vue-js.com/ee0b42b0-a279-11eb-ab90-d9ae814b240d.png)\n\n白色区域则为`width`、`height`，这时候只需要你将白色区域部分宽高逐渐变小，最终变为0，则变成如下图所示：\n\n ![](https://static.vue-js.com/2afaa030-a27a-11eb-85f6-6fac77c0c9b3.png)\n\n这时候就已经能够看到4个不同颜色的三角形，如果需要下方三角形，只需要将上、左、右边框设置为0就可以得到下方的红色三角形\n\n ![](https://static.vue-js.com/2afaa030-a27a-11eb-85f6-6fac77c0c9b3.png)\n\n但这种方式，虽然视觉上是实现了三角形，但实际上，隐藏的部分任然占据部分高度，需要将上方的宽度去掉\n\n最终实现代码如下：\n\n```css\n.border {\n    width: 0;\n    height: 0;\n    border-style:solid;\n    border-width: 0 50px 50px;\n    border-color: transparent transparent #d9534f;\n}\n```\n\n如果想要实现一个只有边框是空心的三角形，由于这里不能再使用`border`属性，所以最直接的方法是利用伪类新建一个小一点的三角形定位上去\n\n```css\n.border {\n    width: 0;\n    height: 0;\n    border-style:solid;\n    border-width: 0 50px 50px;\n    border-color: transparent transparent #d9534f;\n    position: relative;\n}\n.border:after{\n    content: '';\n    border-style:solid;\n    border-width: 0 40px 40px;\n    border-color: transparent transparent #96ceb4;\n    position: absolute;\n    top: 0;\n    left: 0;\n}\n```\n\n效果图如下所示：\n\n ![i](https://static.vue-js.com/59f4d720-a27a-11eb-85f6-6fac77c0c9b3.png)\n\n伪类元素定位参照对象的内容区域宽高都为0，则内容区域即可以理解成中心一点，所以伪元素相对中心这点定位\n\n将元素定位进行微调以及改变颜色，就能够完成下方效果图：\n\n ![](https://static.vue-js.com/653a6e10-a27a-11eb-85f6-6fac77c0c9b3.png)\n\n最终代码如下：\n\n```css\n.border:after {\n    content: '';\n    border-style: solid;\n    border-width: 0 40px 40px;\n    border-color: transparent transparent #96ceb4;\n    position: absolute;\n    top: 6px;\n    left: -40px;\n}\n```\n\n\n\n## 三、原理分析\n\n可以看到，边框是实现三角形的部分，边框实际上并不是一个直线，如果我们将四条边设置不同的颜色，将边框逐渐放大，可以得到每条边框都是一个梯形\n\n ![](https://static.vue-js.com/78d4bd90-a27a-11eb-85f6-6fac77c0c9b3.png)\n\n当分别取消边框的时候，发现下面几种情况：\n\n- 取消一条边的时候，与这条边相邻的两条边的接触部分会变成直的\n- 当仅有邻边时， 两个边会变成对分的三角\n- 当保留边没有其他接触时，极限情况所有东西都会消失\n\n ![](https://static.vue-js.com/84586ef0-a27a-11eb-85f6-6fac77c0c9b3.png)\n\n通过上图的变化规则，利用旋转、隐藏，以及设置内容宽高等属性，就能够实现其他类型的三角形\n\n如设置直角三角形，如上图倒数第三行实现过程，我们就能知道整个实现原理\n\n实现代码如下：\n\n```css\n.box {\n    /* 内部大小 */\n    width: 0px;\n    height: 0px;\n    /* 边框大小 只设置两条边*/\n    border-top: #4285f4 solid;\n    border-right: transparent solid;\n    border-width: 85px; \n    /* 其他设置 */\n    margin: 50px;\n}\n```\n\n\n\n## 参考文献\n\n- https://www.cnblogs.com/echolun/p/11888612.html\n- https://juejin.cn/post/6844903567795421197\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/css/visual_scrolling.md",
    "content": "# 面试官：如何使用css完成视差滚动效果?\n\n ![](https://static.vue-js.com/1b2d33e0-a18d-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n视差滚动（Parallax Scrolling）是指多层背景以不同的速度移动，形成立体的运动效果，带来非常出色的视觉体验\n\n我们可以把网页解刨成：背景层、内容层、悬浮层\n\n ![](https://static.vue-js.com/57c942a0-a1cc-11eb-85f6-6fac77c0c9b3.png)\n\n当滚动鼠标滑轮的时候，各个图层以不同的速度移动，形成视觉差的效果\n\n ![image.png](https://static.vue-js.com/e57ab280-a1dd-11eb-ab90-d9ae814b240d.png)\n\n\n## 二、实现方式\n\n\n使用`css`形式实现视觉差滚动效果的方式有：\n\n- background-attachment\n- transform:translate3D\n\n\n### background-attachment\n\n作用是设置背景图像是否固定或者随着页面的其余部分滚动\n\n值分别有如下：\n\n- scroll：默认值，背景图像会随着页面其余部分的滚动而移动\n- fixed：当页面的其余部分滚动时，背景图像不会移动\n- inherit：继承父元素background-attachment属性的值\n\n完成滚动视觉差就需要将`background-attachment`属性设置为`fixed`，让背景相对于视口固定。及时一个元素有滚动机制，背景也不会随着元素的内容而滚动\n\n也就是说，背景一开始就已经被固定在初始的位置\n\n核心的`css`代码如下：\n\n```css\nsection {\n    height: 100vh;\n}\n\n.g-img {\n    background-image: url(...);\n    background-attachment: fixed;\n    background-size: cover;\n    background-position: center center;\n}\n```\n\n整体例子如下：\n\n```html\n<style>\ndiv {\n            height: 100vh;\n            background: rgba(0, 0, 0, .7);\n            color: #fff;\n            line-height: 100vh;\n            text-align: center;\n            font-size: 20vh;\n        }\n\n        .a-img1 {\n            background-image: url(https://images.pexels.com/photos/1097491/pexels-photo-1097491.jpeg);\n            background-attachment: fixed;\n            background-size: cover;\n            background-position: center center;\n        }\n\n        .a-img2 {\n            background-image: url(https://images.pexels.com/photos/2437299/pexels-photo-2437299.jpeg);\n            background-attachment: fixed;\n            background-size: cover;\n            background-position: center center;\n        }\n\n        .a-img3 {\n            background-image: url(https://images.pexels.com/photos/1005417/pexels-photo-1005417.jpeg);\n            background-attachment: fixed;\n            background-size: cover;\n            background-position: center center;\n        }\n</style>\n <div class=\"a-text\">1</div>\n    <div class=\"a-img1\">2</div>\n    <div class=\"a-text\">3</div>\n    <div class=\"a-img2\">4</div>\n    <div class=\"a-text\">5</div>\n    <div class=\"a-img3\">6</div>\n    <div class=\"a-text\">7</div>\n```\n\n\n\n\n\n### transform:translate3D\n\n同样，让我们先来看一下两个概念`transform`和`perspective`：\n\n- transform: css3 属性，可以对元素进行变换(2d/3d)，包括平移 translate,旋转 rotate,缩放 scale,等等\n- perspective: css3 属性，当元素涉及 3d 变换时，perspective 可以定义我们眼睛看到的 3d 立体效果，即空间感\n\n`3D`视角示意图如下所示：\n\n ![](https://static.vue-js.com/24f37dd0-a18d-11eb-85f6-6fac77c0c9b3.png)\n\n\n举个例子：\n\n```html\n<style>\n    html {\n        overflow: hidden;\n        height: 100%\n    }\n\n    body {\n        /* 视差元素的父级需要3D视角 */\n        perspective: 1px;\n        transform-style: preserve-3d; \n        height: 100%;\n        overflow-y: scroll;\n        overflow-x: hidden;\n    }\n    #app{\n        width: 100vw;\n        height:200vh;\n        background:skyblue;\n        padding-top:100px;\n    }\n    .one{\n        width:500px;\n        height:200px;\n        background:#409eff;\n        transform: translateZ(0px);\n        margin-bottom: 50px;\n    }\n    .two{\n        width:500px;\n        height:200px;\n        background:#67c23a;\n        transform: translateZ(-1px);\n        margin-bottom: 150px;\n    }\n    .three{\n        width:500px;\n        height:200px;\n        background:#e6a23c;\n        transform: translateZ(-2px);\n        margin-bottom: 150px;\n    }\n</style>\n<div id=\"app\">\n    <div class=\"one\">one</div>\n    <div class=\"two\">two</div>\n    <div class=\"three\">three</div>\n</div>\n```\n\n\n而这种方式实现视觉差动的原理如下：\n\n- 容器设置上 transform-style: preserve-3d 和 perspective: xpx，那么处于这个容器的子元素就将位于3D空间中，\n\n- 子元素设置不同的 transform: translateZ()，这个时候，不同元素在 3D Z轴方向距离屏幕（我们的眼睛）的距离也就不一样\n\n- 滚动滚动条，由于子元素设置了不同的 transform: translateZ()，那么他们滚动的上下距离 translateY 相对屏幕（我们的眼睛），也是不一样的，这就达到了滚动视差的效果\n\n\n## 参考文献\n\n- https://imweb.io/topic/5b73ef73a56e07401e48729d\n- https://juejin.cn/post/6844903654458146823#heading-5"
  },
  {
    "path": "docs/design/Factory  Pattern.md",
    "content": "# 面试官：说说你对工厂模式的理解？应用场景？\n\n\n ![](https://static.vue-js.com/27a84d10-3bea-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n工厂模式是用来创建对象的一种最常用的设计模式，不暴露创建对象的具体逻辑，而是将将逻辑封装在一个函数中，那么这个函数就可以被视为一个工厂\n\n其就像工厂一样重复的产生类似的产品，工厂模式只需要我们传入正确的参数，就能生产类似的产品\n\n举个例子：\n\n- 编程中，在一个 A 类中通过 new 的方式实例化了类 B，那么 A 类和 B 类之间就存在关联（耦合）\n- 后期因为需要修改了 B 类的代码和使用方式，比如构造函数中传入参数，那么 A 类也要跟着修改，一个类的依赖可能影响不大，但若有多个类依赖了 B 类，那么这个工作量将会相当的大，容易出现修改错误，也会产生很多的重复代码，这无疑是件非常痛苦的事；\n- 这种情况下，就需要将创建实例的工作从调用方（A类）中分离，与调用方**解耦**，也就是使用工厂方法创建实例的工作封装起来（**减少代码重复**），由工厂管理对象的创建逻辑，调用方不需要知道具体的创建过程，只管使用，**而降低调用者因为创建逻辑导致的错误**；\n\n\n\n## 二、实现\n\n工厂模式根据抽象程度的不同可以分为：\n\n- 简单工厂模式（Simple Factory）\n- 工厂方法模式（Factory Method）\n- 抽象工厂模式（Abstract Factory）\n\n\n\n### 简单工厂模式\n\n简单工厂模式也叫静态工厂模式，用一个工厂对象创建同一类对象类的实例\n\n假设我们要开发一个公司岗位及其工作内容的录入信息，不同岗位的工作内容不一致\n\n代码如下：\n\n```js\nfunction Factory(career) {\n    function User(career, work) {\n        this.career = career \n        this.work = work\n    }\n    let work\n    switch(career) {\n        case 'coder':\n            work =  ['写代码', '修Bug'] \n            return new User(career, work)\n            break\n        case 'hr':\n            work = ['招聘', '员工信息管理']\n            return new User(career, work)\n            break\n        case 'driver':\n            work = ['开车']\n            return new User(career, work)\n            break\n        case 'boss':\n            work = ['喝茶', '开会', '审批文件']\n            return new User(career, work)\n            break\n    }\n}\nlet coder = new Factory('coder')\nconsole.log(coder)\nlet boss = new Factory('boss')\nconsole.log(boss)\n```\n\n`Factory`就是一个简单工厂。当我们调用工厂函数时，只需要传递name、age、career就可以获取到包含用户工作内容的实例对象\n\n\n\n### 工厂方法模式\n\n工厂方法模式跟简单工厂模式差不多，但是把具体的产品放到了工厂函数的`prototype`中\n\n这样一来，扩展产品种类就不必修改工厂函数了，和心累就变成抽象类，也可以随时重写某种具体的产品\n\n也就是相当于工厂总部不生产产品了，交给下辖分工厂进行生产；但是进入工厂之前，需要有个判断来验证你要生产的东西是否是属于我们工厂所生产范围，如果是，就丢给下辖工厂来进行生产\n\n如下代码：\n\n```js\n// 工厂方法\nfunction Factory(career){\n    if(this instanceof Factory){\n        var a = new this[career]();\n        return a;\n    }else{\n        return new Factory(career);\n    }\n}\n// 工厂方法函数的原型中设置所有对象的构造函数\nFactory.prototype={\n    'coder': function(){\n        this.careerName = '程序员'\n        this.work = ['写代码', '修Bug'] \n    },\n    'hr': function(){\n        this.careerName = 'HR'\n        this.work = ['招聘', '员工信息管理']\n    },\n    'driver': function () {\n        this.careerName = '司机'\n        this.work = ['开车']\n    },\n    'boss': function(){\n        this.careerName = '老板'\n        this.work = ['喝茶', '开会', '审批文件']\n    }\n}\nlet coder = new Factory('coder')\nconsole.log(coder)\nlet hr = new Factory('hr')\nconsole.log(hr)\n```\n\n工厂方法关键核心代码是工厂里面的判断this是否属于工厂，也就是做了分支判断，这个工厂只做我能做的产品\n\n\n\n### 抽象工厂模式\n\n上述简单工厂模式和工厂方法模式都是直接生成实例，但是抽象工厂模式不同，抽象工厂模式并不直接生成实例， 而是用于对产品类簇的创建\n\n通俗点来讲就是：简单工厂和工厂方法模式的工作是生产产品，那么抽象工厂模式的工作就是生产工厂的\n\n由于`JavaScript`中并没有抽象类的概念，只能模拟，可以分成四部分：\n\n- 用于创建抽象类的函数\n- 抽象类\n- 具体类\n- 实例化具体类\n\n上面的例子中有`coder`、`hr`、`boss`、`driver`四种岗位，其中`coder`可能使用不同的开发语言进行开发，比如`JavaScript`、`Java`等等。那么这两种语言就是对应的类簇\n\n示例代码如下：\n\n```js\nlet CareerAbstractFactory = function(subType, superType) {\n  // 判断抽象工厂中是否有该抽象类\n  if (typeof CareerAbstractFactory[superType] === 'function') {\n    // 缓存类\n    function F() {}\n    // 继承父类属性和方法\n    F.prototype = new CareerAbstractFactory[superType]()\n    // 将子类的constructor指向父类\n    subType.constructor = subType;\n    // 子类原型继承父类\n    subType.prototype = new F()\n  } else {\n    throw new Error('抽象类不存在')\n  }\n}\n```\n\n上面代码中`CareerAbstractFactory`就是一个抽象工厂方法，该方法在参数中传递子类和父类，在方法体内部实现了子类对父类的继承\n\n\n\n## 三、应用场景\n\n从上面可看到，简单简单工厂的优点就是我们只要传递正确的参数，就能获得所需的对象，而不需要关心其创建的具体细节\n\n应用场景也容易识别，有构造函数的地方，就应该考虑简单工厂，但是如果函数构建函数太多与复杂，会导致工厂函数变得复杂，所以不适合复杂的情况\n\n抽象工厂模式一般用于严格要求以面向对象思想进行开发的超大型项目中，我们一般常规的开发的话一般就是简单工厂和工厂方法模式会用的比较多一些\n\n综上，工厂模式适用场景如下：\n\n- 如果你不想让某个子系统与较大的那个对象之间形成强耦合，而是想运行时从许多子系统中进行挑选的话，那么工厂模式是一个理想的选择\n- 将new操作简单封装，遇到new的时候就应该考虑是否用工厂模式；\n- 需要依赖具体环境创建不同实例，这些实例都有相同的行为,这时候我们可以使用工厂模式，简化实现的过程，同时也可以减少每种对象所需的代码量，有利于消除对象间的耦合，提供更大的灵活性\n\n\n\n## 参考文献\n\n- https://www.runoob.com/design-pattern/factory-pattern.html\n- https://juejin.cn/post/6844903653774458888\n- https://zhuanlan.zhihu.com/p/344119981"
  },
  {
    "path": "docs/design/Observer  Pattern.md",
    "content": "# 面试官：说说你对发布订阅、观察者模式的理解？区别？\n\n ![](https://static.vue-js.com/342739f0-3fb1-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、观察者模式\n\n观察者模式定义了对象间的一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于它的对象都将得到通知，并自动更新\n\n观察者模式属于行为型模式，行为型模式关注的是对象之间的通讯，观察者模式就是观察者和被观察者之间的通讯\n\n ![](https://static.vue-js.com/d3a80020-3f7c-11ec-a752-75723a64e8f5.png)\n\n例如生活中，我们可以用报纸期刊的订阅来形象的说明，当你订阅了一份报纸，每天都会有一份最新的报纸送到你手上，有多少人订阅报纸，报社就会发多少份报纸\n\n报社和订报纸的客户就形成了一对多的依赖关系\n\n实现代码如下：\n\n被观察者模式\n\n```js\nclass Subject {\n\n  constructor() {\n    this.observerList = [];\n  }\n\n  addObserver(observer) {\n    this.observerList.push(observer);\n  }\n\n  removeObserver(observer) {\n    const index = this.observerList.findIndex(o => o.name === observer.name);\n    this.observerList.splice(index, 1);\n  }\n\n  notifyObservers(message) {\n    const observers = this.observeList;\n    observers.forEach(observer => observer.notified(message));\n  }\n\n}\n```\n\n观察者：\n\n```h'\nclass Observer {\n\n  constructor(name, subject) {\n    this.name = name;\n    if (subject) {\n      subject.addObserver(this);\n    }\n  }\n\n  notified(message) {\n    console.log(this.name, 'got message', message);\n  }\n}\n```\n\n使用代码如下：\n\n```js\nconst subject = new Subject();\nconst observerA = new Observer('observerA', subject);\nconst observerB = new Observer('observerB');\nsubject.addObserver(observerB);\nsubject.notifyObservers('Hello from subject');\nsubject.removeObserver(observerA);\nsubject.notifyObservers('Hello again');\n```\n\n上述代码中，观察者主动申请加入被观察者的列表，被观察者主动将观察者加入列表\n\n\n\n\n\n## 二、发布订阅模式\n\n发布-订阅是一种消息范式，消息的发送者（称为发布者）不会将消息直接发送给特定的接收者（称为订阅者）。而是将发布的消息分为不同的类别，无需了解哪些订阅者（如果有的话）可能存在\n\n同样的，订阅者可以表达对一个或多个类别的兴趣，只接收感兴趣的消息，无需了解哪些发布者存在 \n\n![](https://static.vue-js.com/e24d3cd0-3f7c-11ec-8e64-91fdec0f05a1.png)\n\n\n\n实现代码如下：\n\n```js\nclass PubSub {\n  constructor() {\n    this.messages = {};\n    this.listeners = {};\n  }\n  // 添加发布者\n  publish(type, content) {\n    const existContent = this.messages[type];\n    if (!existContent) {\n      this.messages[type] = [];\n    }\n    this.messages[type].push(content);\n  }\n  // 添加订阅者\n  subscribe(type, cb) {\n    const existListener = this.listeners[type];\n    if (!existListener) {\n      this.listeners[type] = [];\n    }\n    this.listeners[type].push(cb);\n  }\n  // 通知\n  notify(type) {\n    const messages = this.messages[type];\n    const subscribers = this.listeners[type] || [];\n    subscribers.forEach((cb, index) => cb(messages[index]));\n  }\n}\n```\n\n发布者代码如下：\n\n```js\nclass Publisher {\n  constructor(name, context) {\n    this.name = name;\n    this.context = context;\n  }\n  publish(type, content) {\n    this.context.publish(type, content);\n  }\n}\n```\n\n订阅者代码如下：\n\n```js\nclass Subscriber {\n  constructor(name, context) {\n    this.name = name;\n    this.context = context;\n  }\n  subscribe(type, cb) {\n    this.context.subscribe(type, cb);\n  }\n}\n```\n\n使用代码如下：\n\n```js\nconst TYPE_A = 'music';\nconst TYPE_B = 'movie';\nconst TYPE_C = 'novel';\n\nconst pubsub = new PubSub();\n\nconst publisherA = new Publisher('publisherA', pubsub);\npublisherA.publish(TYPE_A, 'we are young');\npublisherA.publish(TYPE_B, 'the silicon valley');\nconst publisherB = new Publisher('publisherB', pubsub);\npublisherB.publish(TYPE_A, 'stronger');\nconst publisherC = new Publisher('publisherC', pubsub);\npublisherC.publish(TYPE_C, 'a brief history of time');\n\nconst subscriberA = new Subscriber('subscriberA', pubsub);\nsubscriberA.subscribe(TYPE_A, res => {\n  console.log('subscriberA received', res)\n});\nconst subscriberB = new Subscriber('subscriberB', pubsub);\nsubscriberB.subscribe(TYPE_C, res => {\n  console.log('subscriberB received', res)\n});\nconst subscriberC = new Subscriber('subscriberC', pubsub);\nsubscriberC.subscribe(TYPE_B, res => {\n  console.log('subscriberC received', res)\n});\n\npubsub.notify(TYPE_A);\npubsub.notify(TYPE_B);\npubsub.notify(TYPE_C);\n```\n\n上述代码，发布者和订阅者需要通过发布订阅中心进行关联，发布者的发布动作和订阅者的订阅动作相互独立，无需关注对方，消息派发由发布订阅中心负责\n\n\n\n\n\n## 三、区别\n\n两种设计模式思路是一样的，举个生活例子：\n\n- 观察者模式：某公司给自己员工发月饼发粽子，是由公司的行政部门发送的，这件事不适合交给第三方，原因是“公司”和“员工”是一个整体\n- 发布-订阅模式：某公司要给其他人发各种快递，因为“公司”和“其他人”是独立的，其唯一的桥梁是“快递”，所以这件事适合交给第三方快递公司解决\n\n上述过程中，如果公司自己去管理快递的配送，那公司就会变成一个快递公司，业务繁杂难以管理，影响公司自身的主营业务，因此使用何种模式需要考虑什么情况两者是需要耦合的\n\n两者区别如下图：\n\n![](https://files.mdnice.com/user/155/9141682c-7386-4f12-8412-fb17a1cd4bf6.png)\n\n- 在观察者模式中，观察者是知道Subject的，Subject一直保持对观察者进行记录。然而，在发布订阅模式中，发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。\n\n- 在发布订阅模式中，组件是松散耦合的，正好和观察者模式相反。\n\n- 观察者模式大多数时候是同步的，比如当事件触发，Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的（使用消息队列）\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/zh-hans/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F\n- https://zh.wikipedia.org/wiki/%E5%8F%91%E5%B8%83/%E8%AE%A2%E9%98%85\n- https://www.cnblogs.com/onepixel/p/10806891.html\n- https://juejin.cn/post/6978728619782701087"
  },
  {
    "path": "docs/design/Proxy Pattern.md",
    "content": "# 面试官：说说你对代理模式的理解？应用场景？\n\n ![](https://static.vue-js.com/899a6ef0-3d6a-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、是什么\n\n代理模式（Proxy Pattern）是为一个对象提供一个代用品或占位符，以便控制对它的访问\n\n代理模式的关键是，当客户不方便直接访问一个对象或者不满足需要时，提供一个替身对象来控制这个对象的访问，客户实际上访问的是替身对象\n\n ![](https://static.vue-js.com/951c99b0-3d6a-11ec-a752-75723a64e8f5.png)\n\n在生活中，代理模式的场景是十分常见的，例如我们现在如果有租房、买房的需求，更多的是去找链家等房屋中介机构，而不是直接寻找想卖房或出租房的人谈。此时，链家起到的作用就是代理的作用\n\n\n## 二、使用\n\n在`ES6`中，存在`proxy`构建函数能够让我们轻松使用代理模式：\n\n```js\nconst proxy = new Proxy(target, handler);\n```\n\n关于`Proxy`的使用可以翻看以前的文章\n\n而按照功能来划分，`javascript`代理模式常用的有：\n\n- 缓存代理\n\n- 虚拟代理\n\n\n\n### 缓存代理\n\n缓存代理可以为一些开销大的运算结果提供暂时的存储，在下次运算时，如果传递进来的参数跟之前一致，则可以直接返回前面存储的运算结果\n\n如实现一个求积乘的函数，如下：\n\n```js\nvar muti = function () {\n  console.log(\"开始计算乘积\");\n  var a = 1;\n  for (var i = 0, l = arguments.length; i < l; i++) {\n    a = a * arguments[i];\n  }\n  return a;\n};\n```\n\n现在加入缓存代理，如下：\n\n```js\nvar proxyMult = (function () {\n  var cache = {};\n  return function () {\n    var args = Array.prototype.join.call(arguments, \",\");\n    if (args in cache) {\n      return cache[args];\n    }\n    return (cache[args] = mult.apply(this, arguments));\n  };\n})();\n\nproxyMult(1, 2, 3, 4); // 输出:24\nproxyMult(1, 2, 3, 4); // 输出:24\n```\n\n当第二次调用 `proxyMult(1, 2, 3, 4)` 时，本体 `mult` 函数并没有被计算，`proxyMult` 直接返回了之前缓存好的计算结果\n\n\n\n\n\n### 虚拟代理\n\n虚拟代理把一些开销很大的对象，延迟到真正需要它的时候才去创建\n\n常见的就是图片预加载功能：\n\n未使用代理模式如下：\n\n```js\nlet MyImage = (function(){\n    let imgNode = document.createElement( 'img' );\n    document.body.appendChild( imgNode );\n    // 创建一个Image对象，用于加载需要设置的图片\n    let img = new Image;\n\n    img.onload = function(){\n        // 监听到图片加载完成后，设置src为加载完成后的图片\n        imgNode.src = img.src;\n    };\n\n    return {\n        setSrc: function( src ){\n            // 设置图片的时候，设置为默认的loading图\n            imgNode.src = 'https://img.zcool.cn/community/01deed576019060000018c1bd2352d.gif';\n            // 把真正需要设置的图片传给Image对象的src属性\n            img.src = src;\n        }\n    }\n})();\n\nMyImage.setSrc( 'https://xxx.jpg' );\n```\n\n`MyImage`对象除了负责给`img`节点设置`src`外，还要负责预加载图片，违反了面向对象设计的原则——单一职责原则\n\n上述过程`loding`则是耦合进`MyImage`对象里的，如果以后某个时候，我们不需要预加载显示loading这个功能了，就只能在`MyImage`对象里面改动代码\n\n使用代理模式，代码则如下：\n\n```js\n// 图片本地对象，负责往页面中创建一个img标签，并且提供一个对外的setSrc接口\nlet myImage = (function(){\n    let imgNode = document.createElement( 'img' );\n    document.body.appendChild( imgNode );\n\n    return {\n        //setSrc接口，外界调用这个接口，便可以给该img标签设置src属性\n        setSrc: function( src ){\n            imgNode.src = src;\n        }\n    }\n})();\n// 代理对象，负责图片预加载功能\nlet proxyImage = (function(){\n    // 创建一个Image对象，用于加载需要设置的图片\n    let img = new Image;\n    img.onload = function(){\n        // 监听到图片加载完成后，给被代理的图片本地对象设置src为加载完成后的图片\n        myImage.setSrc( this.src );\n    }\n    return {\n        setSrc: function( src ){\n            // 设置图片时，在图片未被真正加载好时，以这张图作为loading，提示用户图片正在加载\n            myImage.setSrc( 'https://img.zcool.cn/community/01deed576019060000018c1bd2352d.gif' );\n            img.src = src;\n        }\n    }\n})();\n\nproxyImage.setSrc( 'https://xxx.jpg' );\n```\n\n使用代理模式后，图片本地对象负责往页面中创建一个`img`标签，并且提供一个对外的`setSrc`接口；\n\n代理对象负责在图片未加载完成之前，引入预加载的`loading`图，负责了图片预加载的功能\n\n上述并没有改变或者增加`MyImage`的接口，但是通过代理对象，实际上给系统添加了新的行为\n\n并且上述代理模式可以发现，代理和本体接口的一致性，如果有一天不需要预加载，那么就不需要代理对象，可以选择直接请求本体。其中关键是代理对象和本体都对外提供了 `setSrc` 方法\n\n‘\n\n## 三、应用场景\n\n现在的很多前端框架或者状态管理框架都使用代理模式，用与监听变量的变化\n\n使用代理模式代理对象的访问的方式，一般又被称为拦截器，比如我们在项目中经常使用 `Axios` 的实例来进行 HTTP 的请求，使用拦截器 `interceptor` 可以提前对 请求前的数据 服务器返回的数据进行一些预处理\n\n以及上述应用到的缓存代理和虚拟代理\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903555036364814#heading-2\n- https://juejin.cn/post/6992510837403418654#heading-7\n- https://sothx.com/2021/06/26/proxy/"
  },
  {
    "path": "docs/design/Singleton Pattern.md",
    "content": "# 面试官：说说你对单例模式的理解？如何实现？\n\n![](https://static.vue-js.com/7df7d830-3b2b-11ec-8e64-91fdec0f05a1.png)\n\n\n\n  ## 一、是什么\n\n  单例模式（Singleton Pattern）：创建型模式，提供了一种创建对象的最佳方式，这种模式涉及到一个单一的类，该类负责创建自己的对象，同时确保只有单个对象被创建\n\n  在应用程序运行期间，单例模式只会在全局作用域下创建一次实例对象，让所有需要调用的地方都共享这一单例对象，如下图所示：\n\n   ![](https://static.vue-js.com/fa7898d0-3b2c-11ec-8e64-91fdec0f05a1.png)\n\n  从定义上来看，全局变量好像就是单例模式，但是一般情况我们不认为全局变量是一个单例模式，原因是：\n\n  - 全局命名污染\n  - 不易维护，容易被重写覆盖\n\n\n\n  ## 二、实现\n\n在`javascript`中，实现一个单例模式可以用一个变量来标志当前的类已经创建过对象，如果下次获取当前类的实例时，直接返回之前创建的对象即可，如下：\n\n  ```JS\n  // 定义一个类\n  function Singleton(name) {\n      this.name = name;\n      this.instance = null;\n  }\n  // 原型扩展类的一个方法getName()\n  Singleton.prototype.getName = function() {\n      console.log(this.name)\n  };\n  // 获取类的实例\n  Singleton.getInstance = function(name) {\n      if(!this.instance) {\n          this.instance = new Singleton(name);\n      }\n      return this.instance\n  };\n  \n  // 获取对象1\n  const a = Singleton.getInstance('a');\n  // 获取对象2\n  const b = Singleton.getInstance('b');\n  // 进行比较\n  console.log(a === b);\n  ```\n\n  使用闭包也能够实现，如下：\n\n  ```js\n  function Singleton(name) {\n      this.name = name;\n  }\n  // 原型扩展类的一个方法getName()\n  Singleton.prototype.getName = function() {\n      console.log(this.name)\n  };\n  // 获取类的实例\n  Singleton.getInstance = (function() {\n      var instance = null;\n      return function(name) {\n          if(!this.instance) {\n              this.instance = new Singleton(name);\n          }\n          return this.instance\n      }        \n  })();\n  \n  // 获取对象1\n  const a = Singleton.getInstance('a');\n  // 获取对象2\n  const b = Singleton.getInstance('b');\n  // 进行比较\n  console.log(a === b);\n  ```\n\n  也可以将上述的方法稍作修改，变成构造函数的形式，如下：\n\n  ```js\n  // 单例构造函数\n  function CreateSingleton (name) {\n      this.name = name;\n      this.getName();\n  };\n  \n  // 获取实例的名字\n  CreateSingleton.prototype.getName = function() {\n      console.log(this.name)\n  };\n  // 单例对象\n  const Singleton = (function(){\n      var instance;\n      return function (name) {\n          if(!instance) {\n              instance = new CreateSingleton(name);\n          }\n          return instance;\n      }\n  })();\n  \n  // 创建实例对象1\n  const a = new Singleton('a');\n  // 创建实例对象2\n  const b = new Singleton('b');\n  \n  console.log(a===b); // true\n  ```\n\n  \n\n  ## 三、使用场景\n\n  在前端中，很多情况都是用到单例模式，例如页面存在一个模态框的时候，只有用户点击的时候才会创建，而不是加载完成之后再创建弹窗和隐藏，并且保证弹窗全局只有一个\n\n  可以先创建一个通常的获取对象的方法，如下：\n\n  ```js\n  const getSingle = function( fn ){\n    let result;\n    return function(){\n      return result || ( result = fn .apply(this, arguments ) );\n    }\n  }; \n  ```\n\n  创建弹窗的代码如下：\n\n  ```js\n  const createLoginLayer = function(){\n    var div = document.createElement( 'div' );\n    div.innerHTML = '我是浮窗';\n    div.style.display = 'none';\n    document.body.appendChild( div );\n    return div;\n  }; \n  \n  const createSingleLoginLayer = getSingle( createLoginLayer ); \n  \n  document.getElementById( 'loginBtn' ).onclick = function(){\n    var loginLayer = createSingleLoginLayer();\n    loginLayer.style.display = 'block';\n  };\n  ```\n\n  上述这种实现称为惰性单例，意图解决需要时才创建类实例对象\n\n  并且`Vuex`、`redux`全局态管理库也应用单例模式的思想，如下图：\n\n   ![](https://static.vue-js.com/8be50f80-3b2b-11ec-a752-75723a64e8f5.png)\n\n  现在很多第三方库都是单例模式，多次引用只会使用同一个对象，如`jquery`、`lodash`、`moment`...\n\n  \n\n  ## 参考文献\n\n  - https://zh.wikipedia.org/zh-hans/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F\n  - https://www.runoob.com/design-pattern/singleton-pattern.html\n  - https://juejin.cn/post/6844903874210299912#heading-5"
  },
  {
    "path": "docs/design/Strategy Pattern.md",
    "content": "# 面试官：说说你对策略模式的理解？应用场景？\n\n ![](https://static.vue-js.com/e4aad950-3cb2-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n策略模式（Strategy Pattern）指的是定义一系列的算法，把它们一个个封装起来，目的就是将算法的使用与算法的实现分离开来\n\n一个基于策略模式的程序至少由两部分组成：\n\n- 策略类，策略类封装了具体的算法，并负责具体的计算过程\n- 环境类Context，Context 接受客户的请求，随后 把请求委托给某一个策略类\n\n\n## 二、使用\n\n举个例子，公司的年终奖是根据员工的工资和绩效来考核的，绩效为A的人，年终奖为工资的4倍，绩效为B的人，年终奖为工资的3倍，绩效为C的人，年终奖为工资的2倍\n\n若使用`if`来实现，代码则如下：\n\n```js\nvar calculateBouns = function(salary,level) {\n    if(level === 'A') {\n        return salary * 4;\n    }\n    if(level === 'B') {\n        return salary * 3;\n    }\n    if(level === 'C') {\n        return salary * 2;\n    }\n};\n// 调用如下：\nconsole.log(calculateBouns(4000,'A')); // 16000\nconsole.log(calculateBouns(2500,'B')); // 7500\n```\n\n从上述可有看到，函数内部包含过多`if...else`，并且后续改正的时候，需要在函数内部添加逻辑，违反了开放封闭原则\n\n而如果使用策略模式，就是先定义一系列算法，把它们一个个封装起来，将不变的部分和变化的部分隔开，如下：\n\n```js\nvar obj = {\n        \"A\": function(salary) {\n            return salary * 4;\n        },\n        \"B\" : function(salary) {\n            return salary * 3;\n        },\n        \"C\" : function(salary) {\n            return salary * 2;\n        } \n};\nvar calculateBouns =function(level,salary) {\n    return obj[level](salary);\n};\nconsole.log(calculateBouns('A',10000)); // 40000\n```\n\n上述代码中，`obj`对应的是策略类，而`calculateBouns`对应上下通信类\n\n又比如实现一个表单校验的代码，常常会像如下写法：\n\n```js\nvar registerForm = document.getElementById(\"registerForm\");\nregisterForm.onsubmit = function(){\n    if(registerForm.userName.value === '') {\n        alert('用户名不能为空');\n        return;\n    }\n    if(registerForm.password.value.length < 6) {\n        alert(\"密码的长度不能小于6位\");\n        return;\n    }\n    if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {\n        alert(\"手机号码格式不正确\");\n        return;\n    }\n}\n```\n\n上述代码包含多处`if`语句，并且违反了开放封闭原则，如果应用中还有其他的表单，需要重复编写代码\n\n此处也可以使用策略模式进行重构校验，第一步确定不变的内容，即策略规则对象，如下：\n\n```js\nvar strategy = {\n    isNotEmpty: function(value,errorMsg) {\n        if(value === '') {\n            return errorMsg;\n        }\n    },\n    // 限制最小长度\n    minLength: function(value,length,errorMsg) {\n        if(value.length < length) {\n            return errorMsg;\n        }\n    },\n    // 手机号码格式\n    mobileFormat: function(value,errorMsg) {\n        if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) {\n            return errorMsg;\n        }\n    } \n};\n```\n\n然后找出变的地方，作为环境类`context`，负责接收用户的要求并委托给策略规则对象，如下`Validator`类：\n\n```js\nvar Validator = function(){\n        this.cache = [];  // 保存效验规则\n};\nValidator.prototype.add = function(dom,rule,errorMsg) {\n    var str = rule.split(\":\");\n    this.cache.push(function(){\n        // str 返回的是 minLength:6 \n        var strategy = str.shift();\n        str.unshift(dom.value); // 把input的value添加进参数列表\n        str.push(errorMsg);  // 把errorMsg添加进参数列表\n        return strategys[strategy].apply(dom,str);\n    });\n};\nValidator.prototype.start = function(){\n    for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {\n        var msg = validatorFunc(); // 开始效验 并取得效验后的返回信息\n        if(msg) {\n            return msg;\n        }\n    }\n};\n```\n\n通过`validator.add`方法添加校验规则和错误信息提示，使用如下：\n\n```js\nvar validateFunc = function(){\n    var validator = new Validator(); // 创建一个Validator对象\n    /* 添加一些效验规则 */\n    validator.add(registerForm.userName,'isNotEmpty','用户名不能为空');\n    validator.add(registerForm.password,'minLength:6','密码长度不能小于6位');\n    validator.add(registerForm.userName,'mobileFormat','手机号码格式不正确');\n\n    var errorMsg = validator.start(); // 获得效验结果\n    return errorMsg; // 返回效验结果\n};\nvar registerForm = document.getElementById(\"registerForm\");\nregisterForm.onsubmit = function(){\n    var errorMsg = validateFunc();\n    if(errorMsg){\n        alert(errorMsg);\n        return false;\n    }\n}\n```\n\n上述通过策略模式完成表单的验证，并且可以随时调用，在修改表单验证规则的时候，也非常方便，通过传递参数即可调用\n\n\n\n\n\n## 三、应用场景\n\n从上面可以看到，使用策略模式的优点有如下：\n\n- 策略模式利用组合，委托等技术和思想，有效的避免很多if条件语句\n- 策略模式提供了开放-封闭原则，使代码更容易理解和扩展\n- 策略模式中的代码可以复用\n\n策略模式不仅仅用来封装算法，在实际开发中，通常会把算法的含义扩散开来，使策略模式也可以用来封装 一系列的“业务规则”\n\n只要这些业务规则指向的目标一致，并且可以被替换使用，我们就可以用策略模式来封装它们\n\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000021883055\n- https://juejin.cn/post/6844903504109109262\n- https://juejin.cn/post/6844903751225081864"
  },
  {
    "path": "docs/design/design.md",
    "content": "# 面试官：说说对设计模式的理解？常见的设计模式有哪些？\n\n![](https://static.vue-js.com/065bc170-37ce-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n在软件工程中，设计模式是对软件设计中普遍存在的各种问题所提出的解决方案\n\n设计模式并不直接用来完成代码的编写，而是描述在各种不同情况下，要怎么解决问题的一种方案\n\n设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象，避免会引起麻烦的紧耦合，以增强软件设计面对并适应变化的能力\n\n因此，当我们遇到合适的场景时，我们可能会条件反射一样自然而然想到符合这种场景的设计模式\n\n比如，当系统中某个接口的结构已经无法满足我们现在的业务需求，但又不能改动这个接口，因为可能原来的系统很多功能都依赖于这个接口，改动接口会牵扯到太多文件\n\n因此应对这种场景，我们可以很快地想到可以用适配器模式来解决这个问题\n\n\n\n## 二、有哪些\n\n常见的设计模式有：\n\n- 单例模式\n- 工厂模式\n- 策略模式\n- 代理模式\n- 中介者模式\n- 装饰者模式\n- ......\n\n\n\n\n\n\n\n### 单例模式\n\n保证一个类仅有一个实例，并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否，如果存在则直接返回，如果不存在就创建了再返回，这就确保了一个类只有一个实例对象\n\n如下图的车，只有一辆，一旦借出去则不能再借给别人：\n\n ![](https://static.vue-js.com/ea527aa0-37cd-11ec-8e64-91fdec0f05a1.png)\n\n\n\n\n\n### 工厂模式\n\n工厂模式通常会分成3个角色：\n\n- 工厂角色-负责实现创建所有实例的内部逻辑.\n- 抽象产品角色-是所创建的所有对象的父类，负责描述所有实例所共有的公共接口\n- 具体产品角色-是创建目标，所有创建的对象都充当这个角色的某个具体类的实例\n\n ![](https://static.vue-js.com/fadd1920-37cd-11ec-8e64-91fdec0f05a1.png)\n\n\n\n### 策略模式\n\n策略模式，就是定义一系列的算法，把他们一个个封装起来，并且使他们可以相互替换\n\n至少分成两部分：\n\n- 策略类（可变），策略类封装了具体的算法，并负责具体的计算过程\n- 环境类（不变），接受客户的请求，随后将请求委托给某一个策略类\n\n\n\n\n\n\n\n### 代理模式\n\n代理模式：为对象提供一个代用品或占位符，以便控制对它的访问\n\n例如实现图片懒加载的功能，先通过一张`loading`图占位，然后通过异步的方式加载图片，等图片加载好了再把完成的图片加载到`img`标签里面\n\n\n\n### 中介者模式\n\n中介者模式的定义：通过一个中介者对象，其他所有的相关对象都通过该中介者对象来通信，而不是相互引用，当其中的一个对象发生改变时，只需要通知中介者对象即可\n\n通过中介者模式可以解除对象与对象之间的紧耦合关系\n\n\n\n### 装饰者模式\n\n装饰者模式的定义：在不改变对象自身的基础上，在程序运行期间给对象动态地添加方法\n\n通常运用在原有方法维持不变，在原有方法上再挂载其他方法来满足现有需求\n\n\n\n## 三、总结\n\n不断去学习设计模式，会对我们有着极大的帮助，主要如下：\n\n- 从许多优秀的软件系统中总结出的成功的、能够实现可维护性、复用的设计方案，使用这些方案将可以让我们避免做一些重复性的工作\n- 设计模式提供了一套通用的设计词汇和一种通用的形式来方便开发人员之间沟通和交流，使得设计方案更加通俗易懂\n\n- 大部分设计模式都兼顾了系统的可重用性和可扩展性，这使得我们可以更好地重用一些已有的设计方案、功能模块甚至一个完整的软件系统，避免我们经常做一些重复的设计、编写一些重复的代码\n\n- 合理使用设计模式并对设计模式的使用情况进行文档化，将有助于别人更快地理解系统\n\n- 学习设计模式将有助于初学者更加深入地理解面向对象思想\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA)\n- https://juejin.cn/post/6844903795017646094\n- https://segmentfault.com/a/1190000030850326"
  },
  {
    "path": "docs/es6/array.md",
    "content": "# 面试官：ES6中数组新增了哪些扩展？\n\n ![](https://static.vue-js.com/a156b8d0-53c5-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、扩展运算符的应用\n\nES6通过扩展元素符`...`，好比 `rest` 参数的逆运算，将一个数组转为用逗号分隔的参数序列\n\n```js\nconsole.log(...[1, 2, 3])\n// 1 2 3\n\nconsole.log(1, ...[2, 3, 4], 5)\n// 1 2 3 4 5\n\n[...document.querySelectorAll('div')]\n// [<div>, <div>, <div>]\n```\n\n主要用于函数调用的时候，将一个数组变为参数序列\n\n```js\nfunction push(array, ...items) {\n  array.push(...items);\n}\n\nfunction add(x, y) {\n  return x + y;\n}\n\nconst numbers = [4, 38];\nadd(...numbers) // 42\n```\n\n可以将某些数据结构转为数组\n\n```js\n[...document.querySelectorAll('div')]\n```\n\n能够更简单实现数组复制\n\n```js\nconst a1 = [1, 2];\nconst [...a2] = a1;\n// [1,2]\n```\n\n数组的合并也更为简洁了\n\n```js\nconst arr1 = ['a', 'b'];\nconst arr2 = ['c'];\nconst arr3 = ['d', 'e'];\n[...arr1, ...arr2, ...arr3]\n// [ 'a', 'b', 'c', 'd', 'e' ]\n```\n\n注意：通过扩展运算符实现的是浅拷贝，修改了引用指向的值，会同步反映到新数组\n\n下面看个例子就清楚多了\n\n```js\nconst arr1 = ['a', 'b',[1,2]];\nconst arr2 = ['c'];\nconst arr3  = [...arr1,...arr2]\narr[1][0] = 9999 // 修改arr1里面数组成员值\nconsole.log(arr[3]) // 影响到arr3,['a','b',[9999,2],'c']\n```\n\n扩展运算符可以与解构赋值结合起来，用于生成数组\n\n```js\nconst [first, ...rest] = [1, 2, 3, 4, 5];\nfirst // 1\nrest  // [2, 3, 4, 5]\n\nconst [first, ...rest] = [];\nfirst // undefined\nrest  // []\n\nconst [first, ...rest] = [\"foo\"];\nfirst  // \"foo\"\nrest   // []\n```\n\n如果将扩展运算符用于数组赋值，只能放在参数的最后一位，否则会报错\n\n```js\nconst [...butLast, last] = [1, 2, 3, 4, 5];\n// 报错\n\nconst [first, ...middle, last] = [1, 2, 3, 4, 5];\n// 报错\n```\n\n可以将字符串转为真正的数组\n\n```javascript\n[...'hello']\n// [ \"h\", \"e\", \"l\", \"l\", \"o\" ]\n```\n\n定义了遍历器（Iterator）接口的对象，都可以用扩展运算符转为真正的数组\n\n```js\nlet nodeList = document.querySelectorAll('div');\nlet array = [...nodeList];\n\nlet map = new Map([\n  [1, 'one'],\n  [2, 'two'],\n  [3, 'three'],\n]);\n\nlet arr = [...map.keys()]; // [1, 2, 3]\n```\n\n如果对没有 Iterator 接口的对象，使用扩展运算符，将会报错\n\n```javascript\nconst obj = {a: 1, b: 2};\nlet arr = [...obj]; // TypeError: Cannot spread non-iterable object\n```\n\n\n\n## 二、构造函数新增的方法\n\n关于构造函数，数组新增的方法有如下：\n\n- Array.from()\n- Array.of()\n\n### Array.from()\n\n将两类对象转为真正的数组：类似数组的对象和可遍历`（iterable）`的对象（包括 `ES6` 新增的数据结构 `Set` 和 `Map`）\n\n```js\nlet arrayLike = {\n    '0': 'a',\n    '1': 'b',\n    '2': 'c',\n    length: 3\n};\nlet arr2 = Array.from(arrayLike); // ['a', 'b', 'c']\n```\n\n还可以接受第二个参数，用来对每个元素进行处理，将处理后的值放入返回的数组\n\n```js\nArray.from([1, 2, 3], (x) => x * x)\n// [1, 4, 9]\n```\n\n\n\n### Array.of()\n\n用于将一组值，转换为数组\n\n```js\nArray.of(3, 11, 8) // [3,11,8]\n```\n\n没有参数的时候，返回一个空数组\n\n当参数只有一个的时候，实际上是指定数组的长度\n\n参数个数不少于 2 个时，`Array()`才会返回由参数组成的新数组\n\n```js\nArray() // []\nArray(3) // [, , ,]\nArray(3, 11, 8) // [3, 11, 8]\n```\n\n\n\n### 三、实例对象新增的方法\n\n关于数组实例对象新增的方法有如下：\n\n- copyWithin()\n- find()、findIndex()\n- fill()\n- entries()，keys()，values()\n- includes()\n- flat()，flatMap()\n\n### copyWithin()\n\n将指定位置的成员复制到其他位置（会覆盖原有成员），然后返回当前数组\n\n参数如下：\n\n- target（必需）：从该位置开始替换数据。如果为负值，表示倒数。\n- start（可选）：从该位置开始读取数据，默认为 0。如果为负值，表示从末尾开始计算。\n- end（可选）：到该位置前停止读取数据，默认等于数组长度。如果为负值，表示从末尾开始计算。\n\n```js\n[1, 2, 3, 4, 5].copyWithin(0, 3) // 将从 3 号位直到数组结束的成员（4 和 5），复制到从 0 号位开始的位置，结果覆盖了原来的 1 和 2\n// [4, 5, 3, 4, 5] \n```\n\n\n\n### find()、findIndex()\n\n`find()`用于找出第一个符合条件的数组成员\n\n参数是一个回调函数，接受三个参数依次为当前的值、当前的位置和原数组\n\n```js\n[1, 5, 10, 15].find(function(value, index, arr) {\n  return value > 9;\n}) // 10\n```\n\n`findIndex`返回第一个符合条件的数组成员的位置，如果所有成员都不符合条件，则返回`-1`\n\n```javascript\n[1, 5, 10, 15].findIndex(function(value, index, arr) {\n  return value > 9;\n}) // 2\n```\n\n这两个方法都可以接受第二个参数，用来绑定回调函数的`this`对象。\n\n```js\nfunction f(v){\n  return v > this.age;\n}\nlet person = {name: 'John', age: 20};\n[10, 12, 26, 15].find(f, person);    // 26\n```\n\n\n\n### fill()\n\n使用给定值，填充一个数组\n\n```javascript\n['a', 'b', 'c'].fill(7)\n// [7, 7, 7]\n\nnew Array(3).fill(7)\n// [7, 7, 7]\n```\n\n还可以接受第二个和第三个参数，用于指定填充的起始位置和结束位置\n\n```js\n['a', 'b', 'c'].fill(7, 1, 2)\n// ['a', 7, 'c']\n```\n\n注意，如果填充的类型为对象，则是浅拷贝\n\n\n\n### entries()，keys()，values()\n\n`keys()`是对键名的遍历、`values()`是对键值的遍历，`entries()`是对键值对的遍历\n\n```js\nor (let index of ['a', 'b'].keys()) {\n  console.log(index);\n}\n// 0\n// 1\n\nfor (let elem of ['a', 'b'].values()) {\n  console.log(elem);\n}\n// 'a'\n// 'b'\n\nfor (let [index, elem] of ['a', 'b'].entries()) {\n  console.log(index, elem);\n}\n// 0 \"a\"\n```\n\n\n\n### includes()\n\n用于判断数组是否包含给定的值\n\n```js\n[1, 2, 3].includes(2)     // true\n[1, 2, 3].includes(4)     // false\n[1, 2, NaN].includes(NaN) // true\n```\n\n方法的第二个参数表示搜索的起始位置，默认为`0`\n\n参数为负数则表示倒数的位置\n\n```js\n[1, 2, 3].includes(3, 3);  // false\n[1, 2, 3].includes(3, -1); // true\n```\n\n\n\n### flat()，flatMap()\n\n将数组扁平化处理，返回一个新数组，对原数据没有影响\n\n```js\n[1, 2, [3, 4]].flat()\n// [1, 2, 3, 4]\n```\n\n`flat()`默认只会“拉平”一层，如果想要“拉平”多层的嵌套数组，可以将`flat()`方法的参数写成一个整数，表示想要拉平的层数，默认为1\n\n```js\n[1, 2, [3, [4, 5]]].flat()\n// [1, 2, 3, [4, 5]]\n\n[1, 2, [3, [4, 5]]].flat(2)\n// [1, 2, 3, 4, 5]\n```\n\n`flatMap()`方法对原数组的每个成员执行一个函数相当于执行`Array.prototype.map()`，然后对返回值组成的数组执行`flat()`方法。该方法返回一个新数组，不改变原数组\n\n```js\n// 相当于 [[2, 4], [3, 6], [4, 8]].flat()\n[2, 3, 4].flatMap((x) => [x, x * 2])\n// [2, 4, 3, 6, 4, 8]\n```\n\n`flatMap()`方法还可以有第二个参数，用来绑定遍历函数里面的`this`\n\n\n\n### 四、数组的空位\n\n数组的空位指，数组的某一个位置没有任何值\n\nES6 则是明确将空位转为`undefined`，包括`Array.from`、扩展运算符、`copyWithin()`、`fill()`、`entries()`、`keys()`、`values()`、`find()`和`findIndex()`\n\n建议大家在日常书写中，避免出现空位\n\n\n\n\n\n### 五、排序稳定性\n\n将`sort()`默认设置为稳定的排序算法\n\n```js\nconst arr = [\n  'peach',\n  'straw',\n  'apple',\n  'spork'\n];\n\nconst stableSorting = (s1, s2) => {\n  if (s1[0] < s2[0]) return -1;\n  return 1;\n};\n\narr.sort(stableSorting)\n// [\"apple\", \"peach\", \"straw\", \"spork\"]\n```\n\n排序结果中，`straw`在`spork`的前面，跟原始顺序一致\n\n\n\n\n## 参考文献\n\n- https://es6.ruanyifeng.com/#docs/array\n"
  },
  {
    "path": "docs/es6/decorator.md",
    "content": "# 面试官：你是怎么理解ES6中 Decorator 的？使用场景？\n\n ![](https://static.vue-js.com/7df43560-5ba5-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、介绍\n\nDecorator，即装饰器，从名字上很容易让我们联想到装饰者模式\n\n简单来讲，装饰者模式就是一种在不改变原类和使用继承的情况下，动态地扩展对象功能的设计理论。\n\n`ES6`中`Decorator`功能亦如此，其本质也不是什么高大上的结构，就是一个普通的函数，用于扩展类属性和类方法\n\n这里定义一个士兵，这时候他什么装备都没有\n\n```js\nclass soldier{ \n}\n```\n\n定义一个得到 AK 装备的函数，即装饰器\n\n```js\nfunction strong(target){\n    target.AK = true\n}\n```\n\n使用该装饰器对士兵进行增强\n\n```js\n@strong\nclass soldier{\n}\n```\n\n这时候士兵就有武器了\n\n```js\nsoldier.AK // true\n```\n\n上述代码虽然简单，但也能够清晰看到了使用`Decorator`两大优点：\n\n- 代码可读性变强了，装饰器命名相当于一个注释\n- 在不改变原有代码情况下，对原来功能进行扩展\n\n\n\n## 二、用法\n\n`Docorator`修饰对象为下面两种：\n\n- 类的装饰\n- 类属性的装饰\n\n### 类的装饰\n\n当对类本身进行装饰的时候，能够接受一个参数，即类本身\n\n将装饰器行为进行分解，大家能够有个更深入的了解\n\n```js\n@decorator\nclass A {}\n\n// 等同于\n\nclass A {}\nA = decorator(A) || A;\n```\n\n下面`@testable`就是一个装饰器，`target`就是传入的类，即`MyTestableClass`，实现了为类添加静态属性\n\n```js\n@testable\nclass MyTestableClass {\n  // ...\n}\n\nfunction testable(target) {\n  target.isTestable = true;\n}\n\nMyTestableClass.isTestable // true\n```\n\n如果想要传递参数，可以在装饰器外层再封装一层函数\n\n```js\nfunction testable(isTestable) {\n  return function(target) {\n    target.isTestable = isTestable;\n  }\n}\n\n@testable(true)\nclass MyTestableClass {}\nMyTestableClass.isTestable // true\n\n@testable(false)\nclass MyClass {}\nMyClass.isTestable // false\n```\n\n\n\n### 类属性的装饰\n\n当对类属性进行装饰的时候，能够接受三个参数：\n\n- 类的原型对象\n- 需要装饰的属性名\n- 装饰属性名的描述对象\n\n首先定义一个`readonly`装饰器\n\n```js\nfunction readonly(target, name, descriptor){\n  descriptor.writable = false; // 将可写属性设为false\n  return descriptor;\n}\n```\n\n使用`readonly`装饰类的`name`方法\n\n```javascript\nclass Person {\n  @readonly\n  name() { return `${this.first} ${this.last}` }\n}\n```\n\n相当于以下调用\n\n```js\nreadonly(Person.prototype, 'name', descriptor);\n```\n\n如果一个方法有多个装饰器，就像洋葱一样，先从外到内进入，再由内到外执行\n\n```javascript\nfunction dec(id){\n    console.log('evaluated', id);\n    return (target, property, descriptor) =>console.log('executed', id);\n}\n\nclass Example {\n    @dec(1)\n    @dec(2)\n    method(){}\n}\n// evaluated 1\n// evaluated 2\n// executed 2\n// executed 1\n```\n\n外层装饰器`@dec(1)`先进入，但是内层装饰器`@dec(2)`先执行\n\n\n\n### 注意\n\n装饰器不能用于修饰函数，因为函数存在变量声明情况\n\n```js\nvar counter = 0;\n\nvar add = function () {\n  counter++;\n};\n\n@add\nfunction foo() {\n}\n```\n\n编译阶段，变成下面\n\n```js\nvar counter;\nvar add;\n\n@add\nfunction foo() {\n}\n\ncounter = 0;\n\nadd = function () {\n  counter++;\n};\n```\n\n意图是执行后`counter`等于 1，但是实际上结果是`counter`等于 0\n\n\n\n## 三、使用场景\n\n基于`Decorator`强大的作用，我们能够完成各种场景的需求，下面简单列举几种：\n\n使用`react-redux`的时候，如果写成下面这种形式，既不雅观也很麻烦\n\n```js\nclass MyReactComponent extends React.Component {}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);\n```\n\n通过装饰器就变得简洁多了\n\n```js\n@connect(mapStateToProps, mapDispatchToProps)\nexport default class MyReactComponent extends React.Component {}\n```\n\n将`mixins`，也可以写成装饰器，让使用更为简洁了\n\n```js\nfunction mixins(...list) {\n  return function (target) {\n    Object.assign(target.prototype, ...list);\n  };\n}\n\n// 使用\nconst Foo = {\n  foo() { console.log('foo') }\n};\n\n@mixins(Foo)\nclass MyClass {}\n\nlet obj = new MyClass();\nobj.foo() // \"foo\"\n```\n\n\n\n下面再讲讲`core-decorators.js`几个常见的装饰器\n\n#### @antobind\n\n`autobind`装饰器使得方法中的`this`对象，绑定原始对象\n\n```javascript\nimport { autobind } from 'core-decorators';\n\nclass Person {\n  @autobind\n  getPerson() {\n    return this;\n  }\n}\n\nlet person = new Person();\nlet getPerson = person.getPerson;\n\ngetPerson() === person;\n// true\n```\n\n\n\n#### @readonly\n\n`readonly`装饰器使得属性或方法不可写\n\n```javascript\nimport { readonly } from 'core-decorators';\n\nclass Meal {\n  @readonly\n  entree = 'steak';\n}\n\nvar dinner = new Meal();\ndinner.entree = 'salmon';\n// Cannot assign to read only property 'entree' of [object Object]\n```\n\n\n\n\n\n#### @deprecate\n\n`deprecate`或`deprecated`装饰器在控制台显示一条警告，表示该方法将废除\n\n```javascript\nimport { deprecate } from 'core-decorators';\n\nclass Person {\n  @deprecate\n  facepalm() {}\n\n  @deprecate('功能废除了')\n  facepalmHard() {}\n}\n\nlet person = new Person();\n\nperson.facepalm();\n// DEPRECATION Person#facepalm: This function will be removed in future versions.\n\nperson.facepalmHard();\n// DEPRECATION Person#facepalmHard: 功能废除了\n\n```\n\n\n\n## 参考文献\n\n- https://es6.ruanyifeng.com/#docs/decorator"
  },
  {
    "path": "docs/es6/function.md",
    "content": "#  面试官：对象新增了哪些扩展？\n\n![](https://static.vue-js.com/54a04a10-5569-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、参数\n\n`ES6`允许为函数的参数设置默认值\n\n```js\nfunction log(x, y = 'World') {\n  console.log(x, y);\n}\n\nconsole.log('Hello') // Hello World\nconsole.log('Hello', 'China') // Hello China\nconsole.log('Hello', '') // Hello\n```\n\n函数的形参是默认声明的，不能使用`let`或`const`再次声明\n\n```js\nfunction foo(x = 5) {\n    let x = 1; // error\n    const x = 2; // error\n}\n```\n\n参数默认值可以与解构赋值的默认值结合起来使用\n\n```js\nfunction foo({x, y = 5}) {\n  console.log(x, y);\n}\n\nfoo({}) // undefined 5\nfoo({x: 1}) // 1 5\nfoo({x: 1, y: 2}) // 1 2\nfoo() // TypeError: Cannot read property 'x' of undefined\n```\n\n上面的`foo`函数，当参数为对象的时候才能进行解构，如果没有提供参数的时候，变量`x`和`y`就不会生成，从而报错，这里设置默认值避免\n\n```js\nfunction foo({x, y = 5} = {}) {\n  console.log(x, y);\n}\n\nfoo() // undefined 5\n```\n\n参数默认值应该是函数的尾参数，如果不是非尾部的参数设置默认值，实际上这个参数是没发省略的\n\n```javascript\nfunction f(x = 1, y) {\n  return [x, y];\n}\n\nf() // [1, undefined]\nf(2) // [2, undefined]\nf(, 1) // 报错\nf(undefined, 1) // [1, 1]\n```\n\n\n\n## 二、属性\n\n### 函数的length属性\n\n`length`将返回没有指定默认值的参数个数\n\n```js\n(function (a) {}).length // 1\n(function (a = 5) {}).length // 0\n(function (a, b, c = 5) {}).length // 2\n```\n\n`rest` 参数也不会计入`length`属性\n\n```js\n(function(...args) {}).length // 0\n```\n\n如果设置了默认值的参数不是尾参数，那么`length`属性也不再计入后面的参数了\n\n```js\n(function (a = 0, b, c) {}).length // 0\n(function (a, b = 1, c) {}).length // 1\n```\n\n\n\n### name属性\n\n返回该函数的函数名\n\n```js\nvar f = function () {};\n\n// ES5\nf.name // \"\"\n\n// ES6\nf.name // \"f\"\n```\n\n如果将一个具名函数赋值给一个变量，则 `name`属性都返回这个具名函数原本的名字\n\n```js\nconst bar = function baz() {};\nbar.name // \"baz\"\n```\n\n`Function`构造函数返回的函数实例，`name`属性的值为`anonymous`\n\n```javascript\n(new Function).name // \"anonymous\"\n```\n\n`bind`返回的函数，`name`属性值会加上`bound`前缀\n\n```javascript\nfunction foo() {};\nfoo.bind({}).name // \"bound foo\"\n\n(function(){}).bind({}).name // \"bound \"\n```\n\n\n\n## 三、作用域\n\n一旦设置了参数的默认值，函数进行声明初始化时，参数会形成一个单独的作用域\n\n等到初始化结束，这个作用域就会消失。这种语法行为，在不设置参数默认值时，是不会出现的\n\n下面例子中，`y=x`会形成一个单独作用域，`x`没有被定义，所以指向全局变量`x`\n\n```js\nlet x = 1;\n\nfunction f(y = x) { \n  // 等同于 let y = x  \n  let x = 2; \n  console.log(y);\n}\n\nf() // 1\n```\n\n\n\n## 四、严格模式\n\n只要函数参数使用了默认值、解构赋值、或者扩展运算符，那么函数内部就不能显式设定为严格模式，否则会报错\n\n```js\n// 报错\nfunction doSomething(a, b = a) {\n  'use strict';\n  // code\n}\n\n// 报错\nconst doSomething = function ({a, b}) {\n  'use strict';\n  // code\n};\n\n// 报错\nconst doSomething = (...a) => {\n  'use strict';\n  // code\n};\n\nconst obj = {\n  // 报错\n  doSomething({a, b}) {\n    'use strict';\n    // code\n  }\n};\n```\n\n\n\n## 五、箭头函数\n\n使用“箭头”（`=>`）定义函数\n\n```js\nvar f = v => v;\n\n// 等同于\nvar f = function (v) {\n  return v;\n};\n```\n\n如果箭头函数不需要参数或需要多个参数，就使用一个圆括号代表参数部分\n\n```js\nvar f = () => 5;\n// 等同于\nvar f = function () { return 5 };\n\nvar sum = (num1, num2) => num1 + num2;\n// 等同于\nvar sum = function(num1, num2) {\n  return num1 + num2;\n};\n```\n\n如果箭头函数的代码块部分多于一条语句，就要使用大括号将它们括起来，并且使用`return`语句返回\n\n```js\nvar sum = (num1, num2) => { return num1 + num2; }\n```\n\n如果返回对象，需要加括号将对象包裹\n\n```js\nlet getTempItem = id => ({ id: id, name: \"Temp\" });\n```\n\n注意点：\n\n- 函数体内的`this`对象，就是定义时所在的对象，而不是使用时所在的对象\n- 不可以当作构造函数，也就是说，不可以使用`new`命令，否则会抛出一个错误\n- 不可以使用`arguments`对象，该对象在函数体内不存在。如果要用，可以用 `rest` 参数代替\n- 不可以使用`yield`命令，因此箭头函数不能用作 Generator 函数\n\n## 参考文献\n- https://es6.ruanyifeng.com/#docs/function"
  },
  {
    "path": "docs/es6/generator.md",
    "content": "# 面试官：你是怎么理解ES6中 Generator的？使用场景？\n\n ![](https://static.vue-js.com/7db499b0-5947-11eb-ab90-d9ae814b240d.png)\n\n## 一、介绍\n\nGenerator 函数是 ES6 提供的一种异步编程解决方案，语法行为与传统函数完全不同\n\n回顾下上文提到的解决异步的手段：\n\n- 回调函数\n- promise\n\n那么，上文我们提到`promsie`已经是一种比较流行的解决异步方案，那么为什么还出现`Generator`？甚至`async/await`呢？\n\n该问题我们留在后面再进行分析，下面先认识下`Generator`\n\n### Generator函数\n\n执行 `Generator` 函数会返回一个遍历器对象，可以依次遍历 `Generator` 函数内部的每一个状态\n\n形式上，`Generator `函数是一个普通函数，但是有两个特征：\n\n- `function`关键字与函数名之间有一个星号\n- 函数体内部使用`yield`表达式，定义不同的内部状态\n\n```javascript\nfunction* helloWorldGenerator() {\n  yield 'hello';\n  yield 'world';\n  return 'ending';\n}\n```\n\n\n\n## 二、使用\n\n`Generator` 函数会返回一个遍历器对象，即具有`Symbol.iterator`属性，并且返回给自己\n\n```javascript\nfunction* gen(){\n  // some code\n}\n\nvar g = gen();\n\ng[Symbol.iterator]() === g\n// true\n```\n\n通过`yield`关键字可以暂停`generator`函数返回的遍历器对象的状态\n\n```javascript\nfunction* helloWorldGenerator() {\n  yield 'hello';\n  yield 'world';\n  return 'ending';\n}\nvar hw = helloWorldGenerator();\n```\n\n上述存在三个状态：`hello`、`world`、`return`\n\n通过`next`方法才会遍历到下一个内部状态，其运行逻辑如下：\n\n- 遇到`yield`表达式，就暂停执行后面的操作，并将紧跟在`yield`后面的那个表达式的值，作为返回的对象的`value`属性值。\n- 下一次调用`next`方法时，再继续往下执行，直到遇到下一个`yield`表达式\n- 如果没有再遇到新的`yield`表达式，就一直运行到函数结束，直到`return`语句为止，并将`return`语句后面的表达式的值，作为返回的对象的`value`属性值。\n- 如果该函数没有`return`语句，则返回的对象的`value`属性值为`undefined`\n\n```javascript\nhw.next()\n// { value: 'hello', done: false }\n\nhw.next()\n// { value: 'world', done: false }\n\nhw.next()\n// { value: 'ending', done: true }\n\nhw.next()\n// { value: undefined, done: true }\n```\n\n`done`用来判断是否存在下个状态，`value`对应状态值\n\n`yield`表达式本身没有返回值，或者说总是返回`undefined`\n\n通过调用`next`方法可以带一个参数，该参数就会被当作上一个`yield`表达式的返回值\n\n```javascript\nfunction* foo(x) {\n  var y = 2 * (yield (x + 1));\n  var z = yield (y / 3);\n  return (x + y + z);\n}\n\nvar a = foo(5);\na.next() // Object{value:6, done:false}\na.next() // Object{value:NaN, done:false}\na.next() // Object{value:NaN, done:true}\n\nvar b = foo(5);\nb.next() // { value:6, done:false }\nb.next(12) // { value:8, done:false }\nb.next(13) // { value:42, done:true }\n```\n\n正因为`Generator `函数返回`Iterator`对象，因此我们还可以通过`for...of`进行遍历\n```javascript\nfunction* foo() {\n  yield 1;\n  yield 2;\n  yield 3;\n  yield 4;\n  yield 5;\n  return 6;\n}\n\nfor (let v of foo()) {\n  console.log(v);\n}\n// 1 2 3 4 5\n```\n\n原生对象没有遍历接口，通过`Generator `函数为它加上这个接口，就能使用`for...of`进行遍历了\n\n```javascript\nfunction* objectEntries(obj) {\n  let propKeys = Reflect.ownKeys(obj);\n\n  for (let propKey of propKeys) {\n    yield [propKey, obj[propKey]];\n  }\n}\n\nlet jane = { first: 'Jane', last: 'Doe' };\n\nfor (let [key, value] of objectEntries(jane)) {\n  console.log(`${key}: ${value}`);\n}\n// first: Jane\n// last: Doe\n```\n\n\n\n## 三、异步解决方案\n\n回顾之前展开异步解决的方案：\n\n- 回调函数\n- Promise 对象\n- generator 函数\n- async/await\n\n\n\n这里通过文件读取案例，将几种解决异步的方案进行一个比较：\n\n### 回调函数\n\n所谓回调函数，就是把任务的第二段单独写在一个函数里面，等到重新执行这个任务的时候，再调用这个函数\n\n```javascript\nfs.readFile('/etc/fstab', function (err, data) {\n  if (err) throw err;\n  console.log(data);\n  fs.readFile('/etc/shells', function (err, data) {\n    if (err) throw err;\n    console.log(data);\n  });\n});\n```\n\n`readFile`函数的第三个参数，就是回调函数，等到操作系统返回了`/etc/passwd`这个文件以后，回调函数才会执行\n\n\n\n### Promise\n\n`Promise`就是为了解决回调地狱而产生的，将回调函数的嵌套，改成链式调用\n\n```js\nconst fs = require('fs');\n\nconst readFile = function (fileName) {\n  return new Promise(function (resolve, reject) {\n    fs.readFile(fileName, function(error, data) {\n      if (error) return reject(error);\n      resolve(data);\n    });\n  });\n};\n\n\nreadFile('/etc/fstab').then(data =>{\n    console.log(data)\n    return readFile('/etc/shells')\n}).then(data => {\n    console.log(data)\n})\n```\n\n这种链式操作形式，使异步任务的两段执行更清楚了，但是也存在了很明显的问题，代码变得冗杂了，语义化并不强\n\n\n\n### generator\n\n`yield`表达式可以暂停函数执行，`next`方法用于恢复函数执行，这使得`Generator`函数非常适合将异步任务同步化\n\n```javascript\nconst gen = function* () {\n  const f1 = yield readFile('/etc/fstab');\n  const f2 = yield readFile('/etc/shells');\n  console.log(f1.toString());\n  console.log(f2.toString());\n};\n```\n\n\n\n\n\n### async/await\n\n将上面`Generator`函数改成`async/await`形式，更为简洁，语义化更强了\n\n```js\nconst asyncReadFile = async function () {\n  const f1 = await readFile('/etc/fstab');\n  const f2 = await readFile('/etc/shells');\n  console.log(f1.toString());\n  console.log(f2.toString());\n};\n```\n\n\n\n### 区别：\n\n通过上述代码进行分析，将`promise`、`Generator`、`async/await`进行比较：\n\n- `promise`和`async/await`是专门用于处理异步操作的\n- `Generator`并不是为异步而设计出来的，它还有其他功能（对象迭代、控制输出、部署`Interator`接口...）\n- `promise`编写代码相比`Generator`、`async`更为复杂化，且可读性也稍差\n\n- `Generator`、`async`需要与`promise`对象搭配处理异步情况\n- `async`实质是`Generator`的语法糖，相当于会自动执行`Generator`函数\n- `async`使用上更为简洁，将异步代码以同步的形式进行编写，是处理异步编程的最终方案\n\n\n\n## 四、使用场景\n\n`Generator`是异步解决的一种方案，最大特点则是将异步操作同步化表达出来\n\n```js\nfunction* loadUI() {\n  showLoadingScreen();\n  yield loadUIDataAsynchronously();\n  hideLoadingScreen();\n}\nvar loader = loadUI();\n// 加载UI\nloader.next()\n\n// 卸载UI\nloader.next()\n```\n\n包括`redux-saga `中间件也充分利用了`Generator`特性\n\n```js\nimport { call, put, takeEvery, takeLatest } from 'redux-saga/effects'\nimport Api from '...'\n\nfunction* fetchUser(action) {\n   try {\n      const user = yield call(Api.fetchUser, action.payload.userId);\n      yield put({type: \"USER_FETCH_SUCCEEDED\", user: user});\n   } catch (e) {\n      yield put({type: \"USER_FETCH_FAILED\", message: e.message});\n   }\n}\n\nfunction* mySaga() {\n  yield takeEvery(\"USER_FETCH_REQUESTED\", fetchUser);\n}\n\nfunction* mySaga() {\n  yield takeLatest(\"USER_FETCH_REQUESTED\", fetchUser);\n}\n\nexport default mySaga;\n```\n\n还能利用`Generator`函数，在对象上实现`Iterator`接口\n\n```js\nfunction* iterEntries(obj) {\n  let keys = Object.keys(obj);\n  for (let i=0; i < keys.length; i++) {\n    let key = keys[i];\n    yield [key, obj[key]];\n  }\n}\n\nlet myObj = { foo: 3, bar: 7 };\n\nfor (let [key, value] of iterEntries(myObj)) {\n  console.log(key, value);\n}\n\n// foo 3\n// bar 7\n```\n\n\n\n## 参考文献\n\n- https://es6.ruanyifeng.com/#docs/generator-async"
  },
  {
    "path": "docs/es6/module.md",
    "content": "# 面试官：你是怎么理解ES6中Module的？使用场景？\n\n ![](https://static.vue-js.com/b6d19be0-5adb-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、介绍\n\n模块，（Module），是能够单独命名并独立地完成一定功能的程序语句的**集合（即程序代码和数据结构的集合体）**。\n\n两个基本的特征：外部特征和内部特征\n\n- 外部特征是指模块跟外部环境联系的接口（即其他模块或程序调用该模块的方式，包括有输入输出参数、引用的全局变量）和模块的功能\n\n- 内部特征是指模块的内部环境具有的特点（即该模块的局部数据和程序代码）\n\n### 为什么需要模块化\n\n- 代码抽象\n- 代码封装\n- 代码复用\n- 依赖管理\n\n如果没有模块化，我们代码会怎样？\n\n- 变量和方法不容易维护，容易污染全局作用域\n- 加载资源的方式通过script标签从上到下。\n- 依赖的环境主观逻辑偏重，代码较多就会比较复杂。\n- 大型项目资源难以维护，特别是多人合作的情况下，资源的引入会让人奔溃\n\n因此，需要一种将` JavaScript `程序模块化的机制，如\n\n- CommonJs (典型代表：node.js早期)\n- AMD (典型代表：require.js)\n- CMD (典型代表：sea.js)\n\n\n### AMD\n\n`Asynchronous ModuleDefinition`（AMD），异步模块定义，采用异步方式加载模块。所有依赖模块的语句，都定义在一个回调函数中，等到模块加载完成之后，这个回调函数才会运行\n\n代表库为`require.js`\n\n```js\n/** main.js 入口文件/主模块 **/\n// 首先用config()指定各模块路径和引用名\nrequire.config({\n  baseUrl: \"js/lib\",\n  paths: {\n    \"jquery\": \"jquery.min\",  //实际路径为js/lib/jquery.min.js\n    \"underscore\": \"underscore.min\",\n  }\n});\n// 执行基本操作\nrequire([\"jquery\",\"underscore\"],function($,_){\n  // some code here\n});\n```\n\n\n\n### CommonJs\n\n`CommonJS` 是一套 `Javascript` 模块规范，用于服务端\n\n```js\n// a.js\nmodule.exports={ foo , bar}\n\n// b.js\nconst { foo,bar } = require('./a.js')\n```\n\n其有如下特点：\n\n- 所有代码都运行在模块作用域，不会污染全局作用域\n- 模块是同步加载的，即只有加载完成，才能执行后面的操作\n- 模块在首次执行后就会缓存，再次加载只返回缓存结果，如果想要再次执行，可清除缓存\n- `require`返回的值是被输出的值的拷贝，模块内部的变化也不会影响这个值\n\n\n既然存在了`AMD`以及`CommonJs`机制，`ES6`的`Module`又有什么不一样？\n\nES6 在语言标准的层面上，实现了`Module`，即模块功能，完全可以取代 `CommonJS `和 `AMD `规范，成为浏览器和服务器通用的模块解决方案\n\n`CommonJS` 和` AMD` 模块，都只能在运行时确定这些东西。比如，`CommonJS `模块就是对象，输入时必须查找对象属性\n\n```javascript\n// CommonJS模块\nlet { stat, exists, readfile } = require('fs');\n\n// 等同于\nlet _fs = require('fs');\nlet stat = _fs.stat;\nlet exists = _fs.exists;\nlet readfile = _fs.readfile;\n```\n\n`ES6`设计思想是尽量的静态化，使得编译时就能确定模块的依赖关系，以及输入和输出的变量\n\n```js\n// ES6模块\nimport { stat, exists, readFile } from 'fs';\n```\n\n上述代码，只加载3个方法，其他方法不加载，即 `ES6` 可以在编译时就完成模块加载\n\n由于编译加载，使得静态分析成为可能。包括现在流行的`typeScript`也是依靠静态分析实现功能\n\n\n\n## 二、使用\n\n`ES6`模块内部自动采用了严格模式，这里就不展开严格模式的限制，毕竟这是`ES5`之前就已经规定好\n\n模块功能主要由两个命令构成：\n\n- `export`：用于规定模块的对外接口\n- `import`：用于输入其他模块提供的功能\n\n\n\n### export\n\n一个模块就是一个独立的文件，该文件内部的所有变量，外部无法获取。如果你希望外部能够读取模块内部的某个变量，就必须使用`export`关键字输出该变量\n\n```javascript\n// profile.js\nexport var firstName = 'Michael';\nexport var lastName = 'Jackson';\nexport var year = 1958;\n\n或 \n// 建议使用下面写法，这样能瞬间确定输出了哪些变量\nvar firstName = 'Michael';\nvar lastName = 'Jackson';\nvar year = 1958;\n\nexport { firstName, lastName, year };\n```\n\n输出函数或类\n\n```js\nexport function multiply(x, y) {\n  return x * y;\n};\n```\n\n通过`as`可以进行输出变量的重命名\n\n```js\nfunction v1() { ... }\nfunction v2() { ... }\n\nexport {\n  v1 as streamV1,\n  v2 as streamV2,\n  v2 as streamLatestVersion\n};\n```\n\n\n\n### import\n\n使用`export`命令定义了模块的对外接口以后，其他 JS 文件就可以通过`import`命令加载这个模块\n\n```javascript\n// main.js\nimport { firstName, lastName, year } from './profile.js';\n\nfunction setName(element) {\n  element.textContent = firstName + ' ' + lastName;\n}\n```\n\n同样如果想要输入变量起别名，通过`as`关键字\n\n```javascript\nimport { lastName as surname } from './profile.js';\n```\n\n当加载整个模块的时候，需要用到星号`*`\n\n```js\n// circle.js\nexport function area(radius) {\n  return Math.PI * radius * radius;\n}\n\nexport function circumference(radius) {\n  return 2 * Math.PI * radius;\n}\n\n// main.js\nimport * as circle from './circle';\nconsole.log(circle)   // {area:area,circumference:circumference}\n```\n\n输入的变量都是只读的，不允许修改，但是如果是对象，允许修改属性\n\n```js\nimport {a} from './xxx.js'\n\na.foo = 'hello'; // 合法操作\na = {}; // Syntax Error : 'a' is read-only;\n```\n\n不过建议即使能修改，但我们不建议。因为修改之后，我们很难差错\n\n`import`后面我们常接着`from`关键字，`from`指定模块文件的位置，可以是相对路径，也可以是绝对路径\n\n```js\nimport { a } from './a';\n```\n\n如果只有一个模块名，需要有配置文件，告诉引擎模块的位置\n\n```javascript\nimport { myMethod } from 'util';\n```\n\n在编译阶段，`import`会提升到整个模块的头部，首先执行\n\n```javascript\nfoo();\n\nimport { foo } from 'my_module';\n```\n\n多次重复执行同样的导入，只会执行一次\n\n```js\nimport 'lodash';\nimport 'lodash';\n```\n\n上面的情况，大家都能看到用户在导入模块的时候，需要知道加载的变量名和函数，否则无法加载\n\n如果不需要知道变量名或函数就完成加载，就要用到`export default`命令，为模块指定默认输出\n\n```js\n// export-default.js\nexport default function () {\n    console.log('foo');\n}\n```\n\n加载该模块的时候，`import`命令可以为该函数指定任意名字\n\n```js\n// import-default.js\nimport customName from './export-default';\ncustomName(); // 'foo'\n```\n\n\n\n### 动态加载\n\n允许您仅在需要时动态加载模块，而不必预先加载所有模块，这存在明显的性能优势\n\n这个新功能允许您将`import()`作为函数调用，将其作为参数传递给模块的路径。 它返回一个 `promise`，它用一个模块对象来实现，让你可以访问该对象的导出\n\n```js\nimport('/modules/myModule.mjs')\n  .then((module) => {\n    // Do something with the module.\n  });\n```\n\n\n\n### 复合写法\n\n如果在一个模块之中，先输入后输出同一个模块，`import`语句可以与`export`语句写在一起\n\n```javascript\nexport { foo, bar } from 'my_module';\n\n// 可以简单理解为\nimport { foo, bar } from 'my_module';\nexport { foo, bar };\n```\n\n同理能够搭配`as`、`*`搭配使用\n\n\n\n## 三、使用场景\n\n如今，`ES6`模块化已经深入我们日常项目开发中，像`vue`、`react`项目搭建项目，组件化开发处处可见，其也是依赖模块化实现\n\n`vue`组件\n\n```js\n<template>\n  <div class=\"App\">\n      组件化开发 ---- 模块化\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'HelloWorld',\n  props: {\n    msg: String\n  }\n}\n</script>\n```\n\n`react`组件\n\n```js\nfunction App() {\n  return (\n    <div className=\"App\">\n\t\t组件化开发 ---- 模块化\n    </div>\n  );\n}\n\nexport default App;\n```\n\n包括完成一些复杂应用的时候，我们也可以拆分成各个模块\n\n## 参考文献\n- https://macsalvation.net/the-history-of-js-module/\n- https://es6.ruanyifeng.com/#docs/module"
  },
  {
    "path": "docs/es6/object.md",
    "content": "#  面试官：对象新增了哪些扩展？\n\n![](https://static.vue-js.com/4da4dd40-5427-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、属性的简写\n\nES6中，当对象键名与对应值名相等的时候，可以进行简写\n\n```js\nconst baz = {foo:foo}\n\n// 等同于\nconst baz = {foo}\n```\n\n方法也能够进行简写\n\n```js\nconst o = {\n  method() {\n    return \"Hello!\";\n  }\n};\n\n// 等同于\n\nconst o = {\n  method: function() {\n    return \"Hello!\";\n  }\n}\n```\n\n在函数内作为返回值，也会变得方便很多\n\n```js\nfunction getPoint() {\n  const x = 1;\n  const y = 10;\n  return {x, y};\n}\n\ngetPoint()\n// {x:1, y:10}\n```\n\n注意：简写的对象方法不能用作构造函数，否则会报错\n\n```js\nconst obj = {\n  f() {\n    this.foo = 'bar';\n  }\n};\n\nnew obj.f() // 报错\n```\n\n\n\n## 二、属性名表达式\n\nES6 允许字面量定义对象时，将表达式放在括号内\n\n```js\nlet lastWord = 'last word';\n\nconst a = {\n  'first word': 'hello',\n  [lastWord]: 'world'\n};\n\na['first word'] // \"hello\"\na[lastWord] // \"world\"\na['last word'] // \"world\"\n```\n\n表达式还可以用于定义方法名\n\n```js\nlet obj = {\n  ['h' + 'ello']() {\n    return 'hi';\n  }\n};\n\nobj.hello() // hi\n```\n\n注意，属性名表达式与简洁表示法，不能同时使用，会报错\n\n```js\n// 报错\nconst foo = 'bar';\nconst bar = 'abc';\nconst baz = { [foo] };\n\n// 正确\nconst foo = 'bar';\nconst baz = { [foo]: 'abc'};\n```\n\n注意，属性名表达式如果是一个对象，默认情况下会自动将对象转为字符串`[object Object]`\n\n```js\nconst keyA = {a: 1};\nconst keyB = {b: 2};\n\nconst myObject = {\n  [keyA]: 'valueA',\n  [keyB]: 'valueB'\n};\n\nmyObject // Object {[object Object]: \"valueB\"}\n```\n\n\n\n## 三、super关键字\n\n`this`关键字总是指向函数所在的当前对象，ES6 又新增了另一个类似的关键字`super`，指向当前对象的原型对象\n\n```javascript\nconst proto = {\n  foo: 'hello'\n};\n\nconst obj = {\n  foo: 'world',\n  find() {\n    return super.foo;\n  }\n};\n\nObject.setPrototypeOf(obj, proto); // 为obj设置原型对象\nobj.find() // \"hello\"\n```\n\n\n\n## 四、扩展运算符的应用\n\n在解构赋值中，未被读取的可遍历的属性，分配到指定的对象上面\n\n```js\nlet { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };\nx // 1\ny // 2\nz // { a: 3, b: 4 }\n```\n\n注意：解构赋值必须是最后一个参数，否则会报错\n\n解构赋值是浅拷贝\n\n```js\nlet obj = { a: { b: 1 } };\nlet { ...x } = obj;\nobj.a.b = 2; // 修改obj里面a属性中键值\nx.a.b // 2，影响到了结构出来x的值\n```\n\n对象的扩展运算符等同于使用`Object.assign()`方法\n\n\n\n## 五、属性的遍历\n\nES6 一共有 5 种方法可以遍历对象的属性。\n\n- for...in：循环遍历对象自身的和继承的可枚举属性（不含 Symbol 属性）\n\n- Object.keys(obj)：返回一个数组，包括对象自身的（不含继承的）所有可枚举属性（不含 Symbol 属性）的键名\n\n- Object.getOwnPropertyNames(obj)：回一个数组，包含对象自身的所有属性（不含 Symbol 属性，但是包括不可枚举属性）的键名\n\n- Object.getOwnPropertySymbols(obj)：返回一个数组，包含对象自身的所有 Symbol 属性的键名\n\n- Reflect.ownKeys(obj)：返回一个数组，包含对象自身的（不含继承的）所有键名，不管键名是 Symbol 或字符串，也不管是否可枚举\n\n上述遍历，都遵守同样的属性遍历的次序规则：\n\n- 首先遍历所有数值键，按照数值升序排列\n- 其次遍历所有字符串键，按照加入时间升序排列\n- 最后遍历所有 Symbol 键，按照加入时间升序排\n\n```js\nReflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })\n// ['2', '10', 'b', 'a', Symbol()]\n```\n\n\n\n\n\n## 六、对象新增的方法\n\n关于对象新增的方法，分别有以下：\n\n- Object.is()\n- Object.assign()\n- Object.getOwnPropertyDescriptors()\n- Object.setPrototypeOf()，Object.getPrototypeOf()\n- Object.keys()，Object.values()，Object.entries()\n- Object.fromEntries()\n\n\n\n### Object.is()\n\n严格判断两个值是否相等，与严格比较运算符（===）的行为基本一致，不同之处只有两个：一是`+0`不等于`-0`，二是`NaN`等于自身\n\n```js\n+0 === -0 //true\nNaN === NaN // false\n\nObject.is(+0, -0) // false\nObject.is(NaN, NaN) // true\n```\n\n\n\n### Object.assign()\n\n`Object.assign()`方法用于对象的合并，将源对象`source`的所有可枚举属性，复制到目标对象`target`\n\n`Object.assign()`方法的第一个参数是目标对象，后面的参数都是源对象\n\n```javascript\nconst target = { a: 1, b: 1 };\n\nconst source1 = { b: 2, c: 2 };\nconst source2 = { c: 3 };\n\nObject.assign(target, source1, source2);\ntarget // {a:1, b:2, c:3}\n```\n\n注意：`Object.assign()`方法是浅拷贝，遇到同名属性会进行替换\n\n\n\n### Object.getOwnPropertyDescriptors()\n\n返回指定对象所有自身属性（非继承属性）的描述对象\n\n```js\nconst obj = {\n  foo: 123,\n  get bar() { return 'abc' }\n};\n\nObject.getOwnPropertyDescriptors(obj)\n// { foo:\n//    { value: 123,\n//      writable: true,\n//      enumerable: true,\n//      configurable: true },\n//   bar:\n//    { get: [Function: get bar],\n//      set: undefined,\n//      enumerable: true,\n//      configurable: true } }\n```\n\n\n\n### Object.setPrototypeOf()\n\n`Object.setPrototypeOf`方法用来设置一个对象的原型对象\n\n```js\nObject.setPrototypeOf(object, prototype)\n\n// 用法\nconst o = Object.setPrototypeOf({}, null);\n```\n\n\n\n### Object.getPrototypeOf()\n\n用于读取一个对象的原型对象\n\n```js\nObject.getPrototypeOf(obj);\n```\n\n\n\n### Object.keys()\n\n返回自身的（不含继承的）所有可遍历（enumerable）属性的键名的数组\n\n```js\nvar obj = { foo: 'bar', baz: 42 };\nObject.keys(obj)\n// [\"foo\", \"baz\"]\n```\n\n\n\n### Object.values()\n\n返回自身的（不含继承的）所有可遍历（enumerable）属性的键对应值的数组\n\n```js\nconst obj = { foo: 'bar', baz: 42 };\nObject.values(obj)\n// [\"bar\", 42]\n```\n\n\n\n### Object.entries()\n\n返回一个对象自身的（不含继承的）所有可遍历（enumerable）属性的键值对的数组\n\n```js\nconst obj = { foo: 'bar', baz: 42 };\nObject.entries(obj)\n// [ [\"foo\", \"bar\"], [\"baz\", 42] ]\n```\n\n\n\n### Object.fromEntries()\n\n用于将一个键值对数组转为对象\n\n```js\nObject.fromEntries([\n  ['foo', 'bar'],\n  ['baz', 42]\n])\n// { foo: \"bar\", baz: 42 }\n```\n\n\n\n## 参考文献\n\n- https://es6.ruanyifeng.com/#docs/object"
  },
  {
    "path": "docs/es6/promise.md",
    "content": "#  面试官：你是怎么理解ES6中 Promise的？使用场景？\n\n![](https://static.vue-js.com/f033b160-5811-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、介绍\n\n`Promise `，译为承诺，是异步编程的一种解决方案，比传统的解决方案（回调函数）更加合理和更加强大\n\n在以往我们如果处理多层异步操作，我们往往会像下面那样编写我们的代码\n\n```js\ndoSomething(function(result) {\n  doSomethingElse(result, function(newResult) {\n    doThirdThing(newResult, function(finalResult) {\n      console.log('得到最终结果: ' + finalResult);\n    }, failureCallback);\n  }, failureCallback);\n}, failureCallback);\n```\n\n阅读上面代码，是不是很难受，上述形成了经典的回调地狱\n\n现在通过`Promise`的改写上面的代码\n\n```js\ndoSomething().then(function(result) {\n  return doSomethingElse(result);\n})\n.then(function(newResult) {\n  return doThirdThing(newResult);\n})\n.then(function(finalResult) {\n  console.log('得到最终结果: ' + finalResult);\n})\n.catch(failureCallback);\n```\n\n瞬间感受到`promise`解决异步操作的优点：\n\n- 链式操作减低了编码难度\n- 代码可读性明显增强\n\n\n\n下面我们正式来认识`promise`：\n\n### 状态\n\n`promise`对象仅有三种状态\n\n- `pending`（进行中）\n- `fulfilled`（已成功）\n- `rejected`（已失败）\n\n### 特点\n\n- 对象的状态不受外界影响，只有异步操作的结果，可以决定当前是哪一种状态\n- 一旦状态改变（从`pending`变为`fulfilled`和从`pending`变为`rejected`），就不会再变，任何时候都可以得到这个结果\n\n\n\n### 流程\n\n认真阅读下图，我们能够轻松了解`promise`整个流程\n\n ![](https://static.vue-js.com/1b02ae90-58a9-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 二、用法\n\n`Promise`对象是一个构造函数，用来生成`Promise`实例\n\n```javascript\nconst promise = new Promise(function(resolve, reject) {});\n```\n\n`Promise`构造函数接受一个函数作为参数，该函数的两个参数分别是`resolve`和`reject`\n\n- `resolve`函数的作用是，将`Promise`对象的状态从“未完成”变为“成功”\n- `reject`函数的作用是，将`Promise`对象的状态从“未完成”变为“失败”\n\n\n\n### 实例方法\n\n`Promise`构建出来的实例存在以下方法：\n\n- then()\n- catch()\n- finally()\n\n\n\n#### then()\n\n`then`是实例状态发生改变时的回调函数，第一个参数是`resolved`状态的回调函数，第二个参数是`rejected`状态的回调函数\n\n`then`方法返回的是一个新的`Promise`实例，也就是`promise`能链式书写的原因\n\n```javascript\ngetJSON(\"/posts.json\").then(function(json) {\n  return json.post;\n}).then(function(post) {\n  // ...\n});\n```\n\n\n\n#### catch\n\n`catch()`方法是`.then(null, rejection)`或`.then(undefined, rejection)`的别名，用于指定发生错误时的回调函数\n\n```javascript\ngetJSON('/posts.json').then(function(posts) {\n  // ...\n}).catch(function(error) {\n  // 处理 getJSON 和 前一个回调函数运行时发生的错误\n  console.log('发生错误！', error);\n});\n```\n\n`Promise `对象的错误具有“冒泡”性质，会一直向后传递，直到被捕获为止\n\n```javascript\ngetJSON('/post/1.json').then(function(post) {\n  return getJSON(post.commentURL);\n}).then(function(comments) {\n  // some code\n}).catch(function(error) {\n  // 处理前面三个Promise产生的错误\n});\n```\n\n一般来说，使用`catch`方法代替`then()`第二个参数\n\n`Promise `对象抛出的错误不会传递到外层代码，即不会有任何反应\n\n```js\nconst someAsyncThing = function() {\n  return new Promise(function(resolve, reject) {\n    // 下面一行会报错，因为x没有声明\n    resolve(x + 2);\n  });\n};\n```\n\n浏览器运行到这一行，会打印出错误提示`ReferenceError: x is not defined`，但是不会退出进程\n\n`catch()`方法之中，还能再抛出错误，通过后面`catch`方法捕获到\n\n\n\n#### finally()\n\n`finally()`方法用于指定不管 Promise 对象最后状态如何，都会执行的操作\n\n```javascript\npromise\n.then(result => {···})\n.catch(error => {···})\n.finally(() => {···});\n```\n\n\n\n### 构造函数方法\n\n`Promise`构造函数存在以下方法：\n\n- all()\n- race()\n- allSettled()\n- resolve()\n- reject()\n- try()\n\n\n\n### all()\n\n`Promise.all()`方法用于将多个 `Promise `实例，包装成一个新的 `Promise `实例\n\n```javascript\nconst p = Promise.all([p1, p2, p3]);\n```\n\n接受一个数组（迭代对象）作为参数，数组成员都应为`Promise`实例\n\n实例`p`的状态由`p1`、`p2`、`p3`决定，分为两种：\n\n- 只有`p1`、`p2`、`p3`的状态都变成`fulfilled`，`p`的状态才会变成`fulfilled`，此时`p1`、`p2`、`p3`的返回值组成一个数组，传递给`p`的回调函数\n- 只要`p1`、`p2`、`p3`之中有一个被`rejected`，`p`的状态就变成`rejected`，此时第一个被`reject`的实例的返回值，会传递给`p`的回调函数\n\n注意，如果作为参数的 `Promise` 实例，自己定义了`catch`方法，那么它一旦被`rejected`，并不会触发`Promise.all()`的`catch`方法\n\n```javascript\nconst p1 = new Promise((resolve, reject) => {\n  resolve('hello');\n})\n.then(result => result)\n.catch(e => e);\n\nconst p2 = new Promise((resolve, reject) => {\n  throw new Error('报错了');\n})\n.then(result => result)\n.catch(e => e);\n\nPromise.all([p1, p2])\n.then(result => console.log(result))\n.catch(e => console.log(e));\n// [\"hello\", Error: 报错了]\n```\n\n如果`p2`没有自己的`catch`方法，就会调用`Promise.all()`的`catch`方法\n\n```javascript\nconst p1 = new Promise((resolve, reject) => {\n  resolve('hello');\n})\n.then(result => result);\n\nconst p2 = new Promise((resolve, reject) => {\n  throw new Error('报错了');\n})\n.then(result => result);\n\nPromise.all([p1, p2])\n.then(result => console.log(result))\n.catch(e => console.log(e));\n// Error: 报错了\n```\n\n\n\n### race()\n\n`Promise.race()`方法同样是将多个 Promise 实例，包装成一个新的 Promise 实例\n\n```javascript\nconst p = Promise.race([p1, p2, p3]);\n```\n\n只要`p1`、`p2`、`p3`之中有一个实例率先改变状态，`p`的状态就跟着改变\n\n率先改变的 Promise 实例的返回值则传递给`p`的回调函数\n\n```javascript\nconst p = Promise.race([\n  fetch('/resource-that-may-take-a-while'),\n  new Promise(function (resolve, reject) {\n    setTimeout(() => reject(new Error('request timeout')), 5000)\n  })\n]);\n\np\n.then(console.log)\n.catch(console.error);\n```\n\n\n\n### allSettled()\n\n`Promise.allSettled()`方法接受一组 Promise 实例作为参数，包装成一个新的 Promise 实例\n\n只有等到所有这些参数实例都返回结果，不管是`fulfilled`还是`rejected`，包装实例才会结束\n\n```javascript\nconst promises = [\n  fetch('/api-1'),\n  fetch('/api-2'),\n  fetch('/api-3'),\n];\n\nawait Promise.allSettled(promises);\nremoveLoadingIndicator();\n```\n\n\n\n#### resolve()\n\n将现有对象转为 `Promise `对象\n\n```javascript\nPromise.resolve('foo')\n// 等价于\nnew Promise(resolve => resolve('foo'))\n```\n\n参数可以分成四种情况，分别如下：\n\n- 参数是一个 Promise 实例，`promise.resolve`将不做任何修改、原封不动地返回这个实例\n- 参数是一个`thenable`对象，`promise.resolve`会将这个对象转为 `Promise `对象，然后就立即执行`thenable`对象的`then()`方法\n- 参数不是具有`then()`方法的对象，或根本就不是对象，`Promise.resolve()`会返回一个新的 Promise 对象，状态为`resolved`\n- 没有参数时，直接返回一个`resolved`状态的 Promise 对象\n\n\n\n#### reject()\n\n`Promise.reject(reason)`方法也会返回一个新的 Promise 实例，该实例的状态为`rejected`\n\n```javascript\nconst p = Promise.reject('出错了');\n// 等同于\nconst p = new Promise((resolve, reject) => reject('出错了'))\n\np.then(null, function (s) {\n  console.log(s)\n});\n// 出错了\n```\n\n`Promise.reject()`方法的参数，会原封不动地变成后续方法的参数\n\n```javascript\nPromise.reject('出错了')\n.catch(e => {\n  console.log(e === '出错了')\n})\n// true\n```\n\n\n\n## 三、使用场景\n\n将图片的加载写成一个`Promise`，一旦加载完成，`Promise`的状态就发生变化\n\n```javascript\nconst preloadImage = function (path) {\n  return new Promise(function (resolve, reject) {\n    const image = new Image();\n    image.onload  = resolve;\n    image.onerror = reject;\n    image.src = path;\n  });\n};\n```\n\n通过链式操作，将多个渲染数据分别给个`then`，让其各司其职。或当下个异步请求依赖上个请求结果的时候，我们也能够通过链式操作友好解决问题\n\n```js\n// 各司其职\ngetInfo().then(res=>{\n    let { bannerList } = res\n    //渲染轮播图\n    console.log(bannerList)\n    return res\n}).then(res=>{\n    \n    let { storeList } = res\n    //渲染店铺列表\n    console.log(storeList)\n    return res\n}).then(res=>{\n    let { categoryList } = res\n    console.log(categoryList)\n    //渲染分类列表\n    return res\n})\n```\n\n通过`all()`实现多个请求合并在一起，汇总所有请求结果，只需设置一个`loading`即可\n\n```js\nfunction initLoad(){\n    // loading.show() //加载loading\n    Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{\n        console.log(res)\n        loading.hide() //关闭loading\n    }).catch(err=>{\n        console.log(err)\n        loading.hide()//关闭loading\n    })\n}\n//数据初始化    \ninitLoad()\n```\n\n通过`race`可以设置图片请求超时\n\n```js\n//请求某个图片资源\nfunction requestImg(){\n    var p = new Promise(function(resolve, reject){\n        var img = new Image();\n        img.onload = function(){\n           resolve(img);\n        }\n        //img.src = \"https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg\"; 正确的\n        img.src = \"https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1\";\n    });\n    return p;\n}\n\n//延时函数，用于给请求计时\nfunction timeout(){\n    var p = new Promise(function(resolve, reject){\n        setTimeout(function(){\n            reject('图片请求超时');\n        }, 5000);\n    });\n    return p;\n}\n\nPromise\n.race([requestImg(), timeout()])\n.then(function(results){\n    console.log(results);\n})\n.catch(function(reason){\n    console.log(reason);\n});\n```\n\n## 参考文献\n\n- https://es6.ruanyifeng.com/#docs/promise"
  },
  {
    "path": "docs/es6/proxy.md",
    "content": "# 面试官：你是怎么理解ES6中Proxy的？使用场景?\n\n ![](https://static.vue-js.com/6f656e30-59f5-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、介绍\n\n**定义：** 用于定义基本操作的自定义行为\n\n**本质：** 修改的是程序默认形为，就形同于在编程语言层面上做修改，属于元编程`(meta programming)`\n\n元编程（Metaprogramming，又译超编程，是指某类计算机程序的编写，这类计算机程序编写或者操纵其它程序（或者自身）作为它们的数据，或者在运行时完成部分本应在编译时完成的工作\n\n一段代码来理解\n```bash\n#!/bin/bash\n# metaprogram\necho '#!/bin/bash' >program\nfor ((I=1; I<=1024; I++)) do\n    echo \"echo $I\" >>program\ndone\nchmod +x program\n```\n这段程序每执行一次能帮我们生成一个名为`program`的文件，文件内容为1024行`echo`，如果我们手动来写1024行代码，效率显然低效\n\n- 元编程优点：与手工编写全部代码相比，程序员可以获得更高的工作效率，或者给与程序更大的灵活度去处理新的情形而无需重新编译\n\n`Proxy` 亦是如此，用于创建一个对象的代理，从而实现基本操作的拦截和自定义（如属性查找、赋值、枚举、函数调用等）\n\n\n## 二、用法\n\n`Proxy`为 构造函数，用来生成 `Proxy `实例\n\n```javascript\nvar proxy = new Proxy(target, handler)\n```\n\n### 参数\n\n`target`表示所要拦截的目标对象（任何类型的对象，包括原生数组，函数，甚至另一个代理））\n\n`handler`通常以函数作为属性的对象，各属性中的函数分别定义了在执行各种操作时代理 `p` 的行为\n\n\n\n### handler解析\n\n关于`handler`拦截属性，有如下：\n\n- get(target,propKey,receiver)：拦截对象属性的读取\n- set(target,propKey,value,receiver)：拦截对象属性的设置\n- has(target,propKey)：拦截`propKey in proxy`的操作，返回一个布尔值\n- deleteProperty(target,propKey)：拦截`delete proxy[propKey]`的操作，返回一个布尔值\n- ownKeys(target)：拦截`Object.keys(proxy)`、`for...in`等循环，返回一个数组\n- getOwnPropertyDescriptor(target, propKey)：拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`，返回属性的描述对象\n- defineProperty(target, propKey, propDesc)：拦截`Object.defineProperty(proxy, propKey, propDesc）`，返回一个布尔值\n- preventExtensions(target)：拦截`Object.preventExtensions(proxy)`，返回一个布尔值\n- getPrototypeOf(target)：拦截`Object.getPrototypeOf(proxy)`，返回一个对象\n- isExtensible(target)：拦截`Object.isExtensible(proxy)`，返回一个布尔值\n- setPrototypeOf(target, proto)：拦截`Object.setPrototypeOf(proxy, proto)`，返回一个布尔值\n- apply(target, object, args)：拦截 Proxy 实例作为函数调用的操作\n- construct(target, args)：拦截 Proxy 实例作为构造函数调用的操作\n\n\n\n\n\n### Reflect\n\n若需要在`Proxy`内部调用对象的默认行为，建议使用`Reflect`，其是`ES6`中操作对象而提供的新 `API`\n\n基本特点：\n\n- 只要`Proxy`对象具有的代理方法，`Reflect`对象全部具有，以静态方法的形式存在\n- 修改某些`Object`方法的返回结果，让其变得更合理（定义不存在属性行为的时候不报错而是返回`false`）\n- 让`Object`操作都变成函数行为      \n\n\n\n下面我们介绍`proxy`几种用法：\n\n### get()\n\n`get`接受三个参数，依次为目标对象、属性名和 `proxy` 实例本身，最后一个参数可选\n\n```javascript\nvar person = {\n  name: \"张三\"\n};\n\nvar proxy = new Proxy(person, {\n  get: function(target, propKey) {\n    return Reflect.get(target,propKey)\n  }\n});\n\nproxy.name // \"张三\"\n```\n\n`get`能够对数组增删改查进行拦截，下面是试下你数组读取负数的索引\n\n```js\nfunction createArray(...elements) {\n  let handler = {\n    get(target, propKey, receiver) {\n      let index = Number(propKey);\n      if (index < 0) {\n        propKey = String(target.length + index);\n      }\n      return Reflect.get(target, propKey, receiver);\n    }\n  };\n\n  let target = [];\n  target.push(...elements);\n  return new Proxy(target, handler);\n}\n\nlet arr = createArray('a', 'b', 'c');\narr[-1] // c\n```\n\n注意：如果一个属性不可配置（configurable）且不可写（writable），则 Proxy 不能修改该属性，否则会报错\n\n```js\nconst target = Object.defineProperties({}, {\n  foo: {\n    value: 123,\n    writable: false,\n    configurable: false\n  },\n});\n\nconst handler = {\n  get(target, propKey) {\n    return 'abc';\n  }\n};\n\nconst proxy = new Proxy(target, handler);\n\nproxy.foo\n// TypeError: Invariant check failed\n```\n\n\n\n### set()\n\n`set`方法用来拦截某个属性的赋值操作，可以接受四个参数，依次为目标对象、属性名、属性值和 `Proxy` 实例本身\n\n假定`Person`对象有一个`age`属性，该属性应该是一个不大于 200 的整数，那么可以使用`Proxy`保证`age`的属性值符合要求\n\n```js\nlet validator = {\n  set: function(obj, prop, value) {\n    if (prop === 'age') {\n      if (!Number.isInteger(value)) {\n        throw new TypeError('The age is not an integer');\n      }\n      if (value > 200) {\n        throw new RangeError('The age seems invalid');\n      }\n    }\n\n    // 对于满足条件的 age 属性以及其他属性，直接保存\n    obj[prop] = value;\n  }\n};\n\nlet person = new Proxy({}, validator);\n\nperson.age = 100;\n\nperson.age // 100\nperson.age = 'young' // 报错\nperson.age = 300 // 报错\n```\n\n如果目标对象自身的某个属性，不可写且不可配置，那么`set`方法将不起作用\n\n```javascript\nconst obj = {};\nObject.defineProperty(obj, 'foo', {\n  value: 'bar',\n  writable: false,\n});\n\nconst handler = {\n  set: function(obj, prop, value, receiver) {\n    obj[prop] = 'baz';\n  }\n};\n\nconst proxy = new Proxy(obj, handler);\nproxy.foo = 'baz';\nproxy.foo // \"bar\"\n```\n\n注意，严格模式下，`set`代理如果没有返回`true`，就会报错\n\n```javascript\n'use strict';\nconst handler = {\n  set: function(obj, prop, value, receiver) {\n    obj[prop] = receiver;\n    // 无论有没有下面这一行，都会报错\n    return false;\n  }\n};\nconst proxy = new Proxy({}, handler);\nproxy.foo = 'bar';\n// TypeError: 'set' on proxy: trap returned falsish for property 'foo'\n```\n\n\n\n### deleteProperty()\n\n`deleteProperty`方法用于拦截`delete`操作，如果这个方法抛出错误或者返回`false`，当前属性就无法被`delete`命令删除\n\n```javascript\nvar handler = {\n  deleteProperty (target, key) {\n    invariant(key, 'delete');\n    Reflect.deleteProperty(target,key)\n    return true;\n  }\n};\nfunction invariant (key, action) {\n  if (key[0] === '_') {\n    throw new Error(`无法删除私有属性`);\n  }\n}\n\nvar target = { _prop: 'foo' };\nvar proxy = new Proxy(target, handler);\ndelete proxy._prop\n// Error: 无法删除私有属性\n```\n\n注意，目标对象自身的不可配置（configurable）的属性，不能被`deleteProperty`方法删除，否则报错\n\n\n\n### 取消代理\n\n```\nProxy.revocable(target, handler);\n```\n\n## 三、使用场景\n\n`Proxy`其功能非常类似于设计模式中的代理模式，常用功能如下：\n\n- 拦截和监视外部对对象的访问\n- 降低函数或类的复杂度\n- 在复杂操作前对操作进行校验或对所需资源进行管理\n\n\n\n使用 `Proxy` 保障数据类型的准确性\n\n```js\nlet numericDataStore = { count: 0, amount: 1234, total: 14 };\nnumericDataStore = new Proxy(numericDataStore, {\n    set(target, key, value, proxy) {\n        if (typeof value !== 'number') {\n            throw Error(\"属性只能是number类型\");\n        }\n        return Reflect.set(target, key, value, proxy);\n    }\n});\n\nnumericDataStore.count = \"foo\"\n// Error: 属性只能是number类型\n\nnumericDataStore.count = 333\n// 赋值成功\n```\n\n声明了一个私有的 `apiKey`，便于 `api` 这个对象内部的方法调用，但不希望从外部也能够访问 `api._apiKey`\n\n```js\nlet api = {\n    _apiKey: '123abc456def',\n    getUsers: function(){ },\n    getUser: function(userId){ },\n    setUser: function(userId, config){ }\n};\nconst RESTRICTED = ['_apiKey'];\napi = new Proxy(api, {\n    get(target, key, proxy) {\n        if(RESTRICTED.indexOf(key) > -1) {\n            throw Error(`${key} 不可访问.`);\n        } return Reflect.get(target, key, proxy);\n    },\n    set(target, key, value, proxy) {\n        if(RESTRICTED.indexOf(key) > -1) {\n            throw Error(`${key} 不可修改`);\n        } return Reflect.get(target, key, value, proxy);\n    }\n});\n\nconsole.log(api._apiKey)\napi._apiKey = '987654321'\n// 上述都抛出错误\n```\n\n还能通过使用`Proxy`实现观察者模式\n\n观察者模式（Observer mode）指的是函数自动观察数据对象，一旦对象有变化，函数就会自动执行\n\n`observable`函数返回一个原始对象的 `Proxy` 代理，拦截赋值操作，触发充当观察者的各个函数\n\n```javascript\nconst queuedObservers = new Set();\n\nconst observe = fn => queuedObservers.add(fn);\nconst observable = obj => new Proxy(obj, {set});\n\nfunction set(target, key, value, receiver) {\n  const result = Reflect.set(target, key, value, receiver);\n  queuedObservers.forEach(observer => observer());\n  return result;\n}\n```\n\n观察者函数都放进`Set`集合，当修改`obj`的值，在会`set`函数中拦截，自动执行`Set`所有的观察者\n\n\n\n## 参考文献\n\n- https://es6.ruanyifeng.com/#docs/proxy\n- https://vue3js.cn/es6"
  },
  {
    "path": "docs/es6/set_map.md",
    "content": "# 面试官：你是怎么理解ES6新增Set、Map两种数据结构的？\n\n ![](https://static.vue-js.com/2b947d00-560c-11eb-85f6-6fac77c0c9b3.png)\n\n如果要用一句来描述，我们可以说\n\n`Set`是一种叫做集合的数据结构，`Map`是一种叫做字典的数据结构\n\n什么是集合？什么又是字典？\n\n- 集合  \n是由一堆无序的、相关联的，且不重复的内存结构【数学中称为元素】组成的组合\n\n- 字典   \n是一些元素的集合。每个元素有一个称作key 的域，不同元素的key 各不相同\n\n区别？\n\n- 共同点：集合、字典都可以存储不重复的值\n- 不同点：集合是以[值，值]的形式存储元素，字典是以[键，值]的形式存储\n\n## 一、Set\n\n` Set`是`es6`新增的数据结构，类似于数组，但是成员的值都是唯一的，没有重复的值，我们一般称为集合\n\n`Set`本身是一个构造函数，用来生成 Set 数据结构\n\n```js\nconst s = new Set();\n```\n\n\n\n### 增删改查\n\n`Set`的实例关于增删改查的方法：\n\n- add()\n- delete()\n\n- has()\n- clear()\n\n### add()\n\n添加某个值，返回 `Set` 结构本身\n\n当添加实例中已经存在的元素，`set`不会进行处理添加\n\n```js\ns.add(1).add(2).add(2); // 2只被添加了一次\n```\n\n### delete()\n\n删除某个值，返回一个布尔值，表示删除是否成功\n\n```js\ns.delete(1)\n```\n\n### has()\n\n返回一个布尔值，判断该值是否为`Set`的成员\n\n```js\ns.has(2)\n```\n\n### clear()\n\n清除所有成员，没有返回值\n\n```js\ns.clear()\n```\n\n\n\n### 遍历\n\n`Set`实例遍历的方法有如下：\n\n关于遍历的方法，有如下：\n\n- keys()：返回键名的遍历器\n- values()：返回键值的遍历器\n- entries()：返回键值对的遍历器\n- forEach()：使用回调函数遍历每个成员\n\n`Set`的遍历顺序就是插入顺序\n\n`keys`方法、`values`方法、`entries`方法返回的都是遍历器对象\n\n```javascript\nlet set = new Set(['red', 'green', 'blue']);\n\nfor (let item of set.keys()) {\n  console.log(item);\n}\n// red\n// green\n// blue\n\nfor (let item of set.values()) {\n  console.log(item);\n}\n// red\n// green\n// blue\n\nfor (let item of set.entries()) {\n  console.log(item);\n}\n// [\"red\", \"red\"]\n// [\"green\", \"green\"]\n// [\"blue\", \"blue\"]\n```\n\n`forEach()`用于对每个成员执行某种操作，没有返回值，键值、键名都相等，同样的`forEach`方法有第二个参数，用于绑定处理函数的`this`\n\n```javascript\nlet set = new Set([1, 4, 9]);\nset.forEach((value, key) => console.log(key + ' : ' + value))\n// 1 : 1\n// 4 : 4\n// 9 : 9\n```\n\n扩展运算符和` Set` 结构相结合实现数组或字符串去重\n\n```javascript\n// 数组\nlet arr = [3, 5, 2, 2, 5, 5];\nlet unique = [...new Set(arr)]; // [3, 5, 2]\n\n// 字符串\nlet str = \"352255\";\nlet unique = [...new Set(str)].join(\"\"); // \"352\"\n```\n\n实现并集、交集、和差集\n\n```javascript\nlet a = new Set([1, 2, 3]);\nlet b = new Set([4, 3, 2]);\n\n// 并集\nlet union = new Set([...a, ...b]);\n// Set {1, 2, 3, 4}\n\n// 交集\nlet intersect = new Set([...a].filter(x => b.has(x)));\n// set {2, 3}\n\n// （a 相对于 b 的）差集\nlet difference = new Set([...a].filter(x => !b.has(x)));\n// Set {1}\n```\n\n\n\n## 二、Map\n\n`Map`类型是键值对的有序列表，而键和值都可以是任意类型\n\n`Map`本身是一个构造函数，用来生成 `Map` 数据结构\n\n```js\nconst m = new Map()\n```\n\n\n\n### 增删改查\n\n`Map` 结构的实例针对增删改查有以下属性和操作方法：\n\n- size 属性\n- set()\n- get()\n- has()\n- delete()\n- clear()\n\n### size\n\n`size`属性返回 Map 结构的成员总数。\n\n```javascript\nconst map = new Map();\nmap.set('foo', true);\nmap.set('bar', false);\n\nmap.size // 2\n```\n\n\n\n### set()\n\n设置键名`key`对应的键值为`value`，然后返回整个 Map 结构\n\n如果`key`已经有值，则键值会被更新，否则就新生成该键\n\n同时返回的是当前`Map`对象，可采用链式写法\n\n```javascript\nconst m = new Map();\n\nm.set('edition', 6)        // 键是字符串\nm.set(262, 'standard')     // 键是数值\nm.set(undefined, 'nah')    // 键是 undefined\nm.set(1, 'a').set(2, 'b').set(3, 'c') // 链式操作\n```\n\n\n\n### get()\n\n`get`方法读取`key`对应的键值，如果找不到`key`，返回`undefined`\n\n```javascript\nconst m = new Map();\n\nconst hello = function() {console.log('hello');};\nm.set(hello, 'Hello ES6!') // 键是函数\n\nm.get(hello)  // Hello ES6!\n```\n\n\n\n### has()\n\n`has`方法返回一个布尔值，表示某个键是否在当前 Map 对象之中\n\n```javascript\nconst m = new Map();\n\nm.set('edition', 6);\nm.set(262, 'standard');\nm.set(undefined, 'nah');\n\nm.has('edition')     // true\nm.has('years')       // false\nm.has(262)           // true\nm.has(undefined)     // true\n```\n\n\n\n### delete()\n\n`delete`方法删除某个键，返回`true`。如果删除失败，返回`false`\n\n```javascript\nconst m = new Map();\nm.set(undefined, 'nah');\nm.has(undefined)     // true\n\nm.delete(undefined)\nm.has(undefined)       // false\n```\n\n### clear()\n\n`clear`方法清除所有成员，没有返回值\n\n```javascript\nlet map = new Map();\nmap.set('foo', true);\nmap.set('bar', false);\n\nmap.size // 2\nmap.clear()\nmap.size // 0\n```\n\n\n\n### 遍历\n\n`Map `结构原生提供三个遍历器生成函数和一个遍历方法：\n\n- keys()：返回键名的遍历器\n- values()：返回键值的遍历器\n- entries()：返回所有成员的遍历器\n- forEach()：遍历 Map 的所有成员\n\n遍历顺序就是插入顺序\n\n```javascript\nconst map = new Map([\n  ['F', 'no'],\n  ['T',  'yes'],\n]);\n\nfor (let key of map.keys()) {\n  console.log(key);\n}\n// \"F\"\n// \"T\"\n\nfor (let value of map.values()) {\n  console.log(value);\n}\n// \"no\"\n// \"yes\"\n\nfor (let item of map.entries()) {\n  console.log(item[0], item[1]);\n}\n// \"F\" \"no\"\n// \"T\" \"yes\"\n\n// 或者\nfor (let [key, value] of map.entries()) {\n  console.log(key, value);\n}\n// \"F\" \"no\"\n// \"T\" \"yes\"\n\n// 等同于使用map.entries()\nfor (let [key, value] of map) {\n  console.log(key, value);\n}\n// \"F\" \"no\"\n// \"T\" \"yes\"\n\nmap.forEach(function(value, key, map) {\n  console.log(\"Key: %s, Value: %s\", key, value);\n});\n```\n\n## 三、WeakSet 和 WeakMap\n\n### WeakSet\n\n创建`WeakSet`实例\n\n```js\nconst ws = new WeakSet();\n```\n\n`WeakSet `可以接受一个具有 `Iterable `接口的对象作为参数\n\n```js\nconst a = [[1, 2], [3, 4]];\nconst ws = new WeakSet(a);\n// WeakSet {[1, 2], [3, 4]}\n```\n\n在`API`中`WeakSet`与`Set`有两个区别：\n\n- 没有遍历操作的`API`\n- 没有`size`属性\n\n`WeakSet`只能成员只能是引用类型，而不能是其他类型的值\n\n```js\nlet ws=new WeakSet();\n\n// 成员不是引用类型\nlet weakSet=new WeakSet([2,3]);\nconsole.log(weakSet) // 报错\n\n// 成员为引用类型\nlet obj1={name:1}\nlet obj2={name:1}\nlet ws=new WeakSet([obj1,obj2]); \nconsole.log(ws) //WeakSet {{…}, {…}}\n```\n\n`WeakSet `里面的引用只要在外部消失，它在 `WeakSet `里面的引用就会自动消失\n\n\n\n### WeakMap\n\n`WeakMap`结构与`Map`结构类似，也是用于生成键值对的集合\n\n在`API`中`WeakMap`与`Map`有两个区别：\n\n- 没有遍历操作的`API`\n- 没有`clear`清空方法\n\n```javascript\n// WeakMap 可以使用 set 方法添加成员\nconst wm1 = new WeakMap();\nconst key = {foo: 1};\nwm1.set(key, 2);\nwm1.get(key) // 2\n\n// WeakMap 也可以接受一个数组，\n// 作为构造函数的参数\nconst k1 = [1, 2, 3];\nconst k2 = [4, 5, 6];\nconst wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);\nwm2.get(k2) // \"bar\"\n```\n\n`WeakMap`只接受对象作为键名（`null`除外），不接受其他类型的值作为键名\n\n```javascript\nconst map = new WeakMap();\nmap.set(1, 2)\n// TypeError: 1 is not an object!\nmap.set(Symbol(), 2)\n// TypeError: Invalid value used as weak map key\nmap.set(null, 2)\n// TypeError: Invalid value used as weak map key\n```\n\n`WeakMap`的键名所指向的对象，一旦不再需要，里面的键名对象和所对应的键值对会自动消失，不用手动删除引用\n\n举个场景例子：\n\n在网页的 DOM 元素上添加数据，就可以使用`WeakMap`结构，当该 DOM 元素被清除，其所对应的`WeakMap`记录就会自动被移除\n\n```javascript\nconst wm = new WeakMap();\n\nconst element = document.getElementById('example');\n\nwm.set(element, 'some information');\nwm.get(element) // \"some information\"\n```\n\n注意：`WeakMap` 弱引用的只是键名，而不是键值。键值依然是正常引用\n\n下面代码中，键值`obj`会在`WeakMap`产生新的引用，当你修改`obj`不会影响到内部\n\n```js\nconst wm = new WeakMap();\nlet key = {};\nlet obj = {foo: 1};\n\nwm.set(key, obj);\nobj = null;\nwm.get(key)\n// Object {foo: 1}\n```\n\n\n\n## 参考文献\n\n- https://es6.ruanyifeng.com/#docs/set-map\n"
  },
  {
    "path": "docs/es6/var_let_const.md",
    "content": "# 面试官：说说var、let、const之间的区别\n\n![](https://static.vue-js.com/d2aba2e0-50f7-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、var\n\n在ES5中，顶层对象的属性和全局变量是等价的，用`var`声明的变量既是全局变量，也是顶层变量\n\n注意：顶层对象，在浏览器环境指的是`window`对象，在 `Node` 指的是`global`对象\n\n```js\nvar a = 10;\nconsole.log(window.a) // 10\n```\n\n使用`var`声明的变量存在变量提升的情况\n\n```js\nconsole.log(a) // undefined\nvar a = 20\n```\n\n在编译阶段，编译器会将其变成以下执行\n\n```js\nvar a\nconsole.log(a)\na = 20\n```\n\n使用`var`，我们能够对一个变量进行多次声明，后面声明的变量会覆盖前面的变量声明\n\n```js\nvar a = 20 \nvar a = 30\nconsole.log(a) // 30\n```\n\n在函数中使用使用`var`声明变量时候，该变量是局部的\n\n```js\nvar a = 20\nfunction change(){\n    var a = 30\n}\nchange()\nconsole.log(a) // 20 \n```\n\n而如果在函数内不使用`var`，该变量是全局的\n\n```js\nvar a = 20\nfunction change(){\n   a = 30\n}\nchange()\nconsole.log(a) // 30 \n```\n\n## 二、let\n\n`let`是`ES6`新增的命令，用来声明变量\n\n用法类似于`var`，但是所声明的变量，只在`let`命令所在的代码块内有效\n\n```js\n{\n    let a = 20\n}\nconsole.log(a) // ReferenceError: a is not defined.\n```\n\n不存在变量提升\n\n```js\nconsole.log(a) // 报错ReferenceError\nlet a = 2\n```\n\n这表示在声明它之前，变量`a`是不存在的，这时如果用到它，就会抛出一个错误\n\n只要块级作用域内存在`let`命令，这个区域就不再受外部影响\n\n```js\nvar a = 123\nif (true) {\n    a = 'abc' // ReferenceError\n    let a;\n}\n```\n\n使用`let`声明变量前，该变量都不可用，也就是大家常说的“暂时性死区”\n\n最后，`let`不允许在相同作用域中重复声明\n\n```js\nlet a = 20\nlet a = 30\n// Uncaught SyntaxError: Identifier 'a' has already been declared\n```\n\n注意的是相同作用域，下面这种情况是不会报错的\n\n```js\nlet a = 20\n{\n    let a = 30\n}\n```\n\n因此，我们不能在函数内部重新声明参数\n\n```js\nfunction func(arg) {\n  let arg;\n}\nfunc()\n// Uncaught SyntaxError: Identifier 'arg' has already been declared\n```\n\n## 三、const\n\n`const`声明一个只读的常量，一旦声明，常量的值就不能改变\n\n```js\nconst a = 1\na = 3\n// TypeError: Assignment to constant variable.\n```\n\n这意味着，`const`一旦声明变量，就必须立即初始化，不能留到以后赋值\n\n```js\nconst a;\n// SyntaxError: Missing initializer in const declaration\n```\n\n如果之前用`var`或`let`声明过变量，再用`const`声明同样会报错\n\n```js\nvar a = 20\nlet b = 20\nconst a = 30\nconst b = 30\n// 都会报错\n```\n\n`const`实际上保证的并不是变量的值不得改动，而是变量指向的那个内存地址所保存的数据不得改动\n\n对于简单类型的数据，值就保存在变量指向的那个内存地址，因此等同于常量\n\n对于复杂类型的数据，变量指向的内存地址，保存的只是一个指向实际数据的指针，`const`只能保证这个指针是固定的，并不能确保改变量的结构不变\n\n```js\nconst foo = {};\n\n// 为 foo 添加一个属性，可以成功\nfoo.prop = 123;\nfoo.prop // 123\n\n// 将 foo 指向另一个对象，就会报错\nfoo = {}; // TypeError: \"foo\" is read-only\n```\n\n其它情况，`const`与`let`一致\n\n## 四、区别\n\n`var`、`let`、`const`三者区别可以围绕下面五点展开：\n\n- 变量提升\n- 暂时性死区\n- 块级作用域\n- 重复声明\n- 修改声明的变量\n- 使用\n\n\n\n### 变量提升\n\n`var `声明的变量存在变量提升，即变量可以在声明之前调用，值为`undefined`\n\n`let`和`const`不存在变量提升，即它们所声明的变量一定要在声明后使用，否则报错\n\n```js\n// var\nconsole.log(a)  // undefined\nvar a = 10\n\n// let \nconsole.log(b)  // Cannot access 'b' before initialization\nlet b = 10\n\n// const\nconsole.log(c)  // Cannot access 'c' before initialization\nconst c = 10\n```\n\n\n\n### 暂时性死区\n\n`var`不存在暂时性死区\n\n`let`和`const`存在暂时性死区，只有等到声明变量的那一行代码出现，才可以获取和使用该变量\n\n```js\n// var\nconsole.log(a)  // undefined\nvar a = 10\n\n// let\nconsole.log(b)  // Cannot access 'b' before initialization\nlet b = 10\n\n// const\nconsole.log(c)  // Cannot access 'c' before initialization\nconst c = 10\n```\n\n\n\n### 块级作用域\n\n`var`不存在块级作用域\n\n`let`和`const`存在块级作用域\n\n```js\n// var\n{\n    var a = 20\n}\nconsole.log(a)  // 20\n\n// let\n{\n    let b = 20\n}\nconsole.log(b)  // Uncaught ReferenceError: b is not defined\n\n// const\n{\n    const c = 20\n}\nconsole.log(c)  // Uncaught ReferenceError: c is not defined\n```\n\n\n\n### 重复声明\n\n`var`允许重复声明变量\n\n`let`和`const`在同一作用域不允许重复声明变量\n\n```js\n// var\nvar a = 10\nvar a = 20 // 20\n\n// let\nlet b = 10\nlet b = 20 // Identifier 'b' has already been declared\n\n// const\nconst c = 10\nconst c = 20 // Identifier 'c' has already been declared\n```\n\n\n\n### 修改声明的变量\n\n`var`和`let`可以\n\n`const`声明一个只读的常量。一旦声明，常量的值就不能改变\n\n```js\n// var\nvar a = 10\na = 20\nconsole.log(a)  // 20\n\n//let\nlet b = 10\nb = 20\nconsole.log(b)  // 20\n\n// const\nconst c = 10\nc = 20\nconsole.log(c) // Uncaught TypeError: Assignment to constant variable\n```\n\n\n\n### 使用\n能用`const`的情况尽量使用`const`，其他情况下大多数使用`let`，避免使用`var`\n\n\n\n## 参考文献\n\n- https://es6.ruanyifeng.com/"
  },
  {
    "path": "docs/git/Git.md",
    "content": "# 面试官：说说你对Git的理解？\n\n\n\n ![](https://static.vue-js.com/213eba50-f79c-11eb-bc6f-3f06e1491664.png)\n\n## 一、是什么\n\ngit，是一个分布式版本控制软件，最初目的是为更好地管理`Linux`内核开发而设计\n\n分布式版本控制系统的客户端并不只提取最新版本的文件快照，而是把代码仓库完整地镜像下来。这么一来，任何一处协同工作用的服务器发生故障，事后都可以用任何一个镜像出来的本地仓库恢复\n\n ![](https://static.vue-js.com/29240f40-f79c-11eb-991d-334fd31f0201.png)\n\n项目开始，只有一个原始版仓库，别的机器可以`clone`这个原始版本库，那么所有`clone`的机器，它们的版本库其实都是一样的，并没有主次之分\n\n所以在实现团队协作的时候，只要有一台电脑充当服务器的角色，其他每个人都从这个“服务器”仓库`clone`一份到自己的电脑上，并且各自把各自的提交推送到服务器仓库里，也从服务器仓库中拉取别人的提交\n\n`github`实际就可以充当这个服务器角色，其是一个开源协作社区，提供`Git`仓库托管服务，既可以让别人参与你的开源项目，也可以参与别人的开源项目\n\n\n\n## 二、工作原理\n\n当我们通过`git init`创建或者`git clone`一个项目的时候，项目目录会隐藏一个`.git`子目录，其作用是用来跟踪管理版本库的\n\n`Git` 中所有数据在存储前都计算校验和，然后以校验和来引用，所以在我们修改或者删除文件的时候，`git`能够知道\n\n`Git `用以计算校验和的机制叫做 SHA-1 散列（hash，哈希）， 这是一个由 40 个十六进制字符（0-9 和 a-f）组成字符串，基于 Git 中文件的内容或目录结构计算出来，如下：\n\n```text\n24b9da6552252987aa493b52f8696cd6d3b00373\n```\n\n当我们修改文件的时候，`git`就会修改文件的状态，可以通过`git status`进行查询，状态情况如下：\n\n- 已修改（modified）：表示修改了文件，但还没保存到数据库中。\n- 已暂存（staged）：表示对一个已修改文件的当前版本做了标记，使之包含在下次提交的快照中。\n- 已提交（committed）：表示数据已经安全的保存在本地数据库中。\n\n文件状态对应的，不同状态的文件在` Git `中处于不同的工作区域，主要分成了四部分：\n\n- 工作区：相当于本地写代码的区域，如 git clone 一个项目到本地，相当于本地克隆了远程仓库项目的一个副本\n- 暂存区：暂存区是一个文件，保存了下次将提交的文件列表信息，一般在 Git 仓库目录中\n- 本地仓库：提交更新，找到暂存区域的文件，将快照永久性存储到 Git 本地仓库\n- 远程仓库：远程的仓库，如 github\n\n ![](https://static.vue-js.com/3273c9a0-f79c-11eb-bc6f-3f06e1491664.png)\n\n\n\n## 三、命令\n\n从上图可以看到，`git`日常简单的使用就只有上图6个命令：\n\n- add\n- commit \n- push\n- pull\n- clone\n- checkout\n\n但实际上还有很多命令，如果想要熟练使用，还有60个多命令，通过这些命令的配合使用，能够提高个人工作效率和团队协助能力\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/Git\n- https://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html"
  },
  {
    "path": "docs/git/HEAD_tree_index.md",
    "content": "# 面试官：说说Git 中 HEAD、工作树和索引之间的区别？\n\n ![](https://static.vue-js.com/2de056a0-fa40-11eb-991d-334fd31f0201.png)\n\n## 一、HEAD\n\n在`git`中，可以存在很多分支，其本质上是一个指向`commit`对象的可变指针，而`Head`是一个特别的指针，是一个指向你正在工作中的本地分支的指针\n\n简单来讲，就是你现在在哪儿，HEAD 就指向哪儿\n\n例如当前我们处于`master`分支，所以`HEAD`这个指针指向了`master`分支指针\n\n ![](https://static.vue-js.com/36cb0da0-fa40-11eb-991d-334fd31f0201.png)\n\n然后通过调用`git checkout test`切换到`test`分支，那么`HEAD`则指向`test`分支，如下图：\n\n ![](https://static.vue-js.com/3e86ba80-fa40-11eb-991d-334fd31f0201.png)\n\n但我们在`test`分支再一次`commit`信息的时候，`HEAD`指针仍然指向了`test`分支指针，而`test`分支指针已经指向了最新创建的提交，如下图：\n\n ![](https://static.vue-js.com/439839b0-fa66-11eb-991d-334fd31f0201.png)\n\n这个`HEAD`存储的位置就在`.git/HEAD`目录中，查看信息可以看到`HEAD`指向了另一个文件\n\n```cmd\n$ cat .git/HEAD\nref: refs/heads/master\n\n$ cat .git/refs/heads/master\n7406a10efcc169bbab17827aeda189aa20376f7f\n```\n\n这个文件的内容是一串哈希码，而这个哈希码正是`master`分支上最新的提交所对应的哈希码\n\n所以，当我们切换分支的时候，`HEAD`指针通常指向我们所在的分支，当我们在某个分支上创建新的提交时，分支指针总是会指向当前分支的最新提交\n\n所以，HEAD指针 ——–> 分支指针 ——–> 最新提交\n\n\n\n## 二、工作树和索引\n\n在`Git`管理下，大家实际操作的目录被称为工作树，也就是工作区域\n\n在数据库和工作树之间有索引，索引是为了向数据库提交作准备的区域，也被称为暂存区域\n\n ![](https://static.vue-js.com/46e5ac40-fa40-11eb-bc6f-3f06e1491664.png)\n\n`Git`在执行提交的时候，不是直接将工作树的状态保存到数据库，而是将设置在中间索引区域的状态保存到数据库\n\n因此，要提交文件，首先需要把文件加入到索引区域中。\n\n所以，凭借中间的索引，可以避免工作树中不必要的文件提交，还可以将文件修改内容的一部分加入索引区域并提交\n\n\n\n## 三、区别\n\n从所在的位置来看：\n\n- HEAD 指针通常指向我们所在的分支，当我们在某个分支上创建新的提交时，分支指针总是会指向当前分支的最新提交\n\n- 工作树是查看和编辑的（源）文件的实际内容\n\n- 索引是放置你想要提交给 git仓库文件的地方，如工作树的代码通过 git add 则添加到 git 索引中，通过git commit 则将索引区域的文件提交到 git 仓库中\n\n\n\n\n## 参考文献\n\n- https://backlog.com/git-tutorial/cn/intro/intro1_4.html\n- https://juejin.cn/post/6844903598522908686\n- https://www.zsythink.net/archives/3412"
  },
  {
    "path": "docs/git/Version control.md",
    "content": "# 面试官：说说你对版本管理的理解？常用的版本管理工具有哪些？\n\n\n\n ![](https://static.vue-js.com/f0e8a2d0-f5ac-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n版本控制（Version control），是维护工程蓝图的标准作法，能追踪工程蓝图从诞生一直到定案的过程。此外，版本控制也是一种软件工程技巧，借此能在软件开发的过程中，确保由不同人所编辑的同一程序文件都得到同步\n\n透过文档控制，能记录任何工程项目内各个模块的改动历程，并为每次改动编上序号\n\n一种简单的版本控制形式如下：赋给图的初版一个版本等级“A”。当做了第一次改变后，版本等级改为“B”，以此类推\n\n版本控制能提供项目的设计者，将设计恢复到之前任一状态的选择权\n\n简言之，你的修改只要提到到版本控制系统，基本都可以找回，版本控制系统就像一台时光机器，可以让你回到任何一个时间点\n\n\n\n\n\n## 二、有哪些\n\n版本控制系统在当今的软件开发中，被认为是理所当然的配备工具之一，根据类别可以分成：\n\n- 本地版本控制系统\n- 集中式版本控制系统\n- 分布式版本控制系统\n\n\n\n\n\n\n\n### 本地版本控制系统\n\n结构如下图所示：\n\n ![](https://static.vue-js.com/c545ded0-f5ad-11eb-ab90-d9ae814b240d.png)\n\n优点：\n\n- 简单，很多系统中都有内置\n- 适合管理文本，如系统配置\n\n缺点：\n\n- 其不支持远程操作，因此并不适合多人版本开发\n\n\n\n### 集中式版本控制系统\n\n结构如下图所示：\n\n ![](https://static.vue-js.com/8b4b3040-f5ad-11eb-85f6-6fac77c0c9b3.png)\n\n优点：\n\n- 适合多人团队协作开发\n- 代码集中化管理\n\n缺点：\n\n- 单点故障\n- 必须联网，无法单机工作\n\n\n\n\n\n代表工具有`SVN`、`CVS`：\n\n### SVN\n\n`TortoiseSVN`是一款非常易于使用的跨平台的 版本控制/版本控制/源代码控制软件\n\n\n\n### CVS\n\n`CVS`是版本控制系统，是源配置管理（SCM）的重要组成部分。使用它，您可以记录源文件和文档的历史记录\n\n老牌的版本控制系统，它是基于客户端/服务器的行为使得其可容纳多用户，构成网络也很方便\n\n这一特性使得`CVS`成为位于不同地点的人同时处理数据文件（特别是程序的源代码）时的首选\n\n\n\n\n\n#### 分布式版本控制系统\n\n结构如下图：\n\n ![](https://static.vue-js.com/4301a260-f5ad-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n优点：\n\n- 适合多人团队协作开发\n- 代码集中化管理\n- 可以离线工作\n- 每个计算机都是一个完整仓库\n\n分布式版本管理系统每个计算机都有一个完整的仓库，可本地提交，可以做到离线工作，则不用像集中管理那样因为断网情况而无法工作\n\n\n\n代表工具为`Git`、`HG`：\n\n### Git\n\n`Git`是目前世界上最先进的分布式版本控制系统，旨在快速高效地处理从小型到大型项目的所有事务\n\n特性：易于学习，占用内存小，具有闪电般快速的性能\n\n使用`Git`和`Gitlab`搭建版本控制环境是现在互联网公司最流行的版本控制方式\n\n\n\n### HG\n\n`Mercurial`是一个免费的分布式源代码管理工具。它可以有效地处理任何规模的项目，并提供简单直观的界面\n\n`Mercurial `是一种轻量级分布式版本控制系统，采用 `Python `语言实现，易于学习和使用，扩展性强\n\n\n\n\n\n## 三、总结\n\n版本控制系统的优点如下：\n\n- 记录文件所有历史变化，这是版本控制系统的基本能力\n- 随时恢复到任意时间点，历史记录功能使我们不怕改错代码了\n- 支持多功能并行开发，通常版本控制系统都支持分支，保证了并行开发的可行\n- 多人协作并行开发，对于多人协作项目，支持多人协作开发的版本管理将事半功倍\n\n\n\n## 参考文献\n\n- https://pm.readthedocs.io/vcs/understanding.html\n- https://zh.wikipedia.org/wiki/%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6"
  },
  {
    "path": "docs/git/command.md",
    "content": "# 面试官：说说Git常用的命令有哪些？\n\n ![](https://static.vue-js.com/f66b3290-f7af-11eb-bc6f-3f06e1491664.png)\n\n\n\n## 一、前言\n\n`git `的操作可以通过命令的形式如执行，日常使用就如下图6个命令即可\n\n ![](https://static.vue-js.com/fe150520-f7af-11eb-991d-334fd31f0201.png)\n\n实际上，如果想要熟练使用，超过60多个命令需要了解，下面则介绍下常见的的`git `命令\n\n\n\n## 二、有哪些\n\n\n\n\n\n## 配置\n\n`Git `自带一个 `git config` 的工具来帮助设置控制 `Git `外观和行为的配置变量，在我们安装完`git`之后，第一件事就是设置你的用户名和邮件地址\n\n后续每一个提交都会使用这些信息，它们会写入到你的每一次提交中，不可更改\n\n设置提交代码时的用户信息命令如下：\n\n- git config [--global] user.name \"[name]\" \n- git config [--global] user.email \"[email address]\"\n\n\n\n\n\n### 启动\n\n一个`git`项目的初始有两个途径，分别是：\n\n- git init [project-name]：创建或在当前目录初始化一个git代码库\n- git clone url：下载一个项目和它的整个代码历史\n\n\n\n### 日常基本操作\n\n在日常工作中，代码常用的基本操作如下：\n\n- git init 初始化仓库，默认为 master 分支\n- git add . 提交全部文件修改到缓存区\n- git add <具体某个文件路径+全名> 提交某些文件到缓存区\n- git diff  查看当前代码 add后，会 add 哪些内容\n- git diff --staged查看现在 commit 提交后，会提交哪些内容\n- git status 查看当前分支状态\n- git pull <远程仓库名> <远程分支名> 拉取远程仓库的分支与本地当前分支合并\n- git pull <远程仓库名> <远程分支名>:<本地分支名> 拉取远程仓库的分支与本地某个分支合并\n- git commit -m \"<注释>\" 提交代码到本地仓库，并写提交注释\n- git commit -v 提交时显示所有diff信息\n- git commit --amend [file1] [file2] 重做上一次commit，并包括指定文件的新变化\n\n关于提交信息的格式，可以遵循以下的规则：\n\n- feat: 新特性，添加功能\n- fix: 修改 bug\n- refactor: 代码重构\n- docs: 文档修改\n- style: 代码格式修改, 注意不是 css 修改\n- test: 测试用例修改\n- chore: 其他修改, 比如构建流程, 依赖管理\n\n\n\n### 分支操作\n\n- git branch 查看本地所有分支\n- git branch -r 查看远程所有分支\n- git branch -a 查看本地和远程所有分支\n- git merge <分支名> 合并分支\n- git merge --abort 合并分支出现冲突时，取消合并，一切回到合并前的状态\n- git branch <新分支名> 基于当前分支，新建一个分支\n- git checkout --orphan <新分支名> 新建一个空分支（会保留之前分支的所有文件）\n- git branch -D <分支名> 删除本地某个分支\n- git push <远程库名> :<分支名> 删除远程某个分支\n- git branch <新分支名称> <提交ID> 从提交历史恢复某个删掉的某个分支\n- git branch -m <原分支名> <新分支名> 分支更名\n- git checkout <分支名> 切换到本地某个分支\n- git checkout <远程库名>/<分支名> 切换到线上某个分支\n- git checkout -b <新分支名> 把基于当前分支新建分支，并切换为这个分支\n\n\n\n\n\n### 远程同步\n\n远程操作常见的命令：\n\n- git fetch [remote] 下载远程仓库的所有变动\n- git remote -v 显示所有远程仓库\n- git pull [remote] [branch] 拉取远程仓库的分支与本地当前分支合并\n- git fetch 获取线上最新版信息记录，不合并\n- git push [remote] [branch] 上传本地指定分支到远程仓库\n- git push [remote] --force 强行推送当前分支到远程仓库，即使有冲突\n- git push [remote] --all 推送所有分支到远程仓库\n\n\n\n### 撤销\n\n- git checkout [file] 恢复暂存区的指定文件到工作区\n- git checkout [commit] [file]  恢复某个commit的指定文件到暂存区和工作区\n- git checkout . 恢复暂存区的所有文件到工作区\n- git reset [commit] 重置当前分支的指针为指定commit，同时重置暂存区，但工作区不变\n- git reset --hard 重置暂存区与工作区，与上一次commit保持一致\n- git reset [file] 重置暂存区的指定文件，与上一次commit保持一致，但工作区不变\n\n- git revert [commit]  后者的所有变化都将被前者抵消，并且应用到当前分支\n\n> `reset`：真实硬性回滚，目标版本后面的提交记录全部丢失了\n>\n> `revert`：同样回滚，这个回滚操作相当于一个提价，目标版本后面的提交记录也全部都有\n\n\n\n### 存储操作\n\n你正在进行项目中某一部分的工作，里面的东西处于一个比较杂乱的状态，而你想转到其他分支上进行一些工作，但又不想提交这些杂乱的代码，这时候可以将代码进行存储\n\n- git stash 暂时将未提交的变化移除\n- git stash pop 取出储藏中最后存入的工作状态进行恢复，会删除储藏\n\n- git stash list 查看所有储藏中的工作\n- git stash apply <储藏的名称>  取出储藏中对应的工作状态进行恢复，不会删除储藏\n- git stash clear 清空所有储藏中的工作\n- git stash drop <储藏的名称>  删除对应的某个储藏\n\n\n\n## 三、总结\n\n`git`常用命令速查表如下所示：\n\n ![](https://static.vue-js.com/0a10f3c0-f7b0-11eb-991d-334fd31f0201.png)\n\n\n\n## 参考文献\n\n- https://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html\n\n- https://segmentfault.com/a/1190000017875714\n"
  },
  {
    "path": "docs/git/conflict.md",
    "content": "# 面试官：说说 git 发生冲突的场景？如何解决？\n\n ![](https://static.vue-js.com/8aeccc40-fdb3-11eb-bc6f-3f06e1491664.png)\n\n## 一、是什么\n\n一般情况下，出现分支的场景有如下：\n\n- 多个分支代码合并到一个分支时\n- 多个分支向同一个远端分支推送\n\n具体情况就是，多个分支修改了同一个文件（任何地方）或者多个分支修改了同一个文件的名称\n\n如果两个分支中分别修改了不同文件中的部分，是不会产生冲突，直接合并即可\n\n应用在命令中，就是`push`、`pull`、`stash`、`rebase`等命令下都有可能产生冲突情况，从本质上来讲，都是`merge`和`patch`（应用补丁）时产生冲突\n\n\n\n## 二、分析\n\n在本地主分值`master`创建一个`a.txt`文件，文件起始位置写上`master commit`，如下：\n\n ![](https://static.vue-js.com/959ade20-fdb3-11eb-991d-334fd31f0201.png)\n\n然后提交到仓库：\n\n- git add a.txt\n- git commit -m 'master first commit'\n\n创建一个新的分支`featurel1`分支，并进行切换，如下：\n\n```cmd\ngit checkout -b featurel1\n```\n\n然后修改`a.txt`文件首行文字为 `featurel commit`，然后添加到暂存区，并开始进行提交到仓库：\n\n- git add a.txt\n- git commit -m 'featurel  first change'\n\n然后通过`git checkout master`切换到主分支，通过`git merge`进行合并，发现不会冲突\n\n此时`a.txt`文件的内容变成`featurel commit`，没有出现冲突情况，这是因为`git`在内部发生了快速合并\n\n> 如果当前分支的每一个提交(commit)都已经存在另一个分支里了，git 就会执行一个“快速向前”(fast forward)操作\n>\n> git 不创建任何新的提交(commit)，只是将当前分支指向合并进来的分支\n\n如果此时切换到`featurel`分支，将文件的内容修改成`featrue second commit`，然后提交到本地仓库\n\n然后切换到主分支，如果此时在`a.txt`文件再次修改，修改成`mastet second commit`，然后再次提交到本地仓库\n\n此时，`master`分支和`feature1`分支各自都分别有新的提交，变成了下图所示：\n\n ![](https://static.vue-js.com/a05488c0-fdb3-11eb-991d-334fd31f0201.png)\n\n这种情况下，无法执行快速合并，只能试图把各自的修改合并起来，但这种合并就可能会有冲突\n\n现在通过`git merge featurel`进行分支合并，如下所示：\n\n ![](https://static.vue-js.com/b0991d90-fdb3-11eb-bc6f-3f06e1491664.png)\n\n从冲突信息可以看到，`a.txt`发生冲突，必须手动解决冲突之后再提交\n\n而`git status`同样可以告知我们冲突的文件：\n\n ![](https://static.vue-js.com/c5823430-fdb3-11eb-991d-334fd31f0201.png)\n\n打开`a.txt`文件，可以看到如下内容：\n\n ![](https://static.vue-js.com/ce7a0a90-fdb3-11eb-bc6f-3f06e1491664.png)\n\n`git`用`<<<<<<<`，`=======`，`>>>>>>>`标记出不同分支的内容：\n\n- <<<<<<< 和 ======= 之间的区域就是当前更改的内容\n- ======= 和 >>>>>>> 之间的区域就是传入进来更改的内容\n\n现在要做的事情就是将冲突的内容进行更改，对每个文件使用 `git add` 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件，`Git `就会将它们标记为冲突已解决然后再提交：\n\n- git add a.txt\n- git commit -m \"conflict fixed\"\n\n此时`master`分支和`feature1`分支变成了下图所示：\n\n ![](https://static.vue-js.com/d7421e60-fdb3-11eb-bc6f-3f06e1491664.png)\n\n使用`git log`命令可以看到合并的信息：\n\n ![](https://static.vue-js.com/e0dfd1b0-fdb3-11eb-991d-334fd31f0201.png)\n\n\n\n\n\n## 三、总结\n\n当`Git`无法自动合并分支时，就必须首先解决冲突，解决冲突后，再提交，合并完成\n\n解决冲突就是把`Git`合并失败的文件手动编辑为我们希望的内容，再提交\n\n\n\n## 参考文献\n\n- https://www.liaoxuefeng.com/wiki/896043488029600/900004111093344"
  },
  {
    "path": "docs/git/fork_clone_branch.md",
    "content": "# 面试官：说说Git中 fork, clone,branch这三个概念，有什么区别?\n\n ![](https://static.vue-js.com/9c4eb9a0-f7ad-11eb-bc6f-3f06e1491664.png)\n\n\n\n## 一、是什么\n\n### fork\n\n`fork`，英语翻译过来就是叉子，动词形式则是分叉，如下图，从左到右，一条直线变成多条直线\n\n ![](https://static.vue-js.com/ad04ade0-f7ad-11eb-991d-334fd31f0201.png)\n\n转到`git`仓库中，`fork`则可以代表分叉、克隆 出一个（仓库的）新拷贝\n\n ![](https://static.vue-js.com/b4b31450-f7ad-11eb-991d-334fd31f0201.png)\n\n包含了原来的仓库（即upstream repository，上游仓库）所有内容，如分支、Tag、提交\n\n如果想将你的修改合并到原项目中时，可以通过的 Pull Request 把你的提交贡献回 原仓库\n\n### clone\n\n`clone`，译为克隆，它的作用是将文件从远程代码仓下载到本地，从而形成一个本地代码仓\n\n执行`clone`命令后，会在当前目录下创建一个名为`xxx`的目录，并在这个目录下初始化一个 `.git` 文件夹，然后从中读取最新版本的文件的拷贝\n\n默认配置下远程 `Git` 仓库中的每一个文件的每一个版本都将被拉取下来\n\n### branch\n\n`branch`，译为分支，其作用简单而言就是开启另一个分支， 使用分支意味着你可以把你的工作从开发主线上分离开来，以免影响开发主线\n\n` Git` 处理分支的方式十分轻量，创建新分支这一操作几乎能在瞬间完成，并且在不同分支之间的切换操作也是一样便捷\n\n在我们开发中，默认只有一条`master`分支，如下图所示：\n\n ![](https://static.vue-js.com/7fa8e9c0-f923-11eb-991d-334fd31f0201.png)\n\n通过`git branch `可以创建一个分支，但并不会自动切换到新分支中去\n\n ![](https://static.vue-js.com/89efd560-f923-11eb-bc6f-3f06e1491664.png)\n\n通过`git checkout`可以切换到另一个`testing`分支\n\n ![](https://static.vue-js.com/91d1cef0-f923-11eb-bc6f-3f06e1491664.png)\n\n\n## 二、如何使用\n\n### fork\n\n当你在`github`发现感兴趣开源项目的时候，可以通过点击`github`仓库中右上角`fork`标识的按钮，如下图：\n\n ![](https://static.vue-js.com/bc4c4510-f7ad-11eb-991d-334fd31f0201.png)\n\n点击这个操作后会将这个仓库的文件、提交历史、issues和其余东西的仓库复制到自己的`github`仓库中，而你本地仓库是不会存在任何更改\n\n然后你就可以通过`git clone`对你这个复制的远程仓库进行克隆\n\n后续更改任何东西都可以在本地完成，如`git add`、`git commit`一系列的操作，然后通过`push`命令推到自己的远程仓库\n\n如果希望对方接受你的修改，可以通过发送`pull requests`给对方，如果对方接受。则会将你的修改内容更新到仓库中\n\n ![](https://static.vue-js.com/c5265a40-f7ad-11eb-991d-334fd31f0201.png)\n\n整体流程如下图：\n\n ![](https://static.vue-js.com/ced8ce10-f7ad-11eb-bc6f-3f06e1491664.png)\n\n\n### clone\n\n在`github`中，开源项目右侧存在`code`按钮，点击后则会显示开源项目`url`信息，如下图所示：\n\n ![](https://static.vue-js.com/d8685090-f7ad-11eb-bc6f-3f06e1491664.png)\n\n通过`git clone xxx`则能完成远程项目的下载\n\n\n### branch\n\n可通过`git branch`进行查看当前的分支状态，\n\n如果给了`--list`，或者没有非选项参数，现有的分支将被列出；当前的分支将以绿色突出显示，并标有星号\n\n以及通过`git branch`创建一个新的分支出来\n\n\n## 三、区别\n\n其三者区别如下：\n\n- fork 只能对代码仓进行操作，且 fork 不属于 git 的命令，通常用于代码仓托管平台的一种“操作”\n- clone 是 git 的一种命令，它的作用是将文件从远程代码仓下载到本地，从而形成一个本地代码仓\n- branch 特征与 fork 很类似，fork 得到的是一个新的、自己的代码仓，而 branch 得到的是一个代码仓的一个新分支\n\n## 参考文献\n\n- https://git-scm.com/book/zh/v2/Git-基础-获取-Git-仓库\n- https://git-scm.com/book/zh/v2/Git-分支-分支简介\n"
  },
  {
    "path": "docs/git/git pull _git fetch.md",
    "content": "# 说说对git pull 和 git fetch 的理解？有什么区别？\n\n ![](https://static.vue-js.com/cc90c050-fac2-11eb-991d-334fd31f0201.png)\n\n\n\n## 一、是什么\n先回顾两个命令的定义\n- git fetch 命令用于从另一个存储库下载对象和引用\n- git pull 命令用于从另一个存储库或本地分支获取并集成(整合)\n\n再来看一次`git`的工作流程图，如下所示：\n\n ![](https://static.vue-js.com/d523ba60-fac2-11eb-991d-334fd31f0201.png)\n\n可以看到，`git fetch`是将远程主机的最新内容拉到本地，用户在检查了以后决定是否合并到工作本机分支中\n\n而`git pull` 则是将远程主机的最新内容拉下来后直接合并，即：`git pull = git fetch + git merge`，这样可能会产生冲突，需要手动解决\n\n在我们本地的`git`文件中对应也存储了`git`本地仓库分支的`commit ID `和 跟踪的远程分支的`commit ID`，对应文件如下：\n\n-  .git/refs/head/[本地分支]\n-  .git/refs/remotes/[正在跟踪的分支]\n\n使用 `git fetch`更新代码，本地的库中`master`的`commitID`不变\n\n但是与`git`上面关联的那个`orign/master`的`commit ID`发生改变\n\n这时候我们本地相当于存储了两个代码的版本号，我们还要通过`merge`去合并这两个不同的代码版本\n\n ![](https://static.vue-js.com/fd23ff70-fb12-11eb-bc6f-3f06e1491664.png)\n\n也就是`fetch`的时候本地的`master`没有变化，但是与远程仓关联的那个版本号被更新了，接下来就是在本地`merge`合并这两个版本号的代码\n\n相比之下，使用`git pull`就更加简单粗暴，会将本地的代码更新至远程仓库里面最新的代码版本，如下图：\n\n ![](https://static.vue-js.com/091b8140-fb13-11eb-bc6f-3f06e1491664.png)\n\n\n\n\n\n## 二、用法\n\n一般远端仓库里有新的内容更新，当我们需要把新内容下载的时候，就使用到`git pull`或者`git fetch`命令\n\n### fetch\n\n用法如下：\n\n```cmd\ngit fetch <远程主机名> <远程分支名>:<本地分支名>\n```\n\n例如从远程的`origin`仓库的`master`分支下载代码到本地并新建一个`temp`分支\n\n```cmd\ngit fetch origin master:temp\n```\n\n如果上述没有冒号，则表示将远程`origin`仓库的`master`分支拉取下来到本地当前分支\n\n这里`git fetch`不会进行合并，执行后需要手动执行`git merge`合并，如下：\n\n```cmd\ngit merge temp\n```\n\n\n\n### pull\n\n两者的用法十分相似，`pull`用法如下：\n\n```cmd\ngit pull <远程主机名> <远程分支名>:<本地分支名>\n```\n\n例如将远程主机`origin`的`master`分支拉取过来，与本地的`branchtest`分支合并，命令如下：\n\n```cmd\ngit pull origin master:branchtest\n```\n\n同样如果上述没有冒号，则表示将远程`origin`仓库的`master`分支拉取下来与本地当前分支合并\n\n\n\n## 三、区别\n\n相同点：\n\n- 在作用上他们的功能是大致相同的，都是起到了更新代码的作用\n\n不同点：\n\n- git pull是相当于从远程仓库获取最新版本，然后再与本地分支merge，即git pull = git fetch + git merge\n- 相比起来，git fetch 更安全也更符合实际要求，在 merge 前，我们可以查看更新情况，根据实际情况再决定是否合并\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/123370920\n- https://segmentfault.com/a/1190000017030384\n- https://juejin.cn/post/6844903921794859021\n"
  },
  {
    "path": "docs/git/git rebase_ git merge.md",
    "content": "# 面试官：说说你对git rebase 和 git merge的理解？区别？\n\n\n\n ![](https://static.vue-js.com/77590970-fdd4-11eb-bc6f-3f06e1491664.png)\n\n## 一、是什么\n\n在使用 `git` 进行版本管理的项目中，当完成一个特性的开发并将其合并到 `master` 分支时，会有两种方式：\n\n- git merge\n- git rebase\n\n`git rebase` 与 `git merge`都有相同的作用，都是将一个分支的提交合并到另一分支上，但是在原理上却不相同\n\n\n\n用法上两者也十分的简单：\n\n### git merge\n\n将当前分支合并到指定分支，命令用法如下：\n\n```cmd\ngit merge xxx\n```\n\n\n\n### git rebase\n\n将当前分支移植到指定分支或指定`commit`之上，用法如下：\n\n```cmd\ngit rebase -i <commit>\n```\n\n常见的参数有`--continue`，用于解决冲突之后，继续执行`rebase`\n\n```cmd\ngit rebase --continue\n```\n\n\n\n\n\n## 二、分析\n\n### git merge\n\n通过`git merge`将当前分支与`xxx`分支合并，产生的新的`commit`对象有两个父节点\n\n如果“指定分支”本身是当前分支的一个直接子节点，则会产生快照合并\n\n举个例子，`bugfix`分支是从`master`分支分叉出来的，如下所示：\n\n ![](https://static.vue-js.com/88410a30-fdd4-11eb-991d-334fd31f0201.png)\n\n合并` bugfix`分支到`master`分支时，如果`master`分支的状态没有被更改过，即 `bugfix`分支的历史记录包含`master`分支所有的历史记录\n\n所以通过把`master`分支的位置移动到`bugfix`的最新分支上，就完成合并\n\n如果`master`分支的历史记录在创建`bugfix`分支后又有新的提交，如下情况：\n\n ![](https://static.vue-js.com/929eb220-fdd4-11eb-991d-334fd31f0201.png)\n\n这时候使用`git merge`的时候，会生成一个新的提交，并且`master`分支的`HEAD`会移动到新的分支上，如下：\n\n ![](https://static.vue-js.com/9fdfa3e0-fdd4-11eb-991d-334fd31f0201.png)\n\n\n\n从上面可以看到，会把两个分支的最新快照以及二者最近的共同祖先进行三方合并，合并的结果是生成一个新的快照\n\n\n\n### git rebase\n\n同样，`master`分支的历史记录在创建`bugfix`分支后又有新的提交，如下情况：\n\n ![](https://static.vue-js.com/ab2d5120-fdd4-11eb-bc6f-3f06e1491664.png)\n\n通过`git rebase`，会变成如下情况：\n\n ![](https://static.vue-js.com/b72aed70-fdd4-11eb-991d-334fd31f0201.png)\n\n在移交过程中，如果发生冲突，需要修改各自的冲突，如下：\n\n ![](https://static.vue-js.com/c9ba0e80-fdd4-11eb-bc6f-3f06e1491664.png)\n\n`rebase`之后，`master`的`HEAD`位置不变。因此，要合并`master`分支和`bugfix`分支\n\n ![](https://static.vue-js.com/dc660660-fdd4-11eb-991d-334fd31f0201.png)\n\n从上面可以看到，`rebase`会找到不同的分支的最近共同祖先，如上图的`B`\n\n然后对比当前分支相对于该祖先的历次提交，提取相应的修改并存为临时文件（老的提交`X`和`Y`也没有被销毁，只是简单地不能再被访问或者使用）\n\n然后将当前分支指向目标最新位置`D`, 然后将之前另存为临时文件的修改依序应用\n\n\n\n\n\n## 三、区别\n\n从上面可以看到，`merge`和`rebasea`都是合并历史记录，但是各自特性不同：\n\n### merge\n\n通过`merge`合并分支会新增一个`merge commit`，然后将两个分支的历史联系起来\n\n其实是一种非破坏性的操作，对现有分支不会以任何方式被更改，但是会导致历史记录相对复杂\n\n\n\n### rebase\n\n`rebase `会将整个分支移动到另一个分支上，有效地整合了所有分支上的提交\n\n主要的好处是历史记录更加清晰，是在原有提交的基础上将差异内容反映进去，消除了 ` git merge `所需的不必要的合并提交\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/361182707\n- https://yuweijun.github.io/git-zh/1-git-branching.html#_rebasing\n- https://backlog.com/git-tutorial/cn/stepup/stepup1_4.html\n"
  },
  {
    "path": "docs/git/git reset_ git revert.md",
    "content": "# 面试官：说说你对git reset 和 git revert 的理解？区别？\n\n![](https://static.vue-js.com/046b4440-ff74-11eb-bc6f-3f06e1491664.png)\n\n\n## 一、是什么\n\n### git reset\n\n`reset`用于回退版本，可以遗弃不再使用的提交\n\n执行遗弃时，需要根据影响的范围而指定不同的参数，可以指定是否复原索引或工作树内容\n\n ![](https://static.vue-js.com/ab4d0c00-ff72-11eb-bc6f-3f06e1491664.png)\n\n\n\n### git revert\n\n在当前提交后面，新增一次提交，抵消掉上一次提交导致的所有变化，不会改变过去的历史，主要是用于安全地取消过去发布的提交\n\n ![](https://static.vue-js.com/bd12c290-ff72-11eb-991d-334fd31f0201.png)\n\n\n## 二、如何用\n\n### git reset\n\n当没有指定`ID`的时候，默认使用`HEAD`，如果指定`ID`，那么就是基于指向`ID`去变动暂存区或工作区的内容\n\n```cmd\n// 没有指定ID, 暂存区的内容会被当前ID版本号的内容覆盖，工作区不变\ngit reset\n\n// 指定ID，暂存区的内容会被指定ID版本号的内容覆盖，工作区不变\ngit reset <ID> \n```\n\n日志`ID`可以通过查询，可以`git log`进行查询，如下：\n\n```cmd\ncommit a7700083ace1204ccdff9f71631fb34c9913f7c5 (HEAD -> master)\nAuthor: linguanghui <linguanghui@baidu.com>\nDate:   Tue Aug 17 22:34:40 2021 +0800\n\n    second commit\n\ncommit e31118663ce66717edd8a179688a7f3dde5a9393\nAuthor: linguanghui <linguanghui@baidu.com>\nDate:   Tue Aug 17 22:20:01 2021 +0800\n\n    first commit\n```\n\n常见命令如下：\n\n- --mixed（默认）：默认的时候，只有暂存区变化\n\n- --hard参数：如果使用 --hard 参数，那么工作区也会变化\n\n- --soft：如果使用 --soft 参数，那么暂存区和工作区都不会变化\n\n ![](https://static.vue-js.com/225b41e0-ff73-11eb-bc6f-3f06e1491664.png)\n\n\n\n### git revert\n\n跟`git reset`用法基本一致，`git revert` 撤销某次操作，此次操作之前和之后的 `commit`和`history`都会保留，并且把这次撤销，作为一次最新的提交，如下：\n\n```cmd\ngit revert <commit_id> \n```\n\n如果撤销前一个版本，可以通过如下命令：\n\n```cmd\ngit revert HEAD\n```\n\n撤销前前一次，如下：\n\n```cmd\ngit revert HEAD^\n```\n\n## 三、区别\n\n撤销（revert）被设计为撤销公开的提交（比如已经push）的安全方式，`git reset`被设计为重设本地更改\n\n因为两个命令的目的不同，它们的实现也不一样：重设完全地移除了一堆更改，而撤销保留了原来的更改，用一个新的提交来实现撤销\n\n两者主要区别如下：\n\n- git revert是用一次新的commit来回滚之前的commit，git reset是直接删除指定的commit\n- git reset 是把HEAD向后移动了一下，而git revert是HEAD继续前进，只是新的commit的内容和要revert的内容正好相反，能够抵消要被revert的内容\n- 在回滚这一操作上看，效果差不多。但是在日后继续 merge 以前的老版本时有区别\n\n> git revert是用一次逆向的commit“中和”之前的提交，因此日后合并老的branch时，之前提交合并的代码仍然存在，导致不能够重新合并\n>\n> 但是git reset是之间把某些commit在某个branch上删除，因而和老的branch再次merge时，这些被回滚的commit应该还会被引入\n\n- 如果回退分支的代码以后还需要的情况则使用`git revert`， 如果分支是提错了没用的并且不想让别人发现这些错误代码，则使用`git reset`\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903542931587086\n- https://marklodato.github.io/visual-git-guide/index-zh-cn.html#reset"
  },
  {
    "path": "docs/git/git stash.md",
    "content": "# 面试官：说说你对git stash 的理解？应用场景？\n\n ![](https://static.vue-js.com/83ddf210-fd6f-11eb-bc6f-3f06e1491664.png)\n\n\n\n## 一、是什么\n\nstash，译为存放，在 git 中，可以理解为保存当前工作进度，会把暂存区和工作区的改动进行保存，这些修改会保存在一个栈上\n\n后续你可以在任何时候任何分支重新将某次的修改推出来，重新应用这些更改的代码\n\n默认情况下，`git stash`会缓存下列状态的文件：\n\n- 添加到暂存区的修改（staged changes）\n- Git跟踪的但并未添加到暂存区的修改（unstaged changes）\n\n但以下状态的文件不会缓存：\n\n- 在工作目录中新的文件（untracked files）\n- 被忽略的文件（ignored files）\n\n如果想要上述的文件都被缓存，可以使用`-u`或者`--include-untracked`可以工作目录新的文件，使用`-a`或者`--all`命令可以当前目录下的所有修改\n\n\n\n## 二、如何使用\n\n关于`git stash`常见的命令如下：\n\n- git stash\n- git stash save\n\n- git stash list\n- git stash pop\n- git stash apply\n- git stash show\n\n- git stash drop\n- git stash clear\n\n\n\n### git stash\n\n保存当前工作进度，会把暂存区和工作区的改动保存起来\n\n\n\n### git stash save\n\n`git stash save`可以用于存储修改.并且将`git`的工作状态切回到`HEAD`也就是上一次合法提交上\n\n如果给定具体的文件路径,`git stash`只会处理路径下的文件.其他的文件不会被存储，其存在一些参数：\n\n- --keep-index 或者 -k 只会存储为加入 git 管理的文件\n\n- --include-untracked 为追踪的文件也会被缓存,当前的工作空间会被恢复为完全清空的状态\n- -a 或者 --all 命令可以当前目录下的所有修改，包括被 git 忽略的文件\n\n\n\n### git stash list\n\n显示保存进度的列表。也就意味着，`git stash`命令可以多次执行，当多次使用`git stash`命令后，栈里会充满未提交的代码，如下：\n\n ![](https://static.vue-js.com/50216dd0-fccf-11eb-bc6f-3f06e1491664.png)\n\n其中，`stash@{0}`、`stash@{1}`就是当前`stash`的名称\n\n\n\n### git stash pop\n\n`git stash pop` 从栈中读取最近一次保存的内容，也就是栈顶的`stash`会恢复到工作区\n\n也可以通过 `git stash pop` + `stash`名字执行恢复哪个`stash`恢复到当前目录\n\n如果从`stash`中恢复的内容和当前目录中的内容发生了冲突，则需要手动修复冲突或者创建新的分支来解决冲突\n\n\n\n\n\n### git stash apply\n\n将堆栈中的内容应用到当前目录，不同于`git stash pop`，该命令不会将内容从堆栈中删除\n\n也就说该命令能够将堆栈的内容多次应用到工作目录中，适应于多个分支的情况\n\n同样，可以通过`git stash apply` + `stash`名字执行恢复哪个`stash`恢复到当前目录\n\n\n\n\n\n\n\n### git stash show\n\n查看堆栈中最新保存的`stash`和当前目录的差异\n\n通过使用`git stash show -p`查看详细的不同\n\n通过使用`git stash show stash@{1}`查看指定的`stash`和当前目录差异\n\n ![](https://static.vue-js.com/458620a0-fccf-11eb-bc6f-3f06e1491664.png)\n\n\n\n### git stash drop\n\n`git stash drop` + `stash`名称表示从堆栈中移除某个指定的stash\n\n\n\n### git stash clear\n\n删除所有存储的进度\n\n\n\n## 三、应用场景\n\n当你在项目的一部分上已经工作一段时间后，所有东西都进入了混乱的状态， 而这时你想要切换到另一个分支或者拉下远端的代码去做一点别的事情\n\n但是你创建一次未完成的代码的`commit`提交，这时候就可以使用`git stash`\n\n例如以下场景：\n\n当你的开发进行到一半,但是代码还不想进行提交 ,然后需要同步去关联远端代码时.如果你本地的代码和远端代码没有冲突时,可以直接通过`git pull`解决\n\n但是如果可能发生冲突怎么办.直接`git pull`会拒绝覆盖当前的修改，这时候就可以依次使用下述的命令：\n\n- git stash\n- git pull\n- git stash pop\n\n或者当你开发到一半，现在要修改别的分支问题的时候，你也可以使用`git stash`缓存当前区域的代码\n\n- git stash：保存开发到一半的代码\n- git commit -m '修改问题'\n- git stash pop：将代码追加到最新的提交之后"
  },
  {
    "path": "docs/http/1.0_1.1_2.0.md",
    "content": "# 面试官：说说 HTTP1.0/1.1/2.0 的区别?\n\n ![](https://static.vue-js.com/e167a580-b93a-11eb-ab90-d9ae814b240d.png)\n\n## 一、HTTP1.0\n`HTTP`协议的第二个版本，第一个在通讯中指定版本号的HTTP协议版本\n\n`HTTP 1.0` 浏览器与服务器只保持短暂的连接，每次请求都需要与服务器建立一个`TCP`连接\n\n服务器完成请求处理后立即断开`TCP`连接，服务器不跟踪每个客户也不记录过去的请求\n\n简单来讲，每次与服务器交互，都需要新开一个连接\n\n ![](https://static.vue-js.com/efff4da0-b93a-11eb-85f6-6fac77c0c9b3.png)\n\n例如，解析`html`文件，当发现文件中存在资源文件的时候，这时候又创建单独的链接\n\n最终导致，一个`html`文件的访问包含了多次的请求和响应，每次请求都需要创建连接、关系连接\n\n这种形式明显造成了性能上的缺陷\n\n如果需要建立长连接，需要设置一个非标准的Connection字段 `Connection: keep-alive`\n\n\n## 二、HTTP1.1\n\n在`HTTP1.1`中，默认支持长连接（`Connection: keep-alive`），即在一个TCP连接上可以传送多个`HTTP`请求和响应，减少了建立和关闭连接的消耗和延迟\n\n建立一次连接，多次请求均由这个连接完成\n\n ![](https://static.vue-js.com/22db2b90-b93b-11eb-ab90-d9ae814b240d.png)\n\n这样，在加载`html`文件的时候，文件中多个请求和响应就可以在一个连接中传输\n\n同时，`HTTP 1.1`还允许客户端不用等待上一次请求结果返回，就可以发出下一次请求，但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果，以保证客户端能够区分出每次请求的响应内容，这样也显著地减少了整个下载过程所需要的时间\n\n同时，`HTTP1.1`在`HTTP1.0`的基础上，增加更多的请求头和响应头来完善的功能，如下：\n\n- 引入了更多的缓存控制策略，如If-Unmodified-Since, If-Match, If-None-Match等缓存头来控制缓存策略\n- 引入range，允许值请求资源某个部分\n- 引入host，实现了在一台WEB服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点\n\n并且还添加了其他的请求方法：`put`、`delete`、`options`...\n\n\n\n\n\n## 三、HTTP2.0\n\n而`HTTP2.0`在相比之前版本，性能上有很大的提升，如添加了一个特性：\n\n- 多路复用\n- 二进制分帧\n- 首部压缩\n- 服务器推送\n\n\n\n### 多路复用\n\n`HTTP/2` 复用`TCP`连接，在一个连接里，客户端和浏览器都可以**同时**发送多个请求或回应，而且不用按照顺序一一对应，这样就避免了”队头堵塞”\n\n ![](https://static.vue-js.com/313f1980-b93b-11eb-85f6-6fac77c0c9b3.png)\n\n上图中，可以看到第四步中`css`、`js`资源是同时发送到服务端\n\n\n\n### 二进制分帧\n\n帧是`HTTP2`通信中最小单位信息\n\n`HTTP/2` 采用二进制格式传输数据，而非 `HTTP 1.x `的文本格式，解析起来更高效\n\n将请求和响应数据分割为更小的帧，并且它们采用二进制编码\n\n`HTTP2 `中，同域名下所有通信都在单个连接上完成，该连接可以承载任意数量的双向数据流\n\n每个数据流都以消息的形式发送，而消息又由一个或多个帧组成。多个帧之间可以乱序发送，根据帧首部的流标识可以重新组装，这也是多路复用同时发送数据的实现条件\n\n\n\n### 首部压缩\n\n`HTTP/2`在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对，对于相同的数据，不再通过每次请求和响应发送\n\n首部表在`HTTP/2`的连接存续期内始终存在，由客户端和服务器共同渐进地更新\n\n例如：下图中的两个请求， 请求一发送了所有的头部字段，第二个请求则只需要发送差异数据，这样可以减少冗余数据，降低开销\n\n ![](https://static.vue-js.com/3c536740-b93b-11eb-ab90-d9ae814b240d.png)\n\n### 服务器推送\n\n`HTTP2`引入服务器推送，允许服务端推送资源给客户端\n\n服务器会顺便把一些客户端需要的资源一起推送到客户端，如在响应一个页面请求中，就可以随同页面的其它资源\n\n免得客户端再次创建连接发送请求到服务器端获取\n\n这种方式非常合适加载静态资源\n\n ![](https://static.vue-js.com/47130550-b93b-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 四、总结\n\nHTTP1.0：\n\n- 浏览器与服务器只保持短暂的连接，浏览器的每次请求都需要与服务器建立一个TCP连接\n\nHTTP1.1：\n\n- 引入了持久连接，即TCP连接默认不关闭，可以被多个请求复用\n- 在同一个TCP连接里面，客户端可以同时发送多个请求\n- 虽然允许复用TCP连接，但是同一个TCP连接里面，所有的数据通信是按次序进行的，服务器只有处理完一个请求，才会接着处理下一个请求。如果前面的处理特别慢，后面就会有许多请求排队等着\n- 新增了一些请求方法\n- 新增了一些请求头和响应头\n\nHTTP2.0：\n\n- 采用二进制格式而非文本格式\n- 完全多路复用，而非有序并阻塞的、只需一个连接即可实现并行\n- 使用报头压缩，降低开销\n- 服务器推送\n\n\n\n\n\n## 参考文献\n- https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE#HTTP/1.0\n- https://www.jianshu.com/p/52d86558ca57\n- https://segmentfault.com/a/1190000016496448\n- https://zhuanlan.zhihu.com/p/26559480"
  },
  {
    "path": "docs/http/CDN.md",
    "content": "# 面试官：如何理解CDN？说说实现原理？\n\n![](https://static.vue-js.com/437ae0f0-b86b-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\nCDN (全称 Content Delivery Network)，即内容分发网络\n\n构建在现有网络基础之上的智能虚拟网络，依靠部署在各地的边缘服务器，通过中心平台的负载均衡、内容分发、调度等功能模块，使用户就近获取所需内容，降低网络拥塞，提高用户访问响应速度和命中率。`CDN` 的关键技术主要有内容存储和分发技术\n\n简单来讲，`CDN`就是根据用户位置分配最近的资源\n\n于是，用户在上网的时候不用直接访问源站，而是访问离他“最近的”一个 CDN 节点，术语叫**边缘节点**，其实就是缓存了源站内容的代理服务器。如下图：\n\n ![](https://static.vue-js.com/4f0289f0-b86b-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 二、原理分析\n\n在没有应用`CDN`时，我们使用域名访问某一个站点时的路径为\n> 用户提交域名→浏览器对域名进行解释→`DNS` 解析得到目的主机的IP地址→根据IP地址访问发出请求→得到请求数据并回复\n\n应用`CDN`后，`DNS` 返回的不再是 `IP` 地址，而是一个`CNAME`(Canonical Name ) 别名记录，指向`CDN`的全局负载均衡\n\n`CNAME`实际上在域名解析的过程中承担了中间人（或者说代理）的角色，这是`CDN`实现的关键\n\n#### 负载均衡系统\n\n由于没有返回`IP`地址，于是本地`DNS`会向负载均衡系统再发送请求  ，则进入到`CDN`的全局负载均衡系统进行智能调度：\n\n- 看用户的 IP 地址，查表得知地理位置，找相对最近的边缘节点\n- 看用户所在的运营商网络，找相同网络的边缘节点\n\n- 检查边缘节点的负载情况，找负载较轻的节点\n- 其他，比如节点的“健康状况”、服务能力、带宽、响应时间等\n\n结合上面的因素，得到最合适的边缘节点，然后把这个节点返回给用户，用户就能够就近访问`CDN`的缓存代理\n\n整体流程如下图：\n\n ![](https://static.vue-js.com/588d7890-b86b-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n#### 缓存代理\n\n缓存系统是 `CDN `的另一个关键组成部分，缓存系统会有选择地缓存那些最常用的那些资源\n\n其中有两个衡量`CDN`服务质量的指标：\n\n- 命中率：用户访问的资源恰好在缓存系统里，可以直接返回给用户，命中次数与所有访问次数之比\n- 回源率：缓存里没有，必须用代理的方式回源站取，回源次数与所有访问次数之比\n\n缓存系统也可以划分出层次，分成一级缓存节点和二级缓存节点。一级缓存配置高一些，直连源站，二级缓存配置低一些，直连用户\n\n回源的时候二级缓存只找一级缓存，一级缓存没有才回源站，可以有效地减少真正的回源\n\n现在的商业 `CDN`命中率都在 90% 以上，相当于把源站的服务能力放大了 10 倍以上\n\n\n\n## 三、总结\n`CDN` 目的是为了改善互联网的服务质量，通俗一点说其实就是提高访问速度\n\n`CDN` 构建了全国、全球级别的专网，让用户就近访问专网里的边缘节点，降低了传输延迟，实现了网站加速\n\n通过`CDN`的负载均衡系统，智能调度边缘节点提供服务，相当于`CDN`服务的大脑，而缓存系统相当于`CDN`的心脏，缓存命中直接返回给用户，否则回源\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/內容傳遞網路\n- https://juejin.cn/post/6844903890706661389#heading-5\n- https://blog.csdn.net/lxx309707872/article/details/109078783"
  },
  {
    "path": "docs/http/DNS.md",
    "content": "# 面试官：DNS协议 是什么？说说DNS 完整的查询过程?\n\n ![](https://static.vue-js.com/88081710-b78f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\nDNS（Domain Names System），域名系统，是互联网一项服务，是进行域名和与之相对应的 IP 地址进行转换的服务器\n\n简单来讲，`DNS`相当于一个翻译官，负责将域名翻译成`ip`地址\n\n- IP 地址：一长串能够唯一地标记网络上的计算机的数字\n- 域名：是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称，用于在数据传输时对计算机的定位标识\n\n ![](https://static.vue-js.com/965a03a0-b78f-11eb-ab90-d9ae814b240d.png)\n\n\n\n\n\n## 二、域名\n\n域名是一个具有层次的结构，从上到下一次为根域名、顶级域名、二级域名、三级域名...\n\n ![](https://static.vue-js.com/9f112780-b78f-11eb-85f6-6fac77c0c9b3.png)\n\n例如`www.xxx.com`，`www`为三级域名、`xxx`为二级域名、`com`为顶级域名，系统为用户做了兼容，域名末尾的根域名`.`一般不需要输入\n\n在域名的每一层都会有一个域名服务器，如下图：\n\n ![](https://static.vue-js.com/f40e0090-b7a4-11eb-85f6-6fac77c0c9b3.png)\n\n除此之外，还有电脑默认的本地域名服务器\n\n\n\n## 三、查询方式\n\nDNS 查询的方式有两种：\n\n- 递归查询：如果 A 请求 B，那么 B 作为请求的接收者一定要给 A 想要的答案\n\n ![](https://static.vue-js.com/a73be9e0-b78f-11eb-85f6-6fac77c0c9b3.png)\n\n- 迭代查询：如果接收者 B 没有请求者 A 所需要的准确内容，接收者 B 将告诉请求者 A，如何去获得这个内容，但是自己并不去发出请求\n\n ![](https://static.vue-js.com/b023e1c0-b78f-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 四、域名缓存\n\n在域名服务器解析的时候，使用缓存保存域名和`IP`地址的映射\n\n计算机中`DNS`的记录也分成了两种缓存方式：\n\n- 浏览器缓存：浏览器在获取网站域名的实际 IP 地址后会对其进行缓存，减少网络请求的损耗\n- 操作系统缓存：操作系统的缓存其实是用户自己配置的 `hosts` 文件\n\n\n\n## 五、查询过程\n\n解析域名的过程如下：\n\n- 首先搜索浏览器的 DNS 缓存，缓存中维护一张域名与 IP 地址的对应表\n- 若没有命中，则继续搜索操作系统的 DNS 缓存\n- 若仍然没有命中，则操作系统将域名发送至本地域名服务器，本地域名服务器采用递归查询自己的 DNS 缓存，查找成功则返回结果\n- 若本地域名服务器的 DNS 缓存没有命中，则本地域名服务器向上级域名服务器进行迭代查询\n  - 首先本地域名服务器向根域名服务器发起请求，根域名服务器返回顶级域名服务器的地址给本地服务器\n  - 本地域名服务器拿到这个顶级域名服务器的地址后，就向其发起请求，获取权限域名服务器的地址\n  - 本地域名服务器根据权限域名服务器的地址向其发起请求，最终得到该域名对应的 IP 地址\n\n- 本地域名服务器将得到的 IP 地址返回给操作系统，同时自己将 IP 地址缓存起来\n\n- 操作系统将 IP 地址返回给浏览器，同时自己也将 IP 地址缓存起\n\n- 至此，浏览器就得到了域名对应的 IP 地址，并将 IP 地址缓存起\n\n流程如下图所示：\n\n ![](https://static.vue-js.com/bec3c740-b78f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 参考文献\n- https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D%E7%B3%BB%E7%BB%9F\n- https://www.cnblogs.com/jmilkfan-fanguiju/p/12789677.html\n- https://segmentfault.com/a/1190000039039275\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/http/GET_POST.md",
    "content": "# 面试官：说一下 GET 和 POST 的区别？\n\n ![](https://static.vue-js.com/6e8d19e0-bc3d-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、是什么\n\n`GET`和`POST`，两者是`HTTP`协议中发送请求的方法\n\n#### GET\n\n`GET`方法请求一个指定资源的表示形式，使用GET的请求应该只被用于获取数据\n\n#### POST\n`POST`方法用于将实体提交到指定的资源，通常导致在服务器上的状态变化或**副作用**\n\n本质上都是`TCP`链接，并无差别\n\n但是由于`HTTP`的规定和浏览器/服务器的限制，导致他们在应用过程中会体现出一些区别\n\n## 二、区别\n\n从`w3schools`得到的标准答案的区别如下：\n\n- GET在浏览器回退时是无害的，而POST会再次提交请求。\n- GET产生的URL地址可以被Bookmark，而POST不可以。\n- GET请求会被浏览器主动cache，而POST不会，除非手动设置。\n- GET请求只能进行url编码，而POST支持多种编码方式。\n- GET请求参数会被完整保留在浏览器历史记录里，而POST中的参数不会被保留。\n- GET请求在URL中传送的参数是有长度限制的，而POST没有。\n- 对参数的数据类型，GET只接受ASCII字符，而POST没有限制。\n- GET比POST更不安全，因为参数直接暴露在URL上，所以不能用来传递敏感信息。\n- GET参数通过URL传递，POST放在Request body中\n\n\n### 参数位置\n\n貌似从上面看到`GET`与`POST`请求区别非常大，但两者实质并没有区别\n\n无论 `GET `还是 `POST`，用的都是同一个传输层协议，所以在传输上没有区别\n\n当不携带参数的时候，两者最大的区别为第一行方法名不同\n\n> POST /uri HTTP/1.1 \\r\\n\n>\n> GET /uri HTTP/1.1 \\r\\n\n\n当携带参数的时候，我们都知道`GET`请求是放在`url`中，`POST`则放在`body`中\n\n`GET` 方法简约版报文是这样的\n\n```\nGET /index.html?name=qiming.c&age=22 HTTP/1.1\nHost: localhost\n```\n\n`POST `方法简约版报文是这样的\n\n```\nPOST /index.html HTTP/1.1\nHost: localhost\nContent-Type: application/x-www-form-urlencoded\n\nname=qiming.c&age=22\n```\n\n注意：这里只是约定，并不属于`HTTP`规范，相反的，我们可以在`POST`请求中`url`中写入参数，或者`GET`请求中的`body`携带参数\n\n\n### 参数长度\n\n`HTTP `协议没有` Body `和 `URL` 的长度限制，对 `URL `限制的大多是浏览器和服务器的原因\n\n`IE`对`URL`长度的限制是2083字节(2K+35)。对于其他浏览器，如Netscape、FireFox等，理论上没有长度限制，其限制取决于操作系统的支持\n\n这里限制的是整个`URL`长度，而不仅仅是参数值的长度\n\n服务器处理长` URL` 要消耗比较多的资源，为了性能和安全考虑，会给 `URL` 长度加限制\n\n### 安全\n\n`POST `比` GET` 安全，因为数据在地址栏上不可见\n\n然而，从传输的角度来说，他们都是不安全的，因为` HTTP` 在网络上是明文传输的，只要在网络节点上捉包，就能完整地获取数据报文\n\n只有使用`HTTPS`才能加密安全\n\n\n### 数据包\n\n对于`GET`方式的请求，浏览器会把`http header`和`data`一并发送出去，服务器响应200（返回数据）\n\n对于`POST`，浏览器先发送`header`，服务器响应100 `continue`，浏览器再发送`data`，服务器响应200 ok\n\n并不是所有浏览器都会在`POST`中发送两次包，`Firefox`就只发送一次\n\n\n\n## 参考文献\n\n- https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&mid=100000054&idx=1&sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd\n- https://blog.fundebug.com/2019/02/22/compare-http-method-get-and-post/\n- https://www.w3school.com.cn/tags/html_ref_httpmethods.asp\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/http/HTTPS.md",
    "content": "# 面试官：为什么说HTTPS比HTTP安全? HTTPS是如何保证安全的？\n\n ![](https://static.vue-js.com/b5512250-b2ff-11eb-ab90-d9ae814b240d.png)\n\n## 一、安全特性\n\n在上篇文章中，我们了解到`HTTP`在通信过程中，存在以下问题：\n\n- 通信使用明文（不加密），内容可能被窃听\n- 不验证通信方的身份，因此有可能遭遇伪装\n\n而`HTTPS`的出现正是解决这些问题，`HTTPS`是建立在`SSL`之上，其安全性由`SSL`来保证\n\n在采用`SSL`后，`HTTP`就拥有了`HTTPS`的加密、证书和完整性保护这些功能\n\n > SSL(Secure Sockets Layer 安全套接字协议),及其继任者传输层安全（Transport Layer Security，TLS）是为网络通信提供安全及数据完整性的一种安全协议\n\n ![](https://static.vue-js.com/cb559400-b2ff-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n\n\n## 二、如何做\n\n`SSL `的实现这些功能主要依赖于三种手段：\n\n- 对称加密：采用协商的密钥对数据加密\n- 非对称加密：实现身份认证和密钥协商\n- 摘要算法：验证信息的完整性\n- 数字签名：身份验证\n\n\n### 对称加密\n\n对称加密指的是加密和解密使用的秘钥都是同一个，是对称的。只要保证了密钥的安全，那整个通信过程就可以说具有了机密性\n\n ![](https://static.vue-js.com/e3f040f0-b2ff-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 非对称加密\n\n非对称加密，存在两个秘钥，一个叫公钥，一个叫私钥。两个秘钥是不同的，公钥可以公开给任何人使用，私钥则需要保密\n\n公钥和私钥都可以用来加密解密，但公钥加密后只能用私钥解\n密，反过来，私钥加密后也只能用公钥解密\n\n ![](https://static.vue-js.com/d9603e60-b2ff-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 混合加密\n\n在`HTTPS`通信过程中，采用的是对称加密+非对称加密，也就是混合加密\n\n在对称加密中讲到，如果能够保证了密钥的安全，那整个通信过程就可以说具有了机密性\n\n而`HTTPS`采用非对称加密解决秘钥交换的问题\n\n具体做法是发送密文的一方使用对方的公钥进行加密处理“对称的密钥”，然后对方用自己的私钥解密拿到“对称的密钥”\n\n ![](https://static.vue-js.com/f375f290-b2ff-11eb-85f6-6fac77c0c9b3.png)\n\n这样可以确保交换的密钥是安全的前提下，使用对称加密方式进行通信\n\n#### 举个例子：\n\n网站秘密保管私钥，在网上任意分发公钥，你想要登录网站只要用公钥加密就行了，密文只能由私钥持有者才能解密。而黑客因为没有私钥，所以就无法破解密文\n\n\n\n上述的方法解决了数据加密，在网络传输过程中，数据有可能被篡改，并且黑客可以伪造身份发布公钥，如果你获取到假的公钥，那么混合加密也并无多大用处，你的数据扔被黑客解决\n\n因此，在上述加密的基础上仍需加上完整性、身份验证的特性，来实现真正的安全，实现这一功能则是摘要算法\n\n### 摘要算法\n\n实现完整性的手段主要是摘要算法，也就是常说的散列函数、哈希函数\n\n可以理解成一种特殊的压缩算法，它能够把任意长度的数据“压缩”成固定长度、而且独一无二的“摘要”字符串，就好像是给这段数据生成了一个数字“指纹”\n\n ![](https://static.vue-js.com/12798da0-b300-11eb-85f6-6fac77c0c9b3.png)\n\n摘要算法保证了“数字摘要”和原文是完全等价的。所以，我们只要在原文后附上它的摘要，就能够保证数据的完整性\n\n比如，你发了条消息：“转账 1000 元”，然后再加上一个 SHA-2 的摘要。网站收到后也计算一下消息的摘要，把这两份“指纹”做个对比，如果一致，就说明消息是完整可信的，没有被修改\n\n ![](https://static.vue-js.com/023790e0-b300-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 数字签名\n\n数字签名能确定消息确实是由发送方签名并发出来的，因为别人假冒不了发送方的签名\n\n原理其实很简单，就是用私钥加密，公钥解密\n\n签名和公钥一样完全公开，任何人都可以获取。但这个签名只有用私钥对应的公钥才能解开，拿到摘要后，再比对原文验证完整性，就可以像签署文件一样证明消息确实是你发的\n\n ![](https://static.vue-js.com/21aa6880-b300-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n和消息本身一样，因为谁都可以发布公钥，我们还缺少防止黑客伪造公钥的手段，也就是说，怎么判断这个公钥就是你的公钥\n\n这时候就需要一个第三方，就是证书验证机构\n\n### CA验证机构\n\n数字证书认证机构处于客户端与服务器双方都可信赖的第三方机构的立场\n\nCA 对公钥的签名认证要求包括序列号、用途、颁发者、有效时间等等，把这些打成一个包再签名，完整地证明公钥关联的各种信息，形成“数字证书”\n\n流程如下图：\n\n ![](https://static.vue-js.com/395648a0-b300-11eb-85f6-6fac77c0c9b3.png)\n\n- 服务器的运营人员向数字证书认证机构提出公开密钥的申请\n- 数字证书认证机构在判明提出申请者的身份之后，会对已申请的公开密钥做数字签名\n- 然后分配这个已签名的公开密钥，并将该公开密钥放入公钥证书后绑定在一起\n- 服务器会将这份由数字证书认证机构颁发的数字证书发送给客户端，以进行非对称加密方式通信\n\n接到证书的客户端可使用数字证书认证机构的公开密钥，对那张证书上的数字签名进行验证，一旦验证通过，则证明：\n\n- 认证服务器的公开密钥的是真实有效的数字证书认证机构\n- 服务器的公开密钥是值得信赖的\n\n\n\n## 三、总结\n\n可以看到，`HTTPS`与`HTTP`虽然只差一个`SSL`，但是通信安全得到了大大的保障，通信的四大特性都以解决，解决方式如下：\n\n- 机密性：混合算法\n- 完整性：摘要算法\n- 身份认证：数字签名\n- 不可否定：数字签名\n\n同时引入第三方证书机构，确保公开秘钥的安全性\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/100657391\n- https://juejin.cn/post/6844903830987997197#heading-7\n- https://cloud.tencent.com/developer/article/1748862"
  },
  {
    "path": "docs/http/HTTP_HTTPS.md",
    "content": "# 面试官：什么是HTTP? HTTP 和 HTTPS 的区别?\n\n ![](https://static.vue-js.com/f50c71f0-b20b-11eb-ab90-d9ae814b240d.png)\n\n## 一、HTTP\n\n`HTTP` (HyperText Transfer Protocol)，即超文本运输协议，是实现网络通信的一种规范\n\n ![](https://static.vue-js.com/fda119b0-b20b-11eb-85f6-6fac77c0c9b3.png)\n\n在计算机和网络世界有，存在不同的协议，如广播协议、寻址协议、路由协议等等......\n\n而`HTTP`是一个传输协议，即将数据由A传到B或将B传输到A，并且 A 与 B 之间能够存放很多第三方，如： A<=>X<=>Y<=>Z<=>B\n\n传输的数据并不是计算机底层中的二进制包，而是完整的、有意义的数据，如HTML 文件, 图片文件, 查询结果等超文本，能够被上层应用识别\n\n在实际应用中，`HTTP`常被用于在`Web`浏览器和网站服务器之间传递信息，以明文方式发送内容，不提供任何方式的数据加密\n\n特点如下：\n\n- 支持客户/服务器模式\n\n- 简单快速：客户向服务器请求服务时，只需传送请求方法和路径。由于HTTP协议简单，使得HTTP服务器的程序规模小，因而通信速度很快\n- 灵活：HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记\n- 无连接：无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求，并收到客户的应答后，即断开连接。采用这种方式可以节省传输时间\n- 无状态：HTTP协议无法根据之前的状态进行本次的请求处理\n\n\n\n\n\n## 二、HTTPS\n\n在上述介绍`HTTP`中，了解到`HTTP`传递信息是以明文的形式发送内容，这并不安全。而`HTTPS`出现正是为了解决`HTTP`不安全的特性\n\n为了保证这些隐私数据能加密传输，让`HTTP`运行安全的`SSL/TLS`协议上，即 HTTPS = HTTP + SSL/TLS，通过 `SSL`证书来验证服务器的身份，并为浏览器和服务器之间的通信进行加密\n\n`SSL` 协议位于` TCP/IP` 协议与各种应用层协议之间，浏览器和服务器在使用 `SSL` 建立连接时需要选择一组恰当的加密算法来实现安全通信，为数据通讯提供安全支持\n\n ![](https://static.vue-js.com/078c50c0-b20c-11eb-ab90-d9ae814b240d.png)\n\n流程图如下所示：\n\n ![](https://static.vue-js.com/0e409fc0-b20c-11eb-85f6-6fac77c0c9b3.png)\n\n- 首先客户端通过URL访问服务器建立SSL连接\n- 服务端收到客户端请求后，会将网站支持的证书信息（证书中包含公钥）传送一份给客户端\n- 客户端的服务器开始协商SSL连接的安全等级，也就是信息加密的等级\n- 客户端的浏览器根据双方同意的安全等级，建立会话密钥，然后利用网站的公钥将会话密钥加密，并传送给网站\n- 服务器利用自己的私钥解密出会话密钥\n- 服务器利用会话密钥加密与客户端之间的通信\n\n\n\n## 三、区别\n\n- HTTPS是HTTP协议的安全版本，HTTP协议的数据传输是明文的，是不安全的，HTTPS使用了SSL/TLS协议进行了加密处理，相对更安全\n- HTTP 和 HTTPS 使用连接方式不同，默认端口也不一样，HTTP是80，HTTPS是443\n- HTTPS 由于需要设计加密以及多次握手，性能方面不如 HTTP\n- HTTPS需要SSL，SSL 证书需要钱，功能越强大的证书费用越高\n\n\n\n\n\n## 参考文献\n\n- https://www.cnblogs.com/klb561/p/10289199.html\n- https://www.jianshu.com/p/205c0fc51c97\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/http/OSI.md",
    "content": "# 面试官：如何理解OSI七层模型?\n\n ![](https://static.vue-js.com/e2e1b910-b61e-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\nOSI （Open System Interconnect）模型全称为开放式通信系统互连参考模型，是国际标准化组织 ( ISO ) 提出的一个试图使各种计算机在世界范围内互连为网络的标准框架\n\n`OSI `将计算机网络体系结构划分为七层，每一层实现各自的功能和协议，并完成与相邻层的接口通信。即每一层扮演固定的角色，互不打扰\n\n\n## 二、划分\n\n`OSI`主要划分了七层，如下图所示：\n\n ![](https://static.vue-js.com/eb1b2170-b61e-11eb-ab90-d9ae814b240d.png)\n\n\n### 应用层\n\n应用层位于 OSI 参考模型的第七层，其作用是通过应用程序间的交互来完成特定的网络应用\n\n该层协议定义了应用进程之间的交互规则，通过不同的应用层协议为不同的网络应用提供服务。例如域名系统 `DNS`，支持万维网应用的 `HTTP` 协议，电子邮件系统采用的 `SMTP `协议等\n\n在应用层交互的数据单元我们称之为报文\n\n\n\n### 表示层\n\n表示层的作用是使通信的应用程序能够解释交换数据的含义，其位于 `OSI `参考模型的第六层，向上为应用层提供服务，向下接收来自会话层的服务\n\n该层提供的服务主要包括数据压缩，数据加密以及数据描述，使应用程序不必担心在各台计算机中表示和存储的内部格式差异\n\n\n\n### 会话层\n\n会话层就是负责建立、管理和终止表示层实体之间的通信会话\n\n该层提供了数据交换的定界和同步功能，包括了建立检查点和恢复方案的方法\n\n\n\n### 传输层\n\n传输层的主要任务是为两台主机进程之间的通信提供服务，处理数据包错误、数据包次序，以及其他一些关键传输问题\n\n传输层向高层屏蔽了下层数据通信的细节。因此，它是计算机通信体系结构中关键的一层\n\n其中，主要的传输层协议是`TCP`和`UDP`\n\n\n\n\n\n### 网络层\n\n两台计算机之间传送数据时其通信链路往往不止一条，所传输的信息甚至可能经过很多通信子网\n\n网络层的主要任务就是选择合适的网间路由和交换节点，确保数据按时成功传送\n\n在发送数据时，网络层把传输层产生的报文或用户数据报封装成分组和包，向下传输到数据链路层\n\n在网络层使用的协议是无连接的网际协议（Internet Protocol）和许多路由协议，因此我们通常把该层简单地称为 IP 层\n\n\n\n### 数据链路层\n\n数据链路层通常也叫做链路层，在物理层和网络层之间。两台主机之间的数据传输，总是在一段一段的链路上传送的，这就需要使用专门的链路层协议\n\n在两个相邻节点之间传送数据时，数据链路层将网络层交下来的 `IP `数据报组装成帧，在两个相邻节点间的链路上传送帧\n\n每一帧的数据可以分成：报头`head`和数据`data`两部分:\n\n- head 标明数据发送者、接受者、数据类型，如 MAC地址\n- data 存储了计算机之间交互的数据\n\n通过控制信息我们可以知道一个帧的起止比特位置，此外，也能使接收端检测出所收到的帧有无差错，如果发现差错，数据链路层能够简单的丢弃掉这个帧，以避免继续占用网络资源\n\n\n\n### 物理层\n\n作为` OSI` 参考模型中最低的一层，物理层的作用是实现计算机节点之间比特流的透明传送\n\n该层的主要任务是确定与传输媒体的接口的一些特性（机械特性、电气特性、功能特性，过程特性）\n\n该层主要是和硬件有关，与软件关系不大\n\n\n\n\n\n## 三、传输过程\n\n数据在各层之间的传输如下图所示：\n\n ![](https://static.vue-js.com/f3a89d40-b61e-11eb-85f6-6fac77c0c9b3.png)\n\n- 应用层报文被传送到运输层\n- 在最简单的情况下，运输层收取到报文并附上附加信息，该首部将被接收端的运输层使用\n- 应用层报文和运输层首部信息一道构成了运输层报文段。附加的信息可能包括：允许接收端运输层向上向适当的应用程序交付报文的信息以及差错检测位信息。该信息让接收端能够判断报文中的比特是否在途中已被改变\n- 运输层则向网络层传递该报文段，网络层增加了如源和目的端系统地址等网络层首部信息，生成了网络层数据报\n- 网络层数据报接下来被传递给链路层，在数据链路层数据包添加发送端 MAC 地址和接收端 MAC 地址后被封装成数据帧\n- 在物理层数据帧被封装成比特流，之后通过传输介质传送到对端\n- 对端再一步步解开封装，获取到传送的数据\n\n\n\n\n\n## 参考文献\n- https://zh.wikipedia.org/wiki/OSI%E6%A8%A1%E5%9E%8B\n- https://zhuanlan.zhihu.com/p/32059190\n- https://leetcode-cn.com/leetbook/detail/networks-interview-highlights/\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/http/TCP_IP.md",
    "content": "# 面试官：如何理解TCP/IP协议?\n\n ![](https://static.vue-js.com/4f69a930-b647-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\nTCP/IP，**传输控制协议**/**网际协议**，是指能够在多个不同网络间实现信息传输的协议簇\n\n- TCP（传输控制协议）\n\n一种面向连接的、可靠的、基于字节流的传输层通信协议\n\n- IP（网际协议）\n\n用于封包交换数据网络的协议\n\nTCP/IP协议不仅仅指的是`TCP `和`IP`两个协议，而是指一个由`FTP`、`SMTP`、`TCP`、`UDP`、`IP`等协议构成的协议簇，\n\n只是因为在`TCP/IP`协议中`TCP`协议和`IP`协议最具代表性，所以通称为TCP/IP协议族（英语：TCP/IP Protocol Suite，或TCP/IP Protocols）\n\n\n## 二、划分\n\nTCP/IP协议族按层次分别了五层体系或者四层体系\n\n五层体系的协议结构是综合了 OSI 和 TCP/IP 优点的一种协议，包括应用层、传输层、网络层、数据链路层和物理层\n\n五层协议的体系结构只是为介绍网络原理而设计的，实际应用还是 TCP/IP 四层体系结构，包括应用层、传输层、网络层（网际互联层）、网络接口层\n\n如下图所示：\n\n ![](https://static.vue-js.com/5bb93610-b647-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### 五层体系\n\n#### 应用层\n\n`TCP/IP` 模型将 `OSI `参考模型中的会话层、表示层和应用层的功能合并到一个应用层实现，通过不同的应用层协议为不同的应用提供服务\n\n如：`FTP`、`Telnet`、`DNS`、`SMTP` 等\n\n\n\n#### 传输层\n\n该层对应于 OSI 参考模型的传输层，为上层实体提供源端到对端主机的通信功能\n\n传输层定义了两个主要协议：传输控制协议（TCP）和用户数据报协议（UDP）\n\n其中面向连接的 TCP 协议保证了数据的传输可靠性，面向无连接的 UDP 协议能够实现数据包简单、快速地传输\n\n\n\n#### 网络层\n\n负责为分组网络中的不同主机提供通信服务，并通过选择合适的路由将数据传递到目标主机\n\n在发送数据时，网络层把运输层产生的报文段或用户数据封装成分组或包进行传送\n\n\n\n#### 数据链路层\n\n数据链路层在两个相邻节点传输数据时，将网络层交下来的IP数据报组装成帧，在两个相邻节点之间的链路上传送帧\n\n\n\n#### 物理层\n\n保数据可以在各种物理媒介上进行传输，为数据的传输提供可靠的环境\n\n\n\n### 四层体系\n\nTCP/IP 的四层结构则如下表所示：\n\n| 层次名称   | 单位   | 功 能                                                     | 协 议                                                        |\n| ---------- | ------ | --------------------------------------------------------- | ------------------------------------------------------------ |\n| 网络接口层 | 帧     | 负责实际数据的传输，对应OSI参考模型的下两层               | HDLC（高级链路控制协议）PPP（点对点协议） SLIP（串行线路接口协议） |\n| 网络层     | 数据报 | 负责网络间的寻址数据传输，对应OSI参考模型的第三层         | IP（网际协议） ICMP（网际控制消息协议）ARP（地址解析协议） RARP（反向地址解析协议） |\n| 传输层     | 报文段 | 负责提供可靠的传输服务，对应OSI参考模型的第四层           | TCP（控制传输协议） UDP（用户数据报协议）                    |\n| 应用层     |        | 负责实现一切与应用程序相关的功能，对应OSI参考模型的上三层 | FTP（文件传输协议） HTTP（超文本传输协议） DNS（域名服务器协议）SMTP（简单邮件传输协议）NFS（网络文件系统协议） |\n\n\n\n## 三、总结\n\nOSI 参考模型与 TCP/IP 参考模型区别如下：\n\n相同点：\n\n- OSI 参考模型与 TCP/IP 参考模型都采用了层次结构\n- 都能够提供面向连接和无连接两种通信服务机制\n\n不同点：\n\n- OSI 采用的七层模型； TCP/IP 是四层或五层结构\n- TCP/IP 参考模型没有对网络接口层进行细分，只是一些概念性的描述； OSI 参考模型对服务和协议做了明确的区分\n- OSI 参考模型虽然网络划分为七层，但实现起来较困难。TCP/IP 参考模型作为一种简化的分层结构是可以的\n\n- TCP/IP协议去掉表示层和会话层的原因在于会话层、表示层、应用层都是在应用程序内部实现的，最终产出的是一个应用数据包，而应用程序之间是几乎无法实现代码的抽象共享的，这也就造成 `OSI` 设想中的应用程序维度的分层是无法实现的\n\n\n\n三种模型对应关系如下图所示：\n\n![](https://static.vue-js.com/3fbff4d0-b647-11eb-ab90-d9ae814b240d.png)\n\n\n\n\n\n## 参考文献\n- https://zh.wikipedia.org/wiki/TCP/IP%E5%8D%8F%E8%AE%AE%E6%97%8F\n- https://zhuanlan.zhihu.com/p/103162095\n- https://segmentfault.com/a/1190000039204681\n- https://leetcode-cn.com/leetbook/detail/networks-interview-highlights/\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/http/UDP_TCP.md",
    "content": "# 面试官：如何理解UDP 和 TCP? 区别? 应用场景?\n\n ![](https://static.vue-js.com/85ad65b0-b393-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、UDP\n\nUDP（User Datagram Protocol），用户数据包协议，是一个简单的**面向数据报的通信协议**，即对应用层交下来的报文，不合并，不拆分，只是在其上面加上首部后就交给了下面的网络层\n\n也就是说无论应用层交给`UDP`多长的报文，它统统发送，一次发送一个报文\n\n而对接收方，接到后直接去除首部，交给上面的应用层就完成任务\n\n`UDP`报头包括4个字段，每个字段占用2个字节（即16个二进制位），标题短，开销小\n\n ![](https://static.vue-js.com/928e5d20-b393-11eb-ab90-d9ae814b240d.png)\n\n\n\n特点如下：\n\n- UDP 不提供复杂的控制机制，利用 IP 提供面向无连接的通信服务\n- 传输途中出现丢包，UDP 也不负责重发\n- 当包的到达顺序出现乱序时，UDP没有纠正的功能。\n- 并且它是将应用程序发来的数据在收到的那一刻，立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况，UDP 也无法进行流量控制等避免网络拥塞行为\n\n\n\n## 二、TCP\n\nTCP（Transmission Control Protocol），传输控制协议，是一种可靠、**面向字节流的通信协议**，把上面应用层交下来的数据看成无结构的字节流来发送\n\n可以想象成流水形式的，发送方TCP会将数据放入“蓄水池”（缓存区），等到可以发送的时候就发送，不能发送就等着，TCP会根据当前网络的拥塞状态来确定每个报文段的大小\n\n`TCP`报文首部有20个字节，额外开销大\n\n ![](https://static.vue-js.com/a0010d40-b393-11eb-ab90-d9ae814b240d.png)\n\n\n\n特点如下：\n\n- TCP充分地实现了数据传输时各种控制功能，可以进行丢包时的重发控制，还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。\n- 此外，TCP 作为一种面向有连接的协议，只有在确认通信对端存在时才会发送数据，从而可以控制通信流量的浪费。\n- 根据 TCP 的这些机制，在 IP 这种无连接的网络上也能够实现高可靠性的通信（ 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现）\n\n\n\n## 三、区别\n\n`UDP`与`TCP`两者的都位于传输层，如下图所示：\n\n ![](https://static.vue-js.com/a92bda80-b393-11eb-ab90-d9ae814b240d.png)\n\n两者区别如下表所示：\n\n|          | TCP                              | UDP                            |\n| -------- | -------------------------------- | ------------------------------ |\n| 可靠性   | 可靠                             | 不可靠                         |\n| 连接性   | 面向连接                         | 无连接                         |\n| 报文     | 面向字节流                       | 面向报文                       |\n| 效率     | 传输效率低                       | 传输效率高                     |\n| 双共性   | 全双工                           | 一对一、一对多、多对一、多对多 |\n| 流量控制 | 滑动窗口                         | 无                             |\n| 拥塞控制 | 慢开始、拥塞避免、快重传、快恢复 | 无                             |\n| 传输效率 | 慢                               | 快                             |\n\n- TCP 是面向连接的协议，建立连接3次握手、断开连接四次挥手，UDP是面向无连接，数据传输前后不连接连接，发送端只负责将数据发送到网络，接收端从消息队列读取\n- TCP 提供可靠的服务，传输过程采用流量控制、编号与确认、计时器等手段确保数据无差错，不丢失。UDP 则尽可能传递数据，但不保证传递交付给对方\n- TCP 面向字节流，将应用层报文看成一串无结构的字节流，分解为多个TCP报文段传输后，在目的站重新装配。UDP协议面向报文，不拆分应用层报文，只保留报文边界，一次发送一个报文，接收方去除报文首部后，原封不动将报文交给上层应用\n\n- TCP 只能点对点全双工通信。UDP 支持一对一、一对多、多对一和多对多的交互通信\n\n两者应用场景如下图：\n\n ![](https://static.vue-js.com/b6cdd800-b393-11eb-ab90-d9ae814b240d.png)\n\n可以看到，TCP 应用场景适用于对效率要求低，对准确性要求高或者要求有链接的场景，而UDP 适用场景为对效率要求高，对准确性要求低的场景\n\n\n## 参考文献\n- https://zh.wikipedia.org\n- https://www.shangmayuan.com/a/a1e3ceb218284cefb95de7fd.html\n- https://segmentfault.com/a/1190000021815671\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/http/WebSocket.md",
    "content": "# 面试官：说说对WebSocket的理解？应用场景？\n\n ![](https://static.vue-js.com/a358a8c0-c0f1-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\nWebSocket，是一种网络传输协议，位于`OSI`模型的应用层。可在单个`TCP`连接上进行全双工通信，能更好的节省服务器资源和带宽并达到实时通迅\n\n客户端和服务器只需要完成一次握手，两者之间就可以创建持久性的连接，并进行双向数据传输\n\n ![](https://static.vue-js.com/ad386e20-c0f1-11eb-85f6-6fac77c0c9b3.png)\n\n从上图可见，`websocket`服务器与客户端通过握手连接，连接成功后，两者都能主动的向对方发送或接受数据\n\n而在`websocket`出现之前，开发实时`web`应用的方式为轮询\n\n不停地向服务器发送 HTTP 请求，问有没有数据，有数据的话服务器就用响应报文回应。如果轮询的频率比较高，那么就可以近似地实现“实时通信”的效果\n\n轮询的缺点也很明显，反复发送无效查询请求耗费了大量的带宽和 `CPU `资源\n\n\n\n## 二、特点\n\n\n\n### 全双工\n\n通信允许数据在两个方向上同时传输，它在能力上相当于两个单工通信方式的结合\n\n例如指 A→B 的同时 B→A ，是瞬时同步的\n\n\n\n### 二进制帧\n\n采用了二进制帧结构，语法、语义与 HTTP 完全不兼容，相比`http/2`，`WebSocket `更侧重于“实时通信”，而`HTTP/2` 更侧重于提高传输效率，所以两者的帧结构也有很大的区别\n\n不像 `HTTP/2` 那样定义流，也就不存在多路复用、优先级等特性\n\n自身就是全双工，也不需要服务器推送\n\n\n\n\n\n### 协议名\n\n引入`ws`和`wss`分别代表明文和密文的`websocket`协议，且默认端口使用80或443，几乎与`http`一致\n\n```http\nws://www.chrono.com\nws://www.chrono.com:8080/srv\nwss://www.chrono.com:445/im?user_id=xxx\n```\n\n\n\n### 握手\n\n`WebSocket `也要有一个握手过程，然后才能正式收发数据\n\n客户端发送数据格式如下：\n\n```http\nGET /chat HTTP/1.1\nHost: server.example.com\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\nOrigin: http://example.com\nSec-WebSocket-Protocol: chat, superchat\nSec-WebSocket-Version: 13\n```\n\n- Connection：必须设置Upgrade，表示客户端希望连接升级\n- Upgrade：必须设置Websocket，表示希望升级到Websocket协议\n- Sec-WebSocket-Key：客户端发送的一个 base64 编码的密文，用于简单的认证秘钥。要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept应答，否则客户端会抛出错误，并关闭连接\n- Sec-WebSocket-Version ：表示支持的Websocket版本\n\n服务端返回的数据格式：\n\n```http\nHTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chat\n```\n\n- HTTP/1.1 101 Switching Protocols：表示服务端接受 WebSocket 协议的客户端连接\n- Sec-WebSocket-Accep：验证客户端请求报文，同样也是为了防止误连接。具体做法是把请求头里“Sec-WebSocket-Key”的值，加上一个专用的 UUID，再计算摘要\n\n\n\n### 优点\n\n- 较少的控制开销：数据包头部协议较小，不同于http每次请求需要携带完整的头部\n- 更强的实时性：相对于HTTP请求需要等待客户端发起请求服务端才能响应，延迟明显更少\n- 保持创连接状态：创建通信后，可省略状态信息，不同于HTTP每次请求需要携带身份验证\n- 更好的二进制支持：定义了二进制帧，更好处理二进制内容\n- 支持扩展：用户可以扩展websocket协议、实现部分自定义的子协议\n- 更好的压缩效果：Websocket在适当的扩展支持下，可以沿用之前内容的上下文，在传递类似的数据时，可以显著地提高压缩率\n\n\n\n## 二、应用场景\n\n基于`websocket`的事实通信的特点，其存在的应用场景大概有：\n\n- 弹幕\n- 媒体聊天\n- 协同编辑\n- 基于位置的应用\n- 体育实况更新\n- 股票基金报价实时更新\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/WebSocket\n- https://www.oschina.net/translate/9-killer-uses-for-websockets\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/http/after_url.md",
    "content": "# 面试官：说说地址栏输入 URL 敲下回车后发生了什么?\n\n ![](https://static.vue-js.com/11bf1f20-bdf4-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、简单分析\n\n简单的分析，从输入 `URL`到回车后发生的行为如下：\n\n- URL解析\n- DNS 查询\n- TCP 连接\n- HTTP 请求\n- 响应请求\n- 页面渲染\n\n\n## 二、详细分析\n\n### URL解析\n\n首先判断你输入的是一个合法的` URL` 还是一个待搜索的关键词，并且根据你输入的内容进行对应操作\n\n`URL`的解析第过程中的第一步，一个`url`的结构解析如下：\n\n ![](https://static.vue-js.com/27a0c690-bdf4-11eb-ab90-d9ae814b240d.png)\n\n\n\n### DNS查询\n\n在之前文章中讲过`DNS`的查询，这里就不再讲述了\n\n整个查询过程如下图所示：\n\n ![](https://static.vue-js.com/330fb770-bdf4-11eb-85f6-6fac77c0c9b3.png)\n\n最终，获取到了域名对应的目标服务器`IP`地址\n\n\n\n### TCP连接\n\n在之前文章中，了解到`tcp`是一种面向有连接的传输层协议\n\n在确定目标服务器服务器的`IP`地址后，则经历三次握手建立`TCP`连接，流程如下：\n\n ![](https://static.vue-js.com/ad750790-bdf4-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n\n\n### 发送 http 请求\n\n当建立`tcp`连接之后，就可以在这基础上进行通信，浏览器发送 `http` 请求到目标服务器\n\n请求的内容包括：\n\n- 请求行\n- 请求头\n- 请求主体\n\n ![](https://static.vue-js.com/bbcb60f0-bdf4-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 响应请求\n\n当服务器接收到浏览器的请求之后，就会进行逻辑操作，处理完成之后返回一个`HTTP`响应消息，包括：\n\n- 状态行\n- 响应头\n- 响应正文\n\n ![](https://static.vue-js.com/c5fe0140-bdf4-11eb-ab90-d9ae814b240d.png)\n\n在服务器响应之后，由于现在`http`默认开始长连接`keep-alive`，当页面关闭之后，`tcp`链接则会经过四次挥手完成断开\n\n\n\n### 页面渲染\n\n当浏览器接收到服务器响应的资源后，首先会对资源进行解析：\n\n- 查看响应头的信息，根据不同的指示做对应处理，比如重定向，存储cookie，解压gzip，缓存资源等等\n- 查看响应头的 Content-Type的值，根据不同的资源类型采用不同的解析方式\n\n关于页面的渲染过程如下：\n\n- 解析HTML，构建 DOM 树\n- 解析 CSS ，生成 CSS 规则树\n- 合并 DOM 树和 CSS 规则，生成 render 树\n- 布局 render 树（ Layout / reflow ），负责各元素尺寸、位置的计算\n- 绘制 render 树（ paint ），绘制页面像素信息\n- 浏览器会将各层的信息发送给 GPU，GPU 会将各层合成（ composite ），显示在屏幕上\n\n ![](https://static.vue-js.com/db7bddd0-bdf4-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 参考文献\n\n- https://github.com/febobo/web-interview/issues/141\n- https://zhuanlan.zhihu.com/p/80551769"
  },
  {
    "path": "docs/http/handshakes_waves.md",
    "content": "# 面试官：说说TCP为什么需要三次握手和四次挥手？\n\n ![](https://static.vue-js.com/ef4696a0-beb9-11eb-ab90-d9ae814b240d.png)\n\n## 一、三次握手\n\n三次握手（Three-way Handshake）其实就是指建立一个TCP连接时，需要客户端和服务器总共发送3个包\n\n主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备\n\n过程如下：\n\n- 第一次握手：客户端给服务端发一个 SYN 报文，并指明客户端的初始化序列号 ISN(c)，此时客户端处于  SYN_SENT 状态\n- 第二次握手：服务器收到客户端的 SYN 报文之后，会以自己的 SYN 报文作为应答，为了确认客户端的 SYN，将客户端的 ISN+1作为ACK的值，此时服务器处于 SYN_RCVD  的状态\n- 第三次握手：客户端收到 SYN 报文之后，会发送一个 ACK 报文，值为服务器的ISN+1。此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后，也处于 ESTABLISHED  状态，此时，双方已建立起了连接\n\n ![](https://static.vue-js.com/fb489fc0-beb9-11eb-85f6-6fac77c0c9b3.png)\n\n上述每一次握手的作用如下：\n\n- 第一次握手：客户端发送网络包，服务端收到了\n  这样服务端就能得出结论：客户端的发送能力、服务端的接收能力是正常的。\n- 第二次握手：服务端发包，客户端收到了\n  这样客户端就能得出结论：服务端的接收、发送能力，客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常\n- 第三次握手：客户端发包，服务端收到了。\n  这样服务端就能得出结论：客户端的接收、发送能力正常，服务器自己的发送、接收能力也正常\n\n通过三次握手，就能确定双方的接收和发送能力是正常的。之后就可以正常通信了\n\n\n### 为什么不是两次握手?\n\n如果是两次握手，发送端可以确定自己发送的信息能对方能收到，也能确定对方发的包自己能收到，但接收端只能确定对方发的包自己能收到 无法确定自己发的包对方能收到\n\n并且两次握手的话, 客户端有可能因为网络阻塞等原因会发送多个请求报文，延时到达的请求又会与服务器建立连接，浪费掉许多服务器的资源\n\n\n\n## 二、四次挥手\n\n`tcp`终止一个连接，需要经过四次挥手\n\n过程如下：\n\n- 第一次挥手：客户端发送一个 FIN 报文，报文中会指定一个序列号。此时客户端处于  FIN_WAIT1 状态，停止发送数据，等待服务端的确认\n- 第二次挥手：服务端收到 FIN 之后，会发送 ACK 报文，且把客户端的序列号值 +1 作为 ACK 报文的序列号值，表明已经收到客户端的报文了，此时服务端处于 CLOSE_WAIT状态\n- 第三次挥手：如果服务端也想断开连接了，和客户端的第一次挥手一样，发给 FIN 报文，且指定一个序列号。此时服务端处于 `LAST_ACK` 的状态\n- 第四次挥手：客户端收到 FIN 之后，一样发送一个 ACK 报文作为应答，且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值，此时客户端处于 TIME_WAIT状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态，服务端收到 ACK 报文之后，就处于关闭连接了，处于 CLOSED 状态\n\n ![](https://static.vue-js.com/0a3ebb90-beba-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### 四次挥手原因\n\n服务端在收到客户端断开连接`Fin`报文后，并不会立即关闭连接，而是先发送一个`ACK`包先告诉客户端收到关闭连接的请求，只有当服务器的所有报文发送完毕之后，才发送`FIN`报文断开连接，因此需要四次挥手\n\n\n\n\n\n## 三、总结\n\n一个完整的三次握手四次挥手如下图所示：\n\n ![](https://static.vue-js.com/65941490-beba-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/53374516\n- https://segmentfault.com/a/1190000020610336"
  },
  {
    "path": "docs/http/headers.md",
    "content": "# 面试官：说说 HTTP 常见的请求头有哪些? 作用？\n\n ![](https://static.vue-js.com/964abb00-bc69-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\nHTTP头字段（HTTP header fields）,是指在超文本传输协议（HTTP）的请求和响应消息中的消息头部分\n\n它们定义了一个超文本传输协议事务中的操作参数\n\nHTTP头部字段可以自己根据需要定义，因此可能在 `Web `服务器和浏览器上发现非标准的头字段\n\n下面是一个`HTTP`请求的请求头：\n\n```http\nGET /home.html HTTP/1.1\nHost: developer.mozilla.org\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\nAccept-Language: en-US,en;q=0.5\nAccept-Encoding: gzip, deflate, br\nReferer: https://developer.mozilla.org/testpage.html\nConnection: keep-alive\nUpgrade-Insecure-Requests: 1\nIf-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT\nIf-None-Match: \"c561c68d0ba92bbeb8b0fff2a9199f722e3a621a\"\nCache-Control: max-age=0\n```\n\n\n## 二、分类\n\n常见的请求字段如下表所示：\n\n| 字段名            | 说明                                                         | 示例                                                         |\n| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n| Accept            | 能够接受的回应内容类型（Content-Types）                      | Accept: text/plain                                           |\n| Accept-Charset    | 能够接受的字符集                                             | Accept-Charset: utf-8                                        |\n| Accept-Encoding   | 能够接受的编码方式列表                                       | Accept-Encoding: gzip, deflate                               |\n| Accept-Language   | 能够接受的回应内容的自然语言列表                             | Accept-Language: en-US                                       |\n| Authorization     | 用于超文本传输协议的认证的认证信息                           | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==            |\n| Cache-Control     | 用来指定在这次的请求/响应链中的所有缓存机制 都必须 遵守的指令 | Cache-Control: no-cache                                      |\n| Connection        | 该浏览器想要优先使用的连接类型                               | Connection: keep-alive Connection: Upgrade                   |\n| Cookie            | 服务器通过 Set- Cookie （下文详述）发送的一个 超文本传输协议Cookie | Cookie: $Version=1; Skin=new;                                |\n| Content-Length    | 以 八位字节数组 （8位的字节）表示的请求体的长度              | Content-Length: 348                                          |\n| Content-Type      | 请求体的 多媒体类型                                          | Content-Type: application/x-www-form-urlencoded              |\n| Date              | 发送该消息的日期和时间                                       | Date: Tue, 15 Nov 1994 08:12:31 GMT                          |\n| Expect            | 表明客户端要求服务器做出特定的行为                           | Expect: 100-continue                                         |\n| Host              | 服务器的域名(用于虚拟主机 )，以及服务器所监听的传输控制协议端口号 | Host: en.wikipedia.org:80 Host: en.wikipedia.org             |\n| If-Match          | 仅当客户端提供的实体与服务器上对应的实体相匹配时，才进行对应的操作。主要作用时，用作像 PUT 这样的方法中，仅当从用户上次更新某个资源以来，该资源未被修改的情况下，才更新该资源 | If-Match: \"737060cd8c284d8af7ad3082f209582d\"                 |\n| If-Modified-Since | 允许在对应的内容未被修改的情况下返回304未修改                | If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT             |\n| If-None-Match     | 允许在对应的内容未被修改的情况下返回304未修改                | If-None-Match: \"737060cd8c284d8af7ad3082f209582d\"            |\n| If-Range          | 如果该实体未被修改过，则向我发送我所缺少的那一个或多个部分；否则，发送整个新的实体 | If-Range: \"737060cd8c284d8af7ad3082f209582d\"                 |\n| Range             | 仅请求某个实体的一部分                                       | Range: bytes=500-999                                         |\n| User-Agent        | 浏览器的浏览器身份标识字符串                                 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0 |\n| Origin            | 发起一个针对 跨来源资源共享 的请求                           | Origin: http://www.example-social-network.com                |\n\n\n\n\n\n## 三、使用场景\n\n通过配合请求头和响应头，可以满足一些场景的功能实现：\n\n### 协商缓存\n\n协商缓存是利用的是`【Last-Modified，If-Modified-Since】`和`【ETag、If-None-Match】`这两对请求头响应头来管理的\n\n`Last-Modified` 表示本地文件最后修改日期，浏览器会在request header加上`If-Modified-Since`（上次返回的`Last-Modified`的值），询问服务器在该日期后资源是否有更新，有更新的话就会将新的资源发送回来\n\n`Etag`就像一个指纹，资源变化都会导致`ETag`变化，跟最后修改时间没有关系，`ETag`可以保证每一个资源是唯一的\n\n`If-None-Match`的header会将上次返回的`Etag`发送给服务器，询问该资源的`Etag`是否有更新，有变动就会发送新的资源回来\n\n而强制缓存不需要发送请求到服务端，根据请求头`expires`和`cache-control`判断是否命中强缓存\n\n强制缓存与协商缓存的流程图如下所示：\n\n ![](https://static.vue-js.com/a4065b00-bc69-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### 会话状态\n\n`cookie`，类型为「小型文本文件」，指某些网站为了辨别用户身份而储存在用户本地终端上的数据，通过响应头`set-cookie`决定\n\n作为一段一般不超过 4KB 的小型文本数据，它由一个名称（Name）、一个值（Value）和其它几个用于控制 `Cookie `有效期、安全性、使用范围的可选属性组成\n\n`Cookie` 主要用于以下三个方面：\n\n- 会话状态管理（如用户登录状态、购物车、游戏分数或其它需要记录的信息）\n- 个性化设置（如用户自定义设置、主题等）\n- 浏览器行为跟踪（如跟踪分析用户行为等\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/HTTP头字段\n- https://github.com/amandakelake/blog/issues/41"
  },
  {
    "path": "docs/http/status.md",
    "content": "# 面试官：说说HTTP 常见的状态码有哪些，适用场景？\n\n ![](https://static.vue-js.com/038831d0-bbc9-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\nHTTP状态码（英语：HTTP Status Code），用以表示网页服务器超文本传输协议响应状态的3位数字代码\n\n它由 RFC 2616规范定义的，并得到 `RFC 2518`、`RFC 2817`、`RFC 2295`、`RFC 2774`与 `RFC 4918`等规范扩展\n\n简单来讲，`http`状态码的作用是服务器告诉客户端当前请求响应的状态，通过状态码就能判断和分析服务器的运行状态\n\n\n\n## 二、分类\n\n状态码第一位数字决定了不同的响应状态，有如下：\n\n- 1 表示消息\n- 2 表示成功\n- 3 表示重定向\n- 4 表示请求错误\n- 5 表示服务器错误\n\n\n\n### 1xx\n\n代表请求已被接受，需要继续处理。这类响应是临时响应，只包含状态行和某些可选的响应头信息，并以空行结束\n\n常见的有：\n\n- 100（客户端继续发送请求，这是临时响应）：这个临时响应是用来通知客户端它的部分请求已经被服务器接收，且仍未被拒绝。客户端应当继续发送请求的剩余部分，或者如果请求已经完成，忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应\n- 101：服务器根据客户端的请求切换协议，主要用于websocket或http2升级\n\n\n\n\n\n### 2xx\n\n代表请求已成功被服务器接收、理解、并接受\n\n常见的有：\n\n- 200（成功）：请求已成功，请求所希望的响应头或数据体将随此响应返回\n- 201（已创建）：请求成功并且服务器创建了新的资源\n\n- 202（已创建）：服务器已经接收请求，但尚未处理\n- 203（非授权信息）：服务器已成功处理请求，但返回的信息可能来自另一来源\n- 204（无内容）：服务器成功处理请求，但没有返回任何内容\n- 205（重置内容）：服务器成功处理请求，但没有返回任何内容\n- 206（部分内容）：服务器成功处理了部分请求\n\n\n\n### 3xx\n\n表示要完成请求，需要进一步操作。 通常，这些状态代码用来重定向\n\n常见的有：\n\n- 300（多种选择）：针对请求，服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作，或提供操作列表供请求者选择\n- 301（永久移动）：请求的网页已永久移动到新位置。 服务器返回此响应（对 GET 或 HEAD 请求的响应）时，会自动将请求者转到新位置\n- 302（临时移动）： 服务器目前从不同位置的网页响应请求，但请求者应继续使用原有位置来进行以后的请求\n- 303（查看其他位置）：请求者应当对不同的位置使用单独的 GET 请求来检索响应时，服务器返回此代码\n\n- 305 （使用代理）： 请求者只能使用代理访问请求的网页。 如果服务器返回此响应，还表示请求者应使用代理\n- 307 （临时重定向）： 服务器目前从不同位置的网页响应请求，但请求者应继续使用原有位置来进行以后的请求\n\n\n\n### 4xx\n\n代表了客户端看起来可能发生了错误，妨碍了服务器的处理\n\n常见的有：\n\n- 400（错误请求）： 服务器不理解请求的语法\n- 401（未授权）： 请求要求身份验证。 对于需要登录的网页，服务器可能返回此响应。\n- 403（禁止）： 服务器拒绝请求\n- 404（未找到）： 服务器找不到请求的网页\n- 405（方法禁用）： 禁用请求中指定的方法\n- 406（不接受）： 无法使用请求的内容特性响应请求的网页\n- 407（需要代理授权）： 此状态代码与 401（未授权）类似，但指定请求者应当授权使用代理\n- 408（请求超时）： 服务器等候请求时发生超时\n\n\n\n### 5xx\n\n表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生\n\n常见的有：\n\n- 500（服务器内部错误）：服务器遇到错误，无法完成请求\n- 501（尚未实施）：服务器不具备完成请求的功能。 例如，服务器无法识别请求方法时可能会返回此代码\n- 502（错误网关）： 服务器作为网关或代理，从上游服务器收到无效响应\n- 503（服务不可用）： 服务器目前无法使用（由于超载或停机维护）\n- 504（网关超时）： 服务器作为网关或代理，但是没有及时从上游服务器收到请求\n- 505（HTTP 版本不受支持）： 服务器不支持请求中所用的 HTTP 协议版本\n\n\n\n## 三、适用场景\n\n下面给出一些状态码的适用场景：\n\n- 100：客户端在发送POST数据给服务器前，征询服务器情况，看服务器是否处理POST的数据，如果不处理，客户端则不上传POST数据，如果处理，则POST上传数据。常用于POST大数据传输\n\n- 206：一般用来做断点续传，或者是视频文件等大文件的加载\n\n- 301：永久重定向会缓存。新域名替换旧域名，旧的域名不再使用时，用户访问旧域名时用301就重定向到新的域名\n\n- 302：临时重定向不会缓存，常用 于未登陆的用户访问用户中心重定向到登录页面\n- 304：协商缓存，告诉客户端有缓存，直接使用缓存中的数据，返回页面的只有头部信息，是没有内容部分\n- 400：参数有误，请求无法被服务器识别\n- 403：告诉客户端进制访问该站点或者资源，如在外网环境下，然后访问只有内网IP才能访问的时候则返回\n- 404：服务器找不到资源时，或者服务器拒绝请求又不想说明理由时\n- 503：服务器停机维护时，主动用503响应请求或 nginx 设置限速，超过限速，会返回503\n\n- 504：网关超时\n\n\n\n\n## 参考文献\n\n- https://zh.wikipedia.org/wiki/HTTP状态码\n- https://kebingzao.com/2018/10/05/http-status-code/\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/linux/file.md",
    "content": "# 面试官：说说 linux系统下 文件操作常用的命令有哪些？\n\n![](https://static.vue-js.com/6cb38ac0-03c1-11ec-a752-75723a64e8f5.png)\n\n\n## 一、是什么\n`Linux` 是一个开源的操作系统（OS），是一系列Linux内核基础上开发的操作系统的总称（常见的有Ubuntu、centos）\n\n系统通常会包含以下4个主要部分\n- 内核\n- shell\n- 文件系统\n- 应用程序\n\n文件系统是一个**目录树的结构**，文件系统结构从一个根目录开始，根目录下可以有任意多个文件和子目录，子目录中又可以有任意多个文件和子目录\n\n ![](https://static.vue-js.com/b71b64c0-03c1-11ec-a752-75723a64e8f5.png)\n\n\n\n## 二、文件操作\n\n常见处理目录的命令如下：\n\n- ls（英文全拼：list files）: 列出目录及文件名\n- cd（英文全拼：change directory）：切换目录\n- pwd（英文全拼：print work directory）：显示目前的目录\n- mkdir（英文全拼：make directory）：创建一个新的目录\n- rmdir（英文全拼：remove directory）：删除一个空的目录\n- cp（英文全拼：copy file）: 复制文件或目录\n- rm（英文全拼：remove）: 删除文件或目录\n- mv（英文全拼：move file）: 移动文件与目录，或修改文件与目录的名称\n\n\n\n### ls\n\n列出目录文件，选项与参数：\n\n- -a ：全部的文件，连同隐藏文件( 开头为 . 的文件) 一起列出来(常用)\n- -d ：仅列出目录本身，而不是列出目录内的文件数据(常用)\n- -l ：长数据串列出，包含文件的属性与权限等等数据；(常用)\n\n例如将家目录下的所有文件列出来(含属性与隐藏档)\n\n```cmd\n[root@www ~]# ls -al ~\n```\n\n\n### cd\n\n切换工作目录\n\n语法：\n\n```cmd\n cd [相对路径或绝对路径]\n```\n\n```cmd\n# 表示回到自己的家目录，亦即是 /root 这个目录\n[root@www runoob]# cd ~\n\n# 表示去到目前的上一级目录，亦即是 /root 的上一级目录的意思；\n[root@www ~]# cd ..\n```\n\n\n\n### pwd\n\n`pwd` 是 `Print Working Directory` 的缩写，也就是显示目前所在目录的命令。\n\n```\n[root@www ~]# pwd [-P]\n```\n\n选项与参数：\n\n- -P ：显示出确实的路径，而非使用连结 (link) 路径\n\n\n\n\n\n### mkdir\n\n 创建新目录\n\n语法：\n\n```\nmkdir [-mp] 目录名称\n```\n\n选项与参数：\n\n- -m ：配置文件的权限\n- -p ：帮助你直接将所需要的目录(包含上一级目录)递归创建起来\n\n\n\n### rmdir (删除空的目录)\n\n语法：\n\n```\n rmdir [-p] 目录名称\n```\n\n选项与参数：\n\n- -p ：连同上一级『空的』目录也一起删除\n\n\n\n\n\n### cp\n\n即拷贝文件和目录\n\n语法：\n\n```cmd\ncp 目标文件 拷贝文件\n```\n\n用法如下：\n\n```cmd\ncp file file_copy --> file 是目标文件，file_copy 是拷贝出来的文件\ncp file one --> 把 file 文件拷贝到 one 目录下，并且文件名依然为 file\ncp file one/file_copy --> 把 file 文件拷贝到 one 目录下，文件名为file_copy\ncp *.txt folder --> 把当前目录下所有 txt 文件拷贝到 folder 目录下\n复制代码\n```\n\n常用参数如下：\n\n- `-r` 递归的拷贝，常用来拷贝一整个目录\n\n\n\n\n\n### rm (移除文件或目录)\n\n语法：\n\n```\n rm [-fir] 文件或目录\n```\n\n选项与参数：\n\n- -f ：就是 force 的意思，忽略不存在的文件，不会出现警告信息；\n- -i ：互动模式，在删除前会询问使用者是否动作\n- -r ：递归删除啊！最常用在目录的删除了！这是非常危险的选项！！\n\n\n\n### mv (移动文件与目录，或修改名称)\n\n语法：\n\n```\n[root@www ~]# mv [-fiu] source destination\n[root@www ~]# mv [options] source1 source2 source3 .... directory\n```\n\n选项与参数：\n\n- -f ：force 强制的意思，如果目标文件已经存在，不会询问而直接覆盖；\n- -i ：若目标文件 (destination) 已经存在时，就会询问是否覆盖！\n- -u ：若目标文件已经存在，且 source 比较新，才会升级 (update)\n\n\n\n### ln\n\n`Linux` 文件的存储方式分为3个部分，文件名、文件内容以及权限，其中文件名的列表是存储在硬盘的其它地方和文件内容是分开存放的，每个文件名通过 `inode` 标识绑定到文件内容\n\n`Linux` 下有两种链接类型：硬链接和软链接\n\n#### 硬链接\n\n使链接的两个文件共享同样文件内容，就是同样的 `inode` ，一旦文件1和文件2之间有了硬链接，那么修改任何一个文件，修改的都是同一块内容\n\n语法：\n\n```cmd\n# 创建 file2 为 file1 的硬链接\nln file1 file2\n```\n\n\n\n ![](https://static.vue-js.com/c92e7800-03c1-11ec-8e64-91fdec0f05a1.png)\n\n删除文件1不会影响删除文件2，对于硬链接来说，删除任意一方的文件，共同指向的文件内容并不会从硬盘上删除\n\n只有同时删除了两个文件后后，它们共同指向的文件内容才会消失。\n\n\n\n#### 软链接\n\n类似`window`系统的快捷方式\n\n使用方式：\n\n```cmd\nln -s file1 file2\n```\n\n ![](https://static.vue-js.com/d5a22eb0-03c1-11ec-8e64-91fdec0f05a1.png)其实 `file2` 只是 `file1` 的一个快捷方式，它指向的是 `file1` ，所以显示的是 `file1` 的内容，但其实 `file2` 的 `inode` 与 `file1` 并不相同\n\n如果\n\n删除了 `file2` 的话， `file1` 是不会受影响的，但如果删除 `file1` 的话， `file2` 就会变成死链接，因为指向的文件不见了\n\n\n\n## 三、文件查看\n\n常见的文件内容查看有如下：\n\n- cat 由第一行开始显示文件内容\n- less 一页一页的显示文件内容\n- head 只看头几行\n- tail 只看尾巴几行\n\n\n\n### cat\n\n由第一行开始显示文件内容\n\n语法：\n\n```\ncat [-AbEnTv]\n```\n\n常见的选项与参数如下：\n\n- -b ：列出行号，仅针对非空白行做行号显示，空白行不标行号！\n- -n ：列印出行号，连同空白行也会有行号，与 -b 的选项不同\n\n\n\n### less\n\n一页一页翻动，以下实例输出/etc/man.config文件的内容：\n\n```cmd\n[root@www ~]# less /etc/man.config\n#\n# Generated automatically from man.conf.in by the\n# configure script.\n#\n# man.conf from man-1.6d\n....(中间省略)....\n:   <== 这里可以等待你输入命令！\n```\n\nless运行时可以输入的命令有：\n\n- 空白键  ：向下翻动一页；\n- [pagedown]：向下翻动一页；\n- [pageup] ：向上翻动一页；\n- /字串   ：向下搜寻『字串』的功能；\n- ?字串   ：向上搜寻『字串』的功能；\n- n     ：重复前一个搜寻 (与 / 或 ? 有关！)\n- N     ：反向的重复前一个搜寻 (与 / 或 ? 有关！)\n- q     ：离开 less 这个程序\n\n\n\n### head\n\n取出文件前面几行\n\n语法：\n\n```\nhead [-n number] 文件 \n```\n\n选项与参数：\n\n- -n ：后面接数字，代表显示几行的意思\n\n```cmd\n[root@www ~]# head /etc/man.config\n```\n\n\n\n### tail\n\n取出文件后面几行\n\n语法：\n\n```\ntail [-n number] 文件 \n```\n\n选项与参数：\n\n- -n ：后面接数字，代表显示几行的意思\n- -f ：表示持续侦测后面所接的档名，要等到按下[ctrl]-c才会结束tail的侦测\n\n\n\n## 参考文献\n\n- https://www.runoob.com/linux/linux-file-content-manage.html\n- https://juejin.cn/post/6938385978004340744#heading-35\n- https://zh.wikipedia.org/wiki/Linux"
  },
  {
    "path": "docs/linux/linux users.md",
    "content": "# 面试官：说说你对 linux 用户管理的理解？相关的命令有哪些？\n\n ![](https://static.vue-js.com/8d8d9d70-0417-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\nLinux是一个多用户的系统，允许使用者在系统上通过规划不同类型、不同层级的用户，并公平地分配系统资源与工作环境\n\n而与 `Windows` 系统最大的不同， `Linux` 允许不同的用户同时登录主机，同时使用主机的资源\n\n既然是多用户的系统，那么最常见的问题就是权限，不同的用户对于不同的文件都应该有各自的权限\n\n例如，小 A 希望个人文件不被其他用户读取，而如果不对文件进行权限设置，共享了主机资源的小 B 也可以读取小 A 的个人文件，这是不合理的\n\n这里面涉及到用户与用户组的概念\n\n\n\n## 二、用户与用户组\n\n`Linux `以 “用户与用户组” 的概念，建立用户与文件权限之间的联系，保证系统能够充分考虑每个用户的隐私保护，很大程度上保障了 `Linux` 作为多用户系统的可行性\n\n从文件权限的角度出发，“用户与用户组” 引申为三个具体的对象：\n\n- **文件所有者**\n- **用户组成员**\n- **其他人**\n\n每一个对象对某一个文件的持有权限是不同的\n\n\n\n### 文件所有者\n\n当一个用户创建了一个文件，这个用户就是这个文件的文件所有者。文件所有者对文件拥有最高权限，同时排他性地拥有该文件\n\n除非文件所有者开放权限，否则其他人无法对文件执行查看、修改等操作\n\n\n\n### 用户组\n\n将 “其他用户” 区分为用户组成员和其他人后，若文件所有者希望对部分用户开放权限，而对其他人继续保持私有，则只需要将这部分用户与文件所有者划入一个用户组\n\n这样，这部分用户就成了与文件所有者同组的用户组成员。用户可以对用户组成员开放文件权限，用户组成员则具备了查看、修改文件的权限，而对其他无关用户保持私有\n\n例如，团队成员之间保持文件资源共享，但对非团队成员保持私有，这就需要将文件所有者与团队成员用户划分为同一个用户组，再对用户组成员开放权限即可\n\n\n\n### 其他人\n\n既与文件所有者没有任何联系的其他用户\n\n\n\n### 小结\n\n户和用户组的对应关系是：一对一、多对一、一对多或多对多：\n\n- 一对一：某个用户可以是某个组的唯一成员\n- 多对一：多个用户可以是某个唯一的组的成员，不归属其它用户组\n- 一对多：某个用户可以是多个用户组的成员\n- 多对多：多个用户对应多个用户组，并且几个用户可以是归属相同的组\n\n\n\n### 拓展\n\n当我们使用`ls -l`的时候，会列出当前目录的文件信息，如下：\n\n```cmd\ndrwxr-xr-x   3  osmond   osmond    4096  05-16 13:32   nobp\n```\n\n- d：文件类型\n- rwxr-xr-x：文件权限\n- 3 硬链接数或目录包含的文件数\n- osmond：文件所有者\n- 4096：文件长度\n- 05-16 13:32：文件上次修改的事件和日期\n- nobp：文件名\n\n下面主要看看文件权限分析，实际上是由9个字符组成，每3个一组：\n\n- 第一组控制文件**所有者**的访问权限\n- 第二组控制所有者**所在用户组**的其他成员的访问权限\n- 第三组控制**系统其他用户**的访问权限\n\n`-`代表当前没有，`rwx`对应代表的意思如下：\n\n ![](https://static.vue-js.com/9ac2cf60-0417-11ec-8e64-91fdec0f05a1.png)\n\n\n\n\n\n### 三、用户操作\n\n\n\n用户相关的操作有如下：\n\n### 新增用户\n\n`useradd` 可以用来创建新用户，简要语法为：\n\n```text\nuseradd [options] [username]\n```\n\n例如：\n\n添加一个一般用户\n\n```\n# useradd kk //添加用户kk\n```\n\n为添加的用户指定相应的用户组\n\n```\n# useradd -g root kk //添加用户kk，并指定用户所在的组为root用户组\n```\n\n创建一个系统用户\n\n```\n# useradd -r kk //创建一个系统用户kk\n```\n\n为新添加的用户指定/home目录\n\n```\n# useradd-d /home/myf kk //新添加用户kk，其home目录为/home/myf\n//当用户名kk登录主机时，系统进入的默认目录为/home/myf\n```\n\n\n\n## 设置密码\n\n 创建的用户还没有设置登录密码，需要利用`passwd`进行密码设置\n\n```text\nasswd [options] [username]\n```\n\n`option` 参数有如下：\n\n- -d 删除密码\n- -f 强迫用户下次登录时必须修改口令\n- -w 口令要到期提前警告的天数\n- -k 更新只能发送在过期之后\n- -l 停止账号使用\n- -S 显示密码信息\n- -u 启用已被停止的账户\n- -x 指定口令最长存活期\n- -g 修改群组密码\n- 指定口令最短存活期\n- -i 口令过期后多少天停用账户\n\n例如，修改用户密码\n\n```\n# passwd runoob  //设置runoob用户的密码\nEnter new UNIX password:  //输入新密码，输入的密码无回显\nRetype new UNIX password:  //确认密码\npasswd: password updated successfully\n# \n```\n\n显示账号密码信息\n\n```\n# passwd -S runoob\nrunoob P 05/13/2010 0 99999 7 -1\n```\n\n删除用户密码\n\n```\n# passwd -d lx138 \npasswd: password expiry information changed.\n```\n\n\n\n### 修改用户\n\n`chage` 命令用来修改与用户密码相关的过期信息，如密码失效日、密码最短保留天数、失效前警告天数等\n\n```text\nchage [option] [username]\n```\n\n常见的参数有：\n\n- -d：指定密码最后修改日期\n\n- -E：密码到期的日期\n\n- -l：列出用户以及密码的有效期\n\n- -m：密码能够更改的最小天数\n- -M：密码保持有效的最大天数\n\n\n\n\n\n### 删除用户\n\nuserdel 命令用来删除用户的相关的所有数据。\n\n```text\nuserdel [options] [username]\n```\n\n常见的参数有：\n\n- -r：删除用户登入目录以及目录中所有文件\n\n例如删除用户账号\n\n```\n# userdel hnlinux\n```\n\n\n\n\n\n\n\n用户组相关的操作如下：\n\n### 新增用户组\n\n`groupadd`用于创建一个新的工作组，新工作组的信息将被添加到系统文件中\n\n```text\ngroupadd [options] [groupname]\n```\n\n常见的参数有如下：\n\n- -g：指定新建工作组的 id；\n- -r：创建系统工作组，系统工作组的组ID小于 500\n- -K：覆盖配置文件 \"/ect/login.defs\"\n- -o：允许添加组 ID 号不唯一的工作组\n- -f,--force: 如果指定的组已经存在，此选项将失明了仅以成功状态退出\n\n例如创建一个新的组，并添加组 ID。\n\n```\n＃groupadd －g 344 runoob\n```\n\n\n\n\n\n### 修改用户\n\n`groupmod `命令用来修改 `group `相关的参数，例如群组识别码或者名称\n\n```text\ngroupmod [options] [groupname]\n```\n\n常见的参数有：\n\n- -g <群组识别码> 　设置欲使用的群组识别码\n- -o 　重复使用群组识别码\n- -n <新群组名称> 　设置欲使用的群组名\n\n例如修改组名：\n\n```\n# groupmod -n linux linuxso \n```\n\n\n\n\n\n### 删除用户组\n\n`groupdel` 用于删除用户组，如果该群组中仍包括某些用户，则必须先删除这些用户后，方能删除群组\n\n```text\ngroupdel [groupname]\n```\n\n\n\n日常工作通常会碰到只有` root `用户才有权限执行的操作，这就需要使用用户身份切换的命令：\n\n### su\n\n用于变更为其他使用者的身份，除 `root` 外，需要键入该使用者的密码\n\n\n\n\n\n\n\n### sudo\n\n`sudo`命令以系统管理者的身份执行指令，也就是说，经由 sudo 所执行的指令就好像是 root 亲自执行\n\n不是所有的用户都能执行 `sudo` 命令的，而是在 `/etc/sudoers` 文件内的用户才能执行这个命令\n\n例如`sudo`命令使用`ls`：\n\n```\n$ sudo ls\n```\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/37964411\n- https://zhuanlan.zhihu.com/p/105482468"
  },
  {
    "path": "docs/linux/linux.md",
    "content": "# 面试官：说说你对操作系统的理解？核心概念有哪些？\n\n![](https://static.vue-js.com/0f06bf30-008a-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n操作系统（Operating System，缩写：OS）是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序，同时也是计算机系统的内核与基石\n\n简单来讲，操作系统就是一种复杂的软件，相当于软件管家\n\n操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务，\n\n操作系统的类型非常多样，不同机器安装的操作系统可从简单到复杂，可从移动电话的嵌入式系统到超级电脑的大型操作系统，在计算机与用户之间起接口的作用，如下图：\n\n ![](https://static.vue-js.com/0ad1b850-009b-11ec-8e64-91fdec0f05a1.png)\n\n许多操作系统制造者对它涵盖范畴的定义也不尽一致，例如有些操作系统集成了图形用户界面，而有些仅使用命令行界面，将图形用户界面视为一种非必要的应用程序\n\n\n\n\n## 二、核心概念\n\n操作系统的核心概念都是对具体物理硬件的抽象，主要有如下：\n\n- 进程（线程）：进程（线程）是操作系统对CPU的抽象\n- 虚拟内存（地址空间）：虚拟内存是操作系统对物理内存的抽象\n- 文件：文件是操作系统对物理磁盘的抽象\n- shell：它是一个程序，可从键盘获取命令并将其提供给操作系统以执行。\n- GUI ：是一种用户界面，允许用户通过图形图标和音频指示符与电子设备进行交互\n- 计算机架构(computer architecture)： 在计算机工程中，计算机体系结构是描述计算机系统功能，组织和实现的一组规则和方法。它主要包括指令集、内存管理、I/O 和总线结构\n- 多处理系统(Computer multitasking)：是指计算机同时运行多个程序的能力\n- 程序计数器(Program counter)：程序计数器 是一个 CPU 中的寄存器，用于指示计算机在其程序序列中的位置\n- 多线程(multithreading)：是指从软件或者硬件上实现多个线程并发执行的技术\n\n- CPU 核心(core)：它是 CPU 的大脑，它接收指令，并执行计算或运算以满足这些指令。一个 CPU 可以有多个内核\n- 图形处理器(Graphics Processing Unit)：又称显示核心、视觉处理器、显示芯片或绘图芯片\n- 缓存命中(cache hit)：当应用程序或软件请求数据时，会首先发生缓存命中\n\n- RAM((Random Access Memory)：随机存取存储器，也叫主存，是与 CPU 直接交换数据的内部存储器\n\n- ROM (Read Only Memory)：只读存储器是一种半导体存储器，其特性是一旦存储数据就无法改变或删除\n\n- 虚拟地址(virtual memory)： 虚拟内存是计算机系统内存管理的一种机制\n\n- 驱动程序(device driver)：设备驱动程序，简称驱动程序（driver），是一个允许高级别电脑软件与硬件交互的程序\n\n- USB(Universal Serial Bus)：是连接计算机系统与外部设备的一种串口总线标准，也是一种输入输出接口的技术规范\n\n- 地址空间(address space)：地址空间是内存中可供程序或进程使用的有效地址范\n\n- 进程间通信(interprocess communication)： 指至少两个进程或线程间传送数据或信号的一些技术或方法\n\n- 目录(directory)： 在计算机或相关设备中，一个目录或文件夹就是一个装有数字文件系统的虚拟容器\n\n- 路径(path name)： 路径是一种电脑文件或目录的名称的通用表现形式，它指向文件系统上的一个唯一位置。\n- 根目录(root directory)：根目录指的就是计算机系统中的顶层目录，比如 Windows 中的 C 盘和 D 盘，Linux 中的 /\n- 工作目录(Working directory)：它是一个计算机用语。用户在操作系统内所在的目录，用户可在此目录之下，用相对文件名访问文件。\n- 文件描述符(file descriptor)： 文件描述符是计算机科学中的一个术语，是一个用于表述指向文件的引用的抽象化概念\n- 客户端(clients)：客户端是访问服务器提供的服务的计算机硬件或软件。\n- 服务端(servers)： 在计算中，服务器是为其他程序或设备提供功能的计算机程序或设备\n\n\n\n## 三、总结\n\n- 操作系统是管理计算机硬件与软件资源的程序，是计算机的基石\n- 操作系统本质上是一个运行在计算机上的软件程序 ，用于管理计算机硬件和软件资源\n- 操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人，统筹着各种相关事项\n- 操作系统的内核（Kernel）是操作系统的核心部分，它负责系统的内存管理，硬件设备的管理，文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁，决定着系统的性能和稳定性\n\n\n\n## 参考文献\n\n- https://www.cnblogs.com/cxuanBlog/p/13297199.html\n- https://www.cnblogs.com/cxuanblog/p/12607608.html\n- https://www.anvilliu.com/2021/03/06/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E2%80%94%E2%80%94%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/"
  },
  {
    "path": "docs/linux/redirect_pipe.md",
    "content": "# 面试官：说说你对输入输出重定向和管道的理解？应用场景？\n\n\n\n ![](https://static.vue-js.com/1036dde0-0634-11ec-a752-75723a64e8f5.png)\n\n## 一、是什么\n\n`linux`中有三种标准输入输出，分别是`STDIN`，`STDOUT`，`STDERR`，对应的数字是0、1、2：\n\n- STDIN 是标准输入，默认从键盘读取信息\n- STDOUT 是标准输出，默认将输出结果输出至终端\n- STDERR 是标准错误，默认将输出结果输出至终端\n\n对于任何`linux`命令的执行会有下面的过程：\n\n ![](https://static.vue-js.com/1a57caf0-0634-11ec-8e64-91fdec0f05a1.png)\n\n一条命令的执行需要键盘等的标准输入，命令的执行和正确或错误，其中的每一个双向箭头就是一个通道，所以数据流可以流入到文件端（**重定向或管道**）\n\n简单来讲，重定向就是把本来要显示在终端的命令结果，输送到别的地方，分成：\n\n- 输入重定向：流出到屏幕如果命令所需的输入不是来自键盘，而是来自指定的文件\n- 输出重定向：命令的输出可以不显示在屏幕，而是写在指定的文件中\n\n管道就是把两个命令连接起来使用，一个命令的输出作为另一个命令的输入\n\n两者的区别在于：\n\n- 管道触发两个子进程，执行 | 两边的程序；而重定向是在一个进程内执行。\n- 管道两边都是shell命令\n- 重定向符号的右边只能是Linux文件\n- 重定向符号的优先级大于管道\n\n\n\n## 二、命令\n\n重定向常见的命令符号有：\n\n- \\> ： 输出重定向到一个文件或设备 覆盖原来的文件\n\n> 如果该文件不存在，则新建一个文件\n>\n> 如果该文件已经存在，会把文件内容覆盖\n>\n> 这些操纵不会征用用户的确认\n\n- \\>> ：输出重定向到一个文件或设备，但是是 追加原来的文件的末尾\n- <  ：用于制定命令的输入\n- << ：从键盘的输入重定向为某个命令的输入\n\n> 以逐行输入的模式（回车键进行换行）\n>\n> 所有输入的行都将在输入结束字符串之后发送给命令\n\n- 2> 将一个标准错误输出重定向到一个文件或设备，会覆盖原来的文件\n- 2>> 将一个标准错误输出重定向到一个文件或设备，是追加到原来的文件\n- 2>&1：组合符号，将标准错误输出重定向到标准输出相同的地方\n\n> 1就是代表标准输出\n\n- \\>& 将一个标准错误输出重定向到一个文件或设备覆盖原来的文件\n- |& 将一个标准错误管道输出到另一个命令作为输入\n\n\n\n## 三、应用场景\n\n将当前目录的文件输出重定向到`1.txt`文件中，并且会清空原有的`1.txt`的内容\n\n```cmd\nls -a > 1.txt\n```\n\n或者以追加的形式，重定向输入到`1.txt`中\n\n```cmd\nls -a >> 1.txt\n```\n\n将标准错误输出到某个文件，可以如下：\n\n```cmd\n$ touch 2> 2.txt\n$ cat 2.txt\ntouch: 缺少了文件操作数\n请尝试执行 \"touch --help\" 来获取更多信息。\n```\n\n通过组合符号将两者结合一起，无论进程输出的信息是正确还是错误的信息，都会重定向到指定的文件里\n\n```cmd\n[root@linguanghui home]# abc &> file.txt\n[root@linguanghui home]# cat file.txt \n-bash: abc: command not found\n```\n\n再者通过管道查询文件内容是否包含想要的信息：\n\n```cmd\ncat test.txt | grep -n 'xxx'\n```\n\n上述`cat test.txt`会将`test.txt`的内容作为标准输出，然后利用管道，将其作为`grep -n 'xxx'`命令的标准输入。\n\n\n\n### 参考文献\n\n- https://segmentfault.com/a/1190000020519335\n- https://murphypei.github.io/blog/2018/04/linux-redirect-pipe\n- https://www.huaweicloud.com/articles/0fb70e8c724ae79f4fc8d676cd6160d3.html"
  },
  {
    "path": "docs/linux/shell.md",
    "content": "# 面试官：说说你对 shell 的理解？常见的命令？\n\n ![](https://static.vue-js.com/71003620-0883-11ec-a752-75723a64e8f5.png)\n\n\n## 一、是什么\n\n `Shell `是一个由`c`语言编写的应用程序，它是用户使用 Linux 的桥梁。Shell 既是一种命令语言，又是一种程序设计语言\n\n它连接了用户和` Linux `内核，让用户能够更加高效、安全、低成本地使用 `Linux` 内核\n\n其本身并不是内核的一部分，它只是站在内核的基础上编写的一个应用程序，它和 QQ、微信等其它软件没有什么区别，特殊的地方就是开机立马启动，并呈现在用户面前\n\n主要作用是接收用户输入的命令，并对命令进行处理，处理完毕后再将结果反馈给用户，比如输出到显示器、写入到文件等，同样能够调用和组织其他的应用程序，相当于一个领导者的身份，如下图：\n\n ![](https://static.vue-js.com/80db0ca0-0883-11ec-8e64-91fdec0f05a1.png)\n\n那么`shell`脚本就是多个 `Shell` 命令的组合并通过 `if` 条件分支控制或循环来组合运算，实现一些复杂功能，文件后缀名为`.sh`\n\n常用的 `ls` 命令，它本身也是一个 `Shell` 脚本，通过执行这个 `Shell` 脚本可以列举当前目录下的文件列表，如下创建一个`hello.sh`脚本\n\n```shell\n#!/bin/bash\n\n# 执行的命令主体\nls\necho \"hello world\"\n```\n\n- #!/bin/bash ：指定脚本要使用的 Shell  类型为 Bash\n\n- ls、echo： 脚本文件的内容，表明我们执行  hello.sh  脚本时会列举出当前目录的文件列表并且会向控制台打印 `hello world\n\n执行方式为`.hello.zsh`\n\n\n\n## 二、种类\n\n`Linux` 的 `Shell` 种类众多，只要能给用户提供命令行环境的程序，常见的有：\n\n- Bourne Shell（sh），是目前所有 Shell 的祖先，被安装在几乎所有发源于 Unix 的操作系统上\n\n- Bourne Again shell（bash） ，是 sh 的一个进阶版本，比 sh 更优秀， bash 是目前大多数 Linux 发行版以及 macOS 操作系统的默认 Shell\n\n- C Shell（csh） ，它的语法类似 C 语言\n\n- TENEX C Shell（tcsh） ，它是 csh 的优化版本\n\n- Korn shell（ksh） ，一般在收费的 Unix 版本上比较多见\n\n- Z Shell（zsh） ，它是一种比较新近的 Shell ，集 bash 、 ksh 和 tcsh 各家之大成\n\n![](https://static.vue-js.com/8e739440-0883-11ec-a752-75723a64e8f5.png)\n\n关于 `Shell` 的几个常见命令：\n\n- ls：查看文件\n- cd：切换工作目录\n- pwd：显示用户当前目录\n- mkdir：创建目录\n- cp：拷贝\n- rm：删除\n- mv：移动\n- du：显示目录所占用的磁盘空间\n\n## 三、命令\n\n`Shell` 并不是简单的堆砌命令，我们还可以在 `Shell` 中编程，这和使用 `C++`、`C#`、`Java`、`Python` 等常见的编程语言并没有什么两样。\n\nShell 虽然没有 C++、Java、Python 等强大，但也支持了基本的编程元素，例如：\n\n- if...else 选择结构，case...in 开关语句，for、while、until 循环；\n- 变量、数组、字符串、注释、加减乘除、逻辑运算等概念；\n- 函数，包括用户自定义的函数和内置函数（例如 printf、export、eval 等）\n\n\n\n下面以`bash`为例简单了解一下`shell`的基本使用\n\n### 变量\n\n`Bash` 没有数据类型的概念，所有的变量值都是字符串，可以保存一个数字、一个字符、一个字符串等等\n\n同时无需提前声明变量，给变量赋值会直接创建变量\n\n访问变量的语法形式为：`${var}` 和 `$var` 。\n\n变量名外面的花括号是可选的，加不加都行，加花括号是为了帮助解释器识别变量的边界，所以推荐加花括号。\n\n```bash\nword=\"hello\"\necho ${word}\n# Output: hello\n```\n\n\n\n### 条件控制\n\n跟其它程序设计语言一样，Bash 中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在`[[ ]]`里的表达式\n\n跟其他语言一样，使用`if...else`进行表达，如果中括号里的表达式为真，那么`then`和`fi`之间的代码会被执行，如果则`else`和`fi`之间的代码会被执行\n\n```shell\nif [[ 2 -ne 1 ]]; then\n  echo \"true\"\nelse\n  echo \"false\"\nfi\n# Output: true\n```\n\n`fi`标志着条件代码块的结束\n\n\n\n### 函数\n\nbash 函数定义语法如下：\n\n```bash\n[ function ] funname [()] {\n    action;\n    [return int;]\n}\n```\n\n- 函数定义时，function 关键字可有可无\n- 函数返回值 - return 返回函数返回值，返回值类型只能为整数（0-255）。如果不加 return 语句，shell 默认将以最后一条命令的运行结果，作为函数返回值\n- 函数返回值在调用该函数后通过 $?  来获得\n- 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分，直至 shell 解释器首次发现它时，才可以使用。调用函数仅使用其函数名即可\n\n## 参考文献\n\n- http://c.biancheng.net/view/706.html\n- https://juejin.cn/post/6930013333454061575"
  },
  {
    "path": "docs/linux/thread_process.md",
    "content": "# 面试官：说说什么是进程？什么是线程？区别？\n\n \n\n![](https://static.vue-js.com/f414d8a0-02f6-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、进程\n\n操作系统中最核心的概念就是进程，进程是对正在运行中的程序的一个抽象，是系统进行资源分配和调度的基本单位\n\n操作系统的其他所有内容都是围绕着进程展开的，负责执行这些任务的是`CPU`\n\n ![](https://static.vue-js.com/3ff146b0-02f6-11ec-8e64-91fdec0f05a1.png)\n\n\n\n进程是一种抽象的概念，从来没有统一的标准定义看，一般由程序、数据集合和进程控制块三部分组成：\n\n- 程序用于描述进程要完成的功能，是控制进程执行的指令集\n- 数据集合是程序在执行时所需要的数据和工作区\n- 程序控制块，包含进程的描述信息和控制信息，是进程存在的唯一标志\n\n\n## 二、线程\n\n**线程**（thread）是操作系统能够进行**运算调度**的最小单位，其是进程中的一个执行任务（控制单元），负责当前进程中程序的执行\n\n一个进程至少有一个线程，一个进程可以运行多个线程，这些线程共享同一块内存，线程之间可以共享对象、资源，如果有冲突或需要协同，还可以随时沟通以解决冲突或保持同步\n\n举个例子，假设你经营着一家物业管理公司。最初，业务量很小，事事都需要你亲力亲为。给老张家修完暖气管道，立马再去老李家换电灯泡——这叫单线程，所有的工作都得顺序执行\n\n后来业务拓展了，你雇佣了几个工人，这样，你的物业公司就可以同时为多户人家提供服务了——这叫多线程，你是主线程\n\n ![](https://static.vue-js.com/63de34c0-02f6-11ec-a752-75723a64e8f5.png)\n\n但实际上，并不是线程越多，进程的工作效率越高，这是因为在一个进程内，不管你创建了多少线程，它们总是被限定在一颗`CPU`内，或者多核`CPU`的一个核内\n\n这意味着，多线程在宏观上是并行的，在微观上则是分时切换串行的，多线程编程无法充分发挥多核计算资源的优势\n\n这导致使用多线程做任务并行处理时，线程数量超过一定数值后，线程越多速度反倒越慢的原因\n\n\n\n## 三、区别\n\n- **本质区别**：进程是操作系统资源分配的基本单位，而线程是任务调度和执行的基本单位\n\n- **在开销方面**：每个进程都有独立的代码和数据空间（程序上下文），程序之间的切换会有较大的开销；线程可以看做轻量级的进程，同一类线程共享代码和数据空间，每个线程都有自己独立的运行栈和程序计数器（PC），线程之间切换的开销小\n\n- **所处环境**：在操作系统中能同时运行多个进程（程序）；而在同一个进程（程序）中有多个线程同时执行（通过CPU调度，在每个时间片中只有一个线程执行）\n\n- **内存分配方面**：系统在运行的时候会为每个进程分配不同的内存空间；而对线程而言，除了CPU外，系统不会为线程分配内存（线程所使用的资源来自其所属进程的资源），线程组之间只能共享资源\n\n- **包含关系**：没有线程的进程可以看做是单线程的，如果一个进程内有多个线程，则执行过程不是一条线的，而是多条线（线程）共同完成的；线程是进程的一部分，所以线程也被称为轻权进程或者轻量级进程\n\n\n举个例子：进程=火车，线程=车厢\n\n- 线程在进程下行进（单纯的车厢无法运行）\n- 一个进程可以包含多个线程（一辆火车可以有多个车厢）\n- 不同进程间数据很难共享（一辆火车上的乘客很难换到另外一辆火车，比如站点换乘）\n- 同一进程下不同线程间数据很易共享（A车厢换到B车厢很容易）\n- 进程要比线程消耗更多的计算机资源（采用多列火车相比多个车厢更耗资源）\n- 进程间不会相互影响，一个线程挂掉将导致整个进程挂掉（一列火车不会影响到另外一列火车，但是如果一列火车上中间的一节车厢着火了，将影响到所有车厢）\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/106283969\n- https://blog.csdn.net/ThinkWAon/article/details/102021274\n- https://www.zhihu.com/question/25532384"
  },
  {
    "path": "docs/linux/vim.md",
    "content": "# 面试官：说说 linux 系统下 文本编辑常用的命令有哪些？\n\n ![](https://static.vue-js.com/1062b8b0-049b-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n`Vim`是从 `vi` 发展出来的一个文本编辑器，代码补全、编译及错误跳转等方便编程的功能特别丰富，在程序员中被广泛使用。\n\n简单的来说， `vi` 是老式的字处理器，不过功能已经很齐全了，但是还是有可以进步的地方\n\n而`vim `可以说是程序开发者的一项很好用的工具\n\n\n\n## 二、使用\n\n基本上 vi/vim 共分为三种模式，分别是：\n\n- 命令模式（Command mode）\n- 输入模式（Insert mode）\n- 底线命令模式（Last line mode）\n\n ![](https://static.vue-js.com/265a0080-03d6-11ec-a752-75723a64e8f5.png)\n\n\n\n### 命令模式\n\n`Vim` 的默认模式，在这个模式下，你不能输入文本，但是可以让我们在文本间移动，删除一行文本，复制黏贴文本，跳转到指定行，撤销操作，等等\n\n\n\n#### 移动光标\n\n常用的命令如下：\n\n- h 向左移动一个字符\n- j 向下移动一个字符\n- k 向上移动一个字符\n- i 向右移动一个字符\n\n或者使用方向键进行控制\n\n如果想要向下移动`n`行，可通过使用 \"nj\" 或 \"n↓\" 的组合按键\n\n\n\n#### 搜索\n\n常见的命令如下：\n\n- /word：向光标之下寻找一个名称为 word 的字符\n\n- ?word：向光标之上寻找一个字符串名称为 word 的字符串\n- n：代表重复前一个搜寻的动作，即再次执行上一次的操作\n- N：反向进行前一个搜索动作\n\n\n\n\n\n#### 删除、复制、粘贴\n\n常用的命令如下：\n\n- x：向后删除一个字符\n- X：向前删除一个字符\n- nc：n 为数字，连续向后删除 n 个字符\n- dd：删除游标所在的那一整行\n- d0：删除游标所在处，到该行的最前面一个字符\n- d$删除游标所在处，到该行的最后一个字符\n- ndd：除光标所在的向下 n 行\n- yy：复制游标所在的那一行\n- y0：复制光标所在的那个字符到该行行首的所有数据\n- y$：复制光标所在的那个字符到该行行尾的所有数据\n- p：已复制的数据在光标下一行贴上\n- P：已复制的数据在光标上一行贴上\n- nc：重复删除n行数据\n\n\n\n### 输入模式\n\n命令模式通过输入大小写`i`、`a`、`o`可以切换到输入模式，如下：\n\n- i：从目前光标所在处输入\n- I：在目前所在行的第一个非空格符处开始输入\n- a：从目前光标所在的下一个字符处开始输入\n- A：从光标所在行的最后一个字符处开始输入\n- o：在目前光标所在的下一行处输入新的一行\n- O：目前光标所在的上一行处输入新的一行\n\n输入模式我们熟悉的文本编辑器的模式，就是可以输入任何你想输入的内容\n\n如果想从插入模式回到命令模式，使用按下键盘左上角的`ESC`键\n\n\n\n\n\n### 底线命令模式\n\n这个模式下可以运行一些命令例如“退出”，“保存”，等动作，为了进入底线命令模式，首先要进入命令模式，再按下冒号键：\n\n常见的命令如下：\n\n- w：将编辑的数据写入硬盘档案中\n- w!：若文件属性为『只读』时，强制写入该档案\n- q：未修改，直接退出\n- q!：修改过但不存储\n- wq：储存后离开\n\n\n\n## 参考文献\n\n- https://www.runoob.com/linux/linux-vim.html"
  },
  {
    "path": "docs/typescript/class.md",
    "content": "# 面试官：说说你对 TypeScript 中类的理解？应用场景？\n\n ![](https://static.vue-js.com/e4c19060-0cb4-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n类（Class）是面向对象程序设计（OOP，Object-Oriented Programming）实现信息封装的基础\n\n> 类是一种用户定义的引用数据类型，也称类类型\n\n传统的面向对象语言基本都是基于类的，`JavaScript` 基于原型的方式让开发者多了很多理解成本\n\n在 `ES6` 之后，`JavaScript` 拥有了 `class` 关键字，虽然本质依然是构造函数，但是使用起来已经方便了许多\n\n但是` JavaScript` 的` class `依然有一些特性还没有加入，比如修饰符和抽象类\n\n`TypeScript` 的 `class`  支持面向对象的所有特性，比如 类、接口等\n\n\n\n## 二、使用方式\n\n定义类的关键字为 `class`，后面紧跟类名，类可以包含以下几个模块（类的数据成员）：\n\n- **字段** ： 字段是类里面声明的变量。字段表示对象的有关数据。\n- **构造函数**： 类实例化时调用，可以为类的对象分配内存。\n- **方法**： 方法为对象要执行的操作\n\n如下例子：\n\n```ts\nclass Car {\n    // 字段\n    engine:string;\n\n    // 构造函数\n    constructor(engine:string) {\n        this.engine = engine\n    }\n\n    // 方法\n    disp():void {\n        console.log(\"发动机为 :   \"+this.engine)\n    }\n}\n```\n\n### 继承\n\n类的继承使用过`extends`的关键字\n\n```ts\nclass Animal {\n    move(distanceInMeters: number = 0) {\n        console.log(`Animal moved ${distanceInMeters}m.`);\n    }\n}\n\nclass Dog extends Animal {\n    bark() {\n        console.log('Woof! Woof!');\n    }\n}\n\nconst dog = new Dog();\ndog.bark();\ndog.move(10);\ndog.bark();\n```\n\n`Dog`是一个 派生类，它派生自 `Animal` 基类，派生类通常被称作子类，基类通常被称作 超类\n\n`Dog`类继承了`Animal`类，因此实例`dog`也能够使用`Animal`类`move`方法\n\n\n\n同样，类继承后，子类可以对父类的方法重新定义，这个过程称之为方法的重写，通过`super`关键字是对父类的直接引用，该关键字可以引用父类的属性和方法，如下：\n\n```ts\nclass PrinterClass {\n   doPrint():void {\n      console.log(\"父类的 doPrint() 方法。\")\n   }\n}\n\nclass StringPrinter extends PrinterClass {\n   doPrint():void {\n      super.doPrint() // 调用父类的函数\n      console.log(\"子类的 doPrint()方法。\")\n   }\n}\n```\n\n\n\n\n\n\n\n### 修饰符\n\n可以看到，上述的形式跟`ES6`十分的相似，`typescript`在此基础上添加了三种修饰符：\n\n- 公共 public：可以自由的访问类程序里定义的成员\n- 私有 private：只能够在该类的内部进行访问\n- 受保护 protect：除了在该类的内部可以访问，还可以在子类中仍然可以访问\n\n\n\n### 私有修饰符\n\n只能够在该类的内部进行访问，实例对象并不能够访问\n\n ![](https://static.vue-js.com/f57365f0-0cb4-11ec-a752-75723a64e8f5.png)\n\n并且继承该类的子类并不能访问，如下图所示：\n\n ![](https://static.vue-js.com/0072cc20-0cb5-11ec-8e64-91fdec0f05a1.png)\n\n\n\n### 受保护修饰符\n\n跟私有修饰符很相似，实例对象同样不能访问受保护的属性，如下：\n\n ![](https://static.vue-js.com/09e72580-0cb5-11ec-a752-75723a64e8f5.png)\n\n有一点不同的是 `protected` 成员在子类中仍然可以访问\n\n ![](https://static.vue-js.com/137f81a0-0cb5-11ec-8e64-91fdec0f05a1.png)\n\n\n\n\n\n除了上述修饰符之外，还有只读**修饰符**\n\n#### 只读修饰符\n\n通过`readonly`关键字进行声明，只读属性必须在声明时或构造函数里被初始化，如下：\n\n ![](https://static.vue-js.com/1e848d20-0cb5-11ec-8e64-91fdec0f05a1.png)\n\n\n\n除了实例属性之外，同样存在静态属性\n\n### 静态属性\n\n这些属性存在于类本身上面而不是类的实例上，通过`static`进行定义，访问这些属性需要通过 类型.静态属性 的这种形式访问，如下所示：\n\n```ts\nclass Square {\n    static width = '100px'\n}\n\nconsole.log(Square.width) // 100px\n```\n\n\n\n上述的类都能发现一个特点就是，都能够被实例化，在 `typescript`中，还存在一种抽象类\n\n\n\n### 抽象类\n\n抽象类做为其它派生类的基类使用，它们一般不会直接被实例化，不同于接口，抽象类可以包含成员的实现细节\n\n`abstract `关键字是用于定义抽象类和在抽象类内部定义抽象方法，如下所示：\n\n```ts\nabstract class Animal {\n    abstract makeSound(): void;\n    move(): void {\n        console.log('roaming the earch...');\n    }\n}\n```\n\n这种类并不能被实例化，通常需要我们创建子类去继承，如下：\n\n```ts\nclass Cat extends Animal {\n\n    makeSound() {\n        console.log('miao miao')\n    }\n}\n\nconst cat = new Cat()\n\ncat.makeSound() // miao miao\ncat.move() // roaming the earch...\n```\n\n\n\n## 三、应用场景\n\n除了日常借助类的特性完成日常业务代码，还可以将类（class）也可以作为接口，尤其在 `React` 工程中是很常用的，如下：\n\n```ts\nexport default class Carousel extends React.Component<Props, State> {}\n```\n\n由于组件需要传入 `props` 的类型 `Props` ，同时有需要设置默认 `props` 即 `defaultProps`，这时候更加适合使用`class`作为接口\n\n先声明一个类，这个类包含组件 `props` 所需的类型和初始值：\n\n```ts\n// props的类型\nexport default class Props {\n  public children: Array<React.ReactElement<any>> | React.ReactElement<any> | never[] = []\n  public speed: number = 500\n  public height: number = 160\n  public animation: string = 'easeInOutQuad'\n  public isAuto: boolean = true\n  public autoPlayInterval: number = 4500\n  public afterChange: () => {}\n  public beforeChange: () => {}\n  public selesctedColor: string\n  public showDots: boolean = true\n}\n```\n\n当我们需要传入 `props` 类型的时候直接将 `Props` 作为接口传入，此时 `Props` 的作用就是接口，而当需要我们设置`defaultProps`初始值的时候，我们只需要:\n\n```ts\npublic static defaultProps = new Props()\n```\n\n`Props` 的实例就是 `defaultProps` 的初始值，这就是 `class `作为接口的实际应用，我们用一个 `class` 起到了接口和设置初始值两个作用，方便统一管理，减少了代码量\n\n\n\n## 参考文献\n\n- https://www.tslang.cn/docs/handbook/classes.html\n- https://www.runoob.com/typescript/ts-class.html"
  },
  {
    "path": "docs/typescript/data_type.md",
    "content": "# 面试官：说说 typescript 的数据类型有哪些？\n\n ![](https://static.vue-js.com/d88f9450-0998-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n`typescript` 和 `javascript`几乎一样，拥有相同的数据类型，另外在`javascript`基础上提供了更加实用的类型供开发使用\n\n在开发阶段，可以为明确的变量定义为某种类型，这样`typescript`就能在编译阶段进行类型检查，当类型不合符预期结果的时候则会出现错误提示\n\n\n\n## 二、有哪些\n\n`typescript` 的数据类型主要有如下：\n\n- boolean（布尔类型）\n- number（数字类型）\n- string（字符串类型）\n- array（数组类型）\n- tuple（元组类型）\n- enum（枚举类型）\n- any（任意类型）\n- null 和 undefined 类型\n- void 类型\n- never 类型\n- object 对象类型\n\n\n\n### boolean\n\n布尔类型\n\n```tsx\nlet flag:boolean = true;\n// flag = 123; // 错误\nflag = false;  //正确\n```\n\n\n\n### number\n\n数字类型，和`javascript`一样，`typescript`的数值类型都是浮点数，可支持二进制、八进制、十进制和十六进制\n\n```tsx\nlet num:number = 123;\n// num = '456'; // 错误\nnum = 456;  //正确\n```\n\n进制表示：\n\n```tsx\nlet decLiteral: number = 6; // 十进制\nlet hexLiteral: number = 0xf00d; // 十六进制\nlet binaryLiteral: number = 0b1010; // 二进制\nlet octalLiteral: number = 0o744; // 八进制\n```\n\n\n\n### string\n\n字符串类型，和`JavaScript`一样，可以使用双引号（`\"`）或单引号（`'`）表示字符串\n\n```tsx\nlet str:string = 'this is ts';\nstr = 'test';\n```\n\n作为超集，当然也可以使用模版字符串``进行包裹，通过 ${} 嵌入变量\n\n```tsx\nlet name: string = `Gene`;\nlet age: number = 37;\nlet sentence: string = `Hello, my name is ${ name }\n```\n\n\n\n### array\n\n数组类型，跟`javascript`一致，通过`[]`进行包裹，有两种写法：\n\n方式一：元素类型后面接上 `[]`\n\n ```tsx\n  let arr:string[] = ['12', '23'];\n  arr = ['45', '56'];\n ```\n\n方式二：使用数组泛型，`Array<元素类型>`：\n\n  ```tsx\n  let arr:Array<number> = [1, 2];\n  arr = ['45', '56'];\n  ```\n\n\n\n### tuple\n\n元祖类型，允许表示一个已知元素数量和类型的数组，各元素的类型不必相同\n\n```tsx\nlet tupleArr:[number, string, boolean];\ntupleArr = [12, '34', true]; //ok\ntypleArr = [12, '34'] // no ok\n```\n\n赋值的类型、位置、个数需要和定义（生明）的类型、位置、个数一致\n\n\n\n### enum\n\n`enum`类型是对JavaScript标准数据类型的一个补充，使用枚举类型可以为一组数值赋予友好的名字\n\n```tsx\nenum Color {Red, Green, Blue}\nlet c: Color = Color.Green;\n```\n\n\n\n### any\n\n可以指定任何类型的值，在编程阶段还不清楚类型的变量指定一个类型，不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查，这时候可以使用`any`类型\n\n使用`any`类型允许被赋值为任意类型，甚至可以调用其属性、方法\n\n```tsx\nlet num:any = 123;\nnum = 'str';\nnum = true;\n```\n\n定义存储各种类型数据的数组时，示例代码如下：\n\n```tsx\nlet arrayList: any[] = [1, false, 'fine'];\narrayList[1] = 100;\n```\n\n\n\n\n\n### null 和 和 undefined\n\n在` JavaScript` 中 `null `表示 \"什么都没有\"，是一个只有一个值的特殊类型，表示一个空对象引用，而`undefined`表示一个没有设置值的变量\n\n默认情况下`null`和`undefined`是所有类型的子类型， 就是说你可以把 `null `和 `undefined `赋值给 `number `类型的变量\n\n```tsx\nlet num:number | undefined; // 数值类型 或者 undefined\nconsole.log(num); // 正确\nnum = 123;\nconsole.log(num); // 正确\n```\n\n但是`ts`配置了`--strictNullChecks`标记，`null`和`undefined`只能赋值给`void`和它们各自\n\n\n\n### void\n\n用于标识方法返回值的类型，表示该方法没有返回值。\n\n```tsx\nfunction hello(): void {\n    alert(\"Hello Runoob\");\n}\n```\n\n\n### never\n\n`never`是其他类型 （包括` null `和 `undefined`）的子类型，可以赋值给任何类型，代表从不会出现的值\n\n但是没有类型是 never 的子类型，这意味着声明 `never` 的变量只能被 `never` 类型所赋值。\n\n`never` 类型一般用来指定那些总是会抛出异常、无限循环\n\n```tsx\nlet a:never;\na = 123; // 错误的写法\n\na = (() => { // 正确的写法\n  throw new Error('错误');\n})()\n\n// 返回never的函数必须存在无法达到的终点\nfunction error(message: string): never {\n    throw new Error(message);\n}\n```\n\n\n\n\n\n### object\n\n对象类型，非原始类型，常见的形式通过`{}`进行包裹\n\n```tsx\nlet obj:object;\nobj = {name: 'Wang', age: 25};\n```\n\n\n\n## 三、总结\n\n和`javascript`基本一致，也分成：\n\n- 基本类型\n- 引用类型\n\n在基础类型上，`typescript`增添了`void`、`any`、`emum`等原始类型\n\n\n\n## 参考文献\n\n- https://www.tslang.cn/docs/handbook/basic-types.html"
  },
  {
    "path": "docs/typescript/decorator.md",
    "content": "# 面试官：说说你对 TypeScript 装饰器的理解？应用场景？\n\n ![](https://static.vue-js.com/f8905dd0-111c-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n装饰器是一种特殊类型的声明，它能够被附加到类声明，方法， 访问符，属性或参数上\n\n是一种在不改变原类和使用继承的情况下，动态地扩展对象功能\n\n同样的，本质也不是什么高大上的结构，就是一个普通的函数，`@expression` 的形式其实是`Object.defineProperty`的语法糖\n\n`expression `求值后必须也是一个函数，它会在运行时被调用，被装饰的声明信息做为参数传入\n\n\n## 二、使用方式\n\n由于`typescript`是一个实验性特性，若要使用，需要在`tsconfig.json`文件启动，如下：\n\n```ts\n{\n    \"compilerOptions\": {\n        \"target\": \"ES5\",\n        \"experimentalDecorators\": true\n    }\n}\n```\n\n`typescript`装饰器的使用和`javascript`基本一致\n\n\n\n类的装饰器可以装饰：\n\n- 类\n\n- 方法/属性\n- 参数\n\n- 访问器\n\n\n\n### 类装饰\n\n例如声明一个函数 `addAge` 去给 Class 的属性 `age` 添加年龄.\n\n```ts\nfunction addAge(constructor: Function) {\n  constructor.prototype.age = 18;\n}\n\n@addAge\nclass Person{\n  name: string;\n  age!: number;\n  constructor() {\n    this.name = 'huihui';\n  }\n}\n\nlet person = new Person();\n\nconsole.log(person.age); // 18\n```\n\n上述代码，实际等同于以下形式：\n\n```ts\nPerson = addAge(function Person() { ... });\n```\n\n上述可以看到，当装饰器作为修饰类的时候，会把构造器传递进去。 `constructor.prototype.age` 就是在每一个实例化对象上面添加一个 `age` 属性\n\n\n\n### 方法/属性装饰\n\n同样，装饰器可以用于修饰类的方法，这时候装饰器函数接收的参数变成了：\n\n- target：对象的原型\n- propertyKey：方法的名称\n- descriptor：方法的属性描述符\n\n可以看到，这三个属性实际就是`Object.defineProperty`的三个参数，如果是类的属性，则没有传递第三个参数\n\n如下例子：\n\n```ts\n// 声明装饰器修饰方法/属性\nfunction method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n  console.log(target);\n  console.log(\"prop \" + propertyKey);\n  console.log(\"desc \" + JSON.stringify(descriptor) + \"\\n\\n\");\n  descriptor.writable = false;\n};\n\nfunction property(target: any, propertyKey: string) {\n  console.log(\"target\", target)\n  console.log(\"propertyKey\", propertyKey)\n}\n\nclass Person{\n @property\n name: string;\n constructor() {\n   this.name = 'huihui';\n }\n\n @method\n say(){\n   return 'instance method';\n }\n\n @method\n static run(){\n   return 'static method';\n }\n}\n\nconst xmz = new Person();\n\n// 修改实例方法say\nxmz.say = function() {\n return 'edit'\n}\n```\n\n输出如下图所示：\n\n ![](https://static.vue-js.com/e96bc1b0-114d-11ec-8e64-91fdec0f05a1.png)\n\n\n\n### 参数装饰\n\n接收3个参数，分别是：\n\n- target ：当前对象的原型\n- propertyKey ：参数的名称\n- index：参数数组中的位置\n\n```ts\nfunction logParameter(target: Object, propertyName: string, index: number) {\n  console.log(target);\n  console.log(propertyName);\n  console.log(index);\n}\n\nclass Employee {\n  greet(@logParameter message: string): string {\n      return `hello ${message}`;\n  }\n}\nconst emp = new Employee();\nemp.greet('hello');\n```\n\n输入如下图：\n\n ![](https://static.vue-js.com/f2f32de0-114d-11ec-a752-75723a64e8f5.png)\n\n\n\n### 访问器装饰\n\n使用起来方式与方法装饰一致，如下：\n\n```ts\n\nfunction modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {\n  console.log(target);\n  console.log(\"prop \" + propertyKey);\n  console.log(\"desc \" + JSON.stringify(descriptor) + \"\\n\\n\");\n};\n\nclass Person{\n _name: string;\n constructor() {\n   this._name = 'huihui';\n }\n\n @modification\n get name() {\n   return this._name\n }\n}\n```\n\n\n\n\n\n### 装饰器工厂\n\n如果想要传递参数，使装饰器变成类似工厂函数，只需要在装饰器函数内部再函数一个函数即可，如下：\n\n```ts\nfunction addAge(age: number) {\n  return function(constructor: Function) {\n    constructor.prototype.age = age\n  }\n}\n\n@addAge(10)\nclass Person{\n  name: string;\n  age!: number;\n  constructor() {\n    this.name = 'huihui';\n  }\n}\n\nlet person = new Person();\n```\n\n\n\n### 执行顺序\n\n当多个装饰器应用于一个声明上，将由上至下依次对装饰器表达式求值，求值的结果会被当作函数，由下至上依次调用，例如如下：\n\n```ts\nfunction f() {\n    console.log(\"f(): evaluated\");\n    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {\n        console.log(\"f(): called\");\n    }\n}\n\nfunction g() {\n    console.log(\"g(): evaluated\");\n    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {\n        console.log(\"g(): called\");\n    }\n}\n\nclass C {\n    @f()\n    @g()\n    method() {}\n}\n\n// 输出\nf(): evaluated\ng(): evaluated\ng(): called\nf(): called\n```\n\n\n\n\n\n## 三、应用场景\n\n可以看到，使用装饰器存在两个显著的优点：\n\n- 代码可读性变强了，装饰器命名相当于一个注释\n- 在不改变原有代码情况下，对原来功能进行扩展\n\n后面的使用场景中，借助装饰器的特性，除了提高可读性之后，针对已经存在的类，可以通过装饰器的特性，在不改变原有代码情况下，对原来功能进行扩展\n\n\n\n## 参考文献\n\n- https://www.tslang.cn/docs/handbook/decorators.html\n- https://juejin.cn/post/6844903876605280269#heading-5"
  },
  {
    "path": "docs/typescript/enum.md",
    "content": "# 面试官：说说你对 TypeScript 中枚举类型的理解？应用场景？\n\n\n ![](https://static.vue-js.com/76173bf0-0b0c-11ec-a752-75723a64e8f5.png)\n\n\n\n## 一、是什么\n\n枚举是一个被命名的整型常数的集合，用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型\n\n通俗来说，枚举就是一个对象的所有可能取值的集合\n\n在日常生活中也很常见，例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就可以看成是一个枚举\n\n枚举的说明与结构和联合相似，其形式为：\n\n```txt\nenum 枚举名{\n    标识符①[=整型常数],\n    标识符②[=整型常数],\n    ...\n    标识符N[=整型常数],\n}枚举变量;\n```\n\n\n\n## 二、使用\n\n枚举的使用是通过`enum`关键字进行定义，形式如下：\n\n```ts\nenum xxx { ... }\n```\n\n声明关键字为枚举类型的方式如下：\n\n```ts\n// 声明d为枚举类型Direction\nlet d: Direction;\n```\n\n\n\n\n\n类型可以分成：\n\n- 数字枚举\n- 字符串枚举\n\n- 异构枚举\n\n\n\n### 数字枚举\n\n当我们声明一个枚举类型是,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从0开始依次累加:\n\n```ts\nenum Direction {\n    Up,   // 值默认为 0\n    Down, // 值默认为 1\n    Left, // 值默认为 2\n    Right // 值默认为 3\n}\n\nconsole.log(Direction.Up === 0); // true\nconsole.log(Direction.Down === 1); // true\nconsole.log(Direction.Left === 2); // true\nconsole.log(Direction.Right === 3); // true\n```\n\n如果我们将第一个值进行赋值后，后面的值也会根据前一个值进行累加1：\n\n```ts\nenum Direction {\n    Up = 10,\n    Down,\n    Left,\n    Right\n}\n\nconsole.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 10 11 12 13\n```\n\n\n\n\n\n### 字符串枚举\n\n```ts\n枚举类型的值其实也可以是字符串类型：\n\nenum Direction {\n    Up = 'Up',\n    Down = 'Down',\n    Left = 'Left',\n    Right = 'Right'\n}\n\nconsole.log(Direction['Right'], Direction.Up); // Right Up\n```\n\n如果设定了一个变量为字符串之后，后续的字段也需要赋值字符串，否则报错：\n\n```ts\nenum Direction {\n Up = 'UP',\n Down, // error TS1061: Enum member must have initializer\n Left, // error TS1061: Enum member must have initializer\n Right // error TS1061: Enum member must have initializer\n}\n```\n\n\n\n\n\n### 异构枚举\n\n即将数字枚举和字符串枚举结合起来混合起来使用，如下：\n\n```ts\nenum BooleanLikeHeterogeneousEnum {\n    No = 0,\n    Yes = \"YES\",\n}\n```\n\n通常情况下我们很少会使用异构枚举\n\n\n\n### 本质\n\n现在一个枚举的案例如下：\n\n```ts\nenum Direction {\n    Up,\n    Down,\n    Left,\n    Right\n}\n```\n\n通过编译后，`javascript`如下：\n\n```ts\nvar Direction;\n(function (Direction) {\n    Direction[Direction[\"Up\"] = 0] = \"Up\";\n    Direction[Direction[\"Down\"] = 1] = \"Down\";\n    Direction[Direction[\"Left\"] = 2] = \"Left\";\n    Direction[Direction[\"Right\"] = 3] = \"Right\";\n})(Direction || (Direction = {}));\n```\n\n上述代码可以看到， `Direction[Direction[\"Up\"] = 0] = \"Up\"`可以分成\n\n- Direction[\"Up\"] = 0\n- Direction[0] = \"Up\"\n\n所以定义枚举类型后，可以通过正反映射拿到对应的值，如下：\n\n```ts\nenum Direction {\n    Up,\n    Down,\n    Left,\n    Right\n}\n\nconsole.log(Direction.Up === 0); // true\nconsole.log(Direction[0]); // Up\n```\n\n并且多处定义的枚举是可以进行合并操作，如下：\n\n```ts\nenum Direction {\n    Up = 'Up',\n    Down = 'Down',\n    Left = 'Left',\n    Right = 'Right'\n}\n\nenum Direction {\n    Center = 1\n}\n```\n\n编译后，`js`代码如下：\n\n```js\nvar Direction;\n(function (Direction) {\n    Direction[\"Up\"] = \"Up\";\n    Direction[\"Down\"] = \"Down\";\n    Direction[\"Left\"] = \"Left\";\n    Direction[\"Right\"] = \"Right\";\n})(Direction || (Direction = {}));\n(function (Direction) {\n    Direction[Direction[\"Center\"] = 1] = \"Center\";\n})(Direction || (Direction = {}));\n```\n\n可以看到，`Direction`对象属性回叠加\n\n\n\n## 三、应用场景\n\n就拿回生活的例子，后端返回的字段使用 0 - 6 标记对应的日期，这时候就可以使用枚举可提高代码可读性，如下：\n\n```ts\nenum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};\n\nconsole.log(Days[\"Sun\"] === 0); // true\nconsole.log(Days[\"Mon\"] === 1); // true\nconsole.log(Days[\"Tue\"] === 2); // true\nconsole.log(Days[\"Sat\"] === 6); // true\n```\n\n包括后端日常返回0、1 等等状态的时候，我们都可以通过枚举去定义，这样可以提高代码的可读性，便于后续的维护\n\n\n## 参考文献\n- https://zh.wikipedia.org/wiki/%E6%9E%9A%E4%B8%BE\n- https://www.jianshu.com/p/b9e1caa4dd98\n- https://juejin.cn/post/6844904112669065224#heading-30"
  },
  {
    "path": "docs/typescript/function.md",
    "content": "# 面试官：说说你对 TypeScript 中函数的理解？与 JavaScript 函数的区别？\n\n\n\n ![](https://static.vue-js.com/3f1c1390-0d42-11ec-a752-75723a64e8f5.png)\n\n## 一、是什么\n\n函数是` JavaScript` 应用程序的基础，帮助我们实现抽象层、模拟类、信息隐藏和模块\n\n在` TypeScript` 里，虽然已经支持类、命名空间和模块，但函数仍然是主要定义行为的方式，`TypeScript` 为 `JavaScript` 函数添加了额外的功能，丰富了更多的应用场景\n\n函数类型在 `TypeScript` 类型系统中扮演着非常重要的角色，它们是可组合系统的核心构建块\n\n\n## 二、使用方式\n\n跟`javascript` 定义函数十分相似，可以通过`funciton` 关键字、箭头函数等形式去定义，例如下面一个简单的加法函数：\n\n```ts\nconst add = (a: number, b: number) => a + b\n```\n\n上述只定义了函数的两个参数类型，这个时候整个函数虽然没有被显式定义，但是实际上` TypeScript` 编译器是能够通过类型推断到这个函数的类型，如下图所示：\n\n ![](https://static.vue-js.com/4b3415b0-0d42-11ec-8e64-91fdec0f05a1.png)\n\n当鼠标放置在第三行`add`函数名的时候，会出现完整的函数定义类型，通过`:` 的形式来定于参数类型，通过 `=>` 连接参数和返回值类型\n\n当我们没有提供函数实现的情况下，有两种声明函数类型的方式，如下所示：\n\n```ts\n// 方式一\ntype LongHand = {\n  (a: number): number;\n};\n\n// 方式二\ntype ShortHand = (a: number) => number;\n```\n\n当存在函数重载时，只能使用方式一的形式\n\n\n\n### 可选参数\n\n当函数的参数可能是不存在的，只需要在参数后面加上 `?` 代表参数可能不存在，如下：\n\n```ts\nconst add = (a: number, b?: number) => a + (b ? b : 0)\n```\n\n这时候参数`b`可以是`number`类型或者`undefined`类型，即可以传一个`number`类型或者不传都可以\n\n\n\n### 剩余类型\n\n剩余参数与`JavaScript`的语法类似，需要用 `...` 来表示剩余参数\n\n如果剩余参数 `rest` 是一个由`number`类型组成的数组，则如下表示：\n\n```ts\nconst add = (a: number, ...rest: number[]) => rest.reduce(((a, b) => a + b), a)\n```\n\n\n\n### 函数重载\n\n允许创建数项名称相同但输入输出类型或个数不同的子程序，它可以简单地称为一个单独功能可以执行多项任务的能力\n\n关于`typescript`函数重载，必须要把精确的定义放在前面，最后函数实现时，需要使用 `|`操作符或者`?`操作符，把所有可能的输入类型全部包含进去，用于具体实现\n\n这里的函数重载也只是多个函数的声明，具体的逻辑还需要自己去写，`typescript`并不会真的将你的多个重名 `function `的函数体进行合并\n\n例如我们有一个add函数，它可以接收 `string`类型的参数进行拼接，也可以接收 `number` 类型的参数进行相加，如下：\n\n```ts\n// 上边是声明\nfunction add (arg1: string, arg2: string): string\nfunction add (arg1: number, arg2: number): number\n// 因为我们在下边有具体函数的实现，所以这里并不需要添加 declare 关键字\n\n// 下边是实现\nfunction add (arg1: string | number, arg2: string | number) {\n  // 在实现上我们要注意严格判断两个参数的类型是否相等，而不能简单的写一个 arg1 + arg2\n  if (typeof arg1 === 'string' && typeof arg2 === 'string') {\n    return arg1 + arg2\n  } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {\n    return arg1 + arg2\n  }\n}\n```\n\n\n\n## 三、区别\n\n从上面可以看到：\n\n- 从定义的方式而言，typescript 声明函数需要定义参数类型或者声明返回值类型\n- typescript 在参数中，添加可选参数供使用者选择\n- typescript 增添函数重载功能，使用者只需要通过查看函数声明的方式，即可知道函数传递的参数个数以及类型\n\n## 参考文献\n\n- https://www.tslang.cn/docs/handbook/functions.html\n- https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD\n- https://jkchao.github.io/typescript-book-chinese/typings/functions.html#%E9%87%8D%E8%BD%BD"
  },
  {
    "path": "docs/typescript/generic.md",
    "content": "# 面试官：说说你对 TypeScript 中泛型的理解？应用场景？\n\n\n ![](https://static.vue-js.com/5bb5f1d0-0e17-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n泛型程序设计（generic programming）是程序设计语言的一种风格或范式\n\n泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型，在实例化时作为参数指明这些类型\n在`typescript`中，定义函数，接口或者类的时候，不预先定义好具体的类型，而在使用的时候在指定类型的一种特性\n\n假设我们用一个函数，它可接受一个 `number` 参数并返回一个` number` 参数，如下写法：\n\n```ts\nfunction returnItem (para: number): number {\n    return para\n}\n```\n\n如果我们打算接受一个 `string` 类型，然后再返回 `string`类型，则如下写法：\n\n```ts\nfunction returnItem (para: string): string {\n    return para\n}\n```\n\n上述两种编写方式，存在一个最明显的问题在于，代码重复度比较高\n\n虽然可以使用 `any`类型去替代，但这也并不是很好的方案，因为我们的目的是接收什么类型的参数返回什么类型的参数，即在运行时传入参数我们才能确定类型\n\n这种情况就可以使用泛型，如下所示：\n\n```ts\nfunction returnItem<T>(para: T): T {\n    return para\n}\n```\n\n可以看到，泛型给予开发者创造灵活、可重用代码的能力\n\n\n\n## 二、使用方式\n\n泛型通过`<>`的形式进行表述，可以声明：\n\n- 函数\n\n- 接口\n- 类\n\n\n\n### 函数声明\n\n声明函数的形式如下：\n\n```ts\nfunction returnItem<T>(para: T): T {\n    return para\n}\n```\n\n定义泛型的时候，可以一次定义**多个类型参数**，比如我们可以同时定义泛型 `T` 和 泛型 `U`：\n\n```ts\nfunction swap<T, U>(tuple: [T, U]): [U, T] {\n    return [tuple[1], tuple[0]];\n}\n\nswap([7, 'seven']); // ['seven', 7]\n```\n\n\n\n\n\n### 接口声明\n\n声明接口的形式如下：\n\n```ts\ninterface ReturnItemFn<T> {\n    (para: T): T\n}\n```\n\n那么当我们想传入一个number作为参数的时候，就可以这样声明函数:\n\n```ts\nconst returnItem: ReturnItemFn<number> = para => para\n```\n\n###\n\n### 类声明\n\n使用泛型声明类的时候，既可以作用于类本身，也可以作用与类的成员函数\n\n下面简单实现一个元素同类型的栈结构，如下所示：\n\n```ts\nclass Stack<T> {\n    private arr: T[] = []\n\n    public push(item: T) {\n        this.arr.push(item)\n    }\n\n    public pop() {\n        this.arr.pop()\n    }\n}\n```\n\n使用方式如下：\n\n```ts\nconst stack = new Stacn<number>()\n```\n\n如果上述只能传递 `string` 和 `number` 类型，这时候就可以使用 `<T extends xx>` 的方式猜实现**约束泛型**，如下所示：\n\n ![](https://static.vue-js.com/67d212a0-0e17-11ec-8e64-91fdec0f05a1.png)\n\n\n\n\n\n除了上述的形式，泛型更高级的使用如下：\n\n例如要设计一个函数，这个函数接受两个参数，一个参数为对象，另一个参数为对象上的属性，我们通过这两个参数返回这个属性的值\n\n这时候就设计到泛型的索引类型和约束类型共同实现\n\n### 索引类型、约束类型\n\n索引类型 `keyof T` 把传入的对象的属性类型取出生成一个联合类型，这里的泛型 U 被约束在这个联合类型中，如下所示：\n\n```ts\nfunction getValue<T extends object, U extends keyof T>(obj: T, key: U) {\n  return obj[key] // ok\n}\n```\n\n上述为什么需要使用泛型约束，而不是直接定义第一个参数为 `object`类型，是因为默认情况 `object` 指的是`{}`，而我们接收的对象是各种各样的，一个泛型来表示传入的对象类型，比如 `T extends object`\n\n使用如下图所示：\n\n ![](https://static.vue-js.com/74fcbd40-0e17-11ec-a752-75723a64e8f5.png)\n\n\n\n\n\n### 多类型约束\n\n例如如下需要实现两个接口的类型约束：\n\n```ts\ninterface FirstInterface {\n  doSomething(): number\n}\n\ninterface SecondInterface {\n  doSomethingElse(): string\n}\n\n```\n\n可以创建一个接口继承上述两个接口，如下：\n\n```ts\ninterface ChildInterface extends FirstInterface, SecondInterface {\n\n}\n```\n\n正确使用如下：\n\n```ts\nclass Demo<T extends ChildInterface> {\n  private genericProperty: T\n\n  constructor(genericProperty: T) {\n    this.genericProperty = genericProperty\n  }\n  useT() {\n    this.genericProperty.doSomething()\n    this.genericProperty.doSomethingElse()\n  }\n}\n```\n\n通过泛型约束就可以达到多类型约束的目的\n\n\n\n## 三、应用场景\n\n通过上面初步的了解，后述在编写 `typescript` 的时候，定义函数，接口或者类的时候，不预先定义好具体的类型，而在使用的时候在指定类型的一种特性的时候，这种情况下就可以使用泛型\n\n灵活的使用泛型定义类型，是掌握`typescript` 必经之路\n\n\n\n## 参考文献\n\n- https://www.tslang.cn/docs/handbook/generics.html"
  },
  {
    "path": "docs/typescript/high type.md",
    "content": "# 面试官：说说你对 TypeScript 中高级类型的理解？有哪些？\n\n ![](https://static.vue-js.com/bda521e0-1065-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n除了`string`、`number`、`boolean` 这种基础类型外，在 `typescript` 类型声明中还存在一些高级的类型应用\n\n这些高级类型，是`typescript`为了保证语言的灵活性，所使用的一些语言特性。这些特性有助于我们应对复杂多变的开发场景\n\n## 二、有哪些\n\n常见的高级类型有如下：\n\n- 交叉类型\n- 联合类型\n- 类型别名\n- 类型索引\n- 类型约束\n- 映射类型\n- 条件类型\n\n\n### 交叉类型\n\n通过 `&` 将多个类型合并为一个类型，包含了所需的所有类型的特性，本质上是一种并的操作\n\n语法如下：\n\n```ts\nT & U\n```\n\n适用于对象合并场景，如下将声明一个函数，将两个对象合并成一个对象并返回：\n\n```ts\nfunction extend<T , U>(first: T, second: U) : T & U {\n    let result: <T & U> = {}\n    for (let key in first) {\n        result[key] = first[key]\n    }\n    for (let key in second) {\n        if(!result.hasOwnProperty(key)) {\n            result[key] = second[key]\n        }\n    }\n    return result\n}\n```\n\n\n\n\n\n### 联合类型\n\n联合类型的语法规则和逻辑 “或” 的符号一致，表示其类型为连接的多个类型中的任意一个，本质上是一个交的关系\n\n语法如下：\n\n```ts\nT | U\n```\n\n例如 `number` | `string` | `boolean` 的类型只能是这三个的一种，不能共存\n\n如下所示：\n\n```ts\nfunction formatCommandline(command: string[] | string) {\n  let line = '';\n  if (typeof command === 'string') {\n    line = command.trim();\n  } else {\n    line = command.join(' ').trim();\n  }\n}\n```\n\n\n\n### 类型别名\n\n类型别名会给一个类型起个新名字，类型别名有时和接口很像，但是可以作用于原始值、联合类型、元组以及其它任何你需要手写的类型\n\n可以使用 `type SomeName = someValidTypeAnnotation`的语法来创建类型别名：\n\n```ts\ntype some = boolean | string\n\nconst b: some = true // ok\nconst c: some = 'hello' // ok\nconst d: some = 123 // 不能将类型“123”分配给类型“some”\n```\n\n此外类型别名可以是泛型:\n\n```ts\ntype Container<T> = { value: T };\n```\n\n也可以使用类型别名来在属性里引用自己：\n\n```ts\ntype Tree<T> = {\n    value: T;\n    left: Tree<T>;\n    right: Tree<T>;\n}\n```\n\n可以看到，类型别名和接口使用十分相似，都可以描述一个对象或者函数\n\n两者最大的区别在于，`interface `只能用于定义对象类型，而 `type` 的声明方式除了对象之外还可以定义交叉、联合、原始类型等，类型声明的方式适用范围显然更加广泛\n\n\n\n\n\n### 类型索引\n\n`keyof` 类似于 `Object.keys` ，用于获取一个接口中 Key 的联合类型。\n\n```ts\ninterface Button {\n    type: string\n    text: string\n}\n\ntype ButtonKeys = keyof Button\n// 等效于\ntype ButtonKeys = \"type\" | \"text\"\n```\n\n\n\n\n\n### 类型约束\n\n通过关键字 `extend` 进行约束，不同于在 `class` 后使用 `extends` 的继承作用，泛型内使用的主要作用是对泛型加以约束\n\n```ts\ntype BaseType = string | number | boolean\n\n// 这里表示 copy 的参数\n// 只能是字符串、数字、布尔这几种基础类型\nfunction copy<T extends BaseType>(arg: T): T {\n  return arg\n}\n```\n\n类型约束通常和类型索引一起使用，例如我们有一个方法专门用来获取对象的值，但是这个对象并不确定，我们就可以使用 `extends` 和 `keyof` 进行约束。\n\n```ts\nfunction getValue<T, K extends keyof T>(obj: T, key: K) {\n  return obj[key]\n}\n\nconst obj = { a: 1 }\nconst a = getValue(obj, 'a')\n```\n\n\n\n### 映射类型\n\n通过 `in` 关键字做类型的映射，遍历已有接口的 `key` 或者是遍历联合类型，如下例子：\n\n```ts\ntype Readonly<T> = {\n    readonly [P in keyof T]: T[P];\n};\n\ninterface Obj {\n  a: string\n  b: string\n}\n\ntype ReadOnlyObj = Readonly<Obj>\n```\n\n上述的结构，可以分成这些步骤：\n\n- keyof T：通过类型索引 keyof 的得到联合类型 'a' | 'b'\n- P in keyof T 等同于 p in 'a' | 'b'，相当于执行了一次 forEach 的逻辑，遍历 'a' | 'b'\n\n所以最终`ReadOnlyObj`的接口为下述：\n\n```ts\ninterface ReadOnlyObj {\n    readonly a: string;\n    readonly b: string;\n}\n```\n\n\n\n### 条件类型\n\n条件类型的语法规则和三元表达式一致，经常用于一些类型不确定的情况。\n\n```ts\nT extends U ? X : Y\n```\n\n上面的意思就是，如果 T 是 U 的子集，就是类型 X，否则为类型 Y\n\n\n\n## 三、总结\n\n可以看到，如果只是掌握了 `typeScript` 的一些基础类型，可能很难游刃有余的去使用 `typeScript`，需要了解一些`typescript`的高阶用法\n\n并且`typescript`在版本的迭代中新增了很多功能，需要不断学习与掌握\n\n\n\n## 参考文献\n\n- https://www.tslang.cn/docs/handbook/advanced-types.html\n- https://juejin.cn/post/6844904003604578312\n- https://zhuanlan.zhihu.com/p/103846208"
  },
  {
    "path": "docs/typescript/interface.md",
    "content": "# 面试官：说说你对 TypeScript 中接口的理解？应用场景？\n\n\n\n ![](https://static.vue-js.com/193389b0-0b2b-11ec-8e64-91fdec0f05a1.png)\n\n\n\n## 一、是什么\n\n**接口**是一系列抽象方法的声明，是一些方法特征的集合，这些方法都应该是抽象的，需要由具体的**类**去实现，然后第三方就可以通过这组抽象方法调用，让具体的类执行具体的方法\n\n简单来讲，一个接口所描述的是一个对象相关的属性和方法，但并不提供具体创建此对象实例的方法\n\n`typescript`的核心功能之一就是对类型做检测，虽然这种检测方式是“鸭式辨型法”，而接口的作用就是为为这些类型命名和为你的代码或第三方代码定义一个约定\n\n\n\n## 二、使用方式\n\n接口定义如下：\n\n```ts\ninterface interface_name {\n}\n```\n\n例如有一个函数，这个函数接受一个 `User` 对象，然后返回这个 `User` 对象的 `name` 属性:\n\n```ts\nconst getUserName = (user) => user.name\n```\n\n可以看到，参数需要有一个`user`的`name`属性，可以通过接口描述`user`参数的结构\n\n```ts\ninterface User {\n    name: string\n    age: number\n}\n\nconst getUserName = (user: User) => user.name\n```\n\n这些属性并不一定全部实现，上述传入的对象必须拥有`name`和`age`属性，否则`typescript`在编译阶段会报错，如下图：\n\n ![](https://static.vue-js.com/25d3a790-0b2b-11ec-a752-75723a64e8f5.png)\n\n如果不想要`age`属性的话，这时候可以采用**可选属性**，如下表示：\n\n```ts\ninterface User {\n    name: string\n    age?: number\n}\n```\n\n这时候`age`属性则可以是`number`类型或者`undefined`类型\n\n有些时候，我们想要一个属性变成只读属性，在`typescript`只需要使用`readonly`声明，如下：\n\n```ts\ninterface User {\n    name: string\n    age?: number\n    readonly isMale: boolean\n}\n```\n\n当我们修改属性的时候，就会出现警告，如下所示：\n\n ![](https://static.vue-js.com/2f6d3c30-0b2b-11ec-8e64-91fdec0f05a1.png)\n\n这是属性中有一个函数，可以如下表示：\n\n```ts\ninterface User {\n    name: string\n    age?: number\n    readonly isMale: boolean\n    say: (words: string) => string\n}\n```\n\n如果传递的对象不仅仅是上述的属性，这时候可以使用：\n\n- 类型推断\n\n```\ninterface User {\n    name: string\n    age: number\n}\n\nconst getUserName = (user: User) => user.name\ngetUserName({color: 'yellow'} as User)\n```\n\n- 给接口添加字符串**索引签名**\n\n```ts\ninterface User {\n    name: string\n    age: number\n    [propName: string]: any;\n}\n```\n\n接口还能实现继承，如下图：\n\n ![](https://static.vue-js.com/38a41760-0b2b-11ec-8e64-91fdec0f05a1.png)\n\n也可以继承多个，父类通过逗号隔开，如下：\n\n```ts\ninterface Father {\n    color: String\n}\n\ninterface Mother {\n    height: Number\n}\n\ninterface Son extends Father,Mother{\n    name: string\n    age: Number\n}\n```\n\n\n\n## 三、应用场景\n\n例如在`javascript`中定义一个函数，用来获取用户的姓名和年龄：\n\n```js\nconst getUserInfo = function(user) {\n    // ...\n    return name: ${user.name}, age: ${user.age}\n}\n```\n\n如果多人开发的都需要用到这个函数的时候，如果没有注释，则可能出现各种运行时的错误，这时候就可以使用接口定义参数变量：\n\n```ts\n// 先定义一个接口\ninterface IUser {\n  name: string;\n  age: number;\n}\n\nconst getUserInfo = (user: IUser): string => {\n  return `name: ${user.name}, age: ${user.age}`;\n};\n\n// 正确的调用\ngetUserInfo({name: \"koala\", age: 18});\n```\n包括后面讲到类的时候也会应用到接口\n\n\n## 参考文献\n\n- https://www.tslang.cn/docs/handbook/interfaces.html"
  },
  {
    "path": "docs/typescript/namespace_module.md",
    "content": "# 面试官：说说对 TypeScript 中命名空间与模块的理解？区别？\n\n ![](https://static.vue-js.com/9378d760-137e-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、模块\n\n`TypeScript` 与` ECMAScript` 2015 一样，任何包含顶级 `import` 或者 `export` 的文件都被当成一个模块\n\n相反地，如果一个文件不带有顶级的`import`或者`export`声明，那么它的内容被视为全局可见的\n\n例如我们在在一个 `TypeScript` 工程下建立一个文件 `1.ts`，声明一个变量`a`，如下：\n\n```ts\nconst a = 1\n```\n\n然后在另一个文件同样声明一个变量`a`，这时候会出现错误信息\n\n ![](https://static.vue-js.com/a239d970-137e-11ec-a752-75723a64e8f5.png)\n\n提示重复声明`a`变量，但是所处的空间是全局的\n\n如果需要解决这个问题，则通过`import`或者`export`引入模块系统即可，如下：\n\n```ts\nconst a = 10;\n\nexport default a\n```\n\n在`typescript`中，`export`关键字可以导出变量或者类型，用法与`es6`模块一致，如下：\n\n```ts\nexport const a = 1\nexport type Person = {\n    name: String\n}\n```\n\n通过`import` 引入模块，如下：\n\n```ts\nimport { a, Person } from './export';\n```\n\n\n\n## 二、命名空间\n\n命名空间一个最明确的目的就是解决重名问题\n\n命名空间定义了标识符的可见范围，一个标识符可在多个名字空间中定义，它在不同名字空间中的含义是互不相干的\n\n这样，在一个新的名字空间中可定义任何标识符，它们不会与任何已有的标识符发生冲突，因为已有的定义都处于其他名字空间中\n\n`TypeScript` 中命名空间使用 `namespace` 来定义，语法格式如下：\n\n```ts\nnamespace SomeNameSpaceName {\n   export interface ISomeInterfaceName {      }\n   export class SomeClassName {      }\n}\n```\n\n以上定义了一个命名空间 `SomeNameSpaceName`，如果我们需要在外部可以调用 `SomeNameSpaceName` 中的类和接口，则需要在类和接口添加 `export` 关键字\n\n使用方式如下：\n\n```ts\nSomeNameSpaceName.SomeClassName\n```\n\n命名空间本质上是一个对象，作用是将一系列相关的全局变量组织到一个对象的属性，如下：\n\n```ts\nnamespace Letter {\n  export let a = 1;\n  export let b = 2;\n  export let c = 3;\n  // ...\n  export let z = 26;\n}\n```\n\n编译成`js`如下：\n\n```js\nvar Letter;\n(function (Letter) {\n    Letter.a = 1;\n    Letter.b = 2;\n    Letter.c = 3;\n    // ...\n    Letter.z = 26;\n})(Letter || (Letter = {}));\n```\n\n\n\n\n\n## 三、区别\n\n- 命名空间是位于全局命名空间下的一个普通的带有名字的  JavaScript  对象，使用起来十分容易。但就像其它的全局命名空间污染一样，它很难去识别组件之间的依赖关系，尤其是在大型的应用中\n\n- 像命名空间一样，模块可以包含代码和声明。 不同的是模块可以声明它的依赖\n\n- 在正常的TS项目开发过程中并不建议用命名空间，但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间，主要作用是给编译器编写代码的时候参考使用\n\n\n## 参考文献\n\n- https://www.tslang.cn/docs/handbook/modules.html\n- https://www.tslang.cn/docs/handbook/namespaces.html\n- https://www.tslang.cn/docs/handbook/namespaces-and-modules.html"
  },
  {
    "path": "docs/typescript/react.md",
    "content": "# 面试官：说说如何在 React 项目中应用 TypeScript？\n\n![](https://static.vue-js.com/a98974e0-13bc-11ec-a752-75723a64e8f5.png)\n\n## 一、前言\n\n单独的使用 `TypeScript` 并不会导致学习成本很高，但是绝大部分前端开发者的项目都是依赖于框架的\n\n例如与 `Vue`、`React` 这些框架结合使用的时候，会有一定的门槛\n\n使用 `TypeScript` 编写 `React` 代码，除了需要 `TypeScript` 这个库之外，还需要安装 `@types/react`、`@types/react-dom`\n\n```bash\nnpm i @types/react -s\n\nnpm i @types/react-dom -s\n```\n\n至于上述使用 `@types` 的库的原因在于，目前非常多的 `JavaScript` 库并没有提供自己关于 `TypeScript` 的声明文件\n\n所以，`ts` 并不知道这些库的类型以及对应导出的内容，这里 `@types` 实际就是社区中的 `DefinitelyTyped` 库，定义了目前市面上绝大多数的 `JavaScript` 库的声明\n\n所以下载相关的 `JavaScript` 对应的 `@types` 声明时，就能够使用使用该库对应的类型定义\n\n## 二、使用方式\n\n在编写 `React` 项目的时候，最常见的使用的组件就是：\n\n- 无状态组件\n- 有状态组件\n- 受控组件\n\n### 无状态组件\n\n主要作用是用于展示 `UI`，如果使用 `js` 声明，则如下所示：\n\n```jsx\nimport * as React from \"React\";\n\nexport const Logo = (props) => {\n  const { logo, className, alt } = props;\n\n  return <img src={logo} className={className} alt={alt} />;\n};\n```\n\n但这时候 `ts` 会出现报错提示，原因在于没有定义 `porps` 类型，这时候就可以使用 `interface` 接口去定义 `porps` 即可，如下：\n\n```tsx\nimport * as React from \"React\";\n\ninterface IProps {\n  logo?: string;\n  className?: string;\n  alt?: string;\n}\n\nexport const Logo = (props: IProps) => {\n  const { logo, className, alt } = props;\n\n  return <img src={logo} className={className} alt={alt} />;\n};\n```\n\n但是我们都知道 `props` 里面存在 `children` 属性，我们不可能每个 `porps` 接口里面定义多一个 `children`，如下：\n\n```ts\ninterface IProps {\n  logo?: string;\n  className?: string;\n  alt?: string;\n  children?: ReactNode;\n}\n```\n\n更加规范的写法是使用 `React` 里面定义好的 `FC` 属性，里面已经定义好 `children` 类型，如下：\n\n```tsx\nexport const Logo: React.FC<IProps> = (props) => {\n  const { logo, className, alt } = props;\n\n  return <img src={logo} className={className} alt={alt} />;\n};\n```\n\n- React.FC 显式地定义了返回类型，其他方式是隐式推导的\n\n- React.FC 对静态属性：displayName、propTypes、defaultProps 提供了类型检查和自动补全\n- React.FC 为 children 提供了隐式的类型（ReactElement | null）\n\n### 有状态组件\n\n可以是一个类组件且存在 `props` 和 `state` 属性\n\n如果使用 `TypeScript` 声明则如下所示：\n\n```tsx\nimport * as React from \"React\";\n\ninterface IProps {\n  color: string;\n  size?: string;\n}\ninterface IState {\n  count: number;\n}\nclass App extends React.Component<IProps, IState> {\n  public state = {\n    count: 1,\n  };\n  public render() {\n    return <div>Hello world</div>;\n  }\n}\n```\n\n上述通过泛型对 `props`、`state` 进行类型定义，然后在使用的时候就可以在编译器中获取更好的智能提示\n\n关于 `Component` 泛型类的定义，可以参考下 React 的类型定义文件 `node_modules/@types/React/index.d.ts`，如下所示：\n\n```ts\nclass Component<P, S> {\n  readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;\n\n  state: Readonly<S>;\n}\n```\n\n从上述可以看到，`state` 属性也定义了可读类型，目的是为了防止直接调用 `this.state` 更新状态\n\n### 受控组件\n\n受控组件的特性在于元素的内容通过组件的状态 `state` 进行控制\n\n由于组件内部的事件是合成事件，不等同于原生事件，\n\n例如一个 `input` 组件修改内部的状态，常见的定义的时候如下所示：\n\n```ts\nprivate updateValue(e: React.ChangeEvent<HTMLInputElement>) {\n    this.setState({ itemText: e.target.value })\n}\n```\n\n常用 `Event` 事件对象类型：\n\n- ClipboardEvent<T = Element> 剪贴板事件对象\n- DragEvent<T = Element> 拖拽事件对象\n- ChangeEvent<T = Element> Change 事件对象\n- KeyboardEvent<T = Element> 键盘事件对象\n- MouseEvent<T = Element> 鼠标事件对象\n- TouchEvent<T = Element> 触摸事件对象\n- WheelEvent<T = Element> 滚轮事件对象\n- AnimationEvent<T = Element> 动画事件对象\n- TransitionEvent<T = Element> 过渡事件对象\n\n`T` 接收一个 `DOM` 元素类型\n\n## 三、总结\n\n上述只是简单的在 `React` 项目使用 `TypeScript`，但在编写 `React` 项目的时候，还存在 `hooks`、默认参数、以及 `store` 等等......\n\n`TypeScript` 在框架中使用的学习成本相对会更高，需要不断编写才能熟练\n\n## 参考文献\n\n- [https://juejin.cn/post/6952696734078369828](https://juejin.cn/post/6952696734078369828)\n- [https://juejin.cn/post/6844903684422254606](https://juejin.cn/post/6844903684422254606)\n"
  },
  {
    "path": "docs/typescript/typescript_javascript.md",
    "content": "# 面试官：说说你对 TypeScript 的理解？与 JavaScript 的区别？\n\n![](https://static.vue-js.com/58cd3580-0950-11ec-8e64-91fdec0f05a1.png)\n\n## 一、是什么\n\n`TypeScript` 是 `JavaScript` 的类型的超集，支持`ES6`语法，支持面向对象编程的概念，如类、接口、继承、泛型等\n\n> 超集，不得不说另外一个概念，子集，怎么理解这两个呢，举个例子，如果一个集合 A 里面的的所有元素集合 B 里面都存在，那么我们可以理解集合 B 是集合 A 的超集，集合 A 为集合 B 的子集\n\n![](https://static.vue-js.com/61c2c1f0-0950-11ec-a752-75723a64e8f5.png)\n\n其是一种静态类型检查的语言，提供了类型注解，在代码编译阶段就可以检查出数据类型的错误\n\n同时扩展了` JavaScript` 的语法，所以任何现有的` JavaScript` 程序可以不加改变的在 `TypeScript` 下工作\n\n为了保证兼容性，`TypeScript` 在编译阶段需要编译器编译成纯 `JavaScript` 来运行，是为大型应用之开发而设计的语言，如下：\n\n`ts` 文件如下：\n\n```ts\nconst hello: string = \"Hello World!\";\nconsole.log(hello);\n```\n\n编译文件后：\n\n```js\nconst hello = \"Hello World!\";\nconsole.log(hello);\n```\n\n## 二、特性\n\n`TypeScript` 的特性主要有如下：\n\n- **类型批注和编译时类型检查** ：在编译时批注变量类型\n- **类型推断**：ts 中没有批注变量类型会自动推断变量的类型\n- **类型擦除**：在编译过程中批注的内容和接口会在运行时利用工具擦除\n- **接口**：ts 中用接口来定义对象类型\n- **枚举**：用于取值被限定在一定范围内的场景\n- **Mixin**：可以接受任意类型的值\n- **泛型编程**：写代码时使用一些以后才指定的类型\n- **名字空间**：名字只在该区域内有效，其他区域可重复使用该名字而不冲突\n- **元组**：元组合并了不同类型的对象，相当于一个可以装不同类型数据的数组\n- ...\n\n### 类型批注\n\n通过类型批注提供在编译时启动类型检查的静态类型，这是可选的，而且可以忽略而使用 `JavaScript` 常规的动态类型\n\n```tsx\nfunction Add(left: number, right: number): number {\n  return left + right;\n}\n```\n\n对于基本类型的批注是 `number`、`bool` 和 `string`，而弱或动态类型的结构则是 `any` 类型\n\n### 类型推断\n\n当类型没有给出时，TypeScript 编译器利用类型推断来推断类型，如下：\n\n```ts\nlet str = \"string\";\n```\n\n变量 `str` 被推断为字符串类型，这种推断发生在初始化变量和成员，设置默认参数值和决定函数返回值时\n\n如果缺乏声明而不能推断出类型，那么它的类型被视作默认的动态 `any` 类型\n\n### 接口\n\n接口简单来说就是用来描述对象的类型 数据的类型有 `number`、`null`、` string` 等数据格式，对象的类型就是用接口来描述的\n\n```tsx\ninterface Person {\n  name: string;\n  age: number;\n}\n\nlet tom: Person = {\n  name: \"Tom\",\n  age: 25,\n};\n```\n\n## 三、区别\n\n- TypeScript 是 JavaScript 的超集，扩展了 JavaScript 的语法\n- TypeScript 可处理已有的 JavaScript 代码，并只对其中的 TypeScript 代码进行编译\n- TypeScript 文件的后缀名 .ts （.ts，.tsx，.dts），JavaScript 文件是 .js\n- 在编写 TypeScript 的文件的时候就会自动编译成 js 文件\n\n更多的区别如下图所示：\n\n![](https://static.vue-js.com/6b544040-0950-11ec-8e64-91fdec0f05a1.png)\n\n## 参考文献\n\n- [https://zhuanlan.zhihu.com/p/140012915](https://zhuanlan.zhihu.com/p/140012915)\n- [https://www.jianshu.com/p/c8aaba6e8ce0](https://www.jianshu.com/p/c8aaba6e8ce0)\n- [https://www.cnblogs.com/powertoolsteam/p/13500668.html](https://www.cnblogs.com/powertoolsteam/p/13500668.html)\n"
  },
  {
    "path": "docs/typescript/vue.md",
    "content": "# 面试官：说说如何在Vue项目中应用TypeScript？\n\n\n\n ![](https://static.vue-js.com/cc658c10-1565-11ec-8e64-91fdec0f05a1.png)\n\n\n## 一、前言\n与link类似\n\n在`VUE`项目中应用`typescript`，我们需要引入一个库`vue-property-decorator`，\n\n其是基于`vue-class-component`库而来，这个库`vue`官方推出的一个支持使用`class`方式来开发`vue`单文件组件的库\n\n主要的功能如下：\n\n- methods 可以直接声明为类的成员方法\n- 计算属性可以被声明为类的属性访问器\n- 初始化的 data 可以被声明为类属性\n- data、render 以及所有的 Vue 生命周期钩子可以直接作为类的成员方法\n- 所有其他属性，需要放在装饰器中\n\n\n\n## 二、使用\n\nvue-property-decorator 主要提供了多个装饰器和一个函数:\n\n- @Prop\n- @PropSync\n- @Model\n- @Watch\n- @Provide\n- @Inject\n- @ProvideReactive\n- @InjectReactive\n- @Emit\n- @Ref\n- @Component (由 vue-class-component 提供)\n- Mixins (由 vue-class-component 提供)\n\n\n\n### @Component\n\n`Component`装饰器它注明了此类为一个`Vue`组件，因此即使没有设置选项也不能省略\n\n如果需要定义比如 `name`、`components`、`filters`、`directives`以及自定义属性，就可以在`Component`装饰器中定义，如下：\n\n```vue\nimport {Component,Vue} from 'vue-property-decorator';\nimport {componentA,componentB} from '@/components';\n\n @Component({\n    components:{\n        componentA,\n        componentB,\n    },\n    directives: {\n        focus: {\n            // 指令的定义\n            inserted: function (el) {\n                el.focus()\n            }\n        }\n    }\n})\nexport default class YourCompoent extends Vue{\n\n}\n\n```\n\n\n\n### computed、data、methods\n\n这里取消了组件的data和methods属性，以往data返回对象中的属性、methods中的方法需要直接定义在Class中，当做类的属性和方法\n\n```ts\n@Component\nexport default class HelloDecorator extends Vue {\n    count: number = 123 // 类属性相当于以前的 data\n\n    add(): number { // 类方法就是以前的方法\n        this.count + 1\n    }\n\n    // 获取计算属性\n    get total(): number {\n      return this.count + 1\n    }\n\n    // 设置计算属性\n    set total(param:number): void {\n      this.count = param\n    }\n}\n\n```\n\n\n\n### @props\n\n组件接收属性的装饰器，如下使用：\n\n```js\nimport {Component,Vue,Prop} from vue-property-decorator;\n\n@Component\nexport default class YourComponent extends Vue {\n    @Prop(String)\n    propA:string;\n\n    @Prop([String,Number])\n    propB:string|number;\n\n    @Prop({\n     type: String, // type: [String , Number]\n     default: 'default value', // 一般为String或Number\n      //如果是对象或数组的话。默认值从一个工厂函数中返回\n      // defatult: () => {\n      //     return ['a','b']\n      // }\n     required: true,\n     validator: (value) => {\n        return [\n          'InProcess',\n          'Settled'\n        ].indexOf(value) !== -1\n     }\n    })\n    propC:string;\n}\n```\n\n\n\n\n\n### @watch\n\n实际就是`Vue`中的监听器，如下：\n\n```vue\nimport { Vue, Component, Watch } from 'vue-property-decorator'\n\n@Component\nexport default class YourComponent extends Vue {\n  @Watch('child')\n  onChildChanged(val: string, oldVal: string) {}\n\n  @Watch('person', { immediate: true, deep: true })\n  onPersonChanged1(val: Person, oldVal: Person) {}\n\n  @Watch('person')\n  onPersonChanged2(val: Person, oldVal: Person) {}\n}\n```\n\n\n\n\n\n### @emit\n\n`vue-property-decorator` 提供的 `@Emit` 装饰器就是代替`Vue `中的事件的触发`$emit`，如下：\n\n````TS\nimport {Vue, Component, Emit} from 'vue-property-decorator';\n    @Component({})\n    export default class Some extends Vue{\n        mounted(){\n            this.$on('emit-todo', function(n) {\n                console.log(n)\n            })\n            this.emitTodo('world');\n        }\n        @Emit()\n        emitTodo(n: string){\n            console.log('hello');\n        }\n    }\n````\n\n\n\n## 三 、总结\n\n可以看到上述`typescript`版本的`vue class`的语法与平时`javascript`版本使用起来还是有很大的不同，多处用到`class`与装饰器，但实际上本质是一致的，只有不断编写才会得心应手\n"
  },
  {
    "path": "docs/vue/404.md",
    "content": "# 面试官：vue项目本地开发完成后部署到服务器后报404是什么原因呢？\n\n![image.png](https://static.vue-js.com/002c9320-4f3e-11eb-ab90-d9ae814b240d.png)\n\n## 一、如何部署\n\n前后端分离开发模式下，前后端是独立布署的，前端只需要将最后的构建物上传至目标服务器的`web`容器指定的静态目录下即可\n\n我们知道`vue`项目在构建后，是生成一系列的静态文件\n\n\n![](https://imgkr2.cn-bj.ufileos.com/b9d13e56-f859-4b4b-a9da-a703a34c2f5d.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=m1qDXRSFHrfXlnAtAlVhjoLKP70%253D&Expires=1609927181)\n\n\n常规布署我们只需要将这个目录上传至目标服务器即可\n\n```bash\n// scp 上传 user为主机登录用户，host为主机外网ip, xx为web容器静态资源路径\nscp dist.zip user@host:/xx/xx/xx\n```\n\n让`web`容器跑起来，以`nginx`为例\n\n```bash\nserver {\n  listen  80;\n  server_name  www.xxx.com;\n\n  location / {\n    index  /data/dist/index.html;\n  }\n}\n```\n配置完成记得重启`nginx`\n```bash\n// 检查配置是否正确\nnginx -t \n\n// 平滑重启\nnginx -s reload\n```\n\n操作完后就可以在浏览器输入域名进行访问了\n\n当然上面只是提到最简单也是最直接的一种布署方式\n\n什么自动化，镜像，容器，流水线布署，本质也是将这套逻辑抽象，隔离，用程序来代替重复性的劳动，本文不展开\n\n## 二、404问题\n\n这是一个经典的问题，相信很多同学都有遇到过，那么你知道其真正的原因吗？\n\n我们先还原一下场景：\n\n- `vue`项目在本地时运行正常，但部署到服务器中，刷新页面，出现了404错误\n\n先定位一下，HTTP 404 错误意味着链接指向的资源不存在\n\n问题在于为什么不存在？且为什么只有`history`模式下会出现这个问题？\n\n### 为什么history模式下有问题\n\n`Vue`是属于单页应用（single-page application）\n\n而`SPA`是一种网络应用程序或网站的模型，所有用户交互是通过动态重写当前页面，前面我们也看到了，不管我们应用有多少页面，构建物都只会产出一个`index.html`\n\n现在，我们回头来看一下我们的`nginx`配置\n\n```js\nserver {\n  listen  80;\n  server_name  www.xxx.com;\n\n  location / {\n    index  /data/dist/index.html;\n  }\n}\n```\n\n可以根据 `nginx` 配置得出，当我们在地址栏输入 `www.xxx.com` 时，这时会打开我们 `dist` 目录下的 `index.html` 文件，然后我们在跳转路由进入到 `www.xxx.com/login`\n\n关键在这里，当我们在 `website.com/login` 页执行刷新操作，`nginx location` 是没有相关配置的，所以就会出现 404 的情况\n\n\n### 为什么hash模式下没有问题\n\n`router hash` 模式我们都知道是用符号#表示的，如  `website.com/#/login`, `hash` 的值为 `#/login`\n\n它的特点在于：`hash` 虽然出现在 `URL` 中，但不会被包括在 `HTTP` 请求中，对服务端完全没有影响，因此改变 `hash` 不会重新加载页面\n\n`hash` 模式下，仅 `hash` 符号之前的内容会被包含在请求中，如 `website.com/#/login` 只有 `website.com` 会被包含在请求中 ，因此对于服务端来说，即使没有配置`location`，也不会返回404错误\n\n\n\n## 解决方案\n\n看到这里我相信大部分同学都能想到怎么解决问题了，\n\n产生问题的本质是因为我们的路由是通过JS来执行视图切换的，\n\n当我们进入到子路由时刷新页面，`web`容器没有相对应的页面此时会出现404\n\n所以我们只需要配置将任意页面都重定向到 `index.html`，把路由交由前端处理\n\n对`nginx`配置文件`.conf`修改，添加`try_files $uri $uri/ /index.html;`\n\n```bash\nserver {\n  listen  80;\n  server_name  www.xxx.com;\n\n  location / {\n    index  /data/dist/index.html;\n    try_files $uri $uri/ /index.html;\n  }\n}\n```\n\n修改完配置文件后记得配置的更新\n\n```bash\nnginx -s reload\n```\n\n这么做以后，你的服务器就不再返回 404 错误页面，因为对于所有路径都会返回 `index.html` 文件\n\n为了避免这种情况，你应该在 `Vue` 应用里面覆盖所有的路由情况，然后在给出一个 404 页面\n\n```js\nconst router = new VueRouter({\n  mode: 'history',\n  routes: [\n    { path: '*', component: NotFoundComponent }\n  ]\n})\n```\n\n关于后端配置方案还有：`Apache`、`nodejs`等，思想是一致的，这里就不展开述说了\n\n## 参考文献\n\n- https://juejin.cn/post/6844903872637632525\n- https://vue-js.com/topic/5f8cf91d96b2cb0032c385c0"
  },
  {
    "path": "docs/vue/axios.md",
    "content": "# 面试官：Vue项目中有封装过axios吗？主要是封装哪方面的？\n\n![](https://static.vue-js.com/2bf1e460-45a7-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、axios是什么\n\n`axios` 是一个轻量的 `HTTP`客户端\n\n基于 `XMLHttpRequest` 服务来执行 `HTTP` 请求，支持丰富的配置，支持 `Promise`，支持浏览器端和 `Node.js` 端。自`Vue`2.0起，尤大宣布取消对 `vue-resource` 的官方推荐，转而推荐 `axios`。现在 `axios` 已经成为大部分 `Vue` 开发者的首选\n\n### 特性\n\n- 从浏览器中创建 `XMLHttpRequests`\n- 从 `node.js` 创建 `http`请求\n- 支持 `Promise` API\n- 拦截请求和响应\n- 转换请求数据和响应数据\n- 取消请求\n- 自动转换` JSON` 数据\n- 客户端支持防御`XSRF`\n\n### 基本使用\n\n安装\n\n```js\n// 项目中安装\nnpm install axios --S\n// cdn 引入\n<script src=\"https://unpkg.com/axios/dist/axios.min.js\"></script>\n```\n\n导入\n\n```js\nimport axios from 'axios'\n```\n\n发送请求\n\n```js\naxios({        \n  url:'xxx',    // 设置请求的地址\n  method:\"GET\", // 设置请求方法\n  params:{      // get请求使用params进行参数凭借,如果是post请求用data\n    type: '',\n    page: 1\n  }\n}).then(res => {  \n  // res为后端返回的数据\n  console.log(res);   \n})\n```\n\n\n\n并发请求`axios.all([])`\n\n```js\nfunction getUserAccount() {\n    return axios.get('/user/12345');\n}\n\nfunction getUserPermissions() {\n    return axios.get('/user/12345/permissions');\n}\n\naxios.all([getUserAccount(), getUserPermissions()])\n    .then(axios.spread(function (res1, res2) { \n    // res1第一个请求的返回的内容，res2第二个请求返回的内容\n    // 两个请求都执行完成才会执行\n}));\n```\n\n\n\n## 二、为什么要封装\n\n`axios` 的 API 很友好，你完全可以很轻松地在项目中直接使用。\n\n不过随着项目规模增大，如果每发起一次`HTTP`请求，就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作，都需要写一遍\n\n这种重复劳动不仅浪费时间，而且让代码变得冗余不堪，难以维护。为了提高我们的代码质量，我们应该在项目中二次封装一下 `axios` 再使用\n\n举个例子：\n\n```js\naxios('http://localhost:3000/data', {\n  // 配置代码\n  method: 'GET',\n  timeout: 1000,\n  withCredentials: true,\n  headers: {\n    'Content-Type': 'application/json',\n    Authorization: 'xxx',\n  },\n  transformRequest: [function (data, headers) {\n    return data;\n  }],\n  // 其他请求配置...\n})\n.then((data) => {\n  // todo: 真正业务逻辑代码\n  console.log(data);\n}, (err) => {\n  // 错误处理代码  \n  if (err.response.status === 401) {\n  // handle authorization error\n  }\n  if (err.response.status === 403) {\n  // handle server forbidden error\n  }\n  // 其他错误处理.....\n  console.log(err);\n});\n```\n\n如果每个页面都发送类似的请求，都要写一堆的配置与错误处理，就显得过于繁琐了\n\n这时候我们就需要对`axios`进行二次封装，让使用更为便利\n\n\n\n## 三、如何封装\n\n封装的同时，你需要和 后端协商好一些约定，请求头，状态码，请求超时时间.......\n\n设置接口请求前缀：根据开发、测试、生产环境的不同，前缀需要加以区分\n\n请求头 :  来实现一些具体的业务，必须携带一些参数才可以请求(例如：会员业务)\n\n状态码:   根据接口返回的不同`status` ， 来执行不同的业务，这块需要和后端约定好\n\n请求方法：根据`get`、`post`等方法进行一个再次封装，使用起来更为方便\n\n请求拦截器:  根据请求的请求头设定，来决定哪些请求可以访问\n\n响应拦截器： 这块就是根据 后端`返回来的状态码判定执行不同业务\n\n\n\n### 设置接口请求前缀\n\n利用`node`环境变量来作判断，用来区分开发、测试、生产环境\n\n```js\nif (process.env.NODE_ENV === 'development') {\n  axios.defaults.baseURL = 'http://dev.xxx.com'\n} else if (process.env.NODE_ENV === 'production') {\n  axios.defaults.baseURL = 'http://prod.xxx.com'\n}\n```\n\n在本地调试的时候，还需要在`vue.config.js`文件中配置`devServer`实现代理转发，从而实现跨域\n\n```js\ndevServer: {\n    proxy: {\n      '/proxyApi': {\n        target: 'http://dev.xxx.com',\n        changeOrigin: true,\n        pathRewrite: {\n          '/proxyApi': ''\n        }\n      }\n    }\n  }\n```\n\n\n\n### 设置请求头与超时时间\n\n大部分情况下，请求头都是固定的，只有少部分情况下，会需要一些特殊的请求头，这里将普适性的请求头作为基础配置。当需要特殊请求头时，将特殊请求头作为参数传入，覆盖基础配置\n\n```js\nconst service = axios.create({\n    ...\n    timeout: 30000,  // 请求 30s 超时\n\t  headers: {\n        get: {\n          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'\n          // 在开发中，一般还需要单点登录或者其他功能的通用请求头，可以一并配置进来\n        },\n        post: {\n          'Content-Type': 'application/json;charset=utf-8'\n          // 在开发中，一般还需要单点登录或者其他功能的通用请求头，可以一并配置进来\n        }\n  },\n})\n```\n\n\n\n### 封装请求方法\n\n先引入封装好的方法，在要调用的接口重新封装成一个方法暴露出去\n\n```js\n// get 请求\nexport function httpGet({\n  url,\n  params = {}\n}) {\n  return new Promise((resolve, reject) => {\n    axios.get(url, {\n      params\n    }).then((res) => {\n      resolve(res.data)\n    }).catch(err => {\n      reject(err)\n    })\n  })\n}\n\n// post\n// post请求\nexport function httpPost({\n  url,\n  data = {},\n  params = {}\n}) {\n  return new Promise((resolve, reject) => {\n    axios({\n      url,\n      method: 'post',\n      transformRequest: [function (data) {\n        let ret = ''\n        for (let it in data) {\n          ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'\n        }\n        return ret\n      }],\n      // 发送的数据\n      data,\n      // \burl参数\n      params\n\n    }).then(res => {\n      resolve(res.data)\n    })\n  })\n}\n```\n\n把封装的方法放在一个`api.js`文件中\n\n```js\nimport { httpGet, httpPost } from './http'\nexport const getorglist = (params = {}) => httpGet({ url: 'apps/api/org/list', params })\n```\n\n页面中就能直接调用\n\n```js\n// .vue\nimport { getorglist } from '@/assets/js/api'\n\ngetorglist({ id: 200 }).then(res => {\n  console.log(res)\n})\n```\n\n这样可以把`api`统一管理起来，以后维护修改只需要在`api.js`文件操作即可\n\n\n\n### 请求拦截器\n\n请求拦截器可以在每个请求里加上token，做了统一处理后维护起来也方便\n\n```js\n// 请求拦截器\naxios.interceptors.request.use(\n  config => {\n    // 每次发送请求之前判断是否存在token\n    // 如果存在，则统一在http请求的header都加上token，这样后台根据token判断你的登录情况，此处token一般是用户完成登录后储存到localstorage里的\n    token && (config.headers.Authorization = token)\n    return config\n  },\n  error => {\n    return Promise.error(error)\n  })\n```\n\n\n\n### 响应拦截器\n\n响应拦截器可以在接收到响应后先做一层操作，如根据状态码判断登录状态、授权\n\n```js\n// 响应拦截器\naxios.interceptors.response.use(response => {\n  // 如果返回的状态码为200，说明接口请求成功，可以正常拿到数据\n  // 否则的话抛出错误\n  if (response.status === 200) {\n    if (response.data.code === 511) {\n      // 未授权调取授权接口\n    } else if (response.data.code === 510) {\n      // 未登录跳转登录页\n    } else {\n      return Promise.resolve(response)\n    }\n  } else {\n    return Promise.reject(response)\n  }\n}, error => {\n  // 我们可以在这里对异常状态作统一处理\n  if (error.response.status) {\n    // 处理请求失败的情况\n    // 对不同返回码对相应处理\n    return Promise.reject(error.response)\n  }\n})\n```\n\n\n\n### 小结\n\n- 封装是编程中很有意义的手段，简单的`axios`封装，就可以让我们可以领略到它的魅力\n- 封装 `axios` 没有一个绝对的标准，只要你的封装可以满足你的项目需求，并且用起来方便，那就是一个好的封装方案\n\n\n## 参考文献\n\n- https://www.html.cn/qa/vue-js/20544.html\n- https://juejin.cn/post/6844904033782611976\n- https://juejin.cn/post/6844903801451708429"
  },
  {
    "path": "docs/vue/axiosCode.md",
    "content": "# 面试官：你了解axios的原理吗？有看过它的源码吗？\n\n![](https://static.vue-js.com/1564f7d0-4662-11eb-ab90-d9ae814b240d.png)\n\n## 一、axios的使用\n\n关于`axios`的基本使用，上篇文章已经有所涉及，这里再稍微回顾下：\n\n**发送请求**\n\n```js\nimport axios from 'axios';\n\naxios(config) // 直接传入配置\naxios(url[, config]) // 传入url和配置\naxios[method](url[, option]) // 直接调用请求方式方法，传入url和配置\naxios[method](url[, data[, option]]) // 直接调用请求方式方法，传入data、url和配置\naxios.request(option) // 调用 request 方法\n\nconst axiosInstance = axios.create(config)\n// axiosInstance 也具有以上 axios 的能力\n\naxios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))\n// 调用 all 和传入 spread 回调\n\n```\n\n\n\n**请求拦截器**\n\n```js\naxios.interceptors.request.use(function (config) {\n    // 这里写发送请求前处理的代码\n    return config;\n}, function (error) {\n    // 这里写发送请求错误相关的代码\n    return Promise.reject(error);\n});\n```\n\n\n\n**响应拦截器**\n\n```js\naxios.interceptors.response.use(function (response) {\n    // 这里写得到响应数据后处理的代码\n    return response;\n}, function (error) {\n    // 这里写得到错误响应处理的代码\n    return Promise.reject(error);\n});\n```\n\n\n\n**取消请求**\n\n```js\n// 方式一\nconst CancelToken = axios.CancelToken;\nconst source = CancelToken.source();\n\naxios.get('xxxx', {\n  cancelToken: source.token\n})\n// 取消请求 (请求原因是可选的)\nsource.cancel('主动取消请求');\n\n// 方式二\nconst CancelToken = axios.CancelToken;\nlet cancel;\n\naxios.get('xxxx', {\n  cancelToken: new CancelToken(function executor(c) {\n    cancel = c;\n  })\n});\ncancel('主动取消请求');\n```\n\n\n\n\n\n## 二、实现一个简易版axios\n\n构建一个`Axios`构造函数，核心代码为`request`\n\n```js\nclass Axios {\n    constructor() {\n\n    }\n\n    request(config) {\n        return new Promise(resolve => {\n            const {url = '', method = 'get', data = {}} = config;\n            // 发送ajax请求\n            const xhr = new XMLHttpRequest();\n            xhr.open(method, url, true);\n            xhr.onload = function() {\n                console.log(xhr.responseText)\n                resolve(xhr.responseText);\n            }\n            xhr.send(data);\n        })\n    }\n}\n```\n\n导出`axios`实例\n\n```js\n// 最终导出axios的方法，即实例的request方法\nfunction CreateAxiosFn() {\n    let axios = new Axios();\n    let req = axios.request.bind(axios);\n    return req;\n}\n\n// 得到最后的全局变量axios\nlet axios = CreateAxiosFn();\n```\n\n上述就已经能够实现`axios({ })`这种方式的请求\n\n下面是来实现下`axios.method()`这种形式的请求\n\n```js\n// 定义get,post...方法，挂在到Axios原型上\nconst methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];\nmethodsArr.forEach(met => {\n    Axios.prototype[met] = function() {\n        console.log('执行'+met+'方法');\n        // 处理单个方法\n        if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])\n            return this.request({\n                method: met,\n                url: arguments[0],\n                ...arguments[1] || {}\n            })\n        } else { // 3个参数(url[,data[,config]])\n            return this.request({\n                method: met,\n                url: arguments[0],\n                data: arguments[1] || {},\n                ...arguments[2] || {}\n            })\n        }\n\n    }\n})\n```\n\n将`Axios.prototype`上的方法搬运到`request`上\n\n首先实现个工具类，实现将`b`方法混入到`a`，并且修改`this`指向\n\n```js\nconst utils = {\n  extend(a,b, context) {\n    for(let key in b) {\n      if (b.hasOwnProperty(key)) {\n        if (typeof b[key] === 'function') {\n          a[key] = b[key].bind(context);\n        } else {\n          a[key] = b[key]\n        }\n      }\n      \n    }\n  }\n}\n```\n\n修改导出的方法\n\n```js\nfunction CreateAxiosFn() {\n  let axios = new Axios();\n  \n  let req = axios.request.bind(axios);\n  // 增加代码\n  utils.extend(req, Axios.prototype, axios)\n  \n  return req;\n}\n```\n\n\n\n构建拦截器的构造函数\n\n```js\nclass InterceptorsManage {\n  constructor() {\n    this.handlers = [];\n  }\n\n  use(fullfield, rejected) {\n    this.handlers.push({\n      fullfield,\n      rejected\n    })\n  }\n}\n```\n\n实现`axios.interceptors.response.use`和`axios.interceptors.request.use`\n\n```js\nclass Axios {\n    constructor() {\n        // 新增代码\n        this.interceptors = {\n            request: new InterceptorsManage,\n            response: new InterceptorsManage\n        }\n    }\n\n    request(config) {\n \t\t...\n    }\n}\n```\n\n执行语句`axios.interceptors.response.use`和`axios.interceptors.request.use`的时候，实现获取`axios`实例上的`interceptors`对象，然后再获取`response`或`request`拦截器，再执行对应的拦截器的`use`方法\n\n把`Axios`上的方法和属性搬到`request`过去\n\n```js\nfunction CreateAxiosFn() {\n  let axios = new Axios();\n  \n  let req = axios.request.bind(axios);\n  // 混入方法， 处理axios的request方法，使之拥有get,post...方法\n  utils.extend(req, Axios.prototype, axios)\n  // 新增代码\n  utils.extend(req, axios)\n  return req;\n}\n```\n\n现在`request`也有了`interceptors`对象，在发送请求的时候，会先获取`request`拦截器的`handlers`的方法来执行\n\n首先将执行`ajax`的请求封装成一个方法\n\n```js\nrequest(config) {\n    this.sendAjax(config)\n}\nsendAjax(config){\n    return new Promise(resolve => {\n        const {url = '', method = 'get', data = {}} = config;\n        // 发送ajax请求\n        console.log(config);\n        const xhr = new XMLHttpRequest();\n        xhr.open(method, url, true);\n        xhr.onload = function() {\n            console.log(xhr.responseText)\n            resolve(xhr.responseText);\n        };\n        xhr.send(data);\n    })\n}\n```\n\n获得`handlers`中的回调\n\n```js\nrequest(config) {\n    // 拦截器和请求组装队列\n    let chain = [this.sendAjax.bind(this), undefined] // 成对出现的，失败回调暂时不处理\n\n    // 请求拦截\n    this.interceptors.request.handlers.forEach(interceptor => {\n        chain.unshift(interceptor.fullfield, interceptor.rejected)\n    })\n\n    // 响应拦截\n    this.interceptors.response.handlers.forEach(interceptor => {\n        chain.push(interceptor.fullfield, interceptor.rejected)\n    })\n\n    // 执行队列，每次执行一对，并给promise赋最新的值\n    let promise = Promise.resolve(config);\n    while(chain.length > 0) {\n        promise = promise.then(chain.shift(), chain.shift())\n    }\n    return promise;\n}\n```\n\n`chains`大概是`['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']`这种形式\n\n这样就能够成功实现一个简易版`axios`\n\n\n\n## 三、源码分析\n\n首先看看目录结构\n\n ![](https://static.vue-js.com/9d90eaa0-48b6-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n`axios`发送请求有很多实现的方法，实现入口文件为`axios.js `\n\n```js\nfunction createInstance(defaultConfig) {\n  var context = new Axios(defaultConfig);\n\n  // instance指向了request方法，且上下文指向context，所以可以直接以 instance(option) 方式调用 \n  // Axios.prototype.request 内对第一个参数的数据类型判断，使我们能够以 instance(url, option) 方式调用\n  var instance = bind(Axios.prototype.request, context);\n\n  // 把Axios.prototype上的方法扩展到instance对象上，\n  // 并指定上下文为context，这样执行Axios原型链上的方法时，this会指向context\n  utils.extend(instance, Axios.prototype, context);\n\n  // Copy context to instance\n  // 把context对象上的自身属性和方法扩展到instance上\n  // 注：因为extend内部使用的forEach方法对对象做for in 遍历时，只遍历对象本身的属性，而不会遍历原型链上的属性\n  // 这样，instance 就有了  defaults、interceptors 属性。\n  utils.extend(instance, context);\n  return instance;\n}\n\n// Create the default instance to be exported 创建一个由默认配置生成的axios实例\nvar axios = createInstance(defaults);\n\n// Factory for creating new instances 扩展axios.create工厂函数，内部也是 createInstance\naxios.create = function create(instanceConfig) {\n  return createInstance(mergeConfig(axios.defaults, instanceConfig));\n};\n\n// Expose all/spread\naxios.all = function all(promises) {\n  return Promise.all(promises);\n};\n\naxios.spread = function spread(callback) {\n  return function wrap(arr) {\n    return callback.apply(null, arr);\n  };\n};\nmodule.exports = axios;\n```\n\n主要核心是 `Axios.prototype.request`，各种请求方式的调用实现都是在 `request` 内部实现的， 简单看下 `request` 的逻辑\n\n```js\nAxios.prototype.request = function request(config) {\n  // Allow for axios('example/url'[, config]) a la fetch API\n  // 判断 config 参数是否是 字符串，如果是则认为第一个参数是 URL，第二个参数是真正的config\n  if (typeof config === 'string') {\n    config = arguments[1] || {};\n    // 把 url 放置到 config 对象中，便于之后的 mergeConfig\n    config.url = arguments[0];\n  } else {\n    // 如果 config 参数是否是 字符串，则整体都当做config\n    config = config || {};\n  }\n  // 合并默认配置和传入的配置\n  config = mergeConfig(this.defaults, config);\n  // 设置请求方法\n  config.method = config.method ? config.method.toLowerCase() : 'get';\n  /*\n    something... 此部分会在后续拦截器单独讲述\n  */\n};\n\n// 在 Axios 原型上挂载 'delete', 'get', 'head', 'options' 且不传参的请求方法，实现内部也是 request\nutils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {\n  Axios.prototype[method] = function(url, config) {\n    return this.request(utils.merge(config || {}, {\n      method: method,\n      url: url\n    }));\n  };\n});\n\n// 在 Axios 原型上挂载 'post', 'put', 'patch' 且传参的请求方法，实现内部同样也是 request\nutils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {\n  Axios.prototype[method] = function(url, data, config) {\n    return this.request(utils.merge(config || {}, {\n      method: method,\n      url: url,\n      data: data\n    }));\n  };\n});\n```\n\n`request`入口参数为`config`，可以说`config`贯彻了`axios`的一生\n\n`axios` 中的 `config `主要分布在这几个地方：\n\n- 默认配置 `defaults.js`\n- `config.method`默认为 `get`\n- 调用 `createInstance` 方法创建 `axios `实例，传入的`config`\n- 直接或间接调用 `request` 方法，传入的 `config`\n\n```js\n// axios.js\n// 创建一个由默认配置生成的axios实例\nvar axios = createInstance(defaults);\n\n// 扩展axios.create工厂函数，内部也是 createInstance\naxios.create = function create(instanceConfig) {\n  return createInstance(mergeConfig(axios.defaults, instanceConfig));\n};\n\n// Axios.js\n// 合并默认配置和传入的配置\nconfig = mergeConfig(this.defaults, config);\n// 设置请求方法\nconfig.method = config.method ? config.method.toLowerCase() : 'get';\n\n```\n\n从源码中，可以看到优先级：默认配置对象`default` < `method:get` < `Axios`的实例属性`this.default` < `request`参数\n\n下面重点看看`request`方法\n\n```js\nAxios.prototype.request = function request(config) {\n  /*\n    先是 mergeConfig ... 等，不再阐述\n  */\n  // Hook up interceptors middleware 创建拦截器链. dispatchRequest 是重中之重，后续重点\n  var chain = [dispatchRequest, undefined];\n\n  // push各个拦截器方法 注意：interceptor.fulfilled 或 interceptor.rejected 是可能为undefined\n  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {\n    // 请求拦截器逆序 注意此处的 forEach 是自定义的拦截器的forEach方法\n    chain.unshift(interceptor.fulfilled, interceptor.rejected);\n  });\n\n  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {\n    // 响应拦截器顺序 注意此处的 forEach 是自定义的拦截器的forEach方法\n    chain.push(interceptor.fulfilled, interceptor.rejected);\n  });\n\n  // 初始化一个promise对象，状态为resolved，接收到的参数为已经处理合并过的config对象\n  var promise = Promise.resolve(config);\n\n  // 循环拦截器的链\n  while (chain.length) {\n    promise = promise.then(chain.shift(), chain.shift()); // 每一次向外弹出拦截器\n  }\n  // 返回 promise\n  return promise;\n};\n```\n\n拦截器`interceptors`是在构建`axios`实例化的属性\n\n```js\nfunction Axios(instanceConfig) {\n  this.defaults = instanceConfig;\n  this.interceptors = {\n    request: new InterceptorManager(), // 请求拦截\n    response: new InterceptorManager() // 响应拦截\n  };\n}\n```\n\n`InterceptorManager`构造函数\n\n```js\n// 拦截器的初始化 其实就是一组钩子函数\nfunction InterceptorManager() {\n  this.handlers = [];\n}\n\n// 调用拦截器实例的use时就是往钩子函数中push方法\nInterceptorManager.prototype.use = function use(fulfilled, rejected) {\n  this.handlers.push({\n    fulfilled: fulfilled,\n    rejected: rejected\n  });\n  return this.handlers.length - 1;\n};\n\n// 拦截器是可以取消的，根据use的时候返回的ID，把某一个拦截器方法置为null\n// 不能用 splice 或者 slice 的原因是 删除之后 id 就会变化，导致之后的顺序或者是操作不可控\nInterceptorManager.prototype.eject = function eject(id) {\n  if (this.handlers[id]) {\n    this.handlers[id] = null;\n  }\n};\n\n// 这就是在 Axios的request方法中 中循环拦截器的方法 forEach 循环执行钩子函数\nInterceptorManager.prototype.forEach = function forEach(fn) {\n  utils.forEach(this.handlers, function forEachHandler(h) {\n    if (h !== null) {\n      fn(h);\n    }\n  });\n}\n```\n\n请求拦截器方法是被 `unshift`到拦截器中，响应拦截器是被`push`到拦截器中的。最终它们会拼接上一个叫`dispatchRequest`的方法被后续的 `promise` 顺序执行\n\n```js\nvar utils = require('./../utils');\nvar transformData = require('./transformData');\nvar isCancel = require('../cancel/isCancel');\nvar defaults = require('../defaults');\nvar isAbsoluteURL = require('./../helpers/isAbsoluteURL');\nvar combineURLs = require('./../helpers/combineURLs');\n\n// 判断请求是否已被取消，如果已经被取消，抛出已取消\nfunction throwIfCancellationRequested(config) {\n  if (config.cancelToken) {\n    config.cancelToken.throwIfRequested();\n  }\n}\n\nmodule.exports = function dispatchRequest(config) {\n  throwIfCancellationRequested(config);\n\n  // 如果包含baseUrl, 并且不是config.url绝对路径，组合baseUrl以及config.url\n  if (config.baseURL && !isAbsoluteURL(config.url)) {\n    // 组合baseURL与url形成完整的请求路径\n    config.url = combineURLs(config.baseURL, config.url);\n  }\n\n  config.headers = config.headers || {};\n\n  // 使用/lib/defaults.js中的transformRequest方法，对config.headers和config.data进行格式化\n  // 比如将headers中的Accept，Content-Type统一处理成大写\n  // 比如如果请求正文是一个Object会格式化为JSON字符串，并添加application/json;charset=utf-8的Content-Type\n  // 等一系列操作\n  config.data = transformData(\n    config.data,\n    config.headers,\n    config.transformRequest\n  );\n\n  // 合并不同配置的headers，config.headers的配置优先级更高\n  config.headers = utils.merge(\n    config.headers.common || {},\n    config.headers[config.method] || {},\n    config.headers || {}\n  );\n\n  // 删除headers中的method属性\n  utils.forEach(\n    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],\n    function cleanHeaderConfig(method) {\n      delete config.headers[method];\n    }\n  );\n\n  // 如果config配置了adapter，使用config中配置adapter的替代默认的请求方法\n  var adapter = config.adapter || defaults.adapter;\n\n  // 使用adapter方法发起请求（adapter根据浏览器环境或者Node环境会有不同）\n  return adapter(config).then(\n    // 请求正确返回的回调\n    function onAdapterResolution(response) {\n      // 判断是否以及取消了请求，如果取消了请求抛出以取消\n      throwIfCancellationRequested(config);\n\n      // 使用/lib/defaults.js中的transformResponse方法，对服务器返回的数据进行格式化\n      // 例如，使用JSON.parse对响应正文进行解析\n      response.data = transformData(\n        response.data,\n        response.headers,\n        config.transformResponse\n      );\n\n      return response;\n    },\n    // 请求失败的回调\n    function onAdapterRejection(reason) {\n      if (!isCancel(reason)) {\n        throwIfCancellationRequested(config);\n\n        if (reason && reason.response) {\n          reason.response.data = transformData(\n            reason.response.data,\n            reason.response.headers,\n            config.transformResponse\n          );\n        }\n      }\n      return Promise.reject(reason);\n    }\n  );\n};\n```\n\n再来看看`axios`是如何实现取消请求的，实现文件在`CancelToken.js`\n\n```js\nfunction CancelToken(executor) {\n  if (typeof executor !== 'function') {\n    throw new TypeError('executor must be a function.');\n  }\n  // 在 CancelToken 上定义一个 pending 状态的 promise ，将 resolve 回调赋值给外部变量 resolvePromise\n  var resolvePromise;\n  this.promise = new Promise(function promiseExecutor(resolve) {\n    resolvePromise = resolve;\n  });\n\n  var token = this;\n  // 立即执行 传入的 executor函数，将真实的 cancel 方法通过参数传递出去。\n  // 一旦调用就执行 resolvePromise 即前面的 promise 的 resolve，就更改promise的状态为 resolve。\n  // 那么xhr中定义的 CancelToken.promise.then方法就会执行, 从而xhr内部会取消请求\n  executor(function cancel(message) {\n    // 判断请求是否已经取消过，避免多次执行\n    if (token.reason) {\n      return;\n    }\n    token.reason = new Cancel(message);\n    resolvePromise(token.reason);\n  });\n}\n\nCancelToken.source = function source() {\n  // source 方法就是返回了一个 CancelToken 实例，与直接使用 new CancelToken 是一样的操作\n  var cancel;\n  var token = new CancelToken(function executor(c) {\n    cancel = c;\n  });\n  // 返回创建的 CancelToken 实例以及取消方法\n  return {\n    token: token,\n    cancel: cancel\n  };\n};\n```\n\n实际上取消请求的操作是在 `xhr.js` 中也有响应的配合的\n\n```js\nif (config.cancelToken) {\n    config.cancelToken.promise.then(function onCanceled(cancel) {\n        if (!request) {\n            return;\n        }\n        // 取消请求\n        request.abort();\n        reject(cancel);\n    });\n}\n```\n\n巧妙的地方在 `CancelToken`中 `executor` 函数，通过`resolve`函数的传递与执行，控制`promise`的状态\n\n\n\n### 小结\n\n ![](https://static.vue-js.com/b1d2ebd0-48b6-11eb-ab90-d9ae814b240d.png)\n\n\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6856706569263677447#heading-4\n- https://juejin.cn/post/6844903907500490766\n- https://github.com/axios/axios"
  },
  {
    "path": "docs/vue/bind.md",
    "content": "#  面试官：双向数据绑定是什么\n\n![](https://static.vue-js.com/cef7dcc0-3ac9-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、什么是双向绑定\n\n我们先从单向绑定切入单向绑定非常简单，就是把`Model`绑定到`View`，当我们用`JavaScript`代码更新`Model`时，`View`就会自动更新双向绑定就很容易联想到了，在单向绑定的基础上，用户更新了`View`，`Model`的数据也自动被更新了，这种情况就是双向绑定举个栗子\n\n ![](https://static.vue-js.com/d65738d0-3ac9-11eb-ab90-d9ae814b240d.png)\n\n当用户填写表单时，`View`的状态就被更新了，如果此时可以自动更新`Model`的状态，那就相当于我们把`Model`和`View`做了双向绑定关系图如下\n\n ![](https://static.vue-js.com/dcc1d4a0-3ac9-11eb-ab90-d9ae814b240d.png)\n\n## 二、双向绑定的原理是什么\n\n我们都知道 `Vue` 是数据双向绑定的框架，双向绑定由三个重要部分构成\n\n- 数据层（Model）：应用的数据及业务逻辑\n- 视图层（View）：应用的展示效果，各类UI组件\n- 业务逻辑层（ViewModel）：框架封装的核心，它负责将数据与视图关联起来\n\n而上面的这个分层的架构方案，可以用一个专业术语进行称呼：`MVVM`这里的控制层的核心功能便是 “数据双向绑定” 。自然，我们只需弄懂它是什么，便可以进一步了解数据绑定的原理\n\n### 理解ViewModel\n\n它的主要职责就是：\n\n- 数据变化后更新视图\n- 视图变化后更新数据\n\n当然，它还有两个主要部分组成\n\n- 监听器（Observer）：对所有数据的属性进行监听\n- 解析器（Compiler）：对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数\n\n### 三、实现双向绑定\n\n我们还是以`Vue`为例，先来看看`Vue`中的双向绑定流程是什么的\n\n1.  `new Vue()`首先执行初始化，对`data`执行响应化处理，这个过程发生`Observe`中\n2.  同时对模板执行编译，找到其中动态绑定的数据，从`data`中获取并初始化视图，这个过程发生在`Compile`中\n3.  同时定义⼀个更新函数和`Watcher`，将来对应数据变化时`Watcher`会调用更新函数\n4.  由于`data`的某个`key`在⼀个视图中可能出现多次，所以每个`key`都需要⼀个管家`Dep`来管理多个`Watcher`\n5.  将来data中数据⼀旦发生变化，会首先找到对应的`Dep`，通知所有`Watcher`执行更新函数\n\n流程图如下：\n\n ![](https://static.vue-js.com/e5369850-3ac9-11eb-85f6-6fac77c0c9b3.png)\n\n### 实现\n\n先来一个构造函数：执行初始化，对`data`执行响应化处理\n\n```js\nclass Vue {  \n  constructor(options) {  \n    this.$options = options;  \n    this.$data = options.data;  \n        \n    // 对data选项做响应式处理  \n    observe(this.$data);  \n        \n    // 代理data到vm上  \n    proxy(this);  \n        \n    // 执行编译  \n    new Compile(options.el, this);  \n  }  \n}  \n```\n\n对`data`选项执行响应化具体操作\n\n```js\nfunction observe(obj) {  \n  if (typeof obj !== \"object\" || obj == null) {  \n    return;  \n  }  \n  new Observer(obj);  \n}  \n  \nclass Observer {  \n  constructor(value) {  \n    this.value = value;  \n    this.walk(value);  \n  }  \n  walk(obj) {  \n    Object.keys(obj).forEach((key) => {  \n      defineReactive(obj, key, obj[key]);  \n    });  \n  }  \n}  \n```\n\n#### 编译`Compile`\n\n对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数\n\n ![](https://static.vue-js.com/f27e19c0-3ac9-11eb-85f6-6fac77c0c9b3.png)\n\n```js\nclass Compile {  \n  constructor(el, vm) {  \n    this.$vm = vm;  \n    this.$el = document.querySelector(el);  // 获取dom  \n    if (this.$el) {  \n      this.compile(this.$el);  \n    }  \n  }  \n  compile(el) {  \n    const childNodes = el.childNodes;   \n    Array.from(childNodes).forEach((node) => { // 遍历子元素  \n      if (this.isElement(node)) {   // 判断是否为节点  \n        console.log(\"编译元素\" + node.nodeName);  \n      } else if (this.isInterpolation(node)) {  \n        console.log(\"编译插值⽂本\" + node.textContent);  // 判断是否为插值文本 {{}}  \n      }  \n      if (node.childNodes && node.childNodes.length > 0) {  // 判断是否有子元素  \n        this.compile(node);  // 对子元素进行递归遍历  \n      }  \n    });  \n  }  \n  isElement(node) {  \n    return node.nodeType == 1;  \n  }  \n  isInterpolation(node) {  \n    return node.nodeType == 3 && /\\{\\{(.*)\\}\\}/.test(node.textContent);  \n  }  \n}  \n  \n```\n\n#### 依赖收集\n\n视图中会用到`data`中某`key`，这称为依赖。同⼀个`key`可能出现多次，每次都需要收集出来用⼀个`Watcher`来维护它们，此过程称为依赖收集多个`Watcher`需要⼀个`Dep`来管理，需要更新时由`Dep`统⼀通知\n\n ![](https://static.vue-js.com/fa191f40-3ac9-11eb-ab90-d9ae814b240d.png)\n\n实现思路\n\n 1. `defineReactive`时为每⼀个`key`创建⼀个`Dep`实例\n 2. 初始化视图时读取某个`key`，例如`name1`，创建⼀个`watcher1`\n 3. 由于触发`name1`的`getter`方法，便将`watcher1`添加到`name1`对应的Dep中\n 4. 当`name1`更新，`setter`触发时，便可通过对应`Dep`通知其管理所有`Watcher`更新\n\n```js\n// 负责更新视图  \nclass Watcher {  \n  constructor(vm, key, updater) {  \n    this.vm = vm  \n    this.key = key  \n    this.updaterFn = updater  \n  \n    // 创建实例时，把当前实例指定到Dep.target静态属性上  \n    Dep.target = this  \n    // 读一下key，触发get  \n    vm[key]  \n    // 置空  \n    Dep.target = null  \n  }  \n  \n  // 未来执行dom更新函数，由dep调用的  \n  update() {  \n    this.updaterFn.call(this.vm, this.vm[this.key])  \n  }  \n}  \n```\n\n声明`Dep`\n\n```js\nclass Dep {  \n  constructor() {  \n    this.deps = [];  // 依赖管理  \n  }  \n  addDep(dep) {  \n    this.deps.push(dep);  \n  }  \n  notify() {   \n    this.deps.forEach((dep) => dep.update());  \n  }  \n}  \n```\n\n创建`watcher`时触发`getter`\n\n```js\nclass Watcher {  \n  constructor(vm, key, updateFn) {  \n    Dep.target = this;  \n    this.vm[this.key];  \n    Dep.target = null;  \n  }  \n}  \n  \n```\n\n依赖收集，创建`Dep`实例\n\n```js\nfunction defineReactive(obj, key, val) {  \n  this.observe(val);  \n  const dep = new Dep();  \n  Object.defineProperty(obj, key, {  \n    get() {  \n      Dep.target && dep.addDep(Dep.target);// Dep.target也就是Watcher实例  \n      return val;  \n    },  \n    set(newVal) {  \n      if (newVal === val) return;  \n      dep.notify(); // 通知dep执行更新方法  \n    },  \n  });  \n}  \n```\n\n## 参考文献\n\n- https://www.liaoxuefeng.com/wiki/1022910821149312/1109527162256416\n- https://juejin.cn/post/6844903942254510087#heading-9\n\n  \n\n面试官VUE系列总进度：3／33\n\n[面试官：说说你对vue的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484101&idx=1&sn=83b0983f0fca7d7c556e4cb0bff8c9b8&chksm=fc10c093cb674985ef3bd2966f66fc28c5eb70b0037e4be1af4bf54fb6fa9571985abd31d52f&scene=21#wechat_redirect)  \n[面试官：说说你对SPA（单页应用）的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484119&idx=1&sn=d171b28a00d42549d279498944a98519&chksm=fc10c081cb6749976814aaeda6a6433db418223cec57edda7e15b9e5a0ca69ad549655639c61&scene=21#wechat_redirect)\n\n![](https://static.vue-js.com/821b87b0-3ac6-11eb-ab90-d9ae814b240d.png)"
  },
  {
    "path": "docs/vue/communication.md",
    "content": "# 面试官：Vue组件之间的通信方式都有哪些？\n\n![](https://static.vue-js.com/7de50d20-3aca-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、组件间通信的概念  \n\n开始之前，我们把**组件间通信**这个词进行拆分\n\n- 组件\n- 通信\n\n都知道组件是`vue`最强大的功能之一，`vue`中每一个`.vue`我们都可以视之为一个组件通信指的是发送者通过某种媒体以某种格式来传递信息到收信者以达到某个目的。广义上，任何信息的交通都是通信**组件间通信**即指组件\\(`.vue`\\)通过某种方式来传递信息以达到某个目的举个栗子我们在使用`UI`框架中的`table`组件，可能会往`table`组件中传入某些数据，这个本质就形成了组件之间的通信\n\n## 二、组件间通信解决了什么\n\n在古代，人们通过驿站、飞鸽传书、烽火报警、符号、语言、眼神、触碰等方式进行信息传递，到了今天，随着科技水平的飞速发展，通信基本完全利用有线或无线电完成，相继出现了有线电话、固定电话、无线电话、手机、互联网甚至视频电话等各种通信方式从上面这段话，我们可以看到通信的本质是信息同步，共享回到`vue`中，每个组件之间的都有独自的作用域，组件间的数据是无法共享的但实际开发工作中我们常常需要让组件之间共享数据，这也是组件通信的目的要让它们互相之间能进行通讯，这样才能构成一个有机的完整系统\n\n## 二、组件间通信的分类\n\n组件间通信的分类可以分成以下\n\n- 父子组件之间的通信\n- 兄弟组件之间的通信\n- 祖孙与后代组件之间的通信\n- 非关系组件间之间的通信\n\n关系图:\n\n ![](https://static.vue-js.com/85b92400-3aca-11eb-ab90-d9ae814b240d.png)\n\n## 三、组件间通信的方案\n\n整理`vue`中8种常规的通信方案\n\n1.  通过 props 传递\n2.  通过 \\$emit 触发自定义事件\n3.  使用 ref\n4.  EventBus\n5.  $parent 或$root\n6.  attrs 与 listeners\n7.  Provide 与 Inject\n8.  Vuex\n\n### props传递数据\n\n ![](https://static.vue-js.com/8f80a670-3aca-11eb-ab90-d9ae814b240d.png)\n\n- 适用场景：父组件传递数据给子组件\n- 子组件设置`props`属性，定义接收父组件传递过来的参数\n- 父组件在使用子组件标签中通过字面量来传递值\n\n`Children.vue`\n\n```js\nprops:{  \n    // 字符串形式  \n name:String // 接收的类型参数  \n    // 对象形式  \n    age:{    \n        type:Number, // 接收的类型为数值  \n        defaule:18,  // 默认值为18  \n       require:true // age属性必须传递  \n    }  \n}  \n```\n\n`Father.vue`组件\n\n```js\n<Children name=\"jack\" age=18 />  \n```\n\n### \\$emit 触发自定义事件\n\n- 适用场景：子组件传递数据给父组件\n- 子组件通过`$emit触发`自定义事件，`$emit`第二个参数为传递的数值\n- 父组件绑定监听器获取到子组件传递过来的参数\n\n`Chilfen.vue`\n\n```js\nthis.$emit('add', good)  \n```\n\n`Father.vue`\n\n```js\n<Children @add=\"cartAdd($event)\" />  \n```\n\n### ref\n\n- 父组件在使用子组件的时候设置`ref`\n- 父组件通过设置子组件`ref`来获取数据\n\n父组件\n\n```js\n<Children ref=\"foo\" />  \n  \nthis.$refs.foo  // 获取子组件实例，通过子组件实例我们就能拿到对应的数据  \n```\n\n### EventBus\n\n- 使用场景：兄弟组件传值\n- 创建一个中央事件总线`EventBus`\n- 兄弟组件通过`$emit`触发自定义事件，`$emit`第二个参数为传递的数值\n- 另一个兄弟组件通过`$on`监听自定义事件\n\n`Bus.js`\n\n```js\n// 创建一个中央时间总线类  \nclass Bus {  \n  constructor() {  \n    this.callbacks = {};   // 存放事件的名字  \n  }  \n  $on(name, fn) {  \n    this.callbacks[name] = this.callbacks[name] || [];  \n    this.callbacks[name].push(fn);  \n  }  \n  $emit(name, args) {  \n    if (this.callbacks[name]) {  \n      this.callbacks[name].forEach((cb) => cb(args));  \n    }  \n  }  \n}  \n  \n// main.js  \nVue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  \n// 另一种方式  \nVue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能  \n```\n\n`Children1.vue`\n\n```js\nthis.$bus.$emit('foo')  \n```\n\n`Children2.vue`\n\n```js\nthis.$bus.$on('foo', this.handle)  \n```\n\n### $parent 或$ root\n\n- 通过共同祖辈`$parent`或者`$root`搭建通信桥连\n\n兄弟组件\n\n`this.$parent.on('add',this.add)  \n`\n\n另一个兄弟组件\n\n`this.$parent.emit('add')  \n`\n\n### $attrs  与$ listeners\n\n -    适用场景：祖先传递数据给子孙\n -    设置批量向下传属性`$attrs`和 `$listeners`\n -    包含了父级作用域中不作为 `prop` 被识别 \\(且获取\\) 的特性绑定 \\( class 和 style 除外\\)。\n -    可以通过 `v-bind=\"$attrs\"` 传⼊内部组件\n\n```js\n// child：并未在props中声明foo  \n<p>{{$attrs.foo}}</p>  \n  \n// parent  \n<HelloWorld foo=\"foo\"/>  \n```\n\n```js\n// 给Grandson隔代传值，communication/index.vue  \n<Child2 msg=\"lalala\" @some-event=\"onSomeEvent\"></Child2>  \n  \n// Child2做展开  \n<Grandson v-bind=\"$attrs\" v-on=\"$listeners\"></Grandson>  \n  \n// Grandson使⽤  \n<div @click=\"$emit('some-event', 'msg from grandson')\">  \n{{msg}}  \n</div>  \n```\n\n### provide 与 inject\n\n- 在祖先组件定义`provide`属性，返回传递的值\n- 在后代组件通过`inject`接收组件传递过来的值\n\n祖先组件\n\n```js\nprovide(){  \n    return {  \n        foo:'foo'  \n    }  \n}  \n```\n\n后代组件\n\n```js\ninject:['foo'] // 获取到祖先组件传递过来的值  \n```\n\n### `vuex`\n\n- 适用场景: 复杂关系的组件数据传递\n- `Vuex`作用相当于一个用来存储共享变量的容器\n ![](https://static.vue-js.com/fa207cd0-3aca-11eb-ab90-d9ae814b240d.png)\n\n- `state`用来存放共享变量的地方\n- `getter`，可以增加一个`getter`派生状态，\\(相当于`store`中的计算属性），用来获得共享变量的值\n- `mutations`用来存放修改`state`的方法。\n- `actions`也是用来存放修改state的方法，不过`action`是在`mutations`的基础上进行。常用来做一些异步操作\n\n### 小结\n\n- 父子关系的组件数据传递选择 `props`  与 `$emit`进行传递，也可选择`ref`\n- 兄弟关系的组件数据传递可选择`$bus`，其次可以选择`$parent`进行传递\n- 祖先与后代组件数据传递可选择`attrs`与`listeners`或者 `Provide`与 `Inject`\n- 复杂关系的组件数据传递可以通过`vuex`存放共享的变量\n\n## 参考文献\n\n- https://juejin.cn/post/6844903990052782094#heading-0\n- https://zh.wikipedia.org/wiki/\\%E9\\%80\\%9A\\%E4\\%BF\\%A1\n- https://vue3js.cn/docs/zh\n\n  \n\n面试官VUE系列总进度：5／33\n\n[面试官：说说你对vue的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484101&idx=1&sn=83b0983f0fca7d7c556e4cb0bff8c9b8&chksm=fc10c093cb674985ef3bd2966f66fc28c5eb70b0037e4be1af4bf54fb6fa9571985abd31d52f&scene=21#wechat_redirect)  \n\n[面试官：说说你对SPA（单页应用）的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484119&idx=1&sn=d171b28a00d42549d279498944a98519&chksm=fc10c081cb6749976814aaeda6a6433db418223cec57edda7e15b9e5a0ca69ad549655639c61&scene=21#wechat_redirect)\n\n[面试官：说说你对双向绑定的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484167&idx=1&sn=7b00b4333ab2722f25f12586b70667ca&chksm=fc10c151cb6748476008dab2f4e6c6264f5d19678305955c85cec1b619e56e8f7457b7357fb9&scene=21#wechat_redirect)  \n\n[面试官：说说你对Vue生命周期的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484176&idx=1&sn=5623421ed2678046ed9e438aadf6e26f&chksm=fc10c146cb67485015f24f7e9f5862c4c685fc33485fe30e1b375a534b4031978439c554e0c0&scene=21#wechat_redirect)  \n\n ![](https://static.vue-js.com/821b87b0-3ac6-11eb-ab90-d9ae814b240d.png)\n"
  },
  {
    "path": "docs/vue/components_plugin.md",
    "content": "# 面试官：Vue中组件和插件有什么区别？\n\n![image.png](https://static.vue-js.com/683475e0-3acc-11eb-ab90-d9ae814b240d.png)\n\n## 一、组件是什么\n\n回顾以前对组件的定义：\n\n组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念（组件）来实现开发的模式，在`Vue`中每一个`.vue`文件都可以视为一个组件\n\n组件的优势\n\n- 降低整个系统的耦合度，在保持接口不变的情况下，我们可以替换不同的组件快速完成需求，例如输入框，可以替换为日历、时间、范围等组件作具体的实现\n\n- 调试方便，由于整个系统是通过组件组合起来的，在出现问题的时候，可以用排除法直接移除组件，或者根据报错的组件快速定位问题，之所以能够快速定位，是因为每个组件之间低耦合，职责单一，所以逻辑会比分析整个系统要简单\n\n- 提高可维护性，由于每个组件的职责单一，并且组件在系统中是被复用的，所以对代码进行优化可获得系统的整体升级\n\n## 二、插件是什么\n\n插件通常用来为 `Vue` 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种：\n\n- 添加全局方法或者属性。如: `vue-custom-element`\n- 添加全局资源：指令/过滤器/过渡等。如 `vue-touch`\n- 通过全局混入来添加一些组件选项。如` vue-router`\n- 添加 `Vue` 实例方法，通过把它们添加到 `Vue.prototype` 上实现。\n- 一个库，提供自己的 `API`，同时提供上面提到的一个或多个功能。如` vue-router`\n\n## 三、两者的区别\n\n两者的区别主要表现在以下几个方面：\n\n- 编写形式\n- 注册形式\n- 使用场景\n\n\n### 编写形式\n\n#### 编写组件\n\n编写一个组件，可以有很多方式，我们最常见的就是`vue`单文件的这种格式，每一个`.vue`文件我们都可以看成是一个组件\n\n`vue`文件标准格式\n\n```vue\n<template>\n</template>\n<script>\nexport default{ \n    ...\n}\n</script>\n<style>\n</style>\n```\n\n我们还可以通过`template`属性来编写一个组件，如果组件内容多，我们可以在外部定义`template`组件内容，如果组件内容并不多，我们可直接写在`template`属性上\n\n```js\n<template id=\"testComponent\">     // 组件显示的内容\n    <div>component!</div>   \n</template>\n\nVue.component('componentA',{ \n    template: '#testComponent'  \n    template: `<div>component</div>`  // 组件内容少可以通过这种形式\n})\n```\n\n#### 编写插件\n`vue`插件的实现应该暴露一个 `install` 方法。这个方法的第一个参数是 `Vue` 构造器，第二个参数是一个可选的选项对象\n\n```js\nMyPlugin.install = function (Vue, options) {\n  // 1. 添加全局方法或 property\n  Vue.myGlobalMethod = function () {\n    // 逻辑...\n  }\n\n  // 2. 添加全局资源\n  Vue.directive('my-directive', {\n    bind (el, binding, vnode, oldVnode) {\n      // 逻辑...\n    }\n    ...\n  })\n\n  // 3. 注入组件选项\n  Vue.mixin({\n    created: function () {\n      // 逻辑...\n    }\n    ...\n  })\n\n  // 4. 添加实例方法\n  Vue.prototype.$myMethod = function (methodOptions) {\n    // 逻辑...\n  }\n}\n```\n\n\n### 注册形式\n\n#### 组件注册\n\n`vue`组件注册主要分为全局注册与局部注册\n\n全局注册通过`Vue.component`方法，第一个参数为组件的名称，第二个参数为传入的配置项\n\n```js\nVue.component('my-component-name', { /* ... */ })\n```\n\n局部注册只需在用到的地方通过`components`属性注册一个组件\n\n```js\nconst component1 = {...} // 定义一个组件\n\nexport default {\n\tcomponents:{\n\t\tcomponent1   // 局部注册\n\t}\n}\n```\n\n\n#### 插件注册\n插件的注册通过`Vue.use()`的方式进行注册（安装），第一个参数为插件的名字，第二个参数是可选择的配置项\n\n```js\nVue.use(插件名字,{ /* ... */} )\n```\n\n注意的是：\n\n注册插件的时候，需要在调用 `new Vue()` 启动应用之前完成\n\n`Vue.use`会自动阻止多次注册相同插件，只会注册一次\n\n\n\n### 使用场景\n\n具体的其实在插件是什么章节已经表述了，这里在总结一下\n\n组件 `(Component)` 是用来构成你的 `App` 的业务模块，它的目标是 `App.vue`\n\n插件 `(Plugin)` 是用来增强你的技术栈的功能模块，它的目标是 `Vue` 本身\n\n简单来说，插件就是指对`Vue`的功能的增强或补充\n\n\n## 参考文献\n\n- https://vue3js.cn/docs/zh"
  },
  {
    "path": "docs/vue/cors.md",
    "content": "# 面试官：Vue项目中你是如何解决跨域的呢？\n\n![](https://static.vue-js.com/db3045b0-4e31-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、跨域是什么\n\n跨域本质是浏览器基于**同源策略**的一种安全手段\n\n同源策略（Sameoriginpolicy），是一种约定，它是浏览器最核心也最基本的安全功能\n\n所谓同源（即指在同一个域）具有以下三个相同点\n- 协议相同（protocol）\n- 主机相同（host）\n- 端口相同（port）\n\n反之非同源请求，也就是协议、端口、主机其中一项不相同的时候，这时候就会产生跨域\n\n>一定要注意跨域是浏览器的限制，你用抓包工具抓取接口数据，是可以看到接口已经把数据返回回来了，只是浏览器的限制，你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。\n\n## 二、如何解决\n\n解决跨域的方法有很多，下面列举了三种：\n\n- JSONP\n- CORS\n- Proxy\n\n而在`vue`项目中，我们主要针对`CORS`或`Proxy`这两种方案进行展开\n\n### CORS\n\nCORS （Cross-Origin Resource Sharing，跨域资源共享）是一个系统，它由一系列传输的HTTP头组成，这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应\n\n`CORS` 实现起来非常方便，只需要增加一些 `HTTP` 头，让服务器能声明允许的访问来源\n\n只要后端实现了 `CORS`，就实现了跨域\n\n ![](https://static.vue-js.com/140deb80-4e32-11eb-ab90-d9ae814b240d.png)\n\n以` koa`框架举例\n\n添加中间件，直接设置`Access-Control-Allow-Origin`响应头\n\n```js\napp.use(async (ctx, next)=> {\n  ctx.set('Access-Control-Allow-Origin', '*');\n  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');\n  ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');\n  if (ctx.method == 'OPTIONS') {\n    ctx.body = 200; \n  } else {\n    await next();\n  }\n})\n```\n\nps: `Access-Control-Allow-Origin` 设置为*其实意义不大，可以说是形同虚设，实际应用中，上线前我们会将`Access-Control-Allow-Origin` 值设为我们目标`host`\n\n### Proxy\n代理（Proxy）也称网络代理，是一种特殊的网络服务，允许一个（一般为客户端）通过这个服务与另一个网络终端（一般为服务器）进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全，防止攻击\n\n\n**方案一**\n\n如果是通过`vue-cli`脚手架工具搭建项目，我们可以通过`webpack`为我们起一个本地服务器作为请求的代理对象\n\n通过该服务器转发请求至目标服务器，得到结果再转发给前端，但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域\n\n在`vue.config.js`文件，新增以下代码\n\n```js\namodule.exports = {\n    devServer: {\n        host: '127.0.0.1',\n        port: 8084,\n        open: true,// vue项目启动时自动打开浏览器\n        proxy: {\n            '/api': { // '/api'是代理标识，用于告诉node，url前面是/api的就是使用代理的\n                target: \"http://xxx.xxx.xx.xx:8080\", //目标地址，一般是指后台服务器地址\n                changeOrigin: true, //是否跨域\n                pathRewrite: { // pathRewrite 的作用是把实际Request Url中的'/api'用\"\"代替\n                    '^/api': \"\" \n                }\n            }\n        }\n    }\n}\n```\n\n通过`axios`发送请求中，配置请求的根路径\n\n```js\naxios.defaults.baseURL = '/api'\n```\n\n\n\n**方案二**\n\n此外，还可通过服务端实现代理请求转发\n\n以`express`框架为例\n\n```js\nvar express = require('express');\nconst proxy = require('http-proxy-middleware')\nconst app = express()\napp.use(express.static(__dirname + '/'))\napp.use('/api', proxy({ target: 'http://localhost:4000', changeOrigin: false\n                      }));\nmodule.exports = app\n```\n\n\n\n**方案三**\n\n通过配置`nginx`实现代理\n\n```js\nserver {\n    listen    80;\n    # server_name www.josephxia.com;\n    location / {\n        root  /var/www/html;\n        index  index.html index.htm;\n        try_files $uri $uri/ /index.html;\n    }\n    location /api {\n        proxy_pass  http://127.0.0.1:3000;\n        proxy_redirect   off;\n        proxy_set_header  Host       $host;\n        proxy_set_header  X-Real-IP     $remote_addr;\n        proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;\n    }\n}\n```\n"
  },
  {
    "path": "docs/vue/data.md",
    "content": "# 面试官：为什么data属性是一个函数而不是一个对象？\n\n\n ![](https://static.vue-js.com/83e51560-3acc-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、实例和组件定义data的区别\n\n`vue`实例的时候定义`data`属性既可以是一个对象，也可以是一个函数\n\n```js\nconst app = new Vue({\n    el:\"#app\",\n    // 对象格式\n    data:{\n        foo:\"foo\"\n    },\n    // 函数格式\n    data(){\n        return {\n             foo:\"foo\"\n        }\n    }\n})\n```\n\n组件中定义`data`属性，只能是一个函数\n\n如果为组件`data`直接定义为一个对象\n\n```js\nVue.component('component1',{\n    template:`<div>组件</div>`,\n    data:{\n        foo:\"foo\"\n    }\n})\n```\n\n则会得到警告信息\n\n\n ![](https://static.vue-js.com/8e6fc0c0-3acc-11eb-ab90-d9ae814b240d.png)\n\n\n警告说明：返回的`data`应该是一个函数在每一个组件实例中\n\n## 二、组件data定义函数与对象的区别\n\n上面讲到组件`data`必须是一个函数，不知道大家有没有思考过这是为什么呢？\n\n在我们定义好一个组件的时候，`vue`最终都会通过`Vue.extend()`构成组件实例\n\n这里我们模仿组件构造函数，定义`data`属性，采用对象的形式\n\n```js\nfunction Component(){\n \n}\nComponent.prototype.data = {\n\tcount : 0\n}\n```\n\n创建两个组件实例\n\n```\nconst componentA = new Component()\nconst componentB = new Component()\n```\n\n修改`componentA`组件`data`属性的值，`componentB`中的值也发生了改变\n\n```js\nconsole.log(componentB.data.count)  // 0\ncomponentA.data.count = 1\nconsole.log(componentB.data.count)  // 1\n```\n\n产生这样的原因这是两者共用了同一个内存地址，`componentA`修改的内容，同样对`componentB`产生了影响\n\n如果我们采用函数的形式，则不会出现这种情况（函数返回的对象内存地址并不相同）\n\n```js\nfunction Component(){\n\tthis.data = this.data()\n}\nComponent.prototype.data = function (){\n    return {\n   \t\tcount : 0\n    }\n}\n```\n\n修改`componentA`组件`data`属性的值，`componentB`中的值不受影响\n\n```js\nconsole.log(componentB.data.count)  // 0\ncomponentA.data.count = 1\nconsole.log(componentB.data.count)  // 0\n```\n\n`vue`组件可能会有很多个实例，采用函数返回一个全新`data`形式，使每个实例对象的数据不会受到其他实例对象数据的污染\n\n## 三、原理分析\n\n首先可以看看`vue`初始化`data`的代码，`data`的定义可以是函数也可以是对象\n\n源码位置：`/vue-dev/src/core/instance/state.js`\n\n```js\nfunction initData (vm: Component) {\n  let data = vm.$options.data\n  data = vm._data = typeof data === 'function'\n    ? getData(data, vm)\n    : data || {}\n    ...\n}\n```\n`data`既能是`object`也能是`function`，那为什么还会出现上文警告呢？\n\n别急，继续看下文\n\n组件在创建的时候，会进行选项的合并\n\n源码位置：`/vue-dev/src/core/util/options.js`\n\n自定义组件会进入`mergeOptions`进行选项合并\n\n```js\nVue.prototype._init = function (options?: Object) {\n    ...\n    // merge options\n    if (options && options._isComponent) {\n      // optimize internal component instantiation\n      // since dynamic options merging is pretty slow, and none of the\n      // internal component options needs special treatment.\n      initInternalComponent(vm, options)\n    } else {\n      vm.$options = mergeOptions(\n        resolveConstructorOptions(vm.constructor),\n        options || {},\n        vm\n      )\n    }\n    ...\n  }\n```\n\n定义`data`会进行数据校验\n\n源码位置：`/vue-dev/src/core/instance/init.js`\n\n这时候`vm`实例为`undefined`，进入`if`判断，若`data`类型不是`function`，则出现警告提示\n\n```js\nstrats.data = function (\n  parentVal: any,\n  childVal: any,\n  vm?: Component\n): ?Function {\n  if (!vm) {\n    if (childVal && typeof childVal !== \"function\") {\n      process.env.NODE_ENV !== \"production\" &&\n        warn(\n          'The \"data\" option should be a function ' +\n            \"that returns a per-instance value in component \" +\n            \"definitions.\",\n          vm\n        );\n\n      return parentVal;\n    }\n    return mergeDataOrFn(parentVal, childVal);\n  }\n  return mergeDataOrFn(parentVal, childVal, vm);\n};\n```\n\n### 四、结论\n\n- 根实例对象`data`可以是对象也可以是函数（根实例是单例），不会产生数据污染情况\n- 组件实例对象`data`必须为函数，目的是为了防止多个组件实例对象之间共用一个`data`，产生数据污染。采用函数的形式，`initData`时会将其作为工厂函数都会返回全新`data`对象"
  },
  {
    "path": "docs/vue/data_object_add_attrs.md",
    "content": "# 面试官：动态给vue的data添加一个新的属性时会发生什么？怎样解决？\n\n![image.png](https://static.vue-js.com/a502dde0-3acc-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、直接添加属性的问题\n\n我们从一个例子开始\n\n定义一个`p`标签，通过`v-for`指令进行遍历\n\n然后给`botton`标签绑定点击事件，我们预期点击按钮时，数据新增一个属性，界面也 新增一行\n\n```html\n<p v-for=\"(value,key) in item\" :key=\"key\">\n    {{ value }}\n</p>\n<button @click=\"addProperty\">动态添加新属性</button>\n```\n\n实例化一个`vue`实例，定义`data`属性和`methods`方法\n\n```js\nconst app = new Vue({\n    el:\"#app\",\n   \tdata:()=>{\n       \titem:{\n            oldProperty:\"旧属性\"\n        }\n    },\n    methods:{\n        addProperty(){\n            this.items.newProperty = \"新属性\"  // 为items添加新属性\n            console.log(this.items)  // 输出带有newProperty的items\n        }\n    }\n})\n```\n\n点击按钮，发现结果不及预期，数据虽然更新了（`console`打印出了新属性），但页面并没有更新\n\n\n## 二、原理分析\n\n为什么产生上面的情况呢？\n\n下面来分析一下\n\n`vue2`是用过`Object.defineProperty`实现数据响应式\n\n```js\nconst obj = {}\nObject.defineProperty(obj, 'foo', {\n        get() {\n            console.log(`get foo:${val}`);\n            return val\n        },\n        set(newVal) {\n            if (newVal !== val) {\n                console.log(`set foo:${newVal}`);\n                val = newVal\n            }\n        }\n    })\n}\n```\n\n当我们访问`foo`属性或者设置`foo`值的时候都能够触发`setter`与`getter`\n\n```js\nobj.foo   \nobj.foo = 'new'\n```\n\n但是我们为`obj`添加新属性的时候，却无法触发事件属性的拦截\n\n```js\nobj.bar  = '新属性'\n```\n\n原因是一开始`obj`的`foo`属性被设成了响应式数据，而`bar`是后面新增的属性，并没有通过`Object.defineProperty`设置成响应式数据\n\n## 三、解决方案\n\n`Vue` 不允许在已经创建的实例上动态添加新的响应式属性\n\n若想实现数据与视图同步更新，可采取下面三种解决方案：\n\n- Vue.set()\n- Object.assign()\n- $forcecUpdated()\n\n\n\n### Vue.set()\n\nVue.set( target, propertyName/index, value )\n\n参数\n\n- `{Object | Array} target`\n- `{string | number} propertyName/index`\n- `{any} value`\n\n返回值：设置的值\n\n通过`Vue.set`向响应式对象中添加一个`property`，并确保这个新 `property `同样是响应式的，且触发视图更新\n\n关于`Vue.set`源码（省略了很多与本节不相关的代码）\n\n源码位置：`src\\core\\observer\\index.js`\n\n```js\nfunction set (target: Array<any> | Object, key: any, val: any): any {\n  ...\n  defineReactive(ob.value, key, val)\n  ob.dep.notify()\n  return val\n}\n```\n\n这里无非再次调用`defineReactive`方法，实现新增属性的响应式\n\n关于`defineReactive`方法，内部还是通过`Object.defineProperty`实现属性拦截\n\n大致代码如下：\n\n```js\nfunction defineReactive(obj, key, val) {\n    Object.defineProperty(obj, key, {\n        get() {\n            console.log(`get ${key}:${val}`);\n            return val\n        },\n        set(newVal) {\n            if (newVal !== val) {\n                console.log(`set ${key}:${newVal}`);\n                val = newVal\n            }\n        }\n    })\n}\n```\n\n\n\n### Object.assign()\n\n直接使用`Object.assign()`添加到对象的新属性不会触发更新\n\n应创建一个新的对象，合并原对象和混入对象的属性\n\n```js\nthis.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})\n```\n\n\n\n### $forceUpdate\n\n如果你发现你自己需要在 `Vue `中做一次强制更新，99.9% 的情况，是你在某个地方做错了事\n\n`$forceUpdate`迫使` Vue` 实例重新渲染\n\nPS：仅仅影响实例本身和插入插槽内容的子组件，而不是所有子组件。\n\n\n\n### 小结\n\n- 如果为对象添加少量的新属性，可以直接采用`Vue.set()`\n\n- 如果需要为新对象添加大量的新属性，则通过`Object.assign()`创建新对象\n\n- 如果你实在不知道怎么操作时，可采取`$forceUpdate()`进行强制刷新 (不建议)\n  \n\nPS：`vue3`是用过`proxy`实现数据响应式的，直接动态添加新属性仍可以实现数据响应式\n\n\n## 参考文献\n\n- https://cn.vuejs.org/v2/api/#Vue-set\n- https://vue3js.cn/docs/zh"
  },
  {
    "path": "docs/vue/diff.md",
    "content": "# 面试官：你了解vue的diff算法吗？说说看\n\n![](https://static.vue-js.com/5e858e30-4585-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n`diff` 算法是一种通过同层的树节点进行比较的高效算法\n\n其有两个特点：\n- 比较只会在同层级进行, 不会跨层级比较\n- 在diff比较的过程中，循环从两边向中间比较\n\n`diff` 算法在很多场景下都有应用，在 `vue` 中，作用于虚拟 `dom` 渲染成真实 `dom` 的新旧 `VNode` 节点比较\n\n## 二、比较方式\n\n`diff`整体策略为：深度优先，同层比较\n\n1. 比较只会在同层级进行, 不会跨层级比较\n\n<img src=\"https://static001.infoq.cn/resource/image/91/54/91e9c9519a11caa0c5bf70714383f054.png\" alt=\"img\" style=\"zoom:50%;\" />\n\n2. 比较的过程中，循环从两边向中间收拢\n\n<img src=\"https://static001.infoq.cn/resource/image/2d/ec/2dcd6ad5cf82c65b9cfc43a27ba1e4ec.png\" alt=\"img\" style=\"zoom:50%;\" />\n\n下面举个`vue`通过`diff`算法更新的例子：\n\n新旧`VNode`节点如下图所示：\n\n![](https://static001.infoq.cn/resource/image/80/6d/80dc339f73b186479e6d1fc18bfbf66d.png)\n\n第一次循环后，发现旧节点D与新节点D相同，直接复用旧节点D作为`diff`后的第一个真实节点，同时旧节点`endIndex`移动到C，新节点的 `startIndex` 移动到了 C\n\n![](https://static001.infoq.cn/resource/image/76/54/76032c78c8ef74047efd42c070e48854.png)\n\n第二次循环后，同样是旧节点的末尾和新节点的开头(都是 C)相同，同理，`diff` 后创建了 C 的真实节点插入到第一次创建的 D 节点后面。同时旧节点的 `endIndex` 移动到了 B，新节点的 `startIndex` 移动到了 E\n\n![](https://static001.infoq.cn/resource/image/1c/d7/1c76e7489660188d35f0a38ea8c8ecd7.png)\n\n第三次循环中，发现E没有找到，这时候只能直接创建新的真实节点 E，插入到第二次创建的 C 节点之后。同时新节点的 `startIndex` 移动到了 A。旧节点的 `startIndex` 和 `endIndex` 都保持不动\n\n![](https://static001.infoq.cn/resource/image/4b/08/4b622c0d61673ec5474465d82305d308.png)\n\n第四次循环中，发现了新旧节点的开头(都是 A)相同，于是 `diff` 后创建了 A 的真实节点，插入到前一次创建的 E 节点后面。同时旧节点的 `startIndex` 移动到了 B，新节点的` startIndex` 移动到了 B\n\n![](https://static001.infoq.cn/resource/image/59/b4/5982417c3e0b2fa9ae940354a0e67ab4.png)\n\n第五次循环中，情形同第四次循环一样，因此 `diff` 后创建了 B 真实节点 插入到前一次创建的 A 节点后面。同时旧节点的 `startIndex `移动到了 C，新节点的 startIndex 移动到了 F\n\n![](https://static001.infoq.cn/resource/image/16/86/16cf0ef90f6e19d26c0ddffeca067e86.png)\n\n新节点的 `startIndex` 已经大于 `endIndex` 了，需要创建 `newStartIdx` 和 `newEndIdx` 之间的所有节点，也就是节点F，直接创建 F 节点对应的真实节点放到 B 节点后面\n\n![](https://static001.infoq.cn/resource/image/dc/ad/dc215b45682cf6c9cc4700a5425673ad.png)\n\n## 三、原理分析\n\n当数据发生改变时，`set`方法会调用`Dep.notify`通知所有订阅者`Watcher`，订阅者就会调用`patch`给真实的`DOM`打补丁，更新相应的视图\n\n源码位置：src/core/vdom/patch.js\n\n```js\nfunction patch(oldVnode, vnode, hydrating, removeOnly) {\n    if (isUndef(vnode)) { // 没有新节点，直接执行destory钩子函数\n        if (isDef(oldVnode)) invokeDestroyHook(oldVnode)\n        return\n    }\n\n    let isInitialPatch = false\n    const insertedVnodeQueue = []\n\n    if (isUndef(oldVnode)) {\n        isInitialPatch = true\n        createElm(vnode, insertedVnodeQueue) // 没有旧节点，直接用新节点生成dom元素\n    } else {\n        const isRealElement = isDef(oldVnode.nodeType)\n        if (!isRealElement && sameVnode(oldVnode, vnode)) {\n            // 判断旧节点和新节点自身一样，一致执行patchVnode\n            patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)\n        } else {\n            // 否则直接销毁及旧节点，根据新节点生成dom元素\n            if (isRealElement) {\n\n                if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {\n                    oldVnode.removeAttribute(SSR_ATTR)\n                    hydrating = true\n                }\n                if (isTrue(hydrating)) {\n                    if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {\n                        invokeInsertHook(vnode, insertedVnodeQueue, true)\n                        return oldVnode\n                    }\n                }\n                oldVnode = emptyNodeAt(oldVnode)\n            }\n            return vnode.elm\n        }\n    }\n}\n```\n\n`patch`函数前两个参数位为`oldVnode` 和 `Vnode` ，分别代表新的节点和之前的旧节点，主要做了四个判断：\n\n- 没有新节点，直接触发旧节点的`destory`钩子\n- 没有旧节点，说明是页面刚开始初始化的时候，此时，根本不需要比较了，直接全是新建，所以只调用 `createElm`\n- 旧节点和新节点自身一样，通过 `sameVnode` 判断节点是否一样，一样时，直接调用 `patchVnode `去处理这两个节点\n- 旧节点和新节点自身不一样，当两个节点不一样的时候，直接创建新节点，删除旧节点\n\n下面主要讲的是`patchVnode`部分\n\n```js\nfunction patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {\n    // 如果新旧节点一致，什么都不做\n    if (oldVnode === vnode) {\n      return\n    }\n\n    // 让vnode.el引用到现在的真实dom，当el修改时，vnode.el会同步变化\n    const elm = vnode.elm = oldVnode.elm\n\n    // 异步占位符\n    if (isTrue(oldVnode.isAsyncPlaceholder)) {\n      if (isDef(vnode.asyncFactory.resolved)) {\n        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)\n      } else {\n        vnode.isAsyncPlaceholder = true\n      }\n      return\n    }\n    // 如果新旧都是静态节点，并且具有相同的key\n    // 当vnode是克隆节点或是v-once指令控制的节点时，只需要把oldVnode.elm和oldVnode.child都复制到vnode上\n    // 也不用再有其他操作\n    if (isTrue(vnode.isStatic) &&\n      isTrue(oldVnode.isStatic) &&\n      vnode.key === oldVnode.key &&\n      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))\n    ) {\n      vnode.componentInstance = oldVnode.componentInstance\n      return\n    }\n\n    let i\n    const data = vnode.data\n    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {\n      i(oldVnode, vnode)\n    }\n\n    const oldCh = oldVnode.children\n    const ch = vnode.children\n    if (isDef(data) && isPatchable(vnode)) {\n      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)\n      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)\n    }\n    // 如果vnode不是文本节点或者注释节点\n    if (isUndef(vnode.text)) {\n      // 并且都有子节点\n      if (isDef(oldCh) && isDef(ch)) {\n        // 并且子节点不完全一致，则调用updateChildren\n        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)\n\n        // 如果只有新的vnode有子节点\n      } else if (isDef(ch)) {\n        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')\n        // elm已经引用了老的dom节点，在老的dom节点上添加子节点\n        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)\n\n        // 如果新vnode没有子节点，而vnode有子节点，直接删除老的oldCh\n      } else if (isDef(oldCh)) {\n        removeVnodes(elm, oldCh, 0, oldCh.length - 1)\n\n        // 如果老节点是文本节点\n      } else if (isDef(oldVnode.text)) {\n        nodeOps.setTextContent(elm, '')\n      }\n\n      // 如果新vnode和老vnode是文本节点或注释节点\n      // 但是vnode.text != oldVnode.text时，只需要更新vnode.elm的文本内容就可以\n    } else if (oldVnode.text !== vnode.text) {\n      nodeOps.setTextContent(elm, vnode.text)\n    }\n    if (isDef(data)) {\n      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)\n    }\n  }\n```\n\n`patchVnode`主要做了几个判断：\n\n- 新节点是否是文本节点，如果是，则直接更新`dom`的文本内容为新节点的文本内容\n- 新节点和旧节点如果都有子节点，则处理比较更新子节点\n- 只有新节点有子节点，旧节点没有，那么不用比较了，所有节点都是全新的，所以直接全部新建就好了，新建是指创建出所有新`DOM`，并且添加进父节点\n- 只有旧节点有子节点而新节点没有，说明更新后的页面，旧节点全部都不见了，那么要做的，就是把所有的旧节点删除，也就是直接把`DOM` 删除\n\n子节点不完全一致，则调用`updateChildren`\n\n```js\nfunction updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {\n    let oldStartIdx = 0 // 旧头索引\n    let newStartIdx = 0 // 新头索引\n    let oldEndIdx = oldCh.length - 1 // 旧尾索引\n    let newEndIdx = newCh.length - 1 // 新尾索引\n    let oldStartVnode = oldCh[0] // oldVnode的第一个child\n    let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最后一个child\n    let newStartVnode = newCh[0] // newVnode的第一个child\n    let newEndVnode = newCh[newEndIdx] // newVnode的最后一个child\n    let oldKeyToIdx, idxInOld, vnodeToMove, refElm\n\n    // removeOnly is a special flag used only by <transition-group>\n    // to ensure removed elements stay in correct relative positions\n    // during leaving transitions\n    const canMove = !removeOnly\n\n    // 如果oldStartVnode和oldEndVnode重合，并且新的也都重合了，证明diff完了，循环结束\n    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n      // 如果oldVnode的第一个child不存在\n      if (isUndef(oldStartVnode)) {\n        // oldStart索引右移\n        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left\n\n      // 如果oldVnode的最后一个child不存在\n      } else if (isUndef(oldEndVnode)) {\n        // oldEnd索引左移\n        oldEndVnode = oldCh[--oldEndIdx]\n\n      // oldStartVnode和newStartVnode是同一个节点\n      } else if (sameVnode(oldStartVnode, newStartVnode)) {\n        // patch oldStartVnode和newStartVnode， 索引左移，继续循环\n        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)\n        oldStartVnode = oldCh[++oldStartIdx]\n        newStartVnode = newCh[++newStartIdx]\n\n      // oldEndVnode和newEndVnode是同一个节点\n      } else if (sameVnode(oldEndVnode, newEndVnode)) {\n        // patch oldEndVnode和newEndVnode，索引右移，继续循环\n        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newEndVnode = newCh[--newEndIdx]\n\n      // oldStartVnode和newEndVnode是同一个节点\n      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n        // patch oldStartVnode和newEndVnode\n        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)\n        // 如果removeOnly是false，则将oldStartVnode.eml移动到oldEndVnode.elm之后\n        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))\n        // oldStart索引右移，newEnd索引左移\n        oldStartVnode = oldCh[++oldStartIdx]\n        newEndVnode = newCh[--newEndIdx]\n\n      // 如果oldEndVnode和newStartVnode是同一个节点\n      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n        // patch oldEndVnode和newStartVnode\n        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)\n        // 如果removeOnly是false，则将oldEndVnode.elm移动到oldStartVnode.elm之前\n        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)\n        // oldEnd索引左移，newStart索引右移\n        oldEndVnode = oldCh[--oldEndIdx]\n        newStartVnode = newCh[++newStartIdx]\n\n      // 如果都不匹配\n      } else {\n        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)\n\n        // 尝试在oldChildren中寻找和newStartVnode的具有相同的key的Vnode\n        idxInOld = isDef(newStartVnode.key)\n          ? oldKeyToIdx[newStartVnode.key]\n          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)\n\n        // 如果未找到，说明newStartVnode是一个新的节点\n        if (isUndef(idxInOld)) { // New element\n          // 创建一个新Vnode\n          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)\n\n        // 如果找到了和newStartVnodej具有相同的key的Vnode，叫vnodeToMove\n        } else {\n          vnodeToMove = oldCh[idxInOld]\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !vnodeToMove) {\n            warn(\n              'It seems there are duplicate keys that is causing an update error. ' +\n              'Make sure each v-for item has a unique key.'\n            )\n          }\n\n          // 比较两个具有相同的key的新节点是否是同一个节点\n          //不设key，newCh和oldCh只会进行头尾两端的相互比较，设key后，除了头尾两端的比较外，还会从用key生成的对象oldKeyToIdx中查找匹配的节点，所以为节点设置key可以更高效的利用dom。\n          if (sameVnode(vnodeToMove, newStartVnode)) {\n            // patch vnodeToMove和newStartVnode\n            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)\n            // 清除\n            oldCh[idxInOld] = undefined\n            // 如果removeOnly是false，则将找到的和newStartVnodej具有相同的key的Vnode，叫vnodeToMove.elm\n            // 移动到oldStartVnode.elm之前\n            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)\n\n          // 如果key相同，但是节点不相同，则创建一个新的节点\n          } else {\n            // same key but different element. treat as new element\n            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)\n          }\n        }\n\n        // 右移\n        newStartVnode = newCh[++newStartIdx]\n      }\n    }\n```\n\n`while`循环主要处理了以下五种情景：\n\n- 当新老 `VNode` 节点的 `start` 相同时，直接 `patchVnode` ，同时新老 `VNode` 节点的开始索引都加 1\n- 当新老 `VNode` 节点的 `end`相同时，同样直接 `patchVnode` ，同时新老 `VNode` 节点的结束索引都减 1\n- 当老 `VNode` 节点的 `start` 和新 `VNode` 节点的 `end` 相同时，这时候在 `patchVnode` 后，还需要将当前真实 `dom` 节点移动到 `oldEndVnode` 的后面，同时老 `VNode` 节点开始索引加 1，新 `VNode` 节点的结束索引减 1\n- 当老 `VNode` 节点的 `end` 和新 `VNode` 节点的 `start` 相同时，这时候在 `patchVnode` 后，还需要将当前真实 `dom` 节点移动到 `oldStartVnode` 的前面，同时老 `VNode` 节点结束索引减 1，新 `VNode` 节点的开始索引加 1\n- 如果都不满足以上四种情形，那说明没有相同的节点可以复用，则会分为以下两种情况：\n  - 从旧的 `VNode` 为 `key` 值，对应 `index` 序列为 `value` 值的哈希表中找到与 `newStartVnode` 一致 `key` 的旧的 `VNode` 节点，再进行`patchVnode `，同时将这个真实 `dom `移动到 `oldStartVnode` 对应的真实 `dom` 的前面\n  - 调用 `createElm` 创建一个新的 `dom` 节点放到当前 `newStartIdx` 的位置\n\n\n\n### 小结\n\n- 当数据发生改变时，订阅者`watcher`就会调用`patch`给真实的`DOM`打补丁\n- 通过`isSameVnode`进行判断，相同则调用`patchVnode`方法\n- `patchVnode`做了以下操作：\n  - 找到对应的真实`dom`，称为`el`\n  - 如果都有都有文本节点且不相等，将`el`文本节点设置为`Vnode`的文本节点\n  - 如果`oldVnode`有子节点而`VNode`没有，则删除`el`子节点\n  - 如果`oldVnode`没有子节点而`VNode`有，则将`VNode`的子节点真实化后添加到`el`\n  - 如果两者都有子节点，则执行`updateChildren`函数比较子节点\n- `updateChildren`主要做了以下操作：\n  - 设置新旧`VNode`的头尾指针\n  - 新旧头尾指针进行比较，循环向中间靠拢，根据情况调用`patchVnode`进行`patch`重复流程、调用`createElem`创建一个新节点，从哈希表寻找 `key`一致的`VNode` 节点再分情况操作\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6881907432541552648#heading-1\n- https://www.infoq.cn/article/udlcpkh4iqb0cr5wgy7f\n"
  },
  {
    "path": "docs/vue/directive.md",
    "content": "# 面试官：你有写过自定义指令吗？自定义指令的应用场景有哪些？\n\n  ![](https://static.vue-js.com/bd85a970-4345-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、什么是指令\n\n开始之前我们先学习一下指令系统这个词\n\n**指令系统**是计算机硬件的语言系统，也叫机器语言，它是系统程序员看到的计算机的主要属性。因此指令系统表征了计算机的基本功能决定了机器所要求的能力\n\n在`vue`中提供了一套为数据驱动视图更为方便的操作，这些操作被称为指令系统\n\n我们看到的`v- `开头的行内属性，都是指令，不同的指令可以完成或实现不同的功能\n\n除了核心功能默认内置的指令 (`v-model` 和 `v-show`)，`Vue` 也允许注册自定义指令\n\n指令使用的几种方式：\n\n```js\n//会实例化一个指令，但这个指令没有参数 \n`v-xxx`\n\n// -- 将值传到指令中\n`v-xxx=\"value\"`  \n\n// -- 将字符串传入到指令中，如`v-html=\"'<p>内容</p>'\"`\n`v-xxx=\"'string'\"` \n\n// -- 传参数（`arg`），如`v-bind:class=\"className\"`\n`v-xxx:arg=\"value\"` \n\n// -- 使用修饰符（`modifier`）\n`v-xxx:arg.modifier=\"value\"` \n```\n\n### 二、如何实现\n\n注册一个自定义指令有全局注册与局部注册\n\n全局注册主要是通过`Vue.directive`方法进行注册\n\n`Vue.directive`第一个参数是指令的名字（不需要写上`v-`前缀），第二个参数可以是对象数据，也可以是一个指令函数\n\n```js\n// 注册一个全局自定义指令 `v-focus`\nVue.directive('focus', {\n  // 当被绑定的元素插入到 DOM 中时……\n  inserted: function (el) {\n    // 聚焦元素\n    el.focus()  // 页面加载完成之后自动让输入框获取到焦点的小功能\n  }\n})\n```\n\n局部注册通过在组件`options`选项中设置`directive`属性\n\n```js\ndirectives: {\n  focus: {\n    // 指令的定义\n    inserted: function (el) {\n      el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能\n    }\n  }\n}\n```\n\n然后你可以在模板中任何元素上使用新的 `v-focus` property，如下：\n\n```js\n<input v-focus />\n```\n\n自定义指令也像组件那样存在钩子函数：\n\n- `bind`：只调用一次，指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置\n- `inserted`：被绑定元素插入父节点时调用 (仅保证父节点存在，但不一定已被插入文档中)\n- `update`：所在组件的 `VNode` 更新时调用，但是可能发生在其子 `VNode` 更新之前。指令的值可能发生了改变，也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新\n\n- `componentUpdated`：指令所在组件的 `VNode` 及其子 `VNode` 全部更新后调用\n- `unbind`：只调用一次，指令与元素解绑时调用\n\n所有的钩子函数的参数都有以下：\n\n- `el`：指令所绑定的元素，可以用来直接操作 `DOM`\n- `binding`：一个对象，包含以下 `property`：\n    - `name`：指令名，不包括 `v-` 前缀。\n    - `value`：指令的绑定值，例如：`v-my-directive=\"1 + 1\"` 中，绑定值为 `2`。\n    - `oldValue`：指令绑定的前一个值，仅在 `update` 和 `componentUpdated` 钩子中可用。无论值是否改变都可用。\n    - `expression`：字符串形式的指令表达式。例如 `v-my-directive=\"1 + 1\"` 中，表达式为 `\"1 + 1\"`。\n    - `arg`：传给指令的参数，可选。例如 `v-my-directive:foo` 中，参数为 `\"foo\"`。\n    - `modifiers`：一个包含修饰符的对象。例如：`v-my-directive.foo.bar` 中，修饰符对象为 `{ foo: true, bar: true }`\n- `vnode`：`Vue` 编译生成的虚拟节点\n- `oldVnode`：上一个虚拟节点，仅在 `update` 和 `componentUpdated` 钩子中可用\n\n> 除了 `el` 之外，其它参数都应该是只读的，切勿进行修改。如果需要在钩子之间共享数据，建议通过元素的 `dataset` 来进行\n\n举个例子：\n\n```html\n<div v-demo=\"{ color: 'white', text: 'hello!' }\"></div>\n<script>\n    Vue.directive('demo', function (el, binding) {\n    console.log(binding.value.color) // \"white\"\n    console.log(binding.value.text)  // \"hello!\"\n    })\n</script>\n```\n\n\n\n## 三、应用场景\n\n使用自定义指令可以满足我们日常一些场景，这里给出几个自定义指令的案例：\n\n- 表单防止重复提交\n- 图片懒加载\n- 一键 Copy的功能\n\n### 表单防止重复提交\n\n表单防止重复提交这种情况设置一个`v-throttle`自定义指令来实现\n\n举个例子：\n\n```js\n// 1.设置v-throttle自定义指令\nVue.directive('throttle', {\n  bind: (el, binding) => {\n    let throttleTime = binding.value; // 节流时间\n    if (!throttleTime) { // 用户若不设置节流时间，则默认2s\n      throttleTime = 2000;\n    }\n    let cbFun;\n    el.addEventListener('click', event => {\n      if (!cbFun) { // 第一次执行\n        cbFun = setTimeout(() => {\n          cbFun = null;\n        }, throttleTime);\n      } else {\n        event && event.stopImmediatePropagation();\n      }\n    }, true);\n  },\n});\n// 2.为button标签设置v-throttle自定义指令\n<button @click=\"sayHello\" v-throttle>提交</button>\n```\n\n\n\n### 图片懒加载\n\n设置一个`v-lazy`自定义指令完成图片懒加载\n\n```js\nconst LazyLoad = {\n    // install方法\n    install(Vue,options){\n    \t  // 代替图片的loading图\n        let defaultSrc = options.default;\n        Vue.directive('lazy',{\n            bind(el,binding){\n                LazyLoad.init(el,binding.value,defaultSrc);\n            },\n            inserted(el){\n                // 兼容处理\n                if('IntersectionObserver' in window){\n                    LazyLoad.observe(el);\n                }else{\n                    LazyLoad.listenerScroll(el);\n                }\n                \n            },\n        })\n    },\n    // 初始化\n    init(el,val,def){\n        // data-src 储存真实src\n        el.setAttribute('data-src',val);\n        // 设置src为loading图\n        el.setAttribute('src',def);\n    },\n    // 利用IntersectionObserver监听el\n    observe(el){\n        let io = new IntersectionObserver(entries => {\n            let realSrc = el.dataset.src;\n            if(entries[0].isIntersecting){\n                if(realSrc){\n                    el.src = realSrc;\n                    el.removeAttribute('data-src');\n                }\n            }\n        });\n        io.observe(el);\n    },\n    // 监听scroll事件\n    listenerScroll(el){\n        let handler = LazyLoad.throttle(LazyLoad.load,300);\n        LazyLoad.load(el);\n        window.addEventListener('scroll',() => {\n            handler(el);\n        });\n    },\n    // 加载真实图片\n    load(el){\n        let windowHeight = document.documentElement.clientHeight\n        let elTop = el.getBoundingClientRect().top;\n        let elBtm = el.getBoundingClientRect().bottom;\n        let realSrc = el.dataset.src;\n        if(elTop - windowHeight<0&&elBtm > 0){\n            if(realSrc){\n                el.src = realSrc;\n                el.removeAttribute('data-src');\n            }\n        }\n    },\n    // 节流\n    throttle(fn,delay){\n        let timer; \n        let prevTime;\n        return function(...args){\n            let currTime = Date.now();\n            let context = this;\n            if(!prevTime) prevTime = currTime;\n            clearTimeout(timer);\n            \n            if(currTime - prevTime > delay){\n                prevTime = currTime;\n                fn.apply(context,args);\n                clearTimeout(timer);\n                return;\n            }\n\n            timer = setTimeout(function(){\n                prevTime = Date.now();\n                timer = null;\n                fn.apply(context,args);\n            },delay);\n        }\n    }\n\n}\nexport default LazyLoad;\n```\n\n\n\n### 一键 Copy的功能\n\n```js\nimport { Message } from 'ant-design-vue';\n\nconst vCopy = { //\n  /*\n    bind 钩子函数，第一次绑定时调用，可以在这里做初始化设置\n    el: 作用的 dom 对象\n    value: 传给指令的值，也就是我们要 copy 的值\n  */\n  bind(el, { value }) {\n    el.$value = value; // 用一个全局属性来存传进来的值，因为这个值在别的钩子函数里还会用到\n    el.handler = () => {\n      if (!el.$value) {\n      // 值为空的时候，给出提示，我这里的提示是用的 ant-design-vue 的提示，你们随意\n        Message.warning('无复制内容');\n        return;\n      }\n      // 动态创建 textarea 标签\n      const textarea = document.createElement('textarea');\n      // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘，同时将 textarea 移出可视区域\n      textarea.readOnly = 'readonly';\n      textarea.style.position = 'absolute';\n      textarea.style.left = '-9999px';\n      // 将要 copy 的值赋给 textarea 标签的 value 属性\n      textarea.value = el.$value;\n      // 将 textarea 插入到 body 中\n      document.body.appendChild(textarea);\n      // 选中值并复制\n      textarea.select();\n      // textarea.setSelectionRange(0, textarea.value.length);\n      const result = document.execCommand('Copy');\n      if (result) {\n        Message.success('复制成功');\n      }\n      document.body.removeChild(textarea);\n    };\n    // 绑定点击事件，就是所谓的一键 copy 啦\n    el.addEventListener('click', el.handler);\n  },\n  // 当传进来的值更新的时候触发\n  componentUpdated(el, { value }) {\n    el.$value = value;\n  },\n  // 指令与元素解绑的时候，移除事件绑定\n  unbind(el) {\n    el.removeEventListener('click', el.handler);\n  },\n};\n\nexport default vCopy;\n```\n\n关于自定义指令还有很多应用场景，如：拖拽指令、页面水印、权限校验等等应用场景\n\n\n## 参考文献\n\n- https://vue3js.cn/docs/zh\n- https://juejin.cn/post/6844904197448531975#heading-5\n- https://www.cnblogs.com/chenwenhao/p/11924161.html#_label2\n"
  },
  {
    "path": "docs/vue/error.md",
    "content": "# 面试官：你是怎么处理vue项目中的错误的？\n\n ![](https://static.vue-js.com/3cafe4f0-4fd9-11eb-ab90-d9ae814b240d.png)\n\n## 一、错误类型\n\n任何一个框架，对于错误的处理都是一种必备的能力\n\n在`Vue` 中，则是定义了一套对应的错误处理规则给到使用者，且在源代码级别，对部分必要的过程做了一定的错误处理。\n\n主要的错误来源包括：\n\n- 后端接口错误\n- 代码中本身逻辑错误\n\n\n\n## 二、如何处理\n\n### 后端接口错误\n\n通过`axios`的`interceptor`实现网络请求的`response`先进行一层拦截\n\n```js\napiClient.interceptors.response.use(\n  response => {\n    return response;\n  },\n  error => {\n    if (error.response.status == 401) {\n      router.push({ name: \"Login\" });\n    } else {\n      message.error(\"出错了\");\n      return Promise.reject(error);\n    }\n  }\n);\n```\n\n\n\n### 代码逻辑问题\n\n#### 全局设置错误处理\n\n设置全局错误处理函数\n\n```js\nVue.config.errorHandler = function (err, vm, info) {\n  // handle error\n  // `info` 是 Vue 特定的错误信息，比如错误所在的生命周期钩子\n  // 只在 2.2.0+ 可用\n}\n```\n\n`errorHandler`指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时，可获取错误信息和 `Vue` 实例\n\n不过值得注意的是，在不同` Vue` 版本中，该全局 `API` 作用的范围会有所不同：\n\n> 从 2.2.0 起，这个钩子也会捕获组件生命周期钩子里的错误。同样的，当这个钩子是 `undefined` 时，被捕获的错误会通过 `console.error` 输出而避免应用崩\n\n> 从 2.4.0 起，这个钩子也会捕获 Vue 自定义事件处理函数内部的错误了\n\n> 从 2.6.0 起，这个钩子也会捕获 `v-on` DOM 监听器内部抛出的错误。另外，如果任何被覆盖的钩子或处理函数返回一个 Promise 链 (例如 async 函数)，则来自其 Promise 链的错误也会被处理\n\n\n\n#### 生命周期钩子\n\n`errorCaptured`是 2.5.0 新增的一个生命钩子函数，当捕获到一个来自子孙组件的错误时被调用\n\n基本类型\n\n```js\n(err: Error, vm: Component, info: string) => ?boolean\n```\n\n此钩子会收到三个参数：错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 `false` 以阻止该错误继续向上传播\n\n参考官网，错误传播规则如下：\n\n- 默认情况下，如果全局的 `config.errorHandler` 被定义，所有的错误仍会发送它，因此这些错误仍然会向单一的分析服务的地方进行汇报\n- 如果一个组件的继承或父级从属链路中存在多个 `errorCaptured` 钩子，则它们将会被相同的错误逐个唤起。\n- 如果此 `errorCaptured` 钩子自身抛出了一个错误，则这个新错误和原本被捕获的错误都会发送给全局的 `config.errorHandler`\n- 一个 `errorCaptured` 钩子能够返回 `false` 以阻止错误继续向上传播。本质上是说“这个错误已经被搞定了且应该被忽略”。它会阻止其它任何会被这个错误唤起的 `errorCaptured` 钩子和全局的 `config.errorHandler`\n\n下面来看个例子\n\n定义一个父组件`cat`\n\n```js\nVue.component('cat', {\n    template:`\n        <div>\n\t\t\t<h1>Cat: </h1>\n        \t<slot></slot>\n        </div>`,\n    props:{\n        name:{\n            required:true,\n            type:String\n        }\n    },\n    errorCaptured(err,vm,info) {\n        console.log(`cat EC: ${err.toString()}\\ninfo: ${info}`); \n        return false;\n    }\n\n});\n```\n\n定义一个子组件`kitten`，其中`dontexist()`并没有定义，存在错误\n\n```js\nVue.component('kitten', {\n    template:'<div><h1>Kitten: {{ dontexist() }}</h1></div>',\n    props:{\n        name:{\n            required:true,\n            type:String\n        }\n    }\n});\n```\n\n页面中使用组件\n\n```html\n<div id=\"app\" v-cloak>\n    <cat name=\"my cat\">\n        <kitten></kitten>\n    </cat>\n</div>\n```\n\n在父组件的`errorCaptured`则能够捕获到信息\n\n```js\ncat EC: TypeError: dontexist is not a function\ninfo: render\n```\n\n\n\n### 三、源码分析\n\n异常处理源码\n\n源码位置：/src/core/util/error.js\n\n```js\n// Vue 全局配置,也就是上面的Vue.config\nimport config from '../config'\nimport { warn } from './debug'\n// 判断环境\nimport { inBrowser, inWeex } from './env'\n// 判断是否是Promise，通过val.then === 'function' && val.catch === 'function', val ！=== null && val !== undefined\nimport { isPromise } from 'shared/util'\n// 当错误函数处理错误时，停用deps跟踪以避免可能出现的infinite rendering\n// 解决以下出现的问题https://github.com/vuejs/vuex/issues/1505的问题\nimport { pushTarget, popTarget } from '../observer/dep'\n\nexport function handleError (err: Error, vm: any, info: string) {\n    // Deactivate deps tracking while processing error handler to avoid possible infinite rendering.\n    pushTarget()\n    try {\n        // vm指当前报错的组件实例\n        if (vm) {\n            let cur = vm\n            // 首先获取到报错的组件，之后递归查找当前组件的父组件，依次调用errorCaptured 方法。\n            // 在遍历调用完所有 errorCaptured 方法、或 errorCaptured 方法有报错时，调用 globalHandleError 方法\n            while ((cur = cur.$parent)) {\n                const hooks = cur.$options.errorCaptured\n                // 判断是否存在errorCaptured钩子函数\n                if (hooks) {\n                    // 选项合并的策略，钩子函数会被保存在一个数组中\n                    for (let i = 0; i < hooks.length; i++) {\n                        // 如果errorCaptured 钩子执行自身抛出了错误，\n                        // 则用try{}catch{}捕获错误，将这个新错误和原本被捕获的错误都会发送给全局的config.errorHandler\n                        // 调用globalHandleError方法\n                        try {\n                            // 当前errorCaptured执行，根据返回是否是false值\n                            // 是false，capture = true，阻止其它任何会被这个错误唤起的 errorCaptured 钩子和全局的 config.errorHandler\n                            // 是true capture = fale，组件的继承或父级从属链路中存在的多个 errorCaptured 钩子，会被相同的错误逐个唤起\n                            // 调用对应的钩子函数，处理错误\n                            const capture = hooks[i].call(cur, err, vm, info) === false\n                            if (capture) return\n                        } catch (e) {\n                            globalHandleError(e, cur, 'errorCaptured hook')\n                        }\n                    }\n                }\n            }\n        }\n        // 除非禁止错误向上传播，否则都会调用全局的错误处理函数\n        globalHandleError(err, vm, info)\n    } finally {\n        popTarget()\n    }\n}\n// 异步错误处理函数\nexport function invokeWithErrorHandling (\nhandler: Function,\n context: any,\n args: null | any[],\n    vm: any,\n        info: string\n        ) {\n            let res\n            try {\n                // 根据参数选择不同的handle执行方式\n                res = args ? handler.apply(context, args) : handler.call(context)\n                // handle返回结果存在\n                // res._isVue an flag to avoid this being observed，如果传入值的_isVue为ture时(即传入的值是Vue实例本身)不会新建observer实例\n                // isPromise(res) 判断val.then === 'function' && val.catch === 'function', val ！=== null && val !== undefined\n                // !res._handled  _handle是Promise 实例的内部变量之一，默认是false，代表onFulfilled,onRejected是否被处理\n                if (res && !res._isVue && isPromise(res) && !res._handled) {\n                    res.catch(e => handleError(e, vm, info + ` (Promise/async)`))\n                    // avoid catch triggering multiple times when nested calls\n                    // 避免嵌套调用时catch多次的触发\n                    res._handled = true\n                }\n            } catch (e) {\n                // 处理执行错误\n                handleError(e, vm, info)\n            }\n            return res\n        }\n\n//全局错误处理\nfunction globalHandleError (err, vm, info) {\n    // 获取全局配置，判断是否设置处理函数，默认undefined\n    // 已配置\n    if (config.errorHandler) {\n        // try{}catch{} 住全局错误处理函数\n        try {\n            // 执行设置的全局错误处理函数，handle error 想干啥就干啥💗\n            return config.errorHandler.call(null, err, vm, info)\n        } catch (e) {\n            // 如果开发者在errorHandler函数中手动抛出同样错误信息throw err\n            // 判断err信息是否相等，避免log两次\n            // 如果抛出新的错误信息throw err Error('你好毒')，将会一起log输出\n            if (e !== err) {\n                logError(e, null, 'config.errorHandler')\n            }\n        }\n    }\n    // 未配置常规log输出\n    logError(err, vm, info)\n}\n\n// 错误输出函数\nfunction logError (err, vm, info) {\n    if (process.env.NODE_ENV !== 'production') {\n        warn(`Error in ${info}: \"${err.toString()}\"`, vm)\n    }\n    /* istanbul ignore else */\n    if ((inBrowser || inWeex) && typeof console !== 'undefined') {\n        console.error(err)\n    } else {\n        throw err\n    }\n}\n```\n\n### 小结\n\n- `handleError`在需要捕获异常的地方调用，首先获取到报错的组件，之后递归查找当前组件的父组件，依次调用`errorCaptured` 方法，在遍历调用完所有 `errorCaptured` 方法或 `errorCaptured` 方法有报错时，调用 `globalHandleError` 方法\n- `globalHandleError `调用全局的 `errorHandler` 方法，再通过`logError`判断环境输出错误信息\n- `invokeWithErrorHandling`更好的处理异步错误信息\n- `logError`判断环境，选择不同的抛错方式。非生产环境下，调用`warn`方法处理错误\n\n\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844904096936230925\n\n- https://segmentfault.com/a/1190000018606181"
  },
  {
    "path": "docs/vue/filter.md",
    "content": "# 面试官：Vue中的过滤器了解吗？过滤器的应用场景有哪些？\n\n ![](https://static.vue-js.com/fe68eea0-440f-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n过滤器（`filter`）是输送介质管道上不可缺少的一种装置\n\n大白话，就是把一些不必要的东西过滤掉\n\n过滤器实质不改变原始数据，只是对数据进行加工处理后返回过滤后的数据再进行调用处理，我们也可以理解其为一个纯函数\n\n`Vue` 允许你自定义过滤器，可被用于一些常见的文本格式化\n\nps: `Vue3`中已废弃`filter`\n\n## 二、如何用\n\n`vue`中的过滤器可以用在两个地方：双花括号插值和 `v-bind` 表达式，过滤器应该被添加在 `JavaScript `表达式的尾部，由“管道”符号指示：\n\n```js\n<!-- 在双花括号中 -->\n{{ message | capitalize }}\n\n<!-- 在 `v-bind` 中 -->\n<div v-bind:id=\"rawId | formatId\"></div>\n```\n\n### 定义filter\n\n在组件的选项中定义本地的过滤器\n\n```js\nfilters: {\n  capitalize: function (value) {\n    if (!value) return ''\n    value = value.toString()\n    return value.charAt(0).toUpperCase() + value.slice(1)\n  }\n}\n```\n\n定义全局过滤器：\n\n```js\nVue.filter('capitalize', function (value) {\n  if (!value) return ''\n  value = value.toString()\n  return value.charAt(0).toUpperCase() + value.slice(1)\n})\n\nnew Vue({\n  // ...\n})\n```\n\n注意：当全局过滤器和局部过滤器重名时，会采用局部过滤器\n\n过滤器函数总接收表达式的值 (之前的操作链的结果) 作为第一个参数。在上述例子中，`capitalize` 过滤器函数将会收到 `message` 的值作为第一个参数\n\n过滤器可以串联：\n\n```\n{{ message | filterA | filterB }}\n```\n\n在这个例子中，`filterA` 被定义为接收单个参数的过滤器函数，表达式 `message` 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 `filterB`，将 `filterA` 的结果传递到 `filterB` 中。\n\n过滤器是 `JavaScript `函数，因此可以接收参数：\n\n```\n{{ message | filterA('arg1', arg2) }}\n```\n\n这里，`filterA` 被定义为接收三个参数的过滤器函数。\n\n其中 `message` 的值作为第一个参数，普通字符串 `'arg1'` 作为第二个参数，表达式 `arg2` 的值作为第三个参数\n\n举个例子：\n\n```html\n<div id=\"app\">\n    <p>{{ msg | msgFormat('疯狂','--')}}</p>\n</div>\n\n<script>\n    // 定义一个 Vue 全局的过滤器，名字叫做  msgFormat\n    Vue.filter('msgFormat', function(msg, arg, arg2) {\n        // 字符串的  replace 方法，第一个参数，除了可写一个 字符串之外，还可以定义一个正则\n        return msg.replace(/单纯/g, arg+arg2)\n    })\n</script>\n```\n\n### 小结：\n\n- 部过滤器优先于全局过滤器被调用\n- 一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右\n\n\n\n## 三、应用场景\n\n平时开发中，需要用到过滤器的地方有很多，比如单位转换、数字打点、文本格式化、时间格式化之类的等\n\n比如我们要实现将30000 => 30,000，这时候我们就需要使用过滤器\n\n```js\nVue.filter('toThousandFilter', function (value) {\n     if (!value) return ''\n     value = value.toString()\n     return .replace(str.indexOf('.') > -1 ? /(\\d)(?=(\\d{3})+\\.)/g : /(\\d)(?=(?:\\d{3})+$)/g, '$1,')\n})\n```\n\n\n\n## 四、原理分析\n\n使用过滤器\n\n```js\n{{ message | capitalize }}\n```\n\n在模板编译阶段过滤器表达式将会被编译为过滤器函数，主要是用过`parseFilters`，我们放到最后讲\n\n```js\n_s(_f('filterFormat')(message))\n```\n\n首先分析一下`_f`：\n\n_f 函数全名是：`resolveFilter`，这个函数的作用是从`this.$options.filters`中找出注册的过滤器并返回\n\n```js\n// 变为\nthis.$options.filters['filterFormat'](message) // message为参数\n```\n\n关于`resolveFilter`\n\n```js\nimport { indentity,resolveAsset } from 'core/util/index' \n\nexport function resolveFilter(id){\n    return resolveAsset(this.$options,'filters',id,true) || identity\n}\n```\n\n内部直接调用`resolveAsset`，将`option`对象，类型，过滤器`id`，以及一个触发警告的标志作为参数传递，如果找到，则返回过滤器；\n\n`resolveAsset`的代码如下：\n\n```js\nexport function resolveAsset(options,type,id,warnMissing){ // 因为我们找的是过滤器，所以在 resolveFilter函数中调用时 type 的值直接给的 'filters',实际这个函数还可以拿到其他很多东西\n    if(typeof id !== 'string'){ // 判断传递的过滤器id 是不是字符串，不是则直接返回\n        return \n    }\n    const assets = options[type]  // 将我们注册的所有过滤器保存在变量中\n    // 接下来的逻辑便是判断id是否在assets中存在，即进行匹配\n    if(hasOwn(assets,id)) return assets[id] // 如找到，直接返回过滤器\n    // 没有找到，代码继续执行\n    const camelizedId  = camelize(id) // 万一你是驼峰的呢\n    if(hasOwn(assets,camelizedId)) return assets[camelizedId]\n    // 没找到，继续执行\n    const PascalCaseId = capitalize(camelizedId) // 万一你是首字母大写的驼峰呢\n    if(hasOwn(assets,PascalCaseId)) return assets[PascalCaseId]\n    // 如果还是没找到，则检查原型链(即访问属性)\n    const result = assets[id] || assets[camelizedId] || assets[PascalCaseId]\n    // 如果依然没找到，则在非生产环境的控制台打印警告\n    if(process.env.NODE_ENV !== 'production' && warnMissing && !result){\n        warn('Failed to resolve ' + type.slice(0,-1) + ': ' + id, options)\n    }\n    // 无论是否找到，都返回查找结果\n    return result\n}\n```\n\n下面再来分析一下`_s`：\n\n `_s` 函数的全称是 `toString`,过滤器处理后的结果会当作参数传递给 `toString`函数，最终 `toString`函数执行后的结果会保存到`Vnode`中的text属性中，渲染到视图中\n\n```js\nfunction toString(value){\n    return value == null\n    ? ''\n    : typeof value === 'object'\n      ? JSON.stringify(value,null,2)// JSON.stringify()第三个参数可用来控制字符串里面的间距\n      : String(value)\n}\n```\n\n最后，在分析下`parseFilters`，在模板编译阶段使用该函数阶段将模板过滤器解析为过滤器函数调用表达式\n\n```js\nfunction parseFilters (filter) {\n    let filters = filter.split('|')\n    let expression = filters.shift().trim() // shift()删除数组第一个元素并将其返回，该方法会更改原数组\n    let i\n    if (filters) {\n        for(i = 0;i < filters.length;i++){\n            experssion = warpFilter(expression,filters[i].trim()) // 这里传进去的expression实际上是管道符号前面的字符串，即过滤器的第一个参数\n        }\n    }\n    return expression\n}\n// warpFilter函数实现\nfunction warpFilter(exp,filter){\n    // 首先判断过滤器是否有其他参数\n    const i = filter.indexof('(')\n    if(i<0){ // 不含其他参数，直接进行过滤器表达式字符串的拼接\n        return `_f(\"${filter}\")(${exp})`\n    }else{\n        const name = filter.slice(0,i) // 过滤器名称\n        const args = filter.slice(i+1) // 参数，但还多了 ‘)’\n        return `_f('${name}')(${exp},${args}` // 注意这一步少给了一个 ')'\n    }\n}\n```\n\n### 小结：\n\n- 在编译阶段通过`parseFilters`将过滤器编译成函数调用（串联过滤器则是一个嵌套的函数调用，前一个过滤器执行的结果是后一个过滤器函数的参数）\n- 编译后通过调用`resolveFilter`函数找到对应过滤器并返回结果\n- 执行结果作为参数传递给`toString`函数，而`toString`执行后，其结果会保存在`Vnode`的`text`属性中，渲染到视图\n\n\n\n## 参考文献\n\n- https://cn.vuejs.org/v2/guide/filters.html#ad\n- https://blog.csdn.net/weixin_42724176/article/details/105546684\n- https://vue3js.cn"
  },
  {
    "path": "docs/vue/first_page_time.md",
    "content": "# 面试官：SPA首屏加载速度慢的怎么解决？\n\n![image.png](https://static.vue-js.com/24617c00-3acc-11eb-ab90-d9ae814b240d.png)\n\n\n## 一、什么是首屏加载\n\n首屏时间（First Contentful Paint），指的是浏览器从响应用户输入网址地址，到首屏内容渲染完成的时间，此时整个网页不一定要全部渲染完成，但需要展示当前视窗需要的内容\n\n首屏加载可以说是用户体验中**最重要**的环节\n\n### 关于计算首屏时间\n利用`performance.timing`提供的数据：\n\n ![image.png](https://static.vue-js.com/2e2491a0-3acc-11eb-85f6-6fac77c0c9b3.png)\n\n通过`DOMContentLoad`或者`performance`来计算出首屏时间\n\n```js\n// 方案一：\ndocument.addEventListener('DOMContentLoaded', (event) => {\n    console.log('first contentful painting');\n});\n// 方案二：\nperformance.getEntriesByName(\"first-contentful-paint\")[0].startTime\n\n// performance.getEntriesByName(\"first-contentful-paint\")[0]\n// 会返回一个 PerformancePaintTiming的实例，结构如下：\n{\n  name: \"first-contentful-paint\",\n  entryType: \"paint\",\n  startTime: 507.80000002123415,\n  duration: 0,\n};\n```\n\n## 二、加载慢的原因\n\n在页面渲染的过程，导致加载速度慢的因素可能如下：\n\n- 网络延时问题\n- 资源文件体积是否过大\n- 资源是否重复发送请求去加载了\n- 加载脚本的时候，渲染内容堵塞了\n\n\n\n## 三、解决方案\n\n常见的几种SPA首屏优化方式\n\n- 减小入口文件积\n- 静态资源本地缓存\n- UI框架按需加载\n- 图片资源的压缩\n- 组件重复打包\n- 开启GZip压缩\n- 使用SSR\n\n\n\n### 减小入口文件体积\n\n常用的手段是路由懒加载，把不同路由对应的组件分割成不同的代码块，待路由被请求的时候会单独打包路由，使得入口文件变小，加载速度大大增加\n\n ![image.png](https://static.vue-js.com/486cee90-3acc-11eb-ab90-d9ae814b240d.png)\n\n在`vue-router`配置路由的时候，采用动态加载路由的形式\n\n```js\nroutes:[ \n    path: 'Blogs',\n    name: 'ShowBlogs',\n    component: () => import('./components/ShowBlogs.vue')\n]\n```\n\n以函数的形式加载路由，这样就可以把各自的路由文件分别打包，只有在解析给定的路由时，才会加载路由组件\n\n\n\n### 静态资源本地缓存\n\n后端返回资源问题：\n\n- 采用`HTTP`缓存，设置`Cache-Control`，`Last-Modified`，`Etag`等响应头\n\n- 采用`Service Worker`离线缓存\n\n前端合理利用`localStorage`\n\n\n\n### UI框架按需加载\n\n在日常使用`UI`框架，例如`element-UI`、或者`antd`，我们经常性直接引用整个`UI`库\n\n```js\nimport ElementUI from 'element-ui'\nVue.use(ElementUI)\n```\n\n但实际上我用到的组件只有按钮，分页，表格，输入与警告 所以我们要按需引用\n\n```js\nimport { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';\nVue.use(Button)\nVue.use(Input)\nVue.use(Pagination)\n```\n\n\n\n### 组件重复打包\n\n假设`A.js`文件是一个常用的库，现在有多个路由使用了`A.js`文件，这就造成了重复下载\n\n解决方案：在`webpack`的`config`文件中，修改`CommonsChunkPlugin`的配置\n\n```js\nminChunks: 3\n```\n\n`minChunks`为3表示会把使用3次及以上的包抽离出来，放进公共依赖文件，避免了重复加载组件\n\n\n\n### 图片资源的压缩\n\n图片资源虽然不在编码过程中，但它却是对页面性能影响最大的因素\n\n对于所有的图片资源，我们可以进行适当的压缩\n\n对页面上使用到的`icon`，可以使用在线字体图标，或者雪碧图，将众多小图标合并到同一张图上，用以减轻`http`请求压力。\n\n\n\n### 开启GZip压缩\n\n拆完包之后，我们再用`gzip`做一下压缩 安装`compression-webpack-plugin`\n\n```js\ncnmp i compression-webpack-plugin -D\n```\n\n在`vue.congig.js`中引入并修改`webpack`配置\n\n```js\nconst CompressionPlugin = require('compression-webpack-plugin')\n\nconfigureWebpack: (config) => {\n        if (process.env.NODE_ENV === 'production') {\n            // 为生产环境修改配置...\n            config.mode = 'production'\n            return {\n                plugins: [new CompressionPlugin({\n                    test: /\\.js$|\\.html$|\\.css/, //匹配文件名\n                    threshold: 10240, //对超过10k的数据进行压缩\n                    deleteOriginalAssets: false //是否删除原文件\n                })]\n            }\n        }\n```\n\n在服务器我们也要做相应的配置 如果发送请求的浏览器支持`gzip`，就发送给它`gzip`格式的文件 我的服务器是用`express`框架搭建的 只要安装一下`compression`就能使用\n\n```\nconst compression = require('compression')\napp.use(compression())  // 在其他中间件使用之前调用\n```\n\n\n\n### 使用SSR\n\nSSR（Server side ），也就是服务端渲染，组件或页面通过服务器生成html字符串，再发送到浏览器\n\n从头搭建一个服务端渲染是很复杂的，`vue`应用建议使用`Nuxt.js`实现服务端渲染\n\n\n\n### 小结：\n\n减少首屏渲染时间的方法有很多，总的来讲可以分成两大部分 ：资源加载优化 和 页面渲染优化\n\n下图是更为全面的首屏优化的方案\n\n ![image.png](https://static.vue-js.com/4fafe900-3acc-11eb-85f6-6fac77c0c9b3.png)\n\n\n大家可以根据自己项目的情况选择各种方式进行首屏渲染的优化\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/88639980?utm_source=wechat_session\n- https://www.chengrang.com/how-browsers-work.html\n- https://juejin.cn/post/6844904185264095246\n- https://vue3js.cn/docs/zh"
  },
  {
    "path": "docs/vue/if_for.md",
    "content": "# 面试官：v-if和v-for的优先级是什么？\n\n![](https://static.vue-js.com/e8764810-3acb-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、作用\n\n`v-if` 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 `true`值的时候被渲染\n\n`v-for` 指令基于一个数组来渲染一个列表。`v-for` 指令需要使用 `item in items` 形式的特殊语法，其中 `items` 是源数据数组或者对象，而 `item` 则是被迭代的数组元素的别名\n\n在 `v-for` 的时候，建议设置`key`值，并且保证每个`key`值是独一无二的，这便于`diff`算法进行优化\n\n两者在用法上\n\n```js\n<Modal v-if=\"isShow\" />\n\n<li v-for=\"item in items\" :key=\"item.id\">\n    {{ item.label }}\n</li>\n```\n\n## 二、优先级\n\n`v-if`与`v-for`都是`vue`模板系统中的指令\n\n在`vue`模板编译的时候，会将指令系统转化成可执行的`render`函数\n\n### 示例\n\n编写一个`p`标签，同时使用`v-if`与 `v-for`\n\n```html\n<div id=\"app\">\n    <p v-if=\"isShow\" v-for=\"item in items\">\n        {{ item.title }}\n    </p>\n</div>\n```\n\n创建`vue`实例，存放`isShow`与`items`数据\n\n```js\nconst app = new Vue({\n  el: \"#app\",\n  data() {\n    return {\n      items: [\n        { title: \"foo\" },\n        { title: \"baz\" }]\n    }\n  },\n  computed: {\n    isShow() {\n      return this.items && this.items.length > 0\n    }\n  }\n})\n```\n\n模板指令的代码都会生成在`render`函数中，通过`app.$options.render`就能得到渲染函数\n\n```js\nƒ anonymous() {\n  with (this) { return \n    _c('div', { attrs: { \"id\": \"app\" } }, \n    _l((items), function (item) \n    { return (isShow) ? _c('p', [_v(\"\\n\" + _s(item.title) + \"\\n\")]) : _e() }), 0) }\n}\n```\n\n`_l`是`vue`的列表渲染函数，函数内部都会进行一次`if`判断\n\n初步得到结论：`v-for`优先级是比`v-if`高\n\n再将`v-for`与`v-if`置于不同标签\n\n```html\n<div id=\"app\">\n    <template v-if=\"isShow\">\n        <p v-for=\"item in items\">{{item.title}}</p>\n    </template>\n</div>\n```\n\n再输出下`render`函数\n\n```js\nƒ anonymous() {\n  with(this){return \n    _c('div',{attrs:{\"id\":\"app\"}},\n    [(isShow)?[_v(\"\\n\"),\n    _l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}\n}\n```\n\n这时候我们可以看到，`v-for`与`v-if`作用在不同标签时候，是先进行判断，再进行列表的渲染\n\n我们再在查看下`vue`源码\n\n源码位置：` \\vue-dev\\src\\compiler\\codegen\\index.js`\n\n```js\nexport function genElement (el: ASTElement, state: CodegenState): string {\n  if (el.parent) {\n    el.pre = el.pre || el.parent.pre\n  }\n  if (el.staticRoot && !el.staticProcessed) {\n    return genStatic(el, state)\n  } else if (el.once && !el.onceProcessed) {\n    return genOnce(el, state)\n  } else if (el.for && !el.forProcessed) {\n    return genFor(el, state)\n  } else if (el.if && !el.ifProcessed) {\n    return genIf(el, state)\n  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {\n    return genChildren(el, state) || 'void 0'\n  } else if (el.tag === 'slot') {\n    return genSlot(el, state)\n  } else {\n    // component or element\n    ...\n}\n```\n\n在进行`if`判断的时候，`v-for`是比`v-if`先进行判断\n\n最终结论：`v-for`优先级比`v-if`高\n\n## 三、注意事项\n\n1. 永远不要把 `v-if` 和 `v-for` 同时用在同一个元素上，带来性能方面的浪费（每次渲染都会先循环再进行条件判断）\n2. 如果避免出现这种情况，则在外层嵌套`template`（页面渲染不生成`dom`节点），在这一层进行v-if判断，然后在内部进行v-for循环\n\n```js\n<template v-if=\"isShow\">\n    <p v-for=\"item in items\">\n</template>\n```\n\n3. 如果条件出现在循环内部，可通过计算属性`computed`提前过滤掉那些不需要显示的项\n\n```js\ncomputed: {\n    items: function() {\n      return this.list.filter(function (item) {\n        return item.isShow\n      })\n    }\n}\n```"
  },
  {
    "path": "docs/vue/keepalive.md",
    "content": "# 面试官：说说你对keep-alive的理解是什么？\n![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9nSDMxdUY5VklpYlRaSXdpY3ZmUkR3STRiamRBVGlhVEpFZDNzamRoeTd3MDlVM0k5ZERjNUVVSUNFVk1WSVE2aDFYMjVpY1NRT3lraWFwWEpEUFM0VGJST0l3LzY0MA?x-oss-process=image/format,png)\n\n## 一、Keep-alive 是什么\n\n`keep-alive`是`vue`中的内置组件，能在组件切换过程中将状态保留在内存中，防止重复渲染`DOM`\n\n`keep-alive` 包裹动态组件时，会缓存不活动的组件实例，而不是销毁它们\n\n`keep-alive`可以设置以下`props`属性：\n\n- `include` \\- 字符串或正则表达式。只有名称匹配的组件会被缓存\n\n- `exclude` \\- 字符串或正则表达式。任何名称匹配的组件都不会被缓存\n\n- `max` \\- 数字。最多可以缓存多少组件实例\n\n关于`keep-alive`的基本用法：\n\n```go\n<keep-alive>\n  <component :is=\"view\"></component>\n</keep-alive>\n```\n\n使用`includes`和`exclude`：\n\n```go\n<keep-alive include=\"a,b\">\n  <component :is=\"view\"></component>\n</keep-alive>\n\n<!-- 正则表达式 (使用 `v-bind`) -->\n<keep-alive :include=\"/a|b/\">\n  <component :is=\"view\"></component>\n</keep-alive>\n\n<!-- 数组 (使用 `v-bind`) -->\n<keep-alive :include=\"['a', 'b']\">\n  <component :is=\"view\"></component>\n</keep-alive>\n```\n\n匹配首先检查组件自身的 `name` 选项，如果 `name` 选项不可用，则匹配它的局部注册名称 \\(父组件 `components` 选项的键值\\)，匿名组件不能被匹配\n\n设置了 keep-alive 缓存的组件，会多出两个生命周期钩子（`activated`与`deactivated`）：\n\n- 首次进入组件时：`beforeRouteEnter` > `beforeCreate` > `created`\\> `mounted` > `activated` > ... ... > `beforeRouteLeave` > `deactivated`\n\n- 再次进入组件时：`beforeRouteEnter` >`activated` > ... ... > `beforeRouteLeave` > `deactivated`\n\n## 二、使用场景\n\n使用原则：当我们在某些场景下不需要让页面重新加载时我们可以使用`keepalive`\n\n举个栗子:\n\n当我们从`首页`–>`列表页`–>`商详页`–>`再返回`，这时候列表页应该是需要`keep-alive`\n\n从`首页`–>`列表页`–>`商详页`–>`返回到列表页(需要缓存)`–>`返回到首页(需要缓存)`–>`再次进入列表页(不需要缓存)`，这时候可以按需来控制页面的`keep-alive`\n\n在路由中设置`keepAlive`属性判断是否需要缓存\n\n```go\n{\n  path: 'list',\n  name: 'itemList', // 列表页\n  component (resolve) {\n    require(['@/pages/item/list'], resolve)\n },\n meta: {\n  keepAlive: true,\n  title: '列表页'\n }\n}\n```\n\n使用`<keep-alive>`\n\n```go\n<div id=\"app\" class='wrapper'>\n    <keep-alive>\n        <!-- 需要缓存的视图组件 --> \n        <router-view v-if=\"$route.meta.keepAlive\"></router-view>\n     </keep-alive>\n      <!-- 不需要缓存的视图组件 -->\n     <router-view v-if=\"!$route.meta.keepAlive\"></router-view>\n</div>\n```\n\n## 三、原理分析\n\n`keep-alive`是`vue`中内置的一个组件\n\n源码位置：src/core/components/keep-alive.js\n\n```go\nexport default {\n  name: 'keep-alive',\n  abstract: true,\n\n  props: {\n    include: [String, RegExp, Array],\n    exclude: [String, RegExp, Array],\n    max: [String, Number]\n  },\n\n  created () {\n    this.cache = Object.create(null)\n    this.keys = []\n  },\n\n  destroyed () {\n    for (const key in this.cache) {\n      pruneCacheEntry(this.cache, key, this.keys)\n    }\n  },\n\n  mounted () {\n    this.$watch('include', val => {\n      pruneCache(this, name => matches(val, name))\n    })\n    this.$watch('exclude', val => {\n      pruneCache(this, name => !matches(val, name))\n    })\n  },\n\n  render() {\n    /* 获取默认插槽中的第一个组件节点 */\n    const slot = this.$slots.default\n    const vnode = getFirstComponentChild(slot)\n    /* 获取该组件节点的componentOptions */\n    const componentOptions = vnode && vnode.componentOptions\n\n    if (componentOptions) {\n      /* 获取该组件节点的名称，优先获取组件的name字段，如果name不存在则获取组件的tag */\n      const name = getComponentName(componentOptions)\n\n      const { include, exclude } = this\n      /* 如果name不在inlcude中或者存在于exlude中则表示不缓存，直接返回vnode */\n      if (\n        (include && (!name || !matches(include, name))) ||\n        // excluded\n        (exclude && name && matches(exclude, name))\n      ) {\n        return vnode\n      }\n\n      const { cache, keys } = this\n      /* 获取组件的key值 */\n      const key = vnode.key == null\n        // same constructor may get registered as different local components\n        // so cid alone is not enough (#3269)\n        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')\n        : vnode.key\n     /*  拿到key值后去this.cache对象中去寻找是否有该值，如果有则表示该组件有缓存，即命中缓存 */\n      if (cache[key]) {\n        vnode.componentInstance = cache[key].componentInstance\n        // make current key freshest\n        remove(keys, key)\n        keys.push(key)\n      }\n        /* 如果没有命中缓存，则将其设置进缓存 */\n        else {\n        cache[key] = vnode\n        keys.push(key)\n        // prune oldest entry\n        /* 如果配置了max并且缓存的长度超过了this.max，则从缓存中删除第一个 */\n        if (this.max && keys.length > parseInt(this.max)) {\n          pruneCacheEntry(cache, keys[0], keys, this._vnode)\n        }\n      }\n\n      vnode.data.keepAlive = true\n    }\n    return vnode || (slot && slot[0])\n  }\n}\n```\n\n可以看到该组件没有`template`，而是用了`render`，在组件渲染的时候会自动执行`render`函数\n\n`this.cache`是一个对象，用来存储需要缓存的组件，它将以如下形式存储：\n\n```go\nthis.cache = {\n    'key1':'组件1',\n    'key2':'组件2',\n    // ...\n}\n```\n\n在组件销毁的时候执行`pruneCacheEntry`函数\n\n```go\nfunction pruneCacheEntry (\n  cache: VNodeCache,\n  key: string,\n  keys: Array<string>,\n  current?: VNode\n) {\n  const cached = cache[key]\n  /* 判断当前没有处于被渲染状态的组件，将其销毁*/\n  if (cached && (!current || cached.tag !== current.tag)) {\n    cached.componentInstance.$destroy()\n  }\n  cache[key] = null\n  remove(keys, key)\n}\n```\n\n在`mounted`钩子函数中观测 `include` 和 `exclude` 的变化，如下：\n\n```go\nmounted () {\n    this.$watch('include', val => {\n        pruneCache(this, name => matches(val, name))\n    })\n    this.$watch('exclude', val => {\n        pruneCache(this, name => !matches(val, name))\n    })\n}\n```\n\n如果`include` 或`exclude` 发生了变化，即表示定义需要缓存的组件的规则或者不需要缓存的组件的规则发生了变化，那么就执行`pruneCache`函数，函数如下：\n\n```go\nfunction pruneCache (keepAliveInstance, filter) {\n  const { cache, keys, _vnode } = keepAliveInstance\n  for (const key in cache) {\n    const cachedNode = cache[key]\n    if (cachedNode) {\n      const name = getComponentName(cachedNode.componentOptions)\n      if (name && !filter(name)) {\n        pruneCacheEntry(cache, key, keys, _vnode)\n      }\n    }\n  }\n}\n```\n\n在该函数内对`this.cache`对象进行遍历，取出每一项的`name`值，用其与新的缓存规则进行匹配，如果匹配不上，则表示在新的缓存规则下该组件已经不需要被缓存，则调用`pruneCacheEntry`函数将其从`this.cache`对象剔除即可\n\n关于`keep-alive`的最强大缓存功能是在`render`函数中实现\n\n首先获取组件的`key`值：\n\n```go\nconst key = vnode.key == null? \ncomponentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')\n: vnode.key\n```\n\n拿到`key`值后去`this.cache`对象中去寻找是否有该值，如果有则表示该组件有缓存，即命中缓存，如下：\n\n```go\n/* 如果命中缓存，则直接从缓存中拿 vnode 的组件实例 */\nif (cache[key]) {\n    vnode.componentInstance = cache[key].componentInstance\n    /* 调整该组件key的顺序，将其从原来的地方删掉并重新放在最后一个 */\n    remove(keys, key)\n    keys.push(key)\n} \n```\n\n直接从缓存中拿 `vnode` 的组件实例，此时重新调整该组件`key`的顺序，将其从原来的地方删掉并重新放在`this.keys`中最后一个\n\n`this.cache`对象中没有该`key`值的情况，如下：\n\n```go\n/* 如果没有命中缓存，则将其设置进缓存 */\nelse {\n    cache[key] = vnode\n    keys.push(key)\n    /* 如果配置了max并且缓存的长度超过了this.max，则从缓存中删除第一个 */\n    if (this.max && keys.length > parseInt(this.max)) {\n        pruneCacheEntry(cache, keys[0], keys, this._vnode)\n    }\n}\n```\n\n表明该组件还没有被缓存过，则以该组件的`key`为键，组件`vnode`为值，将其存入`this.cache`中，并且把`key`存入`this.keys`中\n\n此时再判断`this.keys`中缓存组件的数量是否超过了设置的最大缓存数量值`this.max`，如果超过了，则把第一个缓存组件删掉\n\n## 四、思考题：缓存后如何获取数据\n\n解决方案可以有以下两种：\n\n- beforeRouteEnter\n\n- actived\n\n### beforeRouteEnter\n\n每次组件渲染的时候，都会执行`beforeRouteEnter`\n\n```go\nbeforeRouteEnter(to, from, next){\n    next(vm=>{\n        console.log(vm)\n        // 每次进入路由执行\n        vm.getData()  // 获取数据\n    })\n},\n```\n\n### actived\n\n在`keep-alive`缓存的组件被激活的时候，都会执行`actived`钩子\n\n```go\nactivated(){\n   this.getData() // 获取数据\n},\n```\n\n注意：服务器端渲染期间`avtived`不被调用\n\n## 参考文献\n\n- https://www.cnblogs.com/dhui/p/13589401.html\n- https://www.cnblogs.com/wangjiachen666/p/11497200.html\n- https://vue3js.cn/docs/zh  \n"
  },
  {
    "path": "docs/vue/key.md",
    "content": "# 面试官：你知道vue中key的原理吗？说说你对它的理解\n\n![](https://static.vue-js.com/bc6e9540-3f41-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、Key是什么\n\n开始之前，我们先还原两个实际工作场景\n\n1.  当我们在使用`v-for`时，需要给单元加上`key`\n\n```js\n<ul>\n    <li v-for=\"item in items\" :key=\"item.id\">...</li>\n</ul>\n```\n2. 用`+new Date()`生成的时间戳作为`key`，手动强制触发重新渲染\n```js\n<Comp :key=\"+new Date()\" />\n```\n\n那么这背后的逻辑是什么，`key`的作用又是什么？\n\n一句话来讲\n\n> key是给每一个vnode的唯一id，也是diff的一种优化策略，可以根据key，更准确， 更快的找到对应的vnode节点\n\n### 场景背后的逻辑\n\n当我们在使用`v-for`时，需要给单元加上`key`\n\n- 如果不用key，Vue会采用就地复地原则：最小化element的移动，并且会尝试尽最大程度在同适当的地方对相同类型的element，做patch或者reuse。\n\n- 如果使用了key，Vue会根据keys的顺序记录element，曾经拥有了key的element如果不再出现的话，会被直接remove或者destoryed\n\n用`+new Date()`生成的时间戳作为`key`，手动强制触发重新渲染\n\n- 当拥有新值的rerender作为key时，拥有了新key的Comp出现了，那么旧key Comp会被移除，新key Comp触发渲染\n\n\n## 二、设置key与不设置key区别\n\n\n举个例子：\n\n创建一个实例，2秒后往`items`数组插入数据\n\n```html\n<body>\n  <div id=\"demo\">\n    <p v-for=\"item in items\" :key=\"item\">{{item}}</p>\n  </div>\n  <script src=\"../../dist/vue.js\"></script>\n  <script>\n    // 创建实例\n    const app = new Vue({\n      el: '#demo',\n      data: { items: ['a', 'b', 'c', 'd', 'e'] },\n      mounted () {\n        setTimeout(() => { \n          this.items.splice(2, 0, 'f')  // \n       }, 2000);\n     },\n   });\n  </script>\n</body>\n```\n\n在不使用`key`的情况，`vue`会进行这样的操作：\n\n ![](https://static.vue-js.com/c9da6790-3f41-11eb-85f6-6fac77c0c9b3.png)\n\n分析下整体流程：\n\n- 比较A，A，相同类型的节点，进行`patch`，但数据相同，不发生`dom`操作\n- 比较B，B，相同类型的节点，进行`patch`，但数据相同，不发生`dom`操作\n- 比较C，F，相同类型的节点，进行`patch`，数据不同，发生`dom`操作\n- 比较D，C，相同类型的节点，进行`patch`，数据不同，发生`dom`操作\n- 比较E，D，相同类型的节点，进行`patch`，数据不同，发生`dom`操作\n- 循环结束，将E插入到`DOM`中\n\n一共发生了3次更新，1次插入操作\n\n在使用`key`的情况：`vue`会进行这样的操作：\n\n- 比较A，A，相同类型的节点，进行`patch`，但数据相同，不发生`dom`操作\n- 比较B，B，相同类型的节点，进行`patch`，但数据相同，不发生`dom`操作\n- 比较C，F，不相同类型的节点\n  - 比较E、E，相同类型的节点，进行`patch`，但数据相同，不发生`dom`操作\n- 比较D、D，相同类型的节点，进行`patch`，但数据相同，不发生`dom`操作\n- 比较C、C，相同类型的节点，进行`patch`，但数据相同，不发生`dom`操作\n- 循环结束，将F插入到C之前\n\n一共发生了0次更新，1次插入操作\n\n通过上面两个小例子，可见设置`key`能够大大减少对页面的`DOM`操作，提高了`diff`效率\n\n### 设置key值一定能提高diff效率吗？\n\n其实不然，文档中也明确表示\n\n> 当 Vue.js 用 v-for 正在更新已渲染过的元素列表时，它默认用“就地复用”策略。如果数据项的顺序被改变，Vue 将不会移动 DOM 元素来匹配数据项的顺序， 而是简单复用此处每个元素，并且确保它在特定索引下显示已被渲染过的每个元素\n\n这个默认的模式是高效的，但是只适用于不依赖子组件状态或临时 DOM 状态 (例如：表单输入值) 的列表渲染输出\n\n建议尽可能在使用 `v-for` 时提供 `key`，除非遍历输出的 DOM 内容非常简单，或者是刻意依赖默认行为以获取性能上的提升\n\n\n## 三、原理分析\n\n源码位置：core/vdom/patch.js\n\n这里判断是否为同一个`key`，首先判断的是`key`值是否相等如果没有设置`key`，那么`key`为`undefined`，这时候`undefined`是恒等于`undefined`\n\n```js\nfunction sameVnode (a, b) {\n    return (\n        a.key === b.key && (\n            (\n                a.tag === b.tag &&\n                a.isComment === b.isComment &&\n                isDef(a.data) === isDef(b.data) &&\n                sameInputType(a, b)\n            ) || (\n                isTrue(a.isAsyncPlaceholder) &&\n                a.asyncFactory === b.asyncFactory &&\n                isUndef(b.asyncFactory.error)\n            )\n        )\n    )\n}\n```\n\n`updateChildren`方法中会对新旧`vnode`进行`diff`，然后将比对出的结果用来更新真实的`DOM`\n\n```js\nfunction updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {\n    ...\n    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n        if (isUndef(oldStartVnode)) {\n            ...\n        } else if (isUndef(oldEndVnode)) {\n            ...\n        } else if (sameVnode(oldStartVnode, newStartVnode)) {\n            ...\n        } else if (sameVnode(oldEndVnode, newEndVnode)) {\n            ...\n        } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n            ...\n        } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n            ...\n        } else {\n            if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)\n            idxInOld = isDef(newStartVnode.key)\n                ? oldKeyToIdx[newStartVnode.key]\n                : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)\n            if (isUndef(idxInOld)) { // New element\n                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)\n            } else {\n                vnodeToMove = oldCh[idxInOld]\n                if (sameVnode(vnodeToMove, newStartVnode)) {\n                    patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)\n                    oldCh[idxInOld] = undefined\n                    canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)\n                } else {\n                    // same key but different element. treat as new element\n                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)\n                }\n            }\n            newStartVnode = newCh[++newStartIdx]\n        }\n    }\n    ...\n}\n```\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903826693029895\n- https://juejin.cn/post/6844903985397104648\n- https://vue3js.cn/docs/zh"
  },
  {
    "path": "docs/vue/lifecycle.md",
    "content": "#  面试官：请描述下你对vue生命周期的理解？在created和mounted这两个生命周期中请求数据有什么区别呢？\n\n![](https://static.vue-js.com/3a119e10-3aca-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、生命周期是什么  \n\n生命周期`（Life Cycle）`的概念应用很广泛，特别是在政治、经济、环境、技术、社会等诸多领域经常出现，其基本涵义可以通俗地理解为“从摇篮到坟墓”`（Cradle-to-Grave）`的整个过程在`Vue`中实例从创建到销毁的过程就是生命周期，即指从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程我们可以把组件比喻成工厂里面的一条流水线，每个工人（生命周期）站在各自的岗位，当任务流转到工人身边的时候，工人就开始工作PS：在`Vue`生命周期钩子会自动绑定 `this` 上下文到实例中，因此你可以访问数据，对 `property` 和方法进行运算这意味着**你不能使用箭头函数来定义一个生命周期方法** \\(例如 `created: () => this.fetchTodos()`\\)\n\n## 二、生命周期有哪些\n\nVue生命周期总共可以分为8个阶段：创建前后, 载入前后,更新前后,销毁前销毁后，以及一些特殊场景的生命周期\n\n| 生命周期 | 描述 |\n| :-- | :-- |\n| beforeCreate | 组件实例被创建之初 |\n| created | 组件实例已经完全创建 |\n| beforeMount | 组件挂载之前 |\n| mounted | 组件挂载到实例上去之后 |\n| beforeUpdate | 组件数据发生变化，更新之前 |\n| updated | 组件数据更新之后 |\n| beforeDestroy | 组件实例销毁之前 |\n| destroyed | 组件实例销毁之后 |\n| activated | keep-alive 缓存的组件激活时 |\n| deactivated | keep-alive 缓存的组件停用时调用 |\n| errorCaptured | 捕获一个来自子孙组件的错误时被调用 |\n\n## 三、生命周期整体流程\n\n`Vue`生命周期流程图\n\n ![](https://static.vue-js.com/44114780-3aca-11eb-85f6-6fac77c0c9b3.png)\n\n#### 具体分析\n\n**beforeCreate -> created**\n\n- 初始化`vue`实例，进行数据观测\n\n**created**\n\n- 完成数据观测，属性与方法的运算，`watch`、`event`事件回调的配置\n- 可调用`methods`中的方法，访问和修改data数据触发响应式渲染`dom`，可通过`computed`和`watch`完成数据计算\n- 此时`vm.$el` 并没有被创建\n\n**created -> beforeMount**\n\n- 判断是否存在`el`选项，若不存在则停止编译，直到调用`vm.$mount(el)`才会继续编译\n- 优先级：`render` > `template` > `outerHTML`\n- `vm.el`获取到的是挂载`DOM`的\n\n**beforeMount**\n\n- 在此阶段可获取到`vm.el`\n- 此阶段`vm.el`虽已完成DOM初始化，但并未挂载在`el`选项上\n\n**beforeMount -> mounted**\n\n- 此阶段`vm.el`完成挂载，`vm.$el`生成的`DOM`替换了`el`选项所对应的`DOM`\n\n**mounted**\n\n- `vm.el`已完成`DOM`的挂载与渲染，此刻打印`vm.$el`，发现之前的挂载点及内容已被替换成新的DOM\n\n**beforeUpdate**\n\n- 更新的数据必须是被渲染在模板上的（`el`、`template`、`render`之一）\n- 此时`view`层还未更新\n- 若在`beforeUpdate`中再次修改数据，不会再次触发更新方法\n\n**updated**\n\n- 完成`view`层的更新\n- 若在`updated`中再次修改数据，会再次触发更新方法（`beforeUpdate`、`updated`）\n\n**beforeDestroy**\n\n- 实例被销毁前调用，此时实例属性与方法仍可访问\n\n**destroyed**\n\n- 完全销毁一个实例。可清理它与其它实例的连接，解绑它的全部指令及事件监听器\n- 并不能清除DOM，仅仅销毁实例\n\n  \n\n**使用场景分析**\n\n  \n\n| 生命周期 | 描述 |\n| :-- | :-- |\n| beforeCreate | 执行时组件实例还未创建，通常用于插件开发中执行一些初始化任务 |\n| created | 组件初始化完毕，各种数据可以使用，常用于异步数据获取 |\n| beforeMount | 未执行渲染、更新，dom未创建 |\n| mounted | 初始化结束，dom已创建，可用于获取访问数据和dom元素 |\n| beforeUpdate | 更新前，可用于获取更新前各种状态 |\n| updated | 更新后，所有状态已是最新 |\n| beforeDestroy | 销毁前，可用于一些定时器或订阅的取消 |\n| destroyed | 组件已销毁，作用同上 |\n\n## 四、题外话：数据请求在created和mouted的区别\n\n`created`是在组件实例一旦创建完成的时候立刻调用，这时候页面`dom`节点并未生成；`mounted`是在页面`dom`节点渲染完毕之后就立刻执行的。触发时机上`created`是比`mounted`要更早的，两者的相同点：都能拿到实例对象的属性和方法。\n讨论这个问题本质就是触发的时机，放在`mounted`中的请求有可能导致页面闪动（因为此时页面`dom`结构已经生成），但如果在页面加载前完成请求，则不会出现此情况。建议对页面内容的改动放在`created`生命周期当中。\n\n## 参考文献\n\n- https://juejin.cn/post/6844903811094413320\n- https://baike.baidu.com/\n- http://cn.vuejs.org/\n\n  \n\n面试官VUE系列总进度：4／33\n\n[面试官：说说你对vue的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484101&idx=1&sn=83b0983f0fca7d7c556e4cb0bff8c9b8&chksm=fc10c093cb674985ef3bd2966f66fc28c5eb70b0037e4be1af4bf54fb6fa9571985abd31d52f&scene=21#wechat_redirect)  \n\n[面试官：说说你对SPA（单页应用）的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484119&idx=1&sn=d171b28a00d42549d279498944a98519&chksm=fc10c081cb6749976814aaeda6a6433db418223cec57edda7e15b9e5a0ca69ad549655639c61&scene=21#wechat_redirect)\n\n[面试官：说说你对双向绑定的理解\\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484167&idx=1&sn=7b00b4333ab2722f25f12586b70667ca&chksm=fc10c151cb6748476008dab2f4e6c6264f5d19678305955c85cec1b619e56e8f7457b7357fb9&scene=21#wechat_redirect)  \n\n![](https://static.vue-js.com/821b87b0-3ac6-11eb-ab90-d9ae814b240d.png)\n"
  },
  {
    "path": "docs/vue/mixin.md",
    "content": "# 面试官：说说你对vue的mixin的理解，有什么应用场景？ \n\n![](https://static.vue-js.com/8a739c90-3b7f-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、mixin是什么\n\n`Mixin`是面向对象程序设计语言中的类，提供了方法的实现。其他类可以访问`mixin`类的方法而不必成为其子类\n\n`Mixin`类通常作为功能模块使用，在需要该功能时“混入”，有利于代码复用又避免了多继承的复杂\n\n### Vue中的mixin\n先来看一下官方定义\n> `mixin`（混入），提供了一种非常灵活的方式，来分发 `Vue` 组件中的可复用功能。\n\n本质其实就是一个`js`对象，它可以包含我们组件中任意功能选项，如`data`、`components`、`methods `、`created`、`computed`等等\n\n我们只要将共用的功能以对象的方式传入 `mixins`选项中，当组件使用 `mixins`对象时所有`mixins`对象的选项都将被混入该组件本身的选项中来\n\n在`Vue`中我们可以**局部混入**跟**全局混入**\n\n### 局部混入\n\n定义一个`mixin`对象，有组件`options`的`data`、`methods`属性\n\n```js\nvar myMixin = {\n  created: function () {\n    this.hello()\n  },\n  methods: {\n    hello: function () {\n      console.log('hello from mixin!')\n    }\n  }\n}\n```\n\n组件通过`mixins`属性调用`mixin`对象\n\n```js\nVue.component('componentA',{\n  mixins: [myMixin]\n})\n```\n\n该组件在使用的时候，混合了`mixin`里面的方法，在自动执行`created`生命钩子，执行`hello`方法\n\n### 全局混入\n\n通过`Vue.mixin()`进行全局的混入\n\n```js\nVue.mixin({\n  created: function () {\n      console.log(\"全局混入\")\n    }\n})\n```\n\n使用全局混入需要特别注意，因为它会影响到每一个组件实例（包括第三方组件）\n\nPS：全局混入常用于插件的编写\n\n### 注意事项：\n\n当组件存在与`mixin`对象相同的选项的时候，进行递归合并的时候组件的选项会覆盖`mixin`的选项\n\n但是如果相同选项为生命周期钩子的时候，会合并成一个数组，先执行`mixin`的钩子，再执行组件的钩子\n\n\n\n## 二、使用场景\n\n在日常的开发中，我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码，这些代码的功能相对独立\n\n这时，可以通过`Vue`的`mixin`功能将相同或者相似的代码提出来\n\n举个例子\n\n定义一个`modal`弹窗组件，内部通过`isShowing`来控制显示\n\n```js\nconst Modal = {\n  template: '#modal',\n  data() {\n    return {\n      isShowing: false\n    }\n  },\n  methods: {\n    toggleShow() {\n      this.isShowing = !this.isShowing;\n    }\n  }\n}\n```\n\n定义一个`tooltip`提示框，内部通过`isShowing`来控制显示\n\n```js\nconst Tooltip = {\n  template: '#tooltip',\n  data() {\n    return {\n      isShowing: false\n    }\n  },\n  methods: {\n    toggleShow() {\n      this.isShowing = !this.isShowing;\n    }\n  }\n}\n```\n\n通过观察上面两个组件，发现两者的逻辑是相同，代码控制显示也是相同的，这时候`mixin`就派上用场了\n\n首先抽出共同代码，编写一个`mixin`\n\n```js\nconst toggle = {\n  data() {\n    return {\n      isShowing: false\n    }\n  },\n  methods: {\n    toggleShow() {\n      this.isShowing = !this.isShowing;\n    }\n  }\n}\n```\n\n两个组件在使用上，只需要引入`mixin`\n\n```js\nconst Modal = {\n  template: '#modal',\n  mixins: [toggle]\n};\n \nconst Tooltip = {\n  template: '#tooltip',\n  mixins: [toggle]\n}\n```\n\n通过上面小小的例子，让我们知道了`Mixin`对于封装一些可复用的功能如此有趣、方便、实用\n\n\n\n## 三、源码分析\n\n首先从`Vue.mixin`入手\n\n源码位置：/src/core/global-api/mixin.js\n\n```js\nexport function initMixin (Vue: GlobalAPI) {\n  Vue.mixin = function (mixin: Object) {\n    this.options = mergeOptions(this.options, mixin)\n    return this\n  }\n}\n```\n\n主要是调用`merOptions`方法\n\n源码位置：/src/core/util/options.js\n\n```js\nexport function mergeOptions (\n  parent: Object,\n  child: Object,\n  vm?: Component\n): Object {\n\nif (child.mixins) { // 判断有没有mixin 也就是mixin里面挂mixin的情况 有的话递归进行合并\n    for (let i = 0, l = child.mixins.length; i < l; i++) {\n    parent = mergeOptions(parent, child.mixins[i], vm)\n    }\n}\n\n  const options = {} \n  let key\n  for (key in parent) {\n    mergeField(key) // 先遍历parent的key 调对应的strats[XXX]方法进行合并\n  }\n  for (key in child) {\n    if (!hasOwn(parent, key)) { // 如果parent已经处理过某个key 就不处理了\n      mergeField(key) // 处理child中的key 也就parent中没有处理过的key\n    }\n  }\n  function mergeField (key) {\n    const strat = strats[key] || defaultStrat\n    options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats中不同的方法进行合并\n  }\n  return options\n}\n```\n从上面的源码，我们得到以下几点：\n\n- 优先递归处理 `mixins`\n- 先遍历合并`parent` 中的`key`，调用`mergeField`方法进行合并，然后保存在变量`options`\n- 再遍历 `child`，合并补上 `parent` 中没有的`key`，调用`mergeField`方法进行合并，保存在变量`options`\n- 通过 `mergeField` 函数进行了合并\n\n下面是关于`Vue`的几种类型的合并策略\n\n- 替换型\n- 合并型\n- 队列型\n- 叠加型\n\n### 替换型\n\n替换型合并有`props`、`methods`、`inject`、`computed`\n\n```js\nstrats.props =\nstrats.methods =\nstrats.inject =\nstrats.computed = function (\n  parentVal: ?Object,\n  childVal: ?Object,\n  vm?: Component,\n  key: string\n): ?Object {\n  if (!parentVal) return childVal // 如果parentVal没有值，直接返回childVal\n  const ret = Object.create(null) // 创建一个第三方对象 ret\n  extend(ret, parentVal) // extend方法实际是把parentVal的属性复制到ret中\n  if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中\n  return ret\n}\nstrats.provide = mergeDataOrFn\n```\n\n同名的`props`、`methods`、`inject`、`computed`会被后来者代替\n\n### 合并型\n\n和并型合并有：`data`\n\n```js\nstrats.data = function(parentVal, childVal, vm) {    \n    return mergeDataOrFn(\n        parentVal, childVal, vm\n    )\n};\n\nfunction mergeDataOrFn(parentVal, childVal, vm) {    \n    return function mergedInstanceDataFn() {        \n        var childData = childVal.call(vm, vm) // 执行data挂的函数得到对象\n        var parentData = parentVal.call(vm, vm)        \n        if (childData) {            \n            return mergeData(childData, parentData) // 将2个对象进行合并                                 \n        } else {            \n            return parentData // 如果没有childData 直接返回parentData\n        }\n    }\n}\n\nfunction mergeData(to, from) {    \n    if (!from) return to    \n    var key, toVal, fromVal;    \n    var keys = Object.keys(from);   \n    for (var i = 0; i < keys.length; i++) {\n        key = keys[i];\n        toVal = to[key];\n        fromVal = from[key];    \n        // 如果不存在这个属性，就重新设置\n        if (!to.hasOwnProperty(key)) {\n            set(to, key, fromVal);\n        }      \n        // 存在相同属性，合并对象\n        else if (typeof toVal ==\"object\" && typeof fromVal ==\"object\") {\n            mergeData(toVal, fromVal);\n        }\n    }    \n    return to\n}\n```\n\n`mergeData`函数遍历了要合并的 data 的所有属性，然后根据不同情况进行合并：\n\n- 当目标 data 对象不包含当前属性时，调用 `set` 方法进行合并（set方法其实就是一些合并重新赋值的方法）\n- 当目标 data 对象包含当前属性并且当前值为纯对象时，递归合并当前对象值，这样做是为了防止对象存在新增属性\n\n### 队列性\n\n队列性合并有：全部生命周期和`watch`\n\n```js\nfunction mergeHook (\n  parentVal: ?Array<Function>,\n  childVal: ?Function | ?Array<Function>\n): ?Array<Function> {\n  return childVal\n    ? parentVal\n      ? parentVal.concat(childVal)\n      : Array.isArray(childVal)\n        ? childVal\n        : [childVal]\n    : parentVal\n}\n\nLIFECYCLE_HOOKS.forEach(hook => {\n  strats[hook] = mergeHook\n})\n\n// watch\nstrats.watch = function (\n  parentVal,\n  childVal,\n  vm,\n  key\n) {\n  // work around Firefox's Object.prototype.watch...\n  if (parentVal === nativeWatch) { parentVal = undefined; }\n  if (childVal === nativeWatch) { childVal = undefined; }\n  /* istanbul ignore if */\n  if (!childVal) { return Object.create(parentVal || null) }\n  {\n    assertObjectType(key, childVal, vm);\n  }\n  if (!parentVal) { return childVal }\n  var ret = {};\n  extend(ret, parentVal);\n  for (var key$1 in childVal) {\n    var parent = ret[key$1];\n    var child = childVal[key$1];\n    if (parent && !Array.isArray(parent)) {\n      parent = [parent];\n    }\n    ret[key$1] = parent\n      ? parent.concat(child)\n      : Array.isArray(child) ? child : [child];\n  }\n  return ret\n};\n```\n\n生命周期钩子和`watch`被合并为一个数组，然后正序遍历一次执行\n\n### 叠加型\n\n叠加型合并有：`component`、`directives`、`filters`\n\n```js\nstrats.components=\nstrats.directives=\n\nstrats.filters = function mergeAssets(\n    parentVal, childVal, vm, key\n) {    \n    var res = Object.create(parentVal || null);    \n    if (childVal) { \n        for (var key in childVal) {\n            res[key] = childVal[key];\n        }   \n    } \n    return res\n}\n```\n\n叠加型主要是通过原型链进行层层的叠加\n\n\n\n### 小结：\n\n- 替换型策略有`props`、`methods`、`inject`、`computed`，就是将新的同名参数替代旧的参数\n- 合并型策略是`data`, 通过`set`方法进行合并和重新赋值\n- 队列型策略有生命周期函数和`watch`，原理是将函数存入一个数组，然后正序遍历依次执行\n- 叠加型有`component`、`directives`、`filters`，通过原型链进行层层的叠加\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/31018570\n- https://juejin.cn/post/6844904015495446536#heading-1\n- https://juejin.cn/post/6844903846775357453\n- https://vue3js.cn/docs/zh"
  },
  {
    "path": "docs/vue/modifier.md",
    "content": "# 面试官：Vue常用的修饰符有哪些有什么应用场景\n\n![](https://static.vue-js.com/8f718e30-42c0-11eb-ab90-d9ae814b240d.png)\n\n## 一、修饰符是什么\n\n在程序世界里，修饰符是用于限定类型以及类型成员的声明的一种符号\n\n在`Vue`中，修饰符处理了许多`DOM`事件的细节，让我们不再需要花大量的时间去处理这些烦恼的事情，而能有更多的精力专注于程序的逻辑处理\n\n`vue`中修饰符分为以下五种：\n\n- 表单修饰符\n- 事件修饰符\n- 鼠标按键修饰符\n- 键值修饰符\n- v-bind修饰符\n\n## 二、修饰符的作用\n\n### 表单修饰符\n\n在我们填写表单的时候用得最多的是`input`标签，指令用得最多的是`v-model`\n\n关于表单的修饰符有如下：\n\n- lazy\n- trim\n- number\n\n#### lazy\n\n在我们填完信息，光标离开标签的时候，才会将值赋予给`value`，也就是在`change`事件之后再进行信息同步\n\n```js\n<input type=\"text\" v-model.lazy=\"value\">\n<p>{{value}}</p>\n```\n\n#### trim\n\n自动过滤用户输入的首空格字符，而中间的空格不会过滤\n\n```js\n<input type=\"text\" v-model.trim=\"value\">\n```\n\n#### number\n\n自动将用户的输入值转为数值类型，但如果这个值无法被`parseFloat`解析，则会返回原来的值\n\n```js\n<input v-model.number=\"age\" type=\"number\">\n```\n\n### 事件修饰符\n\n事件修饰符是对事件捕获以及目标进行了处理，有如下修饰符：\n\n- stop\n- prevent\n- self\n- once\n- capture\n- passive\n- native\n\n#### stop\n\n阻止了事件冒泡，相当于调用了`event.stopPropagation`方法\n\n```js\n<div @click=\"shout(2)\">\n  <button @click.stop=\"shout(1)\">ok</button>\n</div>\n//只输出1\n```\n\n#### prevent\n\n阻止了事件的默认行为，相当于调用了`event.preventDefault`方法\n\n```js\n<form v-on:submit.prevent=\"onSubmit\"></form>\n```\n\n#### self\n\n只当在 `event.target` 是当前元素自身时触发处理函数\n\n```js\n<div v-on:click.self=\"doThat\">...</div>\n```\n\n> 使用修饰符时，顺序很重要；相应的代码会以同样的顺序产生。因此，用 `v-on:click.prevent.self` 会阻止**所有的点击**，而 `v-on:click.self.prevent` 只会阻止对元素自身的点击\n\n#### once\n\n绑定了事件以后只能触发一次，第二次就不会触发\n\n```js\n<button @click.once=\"shout(1)\">ok</button>\n```\n\n#### capture\n\n使事件触发从包含这个元素的顶层开始往下触发\n\n```js\n<div @click.capture=\"shout(1)\">\n    obj1\n<div @click.capture=\"shout(2)\">\n    obj2\n<div @click=\"shout(3)\">\n    obj3\n<div @click=\"shout(4)\">\n    obj4\n</div>\n</div>\n</div>\n</div>\n// 输出结构: 1 2 4 3 \n```\n\n#### passive\n\n在移动端，当我们在监听元素滚动事件的时候，会一直触发`onscroll`事件会让我们的网页变卡，因此我们使用这个修饰符的时候，相当于给`onscroll`事件整了一个`.lazy`修饰符\n\n```js\n<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->\n<!-- 而不会等待 `onScroll` 完成  -->\n<!-- 这其中包含 `event.preventDefault()` 的情况 -->\n<div v-on:scroll.passive=\"onScroll\">...</div>\n```\n\n> 不要把 `.passive` 和 `.prevent` 一起使用,因为 `.prevent` 将会被忽略，同时浏览器可能会向你展示一个警告。\n>\n> `passive` 会告诉浏览器你不想阻止事件的默认行为\n\n#### native\n\n让组件变成像`html`内置标签那样监听根元素的原生事件，否则组件上使用 `v-on` 只会监听自定义事件\n\n```js\n<my-component v-on:click.native=\"doSomething\"></my-component>\n```\n\n> 使用.native修饰符来操作普通HTML标签是会令事件失效的\n\n### 鼠标按钮修饰符\n\n鼠标按钮修饰符针对的就是左键、右键、中键点击，有如下：\n\n- left 左键点击\n- right 右键点击\n- middle 中键点击\n\n```js\n<button @click.left=\"shout(1)\">ok</button>\n<button @click.right=\"shout(1)\">ok</button>\n<button @click.middle=\"shout(1)\">ok</button>\n```\n\n### 键盘修饰符\n\n键盘修饰符是用来修饰键盘事件（`onkeyup`，`onkeydown`）的，有如下：\n\n`keyCode`存在很多，但`vue`为我们提供了别名，分为以下两种：\n\n- 普通键（enter、tab、delete、space、esc、up...）\n- 系统修饰键（ctrl、alt、meta、shift...）\n\n```js\n// 只有按键为keyCode的时候才触发\n<input type=\"text\" @keyup.keyCode=\"shout()\">\n```\n\n还可以通过以下方式自定义一些全局的键盘码别名\n\n```js\nVue.config.keyCodes.f2 = 113\n```\n\n### v-bind修饰符\n\nv-bind修饰符主要是为属性进行操作，用来分别有如下：\n\n- async\n- prop\n- camel\n\n#### async\n\n能对`props`进行一个双向绑定\n\n```js\n//父组件\n<comp :myMessage.sync=\"bar\"></comp> \n//子组件\nthis.$emit('update:myMessage',params);\n```\n\n以上这种方法相当于以下的简写\n\n```js\n//父亲组件\n<comp :myMessage=\"bar\" @update:myMessage=\"func\"></comp>\nfunc(e){\n this.bar = e;\n}\n//子组件js\nfunc2(){\n  this.$emit('update:myMessage',params);\n}\n```\n\n使用`async`需要注意以下两点：\n\n- 使用`sync`的时候，子组件传递的事件名格式必须为`update:value`，其中`value`必须与子组件中`props`中声明的名称完全一致\n\n- 注意带有 `.sync` 修饰符的 `v-bind` 不能和表达式一起使用\n\n- 将 `v-bind.sync` 用在一个字面量的对象上，例如 `v-bind.sync=”{ title: doc.title }”`，是无法正常工作的\n\n#### props\n\n设置自定义标签属性，避免暴露数据，防止污染HTML结构\n\n```js\n<input id=\"uid\" title=\"title1\" value=\"1\" :index.prop=\"index\">\n```\n\n#### camel\n\n将命名变为驼峰命名法，如将` view-Box`属性名转换为 `viewBox`\n\n```js\n<svg :viewBox=\"viewBox\"></svg>\n```\n\n## 三、应用场景\n\n根据每一个修饰符的功能，我们可以得到以下修饰符的应用场景：\n\n- .stop：阻止事件冒泡\n- .native：绑定原生事件\n- .once：事件只执行一次\n- .self ：将事件绑定在自身身上，相当于阻止事件冒泡\n- .prevent：阻止默认事件\n- .caption：用于事件捕获\n- .once：只触发一次\n- .keyCode：监听特定键盘按下\n- .right：右键\n\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000016786254\n- https://vue3js.cn/docs/zh"
  },
  {
    "path": "docs/vue/new_vue.md",
    "content": "# 面试官：Vue实例挂载的过程\n\n![](https://static.vue-js.com/63194810-3a09-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、思考\n\n我们都听过知其然知其所以然这句话\n\n那么不知道大家是否思考过`new Vue()`这个过程中究竟做了些什么？\n\n过程中是如何完成数据的绑定，又是如何将数据渲染到视图的等等\n\n## 一、分析\n\n首先找到`vue`的构造函数\n\n源码位置：src\\core\\instance\\index.js\n\n```js\nfunction Vue (options) {\n  if (process.env.NODE_ENV !== 'production' &&\n    !(this instanceof Vue)\n  ) {\n    warn('Vue is a constructor and should be called with the `new` keyword')\n  }\n  this._init(options)\n}\n```\n\n`options`是用户传递过来的配置项，如`data、methods`等常用的方法\n\n`vue`构建函数调用`_init`方法，但我们发现本文件中并没有此方法，但仔细可以看到文件下方定定义了很多初始化方法\n\n```js\ninitMixin(Vue);     // 定义 _init\nstateMixin(Vue);    // 定义 $set $get $delete $watch 等\neventsMixin(Vue);   // 定义事件  $on  $once $off $emit\nlifecycleMixin(Vue);// 定义 _update  $forceUpdate  $destroy\nrenderMixin(Vue);   // 定义 _render 返回虚拟dom\n```\n\n首先可以看`initMixin`方法，发现该方法在`Vue`原型上定义了`_init`方法\n\n源码位置：src\\core\\instance\\init.js\n\n```js\nVue.prototype._init = function (options?: Object) {\n    const vm: Component = this\n    // a uid\n    vm._uid = uid++\n    let startTag, endTag\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n      startTag = `vue-perf-start:${vm._uid}`\n      endTag = `vue-perf-end:${vm._uid}`\n      mark(startTag)\n    }\n\n    // a flag to avoid this being observed\n    vm._isVue = true\n    // merge options\n    // 合并属性，判断初始化的是否是组件，这里合并主要是 mixins 或 extends 的方法\n    if (options && options._isComponent) {\n      // optimize internal component instantiation\n      // since dynamic options merging is pretty slow, and none of the\n      // internal component options needs special treatment.\n      initInternalComponent(vm, options)\n    } else { // 合并vue属性\n      vm.$options = mergeOptions(\n        resolveConstructorOptions(vm.constructor),\n        options || {},\n        vm\n      )\n    }\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      // 初始化proxy拦截器\n      initProxy(vm)\n    } else {\n      vm._renderProxy = vm\n    }\n    // expose real self\n    vm._self = vm\n    // 初始化组件生命周期标志位\n    initLifecycle(vm)\n    // 初始化组件事件侦听\n    initEvents(vm)\n    // 初始化渲染方法\n    initRender(vm)\n    callHook(vm, 'beforeCreate')\n    // 初始化依赖注入内容，在初始化data、props之前\n    initInjections(vm) // resolve injections before data/props\n    // 初始化props/data/method/watch/methods\n    initState(vm)\n    initProvide(vm) // resolve provide after data/props\n    callHook(vm, 'created')\n\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n      vm._name = formatComponentName(vm, false)\n      mark(endTag)\n      measure(`vue ${vm._name} init`, startTag, endTag)\n    }\n    // 挂载元素\n    if (vm.$options.el) {\n      vm.$mount(vm.$options.el)\n    }\n  }\n```\n\n仔细阅读上面的代码，我们得到以下结论：\n\n- 在调用`beforeCreate`之前，数据初始化并未完成，像`data`、`props`这些属性无法访问到\n\n- 到了`created`的时候，数据已经初始化完成，能够访问`data`、`props`这些属性，但这时候并未完成`dom`的挂载，因此无法访问到`dom`元素\n- 挂载方法是调用`vm.$mount`方法\n\n`initState`方法是完成`props/data/method/watch/methods`的初始化\n\n源码位置：src\\core\\instance\\state.js\n\n```js\nexport function initState (vm: Component) {\n  // 初始化组件的watcher列表\n  vm._watchers = []\n  const opts = vm.$options\n  // 初始化props\n  if (opts.props) initProps(vm, opts.props)\n  // 初始化methods方法\n  if (opts.methods) initMethods(vm, opts.methods)\n  if (opts.data) {\n    // 初始化data  \n    initData(vm)\n  } else {\n    observe(vm._data = {}, true /* asRootData */)\n  }\n  if (opts.computed) initComputed(vm, opts.computed)\n  if (opts.watch && opts.watch !== nativeWatch) {\n    initWatch(vm, opts.watch)\n  }\n}\n```\n\n我们和这里主要看初始化`data`的方法为`initData`，它与`initState`在同一文件上\n\n```js\nfunction initData (vm: Component) {\n  let data = vm.$options.data\n  // 获取到组件上的data\n  data = vm._data = typeof data === 'function'\n    ? getData(data, vm)\n    : data || {}\n  if (!isPlainObject(data)) {\n    data = {}\n    process.env.NODE_ENV !== 'production' && warn(\n      'data functions should return an object:\\n' +\n      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',\n      vm\n    )\n  }\n  // proxy data on instance\n  const keys = Object.keys(data)\n  const props = vm.$options.props\n  const methods = vm.$options.methods\n  let i = keys.length\n  while (i--) {\n    const key = keys[i]\n    if (process.env.NODE_ENV !== 'production') {\n      // 属性名不能与方法名重复\n      if (methods && hasOwn(methods, key)) {\n        warn(\n          `Method \"${key}\" has already been defined as a data property.`,\n          vm\n        )\n      }\n    }\n    // 属性名不能与state名称重复\n    if (props && hasOwn(props, key)) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `The data property \"${key}\" is already declared as a prop. ` +\n        `Use prop default value instead.`,\n        vm\n      )\n    } else if (!isReserved(key)) { // 验证key值的合法性\n      // 将_data中的数据挂载到组件vm上,这样就可以通过this.xxx访问到组件上的数据\n      proxy(vm, `_data`, key)\n    }\n  }\n  // observe data\n  // 响应式监听data是数据的变化\n  observe(data, true /* asRootData */)\n}\n```\n\n仔细阅读上面的代码，我们可以得到以下结论：\n\n- 初始化顺序：`props`、`methods`、`data`\n\n- `data`定义的时候可选择函数形式或者对象形式（组件只能为函数形式）\n\n关于数据响应式在这就不展开详细说明\n\n上文提到挂载方法是调用`vm.$mount`方法\n\n源码位置：\n\n```js\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  // 获取或查询元素\n  el = el && query(el)\n\n  /* istanbul ignore if */\n  // vue 不允许直接挂载到body或页面文档上\n  if (el === document.body || el === document.documentElement) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`\n    )\n    return this\n  }\n\n  const options = this.$options\n  // resolve template/el and convert to render function\n  if (!options.render) {\n    let template = options.template\n    // 存在template模板，解析vue模板文件\n    if (template) {\n      if (typeof template === 'string') {\n        if (template.charAt(0) === '#') {\n          template = idToTemplate(template)\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !template) {\n            warn(\n              `Template element not found or is empty: ${options.template}`,\n              this\n            )\n          }\n        }\n      } else if (template.nodeType) {\n        template = template.innerHTML\n      } else {\n        if (process.env.NODE_ENV !== 'production') {\n          warn('invalid template option:' + template, this)\n        }\n        return this\n      }\n    } else if (el) {\n      // 通过选择器获取元素内容\n      template = getOuterHTML(el)\n    }\n    if (template) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile')\n      }\n      /**\n       *  1.将temmplate解析ast tree\n       *  2.将ast tree转换成render语法字符串\n       *  3.生成render方法\n       */\n      const { render, staticRenderFns } = compileToFunctions(template, {\n        outputSourceRange: process.env.NODE_ENV !== 'production',\n        shouldDecodeNewlines,\n        shouldDecodeNewlinesForHref,\n        delimiters: options.delimiters,\n        comments: options.comments\n      }, this)\n      options.render = render\n      options.staticRenderFns = staticRenderFns\n\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile end')\n        measure(`vue ${this._name} compile`, 'compile', 'compile end')\n      }\n    }\n  }\n  return mount.call(this, el, hydrating)\n}\n```\n\n阅读上面代码，我们能得到以下结论：\n\n- 不要将根元素放到`body`或者`html`上\n\n- 可以在对象中定义`template/render`或者直接使用`template`、`el`表示元素选择器\n\n- 最终都会解析成`render`函数，调用`compileToFunctions`，会将`template`解析成`render`函数\n\n对`template`的解析步骤大致分为以下几步：\n\n- 将`html`文档片段解析成`ast`描述符\n\n- 将`ast`描述符解析成字符串\n\n- 生成`render`函数\n\n\n\n生成`render`函数，挂载到`vm`上后，会再次调用`mount`方法\n\n源码位置：src\\platforms\\web\\runtime\\index.js\n\n```js\n// public mount method\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && inBrowser ? query(el) : undefined\n  // 渲染组件\n  return mountComponent(this, el, hydrating)\n}\n```\n\n调用`mountComponent`渲染组件\n\n```js\nexport function mountComponent (\n  vm: Component,\n  el: ?Element,\n  hydrating?: boolean\n): Component {\n  vm.$el = el\n  // 如果没有获取解析的render函数，则会抛出警告\n  // render是解析模板文件生成的\n  if (!vm.$options.render) {\n    vm.$options.render = createEmptyVNode\n    if (process.env.NODE_ENV !== 'production') {\n      /* istanbul ignore if */\n      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||\n        vm.$options.el || el) {\n        warn(\n          'You are using the runtime-only build of Vue where the template ' +\n          'compiler is not available. Either pre-compile the templates into ' +\n          'render functions, or use the compiler-included build.',\n          vm\n        )\n      } else {\n        // 没有获取到vue的模板文件\n        warn(\n          'Failed to mount component: template or render function not defined.',\n          vm\n        )\n      }\n    }\n  }\n  // 执行beforeMount钩子\n  callHook(vm, 'beforeMount')\n\n  let updateComponent\n  /* istanbul ignore if */\n  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n    updateComponent = () => {\n      const name = vm._name\n      const id = vm._uid\n      const startTag = `vue-perf-start:${id}`\n      const endTag = `vue-perf-end:${id}`\n\n      mark(startTag)\n      const vnode = vm._render()\n      mark(endTag)\n      measure(`vue ${name} render`, startTag, endTag)\n\n      mark(startTag)\n      vm._update(vnode, hydrating)\n      mark(endTag)\n      measure(`vue ${name} patch`, startTag, endTag)\n    }\n  } else {\n    // 定义更新函数\n    updateComponent = () => {\n      // 实际调⽤是在lifeCycleMixin中定义的_update和renderMixin中定义的_render\n      vm._update(vm._render(), hydrating)\n    }\n  }\n  // we set this to vm._watcher inside the watcher's constructor\n  // since the watcher's initial patch may call $forceUpdate (e.g. inside child\n  // component's mounted hook), which relies on vm._watcher being already defined\n  // 监听当前组件状态，当有数据变化时，更新组件\n  new Watcher(vm, updateComponent, noop, {\n    before () {\n      if (vm._isMounted && !vm._isDestroyed) {\n        // 数据更新引发的组件更新\n        callHook(vm, 'beforeUpdate')\n      }\n    }\n  }, true /* isRenderWatcher */)\n  hydrating = false\n\n  // manually mounted instance, call mounted on self\n  // mounted is called for render-created child components in its inserted hook\n  if (vm.$vnode == null) {\n    vm._isMounted = true\n    callHook(vm, 'mounted')\n  }\n  return vm\n}\n```\n\n阅读上面代码，我们得到以下结论：\n\n- 会触发`beforeCreate`钩子\n- 定义`updateComponent`渲染页面视图的方法\n- 监听组件数据，一旦发生变化，触发`beforeUpdate`生命钩子\n\n`updateComponent`方法主要执行在`vue`初始化时声明的`render`，`update`方法\n\n`render`的作用主要是生成`vnode`\n\n源码位置：src\\core\\instance\\render.js\n\n```js\n// 定义vue 原型上的render方法\nVue.prototype._render = function (): VNode {\n    const vm: Component = this\n    // render函数来自于组件的option\n    const { render, _parentVnode } = vm.$options\n\n    if (_parentVnode) {\n        vm.$scopedSlots = normalizeScopedSlots(\n            _parentVnode.data.scopedSlots,\n            vm.$slots,\n            vm.$scopedSlots\n        )\n    }\n\n    // set parent vnode. this allows render functions to have access\n    // to the data on the placeholder node.\n    vm.$vnode = _parentVnode\n    // render self\n    let vnode\n    try {\n        // There's no need to maintain a stack because all render fns are called\n        // separately from one another. Nested component's render fns are called\n        // when parent component is patched.\n        currentRenderingInstance = vm\n        // 调用render方法，自己的独特的render方法， 传入createElement参数，生成vNode\n        vnode = render.call(vm._renderProxy, vm.$createElement)\n    } catch (e) {\n        handleError(e, vm, `render`)\n        // return error render result,\n        // or previous vnode to prevent render error causing blank component\n        /* istanbul ignore else */\n        if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {\n            try {\n                vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)\n            } catch (e) {\n                handleError(e, vm, `renderError`)\n                vnode = vm._vnode\n            }\n        } else {\n            vnode = vm._vnode\n        }\n    } finally {\n        currentRenderingInstance = null\n    }\n    // if the returned array contains only a single node, allow it\n    if (Array.isArray(vnode) && vnode.length === 1) {\n        vnode = vnode[0]\n    }\n    // return empty vnode in case the render function errored out\n    if (!(vnode instanceof VNode)) {\n        if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {\n            warn(\n                'Multiple root nodes returned from render function. Render function ' +\n                'should return a single root node.',\n                vm\n            )\n        }\n        vnode = createEmptyVNode()\n    }\n    // set parent\n    vnode.parent = _parentVnode\n    return vnode\n}\n```\n\n`_update`主要功能是调用`patch`，将`vnode`转换为真实`DOM`，并且更新到页面中\n\n源码位置：src\\core\\instance\\lifecycle.js\n\n```js\nVue.prototype._update = function (vnode: VNode, hydrating?: boolean) {\n    const vm: Component = this\n    const prevEl = vm.$el\n    const prevVnode = vm._vnode\n    // 设置当前激活的作用域\n    const restoreActiveInstance = setActiveInstance(vm)\n    vm._vnode = vnode\n    // Vue.prototype.__patch__ is injected in entry points\n    // based on the rendering backend used.\n    if (!prevVnode) {\n      // initial render\n      // 执行具体的挂载逻辑\n      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)\n    } else {\n      // updates\n      vm.$el = vm.__patch__(prevVnode, vnode)\n    }\n    restoreActiveInstance()\n    // update __vue__ reference\n    if (prevEl) {\n      prevEl.__vue__ = null\n    }\n    if (vm.$el) {\n      vm.$el.__vue__ = vm\n    }\n    // if parent is an HOC, update its $el as well\n    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {\n      vm.$parent.$el = vm.$el\n    }\n    // updated hook is called by the scheduler to ensure that children are\n    // updated in a parent's updated hook.\n  }\n```\n\n\n\n## 三、结论\n\n- `new Vue`的时候调用会调用`_init`方法\n  - 定义 `$set`、` $get` 、`$delete`、`$watch` 等方法\n  - 定义 `$on`、`$off`、`$emit`、`$off `等事件\n  - 定义 `_update`、`$forceUpdate`、`$destroy`生命周期\n\n- 调用`$mount`进行页面的挂载\n- 挂载的时候主要是通过`mountComponent`方法\n- 定义`updateComponent`更新函数\n- 执行`render`生成虚拟`DOM`\n- `_update`将虚拟`DOM`生成真实`DOM`结构，并且渲染到页面中\n\n\n\n## 参考文献\n\n- https://www.cnblogs.com/gerry2019/p/12001661.html\n- https://github.com/vuejs/vue/tree/dev/src/core/instance \n- https://vue3js.cn"
  },
  {
    "path": "docs/vue/nexttick.md",
    "content": "# 面试官：Vue中的$nextTick有什么作用？\n\n![](https://static.vue-js.com/76484d30-3aba-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、NextTick是什么\n\n官方对其的定义\n\n> 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法，获取更新后的 DOM\n\n什么意思呢？\n\n我们可以理解成，`Vue` 在更新 `DOM` 时是异步执行的。当数据发生变化，`Vue`将开启一个异步更新队列，视图需要等队列中所有数据变化完成之后，再统一进行更新\n\n举例一下\n\n`Html`结构\n\n```html\n<div id=\"app\"> {{ message }} </div>\n```\n\n构建一个`vue`实例\n\n```js\nconst vm = new Vue({\n  el: '#app',\n  data: {\n    message: '原始值'\n  }\n})\n```\n\n修改`message`\n\n```js\nthis.message = '修改后的值1'\nthis.message = '修改后的值2'\nthis.message = '修改后的值3'\n```\n\n这时候想获取页面最新的`DOM`节点，却发现获取到的是旧值\n\n```js\nconsole.log(vm.$el.textContent) // 原始值\n```\n\n这是因为`message`数据在发现变化的时候，`vue`并不会立刻去更新`Dom`，而是将修改数据的操作放在了一个异步操作队列中\n\n如果我们一直修改相同数据，异步操作队列还会进行去重\n\n等待同一事件循环中的所有数据变化完成之后，会将队列中的事件拿来进行处理，进行`DOM`的更新\n\n#### 为什么要有nexttick\n\n举个例子\n```js\n{{num}}\nfor(let i=0; i<100000; i++){\n    num = i\n}\n```\n如果没有 `nextTick` 更新机制，那么 `num` 每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图)，有了`nextTick`机制，只需要更新一次，所以`nextTick`本质是一种优化策略\n\n## 二、使用场景\n\n如果想要在修改数据后立刻得到更新后的`DOM`结构，可以使用`Vue.nextTick()`\n\n第一个参数为：回调函数（可以获取最近的`DOM`结构）\n\n第二个参数为：执行函数上下文\n\n```js\n// 修改数据\nvm.message = '修改后的值'\n// DOM 还没有更新\nconsole.log(vm.$el.textContent) // 原始的值\nVue.nextTick(function () {\n  // DOM 更新了\n  console.log(vm.$el.textContent) // 修改后的值\n})\n```\n\n组件内使用 `vm.$nextTick()` 实例方法只需要通过`this.$nextTick()`，并且回调函数中的 `this` 将自动绑定到当前的 `Vue` 实例上\n\n```js\nthis.message = '修改后的值'\nconsole.log(this.$el.textContent) // => '原始的值'\nthis.$nextTick(function () {\n    console.log(this.$el.textContent) // => '修改后的值'\n})\n```\n\n`$nextTick()` 会返回一个 `Promise` 对象，可以是用`async/await`完成相同作用的事情\n\n```js\nthis.message = '修改后的值'\nconsole.log(this.$el.textContent) // => '原始的值'\nawait this.$nextTick()\nconsole.log(this.$el.textContent) // => '修改后的值'\n```\n\n## 三、实现原理\n\n\n\n源码位置：`/src/core/util/next-tick.js`\n\n`callbacks`也就是异步操作队列\n\n`callbacks`新增回调函数后又执行了`timerFunc`函数，`pending`是用来标识同一个时间只能执行一次\n\n```js\nexport function nextTick(cb?: Function, ctx?: Object) {\n  let _resolve;\n\n  // cb 回调函数会经统一处理压入 callbacks 数组\n  callbacks.push(() => {\n    if (cb) {\n      // 给 cb 回调函数执行加上了 try-catch 错误处理\n      try {\n        cb.call(ctx);\n      } catch (e) {\n        handleError(e, ctx, 'nextTick');\n      }\n    } else if (_resolve) {\n      _resolve(ctx);\n    }\n  });\n\n  // 执行异步延迟函数 timerFunc\n  if (!pending) {\n    pending = true;\n    timerFunc();\n  }\n\n  // 当 nextTick 没有传入函数参数的时候，返回一个 Promise 化的调用\n  if (!cb && typeof Promise !== 'undefined') {\n    return new Promise(resolve => {\n      _resolve = resolve;\n    });\n  }\n}\n```\n\n`timerFunc`函数定义，这里是根据当前环境支持什么方法则确定调用哪个，分别有：\n\n`Promise.then`、`MutationObserver`、`setImmediate`、`setTimeout`\n\n通过上面任意一种方法，进行降级操作\n\n```js\nexport let isUsingMicroTask = false\nif (typeof Promise !== 'undefined' && isNative(Promise)) {\n  //判断1：是否原生支持Promise\n  const p = Promise.resolve()\n  timerFunc = () => {\n    p.then(flushCallbacks)\n    if (isIOS) setTimeout(noop)\n  }\n  isUsingMicroTask = true\n} else if (!isIE && typeof MutationObserver !== 'undefined' && (\n  isNative(MutationObserver) ||\n  MutationObserver.toString() === '[object MutationObserverConstructor]'\n)) {\n  //判断2：是否原生支持MutationObserver\n  let counter = 1\n  const observer = new MutationObserver(flushCallbacks)\n  const textNode = document.createTextNode(String(counter))\n  observer.observe(textNode, {\n    characterData: true\n  })\n  timerFunc = () => {\n    counter = (counter + 1) % 2\n    textNode.data = String(counter)\n  }\n  isUsingMicroTask = true\n} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {\n  //判断3：是否原生支持setImmediate\n  timerFunc = () => {\n    setImmediate(flushCallbacks)\n  }\n} else {\n  //判断4：上面都不行，直接用setTimeout\n  timerFunc = () => {\n    setTimeout(flushCallbacks, 0)\n  }\n}\n```\n\n无论是微任务还是宏任务，都会放到`flushCallbacks`使用\n\n这里将`callbacks`里面的函数复制一份，同时`callbacks`置空\n\n依次执行`callbacks`里面的函数\n\n```js\nfunction flushCallbacks () {\n  pending = false\n  const copies = callbacks.slice(0)\n  callbacks.length = 0\n  for (let i = 0; i < copies.length; i++) {\n    copies[i]()\n  }\n}\n```\n\n**小结：**\n\n1. 把回调函数放入callbacks等待执行\n2. 将执行函数放到微任务或者宏任务中\n3. 事件循环到了微任务或者宏任务，执行函数依次执行callbacks中的回调\n\n## 参考文献\n\n- https://juejin.cn/post/6844904147804749832"
  },
  {
    "path": "docs/vue/observable.md",
    "content": "# 面试官：Vue.observable你有了解过吗？说说看 \n\n![](https://static.vue-js.com/193782e0-3e7b-11eb-ab90-d9ae814b240d.png)  \n\n## 一、Observable 是什么\n\n`Observable` 翻译过来我们可以理解成**可观察的**\n\n我们先来看一下其在`Vue`中的定义\n\n> `Vue.observable`，让一个对象变成响应式数据。`Vue` 内部会用它来处理 `data` 函数返回的对象\n\n返回的对象可以直接用于渲染函数和计算属性内，并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器\n\n```js\nVue.observable({ count : 1})\n```\n\n其作用等同于\n\n```js\nnew vue({ count : 1})\n```\n\n在 `Vue 2.x` 中，被传入的对象会直接被 `Vue.observable` 变更，它和被返回的对象是同一个对象\n\n在 `Vue 3.x` 中，则会返回一个可响应的代理，而对源对象直接进行变更仍然是不可响应的\n\n## 二、使用场景\n\n在非父子组件通信时，可以使用通常的`bus`或者使用`vuex`，但是实现的功能不是太复杂，而使用上面两个又有点繁琐。这时，`observable`就是一个很好的选择\n\n创建一个`js`文件\n\n```js\n// 引入vue\nimport Vue from 'vue\n// 创建state对象，使用observable让state对象可响应\nexport let state = Vue.observable({\n  name: '张三',\n  'age': 38\n})\n// 创建对应的方法\nexport let mutations = {\n  changeName(name) {\n    state.name = name\n  },\n  setAge(age) {\n    state.age = age\n  }\n}\n```\n\n在`.vue`文件中直接使用即可\n\n```js\n<template>\n  <div>\n    姓名：{{ name }}\n    年龄：{{ age }}\n    <button @click=\"changeName('李四')\">改变姓名</button>\n    <button @click=\"setAge(18)\">改变年龄</button>\n  </div>\n</template>\nimport { state, mutations } from '@/store\nexport default {\n  // 在计算属性中拿到值\n  computed: {\n    name() {\n      return state.name\n    },\n    age() {\n      return state.age\n    }\n  },\n  // 调用mutations里面的方法，更新数据\n  methods: {\n    changeName: mutations.changeName,\n    setAge: mutations.setAge\n  }\n}\n```\n\n## 三、原理分析\n\n源码位置：src\\core\\observer\\index.js\n\n```js\nexport function observe (value: any, asRootData: ?boolean): Observer | void {\n  if (!isObject(value) || value instanceof VNode) {\n    return\n  }\n  let ob: Observer | void\n  // 判断是否存在__ob__响应式属性\n  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {\n    ob = value.__ob__\n  } else if (\n    shouldObserve &&\n    !isServerRendering() &&\n    (Array.isArray(value) || isPlainObject(value)) &&\n    Object.isExtensible(value) &&\n    !value._isVue\n  ) {\n    // 实例化Observer响应式对象\n    ob = new Observer(value)\n  }\n  if (asRootData && ob) {\n    ob.vmCount++\n  }\n  return ob\n}\n```\n\n`Observer`类\n\n```js\nexport class Observer {\n    value: any;\n    dep: Dep;\n    vmCount: number; // number of vms that have this object as root $data\n\n    constructor (value: any) {\n        this.value = value\n        this.dep = new Dep()\n        this.vmCount = 0\n        def(value, '__ob__', this)\n        if (Array.isArray(value)) {\n            if (hasProto) {\n                protoAugment(value, arrayMethods)\n            } else {\n                copyAugment(value, arrayMethods, arrayKeys)\n            }\n            this.observeArray(value)\n        } else {\n            // 实例化对象是一个对象，进入walk方法\n            this.walk(value)\n        }\n}\n```\n\n`walk`函数\n\n```js\nwalk (obj: Object) {\n    const keys = Object.keys(obj)\n    // 遍历key，通过defineReactive创建响应式对象\n    for (let i = 0; i < keys.length; i++) {\n        defineReactive(obj, keys[i])\n    }\n}\n```\n\n`defineReactive`方法\n\n```js\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: ?Function,\n  shallow?: boolean\n) {\n  const dep = new Dep()\n\n  const property = Object.getOwnPropertyDescriptor(obj, key)\n  if (property && property.configurable === false) {\n    return\n  }\n\n  // cater for pre-defined getter/setters\n  const getter = property && property.get\n  const setter = property && property.set\n  if ((!getter || setter) && arguments.length === 2) {\n    val = obj[key]\n  }\n\n  let childOb = !shallow && observe(val)\n  // 接下来调用Object.defineProperty()给对象定义响应式属性\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    get: function reactiveGetter () {\n      const value = getter ? getter.call(obj) : val\n      if (Dep.target) {\n        dep.depend()\n        if (childOb) {\n          childOb.dep.depend()\n          if (Array.isArray(value)) {\n            dependArray(value)\n          }\n        }\n      }\n      return value\n    },\n    set: function reactiveSetter (newVal) {\n      const value = getter ? getter.call(obj) : val\n      /* eslint-disable no-self-compare */\n      if (newVal === value || (newVal !== newVal && value !== value)) {\n        return\n      }\n      /* eslint-enable no-self-compare */\n      if (process.env.NODE_ENV !== 'production' && customSetter) {\n        customSetter()\n      }\n      // #7981: for accessor properties without setter\n      if (getter && !setter) return\n      if (setter) {\n        setter.call(obj, newVal)\n      } else {\n        val = newVal\n      }\n      childOb = !shallow && observe(newVal)\n      // 对观察者watchers进行通知,state就成了全局响应式对象\n      dep.notify()\n    }\n  })\n}\n```\n\n\n\n## 参考文献\n\n- https://blog.csdn.net/qq_32682301/article/details/105419673\n- https://wbbyouzi.com/archives/343"
  },
  {
    "path": "docs/vue/permission.md",
    "content": "# 面试官：vue要做权限管理该怎么做？如果控制到按钮级别的权限怎么做？\n\n![](https://static.vue-js.com/397e1fa0-4dad-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n权限是对特定资源的访问许可，所谓权限控制，也就是确保用户只能访问到被分配的资源\n\n而前端权限归根结底是请求的发起权，请求的发起可能有下面两种形式触发\n\n- 页面加载触发\n- 页面上的按钮点击触发\n\n总的来说，所有的请求发起都触发自前端路由或视图\n\n所以我们可以从这两方面入手，对触发权限的源头进行控制，最终要实现的目标是：\n\n- 路由方面，用户登录后只能看到自己有权访问的导航菜单，也只能访问自己有权访问的路由地址，否则将跳转 `4xx` 提示页\n\n- 视图方面，用户只能看到自己有权浏览的内容和有权操作的控件\n\n- 最后再加上请求控制作为最后一道防线，路由可能配置失误，按钮可能忘了加权限，这种时候请求控制可以用来兜底，越权请求将在前端被拦截\n\n\n\n## 二、如何做\n\n前端权限控制可以分为四个方面：\n\n- 接口权限\n- 按钮权限\n- 菜单权限\n- 路由权限\n\n\n\n### 接口权限\n\n接口权限目前一般采用`jwt`的形式来验证，没有通过的话一般返回`401`，跳转到登录页面重新进行登录\n\n登录完拿到`token`，将`token`存起来，通过`axios`请求拦截器进行拦截，每次请求的时候头部携带`token`\n\n```js\naxios.interceptors.request.use(config => {\n    config.headers['token'] = cookie.get('token')\n    return config\n})\naxios.interceptors.response.use(res=>{},{response}=>{\n    if (response.data.code === 40099 || response.data.code === 40098) { //token过期或者错误\n        router.push('/login')\n    }\n})\n```\n\n\n\n### 路由权限控制\n\n**方案一**\n\n初始化即挂载全部路由，并且在路由上标记相应的权限信息，每次路由跳转前做校验\n\n```js\nconst routerMap = [\n  {\n    path: '/permission',\n    component: Layout,\n    redirect: '/permission/index',\n    alwaysShow: true, // will always show the root menu\n    meta: {\n      title: 'permission',\n      icon: 'lock',\n      roles: ['admin', 'editor'] // you can set roles in root nav\n    },\n    children: [{\n      path: 'page',\n      component: () => import('@/views/permission/page'),\n      name: 'pagePermission',\n      meta: {\n        title: 'pagePermission',\n        roles: ['admin'] // or you can only set roles in sub nav\n      }\n    }, {\n      path: 'directive',\n      component: () => import('@/views/permission/directive'),\n      name: 'directivePermission',\n      meta: {\n        title: 'directivePermission'\n        // if do not set roles, means: this page does not require permission\n      }\n    }]\n  }]\n\n```\n\n这种方式存在以下四种缺点：\n\n- 加载所有的路由，如果路由很多，而用户并不是所有的路由都有权限访问，对性能会有影响。\n\n- 全局路由守卫里，每次路由跳转都要做权限判断。\n\n- 菜单信息写死在前端，要改个显示文字或权限信息，需要重新编译\n\n- 菜单跟路由耦合在一起，定义路由的时候还有添加菜单显示标题，图标之类的信息，而且路由不一定作为菜单显示，还要多加字段进行标识\n\n\n\n**方案二**\n\n初始化的时候先挂载不需要权限控制的路由，比如登录页，404等错误页。如果用户通过URL进行强制访问，则会直接进入404，相当于从源头上做了控制\n\n登录后，获取用户的权限信息，然后筛选有权限访问的路由，在全局路由守卫里进行调用`addRoutes`添加路由\n\n```js\nimport router from './router'\nimport store from './store'\nimport { Message } from 'element-ui'\nimport NProgress from 'nprogress' // progress bar\nimport 'nprogress/nprogress.css'// progress bar style\nimport { getToken } from '@/utils/auth' // getToken from cookie\n\nNProgress.configure({ showSpinner: false })// NProgress Configuration\n\n// permission judge function\nfunction hasPermission(roles, permissionRoles) {\n  if (roles.indexOf('admin') >= 0) return true // admin permission passed directly\n  if (!permissionRoles) return true\n  return roles.some(role => permissionRoles.indexOf(role) >= 0)\n}\n\nconst whiteList = ['/login', '/authredirect']// no redirect whitelist\n\nrouter.beforeEach((to, from, next) => {\n  NProgress.start() // start progress bar\n  if (getToken()) { // determine if there has token\n    /* has token*/\n    if (to.path === '/login') {\n      next({ path: '/' })\n      NProgress.done() // if current page is dashboard will not trigger\tafterEach hook, so manually handle it\n    } else {\n      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息\n        store.dispatch('GetUserInfo').then(res => { // 拉取user_info\n          const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']\n          store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表\n            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表\n            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record\n          })\n        }).catch((err) => {\n          store.dispatch('FedLogOut').then(() => {\n            Message.error(err || 'Verification failed, please login again')\n            next({ path: '/' })\n          })\n        })\n      } else {\n        // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓\n        if (hasPermission(store.getters.roles, to.meta.roles)) {\n          next()//\n        } else {\n          next({ path: '/401', replace: true, query: { noGoBack: true }})\n        }\n        // 可删 ↑\n      }\n    }\n  } else {\n    /* has no token*/\n    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单，直接进入\n      next()\n    } else {\n      next('/login') // 否则全部重定向到登录页\n      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it\n    }\n  }\n})\n\nrouter.afterEach(() => {\n  NProgress.done() // finish progress bar\n})\n\n```\n\n按需挂载，路由就需要知道用户的路由权限，也就是在用户登录进来的时候就要知道当前用户拥有哪些路由权限\n\n这种方式也存在了以下的缺点：\n\n- 全局路由守卫里，每次路由跳转都要做判断\n- 菜单信息写死在前端，要改个显示文字或权限信息，需要重新编译\n- 菜单跟路由耦合在一起，定义路由的时候还有添加菜单显示标题，图标之类的信息，而且路由不一定作为菜单显示，还要多加字段进行标识\n\n\n\n### 菜单权限\n\n菜单权限可以理解成将页面与理由进行解耦\n\n#### 方案一\n\n菜单与路由分离，菜单由后端返回\n\n前端定义路由信息\n\n```js\n{\n    name: \"login\",\n    path: \"/login\",\n    component: () => import(\"@/pages/Login.vue\")\n}\n```\n\n`name`字段都不为空，需要根据此字段与后端返回菜单做关联，后端返回的菜单信息中必须要有`name`对应的字段，并且做唯一性校验\n\n全局路由守卫里做判断\n\n```js\nfunction hasPermission(router, accessMenu) {\n  if (whiteList.indexOf(router.path) !== -1) {\n    return true;\n  }\n  let menu = Util.getMenuByName(router.name, accessMenu);\n  if (menu.name) {\n    return true;\n  }\n  return false;\n\n}\n\nRouter.beforeEach(async (to, from, next) => {\n  if (getToken()) {\n    let userInfo = store.state.user.userInfo;\n    if (!userInfo.name) {\n      try {\n        await store.dispatch(\"GetUserInfo\")\n        await store.dispatch('updateAccessMenu')\n        if (to.path === '/login') {\n          next({ name: 'home_index' })\n        } else {\n          //Util.toDefaultPage([...routers], to.name, router, next);\n          next({ ...to, replace: true })//菜单权限更新完成,重新进一次当前路由\n        }\n      }  \n      catch (e) {\n        if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单，直接进入\n          next()\n        } else {\n          next('/login')\n        }\n      }\n    } else {\n      if (to.path === '/login') {\n        next({ name: 'home_index' })\n      } else {\n        if (hasPermission(to, store.getters.accessMenu)) {\n          Util.toDefaultPage(store.getters.accessMenu,to, routes, next);\n        } else {\n          next({ path: '/403',replace:true })\n        }\n      }\n    }\n  } else {\n    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单，直接进入\n      next()\n    } else {\n      next('/login')\n    }\n  }\n  let menu = Util.getMenuByName(to.name, store.getters.accessMenu);\n  Util.title(menu.title);\n});\n\nRouter.afterEach((to) => {\n  window.scrollTo(0, 0);\n});\n```\n\n每次路由跳转的时候都要判断权限，这里的判断也很简单，因为菜单的`name`与路由的`name`是一一对应的，而后端返回的菜单就已经是经过权限过滤的\n\n如果根据路由`name`找不到对应的菜单，就表示用户有没权限访问\n\n如果路由很多，可以在应用初始化的时候，只挂载不需要权限控制的路由。取得后端返回的菜单后，根据菜单与路由的对应关系，筛选出可访问的路由，通过`addRoutes`动态挂载\n\n这种方式的缺点：\n\n- 菜单需要与路由做一一对应，前端添加了新功能，需要通过菜单管理功能添加新的菜单，如果菜单配置的不对会导致应用不能正常使用\n- 全局路由守卫里，每次路由跳转都要做判断\n\n\n\n#### 方案二\n\n菜单和路由都由后端返回\n\n前端统一定义路由组件\n\n```js\nconst Home = () => import(\"../pages/Home.vue\");\nconst UserInfo = () => import(\"../pages/UserInfo.vue\");\nexport default {\n    home: Home,\n    userInfo: UserInfo\n};\n```\n\n后端路由组件返回以下格式\n\n```js\n[\n    {\n        name: \"home\",\n        path: \"/\",\n        component: \"home\"\n    },\n    {\n        name: \"home\",\n        path: \"/userinfo\",\n        component: \"userInfo\"\n    }\n]\n```\n\n在将后端返回路由通过`addRoutes`动态挂载之间，需要将数据处理一下，将`component`字段换为真正的组件\n\n如果有嵌套路由，后端功能设计的时候，要注意添加相应的字段，前端拿到数据也要做相应的处理\n\n这种方法也会存在缺点：\n\n- 全局路由守卫里，每次路由跳转都要做判断\n- 前后端的配合要求更高\n\n\n\n### 按钮权限\n\n#### 方案一\n\n按钮权限也可以用`v-if`判断\n\n但是如果页面过多，每个页面页面都要获取用户权限`role`和路由表里的`meta.btnPermissions`，然后再做判断\n\n这种方式就不展开举例了\n\n\n\n#### 方案二\n\n通过自定义指令进行按钮权限的判断\n\n首先配置路由\n\n```js\n{\n    path: '/permission',\n    component: Layout,\n    name: '权限测试',\n    meta: {\n        btnPermissions: ['admin', 'supper', 'normal']\n    },\n    //页面需要的权限\n    children: [{\n        path: 'supper',\n        component: _import('system/supper'),\n        name: '权限测试页',\n        meta: {\n            btnPermissions: ['admin', 'supper']\n        } //页面需要的权限\n    },\n    {\n        path: 'normal',\n        component: _import('system/normal'),\n        name: '权限测试页',\n        meta: {\n            btnPermissions: ['admin']\n        } //页面需要的权限\n    }]\n}\n```\n\n自定义权限鉴定指令\n\n```js\nimport Vue from 'vue'\n/**权限指令**/\nconst has = Vue.directive('has', {\n    bind: function (el, binding, vnode) {\n        // 获取页面按钮权限\n        let btnPermissionsArr = [];\n        if(binding.value){\n            // 如果指令传值，获取指令参数，根据指令参数和当前登录人按钮权限做比较。\n            btnPermissionsArr = Array.of(binding.value);\n        }else{\n            // 否则获取路由中的参数，根据路由的btnPermissionsArr和当前登录人按钮权限做比较。\n            btnPermissionsArr = vnode.context.$route.meta.btnPermissions;\n        }\n        if (!Vue.prototype.$_has(btnPermissionsArr)) {\n            el.parentNode.removeChild(el);\n        }\n    }\n});\n// 权限检查方法\nVue.prototype.$_has = function (value) {\n    let isExist = false;\n    // 获取用户按钮权限\n    let btnPermissionsStr = sessionStorage.getItem(\"btnPermissions\");\n    if (btnPermissionsStr == undefined || btnPermissionsStr == null) {\n        return false;\n    }\n    if (value.indexOf(btnPermissionsStr) > -1) {\n        isExist = true;\n    }\n    return isExist;\n};\nexport {has}\n```\n\n在使用的按钮中只需要引用`v-has`指令\n\n```js\n<el-button @click='editClick' type=\"primary\" v-has>编辑</el-button>\n```\n\n\n\n### 小结\n\n关于权限如何选择哪种合适的方案，可以根据自己项目的方案项目，如考虑路由与菜单是否分离\n\n权限需要前后端结合，前端尽可能的去控制，更多的需要后台判断\n\n\n\n## 参考文献\n\n- https://mp.weixin.qq.com/s/b-D2eH1mLwL_FkaZwjueSw\n- https://segmentfault.com/a/1190000020887109\n- https://juejin.cn/post/6844903648057622536#heading-6"
  },
  {
    "path": "docs/vue/show_if.md",
    "content": "# 面试官：v-show和v-if有什么区别？使用场景分别是什么？\n\n![](https://static.vue-js.com/d21c3c50-3acb-11eb-85f6-6fac77c0c9b3.png)\n\n\n## 一、v-show与v-if的共同点\n\n我们都知道在 `vue` 中 `v-show ` 与 `v-if` 的作用效果是相同的(不含v-else)，都能控制元素在页面是否显示\n\n在用法上也是相同的\n\n```js\n<Model v-show=\"isShow\" />\n<Model v-if=\"isShow\" />\n```\n\n- 当表达式为`true`的时候，都会占据页面的位置\n- 当表达式都为`false`时，都不会占据页面位置\n\n\n## 二、v-show与v-if的区别\n\n- 控制手段不同\n- 编译过程不同\n- 编译条件不同\n\n控制手段：`v-show`隐藏则是为该元素添加`css--display:none`，`dom`元素依旧还在。`v-if`显示隐藏是将`dom`元素整个添加或删除\n\n编译过程：`v-if`切换有一个局部编译/卸载的过程，切换过程中合适地销毁和重建内部的事件监听和子组件；`v-show`只是简单的基于css切换\n\n编译条件：`v-if`是真正的条件渲染，它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时，并不做操作，直到为真才渲染\n\n- `v-show` 由`false`变为`true`的时候不会触发组件的生命周期\n\n- `v-if`由`false`变为`true`的时候，触发组件的`beforeCreate`、`create`、`beforeMount`、`mounted`钩子，由`true`变为`false`的时候触发组件的`beforeDestory`、`destoryed`方法\n\n性能消耗：`v-if`有更高的切换消耗；`v-show`有更高的初始渲染消耗；\n\n## 三、v-show与v-if原理分析\n\n具体解析流程这里不展开讲，大致流程如下\n- 将模板`template`转为`ast`结构的`JS`对象\n- 用`ast`得到的`JS`对象拼装`render`和`staticRenderFns`函数\n- `render`和`staticRenderFns`函数被调用后生成虚拟`VNODE`节点，该节点包含创建`DOM`节点所需信息\n- `vm.patch`函数通过虚拟`DOM`算法利用`VNODE`节点创建真实`DOM`节点\n\n### v-show原理\n\n不管初始条件是什么，元素总是会被渲染\n\n我们看一下在`vue`中是如何实现的\n\n代码很好理解，有`transition`就执行`transition`，没有就直接设置`display`属性\n\n```js\n// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts\nexport const vShow: ObjectDirective<VShowElement> = {\n  beforeMount(el, { value }, { transition }) {\n    el._vod = el.style.display === 'none' ? '' : el.style.display\n    if (transition && value) {\n      transition.beforeEnter(el)\n    } else {\n      setDisplay(el, value)\n    }\n  },\n  mounted(el, { value }, { transition }) {\n    if (transition && value) {\n      transition.enter(el)\n    }\n  },\n  updated(el, { value, oldValue }, { transition }) {\n    // ...\n  },\n  beforeUnmount(el, { value }) {\n    setDisplay(el, value)\n  }\n}\n```\n\n### v-if原理\n\n`v-if`在实现上比`v-show`要复杂的多，因为还有`else` `else-if` 等条件需要处理，这里我们也只摘抄源码中处理 `v-if` 的一小部分\n\n返回一个`node`节点，`render`函数通过表达式的值来决定是否生成`DOM`\n\n```js\n// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      // ...\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            key,\n            context\n          ) as IfConditionalExpression\n        } else {\n          // attach this branch's codegen node to the v-if root.\n          const parentCondition = getParentCondition(ifNode.codegenNode!)\n          parentCondition.alternate = createCodegenNodeForBranch(\n            branch,\n            key + ifNode.branches.length - 1,\n            context\n          )\n        }\n      }\n    })\n  }\n)\n```\n\n## 四、v-show与v-if的使用场景\n\n`v-if` 与 `v-show` 都能控制`dom`元素在页面的显示\n\n`v-if` 相比 `v-show` 开销更大的（直接操作`dom`节点增加与删除） \n\n如果需要非常频繁地切换，则使用 v-show 较好\n\n如果在运行时条件很少改变，则使用 v-if 较好\n\n\n## 参考文献\n\n- https://www.jianshu.com/p/7af8554d8f08\n- https://juejin.cn/post/6897948855904501768\n- https://vue3js/docs/zh"
  },
  {
    "path": "docs/vue/slot.md",
    "content": "# 面试官：说说你对slot的理解？slot使用场景有哪些？\n\n ![](https://static.vue-js.com/141ca660-3dbc-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、slot是什么\n\n在HTML中 `slot` 元素 ，作为 `Web Components` 技术套件的一部分，是Web组件内的一个占位符\n\n该占位符可以在后期使用自己的标记语言填充\n\n举个栗子\n```html\n<template id=\"element-details-template\">\n  <slot name=\"element-name\">Slot template</slot>\n</template>\n<element-details>\n  <span slot=\"element-name\">1</span>\n</element-details>\n<element-details>\n  <span slot=\"element-name\">2</span>\n</element-details>\n```\n`template`不会展示到页面中，需要用先获取它的引用，然后添加到`DOM`中，\n\n```js\ncustomElements.define('element-details',\n  class extends HTMLElement {\n    constructor() {\n      super();\n      const template = document\n        .getElementById('element-details-template')\n        .content;\n      const shadowRoot = this.attachShadow({mode: 'open'})\n        .appendChild(template.cloneNode(true));\n  }\n})\n```\n\n在`Vue`中的概念也是如此\n\n`Slot` 艺名插槽，花名“占坑”，我们可以理解为`solt`在组件模板中占好了位置，当使用该组件标签时候，组件标签里面的内容就会自动填坑（替换组件模板中`slot`位置），作为承载分发内容的出口\n\n可以将其类比为插卡式的FC游戏机，游戏机暴露卡槽（插槽）让用户插入不同的游戏磁条（自定义内容）\n\n放张图感受一下\n![](https://static.vue-js.com/63c0dff0-3dbd-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 二、使用场景\n\n通过插槽可以让用户可以拓展组件，去更好地复用组件和对其做定制化处理\n\n如果父组件在使用到一个复用组件的时候，获取这个组件在不同的地方有少量的更改，如果去重写组件是一件不明智的事情\n\n通过`slot`插槽向组件内部指定位置传递内容，完成这个复用组件在不同场景的应用\n\n比如布局组件、表格列、下拉选、弹框显示内容等\n\n## 三、分类\n\n`slot`可以分来以下三种：\n\n- 默认插槽\n- 具名插槽\n- 作用域插槽\n\n\n\n### 默认插槽\n\n子组件用`<slot>`标签来确定渲染的位置，标签里面可以放`DOM`结构，当父组件使用的时候没有往插槽传入内容，标签内`DOM`结构就会显示在页面\n\n父组件在使用的时候，直接在子组件的标签内写入内容即可\n\n子组件`Child.vue`\n\n```html\n<template>\n    <slot>\n      <p>插槽后备的内容</p>\n    </slot>\n</template>\n```\n\n父组件\n\n```html\n<Child>\n  <div>默认插槽</div>  \n</Child>\n```\n\n\n\n### 具名插槽\n\n子组件用`name`属性来表示插槽的名字，不传为默认插槽\n\n父组件中在使用时在默认插槽的基础上加上`slot`属性，值为子组件插槽`name`属性值\n\n子组件`Child.vue`\n\n```html\n<template>\n    <slot>插槽后备的内容</slot>\n  <slot name=\"content\">插槽后备的内容</slot>\n</template>\n```\n\n父组件\n\n```html\n<child>\n    <template v-slot:default>具名插槽</template>\n    <!-- 具名插槽⽤插槽名做参数 -->\n    <template v-slot:content>内容...</template>\n</child>\n```\n\n\n\n### 作用域插槽\n\n子组件在作用域上绑定属性来将子组件的信息传给父组件使用，这些属性会被挂在父组件`v-slot`接受的对象上\n\n父组件中在使用时通过`v-slot:`（简写：#）获取子组件的信息，在内容中使用\n\n子组件`Child.vue`\n\n```html\n<template> \n  <slot name=\"footer\" testProps=\"子组件的值\">\n          <h3>没传footer插槽</h3>\n    </slot>\n</template>\n```\n\n父组件\n\n```html\n<child> \n    <!-- 把v-slot的值指定为作⽤域上下⽂对象 -->\n    <template v-slot:default=\"slotProps\">\n      来⾃⼦组件数据：{{slotProps.testProps}}\n    </template>\n    <template #default=\"slotProps\">\n      来⾃⼦组件数据：{{slotProps.testProps}}\n    </template>\n</child>\n```\n\n\n\n### 小结：\n\n- `v-slot`属性只能在`<template>`上使用，但在只有默认插槽时可以在组件标签上使用\n- 默认插槽名为`default`，可以省略default直接写`v-slot`\n- 缩写为`#`时不能不写参数，写成`#default`\n- 可以通过解构获取`v-slot={user}`，还可以重命名`v-slot=\"{user: newName}\"`和定义默认值`v-slot=\"{user = '默认值'}\"`\n\n\n\n## 四、原理分析\n\n`slot`本质上是返回`VNode`的函数，一般情况下，`Vue`中的组件要渲染到页面上需要经过`template -> render function -> VNode -> DOM` 过程，这里看看`slot`如何实现：\n\n编写一个`buttonCounter`组件，使用匿名插槽\n\n```js\nVue.component('button-counter', {\n  template: '<div> <slot>我是默认内容</slot></div>'\n})\n```\n\n使用该组件\n\n```js\nnew Vue({\n    el: '#app',\n    template: '<button-counter><span>我是slot传入内容</span></button-counter>',\n    components:{buttonCounter}\n})\n```\n\n获取`buttonCounter`组件渲染函数\n\n```js\n(function anonymous(\n) {\nwith(this){return _c('div',[_t(\"default\",[_v(\"我是默认内容\")])],2)}\n})\n```\n\n`_v`表示穿件普通文本节点，`_t`表示渲染插槽的函数\n\n渲染插槽函数`renderSlot`（做了简化）\n\n```js\nfunction renderSlot (\n  name,\n  fallback,\n  props,\n  bindObject\n) {\n  // 得到渲染插槽内容的函数    \n  var scopedSlotFn = this.$scopedSlots[name];\n  var nodes;\n  // 如果存在插槽渲染函数，则执行插槽渲染函数，生成nodes节点返回\n  // 否则使用默认值\n  nodes = scopedSlotFn(props) || fallback;\n  return nodes;\n}\n```\n\n`name`属性表示定义插槽的名字，默认值为`default`，`fallback`表示子组件中的`slot`节点的默认值\n\n关于`this.$scopredSlots`是什么，我们可以先看看`vm.slot`\n\n```js\nfunction initRender (vm) {\n  ...\n  vm.$slots = resolveSlots(options._renderChildren, renderContext);\n  ...\n}\n```\n\n`resolveSlots`函数会对`children`节点做归类和过滤处理，返回`slots`\n\n```js\nfunction resolveSlots (\n    children,\n    context\n  ) {\n    if (!children || !children.length) {\n      return {}\n    }\n    var slots = {};\n    for (var i = 0, l = children.length; i < l; i++) {\n      var child = children[i];\n      var data = child.data;\n      // remove slot attribute if the node is resolved as a Vue slot node\n      if (data && data.attrs && data.attrs.slot) {\n        delete data.attrs.slot;\n      }\n      // named slots should only be respected if the vnode was rendered in the\n      // same context.\n      if ((child.context === context || child.fnContext === context) &&\n        data && data.slot != null\n      ) {\n        // 如果slot存在(slot=\"header\") 则拿对应的值作为key\n        var name = data.slot;\n        var slot = (slots[name] || (slots[name] = []));\n        // 如果是tempalte元素 则把template的children添加进数组中，这也就是为什么你写的template标签并不会渲染成另一个标签到页面\n        if (child.tag === 'template') {\n          slot.push.apply(slot, child.children || []);\n        } else {\n          slot.push(child);\n        }\n      } else {\n        // 如果没有就默认是default\n        (slots.default || (slots.default = [])).push(child);\n      }\n    }\n    // ignore slots that contains only whitespace\n    for (var name$1 in slots) {\n      if (slots[name$1].every(isWhitespace)) {\n        delete slots[name$1];\n      }\n    }\n    return slots\n}\n```\n\n`_render`渲染函数通过`normalizeScopedSlots`得到`vm.$scopedSlots`\n\n```js\nvm.$scopedSlots = normalizeScopedSlots(\n  _parentVnode.data.scopedSlots,\n  vm.$slots,\n  vm.$scopedSlots\n);\n```\n\n作用域插槽中父组件能够得到子组件的值是因为在`renderSlot`的时候执行会传入`props`，也就是上述`_t`第三个参数，父组件则能够得到子组件传递过来的值\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844903817746628615#heading-4\n- https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots\n- https://vue3js.cn/docs/zh\n- https://segmentfault.com/a/1190000019492734?utm_source=tag-newest\n"
  },
  {
    "path": "docs/vue/spa.md",
    "content": "# 面试官：你对SPA单页面的理解，它的优缺点分别是什么？如何实现SPA应用呢\n\n![](https://static.vue-js.com/cf6aa320-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、什么是SPA\n\nSPA（single-page application），翻译过来就是单页应用`SPA`是一种网络应用程序或网站的模型，它通过动态重写当前页面来与用户交互，这种方法避免了页面之间切换打断用户体验在单页应用中，所有必要的代码（`HTML`、`JavaScript`和`CSS`）都通过单个页面的加载而检索，或者根据需要（通常是为响应用户操作）动态装载适当的资源并添加到页面页面在任何时间点都不会重新加载，也不会将控制转移到其他页面举个例子来讲就是一个杯子，早上装的牛奶，中午装的是开水，晚上装的是茶，我们发现，变的始终是杯子里的内容，而杯子始终是那个杯子结构如下图\n\n ![](https://static.vue-js.com/df14a5a0-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\n我们熟知的JS框架如`react`,`vue`,`angular`,`ember`都属于`SPA`\n\n## 二、SPA和MPA的区别\n\n上面大家已经对单页面有所了解了，下面来讲讲多页应用MPA（MultiPage-page application），翻译过来就是多页应用在`MPA`中，每个页面都是一个主页面，都是独立的当我们在访问另一个页面的时候，都需要重新加载`html`、`css`、`js`文件，公共文件则根据需求按需加载如下图\n\n ![](https://static.vue-js.com/eeb13aa0-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\n#### 单页应用与多页应用的区别\n\n|   | 单页面应用（SPA） | 多页面应用（MPA） |\n| :-- | :-- | :-- |\n| 组成 | 一个主页面和多个页面片段 | 多个主页面 |\n| 刷新方式 | 局部刷新 | 整页刷新 |\n| url模式 | 哈希模式 | 历史模式 |\n| SEO搜索引擎优化 | 难实现，可使用SSR方式改善 | 容易实现 |\n| 数据传递 | 容易 | 通过url、cookie、localStorage等传递 |\n| 页面切换 | 速度快，用户体验良好 | 切换加载资源，速度慢，用户体验差 |\n| 维护成本 | 相对容易 | 相对复杂 |\n\n#### 单页应用优缺点\n\n优点：\n\n- 具有桌面应用的即时性、网站的可移植性和可访问性\n- 用户体验好、快，内容的改变不需要重新加载整个页面\n- 良好的前后端分离，分工更明确\n\n缺点：\n\n- 不利于搜索引擎的抓取\n- 首次渲染速度相对较慢\n- \n\n## 三、实现一个SPA\n\n#### 原理\n\n1.  监听地址栏中`hash`变化驱动界面变化\n2.  用`pushsate`记录浏览器的历史，驱动界面发送变化\n\n ![](https://static.vue-js.com/fc95bf60-3ac6-11eb-ab90-d9ae814b240d.png)\n\n#### 实现\n\n##### `hash` 模式\n\n核心通过监听`url`中的`hash`来进行路由跳转\n\n```js\n// 定义 Router  \nclass Router {  \n    constructor () {  \n        this.routes = {}; // 存放路由path及callback  \n        this.currentUrl = '';  \n          \n        // 监听路由change调用相对应的路由回调  \n        window.addEventListener('load', this.refresh, false);  \n        window.addEventListener('hashchange', this.refresh, false);  \n    }  \n      \n    route(path, callback){  \n        this.routes[path] = callback;  \n    }  \n      \n    push(path) {  \n        this.routes[path] && this.routes[path]()  \n    }  \n}  \n  \n// 使用 router  \nwindow.miniRouter = new Router();  \nminiRouter.route('/', () => console.log('page1'))  \nminiRouter.route('/page2', () => console.log('page2'))  \n  \nminiRouter.push('/') // page1  \nminiRouter.push('/page2') // page2  \n```\n\n##### history模式\n\n`history` 模式核心借用 `HTML5 history api`，`api` 提供了丰富的 `router` 相关属性先了解一个几个相关的api\n\n -    `history.pushState` 浏览器历史纪录添加记录\n -    `history.replaceState`修改浏览器历史纪录中当前纪录\n -    `history.popState` 当 `history` 发生变化时触发\n\n```js\n// 定义 Router  \nclass Router {  \n    constructor () {  \n        this.routes = {};  \n        this.listerPopState()  \n    }  \n      \n    init(path) {  \n        history.replaceState({path: path}, null, path);  \n        this.routes[path] && this.routes[path]();  \n    }  \n      \n    route(path, callback){  \n        this.routes[path] = callback;  \n    }  \n      \n    push(path) {  \n        history.pushState({path: path}, null, path);  \n        this.routes[path] && this.routes[path]();  \n    }  \n      \n    listerPopState () {  \n        window.addEventListener('popstate' , e => {  \n            const path = e.state && e.state.path;  \n            this.routers[path] && this.routers[path]()  \n        })  \n    }  \n}  \n  \n// 使用 Router  \n  \nwindow.miniRouter = new Router();  \nminiRouter.route('/', ()=> console.log('page1'))  \nminiRouter.route('/page2', ()=> console.log('page2'))  \n  \n// 跳转  \nminiRouter.push('/page2')  // page2  \n```\n\n### 四、题外话：如何给SPA做SEO\n\n下面给出基于`Vue`的`SPA`如何实现`SEO`的三种方式\n\n1.  **SSR服务端渲染**\n\n将组件或页面通过服务器生成html，再返回给浏览器，如`nuxt.js`\n\n2.  **静态化**\n\n目前主流的静态化主要有两种：（1）一种是通过程序将动态页面抓取并保存为静态页面，这样的页面的实际存在于服务器的硬盘中（2）另外一种是通过WEB服务器的 `URL Rewrite`的方式，它的原理是通过web服务器内部模块按一定规则将外部的URL请求转化为内部的文件地址，一句话来说就是把外部请求的静态地址转化为实际的动态页面地址，而静态页面实际是不存在的。这两种方法都达到了实现URL静态化的效果\n\n3.  **使用`Phantomjs`针对爬虫处理**\n\n原理是通过`Nginx`配置，判断访问来源是否为爬虫，如果是则搜索引擎的爬虫请求会转发到一个`node server`，再通过`PhantomJS`来解析完整的`HTML`，返回给爬虫。下面是大致流程图\n\n ![](https://static.vue-js.com/25be6630-3ac7-11eb-ab90-d9ae814b240d.png)\n\n### 参考文献\n\n- https://segmentfault.com/a/1190000019623624\n- https://juejin.cn/post/6844903512107663368\n- https://www.cnblogs.com/constantince/p/5586851.html\n\n ![](https://static.vue-js.com/821b87b0-3ac6-11eb-ab90-d9ae814b240d.png)\n"
  },
  {
    "path": "docs/vue/ssr.md",
    "content": "# 面试官：SSR解决了什么问题？有做过SSR吗？你是怎么做的？\n\n![](https://static.vue-js.com/84bd83f0-4986-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n\n`Server-Side Rendering` 我们称其为`SSR`，意为服务端渲染\n\n指由服务侧完成页面的 `HTML` 结构拼接的页面处理技术，发送到浏览器，然后为其绑定状态与事件，成为完全可交互页面的过程\n\n先来看看`Web`3个阶段的发展史：\n\n- 传统服务端渲染SSR\n- 单页面应用SPA\n- 服务端渲染SSR\n\n### **传统web开发**\n\n网页内容在服务端渲染完成，⼀次性传输到浏览器\n\n![img](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e4d666b24e784fd09e565458c7753b54~tplv-k3u1fbpfcp-watermark.image)\n\n打开页面查看源码，浏览器拿到的是全部的`dom`结构\n\n### **单页应用SPA**\n\n单页应用优秀的用户体验，使其逐渐成为主流，页面内容由`JS`渲染出来，这种方式称为客户端渲染\n\n![img](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e8e524a8e7d44cba73e0c3416690087~tplv-k3u1fbpfcp-watermark.image)\n\n打开页面查看源码，浏览器拿到的仅有宿主元素`#app`，并没有内容\n\n### 服务端渲染SSR\n\n`SSR`解决方案，后端渲染出完整的首屏的`dom`结构返回，前端拿到的内容包括首屏及完整`spa`结构，应用激活后依然按照`spa`方式运行\n\n![img](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f1604e7cfad7431f99920e8ab833bc37~tplv-k3u1fbpfcp-watermark.image)\n\n\n\n看完前端发展，我们再看看`Vue`官方对`SSR`的解释：\n\n> Vue.js 是构建客户端应用程序的框架。默认情况下，可以在浏览器中输出 Vue 组件，进行生成 DOM 和操作 DOM。然而，也可以将同一个组件渲染为服务器端的 HTML 字符串，将它们直接发送到浏览器，最后将这些静态标记\"激活\"为客户端上完全可交互的应用程序\n>\n> 服务器渲染的 Vue.js 应用程序也可以被认为是\"同构\"或\"通用\"，因为应用程序的大部分代码都可以在服务器和客户端上运行\n\n我们从上门解释得到以下结论：\n\n- `Vue SSR`是一个在`SPA`上进行改良的服务端渲染\n- 通过`Vue SSR`渲染的页面，需要在客户端激活才能实现交互\n- `Vue SSR`将包含两部分：服务端渲染的首屏，包含交互的`SPA`\n\n\n\n## 二、解决了什么\n\nSSR主要解决了以下两种问题：\n\n- seo：搜索引擎优先爬取页面`HTML`结构，使用`ssr`时，服务端已经生成了和业务想关联的`HTML`，有利于`seo`\n- 首屏呈现渲染：用户无需等待页面所有`js`加载完成就可以看到页面视图（压力来到了服务器，所以需要权衡哪些用服务端渲染，哪些交给客户端）\n\n但是使用`SSR`同样存在以下的缺点：\n\n- 复杂度：整个项目的复杂度\n\n- 库的支持性，代码兼容\n\n- 性能问题\n\n  - 每个请求都是`n`个实例的创建，不然会污染，消耗会变得很大\n\n  - 缓存 `node serve `、 `nginx`判断当前用户有没有过期，如果没过期的话就缓存，用刚刚的结果。\n  - 降级：监控`cpu`、内存占用过多，就`spa`，返回单个的壳\n\n- 服务器负载变大，相对于前后端分离服务器只需要提供静态资源来说，服务器负载更大，所以要慎重使用\n\n所以在我们选择是否使用`SSR`前，我们需要慎重问问自己这些问题：\n\n1. 需要`SEO`的页面是否只是少数几个，这些是否可以使用预渲染（Prerender SPA Plugin）实现\n2. 首屏的请求响应逻辑是否复杂，数据返回是否大量且缓慢\n\n## 三、如何实现\n\n对于同构开发，我们依然使用`webpack`打包，我们要解决两个问题：服务端首屏渲染和客户端激活\n\n这里需要生成一个服务器`bundle`文件用于服务端首屏渲染和一个客户端`bundle`文件用于客户端激活\n\n ![](https://static.vue-js.com/9dcd12c0-4986-11eb-85f6-6fac77c0c9b3.png)\n\n代码结构 除了两个不同入口之外，其他结构和之前`vue`应用完全相同\n\n```js\nsrc\n├── router\n├────── index.js # 路由声明\n├── store\n├────── index.js # 全局状态\n├── main.js # ⽤于创建vue实例\n├── entry-client.js # 客户端⼊⼝，⽤于静态内容“激活”\n└── entry-server.js # 服务端⼊⼝，⽤于⾸屏内容渲染\n```\n\n路由配置\n\n```js\nimport Vue from \"vue\";\nimport Router from \"vue-router\";\n\nVue.use(Router);\n//导出⼯⼚函数\n\nexport function createRouter() {\n    return new Router({\n        mode: 'history',\n        routes: [\n            // 客户端没有编译器，这⾥要写成渲染函数\n            { path: \"/\", component: { render: h => h('div', 'index page') } },\n            { path: \"/detail\", component: { render: h => h('div', 'detail page') } }\n        ]\n    });\n}\n```\n\n主文件main.js\n\n跟之前不同，主文件是负责创建`vue`实例的工厂，每次请求均会有独立的`vue`实例创建\n\n```js\nimport Vue from \"vue\";\nimport App from \"./App.vue\";\nimport { createRouter } from \"./router\";\n// 导出Vue实例⼯⼚函数，为每次请求创建独⽴实例\n// 上下⽂⽤于给vue实例传递参数\nexport function createApp(context) {\n    const router = createRouter();\n    const app = new Vue({\n        router,\n        context,\n        render: h => h(App)\n    });\n    return { app, router };\n}\n```\n\n编写服务端入口`src/entry-server.js`\n\n它的任务是创建`Vue`实例并根据传入`url`指定首屏\n\n```js\nimport { createApp } from \"./main\";\n// 返回⼀个函数，接收请求上下⽂，返回创建的vue实例\nexport default context => {\n    // 这⾥返回⼀个Promise，确保路由或组件准备就绪\n    return new Promise((resolve, reject) => {\n        const { app, router } = createApp(context);\n        // 跳转到⾸屏的地址\n        router.push(context.url);\n        // 路由就绪，返回结果\n        router.onReady(() => {\n            resolve(app);\n        }, reject);\n    });\n};\n```\n\n编写客户端入口`entry-client.js`\n\n客户端入口只需创建`vue`实例并执行挂载，这⼀步称为激活\n\n```js\nimport { createApp } from \"./main\";\n// 创建vue、router实例\nconst { app, router } = createApp();\n// 路由就绪，执⾏挂载\nrouter.onReady(() => {\n    app.$mount(\"#app\");\n});\n```\n\n对`webpack`进行配置\n\n安装依赖\n\n```js\nnpm install webpack-node-externals lodash.merge -D\n```\n\n对`vue.config.js`进行配置\n\n```js\n// 两个插件分别负责打包客户端和服务端\nconst VueSSRServerPlugin = require(\"vue-server-renderer/server-plugin\");\nconst VueSSRClientPlugin = require(\"vue-server-renderer/client-plugin\");\nconst nodeExternals = require(\"webpack-node-externals\");\nconst merge = require(\"lodash.merge\");\n// 根据传⼊环境变量决定⼊⼝⽂件和相应配置项\nconst TARGET_NODE = process.env.WEBPACK_TARGET === \"node\";\nconst target = TARGET_NODE ? \"server\" : \"client\";\nmodule.exports = {\n    css: {\n        extract: false\n    },\n    outputDir: './dist/'+target,\n    configureWebpack: () => ({\n        // 将 entry 指向应⽤程序的 server / client ⽂件\n        entry: `./src/entry-${target}.js`,\n        // 对 bundle renderer 提供 source map ⽀持\n        devtool: 'source-map',\n        // target设置为node使webpack以Node适⽤的⽅式处理动态导⼊，\n        // 并且还会在编译Vue组件时告知`vue-loader`输出⾯向服务器代码。\n        target: TARGET_NODE ? \"node\" : \"web\",\n        // 是否模拟node全局变量\n        node: TARGET_NODE ? undefined : false,\n        output: {\n            // 此处使⽤Node⻛格导出模块\n            libraryTarget: TARGET_NODE ? \"commonjs2\" : undefined\n        },\n        // https://webpack.js.org/configuration/externals/#function\n        // https://github.com/liady/webpack-node-externals\n        // 外置化应⽤程序依赖模块。可以使服务器构建速度更快，并⽣成较⼩的打包⽂件。\n        externals: TARGET_NODE\n        ? nodeExternals({\n            // 不要外置化webpack需要处理的依赖模块。\n            // 可以在这⾥添加更多的⽂件类型。例如，未处理 *.vue 原始⽂件，\n            // 还应该将修改`global`（例如polyfill）的依赖模块列⼊⽩名单\n            whitelist: [/\\.css$/]\n        })\n        : undefined,\n        optimization: {\n            splitChunks: undefined\n        },\n        // 这是将服务器的整个输出构建为单个 JSON ⽂件的插件。\n        // 服务端默认⽂件名为 `vue-ssr-server-bundle.json`\n        // 客户端默认⽂件名为 `vue-ssr-client-manifest.json`。\n        plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new\n                  VueSSRClientPlugin()]\n    }),\n    chainWebpack: config => {\n        // cli4项⽬添加\n        if (TARGET_NODE) {\n            config.optimization.delete('splitChunks')\n        }\n\n        config.module\n            .rule(\"vue\")\n            .use(\"vue-loader\")\n            .tap(options => {\n            merge(options, {\n                optimizeSSR: false\n            });\n        });\n    }\n};\n```\n\n对脚本进行配置，安装依赖\n\n```js\nnpm i cross-env -D\n```\n\n定义创建脚本`package.json`\n\n```js\n\"scripts\": {\n \"build:client\": \"vue-cli-service build\",\n \"build:server\": \"cross-env WEBPACK_TARGET=node vue-cli-service build\",\n \"build\": \"npm run build:server && npm run build:client\"\n}\n```\n\n> 执行打包：npm run build\n\n最后修改宿主文件`/public/index.html`\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n        <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n        <title>Document</title>\n    </head>\n    <body>\n        <!--vue-ssr-outlet-->\n    </body>\n</html>\n```\n\n> <!--vue-ssr-outlet-->   是服务端渲染入口位置，注意不能为了好看而在前后加空格\n\n\n\n安装`vuex`\n\n```js\nnpm install -S vuex\n```\n\n创建`vuex`工厂函数\n\n```js\nimport Vue from 'vue'\nimport Vuex from 'vuex'\nVue.use(Vuex)\nexport function createStore () {\n    return new Vuex.Store({\n        state: {\n            count:108\n        },\n        mutations: {\n            add(state){\n                state.count += 1;\n            }\n        }\n    })\n}\n```\n\n在`main.js`文件中挂载`store`\n\n```js\nimport { createStore } from './store'\nexport function createApp (context) {\n    // 创建实例\n    const store = createStore()\n    const app = new Vue({\n        store, // 挂载\n        render: h => h(App)\n    })\n    return { app, router, store }\n}\n```\n\n服务器端渲染的是应用程序的\"快照\"，如果应用依赖于⼀些异步数据，那么在开始渲染之前，需要先预取和解析好这些数据\n\n在`store`进行一步数据获取\n\n```js\nexport function createStore() {\n    return new Vuex.Store({\n        mutations: {\n            // 加⼀个初始化\n            init(state, count) {\n                state.count = count;\n            },\n        },\n        actions: {\n            // 加⼀个异步请求count的action\n            getCount({ commit }) {\n                return new Promise(resolve => {\n                    setTimeout(() => {\n                        commit(\"init\", Math.random() * 100);\n                        resolve();\n                    }, 1000);\n                });\n            },\n        },\n    });\n}\n```\n\n组件中的数据预取逻辑\n\n```js\nexport default {\n    asyncData({ store, route }) { // 约定预取逻辑编写在预取钩⼦asyncData中\n        // 触发 action 后，返回 Promise 以便确定请求结果\n        return store.dispatch(\"getCount\");\n    }\n};\n```\n\n服务端数据预取，`entry-server.js`\n\n```js\nimport { createApp } from \"./app\";\nexport default context => {\n    return new Promise((resolve, reject) => {\n        // 拿出store和router实例\n        const { app, router, store } = createApp(context);\n        router.push(context.url);\n        router.onReady(() => {\n            // 获取匹配的路由组件数组\n            const matchedComponents = router.getMatchedComponents();\n\n            // 若⽆匹配则抛出异常\n            if (!matchedComponents.length) {\n                return reject({ code: 404 });\n            }\n\n            // 对所有匹配的路由组件调⽤可能存在的`asyncData()`\n            Promise.all(\n                matchedComponents.map(Component => {\n                    if (Component.asyncData) {\n                        return Component.asyncData({\n                            store,\n                            route: router.currentRoute,\n                        });\n                    }\n                }),\n            )\n                .then(() => {\n                // 所有预取钩⼦ resolve 后，\n                // store 已经填充⼊渲染应⽤所需状态\n                // 将状态附加到上下⽂，且 `template` 选项⽤于 renderer 时，\n                // 状态将⾃动序列化为 `window.__INITIAL_STATE__`，并注⼊ HTML\n                context.state = store.state;\n\n                resolve(app);\n            })\n                .catch(reject);\n        }, reject);\n    });\n};\n```\n\n客户端在挂载到应用程序之前，`store` 就应该获取到状态，`entry-client.js`\n\n```js\n// 导出store\nconst { app, router, store } = createApp();\n// 当使⽤ template 时，context.state 将作为 window.__INITIAL_STATE__ 状态⾃动嵌⼊到最终的 HTML \n// 在客户端挂载到应⽤程序之前，store 就应该获取到状态：\nif (window.__INITIAL_STATE__) {\n    store.replaceState(window.__INITIAL_STATE__);\n}\n```\n\n客户端数据预取处理，`main.js`\n\n```js\nVue.mixin({\n    beforeMount() {\n        const { asyncData } = this.$options;\n        if (asyncData) {\n            // 将获取数据操作分配给 promise\n            // 以便在组件中，我们可以在数据准备就绪后\n            // 通过运⾏ `this.dataPromise.then(...)` 来执⾏其他任务\n            this.dataPromise = asyncData({\n                store: this.$store,\n                route: this.$route,\n            });\n        }\n    },\n});\n```\n\n修改服务器启动文件\n\n```js\n// 获取⽂件路径\nconst resolve = dir => require('path').resolve(__dirname, dir)\n// 第 1 步：开放dist/client⽬录，关闭默认下载index⻚的选项，不然到不了后⾯路由\napp.use(express.static(resolve('../dist/client'), {index: false}))\n// 第 2 步：获得⼀个createBundleRenderer\nconst { createBundleRenderer } = require(\"vue-server-renderer\");\n// 第 3 步：服务端打包⽂件地址\nconst bundle = resolve(\"../dist/server/vue-ssr-server-bundle.json\");\n// 第 4 步：创建渲染器\nconst renderer = createBundleRenderer(bundle, {\n    runInNewContext: false, // https://ssr.vuejs.org/zh/api/#runinnewcontext\n    template: require('fs').readFileSync(resolve(\"../public/index.html\"), \"utf8\"), // 宿主⽂件\n    clientManifest: require(resolve(\"../dist/client/vue-ssr-clientmanifest.json\")) // 客户端清单\n});\napp.get('*', async (req,res)=>{\n    // 设置url和title两个重要参数\n    const context = {\n        title:'ssr test',\n        url:req.url\n    }\n    const html = await renderer.renderToString(context);\n    res.send(html)\n})\n```\n\n\n\n### 小结\n\n- 使用`ssr`不存在单例模式，每次用户请求都会创建一个新的`vue`实例\n- 实现`ssr`需要实现服务端首屏渲染和客户端激活\n- 服务端异步获取数据`asyncData`可以分为首屏异步获取和切换组件获取\n  - 首屏异步获取数据，在服务端预渲染的时候就应该已经完成\n  - 切换组件通过`mixin`混入，在`beforeMount`钩子完成数据获取\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6896007907050487816\n- https://vue3js.cn/docs/zh"
  },
  {
    "path": "docs/vue/structure.md",
    "content": "# 面试官：说下你的vue项目的目录结构，如果是大型项目你该怎么划分结构和划分组件呢？\n\n![](https://static.vue-js.com/b6cd6a60-4aba-11eb-ab90-d9ae814b240d.png)\n\n## 一、为什么要划分\n\n使用`vue`构建项目，项目结构清晰会提高开发效率，熟悉项目的各种配置同样会让开发效率更高\n\n在划分项目结构的时候，需要遵循一些基本的原则：\n\n- 文件夹和文件夹内部文件的语义一致性\n- 单一入口/出口\n- 就近原则，紧耦合的文件应该放到一起，且应以相对路径引用\n- 公共的文件应该以绝对路径的方式从根目录引用\n- `/src` 外的文件不应该被引入\n\n\n\n### 文件夹和文件夹内部文件的语义一致性\n\n我们的目录结构都会有一个文件夹是按照路由模块来划分的，如`pages`文件夹，这个文件夹里面应该包含我们项目所有的路由模块，并且仅应该包含路由模块，而不应该有别的其他的非路由模块的文件夹\n\n这样做的好处在于一眼就从 `pages`文件夹看出这个项目的路由有哪些\n\n\n\n### 单一入口/出口\n\n举个例子，在`pages`文件夹里面存在一个`seller`文件夹，这时候`seller` 文件夹应该作为一个独立的模块由外部引入，并且 `seller/index.js` 应该作为外部引入 seller 模块的唯一入口\n\n```js\n// 错误用法\nimport sellerReducer from 'src/pages/seller/reducer'\n\n// 正确用法\nimport { reducer as sellerReducer } from 'src/pages/seller'\n```\n\n这样做的好处在于，无论你的模块文件夹内部有多乱，外部引用的时候，都是从一个入口文件引入，这样就很好的实现了隔离，如果后续有重构需求，你就会发现这种方式的优点\n\n\n\n\n\n### 就近原则，紧耦合的文件应该放到一起，且应以相对路径引用\n\n使用相对路径可以保证模块内部的独立性\n\n```js\n// 正确用法\nimport styles from './index.module.scss'\n// 错误用法\nimport styles from 'src/pages/seller/index.module.scss'\n```\n\n举个例子\n\n假设我们现在的 seller 目录是在 `src/pages/seller`，如果我们后续发生了路由变更，需要加一个层级，变成 `src/pages/user/seller`。\n\n如果我们采用第一种相对路径的方式，那就可以直接将整个文件夹拖过去就好，`seller` 文件夹内部不需要做任何变更。\n\n但是如果我们采用第二种绝对路径的方式，移动文件夹的同时，还需要对每个 `import` 的路径做修改\n\n\n\n### 公共的文件应该以绝对路径的方式从根目录引用\n\n公共指的是多个路由模块共用，如一些公共的组件，我们可以放在`src/components`下\n\n在使用到的页面中，采用绝对路径的形式引用\n\n```js\n// 错误用法\nimport Input from '../../components/input'\n// 正确用法\nimport Input from 'src/components/input'\n```\n\n同样的，如果我们需要对文件夹结构进行调整。将 `/src/components/input` 变成 `/src/components/new/input`，如果使用绝对路径，只需要全局搜索替换\n\n再加上绝对路径有全局的语义，相对路径有独立模块的语义\n\n\n\n### /src 外的文件不应该被引入\n\n`vue-cli`脚手架已经帮我们做了相关的约束了，正常我们的前端项目都会有个` src `文件夹，里面放着所有的项目需要的资源，`js`,` css`, `png`, `svg` 等等。`src` 外会放一些项目配置，依赖，环境等文件\n\n这样的好处是方便划分项目代码文件和配置文件\n\n\n\n## 二、目录结构\n\n单页面目录结构\n\n```js\nproject\n│  .browserslistrc\n│  .env.production\n│  .eslintrc.js\n│  .gitignore\n│  babel.config.js\n│  package-lock.json\n│  package.json\n│  README.md\n│  vue.config.js\n│  yarn-error.log\n│  yarn.lock\n│\n├─public\n│      favicon.ico\n│      index.html\n│\n|-- src\n    |-- components\n        |-- input\n            |-- index.js\n            |-- index.module.scss\n    |-- pages\n        |-- seller\n            |-- components\n                |-- input\n                    |-- index.js\n                    |-- index.module.scss\n            |-- reducer.js\n            |-- saga.js\n            |-- index.js\n            |-- index.module.scss\n        |-- buyer\n            |-- index.js\n        |-- index.js\n```\n\n多页面目录结构\n\n```js\nmy-vue-test:.\n│  .browserslistrc\n│  .env.production\n│  .eslintrc.js\n│  .gitignore\n│  babel.config.js\n│  package-lock.json\n│  package.json\n│  README.md\n│  vue.config.js\n│  yarn-error.log\n│  yarn.lock\n│\n├─public\n│      favicon.ico\n│      index.html\n│\n└─src\n    ├─apis //接口文件根据页面或实例模块化\n    │      index.js\n    │      login.js\n    │\n    ├─components //全局公共组件\n    │  └─header\n    │          index.less\n    │          index.vue\n    │\n    ├─config //配置（环境变量配置不同passid等）\n    │      env.js\n    │      index.js\n    │\n    ├─contant //常量\n    │      index.js\n    │\n    ├─images //图片\n    │      logo.png\n    │\n    ├─pages //多页面vue项目，不同的实例\n    │  ├─index //主实例\n    │  │  │  index.js\n    │  │  │  index.vue\n    │  │  │  main.js\n    │  │  │  router.js\n    │  │  │  store.js\n    │  │  │\n    │  │  ├─components //业务组件\n    │  │  └─pages //此实例中的各个路由\n    │  │      ├─amenu\n    │  │      │      index.vue\n    │  │      │\n    │  │      └─bmenu\n    │  │              index.vue\n    │  │\n    │  └─login //另一个实例\n    │          index.js\n    │          index.vue\n    │          main.js\n    │\n    ├─scripts //包含各种常用配置，工具函数\n    │  │  map.js\n    │  │\n    │  └─utils\n    │          helper.js\n    │\n    ├─store //vuex仓库\n    │  │  index.js\n    │  │\n    │  ├─index\n    │  │      actions.js\n    │  │      getters.js\n    │  │      index.js\n    │  │      mutation-types.js\n    │  │      mutations.js\n    │  │      state.js\n    │  │\n    │  └─user\n    │          actions.js\n    │          getters.js\n    │          index.js\n    │          mutation-types.js\n    │          mutations.js\n    │          state.js\n    │\n    └─styles //样式统一配置\n        │  components.less\n        │\n        ├─animation\n        │      index.less\n        │      slide.less\n        │\n        ├─base\n        │      index.less\n        │      style.less\n        │      var.less\n        │      widget.less\n        │\n        └─common\n                index.less\n                reset.less\n                style.less\n                transition.less\n```\n\n\n\n### 小结\n\n项目的目录结构很重要，因为目录结构能体现很多东西，怎么规划目录结构可能每个人有自己的理解，但是按照一定的规范去进行目录的设计，能让项目整个架构看起来更为简洁，更加易用\n\n\n## 参考文献\n\n- https://juejin.cn/post/6844904129186234381#heading-0\n\n- https://zhuanlan.zhihu.com/p/89693668"
  },
  {
    "path": "docs/vue/vnode.md",
    "content": "# 面试官：什么是虚拟DOM？如何实现一个虚拟DOM？说说你的思路\n\n ![](https://static.vue-js.com/770b9670-442c-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、什么是虚拟DOM\n\n虚拟 DOM （`Virtual DOM` ）这个概念相信大家都不陌生，从 `React` 到 `Vue` ，虚拟 `DOM` 为这两个框架都带来了跨平台的能力（`React-Native` 和 `Weex`）\n\n实际上它只是一层对真实`DOM`的抽象，以`JavaScript` 对象 (`VNode` 节点) 作为基础的树，用对象的属性来描述节点，最终可以通过一系列操作使这棵树映射到真实环境上\n\n在`Javascript`对象中，虚拟`DOM` 表现为一个 `Object `对象。并且最少包含标签名 (`tag`)、属性 (`attrs`) 和子元素对象 (`children`) 三个属性，不同框架对这三个属性的名命可能会有差别\n\n创建虚拟`DOM`就是为了更好将虚拟的节点渲染到页面视图中，所以虚拟`DOM`对象的节点与真实`DOM`的属性一一照应\n\n在`vue`中同样使用到了虚拟`DOM`技术\n\n定义真实`DOM`\n\n```html\n<div id=\"app\">\n    <p class=\"p\">节点内容</p>\n    <h3>{{ foo }}</h3>\n</div>\n```\n\n实例化`vue`\n\n```js\nconst app = new Vue({\n    el:\"#app\",\n    data:{\n        foo:\"foo\"\n    }\n})\n```\n\n观察`render`的`render`，我们能得到虚拟`DOM`\n\n```js\n(function anonymous(\n) {\n\twith(this){return _c('div',{attrs:{\"id\":\"app\"}},[_c('p',{staticClass:\"p\"},\n\t\t\t\t\t  [_v(\"节点内容\")]),_v(\" \"),_c('h3',[_v(_s(foo))])])}})\n```\n\n通过`VNode`，`vue`可以对这颗抽象树进行创建节点,删除节点以及修改节点的操作， 经过`diff`算法得出一些需要修改的最小单位,再更新视图，减少了`dom`操作，提高了性能\n\n## 二、为什么需要虚拟DOM\n\n`DOM`是很慢的，其元素非常庞大，页面的性能问题，大部分都是由`DOM`操作引起的\n\n真实的`DOM`节点，哪怕一个最简单的`div`也包含着很多属性，可以打印出来直观感受一下：\n ![](https://static.vue-js.com/cc95c7f0-442c-11eb-ab90-d9ae814b240d.png)\n\n由此可见，操作`DOM`的代价仍旧是昂贵的，频繁操作还是会出现页面卡顿，影响用户的体验\n\n**举个例子：**\n\n你用传统的原生`api`或`jQuery`去操作`DOM`时，浏览器会从构建`DOM`树开始从头到尾执行一遍流程\n\n当你在一次操作时，需要更新10个`DOM`节点，浏览器没这么智能，收到第一个更新`DOM`请求后，并不知道后续还有9次更新操作，因此会马上执行流程，最终执行10次流程\n\n而通过`VNode`，同样更新10个`DOM`节点，虚拟`DOM`不会立即操作`DOM`，而是将这10次更新的`diff`内容保存到本地的一个`js`对象中，最终将这个`js`对象一次性`attach`到`DOM`树上，避免大量的无谓计算\n\n> 很多人认为虚拟 DOM 最大的优势是 diff 算法，减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势，但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程，实现了跨平台的能力，而不仅仅局限于浏览器的 DOM，可以是安卓和 IOS 的原生组件，可以是近期很火热的小程序，也可以是各种GUI\n\n\n## 三、如何实现虚拟DOM\n\n首先可以看看`vue`中`VNode`的结构\n\n源码位置：src/core/vdom/vnode.js\n\n```js\nexport default class VNode {\n  tag: string | void;\n  data: VNodeData | void;\n  children: ?Array<VNode>;\n  text: string | void;\n  elm: Node | void;\n  ns: string | void;\n  context: Component | void; // rendered in this component's scope\n  functionalContext: Component | void; // only for functional component root nodes\n  key: string | number | void;\n  componentOptions: VNodeComponentOptions | void;\n  componentInstance: Component | void; // component instance\n  parent: VNode | void; // component placeholder node\n  raw: boolean; // contains raw HTML? (server only)\n  isStatic: boolean; // hoisted static node\n  isRootInsert: boolean; // necessary for enter transition check\n  isComment: boolean; // empty comment placeholder?\n  isCloned: boolean; // is a cloned node?\n  isOnce: boolean; // is a v-once node?\n\n  constructor (\n    tag?: string,\n    data?: VNodeData,\n    children?: ?Array<VNode>,\n    text?: string,\n    elm?: Node,\n    context?: Component,\n    componentOptions?: VNodeComponentOptions\n  ) {\n    /*当前节点的标签名*/\n    this.tag = tag\n    /*当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息*/\n    this.data = data\n    /*当前节点的子节点，是一个数组*/\n    this.children = children\n    /*当前节点的文本*/\n    this.text = text\n    /*当前虚拟节点对应的真实dom节点*/\n    this.elm = elm\n    /*当前节点的名字空间*/\n    this.ns = undefined\n    /*编译作用域*/\n    this.context = context\n    /*函数化组件作用域*/\n    this.functionalContext = undefined\n    /*节点的key属性，被当作节点的标志，用以优化*/\n    this.key = data && data.key\n    /*组件的option选项*/\n    this.componentOptions = componentOptions\n    /*当前节点对应的组件的实例*/\n    this.componentInstance = undefined\n    /*当前节点的父节点*/\n    this.parent = undefined\n    /*简而言之就是是否为原生HTML或只是普通文本，innerHTML的时候为true，textContent的时候为false*/\n    this.raw = false\n    /*静态节点标志*/\n    this.isStatic = false\n    /*是否作为跟节点插入*/\n    this.isRootInsert = true\n    /*是否为注释节点*/\n    this.isComment = false\n    /*是否为克隆节点*/\n    this.isCloned = false\n    /*是否有v-once指令*/\n    this.isOnce = false\n  }\n\n  // DEPRECATED: alias for componentInstance for backwards compat.\n  /* istanbul ignore next https://github.com/answershuto/learnVue*/\n  get child (): Component | void {\n    return this.componentInstance\n  }\n}\n```\n\n这里对`VNode`进行稍微的说明：\n\n- 所有对象的 `context` 选项都指向了 `Vue` 实例\n- `elm` 属性则指向了其相对应的真实 `DOM` 节点\n\n`vue`是通过`createElement`生成`VNode`\n\n源码位置：src/core/vdom/create-element.js\n\n```js\nexport function createElement (\n  context: Component,\n  tag: any,\n  data: any,\n  children: any,\n  normalizationType: any,\n  alwaysNormalize: boolean\n): VNode | Array<VNode> {\n  if (Array.isArray(data) || isPrimitive(data)) {\n    normalizationType = children\n    children = data\n    data = undefined\n  }\n  if (isTrue(alwaysNormalize)) {\n    normalizationType = ALWAYS_NORMALIZE\n  }\n  return _createElement(context, tag, data, children, normalizationType)\n}\n```\n\n上面可以看到`createElement` 方法实际上是对 `_createElement` 方法的封装，对参数的传入进行了判断\n\n```javascript\nexport function _createElement(\n    context: Component,\n    tag?: string | Class<Component> | Function | Object,\n    data?: VNodeData,\n    children?: any,\n    normalizationType?: number\n): VNode | Array<VNode> {\n    if (isDef(data) && isDef((data: any).__ob__)) {\n        process.env.NODE_ENV !== 'production' && warn(\n            `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\\n` +\n            'Always create fresh vnode data objects in each render!',\n            context`\n        )\n        return createEmptyVNode()\n    }\n    // object syntax in v-bind\n    if (isDef(data) && isDef(data.is)) {\n        tag = data.is\n    }\n    if (!tag) {\n        // in case of component :is set to falsy value\n        return createEmptyVNode()\n    }\n    ... \n    // support single function children as default scoped slot\n    if (Array.isArray(children) &&\n        typeof children[0] === 'function'\n    ) {\n        data = data || {}\n        data.scopedSlots = { default: children[0] }\n        children.length = 0\n    }\n    if (normalizationType === ALWAYS_NORMALIZE) {\n        children = normalizeChildren(children)\n    } else if ( === SIMPLE_NORMALIZE) {\n        children = simpleNormalizeChildren(children)\n    }\n\t// 创建VNode\n    ...\n}\n```\n\n可以看到`_createElement`接收5个参数：\n\n- `context` 表示 `VNode` 的上下文环境，是 `Component` 类型\n- tag 表示标签，它可以是一个字符串，也可以是一个 `Component`\n\n- `data` 表示 `VNode` 的数据，它是一个 `VNodeData` 类型\n\n- `children` 表示当前 `VNode `的子节点，它是任意类型的\n\n- `normalizationType` 表示子节点规范的类型，类型不同规范的方法也就不一样，主要是参考 `render` 函数是编译生成的还是用户手写的\n\n根据`normalizationType` 的类型，`children`会有不同的定义\n\n```js\nif (normalizationType === ALWAYS_NORMALIZE) {\n    children = normalizeChildren(children)\n} else if ( === SIMPLE_NORMALIZE) {\n    children = simpleNormalizeChildren(children)\n}\n```\n\n`simpleNormalizeChildren`方法调用场景是 `render` 函数是编译生成的\n\n`normalizeChildren`方法调用场景分为下面两种：\n\n-  `render` 函数是用户手写的\n- 编译 `slot`、`v-for` 的时候会产生嵌套数组\n\n无论是`simpleNormalizeChildren`还是`normalizeChildren`都是对`children`进行规范（使`children` 变成了一个类型为 `VNode` 的 `Array`），这里就不展开说了\n\n规范化`children`的源码位置在：src/core/vdom/helpers/normalzie-children.js\n\n在规范化`children`后，就去创建`VNode`\n\n```js\nlet vnode, ns\n// 对tag进行判断\nif (typeof tag === 'string') {\n  let Ctor\n  ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)\n  if (config.isReservedTag(tag)) {\n    // 如果是内置的节点，则直接创建一个普通VNode\n    vnode = new VNode(\n      config.parsePlatformTagName(tag), data, children,\n      undefined, undefined, context\n    )\n  } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {\n    // component\n    // 如果是component类型，则会通过createComponent创建VNode节点\n    vnode = createComponent(Ctor, data, context, children, tag)\n  } else {\n    vnode = new VNode(\n      tag, data, children,\n      undefined, undefined, context\n    )\n  }\n} else {\n  // direct component options / constructor\n  vnode = createComponent(tag, data, context, children)\n}\n```\n\n`createComponent`同样是创建`VNode`\n\n源码位置：src/core/vdom/create-component.js\n\n```js\nexport function createComponent (\n  Ctor: Class<Component> | Function | Object | void,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag?: string\n): VNode | Array<VNode> | void {\n  if (isUndef(Ctor)) {\n    return\n  }\n // 构建子类构造函数 \n  const baseCtor = context.$options._base\n\n  // plain options object: turn it into a constructor\n  if (isObject(Ctor)) {\n    Ctor = baseCtor.extend(Ctor)\n  }\n\n  // if at this stage it's not a constructor or an async component factory,\n  // reject.\n  if (typeof Ctor !== 'function') {\n    if (process.env.NODE_ENV !== 'production') {\n      warn(`Invalid Component definition: ${String(Ctor)}`, context)\n    }\n    return\n  }\n\n  // async component\n  let asyncFactory\n  if (isUndef(Ctor.cid)) {\n    asyncFactory = Ctor\n    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)\n    if (Ctor === undefined) {\n      return createAsyncPlaceholder(\n        asyncFactory,\n        data,\n        context,\n        children,\n        tag\n      )\n    }\n  }\n\n  data = data || {}\n\n  // resolve constructor options in case global mixins are applied after\n  // component constructor creation\n  resolveConstructorOptions(Ctor)\n\n  // transform component v-model data into props & events\n  if (isDef(data.model)) {\n    transformModel(Ctor.options, data)\n  }\n\n  // extract props\n  const propsData = extractPropsFromVNodeData(data, Ctor, tag)\n\n  // functional component\n  if (isTrue(Ctor.options.functional)) {\n    return createFunctionalComponent(Ctor, propsData, data, context, children)\n  }\n\n  // extract listeners, since these needs to be treated as\n  // child component listeners instead of DOM listeners\n  const listeners = data.on\n  // replace with listeners with .native modifier\n  // so it gets processed during parent component patch.\n  data.on = data.nativeOn\n\n  if (isTrue(Ctor.options.abstract)) {\n    const slot = data.slot\n    data = {}\n    if (slot) {\n      data.slot = slot\n    }\n  }\n\n  // 安装组件钩子函数，把钩子函数合并到data.hook中\n  installComponentHooks(data)\n\n  //实例化一个VNode返回。组件的VNode是没有children的\n  const name = Ctor.options.name || tag\n  const vnode = new VNode(\n    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n    data, undefined, undefined, undefined, context,\n    { Ctor, propsData, listeners, tag, children },\n    asyncFactory\n  )\n  if (__WEEX__ && isRecyclableComponent(vnode)) {\n    return renderRecyclableComponentTemplate(vnode)\n  }\n\n  return vnode\n}\n```\n\n稍微提下`createComponent`生成`VNode`的三个关键流程：\n\n- 构造子类构造函数`Ctor `\n- `installComponentHooks`安装组件钩子函数\n- 实例化 `vnode`\n\n### 小结\n\n`createElement` 创建 `VNode` 的过程，每个 `VNode` 有 `children`，`children` 每个元素也是一个`VNode`，这样就形成了一个虚拟树结构，用于描述真实的`DOM`树结构\n\n## 参考文献\n\n- https://ustbhuangyi.github.io/vue-analysis/v2/data-driven/create-element.html#children-%E7%9A%84%E8%A7%84%E8%8C%83%E5%8C%96\n- https://juejin.cn/post/6876711874050818061"
  },
  {
    "path": "docs/vue/vue.md",
    "content": "# 面试官：有使用过vue吗？说说你对vue的理解\n\n![](https://static.vue-js.com/02ac1620-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、从历史说起\n\nWeb是World Wide Web的简称，中文译为万维网我们可以将它规划成如下的几个时代来进行理解\n\n- 石器时代\n- 文明时代\n- 工业革命时代\n- 百花齐放时代\n\n### 石器时代\n\n石器时代指的就是我们的静态网页，可以欣赏一下1997的Apple官网\n\n ![](https://static.vue-js.com/1734e450-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\n最早的网页是没有数据库的，可以理解成就是一张可以在网络上浏览的报纸，直到CGI技术的出现通过 CGI Perl 运行一小段代码与数据库或文件系统进行交互，如当时的Google（1998年）\n\n ![](https://static.vue-js.com/23189000-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\n### 文明时代\n\nASP，JSP大家应该都不会太陌生，最早出现于 2005 年左右，先后出现了微软的 ASP 和 Java Server Pages \\[JSP\\] 等技术,取代了 CGI ，增强了 WEB 与服务端的交互的安全性，类似于下面这样，其实就是Java + HTML\n\n`<%@ page language=\"java\" contentType=\"text/html; charset=utf-8\"  \n    pageEncoding=\"utf-8\"%>  \n<!DOCTYPE html>  \n<html>  \n<head>  \n  <meta charset=\"utf-8\">  \n  <title>JSP demo</title>  \n</head>  \n<body>  \n  <img src=\"http://localhost:8080/web05_session/1.jpg\" width=200 height=100 />  \n</body>  \n</html>  \n`\n\nJSP有一个很大的缺点，就是不太灵活，因为JSP是在服务器端执行的，通常返回该客户端的就是一个HTML文本。我们每次的请求：获取的数据、内容的加载，都是服务器为我们返回渲染完成之后的 DOM，这也就使得我们开发网站的灵活度大打折扣在这种情况下，同年：Ajax火了\\(小细节，这里为什么说火了，因为 Ajax 技术并不是 2005 年出现的，他的雏形是 1999 年\\)，现在看来很常见的技术手段，在当时可是珍贵无比\n\n### 工业革命时代\n\n到这里大家就更熟悉了，移动设备的普及，Jquery的出现，以及SPA（Single Page Application 单页面应用）的雏形，Backbone EmberJS AngularJS 这样一批前端框架随之出现，但当时SPA的路不好走，例如SEO问题，SPA 过多的页面、复杂场景下 View 的绑定等，都没有很好的处理经过这几年的飞速发展，节约了开发人员大量的精力、降低了开发者和开发过程的门槛，极大提升了开发效率和迭代速度，我们可以称之其为工业时代\n\n### 百花齐放时代\n\n这里没有文字，放一张图感受一下\n\n\n ![](https://static.vue-js.com/32a6f430-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\nPS：这里为什么要说这么多Web的历史，我们可以看到Web技术的变化之大与快，每一种新的技术出现都是一些特定场景的解决方案，那我们今天的主角Vue又是为了解决什么呢？我们接着往下看\n\n## 二、vue是什么\n\nVue.js（/vjuː/，或简称为Vue）是一个用于创建用户界面的开源JavaScript框架，也是一个创建单页应用的Web应用框架。2016年一项针对JavaScript的调查表明，Vue有着89\\%的开发者满意度。在GitHub上，该项目平均每天能收获95颗星，为Github有史以来星标数第3多的项目同时也是一款流行的JavaScript前端框架，旨在更好地组织与简化Web开发。Vue所关注的核心是MVC模式中的视图层，同时，它也能方便地获取数据更新，并通过组件内部特定的方法实现视图与模型的交互PS: Vue作者尤雨溪是在为AngularJS工作之后开发出了这一框架。他声称自己的思路是提取Angular中为自己所喜欢的部分，构建出一款相当轻量的框架最早发布于2014年2月\n\n## 三、Vue核心特性\n\n### 数据驱动（MVVM\\)\n\n`MVVM`表示的是 `Model-View-ViewModel`\n\n- Model：模型层，负责处理业务逻辑以及和服务器端进行交互\n- View：视图层：负责将数据模型转化为UI展示出来，可以简单的理解为HTML页面\n- ViewModel：视图模型层，用来连接Model和View，是Model和View之间的通信桥梁\n\n这时候需要一张直观的关系图，如下\n ![image.png](https://static.vue-js.com/4402c560-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\n### 组件化\n\n1.什么是组件化一句话来说就是把图形、非图形的各种逻辑均抽象为一个统一的概念（组件）来实现开发的模式，在`Vue`中每一个`.vue`文件都可以视为一个组件2.组件化的优势\n\n- 降低整个系统的耦合度，在保持接口不变的情况下，我们可以替换不同的组件快速完成需求，例如输入框，可以替换为日历、时间、范围等组件作具体的实现\n- 调试方便，由于整个系统是通过组件组合起来的，在出现问题的时候，可以用排除法直接移除组件，或者根据报错的组件快速定位问题，之所以能够快速定位，是因为每个组件之间低耦合，职责单一，所以逻辑会比分析整个系统要简单\n- 提高可维护性，由于每个组件的职责单一，并且组件在系统中是被复用的，所以对代码进行优化可获得系统的整体升级\n\n### 指令系统\n\n解释：指令 \\(Directives\\) 是带有 v- 前缀的特殊属性作用：当表达式的值改变时，将其产生的连带影响，响应式地作用于 DOM\n\n- 常用的指令\n\n  - 条件渲染指令 `v-if`\n  - 列表渲染指令`v-for`\n  - 属性绑定指令`v-bind`\n  - 事件绑定指令`v-on`\n  - 双向数据绑定指令`v-model`\n\n没有指令之前我们是怎么做的？是不是先要获取到DOM然后在....干点啥\n\n## 四、Vue跟传统开发的区别\n\n没有落地使用场景的革命不是好革命，就以一个高频的应用场景来示意吧注册账号这个需求大家应该很熟悉了，如下\n\n ![](https://static.vue-js.com/5ae84840-3ac6-11eb-ab90-d9ae814b240d.png)\n\n用`jquery`来实现大概的思路就是选择流程dom对象，点击按钮隐藏当前活动流程dom对象，显示下一流程dom对象如下图\\(代码就不上了，上了就篇文章就没了..\\)\n\n ![](https://static.vue-js.com/65f89e60-3ac6-11eb-85f6-6fac77c0c9b3.png)\n\n用`vue`来实现，我们知道`vue`基本不操作`dom`节点， 双向绑定使`dom`节点跟视图绑定后，通过修改变量的值控制`dom`节点的各类属性。所以其实现思路为：视图层使用一变量控制dom节点显示与否，点击按钮则改变该变量，如下图\n\n ![](https://static.vue-js.com/6f916fb0-3ac6-11eb-ab90-d9ae814b240d.png)\n\n总结就是：\n\n- Vue所有的界面事件，都是只去操作数据的，Jquery操作DOM\n- Vue所有界面的变动，都是根据数据自动绑定出来的，Jquery操作DOM\n\n## 五、Vue和React对比\n\n这里就做几个简单的类比吧，当然没有好坏之分，只是使用场景不同\n\n### 相同点\n\n- 都有组件化思想\n- 都支持服务器端渲染\n- 都有Virtual DOM（虚拟dom）\n- 数据驱动视图\n- 都有支持native的方案：`Vue`的`weex`、`React`的`React native`\n- 都有自己的构建工具：`Vue`的`vue-cli`、`React`的`Create React App`\n\n### 区别\n\n- 数据流向的不同。`react`从诞生开始就推崇单向数据流，而`Vue`是双向数据流\n- 数据变化的实现原理不同。`react`使用的是不可变数据，而`Vue`使用的是可变的数据\n- 组件化通信的不同。`react`中我们通过使用回调函数来进行通信的，而`Vue`中子组件向父组件传递消息有两种方式：事件和回调函数\n- diff算法不同。`react`主要使用diff队列保存需要更新哪些DOM，得到patch树，再统一操作批量更新DOM。`Vue` 使用双向指针，边对比，边更新DOM\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000016269636\n- https://zh.wikipedia.org/zh-cn/Vue.js\n- https://zhuanlan.zhihu.com/p/20197803\n- https://zhuanlan.zhihu.com/p/38296857\n\n ![](https://static.vue-js.com/821b87b0-3ac6-11eb-ab90-d9ae814b240d.png)\n"
  },
  {
    "path": "docs/vue/vue3_vue2.md",
    "content": "# 面试官：vue3有了解过吗？能说说跟vue2的区别吗？\n\n ![](https://static.vue-js.com/774b6950-5087-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、Vue3介绍\n\n关于`vue3`的重构背景，尤大是这样说的：\n\n「Vue 新版本的理念成型于 2018 年末，当时 Vue 2 的代码库已经有两岁半了。比起通用软件的生命周期来这好像也没那么久，但在这段时期，前端世界已经今昔非比了\n\n在我们更新（和重写）Vue 的主要版本时，主要考虑两点因素：首先是新的 JavaScript 语言特性在主流浏览器中的受支持水平；其次是当前代码库中随时间推移而逐渐暴露出来的一些设计和架构问题」\n\n简要就是：\n- 利用新的语言特性(es6)\n- 解决架构问题\n\n## 哪些变化\n ![](https://static.vue-js.com/9169a900-5087-11eb-85f6-6fac77c0c9b3.png)\n\n从上图中，我们可以概览`Vue3`的新特性，如下：\n\n- 速度更快\n- 体积减少\n- 更易维护\n- 更接近原生\n- 更易使用\n\n\n### 速度更快\n\n`vue3`相比`vue2`\n\n- 重写了虚拟`Dom`实现\n\n- 编译模板的优化\n\n- 更高效的组件初始化\n\n- `undate`性能提高1.3~2倍\n\n- `SSR`速度提高了2~3倍\n\n ![](https://static.vue-js.com/ac1d23d0-5087-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 体积更小\n\n通过`webpack`的`tree-shaking`功能，可以将无用模块“剪辑”，仅打包需要的\n\n能够`tree-shaking`，有两大好处：\n\n- 对开发人员，能够对`vue`实现更多其他的功能，而不必担忧整体体积过大\n\n- 对使用者，打包出来的包体积变小了\n\n`vue`可以开发出更多其他的功能，而不必担忧`vue`打包出来的整体体积过多\n\n ![](https://static.vue-js.com/c01af010-5087-11eb-85f6-6fac77c0c9b3.png) \n\n\n\n### 更易维护\n\n#### compositon Api\n\n- 可与现有的`Options API`一起使用\n- 灵活的逻辑组合与复用\n- `Vue3`模块可以和其他框架搭配使用\n\n![](https://static.vue-js.com/c5c919b0-5087-11eb-ab90-d9ae814b240d.png) \n\n\n\n#### 更好的Typescript支持\n\n`VUE3`是基于`typescipt`编写的，可以享受到自动的类型定义提示\n\n![](https://static.vue-js.com/cc688120-5087-11eb-ab90-d9ae814b240d.png)\n\n#### 编译器重写\n\n![](https://static.vue-js.com/fcd33800-5087-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### 更接近原生\n\n可以自定义渲染 API\n\n![](https://static.vue-js.com/0c7d88a0-5088-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 更易使用\n\n响应式 `Api` 暴露出来\n\n![](https://static.vue-js.com/26070260-5088-11eb-ab90-d9ae814b240d.png)\n\n轻松识别组件重新渲染原因\n\n![](https://static.vue-js.com/43b2fcb0-5088-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 二、Vue3新增特性\n\nVue 3 中需要关注的一些新功能包括：\n\n- framents\n- Teleport\n- composition Api\n- createRenderer\n\n\n\n### framents\n\n在 `Vue3.x` 中，组件现在支持有多个根节点\n\n```js\n<!-- Layout.vue -->\n<template>\n  <header>...</header>\n  <main v-bind=\"$attrs\">...</main>\n  <footer>...</footer>\n</template>\n```\n\n\n\n### Teleport\n\n`Teleport` 是一种能够将我们的模板移动到 `DOM` 中 `Vue app` 之外的其他位置的技术，就有点像哆啦A梦的“任意门”\n\n在`vue2`中，像 `modals`,`toast` 等这样的元素，如果我们嵌套在 `Vue` 的某个组件内部，那么处理嵌套组件的定位、`z-index` 和样式就会变得很困难\n\n通过`Teleport`，我们可以在组件的逻辑位置写模板代码，然后在 `Vue` 应用范围之外渲染它\n\n```html\n<button @click=\"showToast\" class=\"btn\">打开 toast</button>\n<!-- to 属性就是目标位置 -->\n<teleport to=\"#teleport-target\">\n    <div v-if=\"visible\" class=\"toast-wrap\">\n        <div class=\"toast-msg\">我是一个 Toast 文案</div>\n    </div>\n</teleport>\n```\n\n\n\n### createRenderer\n\n通过`createRenderer`，我们能够构建自定义渲染器，我们能够将 `vue` 的开发模型扩展到其他平台\n\n我们可以将其生成在`canvas`画布上\n\n![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da4437845ec54eb3829313c92fc81afe~tplv-k3u1fbpfcp-watermark.image)\n\n\n\n关于`createRenderer`，我们了解下基本使用，就不展开讲述了\n\n```js\nimport { createRenderer } from '@vue/runtime-core'\n\nconst { render, createApp } = createRenderer({\n  patchProp,\n  insert,\n  remove,\n  createElement,\n  // ...\n})\n\nexport { render, createApp }\n\nexport * from '@vue/runtime-core'\n```\n\n\n\n### composition Api\n\ncomposition Api，也就是组合式`api`，通过这种形式，我们能够更加容易维护我们的代码，将相同功能的变量进行一个集中式的管理\n\n ![](https://static.vue-js.com/5e0bfb70-5088-11eb-ab90-d9ae814b240d.png)\n\n关于`compositon api`的使用，这里以下图展开\n\n![](https://static.vue-js.com/6f67a590-5088-11eb-85f6-6fac77c0c9b3.png)\n\n简单使用:\n\n```js\nexport default {\n    setup() {\n        const count = ref(0)\n        const double = computed(() => count.value * 2)\n        function increment() {\n            count.value++\n        }\n        onMounted(() => console.log('component mounted!'))\n        return {\n            count,\n            double,\n            increment\n        }\n    }\n}\n```\n\n\n\n### 三、非兼容变更\n\n### Global API\n\n- 全局 `Vue API` 已更改为使用应用程序实例\n- 全局和内部 `API` 已经被重构为可 `tree-shakable`\n\n### 模板指令\n\n- 组件上 `v-model` 用法已更改\n- `<template v-for>`和 非 `v-for`节点上`key`用法已更改\n- 在同一元素上使用的 `v-if` 和 `v-for` 优先级已更改\n- `v-bind=\"object\"` 现在排序敏感\n- `v-for` 中的 `ref` 不再注册 `ref` 数组\n\n### 组件\n\n- 只能使用普通函数创建功能组件\n- `functional` 属性在单文件组件 `(SFC) `\n- 异步组件现在需要 `defineAsyncComponent` 方法来创建\n\n### 渲染函数\n\n- 渲染函数` API `改变\n- `$scopedSlots` property 已删除，所有插槽都通过 `$slots` 作为函数暴露\n- 自定义指令 API 已更改为与组件生命周期一致\n- 一些转换 `class` 被重命名了：\n  - `v-enter` -> `v-enter-from`\n  - `v-leave` -> `v-leave-from`\n- 组件 `watch` 选项和实例方法 `$watch`不再支持点分隔字符串路径，请改用计算函数作为参数\n- 在 `Vue 2.x` 中，应用根容器的 `outerHTML` 将替换为根组件模板 (如果根组件没有模板/渲染选项，则最终编译为模板)。`VUE3.x` 现在使用应用程序容器的 `innerHTML`。\n\n### 其他小改变\n\n- `destroyed` 生命周期选项被重命名为 `unmounted`\n- `beforeDestroy` 生命周期选项被重命名为 `beforeUnmount`\n- `[prop default`工厂函数不再有权访问 `this` 是上下文\n- 自定义指令 API 已更改为与组件生命周期一致\n- `data` 应始终声明为函数\n- 来自 `mixin` 的 `data` 选项现在可简单地合并\n- `attribute` 强制策略已更改\n- 一些过渡 `class` 被重命名\n- 组建 watch 选项和实例方法 `$watch`不再支持以点分隔的字符串路径。请改用计算属性函数作为参数。\n- `<template>` 没有特殊指令的标记 (`v-if/else-if/else`、`v-for` 或 `v-slot`) 现在被视为普通元素，并将生成原生的 `<template>` 元素，而不是渲染其内部内容。\n- 在` Vue 2.x` 中，应用根容器的 `outerHTML` 将替换为根组件模板 (如果根组件没有模板/渲染选项，则最终编译为模板)。`Vue 3.x` 现在使用应用容器的 `innerHTML`，这意味着容器本身不再被视为模板的一部分。\n\n### 移除 API\n\n- `keyCode` 支持作为 `v-on` 的修饰符\n- `$on`，`$off `和` $once` 实例方法\n- 过滤`filter`\n- 内联模板 `attribute`\n- `$destroy` 实例方法。用户不应再手动管理单个` Vue` 组件的生命周期。\n\n\n\n## 参考文献\n\n- https://vue3js.cn/docs/zh/guide/migration/introduction.html#%E6%A8%A1%E6%9D%BF%E6%8C%87%E4%BB%A4\n- https://composition-api.vuejs.org/zh/#api-%E4%BB%8B%E7%BB%8D"
  },
  {
    "path": "docs/vue3/composition.md",
    "content": "# 面试官：Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同？\n\n ![](https://static.vue-js.com/8d6dd7b0-6048-11eb-85f6-6fac77c0c9b3.png)\n\n## 开始之前\n`Composition API` 可以说是`Vue3`的最大特点，那么为什么要推出`Composition Api`，解决了什么问题？\n\n通常使用`Vue2`开发的项目，普遍会存在以下问题：\n\n- 代码的可读性随着组件变大而变差\n- 每一种代码复用的方式，都存在缺点\n- TypeScript支持有限\n\n以上通过使用`Composition Api`都能迎刃而解\n\n## 正文\n### 一、Options Api\n\n`Options API`，即大家常说的选项API，即以`vue`为后缀的文件，通过定义`methods`，`computed`，`watch`，`data`等属性与方法，共同处理页面逻辑\n\n如下图：\n\n ![](https://static.vue-js.com/9bf6d9d0-6048-11eb-85f6-6fac77c0c9b3.png)\n\n可以看到`Options`代码编写方式，如果是组件状态，则写在`data`属性上，如果是方法，则写在`methods`属性上...\n\n用组件的选项 (`data`、`computed`、`methods`、`watch`) 组织逻辑在大多数情况下都有效\n\n然而，当组件变得复杂，导致对应属性的列表也会增长，这可能会导致组件难以阅读和理解\n\n\n### 二、Composition Api \n\n在 Vue3 Composition API 中，组件根据逻辑功能来组织的，一个功能所定义的所有 API 会放在一起（更加的高内聚，低耦合）\n\n即使项目很大，功能很多，我们都能快速的定位到这个功能所用到的所有 API\n\n\n ![](https://static.vue-js.com/acee9200-6048-11eb-ab90-d9ae814b240d.png)\n\n\n\n### 三、对比\n\n下面对`Composition Api `与`Options Api`进行两大方面的比较\n\n- 逻辑组织\n- 逻辑复用\n\n\n\n#### 逻辑组织\n\n##### Options API\n\n假设一个组件是一个大型组件，其内部有很多处理逻辑关注点（对应下图不用颜色）\n\n ![](https://static.vue-js.com/dc83d070-6048-11eb-ab90-d9ae814b240d.png)\n\n\n\n可以看到，这种碎片化使得理解和维护复杂组件变得困难\n\n选项的分离掩盖了潜在的逻辑问题。此外，在处理单个逻辑关注点时，我们必须不断地“跳转”相关代码的选项块\n\n\n\n##### Compostion API\n\n而`Compositon API`正是解决上述问题，将某个逻辑关注点相关的代码全都放在一个函数里，这样当需要修改一个功能时，就不再需要在文件中跳来跳去\n\n下面举个简单例子，将处理`count`属性相关的代码放在同一个函数了\n\n```js\nfunction useCount() {\n    let count = ref(10);\n    let double = computed(() => {\n        return count.value * 2;\n    });\n\n    const handleConut = () => {\n        count.value = count.value * 2;\n    };\n\n    console.log(count);\n\n    return {\n        count,\n        double,\n        handleConut,\n    };\n}\n```\n\n组件上中使用`count`\n\n```js\nexport default defineComponent({\n    setup() {\n        const { count, double, handleConut } = useCount();\n        return {\n            count,\n            double,\n            handleConut\n        }\n    },\n});\n```\n\n再来一张图进行对比，可以很直观地感受到 `Composition API `在逻辑组织方面的优势，以后修改一个属性功能的时候，只需要跳到控制该属性的方法中即可\n\n![](https://static.vue-js.com/e5804bc0-5c58-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n\n\n#### 逻辑复用\n\n在`Vue2`中，我们是用过`mixin`去复用相同的逻辑\n\n下面举个例子，我们会另起一个`mixin.js`文件\n\n```js\nexport const MoveMixin = {\n  data() {\n    return {\n      x: 0,\n      y: 0,\n    };\n  },\n\n  methods: {\n    handleKeyup(e) {\n      console.log(e.code);\n      // 上下左右 x y\n      switch (e.code) {\n        case \"ArrowUp\":\n          this.y--;\n          break;\n        case \"ArrowDown\":\n          this.y++;\n          break;\n        case \"ArrowLeft\":\n          this.x--;\n          break;\n        case \"ArrowRight\":\n          this.x++;\n          break;\n      }\n    },\n  },\n\n  mounted() {\n    window.addEventListener(\"keyup\", this.handleKeyup);\n  },\n\n  unmounted() {\n    window.removeEventListener(\"keyup\", this.handleKeyup);\n  },\n};\n\n```\n\n然后在组件中使用\n\n```js\n<template>\n  <div>\n    Mouse position: x {{ x }} / y {{ y }}\n  </div>\n</template>\n<script>\nimport mousePositionMixin from './mouse'\nexport default {\n  mixins: [mousePositionMixin]\n}\n</script>\n```\n\n使用单个` mixin `似乎问题不大，但是当我们一个组件混入大量不同的 `mixins` 的时候\n\n```js\nmixins: [mousePositionMixin, fooMixin, barMixin, otherMixin]\n```\n\n会存在两个非常明显的问题：\n\n- 命名冲突\n- 数据来源不清晰\n\n\n现在通过`Compositon API`这种方式改写上面的代码\n\n```js\nimport { onMounted, onUnmounted, reactive } from \"vue\";\nexport function useMove() {\n  const position = reactive({\n    x: 0,\n    y: 0,\n  });\n\n  const handleKeyup = (e) => {\n    console.log(e.code);\n    // 上下左右 x y\n    switch (e.code) {\n      case \"ArrowUp\":\n        // y.value--;\n        position.y--;\n        break;\n      case \"ArrowDown\":\n        // y.value++;\n        position.y++;\n        break;\n      case \"ArrowLeft\":\n        // x.value--;\n        position.x--;\n        break;\n      case \"ArrowRight\":\n        // x.value++;\n        position.x++;\n        break;\n    }\n  };\n\n  onMounted(() => {\n    window.addEventListener(\"keyup\", handleKeyup);\n  });\n\n  onUnmounted(() => {\n    window.removeEventListener(\"keyup\", handleKeyup);\n  });\n\n  return { position };\n}\n```\n\n在组件中使用\n\n```js\n<template>\n  <div>\n    Mouse position: x {{ x }} / y {{ y }}\n  </div>\n</template>\n\n<script>\nimport { useMove } from \"./useMove\";\nimport { toRefs } from \"vue\";\nexport default {\n  setup() {\n    const { position } = useMove();\n    const { x, y } = toRefs(position);\n    return {\n      x,\n      y,\n    };\n\n  },\n};\n</script>\n```\n\n可以看到，整个数据来源清晰了，即使去编写更多的 hook 函数，也不会出现命名冲突的问题\n\n\n### 小结\n\n- 在逻辑组织和逻辑复用方面，`Composition API`是优于`Options  API`\n- 因为`Composition API`几乎是函数，会有更好的类型推断。\n- `Composition API `对 `tree-shaking` 友好，代码也更容易压缩\n- `Composition API`中见不到`this`的使用，减少了`this`指向不明的情况\n- 如果是小型组件，可以继续使用`Options API`，也是十分友好的"
  },
  {
    "path": "docs/vue3/goal.md",
    "content": "# 面试官：Vue3.0的设计目标是什么？做了哪些优化\n\n![](https://static.vue-js.com/b93b49c0-5c58-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、设计目标\n\n不以解决实际业务痛点的更新都是耍流氓，下面我们来列举一下`Vue3`之前我们或许会面临的问题\n\n- 随着功能的增长，复杂组件的代码变得越来越难以维护\n\n- 缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制\n\n- 类型推断不够友好\n\n- `bundle`的时间太久了\n\n而 `Vue3` 经过长达两三年时间的筹备，做了哪些事情？\n\n我们从结果反推\n\n- 更小\n- 更快\n- TypeScript支持\n- API设计一致性\n- 提高自身可维护性\n- 开放更多底层功能\n\n一句话概述，就是更小更快更友好了\n\n\n### 更小\n\n`Vue3`移除一些不常用的 `API`\n\n引入`tree-shaking`，可以将无用模块“剪辑”，仅打包需要的，使打包的整体体积变小了\n\n\n\n### 更快\n\n主要体现在编译方面：\n\n- diff算法优化\n- 静态提升\n- 事件监听缓存\n- SSR优化\n\n下篇文章我们会进一步介绍\n\n\n\n### 更友好\n\n`vue3`在兼顾`vue2`的`options API`的同时还推出了`composition API`，大大增加了代码的逻辑组织和代码复用能力\n\n这里代码简单演示下：\n\n存在一个获取鼠标位置的函数\n\n```js\nimport { toRefs, reactive } from 'vue';\nfunction useMouse(){\n    const state = reactive({x:0,y:0});\n    const update = e=>{\n        state.x = e.pageX;\n        state.y = e.pageY;\n    }\n    onMounted(()=>{\n        window.addEventListener('mousemove',update);\n    })\n    onUnmounted(()=>{\n        window.removeEventListener('mousemove',update);\n    })\n\n    return toRefs(state);\n}\n```\n\n我们只需要调用这个函数，即可获取`x`、`y`的坐标，完全不用关注实现过程\n\n试想一下，如果很多类似的第三方库，我们只需要调用即可，不必关注实现过程，开发效率大大提高\n\n同时，`VUE3`是基于`typescipt`编写的，可以享受到自动的类型定义提示\n\n\n\n## 三、优化方案\n\n`vue3`从很多层面都做了优化，可以分成三个方面：\n\n- 源码\n- 性能\n- 语法 API\n\n\n\n### 源码\n\n源码可以从两个层面展开：\n\n- 源码管理\n- TypeScript\n\n\n\n#### 源码管理\n\n`vue3`整个源码是通过 `monorepo `的方式维护的，根据功能将不同的模块拆分到`packages `目录下面不同的子目录中\n\n ![](https://static.vue-js.com/d7c32520-5c58-11eb-ab90-d9ae814b240d.png)\n\n这样使得模块拆分更细化，职责划分更明确，模块之间的依赖关系也更加明确，开发人员也更容易阅读、理解和更改所有模块源码，提高代码的可维护性\n\n另外一些 `package`（比如 `reactivity` 响应式库）是可以独立于 `Vue` 使用的，这样用户如果只想使用 `Vue3 `的响应式能力，可以单独依赖这个响应式库而不用去依赖整个 `Vue`\n\n\n\n#### TypeScript\n\n`Vue3`是基于`typeScript`编写的，提供了更好的类型检查，能支持复杂的类型推导\n\n\n\n### 性能\n\n`vue3`是从什么哪些方面对性能进行进一步优化呢？\n\n- 体积优化\n- 编译优化\n- 数据劫持优化\n\n这里讲述数据劫持：\n\n在`vue2`中，数据劫持是通过`Object.defineProperty `，这个 API 有一些缺陷，并不能检测对象属性的添加和删除\n\n```js\nObject.defineProperty(data, 'a',{\n  get(){\n    // track\n  },\n  set(){\n    // trigger\n  }\n})\n```\n\n尽管` Vue`为了解决这个问题提供了 `set `和`delete `实例方法，但是对于用户来说，还是增加了一定的心智负担\n\n同时在面对嵌套层级比较深的情况下，就存在性能问题\n\n```js\ndefault {\n  data: {\n    a: {\n      b: {\n          c: {\n          d: 1\n        }\n      }\n    }\n  }\n}\n```\n\n相比之下，`vue3`是通过`proxy`监听整个对象，那么对于删除还是监听当然也能监听到\n\n同时`Proxy ` 并不能监听到内部深层次的对象变化，而 `Vue3` 的处理方式是在` getter` 中去递归响应式，这样的好处是真正访问到的内部对象才会变成响应式，而不是无脑递归\n\n\n\n### 语法 API\n\n这里当然说的就是`composition API`，其两大显著的优化：\n\n- 优化逻辑组织\n- 优化逻辑复用\n\n\n\n#### 逻辑组织\n\n一张图，我们可以很直观地感受到 `Composition API `在逻辑组织方面的优势\n\n ![](https://static.vue-js.com/e5804bc0-5c58-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n相同功能的代码编写在一块，而不像`options API`那样，各个功能的代码混成一块\n\n\n\n#### 逻辑复用\n\n在`vue2`中，我们是通过`mixin`实现功能混合，如果多个`mixin`混合，会存在两个非常明显的问题：命名冲突和数据来源不清晰\n\n而通过`composition`这种形式，可以将一些复用的代码抽离出来作为一个函数，只要的使用的地方直接进行调用即可\n\n同样是上文的获取鼠标位置的例子\n\n```js\nimport { toRefs, reactive, onUnmounted, onMounted } from 'vue';\nfunction useMouse(){\n    const state = reactive({x:0,y:0});\n    const update = e=>{\n        state.x = e.pageX;\n        state.y = e.pageY;\n    }\n    onMounted(()=>{\n        window.addEventListener('mousemove',update);\n    })\n    onUnmounted(()=>{\n        window.removeEventListener('mousemove',update);\n    })\n\n    return toRefs(state);\n}\n```\n\n组件使用\n\n```js\nimport useMousePosition from './mouse'\nexport default {\n    setup() {\n        const { x, y } = useMousePosition()\n        return { x, y }\n    }\n}\n```\n\n可以看到，整个数据来源清晰了，即使去编写更多的` hook `函数，也不会出现命名冲突的问题\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6850418112878575629#heading-5\n- https://vue3js.cn/docs/zh"
  },
  {
    "path": "docs/vue3/modal_component.md",
    "content": "# 面试官：用Vue3.0 写过组件吗？如果想实现一个 Modal你会怎么设计？\n\n ![](https://static.vue-js.com/e294c660-6370-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、组件设计\n\n组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念（组件）来实现开发的模式\n\n现在有一个场景，点击新增与编辑都弹框出来进行填写，功能上大同小异，可能只是标题内容或者是显示的主体内容稍微不同\n\n这时候就没必要写两个组件，只需要根据传入的参数不同，组件显示不同内容即可\n\n这样，下次开发相同界面程序时就可以写更少的代码，意义着更高的开发效率，更少的 `Bug `和更少的程序体积\n\n\n\n## 二、需求分析\n\n实现一个`Modal`组件，首先确定需要完成的内容：\n\n- 遮罩层\n\n- 标题内容\n- 主体内容\n- 确定和取消按钮\n\n主体内容需要灵活，所以可以是字符串，也可以是一段 `html` 代码\n\n特点是它们在当前`vue`实例之外独立存在，通常挂载于`body`之上\n\n除了通过引入`import`的形式，我们还可通过`API`的形式进行组件的调用\n\n还可以包括配置全局样式、国际化、与`typeScript`结合\n\n\n\n## 三、实现流程\n\n首先看看大致流程：\n\n- 目录结构\n- 组件内容\n- 实现 API 形式\n- 事件处理\n\n- 其他完善\n\n\n\n\n\n### 目录结构\n\n`Modal`组件相关的目录结构\n\n```\n├── plugins\n│   └── modal\n│       ├── Content.tsx // 维护 Modal 的内容，用于 h 函数和 jsx 语法\n│       ├── Modal.vue // 基础组件\n│       ├── config.ts // 全局默认配置\n│       ├── index.ts // 入口\n│       ├── locale // 国际化相关\n│       │   ├── index.ts\n│       │   └── lang\n│       │       ├── en-US.ts\n│       │       ├── zh-CN.ts\n│       │       └── zh-TW.ts\n│       └── modal.type.ts // ts类型声明相关\n```\n\n因为 Modal 会被 `app.use(Modal)` 调用作为一个插件，所以都放在`plugins`目录下\n\n\n\n\n\n### 组件内容\n\n首先实现`modal.vue`的主体显示内容大致如下\n\n```html\n<Teleport to=\"body\" :disabled=\"!isTeleport\">\n    <div v-if=\"modelValue\" class=\"modal\">\n        <div\n             class=\"mask\"\n             :style=\"style\"\n             @click=\"maskClose && !loading && handleCancel()\"\n             ></div>\n        <div class=\"modal__main\">\n            <div class=\"modal__title line line--b\">\n                <span>{{ title || t(\"r.title\") }}</span>\n                <span\n                      v-if=\"close\"\n                      :title=\"t('r.close')\"\n                      class=\"close\"\n                      @click=\"!loading && handleCancel()\"\n                      >✕</span\n                    >\n            </div>\n            <div class=\"modal__content\">\n                <Content v-if=\"typeof content === 'function'\" :render=\"content\" />\n                <slot v-else>\n                    {{ content }}\n                </slot>\n            </div>\n            <div class=\"modal__btns line line--t\">\n                <button :disabled=\"loading\" @click=\"handleConfirm\">\n                    <span class=\"loading\" v-if=\"loading\"> ❍ </span>{{ t(\"r.confirm\") }}\n                </button>\n                <button @click=\"!loading && handleCancel()\">\n                    {{ t(\"r.cancel\") }}\n                </button>\n            </div>\n        </div>\n    </div>\n</Teleport>\n```\n\n最外层上通过Vue3 `Teleport` 内置组件进行包裹，其相当于传送门，将里面的内容传送至`body`之上\n\n并且从`DOM`结构上来看，把`modal`该有的内容（遮罩层、标题、内容、底部按钮）都实现了\n\n关于主体内容\n\n```html\n<div class=\"modal__content\">\n    <Content v-if=\"typeof content==='function'\"\n             :render=\"content\" />\n    <slot v-else>\n        {{content}}\n    </slot>\n</div>\n```\n\n可以看到根据传入`content`的类型不同，对应显示不同得到内容\n\n最常见的则是通过调用字符串和默认插槽的形式\n\n```html\n// 默认插槽\n<Modal v-model=\"show\"\n       title=\"演示 slot\">\n    <div>hello world~</div>\n</Modal>\n\n// 字符串\n<Modal v-model=\"show\"\n       title=\"演示 content\"\n       content=\"hello world~\" />\n```\n\n通过 API 形式调用`Modal`组件的时候，`content`可以使用下面两种\n\n- h 函数\n\n```js\n$modal.show({\n  title: '演示 h 函数',\n  content(h) {\n    return h(\n      'div',\n      {\n        style: 'color:red;',\n        onClick: ($event: Event) => console.log('clicked', $event.target)\n      },\n      'hello world ~'\n    );\n  }\n});\n```\n\n- JSX\n\n```js\n$modal.show({\n  title: '演示 jsx 语法',\n  content() {\n    return (\n      <div\n        onClick={($event: Event) => console.log('clicked', $event.target)}\n      >\n        hello world ~\n      </div>\n    );\n  }\n});\n```\n\n\n\n\n\n### 实现 API 形式\n\n那么组件如何实现`API`形式调用`Modal`组件呢？\n\n在`Vue2`中，我们可以借助`Vue`实例以及`Vue.extend`的方式获得组件实例，然后挂载到`body`上\n\n```js\nimport Modal from './Modal.vue';\nconst ComponentClass = Vue.extend(Modal);\nconst instance = new ComponentClass({ el: document.createElement(\"div\") });\ndocument.body.appendChild(instance.$el);\n```\n\n虽然`Vue3`移除了`Vue.extend`方法，但可以通过`createVNode`实现\n\n```js\nimport Modal from './Modal.vue';\nconst container = document.createElement('div');\nconst vnode = createVNode(Modal);\nrender(vnode, container);\nconst instance = vnode.component;\ndocument.body.appendChild(container);\n```\n\n在`Vue2`中，可以通过`this`的形式调用全局 API\n\n```js\nexport default {\n    install(vue) {\n       vue.prototype.$create = create\n    }\n}\n```\n\n而在 Vue3 的 `setup` 中已经没有 `this `概念了，需要调用`app.config.globalProperties`挂载到全局\n\n```js\nexport default {\n    install(app) {\n        app.config.globalProperties.$create = create\n    }\n}\n```\n\n\n\n\n\n### 事件处理\n\n下面再看看看`Modal`组件内部是如何处理「确定」「取消」事件的，既然是`Vue3`，当然采用`Compositon API` 形式\n\n```js\n// Modal.vue\nsetup(props, ctx) {\n  let instance = getCurrentInstance(); // 获得当前组件实例\n  onBeforeMount(() => {\n    instance._hub = {\n      'on-cancel': () => {},\n      'on-confirm': () => {}\n    };\n  });\n\n  const handleConfirm = () => {\n    ctx.emit('on-confirm');\n    instance._hub['on-confirm']();\n  };\n  const handleCancel = () => {\n    ctx.emit('on-cancel');\n    ctx.emit('update:modelValue', false);\n    instance._hub['on-cancel']();\n  };\n\n  return {\n    handleConfirm,\n    handleCancel\n  };\n}\n```\n\n在上面代码中，可以看得到除了使用传统`emit`的形式使父组件监听，还可通过`_hub`属性中添加 `on-cancel`，`on-confirm`方法实现在`API`中进行监听\n\n```js\napp.config.globalProperties.$modal = {\n   show({}) {\n     /* 监听 确定、取消 事件 */\n   }\n}\n```\n\n\n\n下面再来目睹下`_hub`是如何实现\n\n```js\n// index.ts\napp.config.globalProperties.$modal = {\n    show({\n        /* 其他选项 */\n        onConfirm,\n        onCancel\n    }) {\n        /* ... */\n\n        const { props, _hub } = instance;\n\n        const _closeModal = () => {\n            props.modelValue = false;\n            container.parentNode!.removeChild(container);\n        };\n        // 往 _hub 新增事件的具体实现\n        Object.assign(_hub, {\n            async 'on-confirm'() {\n            if (onConfirm) {\n                const fn = onConfirm();\n                // 当方法返回为 Promise\n                if (fn && fn.then) {\n                    try {\n                        props.loading = true;\n                        await fn;\n                        props.loading = false;\n                        _closeModal();\n                    } catch (err) {\n                        // 发生错误时，不关闭弹框\n                        console.error(err);\n                        props.loading = false;\n                    }\n                } else {\n                    _closeModal();\n                }\n            } else {\n                _closeModal();\n            }\n        },\n            'on-cancel'() {\n                onCancel && onCancel();\n                _closeModal();\n            }\n    });\n}\n};\n```\n\n\n\n### 其他完善\n\n关于组件实现国际化、与`typsScript`结合，大家可以根据自身情况在此基础上进行更改\n\n\n\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000038928664"
  },
  {
    "path": "docs/vue3/performance.md",
    "content": "# 面试官：Vue3.0性能提升主要是通过哪几方面体现的？\n\n ![](https://static.vue-js.com/2aac1020-5ed0-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、编译阶段\n\n回顾`Vue2`，我们知道每个组件实例都对应一个 `watcher` 实例，它会在组件渲染的过程中把用到的数据`property`记录为依赖，当依赖发生改变，触发`setter`，则会通知`watcher`，从而使关联的组件重新渲染\n\n ![](https://static.vue-js.com/39066120-5ed0-11eb-85f6-6fac77c0c9b3.png)\n\n试想一下，一个组件结构如下图\n\n```html\n<template>\n    <div id=\"content\">\n        <p class=\"text\">静态文本</p>\n        <p class=\"text\">静态文本</p>\n        <p class=\"text\">{{ message }}</p>\n        <p class=\"text\">静态文本</p>\n        ...\n        <p class=\"text\">静态文本</p>\n    </div>\n</template>\n```\n\n可以看到，组件内部只有一个动态节点，剩余一堆都是静态节点，所以这里很多 `diff` 和遍历其实都是不需要的，造成性能浪费\n\n因此，`Vue3`在编译阶段，做了进一步优化。主要有如下：\n\n- diff算法优化\n- 静态提升\n- 事件监听缓存\n- SSR优化\n\n\n\n#### diff算法优化\n\n`vue3`在`diff`算法中相比`vue2`增加了静态标记\n\n关于这个静态标记，其作用是为了会发生变化的地方添加一个`flag`标记，下次发生变化的时候直接找该地方进行比较\n\n下图这里，已经标记静态节点的`p`标签在`diff`过程中则不会比较，把性能进一步提高\n\n ![](https://static.vue-js.com/c732e150-5c58-11eb-ab90-d9ae814b240d.png)\n\n关于静态类型枚举如下\n\n```js\nexport const enum PatchFlags {\n  TEXT = 1,// 动态的文本节点\n  CLASS = 1 << 1,  // 2 动态的 class\n  STYLE = 1 << 2,  // 4 动态的 style\n  PROPS = 1 << 3,  // 8 动态属性，不包括类名和样式\n  FULL_PROPS = 1 << 4,  // 16 动态 key，当 key 变化时需要完整的 diff 算法做比较\n  HYDRATE_EVENTS = 1 << 5,  // 32 表示带有事件监听器的节点\n  STABLE_FRAGMENT = 1 << 6,   // 64 一个不会改变子节点顺序的 Fragment\n  KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment\n  UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment\n  NEED_PATCH = 1 << 9,   // 512\n  DYNAMIC_SLOTS = 1 << 10,  // 动态 solt\n  HOISTED = -1,  // 特殊标志是负整数表示永远不会用作 diff\n  BAIL = -2 // 一个特殊的标志，指代差异算法\n}\n```\n\n\n\n#### 静态提升\n\n`Vue3`中对不参与更新的元素，会做静态提升，只会被创建一次，在渲染时直接复用\n\n这样就免去了重复的创建节点，大型应用会受益于这个改动，免去了重复的创建操作，优化了运行时候的内存占用\n\n```js\n<span>你好</span>\n\n<div>{{ message }}</div>\n```\n\n没有做静态提升之前\n\n```js\nexport function render(_ctx, _cache, $props, $setup, $data, $options) {\n  return (_openBlock(), _createBlock(_Fragment, null, [\n    _createVNode(\"span\", null, \"你好\"),\n    _createVNode(\"div\", null, _toDisplayString(_ctx.message), 1 /* TEXT */)\n  ], 64 /* STABLE_FRAGMENT */))\n}\n```\n\n做了静态提升之后\n\n```js\nconst _hoisted_1 = /*#__PURE__*/_createVNode(\"span\", null, \"你好\", -1 /* HOISTED */)\n\nexport function render(_ctx, _cache, $props, $setup, $data, $options) {\n  return (_openBlock(), _createBlock(_Fragment, null, [\n    _hoisted_1,\n    _createVNode(\"div\", null, _toDisplayString(_ctx.message), 1 /* TEXT */)\n  ], 64 /* STABLE_FRAGMENT */))\n}\n\n// Check the console for the AST\n```\n\n静态内容`_hoisted_1`被放置在`render` 函数外，每次渲染的时候只要取 `_hoisted_1` 即可\n\n同时 `_hoisted_1` 被打上了 `PatchFlag` ，静态标记值为 -1 ，特殊标志是负整数表示永远不会用于 Diff\n\n\n\n#### 事件监听缓存\n\n默认情况下绑定事件行为会被视为动态绑定，所以每次都会去追踪它的变化\n\n```text\n<div>\n  <button @click = 'onClick'>点我</button>\n</div>\n```\n\n没开启事件监听器缓存\n\n```js\nexport const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {\n  return (_openBlock(), _createBlock(\"div\", null, [\n    _createVNode(\"button\", { onClick: _ctx.onClick }, \"点我\", 8 /* PROPS */, [\"onClick\"])\n                                             // PROPS=1<<3,// 8 //动态属性，但不包含类名和样式\n  ]))\n})\n```\n\n开启事件侦听器缓存后\n\n```js\nexport function render(_ctx, _cache, $props, $setup, $data, $options) {\n  return (_openBlock(), _createBlock(\"div\", null, [\n    _createVNode(\"button\", {\n      onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))\n    }, \"点我\")\n  ]))\n}\n```\n\n上述发现开启了缓存后，没有了静态标记。也就是说下次`diff`算法的时候直接使用\n\n\n\n#### SSR优化\n\n当静态内容大到一定量级时候，会用`createStaticVNode`方法在客户端去生成一个static node，这些静态`node`，会被直接`innerHtml`，就不需要创建对象，然后根据对象渲染\n\n```js\ndiv>\n\t<div>\n\t\t<span>你好</span>\n\t</div>\n\t...  // 很多个静态属性\n\t<div>\n\t\t<span>{{ message }}</span>\n\t</div>\n</div>\n```\n\n编译后\n\n```js\nimport { mergeProps as _mergeProps } from \"vue\"\nimport { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from \"@vue/server-renderer\"\n\nexport function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {\n  const _cssVars = { style: { color: _ctx.color }}\n  _push(`<div${\n    _ssrRenderAttrs(_mergeProps(_attrs, _cssVars))\n  }><div><span>你好</span>...<div><span>你好</span><div><span>${\n    _ssrInterpolate(_ctx.message)\n  }</span></div></div>`)\n}\n```\n\n\n\n## 二、源码体积\n\n相比`Vue2`，`Vue3`整体体积变小了，除了移出一些不常用的API，再重要的是`Tree shanking`\n\n任何一个函数，如`ref`、`reavtived`、`computed`等，仅仅在用到的时候才打包，没用到的模块都被摇掉，打包的整体体积变小\n\n```js\nimport { computed, defineComponent, ref } from 'vue';\nexport default defineComponent({\n    setup(props, context) {\n        const age = ref(18)\n\n        let state = reactive({\n            name: 'test'\n        })\n\n        const readOnlyAge = computed(() => age.value++) // 19\n\n        return {\n            age,\n            state,\n            readOnlyAge\n        }\n    }\n});\n```\n\n\n\n## 三、响应式系统\n\n`vue2`中采用 `defineProperty`来劫持整个对象，然后进行深度遍历所有属性，给每个属性添加`getter`和`setter`，实现响应式\n\n`vue3`采用`proxy`重写了响应式系统，因为`proxy`可以对整个对象进行监听，所以不需要深度遍历\n\n- 可以监听动态属性的添加\n- 可以监听到数组的索引和数组`length`属性\n- 可以监听删除属性\n\n关于这两个 API 具体的不同，我们下篇文章会进行一个更加详细的介绍\n\n\n\n## 参考文献\n\n- https://juejin.cn/post/6903171037211557895"
  },
  {
    "path": "docs/vue3/proxy.md",
    "content": "# 面试官：Vue3.0里为什么要用 Proxy API 替代 defineProperty API ？\n\n ![](https://static.vue-js.com/57aa5c80-5f7f-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、Object.defineProperty\n\n定义：`Object.defineProperty()` 方法会直接在一个对象上定义一个新属性，或者修改一个对象的现有属性，并返回此对象\n\n##### 为什么能实现响应式\n\n通过`defineProperty` 两个属性，`get`及`set`\n\n- get \n\n属性的 getter 函数，当访问该属性时，会调用此函数。执行时不传入任何参数，但是会传入 this 对象（由于继承关系，这里的this并不一定是定义该属性的对象）。该函数的返回值会被用作属性的值\n\n- set \n\n属性的 setter 函数，当属性值被修改时，会调用此函数。该方法接受一个参数（也就是被赋予的新值），会传入赋值时的 this 对象。默认为 undefined\n\n下面通过代码展示：\n\n定义一个响应式函数`defineReactive`\n\n```js\nfunction update() {\n    app.innerText = obj.foo\n}\n\nfunction defineReactive(obj, key, val) {\n    Object.defineProperty(obj, key, {\n        get() {\n            console.log(`get ${key}:${val}`);\n            return val\n        },\n        set(newVal) {\n            if (newVal !== val) {\n                val = newVal\n                update()\n            }\n        }\n    })\n}\n```\n\n调用`defineReactive`，数据发生变化触发`update`方法，实现数据响应式\n\n```js\nconst obj = {}\ndefineReactive(obj, 'foo', '')\nsetTimeout(()=>{\n    obj.foo = new Date().toLocaleTimeString()\n},1000)\n```\n\n在对象存在多个`key`情况下，需要进行遍历\n\n```js\nfunction observe(obj) {\n    if (typeof obj !== 'object' || obj == null) {\n        return\n    }\n    Object.keys(obj).forEach(key => {\n        defineReactive(obj, key, obj[key])\n    })\n}\n```\n\n如果存在嵌套对象的情况，还需要在`defineReactive`中进行递归\n\n```js\nfunction defineReactive(obj, key, val) {\n    observe(val)\n    Object.defineProperty(obj, key, {\n        get() {\n            console.log(`get ${key}:${val}`);\n            return val\n        },\n        set(newVal) {\n            if (newVal !== val) {\n                val = newVal\n                update()\n            }\n        }\n    })\n}\n```\n\n当给`key`赋值为对象的时候，还需要在`set`属性中进行递归\n\n```js\nset(newVal) {\n    if (newVal !== val) {\n        observe(newVal) // 新值是对象的情况\n        notifyUpdate()\n    }\n}\n```\n\n上述例子能够实现对一个对象的基本响应式，但仍然存在诸多问题\n\n现在对一个对象进行删除与添加属性操作，无法劫持到\n\n```js\nconst obj = {\n    foo: \"foo\",\n    bar: \"bar\"\n}\nobserve(obj)\ndelete obj.foo // no ok\nobj.jar = 'xxx' // no ok\n```\n\n当我们对一个数组进行监听的时候，并不那么好使了\n\n```js\nconst arrData = [1,2,3,4,5];\narrData.forEach((val,index)=>{\n    defineProperty(arrData,index,val)\n})\narrData.push() // no ok\narrData.pop()  // no ok\narrDate[0] = 99 // ok\n```\n\n可以看到数据的`api`无法劫持到，从而无法实现数据响应式，\n\n所以在`Vue2`中，增加了`set`、`delete` API，并且对数组`api`方法进行一个重写\n\n还有一个问题则是，如果存在深层的嵌套对象关系，需要深层的进行监听，造成了性能的极大问题\n\n### 小结\n\n- 检测不到对象属性的添加和删除\n- 数组`API`方法无法监听到\n- 需要对每个属性进行遍历监听，如果嵌套对象，需要深层监听，造成性能问题\n\n\n\n## 二、proxy\n\n`Proxy`的监听是针对一个对象的，那么对这个对象的所有操作会进入监听操作，这就完全可以代理所有属性了\n\n在`ES6`系列中，我们详细讲解过`Proxy`的使用，就不再述说了\n\n下面通过代码进行展示：\n\n定义一个响应式方法`reactive`\n\n```js\nfunction reactive(obj) {\n    if (typeof obj !== 'object' && obj != null) {\n        return obj\n    }\n    // Proxy相当于在对象外层加拦截\n    const observed = new Proxy(obj, {\n        get(target, key, receiver) {\n            const res = Reflect.get(target, key, receiver)\n            console.log(`获取${key}:${res}`)\n            return res\n        },\n        set(target, key, value, receiver) {\n            const res = Reflect.set(target, key, value, receiver)\n            console.log(`设置${key}:${value}`)\n            return res\n        },\n        deleteProperty(target, key) {\n            const res = Reflect.deleteProperty(target, key)\n            console.log(`删除${key}:${res}`)\n            return res\n        }\n    })\n    return observed\n}\n```\n\n测试一下简单数据的操作，发现都能劫持\n\n```js\nconst state = reactive({\n    foo: 'foo'\n})\n// 1.获取\nstate.foo // ok\n// 2.设置已存在属性\nstate.foo = 'fooooooo' // ok\n// 3.设置不存在属性\nstate.dong = 'dong' // ok\n// 4.删除属性\ndelete state.dong // ok\n```\n\n再测试嵌套对象情况，这时候发现就不那么 OK 了\n\n```js\nconst state = reactive({\n    bar: { a: 1 }\n})\n\n// 设置嵌套对象属性\nstate.bar.a = 10 // no ok\n```\n\n如果要解决，需要在`get`之上再进行一层代理\n\n```js\nfunction reactive(obj) {\n    if (typeof obj !== 'object' && obj != null) {\n        return obj\n    }\n    // Proxy相当于在对象外层加拦截\n    const observed = new Proxy(obj, {\n        get(target, key, receiver) {\n            const res = Reflect.get(target, key, receiver)\n            console.log(`获取${key}:${res}`)\n            return isObject(res) ? reactive(res) : res\n        },\n    return observed\n}\n```\n\n\n## 三、总结\n\n`Object.defineProperty`只能遍历对象属性进行劫持\n\n```js\nfunction observe(obj) {\n    if (typeof obj !== 'object' || obj == null) {\n        return\n    }\n    Object.keys(obj).forEach(key => {\n        defineReactive(obj, key, obj[key])\n    })\n}\n```\n\n`Proxy`直接可以劫持整个对象，并返回一个新对象，我们可以只操作新的对象达到响应式目的\n\n```js\nfunction reactive(obj) {\n    if (typeof obj !== 'object' && obj != null) {\n        return obj\n    }\n    // Proxy相当于在对象外层加拦截\n    const observed = new Proxy(obj, {\n        get(target, key, receiver) {\n            const res = Reflect.get(target, key, receiver)\n            console.log(`获取${key}:${res}`)\n            return res\n        },\n        set(target, key, value, receiver) {\n            const res = Reflect.set(target, key, value, receiver)\n            console.log(`设置${key}:${value}`)\n            return res\n        },\n        deleteProperty(target, key) {\n            const res = Reflect.deleteProperty(target, key)\n            console.log(`删除${key}:${res}`)\n            return res\n        }\n    })\n    return observed\n}\n```\n\n`Proxy`可以直接监听数组的变化（`push`、`shift`、`splice`）\n\n```js\nconst obj = [1,2,3]\nconst proxtObj = reactive(obj)\nobj.psuh(4) // ok\n```\n\n`Proxy`有多达13种拦截方法,不限于`apply`、`ownKeys`、`deleteProperty`、`has`等等，这是`Object.defineProperty`不具备的\n\n正因为`defineProperty`自身的缺陷，导致`Vue2`在实现响应式过程需要实现其他的方法辅助（如重写数组方法、增加额外`set`、`delete`方法）\n\n```js\n// 数组重写\nconst originalProto = Array.prototype\nconst arrayProto = Object.create(originalProto)\n['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {\n  arrayProto[method] = function () {\n    originalProto[method].apply(this.arguments)\n    dep.notice()\n  }\n});\n\n// set、delete\nVue.set(obj,'bar','newbar')\nVue.delete(obj),'bar')\n```\n\n`Proxy` 不兼容IE，也没有 `polyfill`, `defineProperty` 能支持到IE9\n\n### 参考文献\n- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"
  },
  {
    "path": "docs/vue3/treeshaking.md",
    "content": "# 面试官：说说Vue 3.0中Treeshaking特性？举例说明一下？\n\n ![](https://static.vue-js.com/5e8bf1d0-6097-11eb-ab90-d9ae814b240d.png)\n\n## 一、是什么\n\n`Tree shaking` 是一种通过清除多余代码方式来优化项目打包体积的技术，专业术语叫 `Dead code elimination`\n\n简单来讲，就是在保持代码运行结果不变的前提下，去除无用的代码\n\n如果把代码打包比作制作蛋糕，传统的方式是把鸡蛋（带壳）全部丢进去搅拌，然后放入烤箱，最后把（没有用的）蛋壳全部挑选并剔除出去\n\n而` treeshaking `则是一开始就把有用的蛋白蛋黄（import）放入搅拌，最后直接作出蛋糕\n\n也就是说 ，`tree shaking` 其实是找出使用的代码\n\n在`Vue2`中，无论我们使用什么功能，它们最终都会出现在生产代码中。主要原因是`Vue`实例在项目中是单例的，捆绑程序无法检测到该对象的哪些属性在代码中被使用到\n\n```js\nimport Vue from 'vue'\n \nVue.nextTick(() => {})\n```\n\n而`Vue3`源码引入`tree shaking`特性，将全局 API 进行分块。如果您不使用其某些功能，它们将不会包含在您的基础包中\n\n```js\nimport { nextTick, observable } from 'vue'\n \nnextTick(() => {})\n```\n\n\n\n## 二、如何做\n\n`Tree shaking`是基于`ES6`模板语法（`import`与`exports`），主要是借助`ES6`模块的静态编译思想，在编译时就能确定模块的依赖关系，以及输入和输出的变量\n\n`Tree shaking`无非就是做了两件事：\n\n- 编译阶段利用`ES6 Module`判断哪些模块已经加载\n- 判断那些模块和变量未被使用或者引用，进而删除对应代码\n\n下面就来举个例子：\n\n通过脚手架`vue-cli`安装`Vue2`与`Vue3`项目\n\n```c\nvue create vue-demo\n```\n\n### Vue2 项目\n\n组件中使用`data`属性\n\n```vue\n<script>\n    export default {\n        data: () => ({\n            count: 1,\n        }),\n    };\n</script>\n```\n\n对项目进行打包，体积如下图\n\n![](https://static.vue-js.com/6bd2aff0-6097-11eb-85f6-6fac77c0c9b3.png)\n\n为组件设置其他属性（`compted`、`watch`）\n\n```js\nexport default {\n    data: () => ({\n        question:\"\", \n        count: 1,\n    }),\n    computed: {\n        double: function () {\n            return this.count * 2;\n        },\n    },\n    watch: {\n        question: function (newQuestion, oldQuestion) {\n            this.answer = 'xxxx'\n        }\n};\n```\n\n再一次打包，发现打包出来的体积并没有变化\n\n![](https://static.vue-js.com/7c29e260-6097-11eb-ab90-d9ae814b240d.png)\n\n\n\n### Vue3 项目\n\n组件中简单使用\n\n```js\nimport { reactive, defineComponent } from \"vue\";\nexport default defineComponent({\n  setup() {\n    const state = reactive({\n      count: 1,\n    });\n    return {\n      state,\n    };\n  },\n});\n```\n\n将项目进行打包\n\n![](https://static.vue-js.com/95df0000-6097-11eb-85f6-6fac77c0c9b3.png)\n\n在组件中引入`computed`和`watch`\n\n```js\nimport { reactive, defineComponent, computed, watch } from \"vue\";\nexport default defineComponent({\n  setup() {\n    const state = reactive({\n      count: 1,\n    });\n    const double = computed(() => {\n      return state.count * 2;\n    });\n\n    watch(\n      () => state.count,\n      (count, preCount) => {\n        console.log(count);\n        console.log(preCount);\n      }\n    );\n    return {\n      state,\n      double,\n    };\n  },\n});\n```\n\n再次对项目进行打包，可以看到在引入`computer`和`watch`之后，项目整体体积变大了\n\n ![](https://static.vue-js.com/b36a7a00-6097-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 三、作用\n\n通过`Tree shaking`，`Vue3`给我们带来的好处是：\n\n- 减少程序体积（更小）\n- 减少程序执行时间（更快）\n- 便于将来对程序架构进行优化（更友好）\n\n\n\n## 参考文献\n\n- https://segmentfault.com/a/1190000038962700"
  },
  {
    "path": "docs/webpack/HMR.md",
    "content": "# 面试官：说说webpack的热更新是如何做到的？原理是什么？\n\n ![](https://static.vue-js.com/a076da40-acd4-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n### 一、是什么\n\n`HMR `全称 `Hot Module Replacement`，可以理解为模块热替换，指在应用程序运行过程中，替换、添加、删除模块，而无需重新刷新整个应用\n\n例如，我们在应用运行过程中修改了某个模块，通过自动刷新会导致整个应用的整体刷新，那页面中的状态信息都会丢失\n\n如果使用的是 `HMR`，就可以实现只将修改的模块实时替换至应用中，不必完全刷新整个应用\n\n在`webpack`中配置开启热模块也非常的简单，如下代码：\n\n```js\nconst webpack = require('webpack')\nmodule.exports = {\n  // ...\n  devServer: {\n    // 开启 HMR 特性\n    hot: true\n    // hotOnly: true\n  }\n}\n```\n\n通过上述这种配置，如果我们修改并保存`css`文件，确实能够以不刷新的形式更新到页面中\n\n但是，当我们修改并保存`js`文件之后，页面依旧自动刷新了，这里并没有触发热模块\n\n所以，`HMR `并不像 `Webpack` 的其他特性一样可以开箱即用，需要有一些额外的操作\n\n我们需要去指定哪些模块发生更新时进行`HRM`，如下代码：\n\n```js\nif(module.hot){\n    module.hot.accept('./util.js',()=>{\n        console.log(\"util.js更新了\")\n    })\n}\n```\n\n\n\n## 二、实现原理\n\n首先来看看一张图，如下：\n\n ![](https://static.vue-js.com/adc05780-acd4-11eb-ab90-d9ae814b240d.png)\n\n- Webpack Compile：将 JS 源代码编译成 bundle.js\n- HMR Server：用来将热更新的文件输出给 HMR Runtime\n- Bundle Server：静态资源文件服务器，提供文件访问路径\n- HMR Runtime：socket服务器，会被注入到浏览器，更新文件的变化\n- bundle.js：构建输出的文件\n- 在HMR Runtime 和 HMR Server之间建立 websocket，即图上4号线，用于实时更新文件变化\n\n上面图中，可以分成两个阶段：\n\n- 启动阶段为上图 1 - 2 - A - B\n\n在编写未经过`webpack`打包的源代码后，`Webpack Compile` 将源代码和 `HMR Runtime` 一起编译成 `bundle `文件，传输给` Bundle Server` 静态资源服务器\n\n- 更新阶段为上图 1 - 2 - 3 - 4\n\n当某一个文件或者模块发生变化时，`webpack `监听到文件变化对文件重新编译打包，编译生成唯一的` hash `值，这个`hash `值用来作为下一次热更新的标识\n\n根据变化的内容生成两个补丁文件：`manifest`（包含了 `hash` 和 `chundId `，用来说明变化的内容）和` chunk.js` 模块\n\n由于`socket`服务器在`HMR Runtime` 和 `HMR Server`之间建立 `websocket`链接，当文件发生改动的时候，服务端会向浏览器推送一条消息，消息包含文件改动后生成的`hash`值，如下图的`h`属性，作为下一次热更细的标识\n\n ![](https://static.vue-js.com/05a0edf0-ad4a-11eb-85f6-6fac77c0c9b3.png)\n\n在浏览器接受到这条消息之前，浏览器已经在上一次` socket` 消息中已经记住了此时的` hash` 标识，这时候我们会创建一个 `ajax` 去服务端请求获取到变化内容的 `manifest` 文件\n\n`mainfest`文件包含重新`build`生成的`hash`值，以及变化的模块，对应上图的`c`属性\n\n浏览器根据 `manifest` 文件获取模块变化的内容，从而触发`render`流程，实现局部模块更新\n\n ![](https://static.vue-js.com/0e7b7850-ad4a-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 三、总结\n\n关于`webpack`热模块更新的总结如下：\n\n- 通过`webpack-dev-server`创建两个服务器：提供静态资源的服务（express）和Socket服务\n- express server 负责直接提供静态资源的服务（打包后的资源直接被浏览器请求和解析）\n- socket server 是一个 websocket 的长连接，双方可以通信\n- 当 socket server 监听到对应的模块发生变化时，会生成两个文件.json（manifest文件）和.js文件（update chunk）\n- 通过长连接，socket server 可以直接将这两个文件主动发送给客户端（浏览器）\n- 浏览器拿到两个新的文件后，通过HMR runtime机制，加载这两个文件，并且针对修改的模块进行更新\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/138446061\n- https://github.com/Jocs/jocs.github.io/issues/15\n- https://juejin.cn/post/6844904134697549832\n- https://vue3js.cn/interview/"
  },
  {
    "path": "docs/webpack/Loader.md",
    "content": "# 面试官：说说webpack中常见的Loader？解决了什么问题？\n\n ![](https://static.vue-js.com/5660fc40-a6ff-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、是什么\n`loader` 用于对模块的\"源代码\"进行转换，在 `import` 或\"加载\"模块时预处理文件\n\n`webpack`做的事情，仅仅是分析出各种模块的依赖关系，然后形成资源列表，最终打包生成到指定的文件中。如下图所示：\n\n ![](https://static.vue-js.com/7b8d9640-a6ff-11eb-ab90-d9ae814b240d.png)\n\n在`webpack`内部中，任何文件都是模块，不仅仅只是`js`文件\n\n默认情况下，在遇到`import`或者`require`加载模块的时候，`webpack`只支持对`js` 和 `json` 文件打包\n\n像`css`、`sass`、`png`等这些类型的文件的时候，`webpack`则无能为力，这时候就需要配置对应的`loader`进行文件内容的解析\n\n在加载模块的时候，执行顺序如下：\n\n ![](https://static.vue-js.com/9c2c43b0-a6ff-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n当 `webpack` 碰到不识别的模块的时候，`webpack` 会在配置的中查找该文件解析规则\n\n\n\n关于配置`loader`的方式有三种：\n\n- 配置方式（推荐）：在 webpack.config.js文件中指定 loader\n- 内联方式：在每个 import 语句中显式指定 loader\n- CLI 方式：在 shell 命令中指定它们\n\n\n### 配置方式\n\n关于`loader`的配置，我们是写在`module.rules`属性中，属性介绍如下：\n\n- `rules`是一个数组的形式，因此我们可以配置很多个`loader`\n\n- 每一个`loader`对应一个对象的形式，对象属性`test` 为匹配的规则，一般情况为正则表达式\n\n- 属性`use`针对匹配到文件类型，调用对应的 `loader` 进行处理\n\n代码编写，如下形式：\n\n```js\nmodule.exports = {\n  module: {\n    rules: [\n      {\n        test: /\\.css$/,\n        use: [\n          { loader: 'style-loader' },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true\n            }\n          },\n          { loader: 'sass-loader' }\n        ]\n      }\n    ]\n  }\n};\n```\n\n\n\n## 二、特性\n\n这里继续拿上述代码，来讲讲`loader`的特性\n\n从上述代码可以看到，在处理`css`模块的时候，`use`属性中配置了三个`loader`分别处理`css`文件\n\n因为`loader `支持链式调用，链中的每个` loader `会处理之前已处理过的资源，最终变为`js`代码。顺序为相反的顺序执行，即上述执行方式为`sass-loader`、`css-loader`、`style-loader`\n\n除此之外，`loader`的特性还有如下：\n\n- loader 可以是同步的，也可以是异步的\n- loader 运行在 Node.js 中，并且能够执行任何操作\n- 除了常见的通过 `package.json` 的 `main` 来将一个 npm 模块导出为 loader，还可以在 module.rules 中使用 `loader` 字段直接引用一个模块\n- 插件(plugin)可以为 loader 带来更多特性\n- loader 能够产生额外的任意文件\n\n可以通过 loader 的预处理函数，为 JavaScript 生态系统提供更多能力。用户现在可以更加灵活地引入细粒度逻辑，例如：压缩、打包、语言翻译和更多其他特性\n\n\n\n\n\n## 三、常见的loader\n\n在页面开发过程中，我们经常性加载除了`js`文件以外的内容，这时候我们就需要配置响应的`loader`进行加载\n\n常见的`loader`如下：\n\n- style-loader: 将css添加到DOM的内联样式标签style里\n- css-loader :允许将css文件通过require的方式引入，并返回css代码\n- less-loader: 处理less\n- sass-loader: 处理sass\n- postcss-loader: 用postcss来处理CSS\n- autoprefixer-loader: 处理CSS3属性前缀，已被弃用，建议直接使用postcss\n- file-loader: 分发文件到output目录并返回相对路径\n- url-loader: 和file-loader类似，但是当文件小于设定的limit时可以返回一个Data Url\n- html-minify-loader: 压缩HTML\n- babel-loader :用babel来转换ES6文件到ES\n\n\n\n下面给出一些常见的`loader`的使用：\n\n### css-loader\n\n分析 `css` 模块之间的关系，并合成⼀个 `css`\n\n```bash\nnpm install --save-dev css-loader\n```\n\n```js\nrules: [\n  ...,\n {\n  test: /\\.css$/,\n    use: {\n      loader: \"css-loader\",\n      options: {\n     // 启用/禁用 url() 处理\n     url: true,\n     // 启用/禁用 @import 处理\n     import: true,\n        // 启用/禁用 Sourcemap\n        sourceMap: false\n      }\n    }\n }\n]\n```\n\n如果只通过`css-loader`加载文件，这时候页面代码设置的样式并没有生效\n\n原因在于，`css-loader`只是负责将`.css`文件进行一个解析，而并不会将解析后的`css`插入到页面中\n\n如果我们希望再完成插入`style`的操作，那么我们还需要另外一个`loader`，就是`style-loader`\n\n\n\n### style-loader\n\n把 `css-loader` 生成的内容，用 `style` 标签挂载到页面的 `head` 中\n\n```bash\nnpm install --save-dev style-loader\n```\n\n```js\nrules: [\n  ...,\n {\n  test: /\\.css$/,\n    use: [\"style-loader\", \"css-loader\"]\n }\n]\n```\n\n同一个任务的 `loader` 可以同时挂载多个，处理顺序为：从右到左，从下往上\n\n\n\n\n\n### less-loader\n\n开发中，我们也常常会使用`less`、`sass`、`stylus`预处理器编写`css`样式，使开发效率提高，这里需要使用`less-loader`\n\n```cmd\nnpm install less-loader -D\n```\n\n```js\nrules: [\n  ...,\n {\n  test: /\\.css$/,\n    use: [\"style-loader\", \"css-loader\",\"less-loader\"]\n }\n]\n```\n\n\n\n### raw-loader\n\n在 `webpack `中通过 `import `方式导入文件内容，该`loader `并不是内置的，所以首先要安装\n\n```bash\nnpm install --save-dev raw-loader\n```\n\n然后在 webpack.config.js 中进行配置\n\n```javascript\nmodule.exports = {\n  ...,\n  module: {\n      rules: [\n      {\n        test: /\\.(txt|md)$/,\n        use: 'raw-loader'\n     }\n    ]\n }\n}\n```\n\n\n\n### file-loader\n\n把识别出的资源模块，移动到指定的输出⽬目录，并且返回这个资源在输出目录的地址(字符串)\n\n```bash\nnpm install --save-dev file-loader\n```\n\n```javascript\nrules: [\n  ...,\n {\n  test: /\\.(png|jpe?g|gif)$/,\n    use: {\n      loader: \"file-loader\",\n      options: {\n        // placeholder 占位符 [name] 源资源模块的名称\n        // [ext] 源资源模块的后缀\n        name: \"[name]_[hash].[ext]\",\n        //打包后的存放位置\n        outputPath: \"./images\",\n        // 打包后文件的 url\n        publicPath: './images',\n      }\n    }\n }\n]\n```\n\n\n\n### url-loader\n\n可以处理理 `file-loader` 所有的事情，但是遇到图片格式的模块，可以选择性的把图片转成 `base64`  格式的字符串，并打包到 `js` 中，对小体积的图片比较合适，大图片不合适。\n\n```bash\nnpm install --save-dev url-loader\n```\n\n```javascript\nrules: [\n  ...,\n {\n  test: /\\.(png|jpe?g|gif)$/,\n    use: {\n      loader: \"url-loader\",\n      options: {\n        // placeholder 占位符 [name] 源资源模块的名称\n        // [ext] 源资源模块的后缀\n        name: \"[name]_[hash].[ext]\",\n        //打包后的存放位置\n        outputPath: \"./images\"\n        // 打包后文件的 url\n        publicPath: './images',\n        // 小于 100 字节转成 base64 格式\n        limit: 100\n      }\n    }\n }\n]\n```\n\n\n## 参考文献\n\n- https://webpack.docschina.org/concepts/loaders/\n- https://segmentfault.com/a/1190000018680530\n- https://vue3js.cn/interview/\n"
  },
  {
    "path": "docs/webpack/Loader_Plugin.md",
    "content": "# 面试官：说说Loader和Plugin的区别？编写Loader，Plugin的思路？\n\n![](https://static.vue-js.com/93042280-a894-11eb-ab90-d9ae814b240d.png)\n\n## 一、区别\n\n前面两节我们有提到`Loader`与`Plugin`对应的概念，先来回顾下\n\n- loader 是文件加载器，能够加载资源文件，并对这些文件进行一些处理，诸如编译、压缩等，最终一起打包到指定的文件中\n- plugin 赋予了 webpack 各种灵活的功能，例如打包优化、资源管理、环境变量注入等，目的是解决 loader 无法实现的其他事\n\n从整个运行时机上来看，如下图所示：\n\n![](https://static.vue-js.com/9a04ec40-a7c2-11eb-ab90-d9ae814b240d.png)\n\n可以看到，两者在运行时机上的区别：\n\n-  loader 运行在打包文件之前\n-  plugins 在整个编译周期都起作用\n\n在` Webpack` 运行的生命周期中会广播出许多事件，`Plugin` 可以监听这些事件，在合适的时机通过` Webpack `提供的 `API `改变输出结果\n\n对于`loader`，实质是一个转换器，将A文件进行编译形成B文件，操作的是文件，比如将`A.scss`或`A.less`转变为`B.css`，单纯的文件转换过程\n\n\n\n## 二、编写loader\n\n在编写 `loader` 前，我们首先需要了解 `loader` 的本质\n\n其本质为函数，函数中的 `this` 作为上下文会被 `webpack` 填充，因此我们不能将 `loader`设为一个箭头函数\n\n函数接受一个参数，为 `webpack` 传递给 `loader` 的文件源内容\n\n函数中 `this` 是由 `webpack` 提供的对象，能够获取当前 `loader` 所需要的各种信息\n\n函数中有异步操作或同步操作，异步操作通过 `this.callback` 返回，返回值要求为 `string` 或者 `Buffer`\n\n代码如下所示：\n\n```js\n// 导出一个函数，source为webpack传递给loader的文件源内容\nmodule.exports = function(source) {\n    const content = doSomeThing2JsString(source);\n    \n    // 如果 loader 配置了 options 对象，那么this.query将指向 options\n    const options = this.query;\n    \n    // 可以用作解析其他模块路径的上下文\n    console.log('this.context');\n    \n    /*\n     * this.callback 参数：\n     * error：Error | null，当 loader 出错时向外抛出一个 error\n     * content：String | Buffer，经过 loader 编译后需要导出的内容\n     * sourceMap：为方便调试生成的编译后内容的 source map\n     * ast：本次编译生成的 AST 静态语法树，之后执行的 loader 可以直接使用这个 AST，进而省去重复生成 AST 的过程\n     */\n    this.callback(null, content); // 异步\n    return content; // 同步\n}\n```\n\n一般在编写`loader`的过程中，保持功能单一，避免做多种功能\n\n如` less `文件转换成 `css `文件也不是一步到位，而是 `less-loader`、`css-loader`、` style-loader `几个 `loader `的链式调用才能完成转换\n\n\n\n## 三、编写plugin\n\n由于`webpack`基于发布订阅模式，在运行的生命周期中会广播出许多事件，插件通过监听这些事件，就可以在特定的阶段执行自己的插件任务\n\n在之前也了解过，`webpack`编译会创建两个核心对象：\n\n- compiler：包含了 webpack 环境的所有的配置信息，包括 options，loader 和 plugin，和 webpack 整个生命周期相关的钩子\n- compilation：作为 plugin 内置事件回调函数的参数，包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化，一次新的 Compilation 将被创建\n\n如果自己要实现`plugin`，也需要遵循一定的规范：\n\n- 插件必须是一个函数或者是一个包含 `apply` 方法的对象，这样才能访问`compiler`实例\n- 传给每个插件的 `compiler` 和 `compilation` 对象都是同一个引用，因此不建议修改\n- 异步的事件需要在插件处理完任务时调用回调函数通知 `Webpack` 进入下一个流程，不然会卡住\n\n\n\n实现`plugin`的模板如下：\n\n```js\nclass MyPlugin {\n    // Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象\n  apply (compiler) {\n    // 找到合适的事件钩子，实现自己的插件功能\n    compiler.hooks.emit.tap('MyPlugin', compilation => {\n        // compilation: 当前打包构建流程的上下文\n        console.log(compilation);\n        \n        // do something...\n    })\n  }\n}\n```\n\n在 `emit` 事件发生时，代表源文件的转换和组装已经完成，可以读取到最终将输出的资源、代码块、模块及其依赖，并且可以修改输出资源的内容\n\n\n## 参考文献\n\n- https://webpack.docschina.org/api/loaders/\n- https://webpack.docschina.org/api/compiler-hooks/\n- https://segmentfault.com/a/1190000039877943\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/webpack/Plugin.md",
    "content": "# 面试官：说说webpack中常见的Plugin？解决了什么问题？\n\n ![](https://static.vue-js.com/8d3978a0-a7c2-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n`Plugin`（Plug-in）是一种计算机应用程序，它和主应用程序互相交互，以提供特定的功能\n\n是一种遵循一定规范的应用程序接口编写出来的程序，只能运行在程序规定的系统下，因为其需要调用原纯净系统提供的函数库或者数据\n\n`webpack`中的`plugin`也是如此，`plugin`赋予其各种灵活的功能，例如打包优化、资源管理、环境变量注入等，它们会运行在 `webpack` 的不同阶段（钩子 / 生命周期），贯穿了`webpack`整个编译周期\n\n![](https://static.vue-js.com/9a04ec40-a7c2-11eb-ab90-d9ae814b240d.png)\n\n目的在于解决`loader` 无法实现的其他事\n\n### 配置方式\n\n这里讲述文件的配置方式，一般情况，通过配置文件导出对象中`plugins`属性传入`new`实例对象。如下所示：\n\n```js\nconst HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装\nconst webpack = require('webpack'); // 访问内置的插件\nmodule.exports = {\n  ...\n  plugins: [\n    new webpack.ProgressPlugin(),\n    new HtmlWebpackPlugin({ template: './src/index.html' }),\n  ],\n};\n```\n\n\n\n## 二、特性\n\n其本质是一个具有`apply`方法`javascript`对象\n\n`apply` 方法会被 `webpack compiler `调用，并且在整个编译生命周期都可以访问 `compiler `对象\n\n```javascript\nconst pluginName = 'ConsoleLogOnBuildWebpackPlugin';\n\nclass ConsoleLogOnBuildWebpackPlugin {\n  apply(compiler) {\n    compiler.hooks.run.tap(pluginName, (compilation) => {\n      console.log('webpack 构建过程开始！');\n    });\n  }\n}\n\nmodule.exports = ConsoleLogOnBuildWebpackPlugin;\n```\n\n`compiler hook` 的 `tap `方法的第一个参数，应是驼峰式命名的插件名称\n\n关于整个编译生命周期钩子，有如下：\n\n- entry-option ：初始化 option\n- run\n- compile： 真正开始的编译，在创建 compilation 对象之前\n- compilation ：生成好了 compilation 对象\n- make 从 entry 开始递归分析依赖，准备对每个模块进行 build\n- after-compile： 编译 build 过程结束\n- emit ：在将内存中 assets 内容写到磁盘文件夹之前\n- after-emit ：在将内存中 assets 内容写到磁盘文件夹之后\n- done： 完成所有的编译过程\n- failed： 编译失败的时候\n\n\n\n## 三、常见的Plugin\n\n常见的`plugin`有如图所示：\n\n![](https://static.vue-js.com/bd749400-a7c2-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n下面介绍几个常用的插件用法：\n\n### HtmlWebpackPlugin\n\n在打包结束后，⾃动生成⼀个 `html` ⽂文件，并把打包生成的` js` 模块引⼊到该 `html` 中\n\n```bash\nnpm install --save-dev html-webpack-plugin\n```\n\n```js\n// webpack.config.js\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\nmodule.exports = {\n ...\n  plugins: [\n     new HtmlWebpackPlugin({\n       title: \"My App\",\n       filename: \"app.html\",\n       template: \"./src/html/index.html\"\n     }) \n  ]\n};\n```\n\n```html\n<!--./src/html/index.html-->\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title><%=htmlWebpackPlugin.options.title%></title>\n</head>\n<body>\n    <h1>html-webpack-plugin</h1>\n</body>\n</html>\n```\n\n在 `html` 模板中，可以通过 `<%=htmlWebpackPlugin.options.XXX%>` 的方式获取配置的值\n\n更多的配置可以自寻查找\n\n\n\n### clean-webpack-plugin\n\n删除（清理）构建目录\n\n```bash\nnpm install --save-dev clean-webpack-plugin\n```\n\n```js\nconst {CleanWebpackPlugin} = require('clean-webpack-plugin');\nmodule.exports = {\n ...\n  plugins: [\n    ...,\n    new CleanWebpackPlugin(),\n    ...\n  ]\n}\n```\n\n\n\n### mini-css-extract-plugin\n\n提取 `CSS` 到一个单独的文件中\n\n```bash\nnpm install --save-dev mini-css-extract-plugin\n```\n\n```js\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin');\nmodule.exports = {\n ...,\n  module: {\n   rules: [\n    {\n     test: /\\.s[ac]ss$/,\n     use: [\n      {\n       loader: MiniCssExtractPlugin.loader\n     },\n          'css-loader',\n          'sass-loader'\n        ]\n   }\n   ]\n },\n  plugins: [\n    ...,\n    new MiniCssExtractPlugin({\n     filename: '[name].css'\n    }),\n    ...\n  ]\n}\n```\n\n\n\n### DefinePlugin\n\n允许在编译时创建配置的全局对象，是一个`webpack`内置的插件，不需要安装\n\n```js\nconst { DefinePlugun } = require('webpack')\n\nmodule.exports = {\n ...\n    plugins:[\n        new DefinePlugin({\n            BASE_URL:'\"./\"'\n        })\n    ]\n}\n```\n\n这时候编译`template`模块的时候，就能通过下述形式获取全局对象\n\n```html\n<link rel=\"icon\" href=\"<%= BASE_URL%>favicon.ico>\"\n```\n\n\n\n\n\n### copy-webpack-plugin\n\n复制文件或目录到执行区域，如`vue`的打包过程中，如果我们将一些文件放到`public`的目录下，那么这个目录会被复制到`dist`文件夹中\n\n```cmd\nnpm install copy-webpack-plugin -D\n```\n\n```js\nnew CopyWebpackPlugin({\n    parrerns:[\n        {\n            from:\"public\",\n            globOptions:{\n                ignore:[\n                    '**/index.html'\n                ]\n            }\n        }\n    ]\n})\n```\n\n复制的规则在`patterns`属性中设置：\n\n- from：设置从哪一个源中开始复制\n- to：复制到的位置，可以省略，会默认复制到打包的目录下\n\n- globOptions：设置一些额外的选项，其中可以编写需要忽略的文件\n\n\n\n## 参考文献\n- https://webpack.docschina.org/concepts/plugins/\n- https://baike.baidu.com/item/Plugin\n- https://segmentfault.com/a/1190000018695134\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/webpack/Rollup_Parcel_snowpack_Vite.md",
    "content": "# 面试官：与webpack类似的工具还有哪些？区别？\n\n![](https://static.vue-js.com/8ed8d520-b1a4-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、模块化工具\n模块化是一种处理复杂系统分解为更好的可管理模块的方式\n\n可以用来分割，组织和打包应用。每个模块完成一个特定的子功能，所有的模块按某种方法组装起来，成为一个整体(`bundle`)\n\n在前端领域中，并非只有`webpack`这一款优秀的模块打包工具，还有其他类似的工具，例如`Rollup`、`Parcel`、`snowpack`，以及最近风头无两的`Vite`\n\n通过这些模块打包工具，能够提高我们的开发效率，减少开发成本\n\n这里没有提及`gulp`、`grunt`是因为它们只是定义为构建工具，不能类比\n\n### Rollup\n\n`Rollup` 是一款 `ES Modules` 打包器，从作用上来看，`Rollup` 与 `Webpack` 非常类似。不过相比于 `Webpack`，`Rollup `要小巧的多\n\n现在很多我们熟知的库都都使用它进行打包，比如：`Vue`、`React`和`three.js`等\n\n举个例子：\n\n```js\n// ./src/messages.js\nexport default {\n  hi: 'Hey Guys, I am zce~'\n}\n\n// ./src/logger.js\nexport const log = msg => {\n  console.log('---------- INFO ----------')\n  console.log(msg)\n  console.log('--------------------------')\n}\n\nexport const error = msg => {\n  console.error('---------- ERROR ----------')\n  console.error(msg)\n  console.error('---------------------------')\n}\n\n// ./src/index.js\nimport { log } from './logger'\nimport messages from './messages'\nlog(messages.hi)\n```\n\n然后通过`rollup`进行打包\n\n```js\n$ npx rollup ./src/index.js --file ./dist/bundle.js\n```\n\n打包结果如下图![](https://static.vue-js.com/8fe07830-b143-11eb-85f6-6fac77c0c9b3.png)\n\n可以看到，代码非常简洁，完成不像`webpack`那样存在大量引导代码和模块函数\n\n并且`error`方法由于没有被使用，输出的结果中并无`error`方法，可以看到，`rollup`默认开始`Tree-shaking` 优化输出结果\n\n因此，可以看到`Rollup`的优点：\n\n- 代码效率更简洁、效率更高\n- 默认支持 Tree-shaking\n\n但缺点也十分明显，加载其他类型的资源文件或者支持导入 `CommonJS` 模块，又或是编译 `ES` 新特性，这些额外的需求 `Rollup `需要使用插件去完成\n\n综合来看，`rollup`并不适合开发应用使用，因为需要使用第三方模块，而目前第三方模块大多数使用`CommonJs`方式导出成员，并且`rollup`不支持`HMR`，使开发效率降低\n\n但是在用于打包` JavaScript` 库时，`rollup`比 `webpack` 更有优势，因为其打包出来的代码更小、更快，其存在的缺点可以忽略\n\n\n\n\n\n### Parcel\n\nParcel ，是一款完全零配置的前端打包器，它提供了 “傻瓜式” 的使用体验，只需了解简单的命令，就能构建前端应用程序\n\n`Parcel` 跟 `Webpack` 一样都支持以任意类型文件作为打包入口，但建议使用`HTML`文件作为入口，该`HTML`文件像平时一样正常编写代码、引用资源。如下所示：\n\n```js\n<!-- ./src/index.html -->\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Parcel Tutorials</title>\n</head>\n<body>\n  <script src=\"main.js\"></script>\n</body>\n</html>\n```\n\nmain.js文件通过`ES Moudle`方法导入其他模块成员\n\n```js\n// ./src/main.js\nimport { log } from './logger'\nlog('hello parcel')\n// ./src/logger.js\nexport const log = msg => {\n  console.log('---------- INFO ----------')\n  console.log(msg)\n}\n```\n\n运行之后，使用命令打包\n\n```cmd\nnpx parcel src/index.html\n```\n\n执行命令后，`Parcel`不仅打包了应用，同时也启动了一个开发服务器，跟`webpack Dev Server`一样\n\n跟`webpack`类似，也支持模块热替换，但用法更简单\n\n同时，`Parcel`有个十分好用的功能：支持自动安装依赖，像`webpack`开发阶段突然使用安装某个第三方依赖，必然会终止`dev server`然后安装再启动。而`Parcel`则免了这繁琐的工作流程\n\n同时，`Parcel`能够零配置加载其他类型的资源文件，无须像`webpack`那样配置对应的`loader`\n\n打包命令如下：\n\n```cmd\nnpx parcel src/index.html\n```\n\n由于打包过程是多进程同时工作，构建速度会比`Webpack` 快，输出文件也会被压缩，并且样式代码也会被单独提取到单个文件中\n\n![](https://static.vue-js.com/ec17e7a0-b1a2-11eb-85f6-6fac77c0c9b3.png)\n\n可以感受到，`Parcel `给开发者一种很大的自由度，只管去实现业务代码，其他事情用`Parcel`解决\n\n\n\n### Snowpack\n\nSnowpack，是一种闪电般快速的前端构建工具，专为现代`Web`设计，较复杂的打包工具（如`Webpack`或`Parcel`）的替代方案，利用`JavaScript`的本机模块系统，避免不必要的工作并保持流畅的开发体验\n\n开发阶段，每次保存单个文件时，`Webpack`和`Parcel`都需要重新构建和重新打包应用程序的整个`bundle`。而`Snowpack`为你的应用程序每个文件构建一次，就可以永久缓存，文件更改时，`Snowpack`会重新构建该单个文件\n\n下图给出`webpack`与`snowpack`打包区别：\n\n ![](https://static.vue-js.com/79197830-b1a3-11eb-85f6-6fac77c0c9b3.png)\n\n在重新构建每次变更时没有任何的时间浪费，只需要在浏览器中进行HMR更新\n\n### Vite\n\nvite ，是一种新型前端构建工具，能够显著提升前端开发体验\n\n它主要由两部分组成：\n\n- 一个开发服务器，它基于 原生 ES 模块 提供了丰富的内建功能，如速度快到惊人的 [模块热更新HMR\n- 一套构建指令，它使用 Rollup打包你的代码，并且它是预配置的，可以输出用于生产环境的优化过的静态资源\n\n其作用类似`webpack `+ `webpack-dev-server`，其特点如下：\n\n- 快速的冷启动\n- 即时的模块热更新\n- 真正的按需编译\n\n`vite`会直接启动开发服务器，不需要进行打包操作，也就意味着不需要分析模块的依赖、不需要编译，因此启动速度非常快\n\n利用现代浏览器支持`ES Module`的特性，当浏览器请求某个模块的时候，再根据需要对模块的内容进行编译，这种方式大大缩短了编译时间\n\n原理图如下所示：\n\n ![](https://static.vue-js.com/9f2eed30-b143-11eb-85f6-6fac77c0c9b3.png)\n\n在热模块`HMR`方面，当修改一个模块的时候，仅需让浏览器重新请求该模块即可，无须像`webpack`那样需要把该模块的相关依赖模块全部编译一次，效率更高\n\n\n\n### webpack\n\n相比上述的模块化工具，`webpack`大而全，很多常用的功能做到开箱即用。有两大最核心的特点：**一切皆模块**和**按需加载**\n\n与其他构建工具相比，有如下优势：\n\n- 智能解析：对 CommonJS 、 AMD 、ES6 的语法做了兼容\n- 万物模块：对 js、css、图片等资源文件都支持打包\n- 开箱即用：HRM、Tree-shaking等功能\n- 代码分割：可以将代码切割成不同的 chunk，实现按需加载，降低了初始化时间\n- 插件系统，具有强大的 Plugin 接口，具有更好的灵活性和扩展性\n- 易于调试：支持 SourceUrls 和 SourceMaps\n- 快速运行：webpack 使用异步 IO 并具有多级缓存，这使得 webpack 很快且在增量编译上更加快\n- 生态环境好：社区更丰富，出现的问题更容易解决\n\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/95684686\n- https://cn.vitejs.dev/guide/\n- https://segmentfault.com/a/1190000039370642"
  },
  {
    "path": "docs/webpack/build_process.md",
    "content": "# 面试官：说说webpack的构建流程?\n\n![](https://static.vue-js.com/96cf6840-a658-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、运行流程\n\n`webpack` 的运行流程是一个串行的过程，它的工作流程就是将各个插件串联起来\n\n在运行过程中会广播事件，插件只需要监听它所关心的事件，就能加入到这条`webpack`机制中，去改变`webpack`的运作，使得整个系统扩展性良好\n\n从启动到结束会依次执行以下三大步骤：\n\n- 初始化流程：从配置文件和 `Shell` 语句中读取与合并参数，并初始化需要使用的插件和配置插件等执行环境所需要的参数\n- 编译构建流程：从 Entry 发出，针对每个 Module 串行调用对应的 Loader 去翻译文件内容，再找到该 Module 依赖的 Module，递归地进行编译处理\n- 输出流程：对编译后的 Module 组合成 Chunk，把 Chunk 转换成文件，输出到文件系统\n\n ![](https://static.vue-js.com/b566d400-a658-11eb-85f6-6fac77c0c9b3.png)\n\n\n### 初始化流程\n\n从配置文件和 `Shell` 语句中读取与合并参数，得出最终的参数\n\n配置文件默认下为`webpack.config.js`，也或者通过命令的形式指定配置文件，主要作用是用于激活`webpack`的加载项和插件\n\n关于文件配置内容分析，如下注释：\n\n```js\nvar path = require('path');\nvar node_modules = path.resolve(__dirname, 'node_modules');\nvar pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');\n\nmodule.exports = {\n  // 入口文件，是模块构建的起点，同时每一个入口文件对应最后生成的一个 chunk。\n  entry: './path/to/my/entry/file.js'，\n  // 文件路径指向(可加快打包过程)。\n  resolve: {\n    alias: {\n      'react': pathToReact\n    }\n  },\n  // 生成文件，是模块构建的终点，包括输出文件与输出路径。\n  output: {\n    path: path.resolve(__dirname, 'build'),\n    filename: '[name].js'\n  },\n  // 这里配置了处理各模块的 loader ，包括 css 预处理 loader ，es6 编译 loader，图片处理 loader。\n  module: {\n    loaders: [\n      {\n        test: /\\.js$/,\n        loader: 'babel',\n        query: {\n          presets: ['es2015', 'react']\n        }\n      }\n    ],\n    noParse: [pathToReact]\n  },\n  // webpack 各插件对象，在 webpack 的事件流中执行对应的方法。\n  plugins: [\n    new webpack.HotModuleReplacementPlugin()\n  ]\n};\n```\n\n`webpack` 将 `webpack.config.js` 中的各个配置项拷贝到 `options` 对象中，并加载用户配置的 ` plugins`\n\n完成上述步骤之后，则开始初始化`Compiler`编译对象，该对象掌控者`webpack`声明周期，不执行具体的任务，只是进行一些调度工作\n\n```js\nclass Compiler extends Tapable {\n    constructor(context) {\n        super();\n        this.hooks = {\n            beforeCompile: new AsyncSeriesHook([\"params\"]),\n            compile: new SyncHook([\"params\"]),\n            afterCompile: new AsyncSeriesHook([\"compilation\"]),\n            make: new AsyncParallelHook([\"compilation\"]),\n            entryOption: new SyncBailHook([\"context\", \"entry\"])\n            // 定义了很多不同类型的钩子\n        };\n        // ...\n    }\n}\n\nfunction webpack(options) {\n  var compiler = new Compiler();\n  ...// 检查options,若watch字段为true,则开启watch线程\n  return compiler;\n}\n...\n```\n\n`Compiler` 对象继承自 `Tapable`，初始化时定义了很多钩子函数\n\n\n\n### 编译构建流程\n\n根据配置中的 `entry` 找出所有的入口文件\n\n```js\nmodule.exports = {\n  entry: './src/file.js'\n}\n```\n\n初始化完成后会调用`Compiler`的`run`来真正启动`webpack`编译构建流程，主要流程如下：\n\n- `compile` 开始编译\n- `make` 从入口点分析模块及其依赖的模块，创建这些模块对象\n- `build-module` 构建模块\n- `seal` 封装构建结果\n- `emit` 把各个chunk输出到结果文件\n\n\n\n#### compile 编译\n\n执行了`run`方法后，首先会触发`compile`，主要是构建一个`Compilation`对象\n\n该对象是编译阶段的主要执行者，主要会依次下述流程：执行模块创建、依赖收集、分块、打包等主要任务的对象\n\n\n\n#### make 编译模块\n\n当完成了上述的`compilation`对象后，就开始从`Entry`入口文件开始读取，主要执行`_addModuleChain()`函数，如下：\n\n```js\n_addModuleChain(context, dependency, onModule, callback) {\n   ...\n   // 根据依赖查找对应的工厂函数\n   const Dep = /** @type {DepConstructor} */ (dependency.constructor);\n   const moduleFactory = this.dependencyFactories.get(Dep);\n   \n   // 调用工厂函数NormalModuleFactory的create来生成一个空的NormalModule对象\n   moduleFactory.create({\n       dependencies: [dependency]\n       ...\n   }, (err, module) => {\n       ...\n       const afterBuild = () => {\n        this.processModuleDependencies(module, err => {\n         if (err) return callback(err);\n         callback(null, module);\n           });\n    };\n       \n       this.buildModule(module, false, null, null, err => {\n           ...\n           afterBuild();\n       })\n   })\n}\n```\n\n过程如下：\n\n`_addModuleChain`中接收参数`dependency`传入的入口依赖，使用对应的工厂函数`NormalModuleFactory.create`方法生成一个空的`module`对象\n\n回调中会把此`module`存入`compilation.modules`对象和`dependencies.module`对象中，由于是入口文件，也会存入`compilation.entries`中\n\n随后执行`buildModule`进入真正的构建模块`module`内容的过程\n\n\n\n#### build module 完成模块编译\n\n这里主要调用配置的`loaders`，将我们的模块转成标准的`JS`模块\n\n在用` Loader` 对一个模块转换完后，使用 `acorn` 解析转换后的内容，输出对应的抽象语法树（`AST`），以方便 `Webpack `后面对代码的分析\n\n从配置的入口模块开始，分析其 `AST`，当遇到` require `等导入其它模块语句时，便将其加入到依赖的模块列表，同时对新找出的依赖模块递归分析，最终搞清所有模块的依赖关系\n\n\n\n### 输出流程\n\n#### seal 输出资源\n\n`seal`方法主要是要生成`chunks`，对`chunks`进行一系列的优化操作，并生成要输出的代码\n\n`webpack` 中的 `chunk` ，可以理解为配置在 `entry` 中的模块，或者是动态引入的模块\n\n根据入口和模块之间的依赖关系，组装成一个个包含多个模块的 `Chunk`，再把每个 `Chunk` 转换成一个单独的文件加入到输出列表\n\n\n\n#### emit 输出完成\n\n在确定好输出内容后，根据配置确定输出的路径和文件名\n\n```js\noutput: {\n    path: path.resolve(__dirname, 'build'),\n        filename: '[name].js'\n}\n```\n\n在 `Compiler` 开始生成文件前，钩子 `emit` 会被执行，这是我们修改最终文件的最后一个机会\n\n从而`webpack`整个打包过程则结束了\n\n\n\n### 小结\n\n ![](https://static.vue-js.com/d77fc560-a658-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n\n\n## 参考文献\n\n- https://github.com/Cosen95/blog/issues/48\n- https://developer.aliyun.com/article/61047"
  },
  {
    "path": "docs/webpack/improve_build.md",
    "content": "# 面试官：如何提高webpack的构建速度？\n\n ![](https://static.vue-js.com/3a1b8620-b01b-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、背景\n\n随着我们的项目涉及到页面越来越多，功能和业务代码也会随着越多，相应的 `webpack` 的构建时间也会越来越久\n\n构建时间与我们日常开发效率密切相关，当我们本地开发启动 `devServer` 或者 `build` 的时候，如果时间过长，会大大降低我们的工作效率\n\n所以，优化`webpack` 构建速度是十分重要的环节\n\n\n## 二、如何优化\n\n常见的提升构建速度的手段有如下：\n\n- 优化 loader 配置\n- 合理使用 resolve.extensions\n- 优化 resolve.modules\n- 优化 resolve.alias\n- 使用 DLLPlugin 插件\n- 使用 cache-loader\n- terser 启动多线程\n- 合理使用 sourceMap\n\n\n\n\n\n### 优化loader配置\n\n在使用`loader`时，可以通过配置`include`、`exclude`、`test`属性来匹配文件，接触`include`、`exclude`规定哪些匹配应用`loader`\n\n如采用 ES6 的项目为例，在配置 `babel-loader `时，可以这样：\n\n```js\nmodule.exports = {\n  module: {\n    rules: [\n      {\n        // 如果项目源码中只有 js 文件就不要写成 /\\.jsx?$/，提升正则表达式性能\n        test: /\\.js$/,\n        // babel-loader 支持缓存转换出的结果，通过 cacheDirectory 选项开启\n        use: ['babel-loader?cacheDirectory'],\n        // 只对项目根目录下的 src 目录中的文件采用 babel-loader\n        include: path.resolve(__dirname, 'src'),\n      },\n    ]\n  },\n};\n```\n\n\n\n### 合理使用 resolve.extensions\n\n在开发中我们会有各种各样的模块依赖，这些模块可能来自于自己编写的代码，也可能来自第三方库， `resolve`可以帮助`webpack`从每个 `require/import` 语句中，找到需要引入到合适的模块代码\n\n通过`resolve.extensions`是解析到文件时自动添加拓展名，默认情况如下：\n\n```js\nmodule.exports = {\n    ...\n    extensions:[\".warm\",\".mjs\",\".js\",\".json\"]\n}\n```\n\n当我们引入文件的时候，若没有文件后缀名，则会根据数组内的值依次查找\n\n当我们配置的时候，则不要随便把所有后缀都写在里面，这会调用多次文件的查找，这样就会减慢打包速度\n\n\n\n### 优化 resolve.modules\n\n`resolve.modules` 用于配置 `webpack` 去哪些目录下寻找第三方模块。默认值为`['node_modules']`，所以默认会从`node_modules`中查找文件\n当安装的第三方模块都放在项目根目录下的 `./node_modules `目录下时，所以可以指明存放第三方模块的绝对路径，以减少寻找，配置如下：\n\n```\nmodule.exports = {\n  resolve: {\n    // 使用绝对路径指明第三方模块存放的位置，以减少搜索步骤\n    // 其中 __dirname 表示当前工作目录，也就是项目根目录\n    modules: [path.resolve(__dirname, 'node_modules')]\n  },\n};\n```\n\n\n\n### 优化 resolve.alias \n\n`alias`给一些常用的路径起一个别名，特别当我们的项目目录结构比较深的时候，一个文件的路径可能是`./../../`的形式\n\n通过配置`alias`以减少查找过程\n\n```js\nmodule.exports = {\n    ...\n    resolve:{\n        alias:{\n            \"@\":path.resolve(__dirname,'./src')\n        }\n    }\n}\n```\n\n\n\n\n\n### 使用 DLLPlugin 插件\n\n`DLL`全称是 动态链接库，是为软件在winodw种实现共享函数库的一种实现方式，而Webpack也内置了DLL的功能，为的就是可以共享，不经常改变的代码，抽成一个共享的库。这个库在之后的编译过程中，会被引入到其他项目的代码中\n\n使用步骤分成两部分：\n\n- 打包一个 DLL 库\n- 引入 DLL 库\n\n#### 打包一个 DLL 库\n\n`webpack`内置了一个`DllPlugin`可以帮助我们打包一个DLL的库文件\n\n```js\nmodule.exports = {\n    ...\n    plugins:[\n        new webpack.DllPlugin({\n            name:'dll_[name]',\n            path:path.resolve(__dirname,\"./dll/[name].mainfest.json\")\n        })\n    ]\n}\n```\n\n\n\n#### 引入 DLL 库\n\n使用 `webpack` 自带的 `DllReferencePlugin` 插件对 `mainfest.json` 映射文件进行分析，获取要使用的`DLL`库\n\n然后再通过`AddAssetHtmlPlugin`插件，将我们打包的`DLL`库引入到`Html`模块中\n\n```js\nmodule.exports = {\n    ...\n    new webpack.DllReferencePlugin({\n        context:path.resolve(__dirname,\"./dll/dll_react.js\"),\n        mainfest:path.resolve(__dirname,\"./dll/react.mainfest.json\")\n    }),\n    new AddAssetHtmlPlugin({\n        outputPath:\"./auto\",\n        filepath:path.resolve(__dirname,\"./dll/dll_react.js\")\n    })\n}\n```\n\n\n\n### 使用 cache-loader\n\n在一些性能开销较大的 `loader `之前添加 `cache-loader`，以将结果缓存到磁盘里，显著提升二次构建速度\n\n保存和读取这些缓存文件会有一些时间开销，所以请只对性能开销较大的 `loader` 使用此` loader`\n\n```js\nmodule.exports = {\n    module: {\n        rules: [\n            {\n                test: /\\.ext$/,\n                use: ['cache-loader', ...loaders],\n                include: path.resolve('src'),\n            },\n        ],\n    },\n};\n```\n\n\n\n### terser 启动多线程\n\n使用多进程并行运行来提高构建速度\n\n```js\nmodule.exports = {\n  optimization: {\n    minimizer: [\n      new TerserPlugin({\n        parallel: true,\n      }),\n    ],\n  },\n};\n```\n\n\n\n### 合理使用 sourceMap\n\n打包生成  `sourceMap` 的时候，如果信息越详细，打包速度就会越慢。对应属性取值如下所示：\n\n![](https://static.vue-js.com/11647af0-b01d-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n\n\n### 三、总结\n\n可以看到，优化`webpack`构建的方式有很多，主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面入手\n\n\n## 参考文献\n\n- https://github.com/ly2011/blog/issues/44\n- https://xie.infoq.cn/article/541418eb82a674741a0ad8865\n- https://zhuanlan.zhihu.com/p/139498741\n- https://vue3js.cn/interview"
  },
  {
    "path": "docs/webpack/performance.md",
    "content": "# 面试官：说说如何借助webpack来优化前端性能？\n\n ![](https://static.vue-js.com/15e1ace0-aee4-11eb-ab90-d9ae814b240d.png)\n\n\n\n## 一、背景\n\n随着前端的项目逐渐扩大，必然会带来的一个问题就是性能\n\n尤其在大型复杂的项目中，前端业务可能因为一个小小的数据依赖，导致整个页面卡顿甚至奔溃\n\n一般项目在完成后，会通过`webpack`进行打包，利用`webpack`对前端项目性能优化是一个十分重要的环节\n\n\n\n## 二、如何优化\n\n\n\n通过`webpack`优化前端的手段有：\n\n- JS代码压缩\n- CSS代码压缩\n- Html文件代码压缩\n- 文件大小压缩\n- 图片压缩\n- Tree Shaking\n- 代码分离\n- 内联 chunk\n\n\n\n### JS代码压缩\n\n`terser`是一个`JavaScript`的解释、绞肉机、压缩机的工具集，可以帮助我们压缩、丑化我们的代码，让`bundle`更小\n\n在`production`模式下，`webpack` 默认就是使用 `TerserPlugin` 来处理我们的代码的。如果想要自定义配置它，配置方法如下：\n\n```js\nconst TerserPlugin = require('terser-webpack-plugin')\nmodule.exports = {\n    ...\n    optimization: {\n        minimize: true,\n        minimizer: [\n            new TerserPlugin({\n                parallel: true // 电脑cpu核数-1\n            })\n        ]\n    }\n}\n```\n\n属性介绍如下：\n\n-  extractComments：默认值为true，表示会将注释抽取到一个单独的文件中，开发阶段，我们可设置为 false ，不保留注释\n-  parallel：使用多进程并发运行提高构建的速度，默认值是true，并发运行的默认数量： os.cpus().length - 1\n-  terserOptions：设置我们的terser相关的配置：\n  - compress：设置压缩相关的选项，mangle：设置丑化相关的选项，可以直接设置为true\n  - mangle：设置丑化相关的选项，可以直接设置为true\n  - toplevel：底层变量是否进行转换\n  - keep_classnames：保留类的名称\n  - keep_fnames：保留函数的名称\n\n\n\n### CSS代码压缩\n\n`CSS`压缩通常是去除无用的空格等，因为很难去修改选择器、属性的名称、值等\n\nCSS的压缩我们可以使用另外一个插件：`css-minimizer-webpack-plugin`\n\n```cmd\nnpm install css-minimizer-webpack-plugin -D\n```\n\n配置方法如下：\n\n```js\nconst CssMinimizerPlugin = require('css-minimizer-webpack-plugin')\nmodule.exports = {\n    // ...\n    optimization: {\n        minimize: true,\n        minimizer: [\n            new CssMinimizerPlugin({\n                parallel: true\n            })\n        ]\n    }\n}\n```\n\n\n\n\n\n### Html文件代码压缩\n\n使用`HtmlWebpackPlugin`插件来生成`HTML`的模板时候，通过配置属性`minify`进行`html`优化\n\n```js\nmodule.exports = {\n    ...\n    plugin:[\n        new HtmlwebpackPlugin({\n            ...\n            minify:{\n                minifyCSS:false, // 是否压缩css\n                collapseWhitespace:false, // 是否折叠空格\n                removeComments:true // 是否移除注释\n            }\n        })\n    ]\n}\n```\n\n设置了`minify`，实际会使用另一个插件`html-minifier-terser`\n\n\n\n### 文件大小压缩\n\n对文件的大小进行压缩，减少`http`传输过程中宽带的损耗\n\n```js\nnpm install compression-webpack-plugin -D\n```\n\n```js\nnew ComepressionPlugin({\n    test:/\\.(css|js)$/,  // 哪些文件需要压缩\n    threshold:500, // 设置文件多大开始压缩\n    minRatio:0.7, // 至少压缩的比例\n    algorithm:\"gzip\", // 采用的压缩算法\n})\n```\n\n\n\n\n\n### 图片压缩\n\n一般来说在打包之后，一些图片文件的大小是远远要比 `js` 或者 `css` 文件要来的大，所以图片压缩较为重要\n\n配置方法如下：\n\n```js\nmodule: {\n  rules: [\n    {\n      test: /\\.(png|jpg|gif)$/,\n      use: [\n        {\n          loader: 'file-loader',\n          options: {\n            name: '[name]_[hash].[ext]',\n            outputPath: 'images/',\n          }\n        },\n        {\n          loader: 'image-webpack-loader',\n          options: {\n            // 压缩 jpeg 的配置\n            mozjpeg: {\n              progressive: true,\n              quality: 65\n            },\n            // 使用 imagemin**-optipng 压缩 png，enable: false 为关闭\n            optipng: {\n              enabled: false,\n            },\n            // 使用 imagemin-pngquant 压缩 png\n            pngquant: {\n              quality: '65-90',\n              speed: 4\n            },\n            // 压缩 gif 的配置\n            gifsicle: {\n              interlaced: false,\n            },\n            // 开启 webp，会把 jpg 和 png 图片压缩为 webp 格式\n            webp: {\n              quality: 75\n            }\n          }\n        }\n      ]\n    },\n  ]\n} \n```\n\n\n\n\n\n### Tree Shaking\n\n`Tree Shaking` 是一个术语，在计算机中表示消除死代码，依赖于`ES Module`的静态语法分析（不执行任何的代码，可以明确知道模块的依赖关系）\n\n在`webpack`实现`Trss shaking`有两种不同的方案：\n\n- usedExports：通过标记某些函数是否被使用，之后通过Terser来进行优化的\n- sideEffects：跳过整个模块/文件，直接查看该文件是否有副作用\n\n两种不同的配置方案， 有不同的效果\n\n\n\n#### usedExports\n\n配置方法也很简单，只需要将`usedExports`设为`true`\n\n```js\nmodule.exports = {\n    ...\n    optimization:{\n        usedExports\n    }\n}\n```\n\n使用之后，没被用上的代码在`webpack`打包中会加入`unused harmony export mul`注释，用来告知 `Terser` 在优化时，可以删除掉这段代码\n\n 如下面`sum`函数没被用到，`webpack`打包会添加注释，`terser`在优化时，则将该函数去掉\n\n ![](https://static.vue-js.com/21b2e200-aee4-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n\n\n#### sideEffects\n\n`sideEffects`用于告知`webpack compiler`哪些模块时有副作用，配置方法是在`package.json`中设置`sideEffects`属性\n\n如果`sideEffects`设置为false，就是告知`webpack`可以安全的删除未用到的`exports`\n\n如果有些文件需要保留，可以设置为数组的形式\n\n```js\n\"sideEffecis\":[\n    \"./src/util/format.js\",\n    \"*.css\" // 所有的css文件\n]\n```\n\n\n\n上述都是关于`javascript`的`tree shaking`，`css`同样也能够实现`tree shaking`\n\n#### css tree shaking\n\n`css`进行`tree shaking`优化可以安装`PurgeCss`插件\n\n```cmd\nnpm install purgecss-plugin-webpack -D\n```\n\n```js\nconst PurgeCssPlugin = require('purgecss-webpack-plugin')\nmodule.exports = {\n    ...\n    plugins:[\n        new PurgeCssPlugin({\n            path:glob.sync(`${path.resolve('./src')}/**/*`), {nodir:true}// src里面的所有文件\n            satelist:function(){\n                return {\n                    standard:[\"html\"]\n                }\n            }\n        })\n    ]\n}\n```\n\n- paths：表示要检测哪些目录下的内容需要被分析，配合使用glob\n- 默认情况下，Purgecss会将我们的html标签的样式移除掉，如果我们希望保留，可以添加一个safelist的属性\n\n\n\n### 代码分离\n\n将代码分离到不同的`bundle`中，之后我们可以按需加载，或者并行加载这些文件\n\n默认情况下，所有的`JavaScript`代码（业务代码、第三方依赖、暂时没有用到的模块）在首页全部都加载，就会影响首页的加载速度\n\n代码分离可以分出出更小的`bundle`，以及控制资源加载优先级，提供代码的加载性能\n\n这里通过`splitChunksPlugin`来实现，该插件`webpack`已经默认安装和集成，只需要配置即可\n\n默认配置中，chunks仅仅针对于异步（async）请求，我们可以设置为initial或者all\n\n```js\nmodule.exports = {\n    ...\n    optimization:{\n        splitChunks:{\n            chunks:\"all\"\n        }\n    }\n}\n```\n\n`splitChunks`主要属性有如下：\n\n- Chunks，对同步代码还是异步代码进行处理\n- minSize： 拆分包的大小, 至少为minSize，如何包的大小不超过minSize，这个包不会拆分\n- maxSize： 将大于maxSize的包，拆分为不小于minSize的包\n-  minChunks：被引入的次数，默认是1\n\n\n\n### 内联chunk\n\n可以通过`InlineChunkHtmlPlugin`插件将一些`chunk`的模块内联到`html`，如`runtime`的代码（对模块进行解析、加载、模块信息相关的代码），代码量并不大，但是必须加载的\n\n```js\nconst InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nmodule.exports = {\n    ...\n    plugin:[\n        new InlineChunkHtmlPlugin(HtmlWebpackPlugin,[/runtime.+\\.js/]\n}\n```\n\n\n\n\n\n### 三、总结\n\n关于`webpack`对前端性能的优化，可以通过文件体积大小入手，其次还可通过分包的形式、减少http请求次数等方式，实现对前端性能的优化\n\n\n## 参考文献\n\n- https://zhuanlan.zhihu.com/p/139498741\n- https://vue3js.cn/interview/"
  },
  {
    "path": "docs/webpack/proxy.md",
    "content": "# 面试官：说说webpack proxy工作原理？为什么能解决跨域?\n\n ![](https://static.vue-js.com/5b871600-ace5-11eb-85f6-6fac77c0c9b3.png)\n\n\n\n## 一、是什么\n\n`webpack proxy`，即`webpack`提供的代理服务\n\n基本行为就是接收客户端发送的请求后转发给其他服务器\n\n其目的是为了便于开发者在开发模式下解决跨域问题（浏览器安全策略限制）\n\n想要实现代理首先需要一个中间服务器，`webpack`中提供服务器的工具为`webpack-dev-server`\n\n#### webpack-dev-server\n`webpack-dev-server`是 `webpack` 官方推出的一款开发工具，将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起\n\n目的是为了提高开发者日常的开发效率，**只适用在开发阶段**\n\n关于配置方面，在`webpack`配置对象属性中通过`devServer`属性提供，如下：\n\n```js\n// ./webpack.config.js\nconst path = require('path')\n\nmodule.exports = {\n    // ...\n    devServer: {\n        contentBase: path.join(__dirname, 'dist'),\n        compress: true,\n        port: 9000,\n        proxy: {\n            '/api': {\n                target: 'https://api.github.com'\n            }\n        }\n        // ...\n    }\n}\n```\n\n`devServetr`里面`proxy`则是关于代理的配置，该属性为对象的形式，对象中每一个属性就是一个代理的规则匹配\n\n属性的名称是需要被代理的请求路径前缀，一般为了辨别都会设置前缀为` /api`，值为对应的代理匹配规则，对应如下：\n\n- target：表示的是代理到的目标地址\n- pathRewrite：默认情况下，我们的 /api-hy 也会被写入到URL中，如果希望删除，可以使用pathRewrite\n- secure：默认情况下不接收转发到https的服务器上，如果希望支持，可以设置为false\n- changeOrigin：它表示是否更新代理后请求的 headers 中host地址\n\n\n\n\n\n## 二、工作原理\n\n `proxy`工作原理实质上是利用`http-proxy-middleware` 这个`http`代理中间件，实现请求转发给其他服务器\n\n举个例子：\n\n在开发阶段，本地地址为`http://localhost:3000`，该浏览器发送一个前缀带有`/api`标识的请求到服务端获取数据，但响应这个请求的服务器只是将请求转发到另一台服务器中\n\n```js\nconst express = require('express');\nconst proxy = require('http-proxy-middleware');\n\nconst app = express();\n\napp.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));\napp.listen(3000);\n\n// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar\n```\n\n\n\n## 三、跨域\n\n在开发阶段， `webpack-dev-server` 会启动一个本地开发服务器，所以我们的应用在开发阶段是独立运行在 `localhost `的一个端口上，而后端服务又是运行在另外一个地址上\n\n所以在开发阶段中，由于浏览器同源策略的原因，当本地访问后端就会出现跨域请求的问题\n\n通过设置`webpack proxy`实现代理请求后，相当于浏览器与服务端中添加一个代理者\n\n当本地发送请求的时候，代理服务器响应该请求，并将请求转发到目标服务器，目标服务器响应数据后再将数据返回给代理服务器，最终再由代理服务器将数据响应给本地\n\n ![](https://static.vue-js.com/65b5e5c0-ace5-11eb-85f6-6fac77c0c9b3.png)\n\n在代理服务器传递数据给本地浏览器的过程中，两者同源，并不存在跨域行为，这时候浏览器就能正常接收数据\n\n注意：**服务器与服务器之间请求数据并不会存在跨域行为，跨域行为是浏览器安全策略限制**\n\n\n## 参考文献\n\n- https://webpack.docschina.org/configuration/dev-server/#devserverproxy"
  },
  {
    "path": "docs/webpack/webpack.md",
    "content": "# 面试官：说说你对webpack的理解？解决了什么问题？\n\n ![](https://static.vue-js.com/898ed570-a578-11eb-85f6-6fac77c0c9b3.png)\n\n## 一、背景\n\n`Webpack` 最初的目标是实现前端项目的模块化，旨在更高效地管理和维护项目中的每一个资源\n\n#### 模块化\n\n最早的时候，我们会通过文件划分的形式实现模块化，也就是将每个功能及其相关状态数据各自单独放到不同的` JS` 文件中\n\n约定每个文件是一个独立的模块，然后再将这些`js`文件引入到页面，一个`script`标签对应一个模块，然后调用模块化的成员\n\n```html\n<script src=\"module-a.js\"></script>\n<script src=\"module-b.js\"></script>\n```\n\n但这种模块弊端十分的明显，模块都是在全局中工作，大量模块成员污染了环境，模块与模块之间并没有依赖关系、维护困难、没有私有空间等问题\n\n项目一旦变大，上述问题会尤其明显\n\n随后，就出现了命名空间方式，规定每个模块只暴露一个全局对象，然后模块的内容都挂载到这个对象中\n\n```js\nwindow.moduleA = {\n  method1: function () {\n    console.log('moduleA#method1')\n  }\n}\n```\n\n这种方式也并没有解决第一种方式的依赖等问题\n\n再后来，我们使用立即执行函数为模块提供私有空间，通过参数的形式作为依赖声明，如下\n\n```js\n// module-a.js\n(function ($) {\n  var name = 'module-a'\n\n  function method1 () {\n    console.log(name + '#method1')\n    $('body').animate({ margin: '200px' })\n  }\n\n  window.moduleA = {\n    method1: method1\n  }\n})(jQuery)\n```\n\n上述的方式都是早期解决模块的方式，但是仍然存在一些没有解决的问题。例如，我们是用过`script`标签在页面引入这些模块的，这些模块的加载并不受代码的控制，时间一久维护起来也十分的麻烦\n\n理想的解决方式是，在页面中引入一个` JS `入口文件，其余用到的模块可以通过代码控制，按需加载进来\n\n除了模块加载的问题以外，还需要规定模块化的规范，如今流行的则是`CommonJS `、`ES Modules`\n\n\n## 二、问题\n\n从后端渲染的`JSP`、`PHP`，到前端原生`JavaScript`，再到`jQuery`开发，再到目前的三大框架`Vue`、`React`、`Angular`\n\n开发方式，也从`javascript`到后面的`es5`、`es6、7、8、9、10`，再到`typescript`，包括编写`CSS`的预处理器`less`、`scss`等\n\n现代前端开发已经变得十分的复杂，所以我们开发过程中会遇到如下的问题：\n\n- 需要通过模块化的方式来开发\n- 使用一些高级的特性来加快我们的开发效率或者安全性，比如通过ES6+、TypeScript开发脚本逻辑，通过sass、less等方式来编写css样式代码\n- 监听文件的变化来并且反映到浏览器上，提高开发的效率\n- JavaScript 代码需要模块化，HTML 和 CSS 这些资源文件也会面临需要被模块化的问题\n- 开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化\n\n而`webpack`恰巧可以解决以上问题\n\n\n## 三、是什么\n\n`webpack` 是一个用于现代` JavaScript `应用程序的静态模块打包工具\n\n- 静态模块\n\n这里的静态模块指的是开发阶段，可以被 `webpack` 直接引用的资源（可以直接被获取打包进`bundle.js`的资源）\n\n当 `webpack `处理应用程序时，它会在内部构建一个依赖图，此依赖图对应映射到项目所需的每个模块（不再局限`js`文件），并生成一个或多个 `bundle`\n\n ![](https://static.vue-js.com/9ce194a0-a578-11eb-85f6-6fac77c0c9b3.png)\n\n#### `webpack`的能力：\n\n**编译代码能力**，提高效率，解决浏览器兼容问题\n![](https://static.vue-js.com/c5c2d360-a592-11eb-ab90-d9ae814b240d.png)\n**模块整合能力**，提高性能，可维护性，解决浏览器频繁请求文件的问题\n![](https://static.vue-js.com/d306d260-a592-11eb-ab90-d9ae814b240d.png)\n**万物皆可模块能力**，项目维护性增强，支持不同种类的前端模块类型，统一的模块化方案，所有资源文件的加载都可以通过代码控制\n![](https://static.vue-js.com/e3c5a040-a592-11eb-ab90-d9ae814b240d.png)\n\n## 参考文献\n- https://webpack.docschina.org/concepts/\n- https://zhuanlan.zhihu.com/p/267875652"
  },
  {
    "path": "package-lock.json.bakn",
    "content": "{\n  \"name\": \"web-interview\",\n  \"version\": \"1.0.0\",\n  \"lockfileVersion\": 1,\n  \"requires\": true,\n  \"dependencies\": {\n    \"@babel/code-frame\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fcode-frame/-/code-frame-7.10.4.tgz\",\n      \"integrity\": \"sha1-Fo2ho26Q2miujUnA8bSMfGJJITo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/highlight\": \"^7.10.4\"\n      }\n    },\n    \"@babel/compat-data\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fcompat-data/-/compat-data-7.12.7.tgz\",\n      \"integrity\": \"sha1-kym0eCp9a71+71fhGt35HuPvHkE=\",\n      \"dev\": true\n    },\n    \"@babel/core\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fcore/-/core-7.12.10.tgz\",\n      \"integrity\": \"sha1-t5ouG59w7T2Eu/ttjE74JfYGvM0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/generator\": \"^7.12.10\",\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helpers\": \"^7.12.5\",\n        \"@babel/parser\": \"^7.12.10\",\n        \"@babel/template\": \"^7.12.7\",\n        \"@babel/traverse\": \"^7.12.10\",\n        \"@babel/types\": \"^7.12.10\",\n        \"convert-source-map\": \"^1.7.0\",\n        \"debug\": \"^4.1.0\",\n        \"gensync\": \"^1.0.0-beta.1\",\n        \"json5\": \"^2.1.2\",\n        \"lodash\": \"^4.17.19\",\n        \"semver\": \"^5.4.1\",\n        \"source-map\": \"^0.5.0\"\n      }\n    },\n    \"@babel/generator\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fgenerator/-/generator-7.12.10.tgz\",\n      \"integrity\": \"sha1-KxiPwyn7jk92IYFwO+/8D+bfNGA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.10\",\n        \"jsesc\": \"^2.5.1\",\n        \"source-map\": \"^0.5.0\"\n      }\n    },\n    \"@babel/helper-annotate-as-pure\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz\",\n      \"integrity\": \"sha1-VKubAA5gqTZEzhez830xOq8dEV0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.10\"\n      }\n    },\n    \"@babel/helper-builder-binary-assignment-operator-visitor\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz\",\n      \"integrity\": \"sha1-uwt18xv5jL+f8UPBrleLhydK4aM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-explode-assignable-expression\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-compilation-targets\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz\",\n      \"integrity\": \"sha1-y0cMdhmNtqJOnbyJhydWMeXSmDE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/compat-data\": \"^7.12.5\",\n        \"@babel/helper-validator-option\": \"^7.12.1\",\n        \"browserslist\": \"^4.14.5\",\n        \"semver\": \"^5.5.0\"\n      }\n    },\n    \"@babel/helper-create-class-features-plugin\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz\",\n      \"integrity\": \"sha1-PEWZj0Me3UqSFMXx060USKYTf24=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-member-expression-to-functions\": \"^7.12.1\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-create-regexp-features-plugin\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz\",\n      \"integrity\": \"sha1-IIQXLpVEP6CgkhS6G7Mo+a6hJ48=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"regexpu-core\": \"^4.7.1\"\n      }\n    },\n    \"@babel/helper-define-map\": {\n      \"version\": \"7.10.5\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-define-map/-/helper-define-map-7.10.5.tgz\",\n      \"integrity\": \"sha1-tTwQ23imQIABUmkrEzkxR6y5uzA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.5\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"@babel/helper-explode-assignable-expression\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz\",\n      \"integrity\": \"sha1-gAakZmlcSthqKl8vsVtfLDGtVjM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"@babel/helper-function-name\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-function-name/-/helper-function-name-7.10.4.tgz\",\n      \"integrity\": \"sha1-0tOyDFmtjEcRL6fSqUvAnV74Lxo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-get-function-arity\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-get-function-arity\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz\",\n      \"integrity\": \"sha1-sViBejFltfqiBHgl36YZcN3MFs8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.10\"\n      }\n    },\n    \"@babel/helper-hoist-variables\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz\",\n      \"integrity\": \"sha1-1JsAHR1aaMpeZgTdoBpil/fJOB4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-member-expression-to-functions\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz\",\n      \"integrity\": \"sha1-qne9A5bsgRTl4weH76eFmdh0qFU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.7\"\n      }\n    },\n    \"@babel/helper-module-imports\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-module-imports/-/helper-module-imports-7.12.5.tgz\",\n      \"integrity\": \"sha1-G/wCKfeUmI927QpNTpCGCFC1Tfs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"@babel/helper-module-transforms\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-module-transforms/-/helper-module-transforms-7.12.1.tgz\",\n      \"integrity\": \"sha1-eVT+xx9bMsSOSzA7Q3w0RT/XJHw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-imports\": \"^7.12.1\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-simple-access\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.11.0\",\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.1\",\n        \"@babel/types\": \"^7.12.1\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"@babel/helper-optimise-call-expression\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz\",\n      \"integrity\": \"sha1-lMpOMG7hGn3W6fQoI+Ksa0mIHi0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.10\"\n      }\n    },\n    \"@babel/helper-plugin-utils\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz\",\n      \"integrity\": \"sha1-L3WoMSadT2d95JmG3/WZJ1M883U=\",\n      \"dev\": true\n    },\n    \"@babel/helper-remap-async-to-generator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz\",\n      \"integrity\": \"sha1-jE27+RYxT2BH3AXmoiFwdCODR/0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"@babel/helper-wrap-function\": \"^7.10.4\",\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"@babel/helper-replace-supers\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-replace-supers/-/helper-replace-supers-7.12.5.tgz\",\n      \"integrity\": \"sha1-8AmhdUO7u84WsGIGrnO2PT/KaNk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-member-expression-to-functions\": \"^7.12.1\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.5\",\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"@babel/helper-simple-access\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-simple-access/-/helper-simple-access-7.12.1.tgz\",\n      \"integrity\": \"sha1-MkJ+WqYVR9OOsebq9f0UJv2tkTY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"@babel/helper-skip-transparent-expression-wrappers\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz\",\n      \"integrity\": \"sha1-Ri3GOn5DWt6EaDhcY9K4TM5LPL8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"@babel/helper-split-export-declaration\": {\n      \"version\": \"7.11.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz\",\n      \"integrity\": \"sha1-+KSRJErPamdhWKxCBykRuoOtCZ8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.11.0\"\n      }\n    },\n    \"@babel/helper-validator-identifier\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz\",\n      \"integrity\": \"sha1-p4x6clHgH2FlEtMbEK3PUq2l4NI=\",\n      \"dev\": true\n    },\n    \"@babel/helper-validator-option\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-validator-option/-/helper-validator-option-7.12.1.tgz\",\n      \"integrity\": \"sha1-F1VnOAw+d9YP+YpUuwFf548heNk=\",\n      \"dev\": true\n    },\n    \"@babel/helper-wrap-function\": {\n      \"version\": \"7.12.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelper-wrap-function/-/helper-wrap-function-7.12.3.tgz\",\n      \"integrity\": \"sha1-MzIzn8TR+78cJ9eVjCfTRwjpkNk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helpers\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhelpers/-/helpers-7.12.5.tgz\",\n      \"integrity\": \"sha1-Ghukp2jZtYMQ7aUWxEmRP+ZHEW4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.5\",\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"@babel/highlight\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fhighlight/-/highlight-7.10.4.tgz\",\n      \"integrity\": \"sha1-fRvf1ldTU4+r5sOFls23bZrGAUM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"chalk\": \"^2.0.0\",\n        \"js-tokens\": \"^4.0.0\"\n      }\n    },\n    \"@babel/parser\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fparser/-/parser-7.12.10.tgz\",\n      \"integrity\": \"sha1-gkYA1Z6WrqJqWir1qdgSrwXDroE=\",\n      \"dev\": true\n    },\n    \"@babel/plugin-proposal-async-generator-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz\",\n      \"integrity\": \"sha1-3GwRcOJ9isqZ/2X0klvQaxyQVQ4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-remap-async-to-generator\": \"^7.12.1\",\n        \"@babel/plugin-syntax-async-generators\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-class-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz\",\n      \"integrity\": \"sha1-oIL/VB8qKaSCEGW4rdk0bAwW5d4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-class-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-proposal-decorators\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-decorators/-/plugin-proposal-decorators-7.12.1.tgz\",\n      \"integrity\": \"sha1-WScUOf7UFFRWxBBnRQVDruMy0V8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-class-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-decorators\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-proposal-dynamic-import\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz\",\n      \"integrity\": \"sha1-Q+tcKjSH7NmMXI6otf22midJstw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-dynamic-import\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-export-namespace-from\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz\",\n      \"integrity\": \"sha1-i5uPN2stiPXdd05NJKXMLjZ5ttQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-export-namespace-from\": \"^7.8.3\"\n      }\n    },\n    \"@babel/plugin-proposal-json-strings\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz\",\n      \"integrity\": \"sha1-1FQjtRdxTu3VYhqd/cA/qfTrJBw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-json-strings\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-logical-assignment-operators\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz\",\n      \"integrity\": \"sha1-8sSQ024bPJZZJBA0pdLNUCY6J1E=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-logical-assignment-operators\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-proposal-nullish-coalescing-operator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz\",\n      \"integrity\": \"sha1-PtT/8xwBXn8/FGfxkNvlRc17BGw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-nullish-coalescing-operator\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-numeric-separator\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz\",\n      \"integrity\": \"sha1-i/JT3oE5CZ/qGTspfSOp1AbvBWs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-numeric-separator\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-proposal-object-rest-spread\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz\",\n      \"integrity\": \"sha1-3vm9A86g+bcig9rA7CLSicdpEGk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-object-rest-spread\": \"^7.8.0\",\n        \"@babel/plugin-transform-parameters\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-proposal-optional-catch-binding\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz\",\n      \"integrity\": \"sha1-zMJCGvZNOq5QtVinHO3pKaWrKUI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-optional-catch-binding\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-optional-chaining\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz\",\n      \"integrity\": \"sha1-4C8OobXcWdQB7Bb7gkZ59oPTMDw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-skip-transparent-expression-wrappers\": \"^7.12.1\",\n        \"@babel/plugin-syntax-optional-chaining\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-private-methods\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz\",\n      \"integrity\": \"sha1-hoFPbnohN0yYDBDTi0ST5wP0o4k=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-class-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-proposal-unicode-property-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz\",\n      \"integrity\": \"sha1-Khg5WNQXdluerjNPR3WOXWqC4HI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-async-generators\": {\n      \"version\": \"7.8.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz\",\n      \"integrity\": \"sha1-qYP7Gusuw/btBCohD2QOkOeG/g0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-class-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz\",\n      \"integrity\": \"sha1-vLKXxTZueb663vUJVJzZOwTxmXg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-decorators\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz\",\n      \"integrity\": \"sha1-gai1NbKER2xBvm3gaFOogCuYxd0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-dynamic-import\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz\",\n      \"integrity\": \"sha1-Yr+Ysto80h1iYVT8lu5bPLaOrLM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-export-namespace-from\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz\",\n      \"integrity\": \"sha1-AolkqbqA28CUyRXEh618TnpmRlo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.3\"\n      }\n    },\n    \"@babel/plugin-syntax-json-strings\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz\",\n      \"integrity\": \"sha1-AcohtmjNghjJ5kDLbdiMVBKyyWo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-jsx\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz\",\n      \"integrity\": \"sha1-nZ01fMgYqnrnk1kXwSV/Z2d6CSY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-logical-assignment-operators\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz\",\n      \"integrity\": \"sha1-ypHvRjA1MESLkGZSusLp/plB9pk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-nullish-coalescing-operator\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz\",\n      \"integrity\": \"sha1-Fn7XA2iIYIH3S1w2xlqIwDtm0ak=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-numeric-separator\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz\",\n      \"integrity\": \"sha1-ubBws+M1cM2f0Hun+pHA3Te5r5c=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-object-rest-spread\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz\",\n      \"integrity\": \"sha1-YOIl7cvZimQDMqLnLdPmbxr1WHE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-optional-catch-binding\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz\",\n      \"integrity\": \"sha1-YRGiZbz7Ag6579D9/X0mQCue1sE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-optional-chaining\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz\",\n      \"integrity\": \"sha1-T2nCq5UWfgGAzVM2YT+MV4j31Io=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-top-level-await\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz\",\n      \"integrity\": \"sha1-3WwLNXrBuxQtmFN0UKMZYl0T0qA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-arrow-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz\",\n      \"integrity\": \"sha1-gIP/yGrI53f74ktZZ8SyUh88srM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-async-to-generator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz\",\n      \"integrity\": \"sha1-OEmknMKiLpdDy9a1KSbTAzcimvE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-imports\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-remap-async-to-generator\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-transform-block-scoped-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz\",\n      \"integrity\": \"sha1-8qGjZb3itxEuCm3tkGf918B5Bdk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-block-scoping\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz\",\n      \"integrity\": \"sha1-8O5yeHS0KiCKSKWGuEw9IiwrvvE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-classes\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz\",\n      \"integrity\": \"sha1-ZeZQ/K3dPYjdzmfA+DSj1DajLbY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"@babel/helper-define-map\": \"^7.10.4\",\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.10.4\",\n        \"globals\": \"^11.1.0\"\n      }\n    },\n    \"@babel/plugin-transform-computed-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz\",\n      \"integrity\": \"sha1-1oz2ybf4OKikFEutvpdUHqCQSFI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-destructuring\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz\",\n      \"integrity\": \"sha1-uaVw/g0KjUYBFkE8tPl+jgiy+Ec=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-dotall-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz\",\n      \"integrity\": \"sha1-odFsFIYoF7ZAnApnjW+Tc8qc2XU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-duplicate-keys\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz\",\n      \"integrity\": \"sha1-dFZhuropWsBuaGgieXpp+6osoig=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-exponentiation-operator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz\",\n      \"integrity\": \"sha1-sPLtNWuhvhQo7K8Sj/iiTwKDCuA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-builder-binary-assignment-operator-visitor\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-for-of\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz\",\n      \"integrity\": \"sha1-B2QPKIZ+0W+VEcmciIKR9WCSHPo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-function-name\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz\",\n      \"integrity\": \"sha1-LsdiWMcP4IxtfaFUADpIBiDrpmc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz\",\n      \"integrity\": \"sha1-1zuAOiazcBfd+dO7j03Fi/uAb1c=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-member-expression-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz\",\n      \"integrity\": \"sha1-SWA4YC2vFRSmTUPY4Xy7J1Xgw60=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-modules-amd\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz\",\n      \"integrity\": \"sha1-MVQwCwJhhWZu67DA7X+EFf789vk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      }\n    },\n    \"@babel/plugin-transform-modules-commonjs\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz\",\n      \"integrity\": \"sha1-+kAxJFQmNseGz5tGCg/7tIqG5kg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-simple-access\": \"^7.12.1\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      }\n    },\n    \"@babel/plugin-transform-modules-systemjs\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz\",\n      \"integrity\": \"sha1-Zj/qYg1ZPJPyFKRkzTmb9txoMIY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-hoist-variables\": \"^7.10.4\",\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      }\n    },\n    \"@babel/plugin-transform-modules-umd\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz\",\n      \"integrity\": \"sha1-61ohjWscaPPWIXuPosyC/sZUeQI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-named-capturing-groups-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz\",\n      \"integrity\": \"sha1-tAf1yWvg2fX4hGdJf6grMKw+h1M=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-transform-new-target\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz\",\n      \"integrity\": \"sha1-gAc/Au4bstNlw0FkkOCFyVdZ3sA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-object-super\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz\",\n      \"integrity\": \"sha1-TqCGlrjS5lhB0MdwZIKwSL7RBm4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-transform-parameters\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz\",\n      \"integrity\": \"sha1-0uljsDh3FlDJIu/1k3mclthTJV0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-property-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz\",\n      \"integrity\": \"sha1-QbyBIA1zCrtEVquLP71VN7Wa3s0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-regenerator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz\",\n      \"integrity\": \"sha1-Xwoo2EL2RiKB8GqWToi6jXq0l1M=\",\n      \"dev\": true,\n      \"requires\": {\n        \"regenerator-transform\": \"^0.14.2\"\n      }\n    },\n    \"@babel/plugin-transform-reserved-words\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz\",\n      \"integrity\": \"sha1-b9/IzH7cxCs2p8EhiMZ4fIc63Ng=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-runtime\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-runtime/-/plugin-transform-runtime-7.12.10.tgz\",\n      \"integrity\": \"sha1-rw/e1OhGxLNweOjl0G3qxs2EhWI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-imports\": \"^7.12.5\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"semver\": \"^5.5.1\"\n      }\n    },\n    \"@babel/plugin-transform-shorthand-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz\",\n      \"integrity\": \"sha1-C/nKxVUPzgz98ENCD2YdZF/cdeM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-spread\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz\",\n      \"integrity\": \"sha1-Un+fMRvk7H/cK3m7ife/iEs+Hh4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-skip-transparent-expression-wrappers\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-transform-sticky-regex\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz\",\n      \"integrity\": \"sha1-VgIkYTqyOYdFOUjtIdCwsZP6f60=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-template-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz\",\n      \"integrity\": \"sha1-tD7ObtmnnAxxEZ9XbSme8J2UKEM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-typeof-symbol\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz\",\n      \"integrity\": \"sha1-3gHEyPllgL0A8YMHKw0Ozc8N7Es=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-unicode-escapes\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz\",\n      \"integrity\": \"sha1-UjK5+BzLBwcLfDw2xnobePGEVwk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-unicode-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fplugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz\",\n      \"integrity\": \"sha1-zJZh9hOQ21xl4/66zO/Vxqw/rss=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/preset-env\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fpreset-env/-/preset-env-7.12.10.tgz\",\n      \"integrity\": \"sha1-ypgblfZB8mEFMb1xlIZWMGkF5qs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/compat-data\": \"^7.12.7\",\n        \"@babel/helper-compilation-targets\": \"^7.12.5\",\n        \"@babel/helper-module-imports\": \"^7.12.5\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-validator-option\": \"^7.12.1\",\n        \"@babel/plugin-proposal-async-generator-functions\": \"^7.12.1\",\n        \"@babel/plugin-proposal-class-properties\": \"^7.12.1\",\n        \"@babel/plugin-proposal-dynamic-import\": \"^7.12.1\",\n        \"@babel/plugin-proposal-export-namespace-from\": \"^7.12.1\",\n        \"@babel/plugin-proposal-json-strings\": \"^7.12.1\",\n        \"@babel/plugin-proposal-logical-assignment-operators\": \"^7.12.1\",\n        \"@babel/plugin-proposal-nullish-coalescing-operator\": \"^7.12.1\",\n        \"@babel/plugin-proposal-numeric-separator\": \"^7.12.7\",\n        \"@babel/plugin-proposal-object-rest-spread\": \"^7.12.1\",\n        \"@babel/plugin-proposal-optional-catch-binding\": \"^7.12.1\",\n        \"@babel/plugin-proposal-optional-chaining\": \"^7.12.7\",\n        \"@babel/plugin-proposal-private-methods\": \"^7.12.1\",\n        \"@babel/plugin-proposal-unicode-property-regex\": \"^7.12.1\",\n        \"@babel/plugin-syntax-async-generators\": \"^7.8.0\",\n        \"@babel/plugin-syntax-class-properties\": \"^7.12.1\",\n        \"@babel/plugin-syntax-dynamic-import\": \"^7.8.0\",\n        \"@babel/plugin-syntax-export-namespace-from\": \"^7.8.3\",\n        \"@babel/plugin-syntax-json-strings\": \"^7.8.0\",\n        \"@babel/plugin-syntax-logical-assignment-operators\": \"^7.10.4\",\n        \"@babel/plugin-syntax-nullish-coalescing-operator\": \"^7.8.0\",\n        \"@babel/plugin-syntax-numeric-separator\": \"^7.10.4\",\n        \"@babel/plugin-syntax-object-rest-spread\": \"^7.8.0\",\n        \"@babel/plugin-syntax-optional-catch-binding\": \"^7.8.0\",\n        \"@babel/plugin-syntax-optional-chaining\": \"^7.8.0\",\n        \"@babel/plugin-syntax-top-level-await\": \"^7.12.1\",\n        \"@babel/plugin-transform-arrow-functions\": \"^7.12.1\",\n        \"@babel/plugin-transform-async-to-generator\": \"^7.12.1\",\n        \"@babel/plugin-transform-block-scoped-functions\": \"^7.12.1\",\n        \"@babel/plugin-transform-block-scoping\": \"^7.12.1\",\n        \"@babel/plugin-transform-classes\": \"^7.12.1\",\n        \"@babel/plugin-transform-computed-properties\": \"^7.12.1\",\n        \"@babel/plugin-transform-destructuring\": \"^7.12.1\",\n        \"@babel/plugin-transform-dotall-regex\": \"^7.12.1\",\n        \"@babel/plugin-transform-duplicate-keys\": \"^7.12.1\",\n        \"@babel/plugin-transform-exponentiation-operator\": \"^7.12.1\",\n        \"@babel/plugin-transform-for-of\": \"^7.12.1\",\n        \"@babel/plugin-transform-function-name\": \"^7.12.1\",\n        \"@babel/plugin-transform-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-member-expression-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-amd\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-commonjs\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-systemjs\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-umd\": \"^7.12.1\",\n        \"@babel/plugin-transform-named-capturing-groups-regex\": \"^7.12.1\",\n        \"@babel/plugin-transform-new-target\": \"^7.12.1\",\n        \"@babel/plugin-transform-object-super\": \"^7.12.1\",\n        \"@babel/plugin-transform-parameters\": \"^7.12.1\",\n        \"@babel/plugin-transform-property-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-regenerator\": \"^7.12.1\",\n        \"@babel/plugin-transform-reserved-words\": \"^7.12.1\",\n        \"@babel/plugin-transform-shorthand-properties\": \"^7.12.1\",\n        \"@babel/plugin-transform-spread\": \"^7.12.1\",\n        \"@babel/plugin-transform-sticky-regex\": \"^7.12.7\",\n        \"@babel/plugin-transform-template-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-typeof-symbol\": \"^7.12.10\",\n        \"@babel/plugin-transform-unicode-escapes\": \"^7.12.1\",\n        \"@babel/plugin-transform-unicode-regex\": \"^7.12.1\",\n        \"@babel/preset-modules\": \"^0.1.3\",\n        \"@babel/types\": \"^7.12.10\",\n        \"core-js-compat\": \"^3.8.0\",\n        \"semver\": \"^5.5.0\"\n      }\n    },\n    \"@babel/preset-modules\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fpreset-modules/-/preset-modules-0.1.4.tgz\",\n      \"integrity\": \"sha1-Ni8raMZihClw/bXiVP/I/BwuQV4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.0.0\",\n        \"@babel/plugin-proposal-unicode-property-regex\": \"^7.4.4\",\n        \"@babel/plugin-transform-dotall-regex\": \"^7.4.4\",\n        \"@babel/types\": \"^7.4.4\",\n        \"esutils\": \"^2.0.2\"\n      }\n    },\n    \"@babel/runtime\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2fruntime/-/runtime-7.12.5.tgz\",\n      \"integrity\": \"sha1-QQ5+SHRB4bNgwpvnFdhw2bmFiC4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"regenerator-runtime\": \"^0.13.4\"\n      }\n    },\n    \"@babel/template\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2ftemplate/-/template-7.12.7.tgz\",\n      \"integrity\": \"sha1-yBcjNpYBjjn7tsSR0vtoTgXtQ7w=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/parser\": \"^7.12.7\",\n        \"@babel/types\": \"^7.12.7\"\n      }\n    },\n    \"@babel/traverse\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2ftraverse/-/traverse-7.12.10.tgz\",\n      \"integrity\": \"sha1-LR9AQei/QuoJnlstxI1qWUwAAXo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/generator\": \"^7.12.10\",\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-split-export-declaration\": \"^7.11.0\",\n        \"@babel/parser\": \"^7.12.10\",\n        \"@babel/types\": \"^7.12.10\",\n        \"debug\": \"^4.1.0\",\n        \"globals\": \"^11.1.0\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"@babel/types\": {\n      \"version\": \"7.12.10\",\n      \"resolved\": \"https://npm.quanziapp.com/@babel%2ftypes/-/types-7.12.10.tgz\",\n      \"integrity\": \"sha1-eWXkpyYLJvCcVrz8sEmK8fbZsmA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"lodash\": \"^4.17.19\",\n        \"to-fast-properties\": \"^2.0.0\"\n      }\n    },\n    \"@mrmlnc/readdir-enhanced\": {\n      \"version\": \"2.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@mrmlnc%2freaddir-enhanced/-/readdir-enhanced-2.2.1.tgz\",\n      \"integrity\": \"sha1-UkryQNGjYFJ7cwR17PoTRKpUDd4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-me-maybe\": \"^1.0.1\",\n        \"glob-to-regexp\": \"^0.3.0\"\n      }\n    },\n    \"@nodelib/fs.stat\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@nodelib%2ffs.stat/-/fs.stat-1.1.3.tgz\",\n      \"integrity\": \"sha1-K1o6s/kYzKSKjHVMCBaOPwPrphs=\",\n      \"dev\": true\n    },\n    \"@sindresorhus/is\": {\n      \"version\": \"0.14.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@sindresorhus%2fis/-/is-0.14.0.tgz\",\n      \"integrity\": \"sha1-n7OjzzEyMoFR81PeRjLgHlIQK+o=\",\n      \"dev\": true\n    },\n    \"@szmarczak/http-timer\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/@szmarczak%2fhttp-timer/-/http-timer-1.1.2.tgz\",\n      \"integrity\": \"sha1-sWZeLEYaLNkvTBu/UNVFTeDUtCE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"defer-to-connect\": \"^1.0.1\"\n      }\n    },\n    \"@types/glob\": {\n      \"version\": \"7.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@types%2fglob/-/glob-7.1.3.tgz\",\n      \"integrity\": \"sha1-5rqA82t9qtLGhazZJmOC5omFwYM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/minimatch\": \"*\",\n        \"@types/node\": \"*\"\n      }\n    },\n    \"@types/json-schema\": {\n      \"version\": \"7.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/@types%2fjson-schema/-/json-schema-7.0.6.tgz\",\n      \"integrity\": \"sha1-9MfsQ+gbMZqYFRFQMXCfJph4kfA=\",\n      \"dev\": true\n    },\n    \"@types/minimatch\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@types%2fminimatch/-/minimatch-3.0.3.tgz\",\n      \"integrity\": \"sha1-PcoOPzOyAPx9ETnAzZbBJoyt/Z0=\",\n      \"dev\": true\n    },\n    \"@types/node\": {\n      \"version\": \"14.14.11\",\n      \"resolved\": \"https://npm.quanziapp.com/@types%2fnode/-/node-14.14.11.tgz\",\n      \"integrity\": \"sha1-/CWkJIpejQg3AZsdFwFG0HM0q+A=\",\n      \"dev\": true\n    },\n    \"@types/q\": {\n      \"version\": \"1.5.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@types%2fq/-/q-1.5.4.tgz\",\n      \"integrity\": \"sha1-FZJUFOCtLNdlv+9YhC9+JqesyyQ=\",\n      \"dev\": true\n    },\n    \"@vue/babel-helper-vue-jsx-merge-props\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz\",\n      \"integrity\": \"sha1-MWJKelBfsU2h1YAjclpMXycOaoE=\",\n      \"dev\": true\n    },\n    \"@vue/babel-helper-vue-transform-on\": {\n      \"version\": \"1.0.0-rc.2\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.0-rc.2.tgz\",\n      \"integrity\": \"sha1-ckY0H2ZufG5lsT2kIOLOhXFPu8o=\",\n      \"dev\": true\n    },\n    \"@vue/babel-plugin-jsx\": {\n      \"version\": \"1.0.0-rc.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-plugin-jsx/-/babel-plugin-jsx-1.0.0-rc.4.tgz\",\n      \"integrity\": \"sha1-Asm6LgHcUlH9aaia/UnpoZYzMbo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-imports\": \"^7.0.0\",\n        \"@babel/plugin-syntax-jsx\": \"^7.0.0\",\n        \"@babel/template\": \"^7.0.0\",\n        \"@babel/traverse\": \"^7.0.0\",\n        \"@babel/types\": \"^7.0.0\",\n        \"@vue/babel-helper-vue-transform-on\": \"^1.0.0-rc.2\",\n        \"camelcase\": \"^6.0.0\",\n        \"html-tags\": \"^3.1.0\",\n        \"svg-tags\": \"^1.0.0\"\n      }\n    },\n    \"@vue/babel-plugin-transform-vue-jsx\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.2.1.tgz\",\n      \"integrity\": \"sha1-ZGBGxlLC8CQnJ/NFGdkXsGQEHtc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-imports\": \"^7.0.0\",\n        \"@babel/plugin-syntax-jsx\": \"^7.2.0\",\n        \"@vue/babel-helper-vue-jsx-merge-props\": \"^1.2.1\",\n        \"html-tags\": \"^2.0.0\",\n        \"lodash.kebabcase\": \"^4.1.1\",\n        \"svg-tags\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"html-tags\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/html-tags/-/html-tags-2.0.0.tgz\",\n          \"integrity\": \"sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@vue/babel-preset-app\": {\n      \"version\": \"4.5.9\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-preset-app/-/babel-preset-app-4.5.9.tgz\",\n      \"integrity\": \"sha1-pO8YMKITAeT3fXzUoEVV2KAerzM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/core\": \"^7.11.0\",\n        \"@babel/helper-compilation-targets\": \"^7.9.6\",\n        \"@babel/helper-module-imports\": \"^7.8.3\",\n        \"@babel/plugin-proposal-class-properties\": \"^7.8.3\",\n        \"@babel/plugin-proposal-decorators\": \"^7.8.3\",\n        \"@babel/plugin-syntax-dynamic-import\": \"^7.8.3\",\n        \"@babel/plugin-syntax-jsx\": \"^7.8.3\",\n        \"@babel/plugin-transform-runtime\": \"^7.11.0\",\n        \"@babel/preset-env\": \"^7.11.0\",\n        \"@babel/runtime\": \"^7.11.0\",\n        \"@vue/babel-plugin-jsx\": \"^1.0.0-0\",\n        \"@vue/babel-preset-jsx\": \"^1.1.2\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\",\n        \"core-js\": \"^3.6.5\",\n        \"core-js-compat\": \"^3.6.5\",\n        \"semver\": \"^6.1.0\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"6.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-6.3.0.tgz\",\n          \"integrity\": \"sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@vue/babel-preset-jsx\": {\n      \"version\": \"1.2.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-preset-jsx/-/babel-preset-jsx-1.2.4.tgz\",\n      \"integrity\": \"sha1-kv6nnbbxOwHoDToAmeKSS9y+Toc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@vue/babel-helper-vue-jsx-merge-props\": \"^1.2.1\",\n        \"@vue/babel-plugin-transform-vue-jsx\": \"^1.2.1\",\n        \"@vue/babel-sugar-composition-api-inject-h\": \"^1.2.1\",\n        \"@vue/babel-sugar-composition-api-render-instance\": \"^1.2.4\",\n        \"@vue/babel-sugar-functional-vue\": \"^1.2.2\",\n        \"@vue/babel-sugar-inject-h\": \"^1.2.2\",\n        \"@vue/babel-sugar-v-model\": \"^1.2.3\",\n        \"@vue/babel-sugar-v-on\": \"^1.2.3\"\n      }\n    },\n    \"@vue/babel-sugar-composition-api-inject-h\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.2.1.tgz\",\n      \"integrity\": \"sha1-BdbgxDJxDjdYKyvppgSbaJtvA+s=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/plugin-syntax-jsx\": \"^7.2.0\"\n      }\n    },\n    \"@vue/babel-sugar-composition-api-render-instance\": {\n      \"version\": \"1.2.4\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.2.4.tgz\",\n      \"integrity\": \"sha1-5MvGmXw0T6wnF4WteikyXFHWjRk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/plugin-syntax-jsx\": \"^7.2.0\"\n      }\n    },\n    \"@vue/babel-sugar-functional-vue\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-sugar-functional-vue/-/babel-sugar-functional-vue-1.2.2.tgz\",\n      \"integrity\": \"sha1-JnqayNeHyW7b8Dzj85LEnam9Jlg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/plugin-syntax-jsx\": \"^7.2.0\"\n      }\n    },\n    \"@vue/babel-sugar-inject-h\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-sugar-inject-h/-/babel-sugar-inject-h-1.2.2.tgz\",\n      \"integrity\": \"sha1-1zjTyJM2fshJHcu2abAAkZKT46o=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/plugin-syntax-jsx\": \"^7.2.0\"\n      }\n    },\n    \"@vue/babel-sugar-v-model\": {\n      \"version\": \"1.2.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-sugar-v-model/-/babel-sugar-v-model-1.2.3.tgz\",\n      \"integrity\": \"sha1-+h8pulHr8KoabDX6ZtU5vEWaGPI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/plugin-syntax-jsx\": \"^7.2.0\",\n        \"@vue/babel-helper-vue-jsx-merge-props\": \"^1.2.1\",\n        \"@vue/babel-plugin-transform-vue-jsx\": \"^1.2.1\",\n        \"camelcase\": \"^5.0.0\",\n        \"html-tags\": \"^2.0.0\",\n        \"svg-tags\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"camelcase\": {\n          \"version\": \"5.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/camelcase/-/camelcase-5.3.1.tgz\",\n          \"integrity\": \"sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA=\",\n          \"dev\": true\n        },\n        \"html-tags\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/html-tags/-/html-tags-2.0.0.tgz\",\n          \"integrity\": \"sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@vue/babel-sugar-v-on\": {\n      \"version\": \"1.2.3\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fbabel-sugar-v-on/-/babel-sugar-v-on-1.2.3.tgz\",\n      \"integrity\": \"sha1-NCNnF4WGpp85LwS/ujICHQKROto=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/plugin-syntax-jsx\": \"^7.2.0\",\n        \"@vue/babel-plugin-transform-vue-jsx\": \"^1.2.1\",\n        \"camelcase\": \"^5.0.0\"\n      },\n      \"dependencies\": {\n        \"camelcase\": {\n          \"version\": \"5.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/camelcase/-/camelcase-5.3.1.tgz\",\n          \"integrity\": \"sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@vue/component-compiler-utils\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@vue%2fcomponent-compiler-utils/-/component-compiler-utils-3.2.0.tgz\",\n      \"integrity\": \"sha1-j4UYLO7Sjps8dTE95mn4MWbRHl0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"consolidate\": \"^0.15.1\",\n        \"hash-sum\": \"^1.0.2\",\n        \"lru-cache\": \"^4.1.2\",\n        \"merge-source-map\": \"^1.1.0\",\n        \"postcss\": \"^7.0.14\",\n        \"postcss-selector-parser\": \"^6.0.2\",\n        \"prettier\": \"^1.18.2\",\n        \"source-map\": \"~0.6.1\",\n        \"vue-template-es2015-compiler\": \"^1.9.0\"\n      },\n      \"dependencies\": {\n        \"lru-cache\": {\n          \"version\": \"4.1.5\",\n          \"resolved\": \"https://npm.quanziapp.com/lru-cache/-/lru-cache-4.1.5.tgz\",\n          \"integrity\": \"sha1-i75Q6oW+1ZvJ4z3KuCNe6bz0Q80=\",\n          \"dev\": true,\n          \"requires\": {\n            \"pseudomap\": \"^1.0.2\",\n            \"yallist\": \"^2.1.2\"\n          }\n        },\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        },\n        \"yallist\": {\n          \"version\": \"2.1.2\",\n          \"resolved\": \"https://npm.quanziapp.com/yallist/-/yallist-2.1.2.tgz\",\n          \"integrity\": \"sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@vuepress/core\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fcore/-/core-1.7.1.tgz\",\n      \"integrity\": \"sha1-6S+q0OlEX913X44NZeknvDXoBXE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/core\": \"^7.8.4\",\n        \"@vue/babel-preset-app\": \"^4.1.2\",\n        \"@vuepress/markdown\": \"1.7.1\",\n        \"@vuepress/markdown-loader\": \"1.7.1\",\n        \"@vuepress/plugin-last-updated\": \"1.7.1\",\n        \"@vuepress/plugin-register-components\": \"1.7.1\",\n        \"@vuepress/shared-utils\": \"1.7.1\",\n        \"autoprefixer\": \"^9.5.1\",\n        \"babel-loader\": \"^8.0.4\",\n        \"cache-loader\": \"^3.0.0\",\n        \"chokidar\": \"^2.0.3\",\n        \"connect-history-api-fallback\": \"^1.5.0\",\n        \"copy-webpack-plugin\": \"^5.0.2\",\n        \"core-js\": \"^3.6.4\",\n        \"cross-spawn\": \"^6.0.5\",\n        \"css-loader\": \"^2.1.1\",\n        \"file-loader\": \"^3.0.1\",\n        \"js-yaml\": \"^3.13.1\",\n        \"lru-cache\": \"^5.1.1\",\n        \"mini-css-extract-plugin\": \"0.6.0\",\n        \"optimize-css-assets-webpack-plugin\": \"^5.0.1\",\n        \"portfinder\": \"^1.0.13\",\n        \"postcss-loader\": \"^3.0.0\",\n        \"postcss-safe-parser\": \"^4.0.1\",\n        \"toml\": \"^3.0.0\",\n        \"url-loader\": \"^1.0.1\",\n        \"vue\": \"^2.6.10\",\n        \"vue-loader\": \"^15.7.1\",\n        \"vue-router\": \"^3.4.5\",\n        \"vue-server-renderer\": \"^2.6.10\",\n        \"vue-template-compiler\": \"^2.6.10\",\n        \"vuepress-html-webpack-plugin\": \"^3.2.0\",\n        \"vuepress-plugin-container\": \"^2.0.2\",\n        \"webpack\": \"^4.8.1\",\n        \"webpack-chain\": \"^6.0.0\",\n        \"webpack-dev-server\": \"^3.5.1\",\n        \"webpack-merge\": \"^4.1.2\",\n        \"webpackbar\": \"3.2.0\"\n      }\n    },\n    \"@vuepress/markdown\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fmarkdown/-/markdown-1.7.1.tgz\",\n      \"integrity\": \"sha1-VvYMI2L9grjycC7vo2bA1bAv3L0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@vuepress/shared-utils\": \"1.7.1\",\n        \"markdown-it\": \"^8.4.1\",\n        \"markdown-it-anchor\": \"^5.0.2\",\n        \"markdown-it-chain\": \"^1.3.0\",\n        \"markdown-it-emoji\": \"^1.4.0\",\n        \"markdown-it-table-of-contents\": \"^0.4.0\",\n        \"prismjs\": \"^1.13.0\"\n      }\n    },\n    \"@vuepress/markdown-loader\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fmarkdown-loader/-/markdown-loader-1.7.1.tgz\",\n      \"integrity\": \"sha1-86sgll1d7G4vwtEceO8anwjWL3I=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@vuepress/markdown\": \"1.7.1\",\n        \"loader-utils\": \"^1.1.0\",\n        \"lru-cache\": \"^5.1.1\"\n      }\n    },\n    \"@vuepress/plugin-active-header-links\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fplugin-active-header-links/-/plugin-active-header-links-1.7.1.tgz\",\n      \"integrity\": \"sha1-WhYoG+u5d/wcK5PZkrGjt/+EBkE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash.debounce\": \"^4.0.8\"\n      }\n    },\n    \"@vuepress/plugin-last-updated\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fplugin-last-updated/-/plugin-last-updated-1.7.1.tgz\",\n      \"integrity\": \"sha1-ZoxV2qa4vB2O5CzbQWnPZ8Abbpc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cross-spawn\": \"^6.0.5\"\n      }\n    },\n    \"@vuepress/plugin-nprogress\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fplugin-nprogress/-/plugin-nprogress-1.7.1.tgz\",\n      \"integrity\": \"sha1-EB6/cg6qY1pHPhbKFue0p4UDMfo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"nprogress\": \"^0.2.0\"\n      }\n    },\n    \"@vuepress/plugin-register-components\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fplugin-register-components/-/plugin-register-components-1.7.1.tgz\",\n      \"integrity\": \"sha1-H/WOkx6MJ9ZPm4by34ed2s7M3r4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@vuepress/shared-utils\": \"1.7.1\"\n      }\n    },\n    \"@vuepress/plugin-search\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fplugin-search/-/plugin-search-1.7.1.tgz\",\n      \"integrity\": \"sha1-9Stud68w9FIhO8Z3dBzv6Kgwm+I=\",\n      \"dev\": true\n    },\n    \"@vuepress/shared-utils\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2fshared-utils/-/shared-utils-1.7.1.tgz\",\n      \"integrity\": \"sha1-AovGADJHu0xgzclvIx7s+1XnuF0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"chalk\": \"^2.3.2\",\n        \"escape-html\": \"^1.0.3\",\n        \"fs-extra\": \"^7.0.1\",\n        \"globby\": \"^9.2.0\",\n        \"gray-matter\": \"^4.0.1\",\n        \"hash-sum\": \"^1.0.2\",\n        \"semver\": \"^6.0.0\",\n        \"toml\": \"^3.0.0\",\n        \"upath\": \"^1.1.0\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"6.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-6.3.0.tgz\",\n          \"integrity\": \"sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@vuepress/theme-default\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/@vuepress%2ftheme-default/-/theme-default-1.7.1.tgz\",\n      \"integrity\": \"sha1-Nv7lu1FleYwAgsUSy/TZQ1ImDZc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@vuepress/plugin-active-header-links\": \"1.7.1\",\n        \"@vuepress/plugin-nprogress\": \"1.7.1\",\n        \"@vuepress/plugin-search\": \"1.7.1\",\n        \"docsearch.js\": \"^2.5.2\",\n        \"lodash\": \"^4.17.15\",\n        \"stylus\": \"^0.54.8\",\n        \"stylus-loader\": \"^3.0.2\",\n        \"vuepress-plugin-container\": \"^2.0.2\",\n        \"vuepress-plugin-smooth-scroll\": \"^0.0.3\"\n      }\n    },\n    \"@webassemblyjs/ast\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fast/-/ast-1.9.0.tgz\",\n      \"integrity\": \"sha1-vYUGBLQEJFmlpBzX0zjL7Wle2WQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/helper-module-context\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/wast-parser\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/floating-point-hex-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2ffloating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz\",\n      \"integrity\": \"sha1-PD07Jxvd/ITesA9xNEQ4MR1S/7Q=\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-api-error\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fhelper-api-error/-/helper-api-error-1.9.0.tgz\",\n      \"integrity\": \"sha1-ID9nbjM7lsnaLuqzzO8zxFkotqI=\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-buffer\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fhelper-buffer/-/helper-buffer-1.9.0.tgz\",\n      \"integrity\": \"sha1-oUQtJpxf6yP8vJ73WdrDVH8p3gA=\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-code-frame\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fhelper-code-frame/-/helper-code-frame-1.9.0.tgz\",\n      \"integrity\": \"sha1-ZH+Iks0gQ6gqwMjF51w28dkVnyc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/wast-printer\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/helper-fsm\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fhelper-fsm/-/helper-fsm-1.9.0.tgz\",\n      \"integrity\": \"sha1-wFJWtxJEIUZx9LCOwQitY7cO3bg=\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-module-context\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fhelper-module-context/-/helper-module-context-1.9.0.tgz\",\n      \"integrity\": \"sha1-JdiIS3aDmHGgimxvgGw5ee9xLwc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/helper-wasm-bytecode\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fhelper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz\",\n      \"integrity\": \"sha1-T+2L6sm4wU+MWLcNEk1UndH+V5A=\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-wasm-section\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fhelper-wasm-section/-/helper-wasm-section-1.9.0.tgz\",\n      \"integrity\": \"sha1-WkE41aYpK6GLBMWuSXF+QWeWU0Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/ieee754\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fieee754/-/ieee754-1.9.0.tgz\",\n      \"integrity\": \"sha1-Fceg+6roP7JhQ7us9tbfFwKtOeQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@xtuc/ieee754\": \"^1.2.0\"\n      }\n    },\n    \"@webassemblyjs/leb128\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fleb128/-/leb128-1.9.0.tgz\",\n      \"integrity\": \"sha1-8Zygt2ptxVYjoJz/p2noOPoeHJU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"@webassemblyjs/utf8\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2futf8/-/utf8-1.9.0.tgz\",\n      \"integrity\": \"sha1-BNM7Y2945qaBMifoJAL3Y3tiKas=\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/wasm-edit\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fwasm-edit/-/wasm-edit-1.9.0.tgz\",\n      \"integrity\": \"sha1-P+bXnT8PkiGDqoYALELdJWz+6c8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-section\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\",\n        \"@webassemblyjs/wasm-opt\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\",\n        \"@webassemblyjs/wast-printer\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/wasm-gen\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fwasm-gen/-/wasm-gen-1.9.0.tgz\",\n      \"integrity\": \"sha1-ULxw7Gje2OJ2OwGhQYv0NJGnpJw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/ieee754\": \"1.9.0\",\n        \"@webassemblyjs/leb128\": \"1.9.0\",\n        \"@webassemblyjs/utf8\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/wasm-opt\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fwasm-opt/-/wasm-opt-1.9.0.tgz\",\n      \"integrity\": \"sha1-IhEYHlsxMmRDzIES658LkChyGmE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/wasm-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fwasm-parser/-/wasm-parser-1.9.0.tgz\",\n      \"integrity\": \"sha1-nUjkSCbfSmWYKUqmyHRp1kL/9l4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-api-error\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/ieee754\": \"1.9.0\",\n        \"@webassemblyjs/leb128\": \"1.9.0\",\n        \"@webassemblyjs/utf8\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/wast-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fwast-parser/-/wast-parser-1.9.0.tgz\",\n      \"integrity\": \"sha1-MDERXXmsW9JhVWzsw/qQo+9FGRQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/floating-point-hex-parser\": \"1.9.0\",\n        \"@webassemblyjs/helper-api-error\": \"1.9.0\",\n        \"@webassemblyjs/helper-code-frame\": \"1.9.0\",\n        \"@webassemblyjs/helper-fsm\": \"1.9.0\",\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"@webassemblyjs/wast-printer\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@webassemblyjs%2fwast-printer/-/wast-printer-1.9.0.tgz\",\n      \"integrity\": \"sha1-STXVTIX+9jewDOn1I3dFHQDUeJk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/wast-parser\": \"1.9.0\",\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"@xtuc/ieee754\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/@xtuc%2fieee754/-/ieee754-1.2.0.tgz\",\n      \"integrity\": \"sha1-7vAUoxRa5Hehy8AM0eVSM23Ot5A=\",\n      \"dev\": true\n    },\n    \"@xtuc/long\": {\n      \"version\": \"4.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/@xtuc%2flong/-/long-4.2.2.tgz\",\n      \"integrity\": \"sha1-0pHGpOl5ibXGHZrPOWrk/hM6cY0=\",\n      \"dev\": true\n    },\n    \"abbrev\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/abbrev/-/abbrev-1.1.1.tgz\",\n      \"integrity\": \"sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=\",\n      \"dev\": true\n    },\n    \"accepts\": {\n      \"version\": \"1.3.7\",\n      \"resolved\": \"https://npm.quanziapp.com/accepts/-/accepts-1.3.7.tgz\",\n      \"integrity\": \"sha1-UxvHJlF6OytB+FACHGzBXqq1B80=\",\n      \"dev\": true,\n      \"requires\": {\n        \"mime-types\": \"~2.1.24\",\n        \"negotiator\": \"0.6.2\"\n      }\n    },\n    \"acorn\": {\n      \"version\": \"6.4.2\",\n      \"resolved\": \"https://npm.quanziapp.com/acorn/-/acorn-6.4.2.tgz\",\n      \"integrity\": \"sha1-NYZv1xBSjpLeEM8GAWSY5H454eY=\",\n      \"dev\": true\n    },\n    \"agentkeepalive\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/agentkeepalive/-/agentkeepalive-2.2.0.tgz\",\n      \"integrity\": \"sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8=\",\n      \"dev\": true\n    },\n    \"ajv\": {\n      \"version\": \"6.12.6\",\n      \"resolved\": \"https://npm.quanziapp.com/ajv/-/ajv-6.12.6.tgz\",\n      \"integrity\": \"sha1-uvWmLoArB9l3A0WG+MO69a3ybfQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"fast-deep-equal\": \"^3.1.1\",\n        \"fast-json-stable-stringify\": \"^2.0.0\",\n        \"json-schema-traverse\": \"^0.4.1\",\n        \"uri-js\": \"^4.2.2\"\n      }\n    },\n    \"ajv-errors\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ajv-errors/-/ajv-errors-1.0.1.tgz\",\n      \"integrity\": \"sha1-81mGrOuRr63sQQL72FAUlQzvpk0=\",\n      \"dev\": true\n    },\n    \"ajv-keywords\": {\n      \"version\": \"3.5.2\",\n      \"resolved\": \"https://npm.quanziapp.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz\",\n      \"integrity\": \"sha1-MfKdpatuANHC0yms97WSlhTVAU0=\",\n      \"dev\": true\n    },\n    \"algoliasearch\": {\n      \"version\": \"3.35.1\",\n      \"resolved\": \"https://npm.quanziapp.com/algoliasearch/-/algoliasearch-3.35.1.tgz\",\n      \"integrity\": \"sha1-KX0V9TSjUHyrL137mWAZysdWjww=\",\n      \"dev\": true,\n      \"requires\": {\n        \"agentkeepalive\": \"^2.2.0\",\n        \"debug\": \"^2.6.9\",\n        \"envify\": \"^4.0.0\",\n        \"es6-promise\": \"^4.1.0\",\n        \"events\": \"^1.1.0\",\n        \"foreach\": \"^2.0.5\",\n        \"global\": \"^4.3.2\",\n        \"inherits\": \"^2.0.1\",\n        \"isarray\": \"^2.0.1\",\n        \"load-script\": \"^1.0.0\",\n        \"object-keys\": \"^1.0.11\",\n        \"querystring-es3\": \"^0.2.1\",\n        \"reduce\": \"^1.0.1\",\n        \"semver\": \"^5.1.0\",\n        \"tunnel-agent\": \"^0.6.0\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"events\": {\n          \"version\": \"1.1.1\",\n          \"resolved\": \"https://npm.quanziapp.com/events/-/events-1.1.1.tgz\",\n          \"integrity\": \"sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=\",\n          \"dev\": true\n        },\n        \"isarray\": {\n          \"version\": \"2.0.5\",\n          \"resolved\": \"https://npm.quanziapp.com/isarray/-/isarray-2.0.5.tgz\",\n          \"integrity\": \"sha1-ivHkwSISRMxiRZ+vOJQNTmRKVyM=\",\n          \"dev\": true\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"alphanum-sort\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz\",\n      \"integrity\": \"sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=\",\n      \"dev\": true\n    },\n    \"ansi-align\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/ansi-align/-/ansi-align-3.0.0.tgz\",\n      \"integrity\": \"sha1-tTazcc9ofKrvI2wY0+If43l0Z8s=\",\n      \"dev\": true,\n      \"requires\": {\n        \"string-width\": \"^3.0.0\"\n      }\n    },\n    \"ansi-colors\": {\n      \"version\": \"3.2.4\",\n      \"resolved\": \"https://npm.quanziapp.com/ansi-colors/-/ansi-colors-3.2.4.tgz\",\n      \"integrity\": \"sha1-46PaS/uubIapwoViXeEkojQCb78=\",\n      \"dev\": true\n    },\n    \"ansi-escapes\": {\n      \"version\": \"4.3.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz\",\n      \"integrity\": \"sha1-pcR8xDGB8fOP/XB2g3cA05VSKmE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"type-fest\": \"^0.11.0\"\n      }\n    },\n    \"ansi-html\": {\n      \"version\": \"0.0.7\",\n      \"resolved\": \"https://npm.quanziapp.com/ansi-html/-/ansi-html-0.0.7.tgz\",\n      \"integrity\": \"sha1-gTWEAhliqenm/QOflA0S9WynhZ4=\",\n      \"dev\": true\n    },\n    \"ansi-regex\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ansi-regex/-/ansi-regex-2.1.1.tgz\",\n      \"integrity\": \"sha1-w7M6te42DYbg5ijwRorn7yfWVN8=\",\n      \"dev\": true\n    },\n    \"ansi-styles\": {\n      \"version\": \"3.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ansi-styles/-/ansi-styles-3.2.1.tgz\",\n      \"integrity\": \"sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"color-convert\": \"^1.9.0\"\n      }\n    },\n    \"anymatch\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/anymatch/-/anymatch-2.0.0.tgz\",\n      \"integrity\": \"sha1-vLJLTzeTTZqnrBe0ra+J58du8us=\",\n      \"dev\": true,\n      \"requires\": {\n        \"micromatch\": \"^3.1.4\",\n        \"normalize-path\": \"^2.1.1\"\n      },\n      \"dependencies\": {\n        \"normalize-path\": {\n          \"version\": \"2.1.1\",\n          \"resolved\": \"https://npm.quanziapp.com/normalize-path/-/normalize-path-2.1.1.tgz\",\n          \"integrity\": \"sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=\",\n          \"dev\": true,\n          \"requires\": {\n            \"remove-trailing-separator\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"aproba\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/aproba/-/aproba-1.2.0.tgz\",\n      \"integrity\": \"sha1-aALmJk79GMeQobDVF/DyYnvyyUo=\",\n      \"dev\": true\n    },\n    \"argparse\": {\n      \"version\": \"1.0.10\",\n      \"resolved\": \"https://npm.quanziapp.com/argparse/-/argparse-1.0.10.tgz\",\n      \"integrity\": \"sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"sprintf-js\": \"~1.0.2\"\n      }\n    },\n    \"arr-diff\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/arr-diff/-/arr-diff-4.0.0.tgz\",\n      \"integrity\": \"sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=\",\n      \"dev\": true\n    },\n    \"arr-flatten\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/arr-flatten/-/arr-flatten-1.1.0.tgz\",\n      \"integrity\": \"sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=\",\n      \"dev\": true\n    },\n    \"arr-union\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/arr-union/-/arr-union-3.1.0.tgz\",\n      \"integrity\": \"sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=\",\n      \"dev\": true\n    },\n    \"array-flatten\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/array-flatten/-/array-flatten-2.1.2.tgz\",\n      \"integrity\": \"sha1-JO+AoowaiTYX4hSbDG0NeIKTsJk=\",\n      \"dev\": true\n    },\n    \"array-union\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/array-union/-/array-union-1.0.2.tgz\",\n      \"integrity\": \"sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"array-uniq\": \"^1.0.1\"\n      }\n    },\n    \"array-uniq\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/array-uniq/-/array-uniq-1.0.3.tgz\",\n      \"integrity\": \"sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=\",\n      \"dev\": true\n    },\n    \"array-unique\": {\n      \"version\": \"0.3.2\",\n      \"resolved\": \"https://npm.quanziapp.com/array-unique/-/array-unique-0.3.2.tgz\",\n      \"integrity\": \"sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=\",\n      \"dev\": true\n    },\n    \"asn1\": {\n      \"version\": \"0.2.4\",\n      \"resolved\": \"https://npm.quanziapp.com/asn1/-/asn1-0.2.4.tgz\",\n      \"integrity\": \"sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"safer-buffer\": \"~2.1.0\"\n      }\n    },\n    \"asn1.js\": {\n      \"version\": \"5.4.1\",\n      \"resolved\": \"https://npm.quanziapp.com/asn1.js/-/asn1.js-5.4.1.tgz\",\n      \"integrity\": \"sha1-EamAuE67kXgc41sP3C7ilON4Pwc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bn.js\": \"^4.0.0\",\n        \"inherits\": \"^2.0.1\",\n        \"minimalistic-assert\": \"^1.0.0\",\n        \"safer-buffer\": \"^2.1.0\"\n      },\n      \"dependencies\": {\n        \"bn.js\": {\n          \"version\": \"4.11.9\",\n          \"resolved\": \"https://npm.quanziapp.com/bn.js/-/bn.js-4.11.9.tgz\",\n          \"integrity\": \"sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"assert\": {\n      \"version\": \"1.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/assert/-/assert-1.5.0.tgz\",\n      \"integrity\": \"sha1-VcEJqvbgrv2z3EtxJAxwv1dLGOs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"object-assign\": \"^4.1.1\",\n        \"util\": \"0.10.3\"\n      },\n      \"dependencies\": {\n        \"inherits\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/inherits/-/inherits-2.0.1.tgz\",\n          \"integrity\": \"sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=\",\n          \"dev\": true\n        },\n        \"util\": {\n          \"version\": \"0.10.3\",\n          \"resolved\": \"https://npm.quanziapp.com/util/-/util-0.10.3.tgz\",\n          \"integrity\": \"sha1-evsa/lCAUkZInj23/g7TeTNqwPk=\",\n          \"dev\": true,\n          \"requires\": {\n            \"inherits\": \"2.0.1\"\n          }\n        }\n      }\n    },\n    \"assert-plus\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/assert-plus/-/assert-plus-1.0.0.tgz\",\n      \"integrity\": \"sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=\",\n      \"dev\": true\n    },\n    \"assign-symbols\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/assign-symbols/-/assign-symbols-1.0.0.tgz\",\n      \"integrity\": \"sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=\",\n      \"dev\": true\n    },\n    \"async\": {\n      \"version\": \"2.6.3\",\n      \"resolved\": \"https://npm.quanziapp.com/async/-/async-2.6.3.tgz\",\n      \"integrity\": \"sha1-1yYl4jRKNlbjo61Pp0n6gymdgv8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash\": \"^4.17.14\"\n      }\n    },\n    \"async-each\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/async-each/-/async-each-1.0.3.tgz\",\n      \"integrity\": \"sha1-tyfb+H12UWAvBvTUrDh/R9kbDL8=\",\n      \"dev\": true\n    },\n    \"async-limiter\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/async-limiter/-/async-limiter-1.0.1.tgz\",\n      \"integrity\": \"sha1-3TeelPDbgxCwgpH51kwyCXZmF/0=\",\n      \"dev\": true\n    },\n    \"asynckit\": {\n      \"version\": \"0.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/asynckit/-/asynckit-0.4.0.tgz\",\n      \"integrity\": \"sha1-x57Zf380y48robyXkLzDZkdLS3k=\",\n      \"dev\": true\n    },\n    \"atob\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/atob/-/atob-2.1.2.tgz\",\n      \"integrity\": \"sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=\",\n      \"dev\": true\n    },\n    \"autocomplete.js\": {\n      \"version\": \"0.36.0\",\n      \"resolved\": \"https://npm.quanziapp.com/autocomplete.js/-/autocomplete.js-0.36.0.tgz\",\n      \"integrity\": \"sha1-lP53X+ZLbNQuYi0Hbcf9Jr7dg3s=\",\n      \"dev\": true,\n      \"requires\": {\n        \"immediate\": \"^3.2.3\"\n      }\n    },\n    \"autoprefixer\": {\n      \"version\": \"9.8.6\",\n      \"resolved\": \"https://npm.quanziapp.com/autoprefixer/-/autoprefixer-9.8.6.tgz\",\n      \"integrity\": \"sha1-O3NZTKG/kmYyDFrPFYjXTep0IQ8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.12.0\",\n        \"caniuse-lite\": \"^1.0.30001109\",\n        \"colorette\": \"^1.2.1\",\n        \"normalize-range\": \"^0.1.2\",\n        \"num2fraction\": \"^1.2.2\",\n        \"postcss\": \"^7.0.32\",\n        \"postcss-value-parser\": \"^4.1.0\"\n      }\n    },\n    \"aws-sign2\": {\n      \"version\": \"0.7.0\",\n      \"resolved\": \"https://npm.quanziapp.com/aws-sign2/-/aws-sign2-0.7.0.tgz\",\n      \"integrity\": \"sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=\",\n      \"dev\": true\n    },\n    \"aws4\": {\n      \"version\": \"1.11.0\",\n      \"resolved\": \"https://npm.quanziapp.com/aws4/-/aws4-1.11.0.tgz\",\n      \"integrity\": \"sha1-1h9G2DslGSUOJ4Ta9bCUeai0HFk=\",\n      \"dev\": true\n    },\n    \"babel-loader\": {\n      \"version\": \"8.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/babel-loader/-/babel-loader-8.2.2.tgz\",\n      \"integrity\": \"sha1-k2POhMEMmkDmx1N0jhRBtgyKC4E=\",\n      \"dev\": true,\n      \"requires\": {\n        \"find-cache-dir\": \"^3.3.1\",\n        \"loader-utils\": \"^1.4.0\",\n        \"make-dir\": \"^3.1.0\",\n        \"schema-utils\": \"^2.6.5\"\n      }\n    },\n    \"babel-plugin-dynamic-import-node\": {\n      \"version\": \"2.3.3\",\n      \"resolved\": \"https://npm.quanziapp.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz\",\n      \"integrity\": \"sha1-hP2hnJduxcbe/vV/lCez3vZuF6M=\",\n      \"dev\": true,\n      \"requires\": {\n        \"object.assign\": \"^4.1.0\"\n      }\n    },\n    \"balanced-match\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/balanced-match/-/balanced-match-1.0.0.tgz\",\n      \"integrity\": \"sha1-ibTRmasr7kneFk6gK4nORi1xt2c=\",\n      \"dev\": true\n    },\n    \"base\": {\n      \"version\": \"0.11.2\",\n      \"resolved\": \"https://npm.quanziapp.com/base/-/base-0.11.2.tgz\",\n      \"integrity\": \"sha1-e95c7RRbbVUakNuH+DxVi060io8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cache-base\": \"^1.0.1\",\n        \"class-utils\": \"^0.3.5\",\n        \"component-emitter\": \"^1.2.1\",\n        \"define-property\": \"^1.0.0\",\n        \"isobject\": \"^3.0.1\",\n        \"mixin-deep\": \"^1.2.0\",\n        \"pascalcase\": \"^0.1.1\"\n      },\n      \"dependencies\": {\n        \"define-property\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-1.0.0.tgz\",\n          \"integrity\": \"sha1-dp66rz9KY6rTr56NMEybvnm/sOY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-descriptor\": \"^1.0.0\"\n          }\n        },\n        \"is-accessor-descriptor\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz\",\n          \"integrity\": \"sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"kind-of\": \"^6.0.0\"\n          }\n        },\n        \"is-data-descriptor\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz\",\n          \"integrity\": \"sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"kind-of\": \"^6.0.0\"\n          }\n        },\n        \"is-descriptor\": {\n          \"version\": \"1.0.2\",\n          \"resolved\": \"https://npm.quanziapp.com/is-descriptor/-/is-descriptor-1.0.2.tgz\",\n          \"integrity\": \"sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-accessor-descriptor\": \"^1.0.0\",\n            \"is-data-descriptor\": \"^1.0.0\",\n            \"kind-of\": \"^6.0.2\"\n          }\n        }\n      }\n    },\n    \"base64-js\": {\n      \"version\": \"1.5.1\",\n      \"resolved\": \"https://npm.quanziapp.com/base64-js/-/base64-js-1.5.1.tgz\",\n      \"integrity\": \"sha1-GxtEAWClv3rUC2UPCVljSBkDkwo=\",\n      \"dev\": true\n    },\n    \"batch\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://npm.quanziapp.com/batch/-/batch-0.6.1.tgz\",\n      \"integrity\": \"sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=\",\n      \"dev\": true\n    },\n    \"bcrypt-pbkdf\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz\",\n      \"integrity\": \"sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"tweetnacl\": \"^0.14.3\"\n      }\n    },\n    \"big.js\": {\n      \"version\": \"5.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/big.js/-/big.js-5.2.2.tgz\",\n      \"integrity\": \"sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=\",\n      \"dev\": true\n    },\n    \"binary-extensions\": {\n      \"version\": \"1.13.1\",\n      \"resolved\": \"https://npm.quanziapp.com/binary-extensions/-/binary-extensions-1.13.1.tgz\",\n      \"integrity\": \"sha1-WYr+VHVbKGilMw0q/51Ou1Mgm2U=\",\n      \"dev\": true\n    },\n    \"bindings\": {\n      \"version\": \"1.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/bindings/-/bindings-1.5.0.tgz\",\n      \"integrity\": \"sha1-EDU8npRTNLwFEabZCzj7x8nFBN8=\",\n      \"dev\": true,\n      \"optional\": true,\n      \"requires\": {\n        \"file-uri-to-path\": \"1.0.0\"\n      }\n    },\n    \"bluebird\": {\n      \"version\": \"3.7.2\",\n      \"resolved\": \"https://npm.quanziapp.com/bluebird/-/bluebird-3.7.2.tgz\",\n      \"integrity\": \"sha1-nyKcFb4nJFT/qXOs4NvueaGww28=\",\n      \"dev\": true\n    },\n    \"bn.js\": {\n      \"version\": \"5.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/bn.js/-/bn.js-5.1.3.tgz\",\n      \"integrity\": \"sha1-vsoAVAj2Quvr6oCwQrTRjSrA7ms=\",\n      \"dev\": true\n    },\n    \"body-parser\": {\n      \"version\": \"1.19.0\",\n      \"resolved\": \"https://npm.quanziapp.com/body-parser/-/body-parser-1.19.0.tgz\",\n      \"integrity\": \"sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bytes\": \"3.1.0\",\n        \"content-type\": \"~1.0.4\",\n        \"debug\": \"2.6.9\",\n        \"depd\": \"~1.1.2\",\n        \"http-errors\": \"1.7.2\",\n        \"iconv-lite\": \"0.4.24\",\n        \"on-finished\": \"~2.3.0\",\n        \"qs\": \"6.7.0\",\n        \"raw-body\": \"2.4.0\",\n        \"type-is\": \"~1.6.17\"\n      },\n      \"dependencies\": {\n        \"bytes\": {\n          \"version\": \"3.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/bytes/-/bytes-3.1.0.tgz\",\n          \"integrity\": \"sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=\",\n          \"dev\": true\n        },\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"bonjour\": {\n      \"version\": \"3.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/bonjour/-/bonjour-3.5.0.tgz\",\n      \"integrity\": \"sha1-jokKGD2O6aI5OzhExpGkK897yfU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"array-flatten\": \"^2.1.0\",\n        \"deep-equal\": \"^1.0.1\",\n        \"dns-equal\": \"^1.0.0\",\n        \"dns-txt\": \"^2.0.2\",\n        \"multicast-dns\": \"^6.0.1\",\n        \"multicast-dns-service-types\": \"^1.1.0\"\n      }\n    },\n    \"boolbase\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/boolbase/-/boolbase-1.0.0.tgz\",\n      \"integrity\": \"sha1-aN/1++YMUes3cl6p4+0xDcwed24=\",\n      \"dev\": true\n    },\n    \"boxen\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/boxen/-/boxen-4.2.0.tgz\",\n      \"integrity\": \"sha1-5BG2I1fW1tNlh8isPV2XTaoHDmQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-align\": \"^3.0.0\",\n        \"camelcase\": \"^5.3.1\",\n        \"chalk\": \"^3.0.0\",\n        \"cli-boxes\": \"^2.2.0\",\n        \"string-width\": \"^4.1.0\",\n        \"term-size\": \"^2.1.0\",\n        \"type-fest\": \"^0.8.1\",\n        \"widest-line\": \"^3.1.0\"\n      },\n      \"dependencies\": {\n        \"ansi-regex\": {\n          \"version\": \"5.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ansi-regex/-/ansi-regex-5.0.0.tgz\",\n          \"integrity\": \"sha1-OIU59VF5vzkznIGvMKZU1p+Hy3U=\",\n          \"dev\": true\n        },\n        \"ansi-styles\": {\n          \"version\": \"4.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ansi-styles/-/ansi-styles-4.3.0.tgz\",\n          \"integrity\": \"sha1-7dgDYornHATIWuegkG7a00tkiTc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"color-convert\": \"^2.0.1\"\n          }\n        },\n        \"camelcase\": {\n          \"version\": \"5.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/camelcase/-/camelcase-5.3.1.tgz\",\n          \"integrity\": \"sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA=\",\n          \"dev\": true\n        },\n        \"chalk\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/chalk/-/chalk-3.0.0.tgz\",\n          \"integrity\": \"sha1-P3PCv1JlkfV0zEksUeJFY0n4ROQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-styles\": \"^4.1.0\",\n            \"supports-color\": \"^7.1.0\"\n          }\n        },\n        \"color-convert\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/color-convert/-/color-convert-2.0.1.tgz\",\n          \"integrity\": \"sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"color-name\": \"~1.1.4\"\n          }\n        },\n        \"color-name\": {\n          \"version\": \"1.1.4\",\n          \"resolved\": \"https://npm.quanziapp.com/color-name/-/color-name-1.1.4.tgz\",\n          \"integrity\": \"sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=\",\n          \"dev\": true\n        },\n        \"emoji-regex\": {\n          \"version\": \"8.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/emoji-regex/-/emoji-regex-8.0.0.tgz\",\n          \"integrity\": \"sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc=\",\n          \"dev\": true\n        },\n        \"has-flag\": {\n          \"version\": \"4.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/has-flag/-/has-flag-4.0.0.tgz\",\n          \"integrity\": \"sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=\",\n          \"dev\": true\n        },\n        \"is-fullwidth-code-point\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz\",\n          \"integrity\": \"sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0=\",\n          \"dev\": true\n        },\n        \"string-width\": {\n          \"version\": \"4.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/string-width/-/string-width-4.2.0.tgz\",\n          \"integrity\": \"sha1-lSGCxGzHssMT0VluYjmSvRY7crU=\",\n          \"dev\": true,\n          \"requires\": {\n            \"emoji-regex\": \"^8.0.0\",\n            \"is-fullwidth-code-point\": \"^3.0.0\",\n            \"strip-ansi\": \"^6.0.0\"\n          }\n        },\n        \"strip-ansi\": {\n          \"version\": \"6.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/strip-ansi/-/strip-ansi-6.0.0.tgz\",\n          \"integrity\": \"sha1-CxVx3XZpzNTz4G4U7x7tJiJa5TI=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-regex\": \"^5.0.0\"\n          }\n        },\n        \"supports-color\": {\n          \"version\": \"7.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/supports-color/-/supports-color-7.2.0.tgz\",\n          \"integrity\": \"sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=\",\n          \"dev\": true,\n          \"requires\": {\n            \"has-flag\": \"^4.0.0\"\n          }\n        },\n        \"type-fest\": {\n          \"version\": \"0.8.1\",\n          \"resolved\": \"https://npm.quanziapp.com/type-fest/-/type-fest-0.8.1.tgz\",\n          \"integrity\": \"sha1-CeJJ696FHTseSNJ8EFREZn8XuD0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"brace-expansion\": {\n      \"version\": \"1.1.11\",\n      \"resolved\": \"https://npm.quanziapp.com/brace-expansion/-/brace-expansion-1.1.11.tgz\",\n      \"integrity\": \"sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"balanced-match\": \"^1.0.0\",\n        \"concat-map\": \"0.0.1\"\n      }\n    },\n    \"braces\": {\n      \"version\": \"2.3.2\",\n      \"resolved\": \"https://npm.quanziapp.com/braces/-/braces-2.3.2.tgz\",\n      \"integrity\": \"sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"arr-flatten\": \"^1.1.0\",\n        \"array-unique\": \"^0.3.2\",\n        \"extend-shallow\": \"^2.0.1\",\n        \"fill-range\": \"^4.0.0\",\n        \"isobject\": \"^3.0.1\",\n        \"repeat-element\": \"^1.1.2\",\n        \"snapdragon\": \"^0.8.1\",\n        \"snapdragon-node\": \"^2.0.1\",\n        \"split-string\": \"^3.0.2\",\n        \"to-regex\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"extend-shallow\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/extend-shallow/-/extend-shallow-2.0.1.tgz\",\n          \"integrity\": \"sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-extendable\": \"^0.1.0\"\n          }\n        }\n      }\n    },\n    \"brorand\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/brorand/-/brorand-1.1.0.tgz\",\n      \"integrity\": \"sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=\",\n      \"dev\": true\n    },\n    \"browserify-aes\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/browserify-aes/-/browserify-aes-1.2.0.tgz\",\n      \"integrity\": \"sha1-Mmc0ZC9APavDADIJhTu3CtQo70g=\",\n      \"dev\": true,\n      \"requires\": {\n        \"buffer-xor\": \"^1.0.3\",\n        \"cipher-base\": \"^1.0.0\",\n        \"create-hash\": \"^1.1.0\",\n        \"evp_bytestokey\": \"^1.0.3\",\n        \"inherits\": \"^2.0.1\",\n        \"safe-buffer\": \"^5.0.1\"\n      }\n    },\n    \"browserify-cipher\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz\",\n      \"integrity\": \"sha1-jWR0wbhwv9q807z8wZNKEOlPFfA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserify-aes\": \"^1.0.4\",\n        \"browserify-des\": \"^1.0.0\",\n        \"evp_bytestokey\": \"^1.0.0\"\n      }\n    },\n    \"browserify-des\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/browserify-des/-/browserify-des-1.0.2.tgz\",\n      \"integrity\": \"sha1-OvTx9Zg5QDVy8cZiBDdfen9wPpw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cipher-base\": \"^1.0.1\",\n        \"des.js\": \"^1.0.0\",\n        \"inherits\": \"^2.0.1\",\n        \"safe-buffer\": \"^5.1.2\"\n      }\n    },\n    \"browserify-rsa\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz\",\n      \"integrity\": \"sha1-sv0Gtbda4pf3zi3GUfkY9b4VjI0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bn.js\": \"^5.0.0\",\n        \"randombytes\": \"^2.0.1\"\n      }\n    },\n    \"browserify-sign\": {\n      \"version\": \"4.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/browserify-sign/-/browserify-sign-4.2.1.tgz\",\n      \"integrity\": \"sha1-6vSt1G3VS+O7OzbAzxWrvrp5VsM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bn.js\": \"^5.1.1\",\n        \"browserify-rsa\": \"^4.0.1\",\n        \"create-hash\": \"^1.2.0\",\n        \"create-hmac\": \"^1.1.7\",\n        \"elliptic\": \"^6.5.3\",\n        \"inherits\": \"^2.0.4\",\n        \"parse-asn1\": \"^5.1.5\",\n        \"readable-stream\": \"^3.6.0\",\n        \"safe-buffer\": \"^5.2.0\"\n      },\n      \"dependencies\": {\n        \"readable-stream\": {\n          \"version\": \"3.6.0\",\n          \"resolved\": \"https://npm.quanziapp.com/readable-stream/-/readable-stream-3.6.0.tgz\",\n          \"integrity\": \"sha1-M3u9o63AcGvT4CRCaihtS0sskZg=\",\n          \"dev\": true,\n          \"requires\": {\n            \"inherits\": \"^2.0.3\",\n            \"string_decoder\": \"^1.1.1\",\n            \"util-deprecate\": \"^1.0.1\"\n          }\n        },\n        \"safe-buffer\": {\n          \"version\": \"5.2.1\",\n          \"resolved\": \"https://npm.quanziapp.com/safe-buffer/-/safe-buffer-5.2.1.tgz\",\n          \"integrity\": \"sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"browserify-zlib\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz\",\n      \"integrity\": \"sha1-KGlFnZqjviRf6P4sofRuLn9U1z8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"pako\": \"~1.0.5\"\n      }\n    },\n    \"browserslist\": {\n      \"version\": \"4.15.0\",\n      \"resolved\": \"https://npm.quanziapp.com/browserslist/-/browserslist-4.15.0.tgz\",\n      \"integrity\": \"sha1-PUi7ymo/N46GEC/9AX2aA/EivbA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"caniuse-lite\": \"^1.0.30001164\",\n        \"colorette\": \"^1.2.1\",\n        \"electron-to-chromium\": \"^1.3.612\",\n        \"escalade\": \"^3.1.1\",\n        \"node-releases\": \"^1.1.67\"\n      }\n    },\n    \"buffer\": {\n      \"version\": \"4.9.2\",\n      \"resolved\": \"https://npm.quanziapp.com/buffer/-/buffer-4.9.2.tgz\",\n      \"integrity\": \"sha1-Iw6tNEACmIZEhBqwJEr4xEu+Pvg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"base64-js\": \"^1.0.2\",\n        \"ieee754\": \"^1.1.4\",\n        \"isarray\": \"^1.0.0\"\n      }\n    },\n    \"buffer-from\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/buffer-from/-/buffer-from-1.1.1.tgz\",\n      \"integrity\": \"sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=\",\n      \"dev\": true\n    },\n    \"buffer-indexof\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz\",\n      \"integrity\": \"sha1-Uvq8xqYG0aADAoAmSO9o9jnaJow=\",\n      \"dev\": true\n    },\n    \"buffer-json\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/buffer-json/-/buffer-json-2.0.0.tgz\",\n      \"integrity\": \"sha1-9z4TseQvGW/i/WfQAcfXEH7dfCM=\",\n      \"dev\": true\n    },\n    \"buffer-xor\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/buffer-xor/-/buffer-xor-1.0.3.tgz\",\n      \"integrity\": \"sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=\",\n      \"dev\": true\n    },\n    \"builtin-status-codes\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz\",\n      \"integrity\": \"sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=\",\n      \"dev\": true\n    },\n    \"bytes\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/bytes/-/bytes-3.0.0.tgz\",\n      \"integrity\": \"sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=\",\n      \"dev\": true\n    },\n    \"cac\": {\n      \"version\": \"6.6.1\",\n      \"resolved\": \"https://npm.quanziapp.com/cac/-/cac-6.6.1.tgz\",\n      \"integrity\": \"sha1-Pd4/aUP0XUKlZynqNXPAiz57am0=\",\n      \"dev\": true\n    },\n    \"cacache\": {\n      \"version\": \"12.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/cacache/-/cacache-12.0.4.tgz\",\n      \"integrity\": \"sha1-ZovL0QWutfHZL+JVcOyVJcj6pAw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bluebird\": \"^3.5.5\",\n        \"chownr\": \"^1.1.1\",\n        \"figgy-pudding\": \"^3.5.1\",\n        \"glob\": \"^7.1.4\",\n        \"graceful-fs\": \"^4.1.15\",\n        \"infer-owner\": \"^1.0.3\",\n        \"lru-cache\": \"^5.1.1\",\n        \"mississippi\": \"^3.0.0\",\n        \"mkdirp\": \"^0.5.1\",\n        \"move-concurrently\": \"^1.0.1\",\n        \"promise-inflight\": \"^1.0.1\",\n        \"rimraf\": \"^2.6.3\",\n        \"ssri\": \"^6.0.1\",\n        \"unique-filename\": \"^1.1.1\",\n        \"y18n\": \"^4.0.0\"\n      }\n    },\n    \"cache-base\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/cache-base/-/cache-base-1.0.1.tgz\",\n      \"integrity\": \"sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"collection-visit\": \"^1.0.0\",\n        \"component-emitter\": \"^1.2.1\",\n        \"get-value\": \"^2.0.6\",\n        \"has-value\": \"^1.0.0\",\n        \"isobject\": \"^3.0.1\",\n        \"set-value\": \"^2.0.0\",\n        \"to-object-path\": \"^0.3.0\",\n        \"union-value\": \"^1.0.0\",\n        \"unset-value\": \"^1.0.0\"\n      }\n    },\n    \"cache-loader\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/cache-loader/-/cache-loader-3.0.1.tgz\",\n      \"integrity\": \"sha1-zubPSzzcfGEJBbJrrWwvxDnIIa8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"buffer-json\": \"^2.0.0\",\n        \"find-cache-dir\": \"^2.1.0\",\n        \"loader-utils\": \"^1.2.3\",\n        \"mkdirp\": \"^0.5.1\",\n        \"neo-async\": \"^2.6.1\",\n        \"schema-utils\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"find-cache-dir\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz\",\n          \"integrity\": \"sha1-jQ+UzRP+Q8bHwmGg2GEVypGMBfc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"commondir\": \"^1.0.1\",\n            \"make-dir\": \"^2.0.0\",\n            \"pkg-dir\": \"^3.0.0\"\n          }\n        },\n        \"find-up\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/find-up/-/find-up-3.0.0.tgz\",\n          \"integrity\": \"sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=\",\n          \"dev\": true,\n          \"requires\": {\n            \"locate-path\": \"^3.0.0\"\n          }\n        },\n        \"locate-path\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/locate-path/-/locate-path-3.0.0.tgz\",\n          \"integrity\": \"sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-locate\": \"^3.0.0\",\n            \"path-exists\": \"^3.0.0\"\n          }\n        },\n        \"make-dir\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/make-dir/-/make-dir-2.1.0.tgz\",\n          \"integrity\": \"sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU=\",\n          \"dev\": true,\n          \"requires\": {\n            \"pify\": \"^4.0.1\",\n            \"semver\": \"^5.6.0\"\n          }\n        },\n        \"p-locate\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/p-locate/-/p-locate-3.0.0.tgz\",\n          \"integrity\": \"sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-limit\": \"^2.0.0\"\n          }\n        },\n        \"path-exists\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/path-exists/-/path-exists-3.0.0.tgz\",\n          \"integrity\": \"sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=\",\n          \"dev\": true\n        },\n        \"pkg-dir\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/pkg-dir/-/pkg-dir-3.0.0.tgz\",\n          \"integrity\": \"sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"find-up\": \"^3.0.0\"\n          }\n        },\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        }\n      }\n    },\n    \"cacheable-request\": {\n      \"version\": \"6.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/cacheable-request/-/cacheable-request-6.1.0.tgz\",\n      \"integrity\": \"sha1-IP+4vRYrpL4R6VZ9gj22UQUsqRI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"clone-response\": \"^1.0.2\",\n        \"get-stream\": \"^5.1.0\",\n        \"http-cache-semantics\": \"^4.0.0\",\n        \"keyv\": \"^3.0.0\",\n        \"lowercase-keys\": \"^2.0.0\",\n        \"normalize-url\": \"^4.1.0\",\n        \"responselike\": \"^1.0.2\"\n      },\n      \"dependencies\": {\n        \"get-stream\": {\n          \"version\": \"5.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/get-stream/-/get-stream-5.2.0.tgz\",\n          \"integrity\": \"sha1-SWaheV7lrOZecGxLe+txJX1uItM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"pump\": \"^3.0.0\"\n          }\n        },\n        \"lowercase-keys\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz\",\n          \"integrity\": \"sha1-JgPni3tLAAbLyi+8yKMgJVislHk=\",\n          \"dev\": true\n        },\n        \"normalize-url\": {\n          \"version\": \"4.5.0\",\n          \"resolved\": \"https://npm.quanziapp.com/normalize-url/-/normalize-url-4.5.0.tgz\",\n          \"integrity\": \"sha1-RTNUCH5sqWlXvY9br3U/WYIUISk=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"call-bind\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/call-bind/-/call-bind-1.0.0.tgz\",\n      \"integrity\": \"sha1-JBJwVLs/m9y0sfuCQYGGBy93uM4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"function-bind\": \"^1.1.1\",\n        \"get-intrinsic\": \"^1.0.0\"\n      }\n    },\n    \"call-me-maybe\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz\",\n      \"integrity\": \"sha1-JtII6onje1y95gJQoV8DHBak1ms=\",\n      \"dev\": true\n    },\n    \"caller-callsite\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/caller-callsite/-/caller-callsite-2.0.0.tgz\",\n      \"integrity\": \"sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"callsites\": \"^2.0.0\"\n      }\n    },\n    \"caller-path\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/caller-path/-/caller-path-2.0.0.tgz\",\n      \"integrity\": \"sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"caller-callsite\": \"^2.0.0\"\n      }\n    },\n    \"callsites\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/callsites/-/callsites-2.0.0.tgz\",\n      \"integrity\": \"sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=\",\n      \"dev\": true\n    },\n    \"camel-case\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/camel-case/-/camel-case-3.0.0.tgz\",\n      \"integrity\": \"sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=\",\n      \"dev\": true,\n      \"requires\": {\n        \"no-case\": \"^2.2.0\",\n        \"upper-case\": \"^1.1.1\"\n      }\n    },\n    \"camelcase\": {\n      \"version\": \"6.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/camelcase/-/camelcase-6.2.0.tgz\",\n      \"integrity\": \"sha1-kkr4gcnVJaydh/QNlk5c6pgqGAk=\",\n      \"dev\": true\n    },\n    \"caniuse-api\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/caniuse-api/-/caniuse-api-3.0.0.tgz\",\n      \"integrity\": \"sha1-Xk2Q4idJYdRikZl99Znj7QCO5MA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.0.0\",\n        \"caniuse-lite\": \"^1.0.0\",\n        \"lodash.memoize\": \"^4.1.2\",\n        \"lodash.uniq\": \"^4.5.0\"\n      }\n    },\n    \"caniuse-lite\": {\n      \"version\": \"1.0.30001165\",\n      \"resolved\": \"https://npm.quanziapp.com/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz\",\n      \"integrity\": \"sha1-MpVUkNL2ApC7GGu3VPKYGRf6dE8=\",\n      \"dev\": true\n    },\n    \"caseless\": {\n      \"version\": \"0.12.0\",\n      \"resolved\": \"https://npm.quanziapp.com/caseless/-/caseless-0.12.0.tgz\",\n      \"integrity\": \"sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=\",\n      \"dev\": true\n    },\n    \"chalk\": {\n      \"version\": \"2.4.2\",\n      \"resolved\": \"https://npm.quanziapp.com/chalk/-/chalk-2.4.2.tgz\",\n      \"integrity\": \"sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-styles\": \"^3.2.1\",\n        \"escape-string-regexp\": \"^1.0.5\",\n        \"supports-color\": \"^5.3.0\"\n      }\n    },\n    \"chokidar\": {\n      \"version\": \"2.1.8\",\n      \"resolved\": \"https://npm.quanziapp.com/chokidar/-/chokidar-2.1.8.tgz\",\n      \"integrity\": \"sha1-gEs6e2qZNYw8XGHnHYco8EHP+Rc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"anymatch\": \"^2.0.0\",\n        \"async-each\": \"^1.0.1\",\n        \"braces\": \"^2.3.2\",\n        \"fsevents\": \"^1.2.7\",\n        \"glob-parent\": \"^3.1.0\",\n        \"inherits\": \"^2.0.3\",\n        \"is-binary-path\": \"^1.0.0\",\n        \"is-glob\": \"^4.0.0\",\n        \"normalize-path\": \"^3.0.0\",\n        \"path-is-absolute\": \"^1.0.0\",\n        \"readdirp\": \"^2.2.1\",\n        \"upath\": \"^1.1.1\"\n      }\n    },\n    \"chownr\": {\n      \"version\": \"1.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/chownr/-/chownr-1.1.4.tgz\",\n      \"integrity\": \"sha1-b8nXtC0ypYNZYzdmbn0ICE2izGs=\",\n      \"dev\": true\n    },\n    \"chrome-trace-event\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz\",\n      \"integrity\": \"sha1-I0CQ7pfH1K0aLEvq4nUF3v/GCKQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"tslib\": \"^1.9.0\"\n      }\n    },\n    \"ci-info\": {\n      \"version\": \"1.6.0\",\n      \"resolved\": \"https://npm.quanziapp.com/ci-info/-/ci-info-1.6.0.tgz\",\n      \"integrity\": \"sha1-LKINu5zrMtRSSmgzAzE/AwSx5Jc=\",\n      \"dev\": true\n    },\n    \"cipher-base\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/cipher-base/-/cipher-base-1.0.4.tgz\",\n      \"integrity\": \"sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.1\",\n        \"safe-buffer\": \"^5.0.1\"\n      }\n    },\n    \"class-utils\": {\n      \"version\": \"0.3.6\",\n      \"resolved\": \"https://npm.quanziapp.com/class-utils/-/class-utils-0.3.6.tgz\",\n      \"integrity\": \"sha1-+TNprouafOAv1B+q0MqDAzGQxGM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"arr-union\": \"^3.1.0\",\n        \"define-property\": \"^0.2.5\",\n        \"isobject\": \"^3.0.0\",\n        \"static-extend\": \"^0.1.1\"\n      },\n      \"dependencies\": {\n        \"define-property\": {\n          \"version\": \"0.2.5\",\n          \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-0.2.5.tgz\",\n          \"integrity\": \"sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-descriptor\": \"^0.1.0\"\n          }\n        }\n      }\n    },\n    \"clean-css\": {\n      \"version\": \"4.2.3\",\n      \"resolved\": \"https://npm.quanziapp.com/clean-css/-/clean-css-4.2.3.tgz\",\n      \"integrity\": \"sha1-UHtd59l7SO5T2ErbAWD/YhY4D3g=\",\n      \"dev\": true,\n      \"requires\": {\n        \"source-map\": \"~0.6.0\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"cli-boxes\": {\n      \"version\": \"2.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/cli-boxes/-/cli-boxes-2.2.1.tgz\",\n      \"integrity\": \"sha1-3dUDXSUJT84iDpyrQKRYQKRAMY8=\",\n      \"dev\": true\n    },\n    \"clipboard\": {\n      \"version\": \"2.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/clipboard/-/clipboard-2.0.6.tgz\",\n      \"integrity\": \"sha1-UpISlu7A/fd+rRdJQhshyWhkc3Y=\",\n      \"dev\": true,\n      \"optional\": true,\n      \"requires\": {\n        \"good-listener\": \"^1.2.2\",\n        \"select\": \"^1.1.2\",\n        \"tiny-emitter\": \"^2.0.0\"\n      }\n    },\n    \"cliui\": {\n      \"version\": \"5.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/cliui/-/cliui-5.0.0.tgz\",\n      \"integrity\": \"sha1-3u/P2y6AB4SqNPRvoI4GhRx7u8U=\",\n      \"dev\": true,\n      \"requires\": {\n        \"string-width\": \"^3.1.0\",\n        \"strip-ansi\": \"^5.2.0\",\n        \"wrap-ansi\": \"^5.1.0\"\n      },\n      \"dependencies\": {\n        \"ansi-regex\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ansi-regex/-/ansi-regex-4.1.0.tgz\",\n          \"integrity\": \"sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=\",\n          \"dev\": true\n        },\n        \"strip-ansi\": {\n          \"version\": \"5.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/strip-ansi/-/strip-ansi-5.2.0.tgz\",\n          \"integrity\": \"sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-regex\": \"^4.1.0\"\n          }\n        }\n      }\n    },\n    \"clone-response\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/clone-response/-/clone-response-1.0.2.tgz\",\n      \"integrity\": \"sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=\",\n      \"dev\": true,\n      \"requires\": {\n        \"mimic-response\": \"^1.0.0\"\n      }\n    },\n    \"coa\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/coa/-/coa-2.0.2.tgz\",\n      \"integrity\": \"sha1-Q/bCEVG07yv1cYfbDXPeIp4+fsM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/q\": \"^1.5.1\",\n        \"chalk\": \"^2.4.1\",\n        \"q\": \"^1.1.2\"\n      }\n    },\n    \"collection-visit\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/collection-visit/-/collection-visit-1.0.0.tgz\",\n      \"integrity\": \"sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"map-visit\": \"^1.0.0\",\n        \"object-visit\": \"^1.0.0\"\n      }\n    },\n    \"color\": {\n      \"version\": \"3.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/color/-/color-3.1.3.tgz\",\n      \"integrity\": \"sha1-ymf7TnuX1hHc3jns7tQiBn2RWW4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"color-convert\": \"^1.9.1\",\n        \"color-string\": \"^1.5.4\"\n      }\n    },\n    \"color-convert\": {\n      \"version\": \"1.9.3\",\n      \"resolved\": \"https://npm.quanziapp.com/color-convert/-/color-convert-1.9.3.tgz\",\n      \"integrity\": \"sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"color-name\": \"1.1.3\"\n      }\n    },\n    \"color-name\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/color-name/-/color-name-1.1.3.tgz\",\n      \"integrity\": \"sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=\",\n      \"dev\": true\n    },\n    \"color-string\": {\n      \"version\": \"1.5.4\",\n      \"resolved\": \"https://npm.quanziapp.com/color-string/-/color-string-1.5.4.tgz\",\n      \"integrity\": \"sha1-3VHNJc/ulT0Tj+QAI3LMPQ5QTLY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"color-name\": \"^1.0.0\",\n        \"simple-swizzle\": \"^0.2.2\"\n      }\n    },\n    \"colorette\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/colorette/-/colorette-1.2.1.tgz\",\n      \"integrity\": \"sha1-TQuSEyXBT6+SYzCGpTbbbolWSxs=\",\n      \"dev\": true\n    },\n    \"combined-stream\": {\n      \"version\": \"1.0.8\",\n      \"resolved\": \"https://npm.quanziapp.com/combined-stream/-/combined-stream-1.0.8.tgz\",\n      \"integrity\": \"sha1-w9RaizT9cwYxoRCoolIGgrMdWn8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"delayed-stream\": \"~1.0.0\"\n      }\n    },\n    \"commander\": {\n      \"version\": \"2.17.1\",\n      \"resolved\": \"https://npm.quanziapp.com/commander/-/commander-2.17.1.tgz\",\n      \"integrity\": \"sha1-vXerfebelCBc6sxy8XFtKfIKd78=\",\n      \"dev\": true\n    },\n    \"commondir\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/commondir/-/commondir-1.0.1.tgz\",\n      \"integrity\": \"sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=\",\n      \"dev\": true\n    },\n    \"component-emitter\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/component-emitter/-/component-emitter-1.3.0.tgz\",\n      \"integrity\": \"sha1-FuQHD7qK4ptnnyIVhT7hgasuq8A=\",\n      \"dev\": true\n    },\n    \"compressible\": {\n      \"version\": \"2.0.18\",\n      \"resolved\": \"https://npm.quanziapp.com/compressible/-/compressible-2.0.18.tgz\",\n      \"integrity\": \"sha1-r1PMprBw1MPAdQ+9dyhqbXzEb7o=\",\n      \"dev\": true,\n      \"requires\": {\n        \"mime-db\": \">= 1.43.0 < 2\"\n      }\n    },\n    \"compression\": {\n      \"version\": \"1.7.4\",\n      \"resolved\": \"https://npm.quanziapp.com/compression/-/compression-1.7.4.tgz\",\n      \"integrity\": \"sha1-lVI+/xcMpXwpoMpB5v4TH0Hlu48=\",\n      \"dev\": true,\n      \"requires\": {\n        \"accepts\": \"~1.3.5\",\n        \"bytes\": \"3.0.0\",\n        \"compressible\": \"~2.0.16\",\n        \"debug\": \"2.6.9\",\n        \"on-headers\": \"~1.0.2\",\n        \"safe-buffer\": \"5.1.2\",\n        \"vary\": \"~1.1.2\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"concat-map\": {\n      \"version\": \"0.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/concat-map/-/concat-map-0.0.1.tgz\",\n      \"integrity\": \"sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=\",\n      \"dev\": true\n    },\n    \"concat-stream\": {\n      \"version\": \"1.6.2\",\n      \"resolved\": \"https://npm.quanziapp.com/concat-stream/-/concat-stream-1.6.2.tgz\",\n      \"integrity\": \"sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"buffer-from\": \"^1.0.0\",\n        \"inherits\": \"^2.0.3\",\n        \"readable-stream\": \"^2.2.2\",\n        \"typedarray\": \"^0.0.6\"\n      }\n    },\n    \"configstore\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/configstore/-/configstore-5.0.1.tgz\",\n      \"integrity\": \"sha1-02UCG130uYzdGH1qOw4/anzF7ZY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"dot-prop\": \"^5.2.0\",\n        \"graceful-fs\": \"^4.1.2\",\n        \"make-dir\": \"^3.0.0\",\n        \"unique-string\": \"^2.0.0\",\n        \"write-file-atomic\": \"^3.0.0\",\n        \"xdg-basedir\": \"^4.0.0\"\n      }\n    },\n    \"connect-history-api-fallback\": {\n      \"version\": \"1.6.0\",\n      \"resolved\": \"https://npm.quanziapp.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz\",\n      \"integrity\": \"sha1-izIIk1kwjRERFdgcrT/Oq4iPl7w=\",\n      \"dev\": true\n    },\n    \"consola\": {\n      \"version\": \"2.15.0\",\n      \"resolved\": \"https://npm.quanziapp.com/consola/-/consola-2.15.0.tgz\",\n      \"integrity\": \"sha1-QPxO76TS+O8uKAYUfwVuogf8wOk=\",\n      \"dev\": true\n    },\n    \"console-browserify\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/console-browserify/-/console-browserify-1.2.0.tgz\",\n      \"integrity\": \"sha1-ZwY871fOts9Jk6KrOlWECujEkzY=\",\n      \"dev\": true\n    },\n    \"consolidate\": {\n      \"version\": \"0.15.1\",\n      \"resolved\": \"https://npm.quanziapp.com/consolidate/-/consolidate-0.15.1.tgz\",\n      \"integrity\": \"sha1-IasEMjXHGgfUXZqtmFk7DbpWurc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bluebird\": \"^3.1.1\"\n      }\n    },\n    \"constants-browserify\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/constants-browserify/-/constants-browserify-1.0.0.tgz\",\n      \"integrity\": \"sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=\",\n      \"dev\": true\n    },\n    \"content-disposition\": {\n      \"version\": \"0.5.3\",\n      \"resolved\": \"https://npm.quanziapp.com/content-disposition/-/content-disposition-0.5.3.tgz\",\n      \"integrity\": \"sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=\",\n      \"dev\": true,\n      \"requires\": {\n        \"safe-buffer\": \"5.1.2\"\n      }\n    },\n    \"content-type\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/content-type/-/content-type-1.0.4.tgz\",\n      \"integrity\": \"sha1-4TjMdeBAxyexlm/l5fjJruJW/js=\",\n      \"dev\": true\n    },\n    \"convert-source-map\": {\n      \"version\": \"1.7.0\",\n      \"resolved\": \"https://npm.quanziapp.com/convert-source-map/-/convert-source-map-1.7.0.tgz\",\n      \"integrity\": \"sha1-F6LLiC1/d9NJBYXizmxSRCSjpEI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"safe-buffer\": \"~5.1.1\"\n      }\n    },\n    \"cookie\": {\n      \"version\": \"0.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/cookie/-/cookie-0.4.0.tgz\",\n      \"integrity\": \"sha1-vrQ35wIrO21JAZ0IhmUwPr6cFLo=\",\n      \"dev\": true\n    },\n    \"cookie-signature\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/cookie-signature/-/cookie-signature-1.0.6.tgz\",\n      \"integrity\": \"sha1-4wOogrNCzD7oylE6eZmXNNqzriw=\",\n      \"dev\": true\n    },\n    \"copy-concurrently\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz\",\n      \"integrity\": \"sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"aproba\": \"^1.1.1\",\n        \"fs-write-stream-atomic\": \"^1.0.8\",\n        \"iferr\": \"^0.1.5\",\n        \"mkdirp\": \"^0.5.1\",\n        \"rimraf\": \"^2.5.4\",\n        \"run-queue\": \"^1.0.0\"\n      }\n    },\n    \"copy-descriptor\": {\n      \"version\": \"0.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz\",\n      \"integrity\": \"sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=\",\n      \"dev\": true\n    },\n    \"copy-webpack-plugin\": {\n      \"version\": \"5.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz\",\n      \"integrity\": \"sha1-ioieHcr6bJHGzUvhrRWPHTgjuuI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cacache\": \"^12.0.3\",\n        \"find-cache-dir\": \"^2.1.0\",\n        \"glob-parent\": \"^3.1.0\",\n        \"globby\": \"^7.1.1\",\n        \"is-glob\": \"^4.0.1\",\n        \"loader-utils\": \"^1.2.3\",\n        \"minimatch\": \"^3.0.4\",\n        \"normalize-path\": \"^3.0.0\",\n        \"p-limit\": \"^2.2.1\",\n        \"schema-utils\": \"^1.0.0\",\n        \"serialize-javascript\": \"^4.0.0\",\n        \"webpack-log\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"find-cache-dir\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz\",\n          \"integrity\": \"sha1-jQ+UzRP+Q8bHwmGg2GEVypGMBfc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"commondir\": \"^1.0.1\",\n            \"make-dir\": \"^2.0.0\",\n            \"pkg-dir\": \"^3.0.0\"\n          }\n        },\n        \"find-up\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/find-up/-/find-up-3.0.0.tgz\",\n          \"integrity\": \"sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=\",\n          \"dev\": true,\n          \"requires\": {\n            \"locate-path\": \"^3.0.0\"\n          }\n        },\n        \"globby\": {\n          \"version\": \"7.1.1\",\n          \"resolved\": \"https://npm.quanziapp.com/globby/-/globby-7.1.1.tgz\",\n          \"integrity\": \"sha1-+yzP+UAfhgCUXfral0QMypcrhoA=\",\n          \"dev\": true,\n          \"requires\": {\n            \"array-union\": \"^1.0.1\",\n            \"dir-glob\": \"^2.0.0\",\n            \"glob\": \"^7.1.2\",\n            \"ignore\": \"^3.3.5\",\n            \"pify\": \"^3.0.0\",\n            \"slash\": \"^1.0.0\"\n          },\n          \"dependencies\": {\n            \"pify\": {\n              \"version\": \"3.0.0\",\n              \"resolved\": \"https://npm.quanziapp.com/pify/-/pify-3.0.0.tgz\",\n              \"integrity\": \"sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=\",\n              \"dev\": true\n            }\n          }\n        },\n        \"ignore\": {\n          \"version\": \"3.3.10\",\n          \"resolved\": \"https://npm.quanziapp.com/ignore/-/ignore-3.3.10.tgz\",\n          \"integrity\": \"sha1-Cpf7h2mG6AgcYxFg+PnziRV/AEM=\",\n          \"dev\": true\n        },\n        \"locate-path\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/locate-path/-/locate-path-3.0.0.tgz\",\n          \"integrity\": \"sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-locate\": \"^3.0.0\",\n            \"path-exists\": \"^3.0.0\"\n          }\n        },\n        \"make-dir\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/make-dir/-/make-dir-2.1.0.tgz\",\n          \"integrity\": \"sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU=\",\n          \"dev\": true,\n          \"requires\": {\n            \"pify\": \"^4.0.1\",\n            \"semver\": \"^5.6.0\"\n          }\n        },\n        \"p-locate\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/p-locate/-/p-locate-3.0.0.tgz\",\n          \"integrity\": \"sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-limit\": \"^2.0.0\"\n          }\n        },\n        \"path-exists\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/path-exists/-/path-exists-3.0.0.tgz\",\n          \"integrity\": \"sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=\",\n          \"dev\": true\n        },\n        \"pkg-dir\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/pkg-dir/-/pkg-dir-3.0.0.tgz\",\n          \"integrity\": \"sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"find-up\": \"^3.0.0\"\n          }\n        },\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        },\n        \"slash\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/slash/-/slash-1.0.0.tgz\",\n          \"integrity\": \"sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"core-js\": {\n      \"version\": \"3.8.1\",\n      \"resolved\": \"https://npm.quanziapp.com/core-js/-/core-js-3.8.1.tgz\",\n      \"integrity\": \"sha1-9RUjZorIopTRKFw7nbRAJf2mbUc=\",\n      \"dev\": true\n    },\n    \"core-js-compat\": {\n      \"version\": \"3.8.1\",\n      \"resolved\": \"https://npm.quanziapp.com/core-js-compat/-/core-js-compat-3.8.1.tgz\",\n      \"integrity\": \"sha1-jR3dNB1mC6YZTL4M5g9MeUyHo24=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.15.0\",\n        \"semver\": \"7.0.0\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"7.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-7.0.0.tgz\",\n          \"integrity\": \"sha1-XzyjV2HkfgWyBsba/yz4FPAxa44=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"core-util-is\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/core-util-is/-/core-util-is-1.0.2.tgz\",\n      \"integrity\": \"sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=\",\n      \"dev\": true\n    },\n    \"cosmiconfig\": {\n      \"version\": \"5.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz\",\n      \"integrity\": \"sha1-BA9yaAnFked6F8CjYmykW08Wixo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"import-fresh\": \"^2.0.0\",\n        \"is-directory\": \"^0.3.1\",\n        \"js-yaml\": \"^3.13.1\",\n        \"parse-json\": \"^4.0.0\"\n      }\n    },\n    \"create-ecdh\": {\n      \"version\": \"4.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/create-ecdh/-/create-ecdh-4.0.4.tgz\",\n      \"integrity\": \"sha1-1uf0v/pmc2CFoHYv06YyaE2rzE4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bn.js\": \"^4.1.0\",\n        \"elliptic\": \"^6.5.3\"\n      },\n      \"dependencies\": {\n        \"bn.js\": {\n          \"version\": \"4.11.9\",\n          \"resolved\": \"https://npm.quanziapp.com/bn.js/-/bn.js-4.11.9.tgz\",\n          \"integrity\": \"sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"create-hash\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/create-hash/-/create-hash-1.2.0.tgz\",\n      \"integrity\": \"sha1-iJB4rxGmN1a8+1m9IhmWvjqe8ZY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cipher-base\": \"^1.0.1\",\n        \"inherits\": \"^2.0.1\",\n        \"md5.js\": \"^1.3.4\",\n        \"ripemd160\": \"^2.0.1\",\n        \"sha.js\": \"^2.4.0\"\n      }\n    },\n    \"create-hmac\": {\n      \"version\": \"1.1.7\",\n      \"resolved\": \"https://npm.quanziapp.com/create-hmac/-/create-hmac-1.1.7.tgz\",\n      \"integrity\": \"sha1-aRcMeLOrlXFHsriwRXLkfq0iQ/8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cipher-base\": \"^1.0.3\",\n        \"create-hash\": \"^1.1.0\",\n        \"inherits\": \"^2.0.1\",\n        \"ripemd160\": \"^2.0.0\",\n        \"safe-buffer\": \"^5.0.1\",\n        \"sha.js\": \"^2.4.8\"\n      }\n    },\n    \"cross-spawn\": {\n      \"version\": \"6.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/cross-spawn/-/cross-spawn-6.0.5.tgz\",\n      \"integrity\": \"sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q=\",\n      \"dev\": true,\n      \"requires\": {\n        \"nice-try\": \"^1.0.4\",\n        \"path-key\": \"^2.0.1\",\n        \"semver\": \"^5.5.0\",\n        \"shebang-command\": \"^1.2.0\",\n        \"which\": \"^1.2.9\"\n      }\n    },\n    \"crypto-browserify\": {\n      \"version\": \"3.12.0\",\n      \"resolved\": \"https://npm.quanziapp.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz\",\n      \"integrity\": \"sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserify-cipher\": \"^1.0.0\",\n        \"browserify-sign\": \"^4.0.0\",\n        \"create-ecdh\": \"^4.0.0\",\n        \"create-hash\": \"^1.1.0\",\n        \"create-hmac\": \"^1.1.0\",\n        \"diffie-hellman\": \"^5.0.0\",\n        \"inherits\": \"^2.0.1\",\n        \"pbkdf2\": \"^3.0.3\",\n        \"public-encrypt\": \"^4.0.0\",\n        \"randombytes\": \"^2.0.0\",\n        \"randomfill\": \"^1.0.3\"\n      }\n    },\n    \"crypto-random-string\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz\",\n      \"integrity\": \"sha1-7yp6lm7BEIM4g2m6oC6+rSKbMNU=\",\n      \"dev\": true\n    },\n    \"css\": {\n      \"version\": \"2.2.4\",\n      \"resolved\": \"https://npm.quanziapp.com/css/-/css-2.2.4.tgz\",\n      \"integrity\": \"sha1-xkZ1XHOXHyu6amAeLPL9cbEpiSk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.3\",\n        \"source-map\": \"^0.6.1\",\n        \"source-map-resolve\": \"^0.5.2\",\n        \"urix\": \"^0.1.0\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"css-color-names\": {\n      \"version\": \"0.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/css-color-names/-/css-color-names-0.0.4.tgz\",\n      \"integrity\": \"sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=\",\n      \"dev\": true\n    },\n    \"css-declaration-sorter\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz\",\n      \"integrity\": \"sha1-wZiUD2OnbX42wecQGLABchBUyyI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.1\",\n        \"timsort\": \"^0.3.0\"\n      }\n    },\n    \"css-loader\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/css-loader/-/css-loader-2.1.1.tgz\",\n      \"integrity\": \"sha1-2CVPcuQSuyI4u0TdZ0/770lzM+o=\",\n      \"dev\": true,\n      \"requires\": {\n        \"camelcase\": \"^5.2.0\",\n        \"icss-utils\": \"^4.1.0\",\n        \"loader-utils\": \"^1.2.3\",\n        \"normalize-path\": \"^3.0.0\",\n        \"postcss\": \"^7.0.14\",\n        \"postcss-modules-extract-imports\": \"^2.0.0\",\n        \"postcss-modules-local-by-default\": \"^2.0.6\",\n        \"postcss-modules-scope\": \"^2.1.0\",\n        \"postcss-modules-values\": \"^2.0.0\",\n        \"postcss-value-parser\": \"^3.3.0\",\n        \"schema-utils\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"camelcase\": {\n          \"version\": \"5.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/camelcase/-/camelcase-5.3.1.tgz\",\n          \"integrity\": \"sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA=\",\n          \"dev\": true\n        },\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        },\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        }\n      }\n    },\n    \"css-parse\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/css-parse/-/css-parse-2.0.0.tgz\",\n      \"integrity\": \"sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=\",\n      \"dev\": true,\n      \"requires\": {\n        \"css\": \"^2.0.0\"\n      }\n    },\n    \"css-select\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/css-select/-/css-select-2.1.0.tgz\",\n      \"integrity\": \"sha1-ajRlM1ZjWTSoG6ymjQJVQyEF2+8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"boolbase\": \"^1.0.0\",\n        \"css-what\": \"^3.2.1\",\n        \"domutils\": \"^1.7.0\",\n        \"nth-check\": \"^1.0.2\"\n      }\n    },\n    \"css-select-base-adapter\": {\n      \"version\": \"0.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz\",\n      \"integrity\": \"sha1-Oy/0lyzDYquIVhUHqVQIoUMhNdc=\",\n      \"dev\": true\n    },\n    \"css-tree\": {\n      \"version\": \"1.0.0-alpha.37\",\n      \"resolved\": \"https://npm.quanziapp.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz\",\n      \"integrity\": \"sha1-mL69YsTB2flg7DQM+fdSLjBwmiI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"mdn-data\": \"2.0.4\",\n        \"source-map\": \"^0.6.1\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"css-what\": {\n      \"version\": \"3.4.2\",\n      \"resolved\": \"https://npm.quanziapp.com/css-what/-/css-what-3.4.2.tgz\",\n      \"integrity\": \"sha1-6nAm/LAXd+295SEk4h8yfnrpUOQ=\",\n      \"dev\": true\n    },\n    \"cssesc\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/cssesc/-/cssesc-3.0.0.tgz\",\n      \"integrity\": \"sha1-N3QZGZA7hoVl4cCep0dEXNGJg+4=\",\n      \"dev\": true\n    },\n    \"cssnano\": {\n      \"version\": \"4.1.10\",\n      \"resolved\": \"https://npm.quanziapp.com/cssnano/-/cssnano-4.1.10.tgz\",\n      \"integrity\": \"sha1-CsQfCxPRPUZUh+ERt3jULaYxuLI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cosmiconfig\": \"^5.0.0\",\n        \"cssnano-preset-default\": \"^4.0.7\",\n        \"is-resolvable\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\"\n      }\n    },\n    \"cssnano-preset-default\": {\n      \"version\": \"4.0.7\",\n      \"resolved\": \"https://npm.quanziapp.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz\",\n      \"integrity\": \"sha1-UexmLM/KD4izltzZZ5zbkxvhf3Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"css-declaration-sorter\": \"^4.0.1\",\n        \"cssnano-util-raw-cache\": \"^4.0.1\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-calc\": \"^7.0.1\",\n        \"postcss-colormin\": \"^4.0.3\",\n        \"postcss-convert-values\": \"^4.0.1\",\n        \"postcss-discard-comments\": \"^4.0.2\",\n        \"postcss-discard-duplicates\": \"^4.0.2\",\n        \"postcss-discard-empty\": \"^4.0.1\",\n        \"postcss-discard-overridden\": \"^4.0.1\",\n        \"postcss-merge-longhand\": \"^4.0.11\",\n        \"postcss-merge-rules\": \"^4.0.3\",\n        \"postcss-minify-font-values\": \"^4.0.2\",\n        \"postcss-minify-gradients\": \"^4.0.2\",\n        \"postcss-minify-params\": \"^4.0.2\",\n        \"postcss-minify-selectors\": \"^4.0.2\",\n        \"postcss-normalize-charset\": \"^4.0.1\",\n        \"postcss-normalize-display-values\": \"^4.0.2\",\n        \"postcss-normalize-positions\": \"^4.0.2\",\n        \"postcss-normalize-repeat-style\": \"^4.0.2\",\n        \"postcss-normalize-string\": \"^4.0.2\",\n        \"postcss-normalize-timing-functions\": \"^4.0.2\",\n        \"postcss-normalize-unicode\": \"^4.0.1\",\n        \"postcss-normalize-url\": \"^4.0.1\",\n        \"postcss-normalize-whitespace\": \"^4.0.2\",\n        \"postcss-ordered-values\": \"^4.1.2\",\n        \"postcss-reduce-initial\": \"^4.0.3\",\n        \"postcss-reduce-transforms\": \"^4.0.2\",\n        \"postcss-svgo\": \"^4.0.2\",\n        \"postcss-unique-selectors\": \"^4.0.1\"\n      }\n    },\n    \"cssnano-util-get-arguments\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz\",\n      \"integrity\": \"sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=\",\n      \"dev\": true\n    },\n    \"cssnano-util-get-match\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz\",\n      \"integrity\": \"sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=\",\n      \"dev\": true\n    },\n    \"cssnano-util-raw-cache\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz\",\n      \"integrity\": \"sha1-sm1f1fcqEd/np4RvtMZyYPlr8oI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\"\n      }\n    },\n    \"cssnano-util-same-parent\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz\",\n      \"integrity\": \"sha1-V0CC+yhZ0ttDOFWDXZqEVuoYu/M=\",\n      \"dev\": true\n    },\n    \"csso\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/csso/-/csso-4.2.0.tgz\",\n      \"integrity\": \"sha1-6jpWE0bo3J9UbW/r7dUBh884lSk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"css-tree\": \"^1.1.2\"\n      },\n      \"dependencies\": {\n        \"css-tree\": {\n          \"version\": \"1.1.2\",\n          \"resolved\": \"https://npm.quanziapp.com/css-tree/-/css-tree-1.1.2.tgz\",\n          \"integrity\": \"sha1-muOTtdr9fa6KYiR1yux409j717U=\",\n          \"dev\": true,\n          \"requires\": {\n            \"mdn-data\": \"2.0.14\",\n            \"source-map\": \"^0.6.1\"\n          }\n        },\n        \"mdn-data\": {\n          \"version\": \"2.0.14\",\n          \"resolved\": \"https://npm.quanziapp.com/mdn-data/-/mdn-data-2.0.14.tgz\",\n          \"integrity\": \"sha1-cRP8QoGRfWPOKbQ0RvcB5owlulA=\",\n          \"dev\": true\n        },\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"cyclist\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/cyclist/-/cyclist-1.0.1.tgz\",\n      \"integrity\": \"sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=\",\n      \"dev\": true\n    },\n    \"dashdash\": {\n      \"version\": \"1.14.1\",\n      \"resolved\": \"https://npm.quanziapp.com/dashdash/-/dashdash-1.14.1.tgz\",\n      \"integrity\": \"sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"assert-plus\": \"^1.0.0\"\n      }\n    },\n    \"de-indent\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/de-indent/-/de-indent-1.0.2.tgz\",\n      \"integrity\": \"sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=\",\n      \"dev\": true\n    },\n    \"debug\": {\n      \"version\": \"4.3.1\",\n      \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-4.3.1.tgz\",\n      \"integrity\": \"sha1-8NIpxQXgxtjEmsVT0bE9wYP2su4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ms\": \"2.1.2\"\n      }\n    },\n    \"decamelize\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/decamelize/-/decamelize-1.2.0.tgz\",\n      \"integrity\": \"sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=\",\n      \"dev\": true\n    },\n    \"decode-uri-component\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz\",\n      \"integrity\": \"sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=\",\n      \"dev\": true\n    },\n    \"decompress-response\": {\n      \"version\": \"3.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/decompress-response/-/decompress-response-3.3.0.tgz\",\n      \"integrity\": \"sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=\",\n      \"dev\": true,\n      \"requires\": {\n        \"mimic-response\": \"^1.0.0\"\n      }\n    },\n    \"deep-equal\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/deep-equal/-/deep-equal-1.1.1.tgz\",\n      \"integrity\": \"sha1-tcmMlCzv+vfLBR4k4UNKJaLmB2o=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-arguments\": \"^1.0.4\",\n        \"is-date-object\": \"^1.0.1\",\n        \"is-regex\": \"^1.0.4\",\n        \"object-is\": \"^1.0.1\",\n        \"object-keys\": \"^1.1.1\",\n        \"regexp.prototype.flags\": \"^1.2.0\"\n      }\n    },\n    \"deep-extend\": {\n      \"version\": \"0.6.0\",\n      \"resolved\": \"https://npm.quanziapp.com/deep-extend/-/deep-extend-0.6.0.tgz\",\n      \"integrity\": \"sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw=\",\n      \"dev\": true\n    },\n    \"deepmerge\": {\n      \"version\": \"1.5.2\",\n      \"resolved\": \"https://npm.quanziapp.com/deepmerge/-/deepmerge-1.5.2.tgz\",\n      \"integrity\": \"sha1-EEmdhohEza1P7ghC34x/bwyVp1M=\",\n      \"dev\": true\n    },\n    \"default-gateway\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/default-gateway/-/default-gateway-4.2.0.tgz\",\n      \"integrity\": \"sha1-FnEEx1AMIRX23WmwpTa7jtcgVSs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"execa\": \"^1.0.0\",\n        \"ip-regex\": \"^2.1.0\"\n      }\n    },\n    \"defer-to-connect\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz\",\n      \"integrity\": \"sha1-MxrgUMCNz3ifjIOnuB8O2U9KxZE=\",\n      \"dev\": true\n    },\n    \"define-properties\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/define-properties/-/define-properties-1.1.3.tgz\",\n      \"integrity\": \"sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"object-keys\": \"^1.0.12\"\n      }\n    },\n    \"define-property\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-2.0.2.tgz\",\n      \"integrity\": \"sha1-1Flono1lS6d+AqgX+HENcCyxbp0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-descriptor\": \"^1.0.2\",\n        \"isobject\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"is-accessor-descriptor\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz\",\n          \"integrity\": \"sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"kind-of\": \"^6.0.0\"\n          }\n        },\n        \"is-data-descriptor\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz\",\n          \"integrity\": \"sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"kind-of\": \"^6.0.0\"\n          }\n        },\n        \"is-descriptor\": {\n          \"version\": \"1.0.2\",\n          \"resolved\": \"https://npm.quanziapp.com/is-descriptor/-/is-descriptor-1.0.2.tgz\",\n          \"integrity\": \"sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-accessor-descriptor\": \"^1.0.0\",\n            \"is-data-descriptor\": \"^1.0.0\",\n            \"kind-of\": \"^6.0.2\"\n          }\n        }\n      }\n    },\n    \"del\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/del/-/del-4.1.1.tgz\",\n      \"integrity\": \"sha1-no8RciLqRKMf86FWwEm5kFKp8LQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/glob\": \"^7.1.1\",\n        \"globby\": \"^6.1.0\",\n        \"is-path-cwd\": \"^2.0.0\",\n        \"is-path-in-cwd\": \"^2.0.0\",\n        \"p-map\": \"^2.0.0\",\n        \"pify\": \"^4.0.1\",\n        \"rimraf\": \"^2.6.3\"\n      },\n      \"dependencies\": {\n        \"globby\": {\n          \"version\": \"6.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/globby/-/globby-6.1.0.tgz\",\n          \"integrity\": \"sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=\",\n          \"dev\": true,\n          \"requires\": {\n            \"array-union\": \"^1.0.1\",\n            \"glob\": \"^7.0.3\",\n            \"object-assign\": \"^4.0.1\",\n            \"pify\": \"^2.0.0\",\n            \"pinkie-promise\": \"^2.0.0\"\n          },\n          \"dependencies\": {\n            \"pify\": {\n              \"version\": \"2.3.0\",\n              \"resolved\": \"https://npm.quanziapp.com/pify/-/pify-2.3.0.tgz\",\n              \"integrity\": \"sha1-7RQaasBDqEnqWISY59yosVMw6Qw=\",\n              \"dev\": true\n            }\n          }\n        }\n      }\n    },\n    \"delayed-stream\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/delayed-stream/-/delayed-stream-1.0.0.tgz\",\n      \"integrity\": \"sha1-3zrhmayt+31ECqrgsp4icrJOxhk=\",\n      \"dev\": true\n    },\n    \"delegate\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/delegate/-/delegate-3.2.0.tgz\",\n      \"integrity\": \"sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=\",\n      \"dev\": true,\n      \"optional\": true\n    },\n    \"depd\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/depd/-/depd-1.1.2.tgz\",\n      \"integrity\": \"sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=\",\n      \"dev\": true\n    },\n    \"des.js\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/des.js/-/des.js-1.0.1.tgz\",\n      \"integrity\": \"sha1-U4IULhvcU/hdhtU+X0qn3rkeCEM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.1\",\n        \"minimalistic-assert\": \"^1.0.0\"\n      }\n    },\n    \"destroy\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/destroy/-/destroy-1.0.4.tgz\",\n      \"integrity\": \"sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=\",\n      \"dev\": true\n    },\n    \"detect-node\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/detect-node/-/detect-node-2.0.4.tgz\",\n      \"integrity\": \"sha1-AU7o+PZpxcWAI9pkuBecCDooxGw=\",\n      \"dev\": true\n    },\n    \"diffie-hellman\": {\n      \"version\": \"5.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz\",\n      \"integrity\": \"sha1-QOjumPVaIUlgcUaSHGPhrl89KHU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bn.js\": \"^4.1.0\",\n        \"miller-rabin\": \"^4.0.0\",\n        \"randombytes\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"bn.js\": {\n          \"version\": \"4.11.9\",\n          \"resolved\": \"https://npm.quanziapp.com/bn.js/-/bn.js-4.11.9.tgz\",\n          \"integrity\": \"sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"dir-glob\": {\n      \"version\": \"2.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/dir-glob/-/dir-glob-2.2.2.tgz\",\n      \"integrity\": \"sha1-+gnwaUFTyJGLGLoN6vrpR2n8UMQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"path-type\": \"^3.0.0\"\n      }\n    },\n    \"dns-equal\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/dns-equal/-/dns-equal-1.0.0.tgz\",\n      \"integrity\": \"sha1-s55/HabrCnW6nBcySzR1PEfgZU0=\",\n      \"dev\": true\n    },\n    \"dns-packet\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://npm.quanziapp.com/dns-packet/-/dns-packet-1.3.1.tgz\",\n      \"integrity\": \"sha1-EqpCaYEHW+UAuRDu3NC0fdfe2lo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ip\": \"^1.1.0\",\n        \"safe-buffer\": \"^5.0.1\"\n      }\n    },\n    \"dns-txt\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/dns-txt/-/dns-txt-2.0.2.tgz\",\n      \"integrity\": \"sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"buffer-indexof\": \"^1.0.0\"\n      }\n    },\n    \"docsearch.js\": {\n      \"version\": \"2.6.3\",\n      \"resolved\": \"https://npm.quanziapp.com/docsearch.js/-/docsearch.js-2.6.3.tgz\",\n      \"integrity\": \"sha1-V8tGANO2VTxnfny75qc0WT44Yl0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"algoliasearch\": \"^3.24.5\",\n        \"autocomplete.js\": \"0.36.0\",\n        \"hogan.js\": \"^3.0.2\",\n        \"request\": \"^2.87.0\",\n        \"stack-utils\": \"^1.0.1\",\n        \"to-factory\": \"^1.0.0\",\n        \"zepto\": \"^1.2.0\"\n      }\n    },\n    \"dom-converter\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/dom-converter/-/dom-converter-0.2.0.tgz\",\n      \"integrity\": \"sha1-ZyGp2u4uKTaClVtq/kFncWJ7t2g=\",\n      \"dev\": true,\n      \"requires\": {\n        \"utila\": \"~0.4\"\n      }\n    },\n    \"dom-serializer\": {\n      \"version\": \"0.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/dom-serializer/-/dom-serializer-0.2.2.tgz\",\n      \"integrity\": \"sha1-GvuB9TNxcXXUeGVd68XjMtn5u1E=\",\n      \"dev\": true,\n      \"requires\": {\n        \"domelementtype\": \"^2.0.1\",\n        \"entities\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"domelementtype\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/domelementtype/-/domelementtype-2.1.0.tgz\",\n          \"integrity\": \"sha1-qFHAgKbRw9lDRK7RUdmfZp7fWF4=\",\n          \"dev\": true\n        },\n        \"entities\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/entities/-/entities-2.1.0.tgz\",\n          \"integrity\": \"sha1-mS0xKc999ocLlsV4WMJJoSD4uLU=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"dom-walk\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/dom-walk/-/dom-walk-0.1.2.tgz\",\n      \"integrity\": \"sha1-DFSL7wSPTR8qlySQAiNgYNqj/YQ=\",\n      \"dev\": true\n    },\n    \"domain-browser\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/domain-browser/-/domain-browser-1.2.0.tgz\",\n      \"integrity\": \"sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto=\",\n      \"dev\": true\n    },\n    \"domelementtype\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://npm.quanziapp.com/domelementtype/-/domelementtype-1.3.1.tgz\",\n      \"integrity\": \"sha1-0EjESzew0Qp/Kj1f7j9DM9eQSB8=\",\n      \"dev\": true\n    },\n    \"domhandler\": {\n      \"version\": \"2.4.2\",\n      \"resolved\": \"https://npm.quanziapp.com/domhandler/-/domhandler-2.4.2.tgz\",\n      \"integrity\": \"sha1-iAUJfpM9ZehVRvcm1g9euItE+AM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"domelementtype\": \"1\"\n      }\n    },\n    \"domutils\": {\n      \"version\": \"1.7.0\",\n      \"resolved\": \"https://npm.quanziapp.com/domutils/-/domutils-1.7.0.tgz\",\n      \"integrity\": \"sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"dom-serializer\": \"0\",\n        \"domelementtype\": \"1\"\n      }\n    },\n    \"dot-prop\": {\n      \"version\": \"5.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/dot-prop/-/dot-prop-5.3.0.tgz\",\n      \"integrity\": \"sha1-kMzOcIzZzYLMTcjD3dmr3VWyDog=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-obj\": \"^2.0.0\"\n      }\n    },\n    \"duplexer3\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/duplexer3/-/duplexer3-0.1.4.tgz\",\n      \"integrity\": \"sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=\",\n      \"dev\": true\n    },\n    \"duplexify\": {\n      \"version\": \"3.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/duplexify/-/duplexify-3.7.1.tgz\",\n      \"integrity\": \"sha1-Kk31MX9sz9kfhtb9JdjYoQO4gwk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"end-of-stream\": \"^1.0.0\",\n        \"inherits\": \"^2.0.1\",\n        \"readable-stream\": \"^2.0.0\",\n        \"stream-shift\": \"^1.0.0\"\n      }\n    },\n    \"ecc-jsbn\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz\",\n      \"integrity\": \"sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"jsbn\": \"~0.1.0\",\n        \"safer-buffer\": \"^2.1.0\"\n      }\n    },\n    \"ee-first\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ee-first/-/ee-first-1.1.1.tgz\",\n      \"integrity\": \"sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=\",\n      \"dev\": true\n    },\n    \"electron-to-chromium\": {\n      \"version\": \"1.3.621\",\n      \"resolved\": \"https://npm.quanziapp.com/electron-to-chromium/-/electron-to-chromium-1.3.621.tgz\",\n      \"integrity\": \"sha1-C74hAO8LKPiNCxEB+99DMxL2m+A=\",\n      \"dev\": true\n    },\n    \"elliptic\": {\n      \"version\": \"6.5.3\",\n      \"resolved\": \"https://npm.quanziapp.com/elliptic/-/elliptic-6.5.3.tgz\",\n      \"integrity\": \"sha1-y1nrLv2vc6C9eMzXAVpirW4Pk9Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bn.js\": \"^4.4.0\",\n        \"brorand\": \"^1.0.1\",\n        \"hash.js\": \"^1.0.0\",\n        \"hmac-drbg\": \"^1.0.0\",\n        \"inherits\": \"^2.0.1\",\n        \"minimalistic-assert\": \"^1.0.0\",\n        \"minimalistic-crypto-utils\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"bn.js\": {\n          \"version\": \"4.11.9\",\n          \"resolved\": \"https://npm.quanziapp.com/bn.js/-/bn.js-4.11.9.tgz\",\n          \"integrity\": \"sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"emoji-regex\": {\n      \"version\": \"7.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/emoji-regex/-/emoji-regex-7.0.3.tgz\",\n      \"integrity\": \"sha1-kzoEBShgyF6DwSJHnEdIqOTHIVY=\",\n      \"dev\": true\n    },\n    \"emojis-list\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/emojis-list/-/emojis-list-3.0.0.tgz\",\n      \"integrity\": \"sha1-VXBmIEatKeLpFucariYKvf9Pang=\",\n      \"dev\": true\n    },\n    \"encodeurl\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/encodeurl/-/encodeurl-1.0.2.tgz\",\n      \"integrity\": \"sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=\",\n      \"dev\": true\n    },\n    \"end-of-stream\": {\n      \"version\": \"1.4.4\",\n      \"resolved\": \"https://npm.quanziapp.com/end-of-stream/-/end-of-stream-1.4.4.tgz\",\n      \"integrity\": \"sha1-WuZKX0UFe682JuwU2gyl5LJDHrA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"once\": \"^1.4.0\"\n      }\n    },\n    \"enhanced-resolve\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz\",\n      \"integrity\": \"sha1-O4BvO/r8HsfeaVUe+TzKRsFwQSY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"graceful-fs\": \"^4.1.2\",\n        \"memory-fs\": \"^0.5.0\",\n        \"tapable\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"memory-fs\": {\n          \"version\": \"0.5.0\",\n          \"resolved\": \"https://npm.quanziapp.com/memory-fs/-/memory-fs-0.5.0.tgz\",\n          \"integrity\": \"sha1-MkwBKIuIZSlm0WHbd4OHIIRajjw=\",\n          \"dev\": true,\n          \"requires\": {\n            \"errno\": \"^0.1.3\",\n            \"readable-stream\": \"^2.0.1\"\n          }\n        }\n      }\n    },\n    \"entities\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/entities/-/entities-1.1.2.tgz\",\n      \"integrity\": \"sha1-vfpzUplmTfr9NFKe1PhSKidf6lY=\",\n      \"dev\": true\n    },\n    \"envify\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/envify/-/envify-4.1.0.tgz\",\n      \"integrity\": \"sha1-85rT251oAbTmtHi2ECjT8LaBn34=\",\n      \"dev\": true,\n      \"requires\": {\n        \"esprima\": \"^4.0.0\",\n        \"through\": \"~2.3.4\"\n      }\n    },\n    \"envinfo\": {\n      \"version\": \"7.7.3\",\n      \"resolved\": \"https://npm.quanziapp.com/envinfo/-/envinfo-7.7.3.tgz\",\n      \"integrity\": \"sha1-Sy2GIuPnNmr7gJGyPtlVaeoCCMw=\",\n      \"dev\": true\n    },\n    \"errno\": {\n      \"version\": \"0.1.7\",\n      \"resolved\": \"https://npm.quanziapp.com/errno/-/errno-0.1.7.tgz\",\n      \"integrity\": \"sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"prr\": \"~1.0.1\"\n      }\n    },\n    \"error-ex\": {\n      \"version\": \"1.3.2\",\n      \"resolved\": \"https://npm.quanziapp.com/error-ex/-/error-ex-1.3.2.tgz\",\n      \"integrity\": \"sha1-tKxAZIEH/c3PriQvQovqihTU8b8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-arrayish\": \"^0.2.1\"\n      }\n    },\n    \"es-abstract\": {\n      \"version\": \"1.18.0-next.1\",\n      \"resolved\": \"https://npm.quanziapp.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz\",\n      \"integrity\": \"sha1-bjoKS9pxflAjqzuOkL7DYQjSLGg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"es-to-primitive\": \"^1.2.1\",\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\",\n        \"has-symbols\": \"^1.0.1\",\n        \"is-callable\": \"^1.2.2\",\n        \"is-negative-zero\": \"^2.0.0\",\n        \"is-regex\": \"^1.1.1\",\n        \"object-inspect\": \"^1.8.0\",\n        \"object-keys\": \"^1.1.1\",\n        \"object.assign\": \"^4.1.1\",\n        \"string.prototype.trimend\": \"^1.0.1\",\n        \"string.prototype.trimstart\": \"^1.0.1\"\n      }\n    },\n    \"es-to-primitive\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz\",\n      \"integrity\": \"sha1-5VzUyc3BiLzvsDs2bHNjI/xciYo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-callable\": \"^1.1.4\",\n        \"is-date-object\": \"^1.0.1\",\n        \"is-symbol\": \"^1.0.2\"\n      }\n    },\n    \"es6-promise\": {\n      \"version\": \"4.2.8\",\n      \"resolved\": \"https://npm.quanziapp.com/es6-promise/-/es6-promise-4.2.8.tgz\",\n      \"integrity\": \"sha1-TrIVlMlyvEBVPSduUQU5FD21Pgo=\",\n      \"dev\": true\n    },\n    \"escalade\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/escalade/-/escalade-3.1.1.tgz\",\n      \"integrity\": \"sha1-2M/ccACWXFoBdLSoLqpcBVJ0LkA=\",\n      \"dev\": true\n    },\n    \"escape-goat\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/escape-goat/-/escape-goat-2.1.1.tgz\",\n      \"integrity\": \"sha1-Gy3HcANnbEV+x2Cy3GjttkgYhnU=\",\n      \"dev\": true\n    },\n    \"escape-html\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/escape-html/-/escape-html-1.0.3.tgz\",\n      \"integrity\": \"sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=\",\n      \"dev\": true\n    },\n    \"escape-string-regexp\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz\",\n      \"integrity\": \"sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=\",\n      \"dev\": true\n    },\n    \"eslint-scope\": {\n      \"version\": \"4.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/eslint-scope/-/eslint-scope-4.0.3.tgz\",\n      \"integrity\": \"sha1-ygODMxD2iJoyZHgaqC5j65z+eEg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"esrecurse\": \"^4.1.0\",\n        \"estraverse\": \"^4.1.1\"\n      }\n    },\n    \"esprima\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/esprima/-/esprima-4.0.1.tgz\",\n      \"integrity\": \"sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=\",\n      \"dev\": true\n    },\n    \"esrecurse\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/esrecurse/-/esrecurse-4.3.0.tgz\",\n      \"integrity\": \"sha1-eteWTWeauyi+5yzsY3WLHF0smSE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"estraverse\": \"^5.2.0\"\n      },\n      \"dependencies\": {\n        \"estraverse\": {\n          \"version\": \"5.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/estraverse/-/estraverse-5.2.0.tgz\",\n          \"integrity\": \"sha1-MH30JUfmzHMk088DwVXVzbjFOIA=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"estraverse\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/estraverse/-/estraverse-4.3.0.tgz\",\n      \"integrity\": \"sha1-OYrT88WiSUi+dyXoPRGn3ijNvR0=\",\n      \"dev\": true\n    },\n    \"esutils\": {\n      \"version\": \"2.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/esutils/-/esutils-2.0.3.tgz\",\n      \"integrity\": \"sha1-dNLrTeC42hKTcRkQ1Qd1ubcQ72Q=\",\n      \"dev\": true\n    },\n    \"etag\": {\n      \"version\": \"1.8.1\",\n      \"resolved\": \"https://npm.quanziapp.com/etag/-/etag-1.8.1.tgz\",\n      \"integrity\": \"sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=\",\n      \"dev\": true\n    },\n    \"eventemitter3\": {\n      \"version\": \"4.0.7\",\n      \"resolved\": \"https://npm.quanziapp.com/eventemitter3/-/eventemitter3-4.0.7.tgz\",\n      \"integrity\": \"sha1-Lem2j2Uo1WRO9cWVJqG0oHMGFp8=\",\n      \"dev\": true\n    },\n    \"events\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/events/-/events-3.2.0.tgz\",\n      \"integrity\": \"sha1-k7h8GPjvzUICpGGuxN/AVWtjk3k=\",\n      \"dev\": true\n    },\n    \"eventsource\": {\n      \"version\": \"1.0.7\",\n      \"resolved\": \"https://npm.quanziapp.com/eventsource/-/eventsource-1.0.7.tgz\",\n      \"integrity\": \"sha1-j7xyyT/NNAiAkLwKTmT0tc7m2NA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"original\": \"^1.0.0\"\n      }\n    },\n    \"evp_bytestokey\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz\",\n      \"integrity\": \"sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"md5.js\": \"^1.3.4\",\n        \"safe-buffer\": \"^5.1.1\"\n      }\n    },\n    \"execa\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/execa/-/execa-1.0.0.tgz\",\n      \"integrity\": \"sha1-xiNqW7TfbW8V6I5/AXeYIWdJ3dg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cross-spawn\": \"^6.0.0\",\n        \"get-stream\": \"^4.0.0\",\n        \"is-stream\": \"^1.1.0\",\n        \"npm-run-path\": \"^2.0.0\",\n        \"p-finally\": \"^1.0.0\",\n        \"signal-exit\": \"^3.0.0\",\n        \"strip-eof\": \"^1.0.0\"\n      }\n    },\n    \"expand-brackets\": {\n      \"version\": \"2.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/expand-brackets/-/expand-brackets-2.1.4.tgz\",\n      \"integrity\": \"sha1-t3c14xXOMPa27/D4OwQVGiJEliI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"debug\": \"^2.3.3\",\n        \"define-property\": \"^0.2.5\",\n        \"extend-shallow\": \"^2.0.1\",\n        \"posix-character-classes\": \"^0.1.0\",\n        \"regex-not\": \"^1.0.0\",\n        \"snapdragon\": \"^0.8.1\",\n        \"to-regex\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"define-property\": {\n          \"version\": \"0.2.5\",\n          \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-0.2.5.tgz\",\n          \"integrity\": \"sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-descriptor\": \"^0.1.0\"\n          }\n        },\n        \"extend-shallow\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/extend-shallow/-/extend-shallow-2.0.1.tgz\",\n          \"integrity\": \"sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-extendable\": \"^0.1.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"express\": {\n      \"version\": \"4.17.1\",\n      \"resolved\": \"https://npm.quanziapp.com/express/-/express-4.17.1.tgz\",\n      \"integrity\": \"sha1-RJH8OGBc9R+GKdOcK10Cb5ikwTQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"accepts\": \"~1.3.7\",\n        \"array-flatten\": \"1.1.1\",\n        \"body-parser\": \"1.19.0\",\n        \"content-disposition\": \"0.5.3\",\n        \"content-type\": \"~1.0.4\",\n        \"cookie\": \"0.4.0\",\n        \"cookie-signature\": \"1.0.6\",\n        \"debug\": \"2.6.9\",\n        \"depd\": \"~1.1.2\",\n        \"encodeurl\": \"~1.0.2\",\n        \"escape-html\": \"~1.0.3\",\n        \"etag\": \"~1.8.1\",\n        \"finalhandler\": \"~1.1.2\",\n        \"fresh\": \"0.5.2\",\n        \"merge-descriptors\": \"1.0.1\",\n        \"methods\": \"~1.1.2\",\n        \"on-finished\": \"~2.3.0\",\n        \"parseurl\": \"~1.3.3\",\n        \"path-to-regexp\": \"0.1.7\",\n        \"proxy-addr\": \"~2.0.5\",\n        \"qs\": \"6.7.0\",\n        \"range-parser\": \"~1.2.1\",\n        \"safe-buffer\": \"5.1.2\",\n        \"send\": \"0.17.1\",\n        \"serve-static\": \"1.14.1\",\n        \"setprototypeof\": \"1.1.1\",\n        \"statuses\": \"~1.5.0\",\n        \"type-is\": \"~1.6.18\",\n        \"utils-merge\": \"1.0.1\",\n        \"vary\": \"~1.1.2\"\n      },\n      \"dependencies\": {\n        \"array-flatten\": {\n          \"version\": \"1.1.1\",\n          \"resolved\": \"https://npm.quanziapp.com/array-flatten/-/array-flatten-1.1.1.tgz\",\n          \"integrity\": \"sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=\",\n          \"dev\": true\n        },\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"extend\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/extend/-/extend-3.0.2.tgz\",\n      \"integrity\": \"sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=\",\n      \"dev\": true\n    },\n    \"extend-shallow\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/extend-shallow/-/extend-shallow-3.0.2.tgz\",\n      \"integrity\": \"sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"assign-symbols\": \"^1.0.0\",\n        \"is-extendable\": \"^1.0.1\"\n      },\n      \"dependencies\": {\n        \"is-extendable\": {\n          \"version\": \"1.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/is-extendable/-/is-extendable-1.0.1.tgz\",\n          \"integrity\": \"sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-plain-object\": \"^2.0.4\"\n          }\n        }\n      }\n    },\n    \"extglob\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/extglob/-/extglob-2.0.4.tgz\",\n      \"integrity\": \"sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"array-unique\": \"^0.3.2\",\n        \"define-property\": \"^1.0.0\",\n        \"expand-brackets\": \"^2.1.4\",\n        \"extend-shallow\": \"^2.0.1\",\n        \"fragment-cache\": \"^0.2.1\",\n        \"regex-not\": \"^1.0.0\",\n        \"snapdragon\": \"^0.8.1\",\n        \"to-regex\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"define-property\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-1.0.0.tgz\",\n          \"integrity\": \"sha1-dp66rz9KY6rTr56NMEybvnm/sOY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-descriptor\": \"^1.0.0\"\n          }\n        },\n        \"extend-shallow\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/extend-shallow/-/extend-shallow-2.0.1.tgz\",\n          \"integrity\": \"sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-extendable\": \"^0.1.0\"\n          }\n        },\n        \"is-accessor-descriptor\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz\",\n          \"integrity\": \"sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"kind-of\": \"^6.0.0\"\n          }\n        },\n        \"is-data-descriptor\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz\",\n          \"integrity\": \"sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"kind-of\": \"^6.0.0\"\n          }\n        },\n        \"is-descriptor\": {\n          \"version\": \"1.0.2\",\n          \"resolved\": \"https://npm.quanziapp.com/is-descriptor/-/is-descriptor-1.0.2.tgz\",\n          \"integrity\": \"sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-accessor-descriptor\": \"^1.0.0\",\n            \"is-data-descriptor\": \"^1.0.0\",\n            \"kind-of\": \"^6.0.2\"\n          }\n        }\n      }\n    },\n    \"extsprintf\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/extsprintf/-/extsprintf-1.3.0.tgz\",\n      \"integrity\": \"sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=\",\n      \"dev\": true\n    },\n    \"fast-deep-equal\": {\n      \"version\": \"3.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz\",\n      \"integrity\": \"sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=\",\n      \"dev\": true\n    },\n    \"fast-glob\": {\n      \"version\": \"2.2.7\",\n      \"resolved\": \"https://npm.quanziapp.com/fast-glob/-/fast-glob-2.2.7.tgz\",\n      \"integrity\": \"sha1-aVOFfDr6R1//ku5gFdUtpwpM050=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@mrmlnc/readdir-enhanced\": \"^2.2.1\",\n        \"@nodelib/fs.stat\": \"^1.1.2\",\n        \"glob-parent\": \"^3.1.0\",\n        \"is-glob\": \"^4.0.0\",\n        \"merge2\": \"^1.2.3\",\n        \"micromatch\": \"^3.1.10\"\n      }\n    },\n    \"fast-json-stable-stringify\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz\",\n      \"integrity\": \"sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM=\",\n      \"dev\": true\n    },\n    \"faye-websocket\": {\n      \"version\": \"0.10.0\",\n      \"resolved\": \"https://npm.quanziapp.com/faye-websocket/-/faye-websocket-0.10.0.tgz\",\n      \"integrity\": \"sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"websocket-driver\": \">=0.5.1\"\n      }\n    },\n    \"figgy-pudding\": {\n      \"version\": \"3.5.2\",\n      \"resolved\": \"https://npm.quanziapp.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz\",\n      \"integrity\": \"sha1-tO7oFIq7Adzx0aw0Nn1Z4S+mHW4=\",\n      \"dev\": true\n    },\n    \"figures\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/figures/-/figures-3.2.0.tgz\",\n      \"integrity\": \"sha1-YlwYvSk8YE3EqN2y/r8MiDQXRq8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"escape-string-regexp\": \"^1.0.5\"\n      }\n    },\n    \"file-loader\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/file-loader/-/file-loader-3.0.1.tgz\",\n      \"integrity\": \"sha1-+OC6C1mZGLUa3+RdZtHnca1WD6o=\",\n      \"dev\": true,\n      \"requires\": {\n        \"loader-utils\": \"^1.0.2\",\n        \"schema-utils\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        }\n      }\n    },\n    \"file-uri-to-path\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz\",\n      \"integrity\": \"sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90=\",\n      \"dev\": true,\n      \"optional\": true\n    },\n    \"fill-range\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/fill-range/-/fill-range-4.0.0.tgz\",\n      \"integrity\": \"sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"extend-shallow\": \"^2.0.1\",\n        \"is-number\": \"^3.0.0\",\n        \"repeat-string\": \"^1.6.1\",\n        \"to-regex-range\": \"^2.1.0\"\n      },\n      \"dependencies\": {\n        \"extend-shallow\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/extend-shallow/-/extend-shallow-2.0.1.tgz\",\n          \"integrity\": \"sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-extendable\": \"^0.1.0\"\n          }\n        }\n      }\n    },\n    \"finalhandler\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/finalhandler/-/finalhandler-1.1.2.tgz\",\n      \"integrity\": \"sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"debug\": \"2.6.9\",\n        \"encodeurl\": \"~1.0.2\",\n        \"escape-html\": \"~1.0.3\",\n        \"on-finished\": \"~2.3.0\",\n        \"parseurl\": \"~1.3.3\",\n        \"statuses\": \"~1.5.0\",\n        \"unpipe\": \"~1.0.0\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"find-cache-dir\": {\n      \"version\": \"3.3.1\",\n      \"resolved\": \"https://npm.quanziapp.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz\",\n      \"integrity\": \"sha1-ibM/rUpGcNqpT4Vff74x1thP6IA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"commondir\": \"^1.0.1\",\n        \"make-dir\": \"^3.0.2\",\n        \"pkg-dir\": \"^4.1.0\"\n      }\n    },\n    \"find-up\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/find-up/-/find-up-4.1.0.tgz\",\n      \"integrity\": \"sha1-l6/n1s3AvFkoWEt8jXsW6KmqXRk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"locate-path\": \"^5.0.0\",\n        \"path-exists\": \"^4.0.0\"\n      }\n    },\n    \"flush-write-stream\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz\",\n      \"integrity\": \"sha1-jdfYc6G6vCB9lOrQwuDkQnbr8ug=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.3\",\n        \"readable-stream\": \"^2.3.6\"\n      }\n    },\n    \"follow-redirects\": {\n      \"version\": \"1.13.0\",\n      \"resolved\": \"https://npm.quanziapp.com/follow-redirects/-/follow-redirects-1.13.0.tgz\",\n      \"integrity\": \"sha1-tC6Nk6Kn7qXtiGM2dtZZe8jjhNs=\",\n      \"dev\": true\n    },\n    \"for-in\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/for-in/-/for-in-1.0.2.tgz\",\n      \"integrity\": \"sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=\",\n      \"dev\": true\n    },\n    \"foreach\": {\n      \"version\": \"2.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/foreach/-/foreach-2.0.5.tgz\",\n      \"integrity\": \"sha1-C+4AUBiusmDQo6865ljdATbsG5k=\",\n      \"dev\": true\n    },\n    \"forever-agent\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://npm.quanziapp.com/forever-agent/-/forever-agent-0.6.1.tgz\",\n      \"integrity\": \"sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=\",\n      \"dev\": true\n    },\n    \"form-data\": {\n      \"version\": \"2.3.3\",\n      \"resolved\": \"https://npm.quanziapp.com/form-data/-/form-data-2.3.3.tgz\",\n      \"integrity\": \"sha1-3M5SwF9kTymManq5Nr1yTO/786Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"asynckit\": \"^0.4.0\",\n        \"combined-stream\": \"^1.0.6\",\n        \"mime-types\": \"^2.1.12\"\n      }\n    },\n    \"forwarded\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/forwarded/-/forwarded-0.1.2.tgz\",\n      \"integrity\": \"sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=\",\n      \"dev\": true\n    },\n    \"fragment-cache\": {\n      \"version\": \"0.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/fragment-cache/-/fragment-cache-0.2.1.tgz\",\n      \"integrity\": \"sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"map-cache\": \"^0.2.2\"\n      }\n    },\n    \"fresh\": {\n      \"version\": \"0.5.2\",\n      \"resolved\": \"https://npm.quanziapp.com/fresh/-/fresh-0.5.2.tgz\",\n      \"integrity\": \"sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=\",\n      \"dev\": true\n    },\n    \"from2\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/from2/-/from2-2.3.0.tgz\",\n      \"integrity\": \"sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.1\",\n        \"readable-stream\": \"^2.0.0\"\n      }\n    },\n    \"fs-extra\": {\n      \"version\": \"7.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/fs-extra/-/fs-extra-7.0.1.tgz\",\n      \"integrity\": \"sha1-TxicRKoSO4lfcigE9V6iPq3DSOk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"graceful-fs\": \"^4.1.2\",\n        \"jsonfile\": \"^4.0.0\",\n        \"universalify\": \"^0.1.0\"\n      }\n    },\n    \"fs-write-stream-atomic\": {\n      \"version\": \"1.0.10\",\n      \"resolved\": \"https://npm.quanziapp.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz\",\n      \"integrity\": \"sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"graceful-fs\": \"^4.1.2\",\n        \"iferr\": \"^0.1.5\",\n        \"imurmurhash\": \"^0.1.4\",\n        \"readable-stream\": \"1 || 2\"\n      }\n    },\n    \"fs.realpath\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/fs.realpath/-/fs.realpath-1.0.0.tgz\",\n      \"integrity\": \"sha1-FQStJSMVjKpA20onh8sBQRmU6k8=\",\n      \"dev\": true\n    },\n    \"fsevents\": {\n      \"version\": \"1.2.13\",\n      \"resolved\": \"https://npm.quanziapp.com/fsevents/-/fsevents-1.2.13.tgz\",\n      \"integrity\": \"sha1-8yXLBFVZJCi88Rs4M3DvcOO/zDg=\",\n      \"dev\": true,\n      \"optional\": true,\n      \"requires\": {\n        \"bindings\": \"^1.5.0\",\n        \"nan\": \"^2.12.1\"\n      }\n    },\n    \"function-bind\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/function-bind/-/function-bind-1.1.1.tgz\",\n      \"integrity\": \"sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=\",\n      \"dev\": true\n    },\n    \"gensync\": {\n      \"version\": \"1.0.0-beta.2\",\n      \"resolved\": \"https://npm.quanziapp.com/gensync/-/gensync-1.0.0-beta.2.tgz\",\n      \"integrity\": \"sha1-MqbudsPX9S1GsrGuXZP+qFgKJeA=\",\n      \"dev\": true\n    },\n    \"get-caller-file\": {\n      \"version\": \"2.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/get-caller-file/-/get-caller-file-2.0.5.tgz\",\n      \"integrity\": \"sha1-T5RBKoLbMvNuOwuXQfipf+sDH34=\",\n      \"dev\": true\n    },\n    \"get-intrinsic\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz\",\n      \"integrity\": \"sha1-lKl2j8vdBZWhySc6rPTInQdWMb4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\",\n        \"has-symbols\": \"^1.0.1\"\n      }\n    },\n    \"get-stream\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/get-stream/-/get-stream-4.1.0.tgz\",\n      \"integrity\": \"sha1-wbJVV189wh1Zv8ec09K0axw6VLU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"pump\": \"^3.0.0\"\n      }\n    },\n    \"get-value\": {\n      \"version\": \"2.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/get-value/-/get-value-2.0.6.tgz\",\n      \"integrity\": \"sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=\",\n      \"dev\": true\n    },\n    \"getpass\": {\n      \"version\": \"0.1.7\",\n      \"resolved\": \"https://npm.quanziapp.com/getpass/-/getpass-0.1.7.tgz\",\n      \"integrity\": \"sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"assert-plus\": \"^1.0.0\"\n      }\n    },\n    \"glob\": {\n      \"version\": \"7.1.6\",\n      \"resolved\": \"https://npm.quanziapp.com/glob/-/glob-7.1.6.tgz\",\n      \"integrity\": \"sha1-FB8zuBp8JJLhJVlDB0gMRmeSeKY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"fs.realpath\": \"^1.0.0\",\n        \"inflight\": \"^1.0.4\",\n        \"inherits\": \"2\",\n        \"minimatch\": \"^3.0.4\",\n        \"once\": \"^1.3.0\",\n        \"path-is-absolute\": \"^1.0.0\"\n      }\n    },\n    \"glob-parent\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/glob-parent/-/glob-parent-3.1.0.tgz\",\n      \"integrity\": \"sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-glob\": \"^3.1.0\",\n        \"path-dirname\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"is-glob\": {\n          \"version\": \"3.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-glob/-/is-glob-3.1.0.tgz\",\n          \"integrity\": \"sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-extglob\": \"^2.1.0\"\n          }\n        }\n      }\n    },\n    \"glob-to-regexp\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz\",\n      \"integrity\": \"sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=\",\n      \"dev\": true\n    },\n    \"global\": {\n      \"version\": \"4.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/global/-/global-4.4.0.tgz\",\n      \"integrity\": \"sha1-PnsQUXkAajI+1xqvyj6cV6XMZAY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"min-document\": \"^2.19.0\",\n        \"process\": \"^0.11.10\"\n      }\n    },\n    \"global-dirs\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/global-dirs/-/global-dirs-2.0.1.tgz\",\n      \"integrity\": \"sha1-rN87tmhbzVXLNeigUiZlaelGkgE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ini\": \"^1.3.5\"\n      }\n    },\n    \"globals\": {\n      \"version\": \"11.12.0\",\n      \"resolved\": \"https://npm.quanziapp.com/globals/-/globals-11.12.0.tgz\",\n      \"integrity\": \"sha1-q4eVM4hooLq9hSV1gBjCp+uVxC4=\",\n      \"dev\": true\n    },\n    \"globby\": {\n      \"version\": \"9.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/globby/-/globby-9.2.0.tgz\",\n      \"integrity\": \"sha1-/QKacGxwPSm90XD0tts6P3p8tj0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/glob\": \"^7.1.1\",\n        \"array-union\": \"^1.0.2\",\n        \"dir-glob\": \"^2.2.2\",\n        \"fast-glob\": \"^2.2.6\",\n        \"glob\": \"^7.1.3\",\n        \"ignore\": \"^4.0.3\",\n        \"pify\": \"^4.0.1\",\n        \"slash\": \"^2.0.0\"\n      }\n    },\n    \"good-listener\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/good-listener/-/good-listener-1.2.2.tgz\",\n      \"integrity\": \"sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=\",\n      \"dev\": true,\n      \"optional\": true,\n      \"requires\": {\n        \"delegate\": \"^3.1.2\"\n      }\n    },\n    \"got\": {\n      \"version\": \"9.6.0\",\n      \"resolved\": \"https://npm.quanziapp.com/got/-/got-9.6.0.tgz\",\n      \"integrity\": \"sha1-7fRefWf5lUVwXeH3u+7rEhdl7YU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@sindresorhus/is\": \"^0.14.0\",\n        \"@szmarczak/http-timer\": \"^1.1.2\",\n        \"cacheable-request\": \"^6.0.0\",\n        \"decompress-response\": \"^3.3.0\",\n        \"duplexer3\": \"^0.1.4\",\n        \"get-stream\": \"^4.1.0\",\n        \"lowercase-keys\": \"^1.0.1\",\n        \"mimic-response\": \"^1.0.1\",\n        \"p-cancelable\": \"^1.0.0\",\n        \"to-readable-stream\": \"^1.0.0\",\n        \"url-parse-lax\": \"^3.0.0\"\n      }\n    },\n    \"graceful-fs\": {\n      \"version\": \"4.2.4\",\n      \"resolved\": \"https://npm.quanziapp.com/graceful-fs/-/graceful-fs-4.2.4.tgz\",\n      \"integrity\": \"sha1-Ila94U02MpWMRl68ltxGfKB6Kfs=\",\n      \"dev\": true\n    },\n    \"gray-matter\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/gray-matter/-/gray-matter-4.0.2.tgz\",\n      \"integrity\": \"sha1-mqN546yvQhGT/OfSoozr1FGKxFQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"js-yaml\": \"^3.11.0\",\n        \"kind-of\": \"^6.0.2\",\n        \"section-matter\": \"^1.0.0\",\n        \"strip-bom-string\": \"^1.0.0\"\n      }\n    },\n    \"handle-thing\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/handle-thing/-/handle-thing-2.0.1.tgz\",\n      \"integrity\": \"sha1-hX95zjWVgMNA1DCBzGSJcNC7I04=\",\n      \"dev\": true\n    },\n    \"har-schema\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/har-schema/-/har-schema-2.0.0.tgz\",\n      \"integrity\": \"sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=\",\n      \"dev\": true\n    },\n    \"har-validator\": {\n      \"version\": \"5.1.5\",\n      \"resolved\": \"https://npm.quanziapp.com/har-validator/-/har-validator-5.1.5.tgz\",\n      \"integrity\": \"sha1-HwgDufjLIMD6E4It8ezds2veHv0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ajv\": \"^6.12.3\",\n        \"har-schema\": \"^2.0.0\"\n      }\n    },\n    \"has\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/has/-/has-1.0.3.tgz\",\n      \"integrity\": \"sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"function-bind\": \"^1.1.1\"\n      }\n    },\n    \"has-ansi\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/has-ansi/-/has-ansi-2.0.0.tgz\",\n      \"integrity\": \"sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-regex\": \"^2.0.0\"\n      }\n    },\n    \"has-flag\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/has-flag/-/has-flag-3.0.0.tgz\",\n      \"integrity\": \"sha1-tdRU3CGZriJWmfNGfloH87lVuv0=\",\n      \"dev\": true\n    },\n    \"has-symbols\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/has-symbols/-/has-symbols-1.0.1.tgz\",\n      \"integrity\": \"sha1-n1IUdYpEGWxAbZvXbOv4HsLdMeg=\",\n      \"dev\": true\n    },\n    \"has-value\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/has-value/-/has-value-1.0.0.tgz\",\n      \"integrity\": \"sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"get-value\": \"^2.0.6\",\n        \"has-values\": \"^1.0.0\",\n        \"isobject\": \"^3.0.0\"\n      }\n    },\n    \"has-values\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/has-values/-/has-values-1.0.0.tgz\",\n      \"integrity\": \"sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-number\": \"^3.0.0\",\n        \"kind-of\": \"^4.0.0\"\n      },\n      \"dependencies\": {\n        \"kind-of\": {\n          \"version\": \"4.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-4.0.0.tgz\",\n          \"integrity\": \"sha1-IIE989cSkosgc3hpGkUGb65y3Vc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-buffer\": \"^1.1.5\"\n          }\n        }\n      }\n    },\n    \"has-yarn\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/has-yarn/-/has-yarn-2.1.0.tgz\",\n      \"integrity\": \"sha1-E34RNUp7W/EapctknPDG8/8rLnc=\",\n      \"dev\": true\n    },\n    \"hash-base\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/hash-base/-/hash-base-3.1.0.tgz\",\n      \"integrity\": \"sha1-VcOB2eBuHSmXqIO0o/3f5/DTrzM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.4\",\n        \"readable-stream\": \"^3.6.0\",\n        \"safe-buffer\": \"^5.2.0\"\n      },\n      \"dependencies\": {\n        \"readable-stream\": {\n          \"version\": \"3.6.0\",\n          \"resolved\": \"https://npm.quanziapp.com/readable-stream/-/readable-stream-3.6.0.tgz\",\n          \"integrity\": \"sha1-M3u9o63AcGvT4CRCaihtS0sskZg=\",\n          \"dev\": true,\n          \"requires\": {\n            \"inherits\": \"^2.0.3\",\n            \"string_decoder\": \"^1.1.1\",\n            \"util-deprecate\": \"^1.0.1\"\n          }\n        },\n        \"safe-buffer\": {\n          \"version\": \"5.2.1\",\n          \"resolved\": \"https://npm.quanziapp.com/safe-buffer/-/safe-buffer-5.2.1.tgz\",\n          \"integrity\": \"sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"hash-sum\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/hash-sum/-/hash-sum-1.0.2.tgz\",\n      \"integrity\": \"sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=\",\n      \"dev\": true\n    },\n    \"hash.js\": {\n      \"version\": \"1.1.7\",\n      \"resolved\": \"https://npm.quanziapp.com/hash.js/-/hash.js-1.1.7.tgz\",\n      \"integrity\": \"sha1-C6vKU46NTuSg+JiNaIZlN6ADz0I=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.3\",\n        \"minimalistic-assert\": \"^1.0.1\"\n      }\n    },\n    \"he\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/he/-/he-1.2.0.tgz\",\n      \"integrity\": \"sha1-hK5l+n6vsWX922FWauFLrwVmTw8=\",\n      \"dev\": true\n    },\n    \"hex-color-regex\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz\",\n      \"integrity\": \"sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=\",\n      \"dev\": true\n    },\n    \"hmac-drbg\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz\",\n      \"integrity\": \"sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"hash.js\": \"^1.0.3\",\n        \"minimalistic-assert\": \"^1.0.0\",\n        \"minimalistic-crypto-utils\": \"^1.0.1\"\n      }\n    },\n    \"hogan.js\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/hogan.js/-/hogan.js-3.0.2.tgz\",\n      \"integrity\": \"sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"mkdirp\": \"0.3.0\",\n        \"nopt\": \"1.0.10\"\n      },\n      \"dependencies\": {\n        \"mkdirp\": {\n          \"version\": \"0.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/mkdirp/-/mkdirp-0.3.0.tgz\",\n          \"integrity\": \"sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"hpack.js\": {\n      \"version\": \"2.1.6\",\n      \"resolved\": \"https://npm.quanziapp.com/hpack.js/-/hpack.js-2.1.6.tgz\",\n      \"integrity\": \"sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.1\",\n        \"obuf\": \"^1.0.0\",\n        \"readable-stream\": \"^2.0.1\",\n        \"wbuf\": \"^1.1.0\"\n      }\n    },\n    \"hsl-regex\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/hsl-regex/-/hsl-regex-1.0.0.tgz\",\n      \"integrity\": \"sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=\",\n      \"dev\": true\n    },\n    \"hsla-regex\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/hsla-regex/-/hsla-regex-1.0.0.tgz\",\n      \"integrity\": \"sha1-wc56MWjIxmFAM6S194d/OyJfnDg=\",\n      \"dev\": true\n    },\n    \"html-comment-regex\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz\",\n      \"integrity\": \"sha1-l9RoiutcgYhqNk+qDK0d2hTUM6c=\",\n      \"dev\": true\n    },\n    \"html-entities\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://npm.quanziapp.com/html-entities/-/html-entities-1.3.1.tgz\",\n      \"integrity\": \"sha1-+5oaS1sUxdq6gtPjTGrk/nAaDkQ=\",\n      \"dev\": true\n    },\n    \"html-minifier\": {\n      \"version\": \"3.5.21\",\n      \"resolved\": \"https://npm.quanziapp.com/html-minifier/-/html-minifier-3.5.21.tgz\",\n      \"integrity\": \"sha1-0AQOBUcw41TbAIRjWTGUAVIS0gw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"camel-case\": \"3.0.x\",\n        \"clean-css\": \"4.2.x\",\n        \"commander\": \"2.17.x\",\n        \"he\": \"1.2.x\",\n        \"param-case\": \"2.1.x\",\n        \"relateurl\": \"0.2.x\",\n        \"uglify-js\": \"3.4.x\"\n      }\n    },\n    \"html-tags\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/html-tags/-/html-tags-3.1.0.tgz\",\n      \"integrity\": \"sha1-e15vfmZen7QfMAB+2eDUHpf7IUA=\",\n      \"dev\": true\n    },\n    \"htmlparser2\": {\n      \"version\": \"3.10.1\",\n      \"resolved\": \"https://npm.quanziapp.com/htmlparser2/-/htmlparser2-3.10.1.tgz\",\n      \"integrity\": \"sha1-vWedw/WYl7ajS7EHSchVu1OpOS8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"domelementtype\": \"^1.3.1\",\n        \"domhandler\": \"^2.3.0\",\n        \"domutils\": \"^1.5.1\",\n        \"entities\": \"^1.1.1\",\n        \"inherits\": \"^2.0.1\",\n        \"readable-stream\": \"^3.1.1\"\n      },\n      \"dependencies\": {\n        \"readable-stream\": {\n          \"version\": \"3.6.0\",\n          \"resolved\": \"https://npm.quanziapp.com/readable-stream/-/readable-stream-3.6.0.tgz\",\n          \"integrity\": \"sha1-M3u9o63AcGvT4CRCaihtS0sskZg=\",\n          \"dev\": true,\n          \"requires\": {\n            \"inherits\": \"^2.0.3\",\n            \"string_decoder\": \"^1.1.1\",\n            \"util-deprecate\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"http-cache-semantics\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz\",\n      \"integrity\": \"sha1-SekcXL82yblLz81xwj1SSex045A=\",\n      \"dev\": true\n    },\n    \"http-deceiver\": {\n      \"version\": \"1.2.7\",\n      \"resolved\": \"https://npm.quanziapp.com/http-deceiver/-/http-deceiver-1.2.7.tgz\",\n      \"integrity\": \"sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=\",\n      \"dev\": true\n    },\n    \"http-errors\": {\n      \"version\": \"1.7.2\",\n      \"resolved\": \"https://npm.quanziapp.com/http-errors/-/http-errors-1.7.2.tgz\",\n      \"integrity\": \"sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"depd\": \"~1.1.2\",\n        \"inherits\": \"2.0.3\",\n        \"setprototypeof\": \"1.1.1\",\n        \"statuses\": \">= 1.5.0 < 2\",\n        \"toidentifier\": \"1.0.0\"\n      },\n      \"dependencies\": {\n        \"inherits\": {\n          \"version\": \"2.0.3\",\n          \"resolved\": \"https://npm.quanziapp.com/inherits/-/inherits-2.0.3.tgz\",\n          \"integrity\": \"sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"http-proxy\": {\n      \"version\": \"1.18.1\",\n      \"resolved\": \"https://npm.quanziapp.com/http-proxy/-/http-proxy-1.18.1.tgz\",\n      \"integrity\": \"sha1-QBVB8FNIhLv5UmAzTnL4juOXZUk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"eventemitter3\": \"^4.0.0\",\n        \"follow-redirects\": \"^1.0.0\",\n        \"requires-port\": \"^1.0.0\"\n      }\n    },\n    \"http-proxy-middleware\": {\n      \"version\": \"0.19.1\",\n      \"resolved\": \"https://npm.quanziapp.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz\",\n      \"integrity\": \"sha1-GDx9xKoUeRUDBkmMIQza+WCApDo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"http-proxy\": \"^1.17.0\",\n        \"is-glob\": \"^4.0.0\",\n        \"lodash\": \"^4.17.11\",\n        \"micromatch\": \"^3.1.10\"\n      }\n    },\n    \"http-signature\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/http-signature/-/http-signature-1.2.0.tgz\",\n      \"integrity\": \"sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"assert-plus\": \"^1.0.0\",\n        \"jsprim\": \"^1.2.2\",\n        \"sshpk\": \"^1.7.0\"\n      }\n    },\n    \"https-browserify\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/https-browserify/-/https-browserify-1.0.0.tgz\",\n      \"integrity\": \"sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=\",\n      \"dev\": true\n    },\n    \"iconv-lite\": {\n      \"version\": \"0.4.24\",\n      \"resolved\": \"https://npm.quanziapp.com/iconv-lite/-/iconv-lite-0.4.24.tgz\",\n      \"integrity\": \"sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"safer-buffer\": \">= 2.1.2 < 3\"\n      }\n    },\n    \"icss-replace-symbols\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz\",\n      \"integrity\": \"sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=\",\n      \"dev\": true\n    },\n    \"icss-utils\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/icss-utils/-/icss-utils-4.1.1.tgz\",\n      \"integrity\": \"sha1-IRcLU3ie4nRHwvR91oMIFAP5pGc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.14\"\n      }\n    },\n    \"ieee754\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ieee754/-/ieee754-1.2.1.tgz\",\n      \"integrity\": \"sha1-jrehCmP/8l0VpXsAFYbRd9Gw01I=\",\n      \"dev\": true\n    },\n    \"iferr\": {\n      \"version\": \"0.1.5\",\n      \"resolved\": \"https://npm.quanziapp.com/iferr/-/iferr-0.1.5.tgz\",\n      \"integrity\": \"sha1-xg7taebY/bazEEofy8ocGS3FtQE=\",\n      \"dev\": true\n    },\n    \"ignore\": {\n      \"version\": \"4.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/ignore/-/ignore-4.0.6.tgz\",\n      \"integrity\": \"sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=\",\n      \"dev\": true\n    },\n    \"immediate\": {\n      \"version\": \"3.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/immediate/-/immediate-3.3.0.tgz\",\n      \"integrity\": \"sha1-Gu8iVReDa8338qLeJgDHn/AmkmY=\",\n      \"dev\": true\n    },\n    \"import-cwd\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/import-cwd/-/import-cwd-2.1.0.tgz\",\n      \"integrity\": \"sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"import-from\": \"^2.1.0\"\n      }\n    },\n    \"import-fresh\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/import-fresh/-/import-fresh-2.0.0.tgz\",\n      \"integrity\": \"sha1-2BNVwVYS04bGH53dOSLUMEgipUY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"caller-path\": \"^2.0.0\",\n        \"resolve-from\": \"^3.0.0\"\n      }\n    },\n    \"import-from\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/import-from/-/import-from-2.1.0.tgz\",\n      \"integrity\": \"sha1-M1238qev/VOqpHHUuAId7ja387E=\",\n      \"dev\": true,\n      \"requires\": {\n        \"resolve-from\": \"^3.0.0\"\n      }\n    },\n    \"import-lazy\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/import-lazy/-/import-lazy-2.1.0.tgz\",\n      \"integrity\": \"sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=\",\n      \"dev\": true\n    },\n    \"import-local\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/import-local/-/import-local-2.0.0.tgz\",\n      \"integrity\": \"sha1-VQcL44pZk88Y72236WH1vuXFoJ0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"pkg-dir\": \"^3.0.0\",\n        \"resolve-cwd\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"find-up\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/find-up/-/find-up-3.0.0.tgz\",\n          \"integrity\": \"sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=\",\n          \"dev\": true,\n          \"requires\": {\n            \"locate-path\": \"^3.0.0\"\n          }\n        },\n        \"locate-path\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/locate-path/-/locate-path-3.0.0.tgz\",\n          \"integrity\": \"sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-locate\": \"^3.0.0\",\n            \"path-exists\": \"^3.0.0\"\n          }\n        },\n        \"p-locate\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/p-locate/-/p-locate-3.0.0.tgz\",\n          \"integrity\": \"sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-limit\": \"^2.0.0\"\n          }\n        },\n        \"path-exists\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/path-exists/-/path-exists-3.0.0.tgz\",\n          \"integrity\": \"sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=\",\n          \"dev\": true\n        },\n        \"pkg-dir\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/pkg-dir/-/pkg-dir-3.0.0.tgz\",\n          \"integrity\": \"sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"find-up\": \"^3.0.0\"\n          }\n        }\n      }\n    },\n    \"imurmurhash\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/imurmurhash/-/imurmurhash-0.1.4.tgz\",\n      \"integrity\": \"sha1-khi5srkoojixPcT7a21XbyMUU+o=\",\n      \"dev\": true\n    },\n    \"indexes-of\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/indexes-of/-/indexes-of-1.0.1.tgz\",\n      \"integrity\": \"sha1-8w9xbI4r00bHtn0985FVZqfAVgc=\",\n      \"dev\": true\n    },\n    \"infer-owner\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/infer-owner/-/infer-owner-1.0.4.tgz\",\n      \"integrity\": \"sha1-xM78qo5RBRwqQLos6KPScpWvlGc=\",\n      \"dev\": true\n    },\n    \"inflight\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/inflight/-/inflight-1.0.6.tgz\",\n      \"integrity\": \"sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"once\": \"^1.3.0\",\n        \"wrappy\": \"1\"\n      }\n    },\n    \"inherits\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/inherits/-/inherits-2.0.4.tgz\",\n      \"integrity\": \"sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=\",\n      \"dev\": true\n    },\n    \"ini\": {\n      \"version\": \"1.3.7\",\n      \"resolved\": \"https://npm.quanziapp.com/ini/-/ini-1.3.7.tgz\",\n      \"integrity\": \"sha1-oJNj4ZEZcuoW16iFEAXYTPCamoQ=\",\n      \"dev\": true\n    },\n    \"internal-ip\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/internal-ip/-/internal-ip-4.3.0.tgz\",\n      \"integrity\": \"sha1-hFRSuq2dLKO2nGNaE3rLmg2tCQc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"default-gateway\": \"^4.2.0\",\n        \"ipaddr.js\": \"^1.9.0\"\n      }\n    },\n    \"ip\": {\n      \"version\": \"1.1.5\",\n      \"resolved\": \"https://npm.quanziapp.com/ip/-/ip-1.1.5.tgz\",\n      \"integrity\": \"sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=\",\n      \"dev\": true\n    },\n    \"ip-regex\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/ip-regex/-/ip-regex-2.1.0.tgz\",\n      \"integrity\": \"sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=\",\n      \"dev\": true\n    },\n    \"ipaddr.js\": {\n      \"version\": \"1.9.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz\",\n      \"integrity\": \"sha1-v/OFQ+64mEglB5/zoqjmy9RngbM=\",\n      \"dev\": true\n    },\n    \"is-absolute-url\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz\",\n      \"integrity\": \"sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=\",\n      \"dev\": true\n    },\n    \"is-accessor-descriptor\": {\n      \"version\": \"0.1.6\",\n      \"resolved\": \"https://npm.quanziapp.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz\",\n      \"integrity\": \"sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"kind-of\": \"^3.0.2\"\n      },\n      \"dependencies\": {\n        \"kind-of\": {\n          \"version\": \"3.2.2\",\n          \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-3.2.2.tgz\",\n          \"integrity\": \"sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-buffer\": \"^1.1.5\"\n          }\n        }\n      }\n    },\n    \"is-arguments\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-arguments/-/is-arguments-1.1.0.tgz\",\n      \"integrity\": \"sha1-YjUwMd++4HzrNGVqa95Z7+yujdk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\"\n      }\n    },\n    \"is-arrayish\": {\n      \"version\": \"0.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/is-arrayish/-/is-arrayish-0.2.1.tgz\",\n      \"integrity\": \"sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=\",\n      \"dev\": true\n    },\n    \"is-binary-path\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/is-binary-path/-/is-binary-path-1.0.1.tgz\",\n      \"integrity\": \"sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"binary-extensions\": \"^1.0.0\"\n      }\n    },\n    \"is-buffer\": {\n      \"version\": \"1.1.6\",\n      \"resolved\": \"https://npm.quanziapp.com/is-buffer/-/is-buffer-1.1.6.tgz\",\n      \"integrity\": \"sha1-76ouqdqg16suoTqXsritUf776L4=\",\n      \"dev\": true\n    },\n    \"is-callable\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/is-callable/-/is-callable-1.2.2.tgz\",\n      \"integrity\": \"sha1-x8ZxXNItTdtI0+GZcCI6zquwgNk=\",\n      \"dev\": true\n    },\n    \"is-ci\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-ci/-/is-ci-2.0.0.tgz\",\n      \"integrity\": \"sha1-a8YzQYGBDgS1wis9WJ/cpVAmQEw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ci-info\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"ci-info\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ci-info/-/ci-info-2.0.0.tgz\",\n          \"integrity\": \"sha1-Z6npZL4xpR4V5QENWObxKDQAL0Y=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"is-color-stop\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-color-stop/-/is-color-stop-1.1.0.tgz\",\n      \"integrity\": \"sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=\",\n      \"dev\": true,\n      \"requires\": {\n        \"css-color-names\": \"^0.0.4\",\n        \"hex-color-regex\": \"^1.1.0\",\n        \"hsl-regex\": \"^1.0.0\",\n        \"hsla-regex\": \"^1.0.0\",\n        \"rgb-regex\": \"^1.0.1\",\n        \"rgba-regex\": \"^1.0.0\"\n      }\n    },\n    \"is-core-module\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-core-module/-/is-core-module-2.2.0.tgz\",\n      \"integrity\": \"sha1-lwN+89UiJNhRY/VZeytj2a/tmBo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"has\": \"^1.0.3\"\n      }\n    },\n    \"is-data-descriptor\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz\",\n      \"integrity\": \"sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"kind-of\": \"^3.0.2\"\n      },\n      \"dependencies\": {\n        \"kind-of\": {\n          \"version\": \"3.2.2\",\n          \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-3.2.2.tgz\",\n          \"integrity\": \"sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-buffer\": \"^1.1.5\"\n          }\n        }\n      }\n    },\n    \"is-date-object\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/is-date-object/-/is-date-object-1.0.2.tgz\",\n      \"integrity\": \"sha1-vac28s2P0G0yhE53Q7+nSUw7/X4=\",\n      \"dev\": true\n    },\n    \"is-descriptor\": {\n      \"version\": \"0.1.6\",\n      \"resolved\": \"https://npm.quanziapp.com/is-descriptor/-/is-descriptor-0.1.6.tgz\",\n      \"integrity\": \"sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-accessor-descriptor\": \"^0.1.6\",\n        \"is-data-descriptor\": \"^0.1.4\",\n        \"kind-of\": \"^5.0.0\"\n      },\n      \"dependencies\": {\n        \"kind-of\": {\n          \"version\": \"5.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-5.1.0.tgz\",\n          \"integrity\": \"sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"is-directory\": {\n      \"version\": \"0.3.1\",\n      \"resolved\": \"https://npm.quanziapp.com/is-directory/-/is-directory-0.3.1.tgz\",\n      \"integrity\": \"sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=\",\n      \"dev\": true\n    },\n    \"is-extendable\": {\n      \"version\": \"0.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/is-extendable/-/is-extendable-0.1.1.tgz\",\n      \"integrity\": \"sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=\",\n      \"dev\": true\n    },\n    \"is-extglob\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/is-extglob/-/is-extglob-2.1.1.tgz\",\n      \"integrity\": \"sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=\",\n      \"dev\": true\n    },\n    \"is-fullwidth-code-point\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz\",\n      \"integrity\": \"sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=\",\n      \"dev\": true\n    },\n    \"is-glob\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/is-glob/-/is-glob-4.0.1.tgz\",\n      \"integrity\": \"sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-extglob\": \"^2.1.1\"\n      }\n    },\n    \"is-installed-globally\": {\n      \"version\": \"0.3.2\",\n      \"resolved\": \"https://npm.quanziapp.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz\",\n      \"integrity\": \"sha1-/T76ee5nDRGHIzGC1bCh3QAxMUE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"global-dirs\": \"^2.0.1\",\n        \"is-path-inside\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"is-path-inside\": {\n          \"version\": \"3.0.2\",\n          \"resolved\": \"https://npm.quanziapp.com/is-path-inside/-/is-path-inside-3.0.2.tgz\",\n          \"integrity\": \"sha1-9SIPyCo+IzdXKR3dycWHfyofMBc=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"is-negative-zero\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz\",\n      \"integrity\": \"sha1-PedGwY3aIxkkGlNnWQjY92bxHCQ=\",\n      \"dev\": true\n    },\n    \"is-npm\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-npm/-/is-npm-4.0.0.tgz\",\n      \"integrity\": \"sha1-yQ3YOAaW34enptgjwg0LErvjyE0=\",\n      \"dev\": true\n    },\n    \"is-number\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-number/-/is-number-3.0.0.tgz\",\n      \"integrity\": \"sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"kind-of\": \"^3.0.2\"\n      },\n      \"dependencies\": {\n        \"kind-of\": {\n          \"version\": \"3.2.2\",\n          \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-3.2.2.tgz\",\n          \"integrity\": \"sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-buffer\": \"^1.1.5\"\n          }\n        }\n      }\n    },\n    \"is-obj\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-obj/-/is-obj-2.0.0.tgz\",\n      \"integrity\": \"sha1-Rz+wXZc3BeP9liBUUBjKjiLvSYI=\",\n      \"dev\": true\n    },\n    \"is-path-cwd\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz\",\n      \"integrity\": \"sha1-Z9Q7gmZKe1GR/ZEZEn6zAASKn9s=\",\n      \"dev\": true\n    },\n    \"is-path-in-cwd\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz\",\n      \"integrity\": \"sha1-v+Lcomxp85cmWkAJljYCk1oFOss=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-path-inside\": \"^2.1.0\"\n      }\n    },\n    \"is-path-inside\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-path-inside/-/is-path-inside-2.1.0.tgz\",\n      \"integrity\": \"sha1-fJgQWH1lmkDSe8201WFuqwWUlLI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"path-is-inside\": \"^1.0.2\"\n      }\n    },\n    \"is-plain-obj\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz\",\n      \"integrity\": \"sha1-caUMhCnfync8kqOQpKA7OfzVHT4=\",\n      \"dev\": true\n    },\n    \"is-plain-object\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/is-plain-object/-/is-plain-object-2.0.4.tgz\",\n      \"integrity\": \"sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"isobject\": \"^3.0.1\"\n      }\n    },\n    \"is-regex\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/is-regex/-/is-regex-1.1.1.tgz\",\n      \"integrity\": \"sha1-xvmKrMVG9s7FRooHt7FTq1ZKV7k=\",\n      \"dev\": true,\n      \"requires\": {\n        \"has-symbols\": \"^1.0.1\"\n      }\n    },\n    \"is-resolvable\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-resolvable/-/is-resolvable-1.1.0.tgz\",\n      \"integrity\": \"sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=\",\n      \"dev\": true\n    },\n    \"is-stream\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-stream/-/is-stream-1.1.0.tgz\",\n      \"integrity\": \"sha1-EtSj3U5o4Lec6428hBc66A2RykQ=\",\n      \"dev\": true\n    },\n    \"is-svg\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-svg/-/is-svg-3.0.0.tgz\",\n      \"integrity\": \"sha1-kyHb0pwhLlypnE+peUxxS8r6L3U=\",\n      \"dev\": true,\n      \"requires\": {\n        \"html-comment-regex\": \"^1.1.0\"\n      }\n    },\n    \"is-symbol\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/is-symbol/-/is-symbol-1.0.3.tgz\",\n      \"integrity\": \"sha1-OOEBS55jKb4N6dJKQU/XRB7GGTc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"has-symbols\": \"^1.0.1\"\n      }\n    },\n    \"is-typedarray\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-typedarray/-/is-typedarray-1.0.0.tgz\",\n      \"integrity\": \"sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=\",\n      \"dev\": true\n    },\n    \"is-windows\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/is-windows/-/is-windows-1.0.2.tgz\",\n      \"integrity\": \"sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=\",\n      \"dev\": true\n    },\n    \"is-wsl\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-wsl/-/is-wsl-1.1.0.tgz\",\n      \"integrity\": \"sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=\",\n      \"dev\": true\n    },\n    \"is-yarn-global\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz\",\n      \"integrity\": \"sha1-1QLTOCWQ6jAEiTdGdUyJE5lz4jI=\",\n      \"dev\": true\n    },\n    \"isarray\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/isarray/-/isarray-1.0.0.tgz\",\n      \"integrity\": \"sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=\",\n      \"dev\": true\n    },\n    \"isexe\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/isexe/-/isexe-2.0.0.tgz\",\n      \"integrity\": \"sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=\",\n      \"dev\": true\n    },\n    \"isobject\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/isobject/-/isobject-3.0.1.tgz\",\n      \"integrity\": \"sha1-TkMekrEalzFjaqH5yNHMvP2reN8=\",\n      \"dev\": true\n    },\n    \"isstream\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/isstream/-/isstream-0.1.2.tgz\",\n      \"integrity\": \"sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=\",\n      \"dev\": true\n    },\n    \"javascript-stringify\": {\n      \"version\": \"1.6.0\",\n      \"resolved\": \"https://npm.quanziapp.com/javascript-stringify/-/javascript-stringify-1.6.0.tgz\",\n      \"integrity\": \"sha1-FC0RHzpuPa6PSpr9d9RYVbWpzOM=\",\n      \"dev\": true\n    },\n    \"js-tokens\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/js-tokens/-/js-tokens-4.0.0.tgz\",\n      \"integrity\": \"sha1-GSA/tZmR35jjoocFDUZHzerzJJk=\",\n      \"dev\": true\n    },\n    \"js-yaml\": {\n      \"version\": \"3.14.1\",\n      \"resolved\": \"https://npm.quanziapp.com/js-yaml/-/js-yaml-3.14.1.tgz\",\n      \"integrity\": \"sha1-2ugS/bOCX6MGYJqHFzg8UMNqBTc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"argparse\": \"^1.0.7\",\n        \"esprima\": \"^4.0.0\"\n      }\n    },\n    \"jsbn\": {\n      \"version\": \"0.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/jsbn/-/jsbn-0.1.1.tgz\",\n      \"integrity\": \"sha1-peZUwuWi3rXyAdls77yoDA7y9RM=\",\n      \"dev\": true\n    },\n    \"jsesc\": {\n      \"version\": \"2.5.2\",\n      \"resolved\": \"https://npm.quanziapp.com/jsesc/-/jsesc-2.5.2.tgz\",\n      \"integrity\": \"sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=\",\n      \"dev\": true\n    },\n    \"json-buffer\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/json-buffer/-/json-buffer-3.0.0.tgz\",\n      \"integrity\": \"sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=\",\n      \"dev\": true\n    },\n    \"json-parse-better-errors\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz\",\n      \"integrity\": \"sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=\",\n      \"dev\": true\n    },\n    \"json-schema\": {\n      \"version\": \"0.2.3\",\n      \"resolved\": \"https://npm.quanziapp.com/json-schema/-/json-schema-0.2.3.tgz\",\n      \"integrity\": \"sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=\",\n      \"dev\": true\n    },\n    \"json-schema-traverse\": {\n      \"version\": \"0.4.1\",\n      \"resolved\": \"https://npm.quanziapp.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz\",\n      \"integrity\": \"sha1-afaofZUTq4u4/mO9sJecRI5oRmA=\",\n      \"dev\": true\n    },\n    \"json-stringify-safe\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz\",\n      \"integrity\": \"sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=\",\n      \"dev\": true\n    },\n    \"json3\": {\n      \"version\": \"3.3.3\",\n      \"resolved\": \"https://npm.quanziapp.com/json3/-/json3-3.3.3.tgz\",\n      \"integrity\": \"sha1-f8EON1/FrkLEcFpcwKpvYr4wW4E=\",\n      \"dev\": true\n    },\n    \"json5\": {\n      \"version\": \"2.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/json5/-/json5-2.1.3.tgz\",\n      \"integrity\": \"sha1-ybD3+pIzv+WAf+ZvzzpWF+1ZfUM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"minimist\": \"^1.2.5\"\n      }\n    },\n    \"jsonfile\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/jsonfile/-/jsonfile-4.0.0.tgz\",\n      \"integrity\": \"sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=\",\n      \"dev\": true,\n      \"requires\": {\n        \"graceful-fs\": \"^4.1.6\"\n      }\n    },\n    \"jsprim\": {\n      \"version\": \"1.4.1\",\n      \"resolved\": \"https://npm.quanziapp.com/jsprim/-/jsprim-1.4.1.tgz\",\n      \"integrity\": \"sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"assert-plus\": \"1.0.0\",\n        \"extsprintf\": \"1.3.0\",\n        \"json-schema\": \"0.2.3\",\n        \"verror\": \"1.10.0\"\n      }\n    },\n    \"keyv\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/keyv/-/keyv-3.1.0.tgz\",\n      \"integrity\": \"sha1-7MIoSG9pmR5J6UdkhaW+Ho/FxNk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"json-buffer\": \"3.0.0\"\n      }\n    },\n    \"killable\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/killable/-/killable-1.0.1.tgz\",\n      \"integrity\": \"sha1-TIzkQRh6Bhx0dPuHygjipjgZSJI=\",\n      \"dev\": true\n    },\n    \"kind-of\": {\n      \"version\": \"6.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-6.0.3.tgz\",\n      \"integrity\": \"sha1-B8BQNKbDSfoG4k+jWqdttFgM5N0=\",\n      \"dev\": true\n    },\n    \"last-call-webpack-plugin\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz\",\n      \"integrity\": \"sha1-l0LfDhDjz0blwDgcLekNOnotdVU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash\": \"^4.17.5\",\n        \"webpack-sources\": \"^1.1.0\"\n      }\n    },\n    \"latest-version\": {\n      \"version\": \"5.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/latest-version/-/latest-version-5.1.0.tgz\",\n      \"integrity\": \"sha1-EZ3+kI/jjRXfpD7NE/oS7Igy+s4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"package-json\": \"^6.3.0\"\n      }\n    },\n    \"linkify-it\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/linkify-it/-/linkify-it-2.2.0.tgz\",\n      \"integrity\": \"sha1-47VGl+eL+RXHCjis14/QngBYsc8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"uc.micro\": \"^1.0.1\"\n      }\n    },\n    \"load-script\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/load-script/-/load-script-1.0.0.tgz\",\n      \"integrity\": \"sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=\",\n      \"dev\": true\n    },\n    \"loader-runner\": {\n      \"version\": \"2.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/loader-runner/-/loader-runner-2.4.0.tgz\",\n      \"integrity\": \"sha1-7UcGa/5TTX6ExMe5mYwqdWB9k1c=\",\n      \"dev\": true\n    },\n    \"loader-utils\": {\n      \"version\": \"1.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/loader-utils/-/loader-utils-1.4.0.tgz\",\n      \"integrity\": \"sha1-xXm140yzSxp07cbB+za/o3HVphM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"big.js\": \"^5.2.2\",\n        \"emojis-list\": \"^3.0.0\",\n        \"json5\": \"^1.0.1\"\n      },\n      \"dependencies\": {\n        \"json5\": {\n          \"version\": \"1.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/json5/-/json5-1.0.1.tgz\",\n          \"integrity\": \"sha1-d5+wAYYE+oVOrL9iUhgNg1Q+Pb4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"minimist\": \"^1.2.0\"\n          }\n        }\n      }\n    },\n    \"locate-path\": {\n      \"version\": \"5.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/locate-path/-/locate-path-5.0.0.tgz\",\n      \"integrity\": \"sha1-Gvujlq/WdqbUJQTQpno6frn2KqA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"p-locate\": \"^4.1.0\"\n      }\n    },\n    \"lodash\": {\n      \"version\": \"4.17.20\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash/-/lodash-4.17.20.tgz\",\n      \"integrity\": \"sha1-tEqbYpe8tpjxxRo1RaKzs2jVnFI=\",\n      \"dev\": true\n    },\n    \"lodash._reinterpolate\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz\",\n      \"integrity\": \"sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=\",\n      \"dev\": true\n    },\n    \"lodash.clonedeep\": {\n      \"version\": \"4.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz\",\n      \"integrity\": \"sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=\",\n      \"dev\": true\n    },\n    \"lodash.debounce\": {\n      \"version\": \"4.0.8\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz\",\n      \"integrity\": \"sha1-gteb/zCmfEAF/9XiUVMArZyk168=\",\n      \"dev\": true\n    },\n    \"lodash.kebabcase\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz\",\n      \"integrity\": \"sha1-hImxyw0p/4gZXM7KRI/21swpXDY=\",\n      \"dev\": true\n    },\n    \"lodash.memoize\": {\n      \"version\": \"4.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz\",\n      \"integrity\": \"sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=\",\n      \"dev\": true\n    },\n    \"lodash.template\": {\n      \"version\": \"4.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash.template/-/lodash.template-4.5.0.tgz\",\n      \"integrity\": \"sha1-+XYZXPPzR9DV9SSDVp/oAxzM6Ks=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash._reinterpolate\": \"^3.0.0\",\n        \"lodash.templatesettings\": \"^4.0.0\"\n      }\n    },\n    \"lodash.templatesettings\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz\",\n      \"integrity\": \"sha1-5IExDwSdPPbUfpEq0JMTsVTw+zM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash._reinterpolate\": \"^3.0.0\"\n      }\n    },\n    \"lodash.uniq\": {\n      \"version\": \"4.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz\",\n      \"integrity\": \"sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=\",\n      \"dev\": true\n    },\n    \"loglevel\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/loglevel/-/loglevel-1.7.1.tgz\",\n      \"integrity\": \"sha1-AF/eL15uRwaPk1/yhXPhJe9y8Zc=\",\n      \"dev\": true\n    },\n    \"lower-case\": {\n      \"version\": \"1.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/lower-case/-/lower-case-1.1.4.tgz\",\n      \"integrity\": \"sha1-miyr0bno4K6ZOkv31YdcOcQujqw=\",\n      \"dev\": true\n    },\n    \"lowercase-keys\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz\",\n      \"integrity\": \"sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=\",\n      \"dev\": true\n    },\n    \"lru-cache\": {\n      \"version\": \"5.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/lru-cache/-/lru-cache-5.1.1.tgz\",\n      \"integrity\": \"sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"yallist\": \"^3.0.2\"\n      }\n    },\n    \"make-dir\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/make-dir/-/make-dir-3.1.0.tgz\",\n      \"integrity\": \"sha1-QV6WcEazp/HRhSd9hKpYIDcmoT8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"semver\": \"^6.0.0\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"6.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-6.3.0.tgz\",\n          \"integrity\": \"sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"map-cache\": {\n      \"version\": \"0.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/map-cache/-/map-cache-0.2.2.tgz\",\n      \"integrity\": \"sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=\",\n      \"dev\": true\n    },\n    \"map-visit\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/map-visit/-/map-visit-1.0.0.tgz\",\n      \"integrity\": \"sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=\",\n      \"dev\": true,\n      \"requires\": {\n        \"object-visit\": \"^1.0.0\"\n      }\n    },\n    \"markdown-it\": {\n      \"version\": \"8.4.2\",\n      \"resolved\": \"https://npm.quanziapp.com/markdown-it/-/markdown-it-8.4.2.tgz\",\n      \"integrity\": \"sha1-OG+YmY3BWjdyKqdyIIT0Agvdm1Q=\",\n      \"dev\": true,\n      \"requires\": {\n        \"argparse\": \"^1.0.7\",\n        \"entities\": \"~1.1.1\",\n        \"linkify-it\": \"^2.0.0\",\n        \"mdurl\": \"^1.0.1\",\n        \"uc.micro\": \"^1.0.5\"\n      }\n    },\n    \"markdown-it-anchor\": {\n      \"version\": \"5.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz\",\n      \"integrity\": \"sha1-1Ums1khWqOzRvqWDZe84Xv+6x0Q=\",\n      \"dev\": true\n    },\n    \"markdown-it-chain\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/markdown-it-chain/-/markdown-it-chain-1.3.0.tgz\",\n      \"integrity\": \"sha1-zPb+hsECZrr7TlRzgN/X8nfMF7w=\",\n      \"dev\": true,\n      \"requires\": {\n        \"webpack-chain\": \"^4.9.0\"\n      },\n      \"dependencies\": {\n        \"webpack-chain\": {\n          \"version\": \"4.12.1\",\n          \"resolved\": \"https://npm.quanziapp.com/webpack-chain/-/webpack-chain-4.12.1.tgz\",\n          \"integrity\": \"sha1-bIQ5u7KrVQlS1g4eqTGRQZBsAqY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"deepmerge\": \"^1.5.2\",\n            \"javascript-stringify\": \"^1.6.0\"\n          }\n        }\n      }\n    },\n    \"markdown-it-container\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/markdown-it-container/-/markdown-it-container-2.0.0.tgz\",\n      \"integrity\": \"sha1-ABm0P9Au7+zi8ZYKKJX7qBpARpU=\",\n      \"dev\": true\n    },\n    \"markdown-it-emoji\": {\n      \"version\": \"1.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz\",\n      \"integrity\": \"sha1-m+4OmpkKljupbfaYDE/dsF37Tcw=\",\n      \"dev\": true\n    },\n    \"markdown-it-table-of-contents\": {\n      \"version\": \"0.4.4\",\n      \"resolved\": \"https://npm.quanziapp.com/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz\",\n      \"integrity\": \"sha1-PcfOi4/BflmBx3zDmNF4Ixnzf7w=\",\n      \"dev\": true\n    },\n    \"md5.js\": {\n      \"version\": \"1.3.5\",\n      \"resolved\": \"https://npm.quanziapp.com/md5.js/-/md5.js-1.3.5.tgz\",\n      \"integrity\": \"sha1-tdB7jjIW4+J81yjXL3DR5qNCAF8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"hash-base\": \"^3.0.0\",\n        \"inherits\": \"^2.0.1\",\n        \"safe-buffer\": \"^5.1.2\"\n      }\n    },\n    \"mdn-data\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/mdn-data/-/mdn-data-2.0.4.tgz\",\n      \"integrity\": \"sha1-aZs8OKxvHXKAkaZGULZdOIUC/Vs=\",\n      \"dev\": true\n    },\n    \"mdurl\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/mdurl/-/mdurl-1.0.1.tgz\",\n      \"integrity\": \"sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=\",\n      \"dev\": true\n    },\n    \"media-typer\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/media-typer/-/media-typer-0.3.0.tgz\",\n      \"integrity\": \"sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=\",\n      \"dev\": true\n    },\n    \"memory-fs\": {\n      \"version\": \"0.4.1\",\n      \"resolved\": \"https://npm.quanziapp.com/memory-fs/-/memory-fs-0.4.1.tgz\",\n      \"integrity\": \"sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"errno\": \"^0.1.3\",\n        \"readable-stream\": \"^2.0.1\"\n      }\n    },\n    \"merge-descriptors\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz\",\n      \"integrity\": \"sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=\",\n      \"dev\": true\n    },\n    \"merge-source-map\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/merge-source-map/-/merge-source-map-1.1.0.tgz\",\n      \"integrity\": \"sha1-L93n5gIJOfcJBqaPLXrmheTIxkY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"source-map\": \"^0.6.1\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"merge2\": {\n      \"version\": \"1.4.1\",\n      \"resolved\": \"https://npm.quanziapp.com/merge2/-/merge2-1.4.1.tgz\",\n      \"integrity\": \"sha1-Q2iJL4hekHRVpv19xVwMnUBJkK4=\",\n      \"dev\": true\n    },\n    \"methods\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/methods/-/methods-1.1.2.tgz\",\n      \"integrity\": \"sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=\",\n      \"dev\": true\n    },\n    \"micromatch\": {\n      \"version\": \"3.1.10\",\n      \"resolved\": \"https://npm.quanziapp.com/micromatch/-/micromatch-3.1.10.tgz\",\n      \"integrity\": \"sha1-cIWbyVyYQJUvNZoGij/En57PrCM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"arr-diff\": \"^4.0.0\",\n        \"array-unique\": \"^0.3.2\",\n        \"braces\": \"^2.3.1\",\n        \"define-property\": \"^2.0.2\",\n        \"extend-shallow\": \"^3.0.2\",\n        \"extglob\": \"^2.0.4\",\n        \"fragment-cache\": \"^0.2.1\",\n        \"kind-of\": \"^6.0.2\",\n        \"nanomatch\": \"^1.2.9\",\n        \"object.pick\": \"^1.3.0\",\n        \"regex-not\": \"^1.0.0\",\n        \"snapdragon\": \"^0.8.1\",\n        \"to-regex\": \"^3.0.2\"\n      }\n    },\n    \"miller-rabin\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/miller-rabin/-/miller-rabin-4.0.1.tgz\",\n      \"integrity\": \"sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bn.js\": \"^4.0.0\",\n        \"brorand\": \"^1.0.1\"\n      },\n      \"dependencies\": {\n        \"bn.js\": {\n          \"version\": \"4.11.9\",\n          \"resolved\": \"https://npm.quanziapp.com/bn.js/-/bn.js-4.11.9.tgz\",\n          \"integrity\": \"sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"mime\": {\n      \"version\": \"2.4.6\",\n      \"resolved\": \"https://npm.quanziapp.com/mime/-/mime-2.4.6.tgz\",\n      \"integrity\": \"sha1-5bQHyQ20QvK+tbFiNz0Htpr/pNE=\",\n      \"dev\": true\n    },\n    \"mime-db\": {\n      \"version\": \"1.44.0\",\n      \"resolved\": \"https://npm.quanziapp.com/mime-db/-/mime-db-1.44.0.tgz\",\n      \"integrity\": \"sha1-+hHF6wrKEzS0Izy01S8QxaYnL5I=\",\n      \"dev\": true\n    },\n    \"mime-types\": {\n      \"version\": \"2.1.27\",\n      \"resolved\": \"https://npm.quanziapp.com/mime-types/-/mime-types-2.1.27.tgz\",\n      \"integrity\": \"sha1-R5SfmOJ56lMRn1ci4PNOUpvsAJ8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"mime-db\": \"1.44.0\"\n      }\n    },\n    \"mimic-response\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/mimic-response/-/mimic-response-1.0.1.tgz\",\n      \"integrity\": \"sha1-SSNTiHju9CBjy4o+OweYeBSHqxs=\",\n      \"dev\": true\n    },\n    \"min-document\": {\n      \"version\": \"2.19.0\",\n      \"resolved\": \"https://npm.quanziapp.com/min-document/-/min-document-2.19.0.tgz\",\n      \"integrity\": \"sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"dom-walk\": \"^0.1.0\"\n      }\n    },\n    \"mini-css-extract-plugin\": {\n      \"version\": \"0.6.0\",\n      \"resolved\": \"https://npm.quanziapp.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.6.0.tgz\",\n      \"integrity\": \"sha1-o/Ezctb83pEvPuTNA5ZlcEgB47k=\",\n      \"dev\": true,\n      \"requires\": {\n        \"loader-utils\": \"^1.1.0\",\n        \"normalize-url\": \"^2.0.1\",\n        \"schema-utils\": \"^1.0.0\",\n        \"webpack-sources\": \"^1.1.0\"\n      },\n      \"dependencies\": {\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        }\n      }\n    },\n    \"minimalistic-assert\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz\",\n      \"integrity\": \"sha1-LhlN4ERibUoQ5/f7wAznPoPk1cc=\",\n      \"dev\": true\n    },\n    \"minimalistic-crypto-utils\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz\",\n      \"integrity\": \"sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=\",\n      \"dev\": true\n    },\n    \"minimatch\": {\n      \"version\": \"3.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/minimatch/-/minimatch-3.0.4.tgz\",\n      \"integrity\": \"sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"brace-expansion\": \"^1.1.7\"\n      }\n    },\n    \"minimist\": {\n      \"version\": \"1.2.5\",\n      \"resolved\": \"https://npm.quanziapp.com/minimist/-/minimist-1.2.5.tgz\",\n      \"integrity\": \"sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=\",\n      \"dev\": true\n    },\n    \"mississippi\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/mississippi/-/mississippi-3.0.0.tgz\",\n      \"integrity\": \"sha1-6goykfl+C16HdrNj1fChLZTGcCI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"concat-stream\": \"^1.5.0\",\n        \"duplexify\": \"^3.4.2\",\n        \"end-of-stream\": \"^1.1.0\",\n        \"flush-write-stream\": \"^1.0.0\",\n        \"from2\": \"^2.1.0\",\n        \"parallel-transform\": \"^1.1.0\",\n        \"pump\": \"^3.0.0\",\n        \"pumpify\": \"^1.3.3\",\n        \"stream-each\": \"^1.1.0\",\n        \"through2\": \"^2.0.0\"\n      }\n    },\n    \"mixin-deep\": {\n      \"version\": \"1.3.2\",\n      \"resolved\": \"https://npm.quanziapp.com/mixin-deep/-/mixin-deep-1.3.2.tgz\",\n      \"integrity\": \"sha1-ESC0PcNZp4Xc5ltVuC4lfM9HlWY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"for-in\": \"^1.0.2\",\n        \"is-extendable\": \"^1.0.1\"\n      },\n      \"dependencies\": {\n        \"is-extendable\": {\n          \"version\": \"1.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/is-extendable/-/is-extendable-1.0.1.tgz\",\n          \"integrity\": \"sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-plain-object\": \"^2.0.4\"\n          }\n        }\n      }\n    },\n    \"mkdirp\": {\n      \"version\": \"0.5.5\",\n      \"resolved\": \"https://npm.quanziapp.com/mkdirp/-/mkdirp-0.5.5.tgz\",\n      \"integrity\": \"sha1-2Rzv1i0UNsoPQWIOJRKI1CAJne8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"minimist\": \"^1.2.5\"\n      }\n    },\n    \"move-concurrently\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/move-concurrently/-/move-concurrently-1.0.1.tgz\",\n      \"integrity\": \"sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=\",\n      \"dev\": true,\n      \"requires\": {\n        \"aproba\": \"^1.1.1\",\n        \"copy-concurrently\": \"^1.0.0\",\n        \"fs-write-stream-atomic\": \"^1.0.8\",\n        \"mkdirp\": \"^0.5.1\",\n        \"rimraf\": \"^2.5.4\",\n        \"run-queue\": \"^1.0.3\"\n      }\n    },\n    \"ms\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.1.2.tgz\",\n      \"integrity\": \"sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=\",\n      \"dev\": true\n    },\n    \"multicast-dns\": {\n      \"version\": \"6.2.3\",\n      \"resolved\": \"https://npm.quanziapp.com/multicast-dns/-/multicast-dns-6.2.3.tgz\",\n      \"integrity\": \"sha1-oOx72QVcQoL3kMPIL04o2zsxsik=\",\n      \"dev\": true,\n      \"requires\": {\n        \"dns-packet\": \"^1.3.1\",\n        \"thunky\": \"^1.0.2\"\n      }\n    },\n    \"multicast-dns-service-types\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz\",\n      \"integrity\": \"sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=\",\n      \"dev\": true\n    },\n    \"nan\": {\n      \"version\": \"2.14.2\",\n      \"resolved\": \"https://npm.quanziapp.com/nan/-/nan-2.14.2.tgz\",\n      \"integrity\": \"sha1-9TdkAGlRaPTMaUrJOT0MlYXu6hk=\",\n      \"dev\": true,\n      \"optional\": true\n    },\n    \"nanomatch\": {\n      \"version\": \"1.2.13\",\n      \"resolved\": \"https://npm.quanziapp.com/nanomatch/-/nanomatch-1.2.13.tgz\",\n      \"integrity\": \"sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"arr-diff\": \"^4.0.0\",\n        \"array-unique\": \"^0.3.2\",\n        \"define-property\": \"^2.0.2\",\n        \"extend-shallow\": \"^3.0.2\",\n        \"fragment-cache\": \"^0.2.1\",\n        \"is-windows\": \"^1.0.2\",\n        \"kind-of\": \"^6.0.2\",\n        \"object.pick\": \"^1.3.0\",\n        \"regex-not\": \"^1.0.0\",\n        \"snapdragon\": \"^0.8.1\",\n        \"to-regex\": \"^3.0.1\"\n      }\n    },\n    \"negotiator\": {\n      \"version\": \"0.6.2\",\n      \"resolved\": \"https://npm.quanziapp.com/negotiator/-/negotiator-0.6.2.tgz\",\n      \"integrity\": \"sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=\",\n      \"dev\": true\n    },\n    \"neo-async\": {\n      \"version\": \"2.6.2\",\n      \"resolved\": \"https://npm.quanziapp.com/neo-async/-/neo-async-2.6.2.tgz\",\n      \"integrity\": \"sha1-tKr7k+OustgXTKU88WOrfXMIMF8=\",\n      \"dev\": true\n    },\n    \"nice-try\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/nice-try/-/nice-try-1.0.5.tgz\",\n      \"integrity\": \"sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y=\",\n      \"dev\": true\n    },\n    \"no-case\": {\n      \"version\": \"2.3.2\",\n      \"resolved\": \"https://npm.quanziapp.com/no-case/-/no-case-2.3.2.tgz\",\n      \"integrity\": \"sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lower-case\": \"^1.1.1\"\n      }\n    },\n    \"node-forge\": {\n      \"version\": \"0.10.0\",\n      \"resolved\": \"https://npm.quanziapp.com/node-forge/-/node-forge-0.10.0.tgz\",\n      \"integrity\": \"sha1-Mt6ir7Ppkm8C7lzoeUkCaRpna/M=\",\n      \"dev\": true\n    },\n    \"node-libs-browser\": {\n      \"version\": \"2.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz\",\n      \"integrity\": \"sha1-tk9RPRgzhiX5A0bSew0jXmMfZCU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"assert\": \"^1.1.1\",\n        \"browserify-zlib\": \"^0.2.0\",\n        \"buffer\": \"^4.3.0\",\n        \"console-browserify\": \"^1.1.0\",\n        \"constants-browserify\": \"^1.0.0\",\n        \"crypto-browserify\": \"^3.11.0\",\n        \"domain-browser\": \"^1.1.1\",\n        \"events\": \"^3.0.0\",\n        \"https-browserify\": \"^1.0.0\",\n        \"os-browserify\": \"^0.3.0\",\n        \"path-browserify\": \"0.0.1\",\n        \"process\": \"^0.11.10\",\n        \"punycode\": \"^1.2.4\",\n        \"querystring-es3\": \"^0.2.0\",\n        \"readable-stream\": \"^2.3.3\",\n        \"stream-browserify\": \"^2.0.1\",\n        \"stream-http\": \"^2.7.2\",\n        \"string_decoder\": \"^1.0.0\",\n        \"timers-browserify\": \"^2.0.4\",\n        \"tty-browserify\": \"0.0.0\",\n        \"url\": \"^0.11.0\",\n        \"util\": \"^0.11.0\",\n        \"vm-browserify\": \"^1.0.1\"\n      },\n      \"dependencies\": {\n        \"punycode\": {\n          \"version\": \"1.4.1\",\n          \"resolved\": \"https://npm.quanziapp.com/punycode/-/punycode-1.4.1.tgz\",\n          \"integrity\": \"sha1-wNWmOycYgArY4esPpSachN1BhF4=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"node-releases\": {\n      \"version\": \"1.1.67\",\n      \"resolved\": \"https://npm.quanziapp.com/node-releases/-/node-releases-1.1.67.tgz\",\n      \"integrity\": \"sha1-KOv8zNC6pqrY6NTY/ky8Sa4jnBI=\",\n      \"dev\": true\n    },\n    \"nopt\": {\n      \"version\": \"1.0.10\",\n      \"resolved\": \"https://npm.quanziapp.com/nopt/-/nopt-1.0.10.tgz\",\n      \"integrity\": \"sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"abbrev\": \"1\"\n      }\n    },\n    \"normalize-path\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/normalize-path/-/normalize-path-3.0.0.tgz\",\n      \"integrity\": \"sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=\",\n      \"dev\": true\n    },\n    \"normalize-range\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/normalize-range/-/normalize-range-0.1.2.tgz\",\n      \"integrity\": \"sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=\",\n      \"dev\": true\n    },\n    \"normalize-url\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/normalize-url/-/normalize-url-2.0.1.tgz\",\n      \"integrity\": \"sha1-g1qdoVUfom9w6SMpBpojqmV01+Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"prepend-http\": \"^2.0.0\",\n        \"query-string\": \"^5.0.1\",\n        \"sort-keys\": \"^2.0.0\"\n      }\n    },\n    \"npm-run-path\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/npm-run-path/-/npm-run-path-2.0.2.tgz\",\n      \"integrity\": \"sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"path-key\": \"^2.0.0\"\n      }\n    },\n    \"nprogress\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/nprogress/-/nprogress-0.2.0.tgz\",\n      \"integrity\": \"sha1-y480xTIT2JVyP8urkH6UIq28r7E=\",\n      \"dev\": true\n    },\n    \"nth-check\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/nth-check/-/nth-check-1.0.2.tgz\",\n      \"integrity\": \"sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"boolbase\": \"~1.0.0\"\n      }\n    },\n    \"num2fraction\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/num2fraction/-/num2fraction-1.2.2.tgz\",\n      \"integrity\": \"sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=\",\n      \"dev\": true\n    },\n    \"oauth-sign\": {\n      \"version\": \"0.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/oauth-sign/-/oauth-sign-0.9.0.tgz\",\n      \"integrity\": \"sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=\",\n      \"dev\": true\n    },\n    \"object-assign\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/object-assign/-/object-assign-4.1.1.tgz\",\n      \"integrity\": \"sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=\",\n      \"dev\": true\n    },\n    \"object-copy\": {\n      \"version\": \"0.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/object-copy/-/object-copy-0.1.0.tgz\",\n      \"integrity\": \"sha1-fn2Fi3gb18mRpBupde04EnVOmYw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"copy-descriptor\": \"^0.1.0\",\n        \"define-property\": \"^0.2.5\",\n        \"kind-of\": \"^3.0.3\"\n      },\n      \"dependencies\": {\n        \"define-property\": {\n          \"version\": \"0.2.5\",\n          \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-0.2.5.tgz\",\n          \"integrity\": \"sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-descriptor\": \"^0.1.0\"\n          }\n        },\n        \"kind-of\": {\n          \"version\": \"3.2.2\",\n          \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-3.2.2.tgz\",\n          \"integrity\": \"sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-buffer\": \"^1.1.5\"\n          }\n        }\n      }\n    },\n    \"object-inspect\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://npm.quanziapp.com/object-inspect/-/object-inspect-1.9.0.tgz\",\n      \"integrity\": \"sha1-yQUh104RJ7ZyZt7TOUrWEWmGUzo=\",\n      \"dev\": true\n    },\n    \"object-is\": {\n      \"version\": \"1.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/object-is/-/object-is-1.1.4.tgz\",\n      \"integrity\": \"sha1-Y9bIPACkP0y8lDTrl1fIpbhWUGg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\"\n      }\n    },\n    \"object-keys\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/object-keys/-/object-keys-1.1.1.tgz\",\n      \"integrity\": \"sha1-HEfyct8nfzsdrwYWd9nILiMixg4=\",\n      \"dev\": true\n    },\n    \"object-visit\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/object-visit/-/object-visit-1.0.1.tgz\",\n      \"integrity\": \"sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"isobject\": \"^3.0.0\"\n      }\n    },\n    \"object.assign\": {\n      \"version\": \"4.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/object.assign/-/object.assign-4.1.2.tgz\",\n      \"integrity\": \"sha1-DtVKNC7Os3s4/3brgxoOeIy2OUA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"has-symbols\": \"^1.0.1\",\n        \"object-keys\": \"^1.1.1\"\n      }\n    },\n    \"object.getownpropertydescriptors\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz\",\n      \"integrity\": \"sha1-Df2o0QgHTZxWPoBJDIg7ZmEJFUQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.18.0-next.1\"\n      }\n    },\n    \"object.pick\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/object.pick/-/object.pick-1.3.0.tgz\",\n      \"integrity\": \"sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=\",\n      \"dev\": true,\n      \"requires\": {\n        \"isobject\": \"^3.0.1\"\n      }\n    },\n    \"object.values\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/object.values/-/object.values-1.1.2.tgz\",\n      \"integrity\": \"sha1-eiAV4G/LD1Rr1lJIbOhYOkcxxzE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.18.0-next.1\",\n        \"has\": \"^1.0.3\"\n      }\n    },\n    \"obuf\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/obuf/-/obuf-1.1.2.tgz\",\n      \"integrity\": \"sha1-Cb6jND1BhZ69RGKS0RydTbYZCE4=\",\n      \"dev\": true\n    },\n    \"on-finished\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/on-finished/-/on-finished-2.3.0.tgz\",\n      \"integrity\": \"sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ee-first\": \"1.1.1\"\n      }\n    },\n    \"on-headers\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/on-headers/-/on-headers-1.0.2.tgz\",\n      \"integrity\": \"sha1-dysK5qqlJcOZ5Imt+tkMQD6zwo8=\",\n      \"dev\": true\n    },\n    \"once\": {\n      \"version\": \"1.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/once/-/once-1.4.0.tgz\",\n      \"integrity\": \"sha1-WDsap3WWHUsROsF9nFC6753Xa9E=\",\n      \"dev\": true,\n      \"requires\": {\n        \"wrappy\": \"1\"\n      }\n    },\n    \"opencollective-postinstall\": {\n      \"version\": \"2.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz\",\n      \"integrity\": \"sha1-eg//l49tv6TQBiOPusmO1BmMMlk=\",\n      \"dev\": true\n    },\n    \"opn\": {\n      \"version\": \"5.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/opn/-/opn-5.5.0.tgz\",\n      \"integrity\": \"sha1-/HFk+rVtI1kExRw7J9pnWMo7m/w=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-wsl\": \"^1.1.0\"\n      }\n    },\n    \"optimize-css-assets-webpack-plugin\": {\n      \"version\": \"5.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz\",\n      \"integrity\": \"sha1-hYg8ZSiqoC4wu62ZCMkpJrtS3JA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssnano\": \"^4.1.10\",\n        \"last-call-webpack-plugin\": \"^3.0.0\"\n      }\n    },\n    \"original\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/original/-/original-1.0.2.tgz\",\n      \"integrity\": \"sha1-5EKmHP/hxf0gpl8yYcJmY7MD8l8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"url-parse\": \"^1.4.3\"\n      }\n    },\n    \"os-browserify\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/os-browserify/-/os-browserify-0.3.0.tgz\",\n      \"integrity\": \"sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=\",\n      \"dev\": true\n    },\n    \"p-cancelable\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/p-cancelable/-/p-cancelable-1.1.0.tgz\",\n      \"integrity\": \"sha1-0HjRWjr0CSIMiG8dmgyi5EGrJsw=\",\n      \"dev\": true\n    },\n    \"p-finally\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/p-finally/-/p-finally-1.0.0.tgz\",\n      \"integrity\": \"sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=\",\n      \"dev\": true\n    },\n    \"p-limit\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/p-limit/-/p-limit-2.3.0.tgz\",\n      \"integrity\": \"sha1-PdM8ZHohT9//2DWTPrCG2g3CHbE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"p-try\": \"^2.0.0\"\n      }\n    },\n    \"p-locate\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/p-locate/-/p-locate-4.1.0.tgz\",\n      \"integrity\": \"sha1-o0KLtwiLOmApL2aRkni3wpetTwc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"p-limit\": \"^2.2.0\"\n      }\n    },\n    \"p-map\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/p-map/-/p-map-2.1.0.tgz\",\n      \"integrity\": \"sha1-MQko/u+cnsxltosXaTAYpmXOoXU=\",\n      \"dev\": true\n    },\n    \"p-retry\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/p-retry/-/p-retry-3.0.1.tgz\",\n      \"integrity\": \"sha1-MWtMiJPiyNwc+okfQGxLQivr8yg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"retry\": \"^0.12.0\"\n      }\n    },\n    \"p-try\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/p-try/-/p-try-2.2.0.tgz\",\n      \"integrity\": \"sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=\",\n      \"dev\": true\n    },\n    \"package-json\": {\n      \"version\": \"6.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/package-json/-/package-json-6.5.0.tgz\",\n      \"integrity\": \"sha1-b+7ayjXnVyWHbQsOZJdGl/7RRbA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"got\": \"^9.6.0\",\n        \"registry-auth-token\": \"^4.0.0\",\n        \"registry-url\": \"^5.0.0\",\n        \"semver\": \"^6.2.0\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"6.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-6.3.0.tgz\",\n          \"integrity\": \"sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"pako\": {\n      \"version\": \"1.0.11\",\n      \"resolved\": \"https://npm.quanziapp.com/pako/-/pako-1.0.11.tgz\",\n      \"integrity\": \"sha1-bJWZ00DVTf05RjgCUqNXBaa5kr8=\",\n      \"dev\": true\n    },\n    \"parallel-transform\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/parallel-transform/-/parallel-transform-1.2.0.tgz\",\n      \"integrity\": \"sha1-kEnKN9bLIYLDsdLHIL6U0UpYFPw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cyclist\": \"^1.0.1\",\n        \"inherits\": \"^2.0.3\",\n        \"readable-stream\": \"^2.1.5\"\n      }\n    },\n    \"param-case\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/param-case/-/param-case-2.1.1.tgz\",\n      \"integrity\": \"sha1-35T9jPZTHs915r75oIWPvHK+Ikc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"no-case\": \"^2.2.0\"\n      }\n    },\n    \"parse-asn1\": {\n      \"version\": \"5.1.6\",\n      \"resolved\": \"https://npm.quanziapp.com/parse-asn1/-/parse-asn1-5.1.6.tgz\",\n      \"integrity\": \"sha1-OFCAo+wTy2KmLTlAnLPoiETNrtQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"asn1.js\": \"^5.2.0\",\n        \"browserify-aes\": \"^1.0.0\",\n        \"evp_bytestokey\": \"^1.0.0\",\n        \"pbkdf2\": \"^3.0.3\",\n        \"safe-buffer\": \"^5.1.1\"\n      }\n    },\n    \"parse-json\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/parse-json/-/parse-json-4.0.0.tgz\",\n      \"integrity\": \"sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"error-ex\": \"^1.3.1\",\n        \"json-parse-better-errors\": \"^1.0.1\"\n      }\n    },\n    \"parseurl\": {\n      \"version\": \"1.3.3\",\n      \"resolved\": \"https://npm.quanziapp.com/parseurl/-/parseurl-1.3.3.tgz\",\n      \"integrity\": \"sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=\",\n      \"dev\": true\n    },\n    \"pascalcase\": {\n      \"version\": \"0.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/pascalcase/-/pascalcase-0.1.1.tgz\",\n      \"integrity\": \"sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=\",\n      \"dev\": true\n    },\n    \"path-browserify\": {\n      \"version\": \"0.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/path-browserify/-/path-browserify-0.0.1.tgz\",\n      \"integrity\": \"sha1-5sTd1+06onxoogzE5Q4aTug7vEo=\",\n      \"dev\": true\n    },\n    \"path-dirname\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/path-dirname/-/path-dirname-1.0.2.tgz\",\n      \"integrity\": \"sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=\",\n      \"dev\": true\n    },\n    \"path-exists\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/path-exists/-/path-exists-4.0.0.tgz\",\n      \"integrity\": \"sha1-UTvb4tO5XXdi6METfvoZXGxhtbM=\",\n      \"dev\": true\n    },\n    \"path-is-absolute\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz\",\n      \"integrity\": \"sha1-F0uSaHNVNP+8es5r9TpanhtcX18=\",\n      \"dev\": true\n    },\n    \"path-is-inside\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/path-is-inside/-/path-is-inside-1.0.2.tgz\",\n      \"integrity\": \"sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=\",\n      \"dev\": true\n    },\n    \"path-key\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/path-key/-/path-key-2.0.1.tgz\",\n      \"integrity\": \"sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=\",\n      \"dev\": true\n    },\n    \"path-parse\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/path-parse/-/path-parse-1.0.6.tgz\",\n      \"integrity\": \"sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=\",\n      \"dev\": true\n    },\n    \"path-to-regexp\": {\n      \"version\": \"0.1.7\",\n      \"resolved\": \"https://npm.quanziapp.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz\",\n      \"integrity\": \"sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=\",\n      \"dev\": true\n    },\n    \"path-type\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/path-type/-/path-type-3.0.0.tgz\",\n      \"integrity\": \"sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=\",\n      \"dev\": true,\n      \"requires\": {\n        \"pify\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"pify\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/pify/-/pify-3.0.0.tgz\",\n          \"integrity\": \"sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"pbkdf2\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/pbkdf2/-/pbkdf2-3.1.1.tgz\",\n      \"integrity\": \"sha1-y4cksPramEWWhW0abrr9NYRlS5Q=\",\n      \"dev\": true,\n      \"requires\": {\n        \"create-hash\": \"^1.1.2\",\n        \"create-hmac\": \"^1.1.4\",\n        \"ripemd160\": \"^2.0.1\",\n        \"safe-buffer\": \"^5.0.1\",\n        \"sha.js\": \"^2.4.8\"\n      }\n    },\n    \"performance-now\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/performance-now/-/performance-now-2.1.0.tgz\",\n      \"integrity\": \"sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=\",\n      \"dev\": true\n    },\n    \"picomatch\": {\n      \"version\": \"2.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/picomatch/-/picomatch-2.2.2.tgz\",\n      \"integrity\": \"sha1-IfMz6ba46v8CRo9RRupAbTRfTa0=\",\n      \"dev\": true,\n      \"optional\": true\n    },\n    \"pify\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/pify/-/pify-4.0.1.tgz\",\n      \"integrity\": \"sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=\",\n      \"dev\": true\n    },\n    \"pinkie\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/pinkie/-/pinkie-2.0.4.tgz\",\n      \"integrity\": \"sha1-clVrgM+g1IqXToDnckjoDtT3+HA=\",\n      \"dev\": true\n    },\n    \"pinkie-promise\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz\",\n      \"integrity\": \"sha1-ITXW36ejWMBprJsXh3YogihFD/o=\",\n      \"dev\": true,\n      \"requires\": {\n        \"pinkie\": \"^2.0.0\"\n      }\n    },\n    \"pkg-dir\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/pkg-dir/-/pkg-dir-4.2.0.tgz\",\n      \"integrity\": \"sha1-8JkTPfft5CLoHR2ESCcO6z5CYfM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"find-up\": \"^4.0.0\"\n      }\n    },\n    \"portfinder\": {\n      \"version\": \"1.0.28\",\n      \"resolved\": \"https://npm.quanziapp.com/portfinder/-/portfinder-1.0.28.tgz\",\n      \"integrity\": \"sha1-Z8RiKFK9U3TdHdkA93n1NGL6x3g=\",\n      \"dev\": true,\n      \"requires\": {\n        \"async\": \"^2.6.2\",\n        \"debug\": \"^3.1.1\",\n        \"mkdirp\": \"^0.5.5\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"3.2.7\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-3.2.7.tgz\",\n          \"integrity\": \"sha1-clgLfpFF+zm2Z2+cXl+xALk0F5o=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"^2.1.1\"\n          }\n        }\n      }\n    },\n    \"posix-character-classes\": {\n      \"version\": \"0.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz\",\n      \"integrity\": \"sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=\",\n      \"dev\": true\n    },\n    \"postcss\": {\n      \"version\": \"7.0.35\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss/-/postcss-7.0.35.tgz\",\n      \"integrity\": \"sha1-0r4AuZj38hHYonaXQHny6SuXDiQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"chalk\": \"^2.4.2\",\n        \"source-map\": \"^0.6.1\",\n        \"supports-color\": \"^6.1.0\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        },\n        \"supports-color\": {\n          \"version\": \"6.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/supports-color/-/supports-color-6.1.0.tgz\",\n          \"integrity\": \"sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"has-flag\": \"^3.0.0\"\n          }\n        }\n      }\n    },\n    \"postcss-calc\": {\n      \"version\": \"7.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-calc/-/postcss-calc-7.0.5.tgz\",\n      \"integrity\": \"sha1-+KbpnxLmGcLrwjz2xIb9wVhgkz4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.27\",\n        \"postcss-selector-parser\": \"^6.0.2\",\n        \"postcss-value-parser\": \"^4.0.2\"\n      }\n    },\n    \"postcss-colormin\": {\n      \"version\": \"4.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz\",\n      \"integrity\": \"sha1-rgYLzpPteUrHEmTwgTLVUJVr04E=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.0.0\",\n        \"color\": \"^3.0.0\",\n        \"has\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-convert-values\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz\",\n      \"integrity\": \"sha1-yjgT7U2g+BL51DcDWE5Enr4Ymn8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-discard-comments\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz\",\n      \"integrity\": \"sha1-H7q9LCRr/2qq15l7KwkY9NevQDM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\"\n      }\n    },\n    \"postcss-discard-duplicates\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz\",\n      \"integrity\": \"sha1-P+EzzTyCKC5VD8myORdqkge3hOs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\"\n      }\n    },\n    \"postcss-discard-empty\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz\",\n      \"integrity\": \"sha1-yMlR6fc+2UKAGUWERKAq2Qu592U=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\"\n      }\n    },\n    \"postcss-discard-overridden\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz\",\n      \"integrity\": \"sha1-ZSrvipZybwKfXj4AFG7npOdV/1c=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\"\n      }\n    },\n    \"postcss-load-config\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz\",\n      \"integrity\": \"sha1-xepQTyxK7zPHNZo03jVzdyrXUCo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cosmiconfig\": \"^5.0.0\",\n        \"import-cwd\": \"^2.0.0\"\n      }\n    },\n    \"postcss-loader\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-loader/-/postcss-loader-3.0.0.tgz\",\n      \"integrity\": \"sha1-a5eUPkfHLYRfqeA/Jzdz1OjdbC0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"loader-utils\": \"^1.1.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-load-config\": \"^2.0.0\",\n        \"schema-utils\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        }\n      }\n    },\n    \"postcss-merge-longhand\": {\n      \"version\": \"4.0.11\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz\",\n      \"integrity\": \"sha1-YvSaE+Sg7gTnuY9CuxYGLKJUniQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"css-color-names\": \"0.0.4\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\",\n        \"stylehacks\": \"^4.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-merge-rules\": {\n      \"version\": \"4.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz\",\n      \"integrity\": \"sha1-NivqT/Wh+Y5AdacTxsslrv75plA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.0.0\",\n        \"caniuse-api\": \"^3.0.0\",\n        \"cssnano-util-same-parent\": \"^4.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-selector-parser\": \"^3.0.0\",\n        \"vendors\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-selector-parser\": {\n          \"version\": \"3.1.2\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz\",\n          \"integrity\": \"sha1-sxD1xMD9r3b5SQK7qjDbaqhPUnA=\",\n          \"dev\": true,\n          \"requires\": {\n            \"dot-prop\": \"^5.2.0\",\n            \"indexes-of\": \"^1.0.1\",\n            \"uniq\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"postcss-minify-font-values\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz\",\n      \"integrity\": \"sha1-zUw0TM5HQ0P6xdgiBqssvLiv1aY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-minify-gradients\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz\",\n      \"integrity\": \"sha1-k7KcL/UJnFNe7NpWxKpuZlpmNHE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssnano-util-get-arguments\": \"^4.0.0\",\n        \"is-color-stop\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-minify-params\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz\",\n      \"integrity\": \"sha1-a5zvAwwR41Jh+V9hjJADbWgNuHQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"alphanum-sort\": \"^1.0.0\",\n        \"browserslist\": \"^4.0.0\",\n        \"cssnano-util-get-arguments\": \"^4.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\",\n        \"uniqs\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-minify-selectors\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz\",\n      \"integrity\": \"sha1-4uXrQL/uUA0M2SQ1APX46kJi+9g=\",\n      \"dev\": true,\n      \"requires\": {\n        \"alphanum-sort\": \"^1.0.0\",\n        \"has\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-selector-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-selector-parser\": {\n          \"version\": \"3.1.2\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz\",\n          \"integrity\": \"sha1-sxD1xMD9r3b5SQK7qjDbaqhPUnA=\",\n          \"dev\": true,\n          \"requires\": {\n            \"dot-prop\": \"^5.2.0\",\n            \"indexes-of\": \"^1.0.1\",\n            \"uniq\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"postcss-modules-extract-imports\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz\",\n      \"integrity\": \"sha1-gYcZoa4doyX5gyRGsBE27rSTzX4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.5\"\n      }\n    },\n    \"postcss-modules-local-by-default\": {\n      \"version\": \"2.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz\",\n      \"integrity\": \"sha1-3ZlT9t1Ha1/R7y2IMMiSl2C1bmM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.6\",\n        \"postcss-selector-parser\": \"^6.0.0\",\n        \"postcss-value-parser\": \"^3.3.1\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-modules-scope\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz\",\n      \"integrity\": \"sha1-OFyuATzHdD9afXYC0Qc6iequYu4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.6\",\n        \"postcss-selector-parser\": \"^6.0.0\"\n      }\n    },\n    \"postcss-modules-values\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz\",\n      \"integrity\": \"sha1-R5tG3Axco9x/pScIUYNrnscVL2Q=\",\n      \"dev\": true,\n      \"requires\": {\n        \"icss-replace-symbols\": \"^1.1.0\",\n        \"postcss\": \"^7.0.6\"\n      }\n    },\n    \"postcss-normalize-charset\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz\",\n      \"integrity\": \"sha1-izWt067oOhNrBHHg1ZvlilAoXdQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\"\n      }\n    },\n    \"postcss-normalize-display-values\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz\",\n      \"integrity\": \"sha1-Db4EpM6QY9RmftK+R2u4MMglk1o=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssnano-util-get-match\": \"^4.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-normalize-positions\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz\",\n      \"integrity\": \"sha1-BfdX+E8mBDc3g2ipH4ky1LECkX8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssnano-util-get-arguments\": \"^4.0.0\",\n        \"has\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-normalize-repeat-style\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz\",\n      \"integrity\": \"sha1-xOu8KJ85kaAo1EdRy90RkYsXkQw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssnano-util-get-arguments\": \"^4.0.0\",\n        \"cssnano-util-get-match\": \"^4.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-normalize-string\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz\",\n      \"integrity\": \"sha1-zUTECrB6DHo23F6Zqs4eyk7CaQw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"has\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-normalize-timing-functions\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz\",\n      \"integrity\": \"sha1-jgCcoqOUnNr4rSPmtquZy159KNk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssnano-util-get-match\": \"^4.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-normalize-unicode\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz\",\n      \"integrity\": \"sha1-hBvUj9zzAZrUuqdJOj02O1KuHPs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-normalize-url\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz\",\n      \"integrity\": \"sha1-EOQ3+GvHx+WPe5ZS7YeNqqlfquE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-absolute-url\": \"^2.0.0\",\n        \"normalize-url\": \"^3.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"normalize-url\": {\n          \"version\": \"3.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/normalize-url/-/normalize-url-3.3.0.tgz\",\n          \"integrity\": \"sha1-suHE3E98bVd0PfczpPWXjRhlBVk=\",\n          \"dev\": true\n        },\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-normalize-whitespace\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz\",\n      \"integrity\": \"sha1-vx1AcP5Pzqh9E0joJdjMDF+qfYI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-ordered-values\": {\n      \"version\": \"4.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz\",\n      \"integrity\": \"sha1-DPdcgg7H1cTSgBiVWeC1ceusDu4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssnano-util-get-arguments\": \"^4.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-reduce-initial\": {\n      \"version\": \"4.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz\",\n      \"integrity\": \"sha1-f9QuvqXpyBRgljniwuhK4nC6SN8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.0.0\",\n        \"caniuse-api\": \"^3.0.0\",\n        \"has\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\"\n      }\n    },\n    \"postcss-reduce-transforms\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz\",\n      \"integrity\": \"sha1-F++kBerMbge+NBSlyi0QdGgdTik=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssnano-util-get-match\": \"^4.0.0\",\n        \"has\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-safe-parser\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz\",\n      \"integrity\": \"sha1-ptTkjw832ffBGypYG/APi6SHC5Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss\": \"^7.0.26\"\n      }\n    },\n    \"postcss-selector-parser\": {\n      \"version\": \"6.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz\",\n      \"integrity\": \"sha1-VgdaE4CgRgTDiwY+p3Z6Epr1wrM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssesc\": \"^3.0.0\",\n        \"indexes-of\": \"^1.0.1\",\n        \"uniq\": \"^1.0.1\",\n        \"util-deprecate\": \"^1.0.2\"\n      }\n    },\n    \"postcss-svgo\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz\",\n      \"integrity\": \"sha1-F7mXvHEbMzurFDqu07jT1uPTglg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-svg\": \"^3.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-value-parser\": \"^3.0.0\",\n        \"svgo\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-value-parser\": {\n          \"version\": \"3.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz\",\n          \"integrity\": \"sha1-n/giVH4okyE88cMO+lGsX9G6goE=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-unique-selectors\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz\",\n      \"integrity\": \"sha1-lEaRHzKJv9ZMbWgPBzwDsfnuS6w=\",\n      \"dev\": true,\n      \"requires\": {\n        \"alphanum-sort\": \"^1.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"uniqs\": \"^2.0.0\"\n      }\n    },\n    \"postcss-value-parser\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz\",\n      \"integrity\": \"sha1-RD9qIM7WSBor2k+oUypuVdeJoss=\",\n      \"dev\": true\n    },\n    \"prepend-http\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/prepend-http/-/prepend-http-2.0.0.tgz\",\n      \"integrity\": \"sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=\",\n      \"dev\": true\n    },\n    \"prettier\": {\n      \"version\": \"1.19.1\",\n      \"resolved\": \"https://npm.quanziapp.com/prettier/-/prettier-1.19.1.tgz\",\n      \"integrity\": \"sha1-99f1/4qc2HKnvkyhQglZVqYHl8s=\",\n      \"dev\": true,\n      \"optional\": true\n    },\n    \"pretty-error\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/pretty-error/-/pretty-error-2.1.2.tgz\",\n      \"integrity\": \"sha1-von4LYGxyG7I/fvDhQRYgnJ/k7Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash\": \"^4.17.20\",\n        \"renderkid\": \"^2.0.4\"\n      }\n    },\n    \"pretty-time\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/pretty-time/-/pretty-time-1.1.0.tgz\",\n      \"integrity\": \"sha1-/7dCmvq7hTXDRqNOQYc63z103Q4=\",\n      \"dev\": true\n    },\n    \"prismjs\": {\n      \"version\": \"1.22.0\",\n      \"resolved\": \"https://npm.quanziapp.com/prismjs/-/prismjs-1.22.0.tgz\",\n      \"integrity\": \"sha1-c8NACvxYqCPdfu0CP44c6f2Jd/o=\",\n      \"dev\": true,\n      \"requires\": {\n        \"clipboard\": \"^2.0.0\"\n      }\n    },\n    \"process\": {\n      \"version\": \"0.11.10\",\n      \"resolved\": \"https://npm.quanziapp.com/process/-/process-0.11.10.tgz\",\n      \"integrity\": \"sha1-czIwDoQBYb2j5podHZGn1LwW8YI=\",\n      \"dev\": true\n    },\n    \"process-nextick-args\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz\",\n      \"integrity\": \"sha1-eCDZsWEgzFXKmud5JoCufbptf+I=\",\n      \"dev\": true\n    },\n    \"promise-inflight\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/promise-inflight/-/promise-inflight-1.0.1.tgz\",\n      \"integrity\": \"sha1-mEcocL8igTL8vdhoEputEsPAKeM=\",\n      \"dev\": true\n    },\n    \"proxy-addr\": {\n      \"version\": \"2.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/proxy-addr/-/proxy-addr-2.0.6.tgz\",\n      \"integrity\": \"sha1-/cIzZQVEfT8vLGOO0nLK9hS7sr8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"forwarded\": \"~0.1.2\",\n        \"ipaddr.js\": \"1.9.1\"\n      }\n    },\n    \"prr\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/prr/-/prr-1.0.1.tgz\",\n      \"integrity\": \"sha1-0/wRS6BplaRexok/SEzrHXj19HY=\",\n      \"dev\": true\n    },\n    \"pseudomap\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/pseudomap/-/pseudomap-1.0.2.tgz\",\n      \"integrity\": \"sha1-8FKijacOYYkX7wqKw0wa5aaChrM=\",\n      \"dev\": true\n    },\n    \"psl\": {\n      \"version\": \"1.8.0\",\n      \"resolved\": \"https://npm.quanziapp.com/psl/-/psl-1.8.0.tgz\",\n      \"integrity\": \"sha1-kyb4vPsBOtzABf3/BWrM4CDlHCQ=\",\n      \"dev\": true\n    },\n    \"public-encrypt\": {\n      \"version\": \"4.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/public-encrypt/-/public-encrypt-4.0.3.tgz\",\n      \"integrity\": \"sha1-T8ydd6B+SLp1J+fL4N4z0HATMeA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bn.js\": \"^4.1.0\",\n        \"browserify-rsa\": \"^4.0.0\",\n        \"create-hash\": \"^1.1.0\",\n        \"parse-asn1\": \"^5.0.0\",\n        \"randombytes\": \"^2.0.1\",\n        \"safe-buffer\": \"^5.1.2\"\n      },\n      \"dependencies\": {\n        \"bn.js\": {\n          \"version\": \"4.11.9\",\n          \"resolved\": \"https://npm.quanziapp.com/bn.js/-/bn.js-4.11.9.tgz\",\n          \"integrity\": \"sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"pump\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/pump/-/pump-3.0.0.tgz\",\n      \"integrity\": \"sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"end-of-stream\": \"^1.1.0\",\n        \"once\": \"^1.3.1\"\n      }\n    },\n    \"pumpify\": {\n      \"version\": \"1.5.1\",\n      \"resolved\": \"https://npm.quanziapp.com/pumpify/-/pumpify-1.5.1.tgz\",\n      \"integrity\": \"sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"duplexify\": \"^3.6.0\",\n        \"inherits\": \"^2.0.3\",\n        \"pump\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"pump\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/pump/-/pump-2.0.1.tgz\",\n          \"integrity\": \"sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=\",\n          \"dev\": true,\n          \"requires\": {\n            \"end-of-stream\": \"^1.1.0\",\n            \"once\": \"^1.3.1\"\n          }\n        }\n      }\n    },\n    \"punycode\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/punycode/-/punycode-2.1.1.tgz\",\n      \"integrity\": \"sha1-tYsBCsQMIsVldhbI0sLALHv0eew=\",\n      \"dev\": true\n    },\n    \"pupa\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/pupa/-/pupa-2.1.1.tgz\",\n      \"integrity\": \"sha1-9ej9SvwsXZeCj6pSNUnth0SiDWI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"escape-goat\": \"^2.0.0\"\n      }\n    },\n    \"q\": {\n      \"version\": \"1.5.1\",\n      \"resolved\": \"https://npm.quanziapp.com/q/-/q-1.5.1.tgz\",\n      \"integrity\": \"sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=\",\n      \"dev\": true\n    },\n    \"qs\": {\n      \"version\": \"6.7.0\",\n      \"resolved\": \"https://npm.quanziapp.com/qs/-/qs-6.7.0.tgz\",\n      \"integrity\": \"sha1-QdwaAV49WB8WIXdr4xr7KHapsbw=\",\n      \"dev\": true\n    },\n    \"query-string\": {\n      \"version\": \"5.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/query-string/-/query-string-5.1.1.tgz\",\n      \"integrity\": \"sha1-p4wBK3HBfgXy4/ojGd0zBoLvs8s=\",\n      \"dev\": true,\n      \"requires\": {\n        \"decode-uri-component\": \"^0.2.0\",\n        \"object-assign\": \"^4.1.0\",\n        \"strict-uri-encode\": \"^1.0.0\"\n      }\n    },\n    \"querystring\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/querystring/-/querystring-0.2.0.tgz\",\n      \"integrity\": \"sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=\",\n      \"dev\": true\n    },\n    \"querystring-es3\": {\n      \"version\": \"0.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/querystring-es3/-/querystring-es3-0.2.1.tgz\",\n      \"integrity\": \"sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=\",\n      \"dev\": true\n    },\n    \"querystringify\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/querystringify/-/querystringify-2.2.0.tgz\",\n      \"integrity\": \"sha1-M0WUG0FTy50ILY7uTNogFqmu9/Y=\",\n      \"dev\": true\n    },\n    \"randombytes\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/randombytes/-/randombytes-2.1.0.tgz\",\n      \"integrity\": \"sha1-32+ENy8CcNxlzfYpE0mrekc9Tyo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"safe-buffer\": \"^5.1.0\"\n      }\n    },\n    \"randomfill\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/randomfill/-/randomfill-1.0.4.tgz\",\n      \"integrity\": \"sha1-ySGW/IarQr6YPxvzF3giSTHWFFg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"randombytes\": \"^2.0.5\",\n        \"safe-buffer\": \"^5.1.0\"\n      }\n    },\n    \"range-parser\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/range-parser/-/range-parser-1.2.1.tgz\",\n      \"integrity\": \"sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=\",\n      \"dev\": true\n    },\n    \"raw-body\": {\n      \"version\": \"2.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/raw-body/-/raw-body-2.4.0.tgz\",\n      \"integrity\": \"sha1-oc5vucm8NWylLoklarWQWeE9AzI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"bytes\": \"3.1.0\",\n        \"http-errors\": \"1.7.2\",\n        \"iconv-lite\": \"0.4.24\",\n        \"unpipe\": \"1.0.0\"\n      },\n      \"dependencies\": {\n        \"bytes\": {\n          \"version\": \"3.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/bytes/-/bytes-3.1.0.tgz\",\n          \"integrity\": \"sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"rc\": {\n      \"version\": \"1.2.8\",\n      \"resolved\": \"https://npm.quanziapp.com/rc/-/rc-1.2.8.tgz\",\n      \"integrity\": \"sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"deep-extend\": \"^0.6.0\",\n        \"ini\": \"~1.3.0\",\n        \"minimist\": \"^1.2.0\",\n        \"strip-json-comments\": \"~2.0.1\"\n      }\n    },\n    \"readable-stream\": {\n      \"version\": \"2.3.7\",\n      \"resolved\": \"https://npm.quanziapp.com/readable-stream/-/readable-stream-2.3.7.tgz\",\n      \"integrity\": \"sha1-Hsoc9xGu+BTAT2IlKjamL2yyO1c=\",\n      \"dev\": true,\n      \"requires\": {\n        \"core-util-is\": \"~1.0.0\",\n        \"inherits\": \"~2.0.3\",\n        \"isarray\": \"~1.0.0\",\n        \"process-nextick-args\": \"~2.0.0\",\n        \"safe-buffer\": \"~5.1.1\",\n        \"string_decoder\": \"~1.1.1\",\n        \"util-deprecate\": \"~1.0.1\"\n      }\n    },\n    \"readdirp\": {\n      \"version\": \"2.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/readdirp/-/readdirp-2.2.1.tgz\",\n      \"integrity\": \"sha1-DodiKjMlqjPokihcr4tOhGUppSU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"graceful-fs\": \"^4.1.11\",\n        \"micromatch\": \"^3.1.10\",\n        \"readable-stream\": \"^2.0.2\"\n      }\n    },\n    \"reduce\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/reduce/-/reduce-1.0.2.tgz\",\n      \"integrity\": \"sha1-DNaArT/+CwYOV6XGi9/ONxaNNhs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"object-keys\": \"^1.1.0\"\n      }\n    },\n    \"regenerate\": {\n      \"version\": \"1.4.2\",\n      \"resolved\": \"https://npm.quanziapp.com/regenerate/-/regenerate-1.4.2.tgz\",\n      \"integrity\": \"sha1-uTRtiCfo9aMve6KWN9OYtpAUhIo=\",\n      \"dev\": true\n    },\n    \"regenerate-unicode-properties\": {\n      \"version\": \"8.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz\",\n      \"integrity\": \"sha1-5d5xEdZV57pgwFfb6f83yH5lzew=\",\n      \"dev\": true,\n      \"requires\": {\n        \"regenerate\": \"^1.4.0\"\n      }\n    },\n    \"regenerator-runtime\": {\n      \"version\": \"0.13.7\",\n      \"resolved\": \"https://npm.quanziapp.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz\",\n      \"integrity\": \"sha1-ysLazIoepnX+qrrriugziYrkb1U=\",\n      \"dev\": true\n    },\n    \"regenerator-transform\": {\n      \"version\": \"0.14.5\",\n      \"resolved\": \"https://npm.quanziapp.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz\",\n      \"integrity\": \"sha1-yY2hVGg2ccnE3LFuznNlF+G3/rQ=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/runtime\": \"^7.8.4\"\n      }\n    },\n    \"regex-not\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/regex-not/-/regex-not-1.0.2.tgz\",\n      \"integrity\": \"sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"extend-shallow\": \"^3.0.2\",\n        \"safe-regex\": \"^1.1.0\"\n      }\n    },\n    \"regexp.prototype.flags\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz\",\n      \"integrity\": \"sha1-erqJs8E6ZFCdq888qNn7ub31y3U=\",\n      \"dev\": true,\n      \"requires\": {\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.17.0-next.1\"\n      },\n      \"dependencies\": {\n        \"es-abstract\": {\n          \"version\": \"1.17.7\",\n          \"resolved\": \"https://npm.quanziapp.com/es-abstract/-/es-abstract-1.17.7.tgz\",\n          \"integrity\": \"sha1-pN5hsvZpifx0IWdsHLl4dXOs5Uw=\",\n          \"dev\": true,\n          \"requires\": {\n            \"es-to-primitive\": \"^1.2.1\",\n            \"function-bind\": \"^1.1.1\",\n            \"has\": \"^1.0.3\",\n            \"has-symbols\": \"^1.0.1\",\n            \"is-callable\": \"^1.2.2\",\n            \"is-regex\": \"^1.1.1\",\n            \"object-inspect\": \"^1.8.0\",\n            \"object-keys\": \"^1.1.1\",\n            \"object.assign\": \"^4.1.1\",\n            \"string.prototype.trimend\": \"^1.0.1\",\n            \"string.prototype.trimstart\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"regexpu-core\": {\n      \"version\": \"4.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/regexpu-core/-/regexpu-core-4.7.1.tgz\",\n      \"integrity\": \"sha1-LepamgcjMpj78NuR+pq8TG4PitY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"regenerate\": \"^1.4.0\",\n        \"regenerate-unicode-properties\": \"^8.2.0\",\n        \"regjsgen\": \"^0.5.1\",\n        \"regjsparser\": \"^0.6.4\",\n        \"unicode-match-property-ecmascript\": \"^1.0.4\",\n        \"unicode-match-property-value-ecmascript\": \"^1.2.0\"\n      }\n    },\n    \"registry-auth-token\": {\n      \"version\": \"4.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz\",\n      \"integrity\": \"sha1-bXtABkQZGJcszV/tzUHcMix5slA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"rc\": \"^1.2.8\"\n      }\n    },\n    \"registry-url\": {\n      \"version\": \"5.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/registry-url/-/registry-url-5.1.0.tgz\",\n      \"integrity\": \"sha1-6YM0tQ1UNLgRNrROxjjZwgCcUAk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"rc\": \"^1.2.8\"\n      }\n    },\n    \"regjsgen\": {\n      \"version\": \"0.5.2\",\n      \"resolved\": \"https://npm.quanziapp.com/regjsgen/-/regjsgen-0.5.2.tgz\",\n      \"integrity\": \"sha1-kv8pX7He7L9uzaslQ9IH6RqjNzM=\",\n      \"dev\": true\n    },\n    \"regjsparser\": {\n      \"version\": \"0.6.4\",\n      \"resolved\": \"https://npm.quanziapp.com/regjsparser/-/regjsparser-0.6.4.tgz\",\n      \"integrity\": \"sha1-p2n4aEMIQBpm6bUp0kNv9NBmYnI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"jsesc\": \"~0.5.0\"\n      },\n      \"dependencies\": {\n        \"jsesc\": {\n          \"version\": \"0.5.0\",\n          \"resolved\": \"https://npm.quanziapp.com/jsesc/-/jsesc-0.5.0.tgz\",\n          \"integrity\": \"sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"relateurl\": {\n      \"version\": \"0.2.7\",\n      \"resolved\": \"https://npm.quanziapp.com/relateurl/-/relateurl-0.2.7.tgz\",\n      \"integrity\": \"sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=\",\n      \"dev\": true\n    },\n    \"remove-trailing-separator\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz\",\n      \"integrity\": \"sha1-wkvOKig62tW8P1jg1IJJuSN52O8=\",\n      \"dev\": true\n    },\n    \"renderkid\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/renderkid/-/renderkid-2.0.4.tgz\",\n      \"integrity\": \"sha1-0yXlMq+yjT+Hlv/uMGvo/9b8hkw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"css-select\": \"^1.1.0\",\n        \"dom-converter\": \"^0.2\",\n        \"htmlparser2\": \"^3.3.0\",\n        \"lodash\": \"^4.17.20\",\n        \"strip-ansi\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"css-select\": {\n          \"version\": \"1.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/css-select/-/css-select-1.2.0.tgz\",\n          \"integrity\": \"sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=\",\n          \"dev\": true,\n          \"requires\": {\n            \"boolbase\": \"~1.0.0\",\n            \"css-what\": \"2.1\",\n            \"domutils\": \"1.5.1\",\n            \"nth-check\": \"~1.0.1\"\n          }\n        },\n        \"css-what\": {\n          \"version\": \"2.1.3\",\n          \"resolved\": \"https://npm.quanziapp.com/css-what/-/css-what-2.1.3.tgz\",\n          \"integrity\": \"sha1-ptdgRXM2X+dGhsPzEcVlE9iChfI=\",\n          \"dev\": true\n        },\n        \"domutils\": {\n          \"version\": \"1.5.1\",\n          \"resolved\": \"https://npm.quanziapp.com/domutils/-/domutils-1.5.1.tgz\",\n          \"integrity\": \"sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"dom-serializer\": \"0\",\n            \"domelementtype\": \"1\"\n          }\n        }\n      }\n    },\n    \"repeat-element\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/repeat-element/-/repeat-element-1.1.3.tgz\",\n      \"integrity\": \"sha1-eC4NglwMWjuzlzH4Tv7mt0Lmsc4=\",\n      \"dev\": true\n    },\n    \"repeat-string\": {\n      \"version\": \"1.6.1\",\n      \"resolved\": \"https://npm.quanziapp.com/repeat-string/-/repeat-string-1.6.1.tgz\",\n      \"integrity\": \"sha1-jcrkcOHIirwtYA//Sndihtp15jc=\",\n      \"dev\": true\n    },\n    \"request\": {\n      \"version\": \"2.88.2\",\n      \"resolved\": \"https://npm.quanziapp.com/request/-/request-2.88.2.tgz\",\n      \"integrity\": \"sha1-1zyRhzHLWofaBH4gcjQUb2ZNErM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"aws-sign2\": \"~0.7.0\",\n        \"aws4\": \"^1.8.0\",\n        \"caseless\": \"~0.12.0\",\n        \"combined-stream\": \"~1.0.6\",\n        \"extend\": \"~3.0.2\",\n        \"forever-agent\": \"~0.6.1\",\n        \"form-data\": \"~2.3.2\",\n        \"har-validator\": \"~5.1.3\",\n        \"http-signature\": \"~1.2.0\",\n        \"is-typedarray\": \"~1.0.0\",\n        \"isstream\": \"~0.1.2\",\n        \"json-stringify-safe\": \"~5.0.1\",\n        \"mime-types\": \"~2.1.19\",\n        \"oauth-sign\": \"~0.9.0\",\n        \"performance-now\": \"^2.1.0\",\n        \"qs\": \"~6.5.2\",\n        \"safe-buffer\": \"^5.1.2\",\n        \"tough-cookie\": \"~2.5.0\",\n        \"tunnel-agent\": \"^0.6.0\",\n        \"uuid\": \"^3.3.2\"\n      },\n      \"dependencies\": {\n        \"qs\": {\n          \"version\": \"6.5.2\",\n          \"resolved\": \"https://npm.quanziapp.com/qs/-/qs-6.5.2.tgz\",\n          \"integrity\": \"sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"require-directory\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/require-directory/-/require-directory-2.1.1.tgz\",\n      \"integrity\": \"sha1-jGStX9MNqxyXbiNE/+f3kqam30I=\",\n      \"dev\": true\n    },\n    \"require-main-filename\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/require-main-filename/-/require-main-filename-2.0.0.tgz\",\n      \"integrity\": \"sha1-0LMp7MfMD2Fkn2IhW+aa9UqomJs=\",\n      \"dev\": true\n    },\n    \"requires-port\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/requires-port/-/requires-port-1.0.0.tgz\",\n      \"integrity\": \"sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=\",\n      \"dev\": true\n    },\n    \"resolve\": {\n      \"version\": \"1.19.0\",\n      \"resolved\": \"https://npm.quanziapp.com/resolve/-/resolve-1.19.0.tgz\",\n      \"integrity\": \"sha1-GvW/YwQJc0oGfK4pMYqsf6KaJnw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-core-module\": \"^2.1.0\",\n        \"path-parse\": \"^1.0.6\"\n      }\n    },\n    \"resolve-cwd\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz\",\n      \"integrity\": \"sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"resolve-from\": \"^3.0.0\"\n      }\n    },\n    \"resolve-from\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/resolve-from/-/resolve-from-3.0.0.tgz\",\n      \"integrity\": \"sha1-six699nWiBvItuZTM17rywoYh0g=\",\n      \"dev\": true\n    },\n    \"resolve-url\": {\n      \"version\": \"0.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/resolve-url/-/resolve-url-0.2.1.tgz\",\n      \"integrity\": \"sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=\",\n      \"dev\": true\n    },\n    \"responselike\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/responselike/-/responselike-1.0.2.tgz\",\n      \"integrity\": \"sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lowercase-keys\": \"^1.0.0\"\n      }\n    },\n    \"ret\": {\n      \"version\": \"0.1.15\",\n      \"resolved\": \"https://npm.quanziapp.com/ret/-/ret-0.1.15.tgz\",\n      \"integrity\": \"sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=\",\n      \"dev\": true\n    },\n    \"retry\": {\n      \"version\": \"0.12.0\",\n      \"resolved\": \"https://npm.quanziapp.com/retry/-/retry-0.12.0.tgz\",\n      \"integrity\": \"sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=\",\n      \"dev\": true\n    },\n    \"rgb-regex\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/rgb-regex/-/rgb-regex-1.0.1.tgz\",\n      \"integrity\": \"sha1-wODWiC3w4jviVKR16O3UGRX+rrE=\",\n      \"dev\": true\n    },\n    \"rgba-regex\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/rgba-regex/-/rgba-regex-1.0.0.tgz\",\n      \"integrity\": \"sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=\",\n      \"dev\": true\n    },\n    \"rimraf\": {\n      \"version\": \"2.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/rimraf/-/rimraf-2.7.1.tgz\",\n      \"integrity\": \"sha1-NXl/E6f9rcVmFCwp1PB8ytSD4+w=\",\n      \"dev\": true,\n      \"requires\": {\n        \"glob\": \"^7.1.3\"\n      }\n    },\n    \"ripemd160\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/ripemd160/-/ripemd160-2.0.2.tgz\",\n      \"integrity\": \"sha1-ocGm9iR1FXe6XQeRTLyShQWFiQw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"hash-base\": \"^3.0.0\",\n        \"inherits\": \"^2.0.1\"\n      }\n    },\n    \"run-queue\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/run-queue/-/run-queue-1.0.3.tgz\",\n      \"integrity\": \"sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=\",\n      \"dev\": true,\n      \"requires\": {\n        \"aproba\": \"^1.1.1\"\n      }\n    },\n    \"safe-buffer\": {\n      \"version\": \"5.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/safe-buffer/-/safe-buffer-5.1.2.tgz\",\n      \"integrity\": \"sha1-mR7GnSluAxN0fVm9/St0XDX4go0=\",\n      \"dev\": true\n    },\n    \"safe-regex\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/safe-regex/-/safe-regex-1.1.0.tgz\",\n      \"integrity\": \"sha1-QKNmnzsHfR6UPURinhV91IAjvy4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ret\": \"~0.1.10\"\n      }\n    },\n    \"safer-buffer\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/safer-buffer/-/safer-buffer-2.1.2.tgz\",\n      \"integrity\": \"sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=\",\n      \"dev\": true\n    },\n    \"sax\": {\n      \"version\": \"1.2.4\",\n      \"resolved\": \"https://npm.quanziapp.com/sax/-/sax-1.2.4.tgz\",\n      \"integrity\": \"sha1-KBYjTiN4vdxOU1T6tcqold9xANk=\",\n      \"dev\": true\n    },\n    \"schema-utils\": {\n      \"version\": \"2.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-2.7.1.tgz\",\n      \"integrity\": \"sha1-HKTzLRskxZDCA7jnpQvw6kzTlNc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/json-schema\": \"^7.0.5\",\n        \"ajv\": \"^6.12.4\",\n        \"ajv-keywords\": \"^3.5.2\"\n      }\n    },\n    \"section-matter\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/section-matter/-/section-matter-1.0.0.tgz\",\n      \"integrity\": \"sha1-6QQZU1BngOwB1Z8pKhnHuFC4QWc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"extend-shallow\": \"^2.0.1\",\n        \"kind-of\": \"^6.0.0\"\n      },\n      \"dependencies\": {\n        \"extend-shallow\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/extend-shallow/-/extend-shallow-2.0.1.tgz\",\n          \"integrity\": \"sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-extendable\": \"^0.1.0\"\n          }\n        }\n      }\n    },\n    \"select\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/select/-/select-1.1.2.tgz\",\n      \"integrity\": \"sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=\",\n      \"dev\": true,\n      \"optional\": true\n    },\n    \"select-hose\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/select-hose/-/select-hose-2.0.0.tgz\",\n      \"integrity\": \"sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=\",\n      \"dev\": true\n    },\n    \"selfsigned\": {\n      \"version\": \"1.10.8\",\n      \"resolved\": \"https://npm.quanziapp.com/selfsigned/-/selfsigned-1.10.8.tgz\",\n      \"integrity\": \"sha1-DRcgi30Swz+OrIXEGDXyf8PYGjA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"node-forge\": \"^0.10.0\"\n      }\n    },\n    \"semver\": {\n      \"version\": \"5.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-5.7.1.tgz\",\n      \"integrity\": \"sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=\",\n      \"dev\": true\n    },\n    \"semver-diff\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/semver-diff/-/semver-diff-3.1.1.tgz\",\n      \"integrity\": \"sha1-Bfd85Z8yXgDicGr9Z7tQbdscoys=\",\n      \"dev\": true,\n      \"requires\": {\n        \"semver\": \"^6.3.0\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"6.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-6.3.0.tgz\",\n          \"integrity\": \"sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"send\": {\n      \"version\": \"0.17.1\",\n      \"resolved\": \"https://npm.quanziapp.com/send/-/send-0.17.1.tgz\",\n      \"integrity\": \"sha1-wdiwWfeQD3Rm3Uk4vcROEd2zdsg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"debug\": \"2.6.9\",\n        \"depd\": \"~1.1.2\",\n        \"destroy\": \"~1.0.4\",\n        \"encodeurl\": \"~1.0.2\",\n        \"escape-html\": \"~1.0.3\",\n        \"etag\": \"~1.8.1\",\n        \"fresh\": \"0.5.2\",\n        \"http-errors\": \"~1.7.2\",\n        \"mime\": \"1.6.0\",\n        \"ms\": \"2.1.1\",\n        \"on-finished\": \"~2.3.0\",\n        \"range-parser\": \"~1.2.1\",\n        \"statuses\": \"~1.5.0\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          },\n          \"dependencies\": {\n            \"ms\": {\n              \"version\": \"2.0.0\",\n              \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n              \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n              \"dev\": true\n            }\n          }\n        },\n        \"mime\": {\n          \"version\": \"1.6.0\",\n          \"resolved\": \"https://npm.quanziapp.com/mime/-/mime-1.6.0.tgz\",\n          \"integrity\": \"sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=\",\n          \"dev\": true\n        },\n        \"ms\": {\n          \"version\": \"2.1.1\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.1.1.tgz\",\n          \"integrity\": \"sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"serialize-javascript\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz\",\n      \"integrity\": \"sha1-tSXhI4SJpez8Qq+sw/6Z5mb0sao=\",\n      \"dev\": true,\n      \"requires\": {\n        \"randombytes\": \"^2.1.0\"\n      }\n    },\n    \"serve-index\": {\n      \"version\": \"1.9.1\",\n      \"resolved\": \"https://npm.quanziapp.com/serve-index/-/serve-index-1.9.1.tgz\",\n      \"integrity\": \"sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"accepts\": \"~1.3.4\",\n        \"batch\": \"0.6.1\",\n        \"debug\": \"2.6.9\",\n        \"escape-html\": \"~1.0.3\",\n        \"http-errors\": \"~1.6.2\",\n        \"mime-types\": \"~2.1.17\",\n        \"parseurl\": \"~1.3.2\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"http-errors\": {\n          \"version\": \"1.6.3\",\n          \"resolved\": \"https://npm.quanziapp.com/http-errors/-/http-errors-1.6.3.tgz\",\n          \"integrity\": \"sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=\",\n          \"dev\": true,\n          \"requires\": {\n            \"depd\": \"~1.1.2\",\n            \"inherits\": \"2.0.3\",\n            \"setprototypeof\": \"1.1.0\",\n            \"statuses\": \">= 1.4.0 < 2\"\n          }\n        },\n        \"inherits\": {\n          \"version\": \"2.0.3\",\n          \"resolved\": \"https://npm.quanziapp.com/inherits/-/inherits-2.0.3.tgz\",\n          \"integrity\": \"sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=\",\n          \"dev\": true\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        },\n        \"setprototypeof\": {\n          \"version\": \"1.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/setprototypeof/-/setprototypeof-1.1.0.tgz\",\n          \"integrity\": \"sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"serve-static\": {\n      \"version\": \"1.14.1\",\n      \"resolved\": \"https://npm.quanziapp.com/serve-static/-/serve-static-1.14.1.tgz\",\n      \"integrity\": \"sha1-Zm5jbcTwEPfvKZcKiKZ0MgiYsvk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"encodeurl\": \"~1.0.2\",\n        \"escape-html\": \"~1.0.3\",\n        \"parseurl\": \"~1.3.3\",\n        \"send\": \"0.17.1\"\n      }\n    },\n    \"set-blocking\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/set-blocking/-/set-blocking-2.0.0.tgz\",\n      \"integrity\": \"sha1-BF+XgtARrppoA93TgrJDkrPYkPc=\",\n      \"dev\": true\n    },\n    \"set-value\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/set-value/-/set-value-2.0.1.tgz\",\n      \"integrity\": \"sha1-oY1AUw5vB95CKMfe/kInr4ytAFs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"extend-shallow\": \"^2.0.1\",\n        \"is-extendable\": \"^0.1.1\",\n        \"is-plain-object\": \"^2.0.3\",\n        \"split-string\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"extend-shallow\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/extend-shallow/-/extend-shallow-2.0.1.tgz\",\n          \"integrity\": \"sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-extendable\": \"^0.1.0\"\n          }\n        }\n      }\n    },\n    \"setimmediate\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/setimmediate/-/setimmediate-1.0.5.tgz\",\n      \"integrity\": \"sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=\",\n      \"dev\": true\n    },\n    \"setprototypeof\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/setprototypeof/-/setprototypeof-1.1.1.tgz\",\n      \"integrity\": \"sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM=\",\n      \"dev\": true\n    },\n    \"sha.js\": {\n      \"version\": \"2.4.11\",\n      \"resolved\": \"https://npm.quanziapp.com/sha.js/-/sha.js-2.4.11.tgz\",\n      \"integrity\": \"sha1-N6XPC4HsvGlD3hCbopYNGyZYSuc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"^2.0.1\",\n        \"safe-buffer\": \"^5.0.1\"\n      }\n    },\n    \"shebang-command\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/shebang-command/-/shebang-command-1.2.0.tgz\",\n      \"integrity\": \"sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"shebang-regex\": \"^1.0.0\"\n      }\n    },\n    \"shebang-regex\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/shebang-regex/-/shebang-regex-1.0.0.tgz\",\n      \"integrity\": \"sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=\",\n      \"dev\": true\n    },\n    \"signal-exit\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/signal-exit/-/signal-exit-3.0.3.tgz\",\n      \"integrity\": \"sha1-oUEMLt2PB3sItOJTyOrPyvBXRhw=\",\n      \"dev\": true\n    },\n    \"simple-swizzle\": {\n      \"version\": \"0.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz\",\n      \"integrity\": \"sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-arrayish\": \"^0.3.1\"\n      },\n      \"dependencies\": {\n        \"is-arrayish\": {\n          \"version\": \"0.3.2\",\n          \"resolved\": \"https://npm.quanziapp.com/is-arrayish/-/is-arrayish-0.3.2.tgz\",\n          \"integrity\": \"sha1-RXSirlb3qyBolvtDHq7tBm/fjwM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"slash\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/slash/-/slash-2.0.0.tgz\",\n      \"integrity\": \"sha1-3lUoUaF1nfOo8gZTVEL17E3eq0Q=\",\n      \"dev\": true\n    },\n    \"smoothscroll-polyfill\": {\n      \"version\": \"0.4.4\",\n      \"resolved\": \"https://npm.quanziapp.com/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz\",\n      \"integrity\": \"sha1-OiWRMdxpMObKgAA+HLA7YDtpq/g=\",\n      \"dev\": true\n    },\n    \"snapdragon\": {\n      \"version\": \"0.8.2\",\n      \"resolved\": \"https://npm.quanziapp.com/snapdragon/-/snapdragon-0.8.2.tgz\",\n      \"integrity\": \"sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"base\": \"^0.11.1\",\n        \"debug\": \"^2.2.0\",\n        \"define-property\": \"^0.2.5\",\n        \"extend-shallow\": \"^2.0.1\",\n        \"map-cache\": \"^0.2.2\",\n        \"source-map\": \"^0.5.6\",\n        \"source-map-resolve\": \"^0.5.0\",\n        \"use\": \"^3.1.0\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"define-property\": {\n          \"version\": \"0.2.5\",\n          \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-0.2.5.tgz\",\n          \"integrity\": \"sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-descriptor\": \"^0.1.0\"\n          }\n        },\n        \"extend-shallow\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/extend-shallow/-/extend-shallow-2.0.1.tgz\",\n          \"integrity\": \"sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-extendable\": \"^0.1.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"snapdragon-node\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz\",\n      \"integrity\": \"sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"define-property\": \"^1.0.0\",\n        \"isobject\": \"^3.0.0\",\n        \"snapdragon-util\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"define-property\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-1.0.0.tgz\",\n          \"integrity\": \"sha1-dp66rz9KY6rTr56NMEybvnm/sOY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-descriptor\": \"^1.0.0\"\n          }\n        },\n        \"is-accessor-descriptor\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz\",\n          \"integrity\": \"sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"kind-of\": \"^6.0.0\"\n          }\n        },\n        \"is-data-descriptor\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz\",\n          \"integrity\": \"sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"kind-of\": \"^6.0.0\"\n          }\n        },\n        \"is-descriptor\": {\n          \"version\": \"1.0.2\",\n          \"resolved\": \"https://npm.quanziapp.com/is-descriptor/-/is-descriptor-1.0.2.tgz\",\n          \"integrity\": \"sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-accessor-descriptor\": \"^1.0.0\",\n            \"is-data-descriptor\": \"^1.0.0\",\n            \"kind-of\": \"^6.0.2\"\n          }\n        }\n      }\n    },\n    \"snapdragon-util\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz\",\n      \"integrity\": \"sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"kind-of\": \"^3.2.0\"\n      },\n      \"dependencies\": {\n        \"kind-of\": {\n          \"version\": \"3.2.2\",\n          \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-3.2.2.tgz\",\n          \"integrity\": \"sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-buffer\": \"^1.1.5\"\n          }\n        }\n      }\n    },\n    \"sockjs\": {\n      \"version\": \"0.3.20\",\n      \"resolved\": \"https://npm.quanziapp.com/sockjs/-/sockjs-0.3.20.tgz\",\n      \"integrity\": \"sha1-smooPsVi74smh7RAM6Tuzqx12FU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"faye-websocket\": \"^0.10.0\",\n        \"uuid\": \"^3.4.0\",\n        \"websocket-driver\": \"0.6.5\"\n      }\n    },\n    \"sockjs-client\": {\n      \"version\": \"1.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/sockjs-client/-/sockjs-client-1.4.0.tgz\",\n      \"integrity\": \"sha1-yfJWjhnI/YFztJl+o0IOC7MGx9U=\",\n      \"dev\": true,\n      \"requires\": {\n        \"debug\": \"^3.2.5\",\n        \"eventsource\": \"^1.0.7\",\n        \"faye-websocket\": \"~0.11.1\",\n        \"inherits\": \"^2.0.3\",\n        \"json3\": \"^3.3.2\",\n        \"url-parse\": \"^1.4.3\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"3.2.7\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-3.2.7.tgz\",\n          \"integrity\": \"sha1-clgLfpFF+zm2Z2+cXl+xALk0F5o=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"^2.1.1\"\n          }\n        },\n        \"faye-websocket\": {\n          \"version\": \"0.11.3\",\n          \"resolved\": \"https://npm.quanziapp.com/faye-websocket/-/faye-websocket-0.11.3.tgz\",\n          \"integrity\": \"sha1-XA6aiWjokSwoZjn96XeosgnyUI4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"websocket-driver\": \">=0.5.1\"\n          }\n        }\n      }\n    },\n    \"sort-keys\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/sort-keys/-/sort-keys-2.0.0.tgz\",\n      \"integrity\": \"sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-plain-obj\": \"^1.0.0\"\n      }\n    },\n    \"source-list-map\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/source-list-map/-/source-list-map-2.0.1.tgz\",\n      \"integrity\": \"sha1-OZO9hzv8SEecyp6jpUeDXHwVSzQ=\",\n      \"dev\": true\n    },\n    \"source-map\": {\n      \"version\": \"0.5.7\",\n      \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.5.7.tgz\",\n      \"integrity\": \"sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=\",\n      \"dev\": true\n    },\n    \"source-map-resolve\": {\n      \"version\": \"0.5.3\",\n      \"resolved\": \"https://npm.quanziapp.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz\",\n      \"integrity\": \"sha1-GQhmvs51U+H48mei7oLGBrVQmho=\",\n      \"dev\": true,\n      \"requires\": {\n        \"atob\": \"^2.1.2\",\n        \"decode-uri-component\": \"^0.2.0\",\n        \"resolve-url\": \"^0.2.1\",\n        \"source-map-url\": \"^0.4.0\",\n        \"urix\": \"^0.1.0\"\n      }\n    },\n    \"source-map-support\": {\n      \"version\": \"0.5.19\",\n      \"resolved\": \"https://npm.quanziapp.com/source-map-support/-/source-map-support-0.5.19.tgz\",\n      \"integrity\": \"sha1-qYti+G3K9PZzmWSMCFKRq56P7WE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"buffer-from\": \"^1.0.0\",\n        \"source-map\": \"^0.6.0\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"source-map-url\": {\n      \"version\": \"0.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/source-map-url/-/source-map-url-0.4.0.tgz\",\n      \"integrity\": \"sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=\",\n      \"dev\": true\n    },\n    \"spdy\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/spdy/-/spdy-4.0.2.tgz\",\n      \"integrity\": \"sha1-t09GYgOj7aRSwCSSuR+56EonZ3s=\",\n      \"dev\": true,\n      \"requires\": {\n        \"debug\": \"^4.1.0\",\n        \"handle-thing\": \"^2.0.0\",\n        \"http-deceiver\": \"^1.2.7\",\n        \"select-hose\": \"^2.0.0\",\n        \"spdy-transport\": \"^3.0.0\"\n      }\n    },\n    \"spdy-transport\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/spdy-transport/-/spdy-transport-3.0.0.tgz\",\n      \"integrity\": \"sha1-ANSGOmQArXXfkzYaFghgXl3NzzE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"debug\": \"^4.1.0\",\n        \"detect-node\": \"^2.0.4\",\n        \"hpack.js\": \"^2.1.6\",\n        \"obuf\": \"^1.1.2\",\n        \"readable-stream\": \"^3.0.6\",\n        \"wbuf\": \"^1.7.3\"\n      },\n      \"dependencies\": {\n        \"readable-stream\": {\n          \"version\": \"3.6.0\",\n          \"resolved\": \"https://npm.quanziapp.com/readable-stream/-/readable-stream-3.6.0.tgz\",\n          \"integrity\": \"sha1-M3u9o63AcGvT4CRCaihtS0sskZg=\",\n          \"dev\": true,\n          \"requires\": {\n            \"inherits\": \"^2.0.3\",\n            \"string_decoder\": \"^1.1.1\",\n            \"util-deprecate\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"split-string\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/split-string/-/split-string-3.1.0.tgz\",\n      \"integrity\": \"sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=\",\n      \"dev\": true,\n      \"requires\": {\n        \"extend-shallow\": \"^3.0.0\"\n      }\n    },\n    \"sprintf-js\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/sprintf-js/-/sprintf-js-1.0.3.tgz\",\n      \"integrity\": \"sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=\",\n      \"dev\": true\n    },\n    \"sshpk\": {\n      \"version\": \"1.16.1\",\n      \"resolved\": \"https://npm.quanziapp.com/sshpk/-/sshpk-1.16.1.tgz\",\n      \"integrity\": \"sha1-+2YcC+8ps520B2nuOfpwCT1vaHc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"asn1\": \"~0.2.3\",\n        \"assert-plus\": \"^1.0.0\",\n        \"bcrypt-pbkdf\": \"^1.0.0\",\n        \"dashdash\": \"^1.12.0\",\n        \"ecc-jsbn\": \"~0.1.1\",\n        \"getpass\": \"^0.1.1\",\n        \"jsbn\": \"~0.1.0\",\n        \"safer-buffer\": \"^2.0.2\",\n        \"tweetnacl\": \"~0.14.0\"\n      }\n    },\n    \"ssri\": {\n      \"version\": \"6.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ssri/-/ssri-6.0.1.tgz\",\n      \"integrity\": \"sha1-KjxBso3UW2K2Nnbst0ABJlrp7dg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"figgy-pudding\": \"^3.5.1\"\n      }\n    },\n    \"stable\": {\n      \"version\": \"0.1.8\",\n      \"resolved\": \"https://npm.quanziapp.com/stable/-/stable-0.1.8.tgz\",\n      \"integrity\": \"sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88=\",\n      \"dev\": true\n    },\n    \"stack-utils\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/stack-utils/-/stack-utils-1.0.4.tgz\",\n      \"integrity\": \"sha1-S2AJcdz8au0MvfKoJoF3zJFsh8g=\",\n      \"dev\": true,\n      \"requires\": {\n        \"escape-string-regexp\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"escape-string-regexp\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz\",\n          \"integrity\": \"sha1-owME6Z2qMuI7L9IPUbq9B8/8o0Q=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"static-extend\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/static-extend/-/static-extend-0.1.2.tgz\",\n      \"integrity\": \"sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"define-property\": \"^0.2.5\",\n        \"object-copy\": \"^0.1.0\"\n      },\n      \"dependencies\": {\n        \"define-property\": {\n          \"version\": \"0.2.5\",\n          \"resolved\": \"https://npm.quanziapp.com/define-property/-/define-property-0.2.5.tgz\",\n          \"integrity\": \"sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-descriptor\": \"^0.1.0\"\n          }\n        }\n      }\n    },\n    \"statuses\": {\n      \"version\": \"1.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/statuses/-/statuses-1.5.0.tgz\",\n      \"integrity\": \"sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=\",\n      \"dev\": true\n    },\n    \"std-env\": {\n      \"version\": \"2.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/std-env/-/std-env-2.2.1.tgz\",\n      \"integrity\": \"sha1-L/oP3J4iY+AATBIRlm6WCUikD2s=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ci-info\": \"^1.6.0\"\n      }\n    },\n    \"stream-browserify\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/stream-browserify/-/stream-browserify-2.0.2.tgz\",\n      \"integrity\": \"sha1-h1IdOKRKp+6RzhzSpH3wy0ndZgs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"~2.0.1\",\n        \"readable-stream\": \"^2.0.2\"\n      }\n    },\n    \"stream-each\": {\n      \"version\": \"1.2.3\",\n      \"resolved\": \"https://npm.quanziapp.com/stream-each/-/stream-each-1.2.3.tgz\",\n      \"integrity\": \"sha1-6+J6DDibBPvMIzZClS4Qcxr6m64=\",\n      \"dev\": true,\n      \"requires\": {\n        \"end-of-stream\": \"^1.1.0\",\n        \"stream-shift\": \"^1.0.0\"\n      }\n    },\n    \"stream-http\": {\n      \"version\": \"2.8.3\",\n      \"resolved\": \"https://npm.quanziapp.com/stream-http/-/stream-http-2.8.3.tgz\",\n      \"integrity\": \"sha1-stJCRpKIpaJ+xP6JM6z2I95lFPw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"builtin-status-codes\": \"^3.0.0\",\n        \"inherits\": \"^2.0.1\",\n        \"readable-stream\": \"^2.3.6\",\n        \"to-arraybuffer\": \"^1.0.0\",\n        \"xtend\": \"^4.0.0\"\n      }\n    },\n    \"stream-shift\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/stream-shift/-/stream-shift-1.0.1.tgz\",\n      \"integrity\": \"sha1-1wiCgVWasneEJCebCHfaPDktWj0=\",\n      \"dev\": true\n    },\n    \"strict-uri-encode\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz\",\n      \"integrity\": \"sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=\",\n      \"dev\": true\n    },\n    \"string-width\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/string-width/-/string-width-3.1.0.tgz\",\n      \"integrity\": \"sha1-InZ74htirxCBV0MG9prFG2IgOWE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"emoji-regex\": \"^7.0.1\",\n        \"is-fullwidth-code-point\": \"^2.0.0\",\n        \"strip-ansi\": \"^5.1.0\"\n      },\n      \"dependencies\": {\n        \"ansi-regex\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ansi-regex/-/ansi-regex-4.1.0.tgz\",\n          \"integrity\": \"sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=\",\n          \"dev\": true\n        },\n        \"strip-ansi\": {\n          \"version\": \"5.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/strip-ansi/-/strip-ansi-5.2.0.tgz\",\n          \"integrity\": \"sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-regex\": \"^4.1.0\"\n          }\n        }\n      }\n    },\n    \"string.prototype.trimend\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz\",\n      \"integrity\": \"sha1-oivVPMpcfPRNfJ1ccyEYhz1s0Ys=\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\"\n      }\n    },\n    \"string.prototype.trimstart\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz\",\n      \"integrity\": \"sha1-m0y1kOEjuzZWRAHVmCQpjeUP1ao=\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\"\n      }\n    },\n    \"string_decoder\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/string_decoder/-/string_decoder-1.1.1.tgz\",\n      \"integrity\": \"sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=\",\n      \"dev\": true,\n      \"requires\": {\n        \"safe-buffer\": \"~5.1.0\"\n      }\n    },\n    \"strip-ansi\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/strip-ansi/-/strip-ansi-3.0.1.tgz\",\n      \"integrity\": \"sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-regex\": \"^2.0.0\"\n      }\n    },\n    \"strip-bom-string\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz\",\n      \"integrity\": \"sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=\",\n      \"dev\": true\n    },\n    \"strip-eof\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/strip-eof/-/strip-eof-1.0.0.tgz\",\n      \"integrity\": \"sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=\",\n      \"dev\": true\n    },\n    \"strip-json-comments\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz\",\n      \"integrity\": \"sha1-PFMZQukIwml8DsNEhYwobHygpgo=\",\n      \"dev\": true\n    },\n    \"stylehacks\": {\n      \"version\": \"4.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/stylehacks/-/stylehacks-4.0.3.tgz\",\n      \"integrity\": \"sha1-Zxj8r00eB9ihMYaQiB6NlnJqcdU=\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.0.0\",\n        \"postcss\": \"^7.0.0\",\n        \"postcss-selector-parser\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"postcss-selector-parser\": {\n          \"version\": \"3.1.2\",\n          \"resolved\": \"https://npm.quanziapp.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz\",\n          \"integrity\": \"sha1-sxD1xMD9r3b5SQK7qjDbaqhPUnA=\",\n          \"dev\": true,\n          \"requires\": {\n            \"dot-prop\": \"^5.2.0\",\n            \"indexes-of\": \"^1.0.1\",\n            \"uniq\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"stylus\": {\n      \"version\": \"0.54.8\",\n      \"resolved\": \"https://npm.quanziapp.com/stylus/-/stylus-0.54.8.tgz\",\n      \"integrity\": \"sha1-PaPmWWa8Vnp7BEv+DuzmU+CZ0Uc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"css-parse\": \"~2.0.0\",\n        \"debug\": \"~3.1.0\",\n        \"glob\": \"^7.1.6\",\n        \"mkdirp\": \"~1.0.4\",\n        \"safer-buffer\": \"^2.1.2\",\n        \"sax\": \"~1.2.4\",\n        \"semver\": \"^6.3.0\",\n        \"source-map\": \"^0.7.3\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"3.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/debug/-/debug-3.1.0.tgz\",\n          \"integrity\": \"sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"mkdirp\": {\n          \"version\": \"1.0.4\",\n          \"resolved\": \"https://npm.quanziapp.com/mkdirp/-/mkdirp-1.0.4.tgz\",\n          \"integrity\": \"sha1-PrXtYmInVteaXw4qIh3+utdcL34=\",\n          \"dev\": true\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        },\n        \"semver\": {\n          \"version\": \"6.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-6.3.0.tgz\",\n          \"integrity\": \"sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=\",\n          \"dev\": true\n        },\n        \"source-map\": {\n          \"version\": \"0.7.3\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.7.3.tgz\",\n          \"integrity\": \"sha1-UwL4FpAxc1ImVECS5kmB91F1A4M=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"stylus-loader\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/stylus-loader/-/stylus-loader-3.0.2.tgz\",\n      \"integrity\": \"sha1-J6cGQgsFo44DjnyssVNXjUUFE8Y=\",\n      \"dev\": true,\n      \"requires\": {\n        \"loader-utils\": \"^1.0.2\",\n        \"lodash.clonedeep\": \"^4.5.0\",\n        \"when\": \"~3.6.x\"\n      }\n    },\n    \"supports-color\": {\n      \"version\": \"5.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/supports-color/-/supports-color-5.5.0.tgz\",\n      \"integrity\": \"sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"has-flag\": \"^3.0.0\"\n      }\n    },\n    \"svg-tags\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/svg-tags/-/svg-tags-1.0.0.tgz\",\n      \"integrity\": \"sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=\",\n      \"dev\": true\n    },\n    \"svgo\": {\n      \"version\": \"1.3.2\",\n      \"resolved\": \"https://npm.quanziapp.com/svgo/-/svgo-1.3.2.tgz\",\n      \"integrity\": \"sha1-ttxRHAYzRsnkFbgeQ0ARRbltQWc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"chalk\": \"^2.4.1\",\n        \"coa\": \"^2.0.2\",\n        \"css-select\": \"^2.0.0\",\n        \"css-select-base-adapter\": \"^0.1.1\",\n        \"css-tree\": \"1.0.0-alpha.37\",\n        \"csso\": \"^4.0.2\",\n        \"js-yaml\": \"^3.13.1\",\n        \"mkdirp\": \"~0.5.1\",\n        \"object.values\": \"^1.1.0\",\n        \"sax\": \"~1.2.4\",\n        \"stable\": \"^0.1.8\",\n        \"unquote\": \"~1.1.1\",\n        \"util.promisify\": \"~1.0.0\"\n      }\n    },\n    \"tapable\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/tapable/-/tapable-1.1.3.tgz\",\n      \"integrity\": \"sha1-ofzMBrWNth/XpF2i2kT186Pme6I=\",\n      \"dev\": true\n    },\n    \"term-size\": {\n      \"version\": \"2.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/term-size/-/term-size-2.2.1.tgz\",\n      \"integrity\": \"sha1-KmpUhAQywvtjIP6g9BVTHpAYn1Q=\",\n      \"dev\": true\n    },\n    \"terser\": {\n      \"version\": \"4.8.0\",\n      \"resolved\": \"https://npm.quanziapp.com/terser/-/terser-4.8.0.tgz\",\n      \"integrity\": \"sha1-YwVjQ9fHC7KfOvZlhlpG/gOg3xc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"commander\": \"^2.20.0\",\n        \"source-map\": \"~0.6.1\",\n        \"source-map-support\": \"~0.5.12\"\n      },\n      \"dependencies\": {\n        \"commander\": {\n          \"version\": \"2.20.3\",\n          \"resolved\": \"https://npm.quanziapp.com/commander/-/commander-2.20.3.tgz\",\n          \"integrity\": \"sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=\",\n          \"dev\": true\n        },\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"terser-webpack-plugin\": {\n      \"version\": \"1.4.5\",\n      \"resolved\": \"https://npm.quanziapp.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz\",\n      \"integrity\": \"sha1-oheu+uozDnNP+sthIOwfoxLWBAs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cacache\": \"^12.0.2\",\n        \"find-cache-dir\": \"^2.1.0\",\n        \"is-wsl\": \"^1.1.0\",\n        \"schema-utils\": \"^1.0.0\",\n        \"serialize-javascript\": \"^4.0.0\",\n        \"source-map\": \"^0.6.1\",\n        \"terser\": \"^4.1.2\",\n        \"webpack-sources\": \"^1.4.0\",\n        \"worker-farm\": \"^1.7.0\"\n      },\n      \"dependencies\": {\n        \"find-cache-dir\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz\",\n          \"integrity\": \"sha1-jQ+UzRP+Q8bHwmGg2GEVypGMBfc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"commondir\": \"^1.0.1\",\n            \"make-dir\": \"^2.0.0\",\n            \"pkg-dir\": \"^3.0.0\"\n          }\n        },\n        \"find-up\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/find-up/-/find-up-3.0.0.tgz\",\n          \"integrity\": \"sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=\",\n          \"dev\": true,\n          \"requires\": {\n            \"locate-path\": \"^3.0.0\"\n          }\n        },\n        \"locate-path\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/locate-path/-/locate-path-3.0.0.tgz\",\n          \"integrity\": \"sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-locate\": \"^3.0.0\",\n            \"path-exists\": \"^3.0.0\"\n          }\n        },\n        \"make-dir\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/make-dir/-/make-dir-2.1.0.tgz\",\n          \"integrity\": \"sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU=\",\n          \"dev\": true,\n          \"requires\": {\n            \"pify\": \"^4.0.1\",\n            \"semver\": \"^5.6.0\"\n          }\n        },\n        \"p-locate\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/p-locate/-/p-locate-3.0.0.tgz\",\n          \"integrity\": \"sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-limit\": \"^2.0.0\"\n          }\n        },\n        \"path-exists\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/path-exists/-/path-exists-3.0.0.tgz\",\n          \"integrity\": \"sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=\",\n          \"dev\": true\n        },\n        \"pkg-dir\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/pkg-dir/-/pkg-dir-3.0.0.tgz\",\n          \"integrity\": \"sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"find-up\": \"^3.0.0\"\n          }\n        },\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        },\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"text-table\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/text-table/-/text-table-0.2.0.tgz\",\n      \"integrity\": \"sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=\",\n      \"dev\": true\n    },\n    \"through\": {\n      \"version\": \"2.3.8\",\n      \"resolved\": \"https://npm.quanziapp.com/through/-/through-2.3.8.tgz\",\n      \"integrity\": \"sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=\",\n      \"dev\": true\n    },\n    \"through2\": {\n      \"version\": \"2.0.5\",\n      \"resolved\": \"https://npm.quanziapp.com/through2/-/through2-2.0.5.tgz\",\n      \"integrity\": \"sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"readable-stream\": \"~2.3.6\",\n        \"xtend\": \"~4.0.1\"\n      }\n    },\n    \"thunky\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/thunky/-/thunky-1.1.0.tgz\",\n      \"integrity\": \"sha1-Wrr3FKlAXbBQRzK7zNLO3Z75U30=\",\n      \"dev\": true\n    },\n    \"timers-browserify\": {\n      \"version\": \"2.0.12\",\n      \"resolved\": \"https://npm.quanziapp.com/timers-browserify/-/timers-browserify-2.0.12.tgz\",\n      \"integrity\": \"sha1-RKRcEfv0B/NPl7zNFXfGUjYbAO4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"setimmediate\": \"^1.0.4\"\n      }\n    },\n    \"timsort\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/timsort/-/timsort-0.3.0.tgz\",\n      \"integrity\": \"sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=\",\n      \"dev\": true\n    },\n    \"tiny-emitter\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz\",\n      \"integrity\": \"sha1-HRpW7fxRxD6GPLtTgqcjMONVVCM=\",\n      \"dev\": true,\n      \"optional\": true\n    },\n    \"to-arraybuffer\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz\",\n      \"integrity\": \"sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=\",\n      \"dev\": true\n    },\n    \"to-factory\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/to-factory/-/to-factory-1.0.0.tgz\",\n      \"integrity\": \"sha1-hzivi9lxIK0dQEeXKtpVY7+UebE=\",\n      \"dev\": true\n    },\n    \"to-fast-properties\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz\",\n      \"integrity\": \"sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=\",\n      \"dev\": true\n    },\n    \"to-object-path\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://npm.quanziapp.com/to-object-path/-/to-object-path-0.3.0.tgz\",\n      \"integrity\": \"sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=\",\n      \"dev\": true,\n      \"requires\": {\n        \"kind-of\": \"^3.0.2\"\n      },\n      \"dependencies\": {\n        \"kind-of\": {\n          \"version\": \"3.2.2\",\n          \"resolved\": \"https://npm.quanziapp.com/kind-of/-/kind-of-3.2.2.tgz\",\n          \"integrity\": \"sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"is-buffer\": \"^1.1.5\"\n          }\n        }\n      }\n    },\n    \"to-readable-stream\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz\",\n      \"integrity\": \"sha1-zgqgwvPfat+FLvtASng+d8BHV3E=\",\n      \"dev\": true\n    },\n    \"to-regex\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/to-regex/-/to-regex-3.0.2.tgz\",\n      \"integrity\": \"sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"define-property\": \"^2.0.2\",\n        \"extend-shallow\": \"^3.0.2\",\n        \"regex-not\": \"^1.0.2\",\n        \"safe-regex\": \"^1.1.0\"\n      }\n    },\n    \"to-regex-range\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/to-regex-range/-/to-regex-range-2.1.1.tgz\",\n      \"integrity\": \"sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-number\": \"^3.0.0\",\n        \"repeat-string\": \"^1.6.1\"\n      }\n    },\n    \"toidentifier\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/toidentifier/-/toidentifier-1.0.0.tgz\",\n      \"integrity\": \"sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=\",\n      \"dev\": true\n    },\n    \"toml\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/toml/-/toml-3.0.0.tgz\",\n      \"integrity\": \"sha1-NCFg8a8ZBOydIE0DpdYSItdixe4=\",\n      \"dev\": true\n    },\n    \"toposort\": {\n      \"version\": \"1.0.7\",\n      \"resolved\": \"https://npm.quanziapp.com/toposort/-/toposort-1.0.7.tgz\",\n      \"integrity\": \"sha1-LmhELZ9k7HILjMieZEOsbKqVACk=\",\n      \"dev\": true\n    },\n    \"tough-cookie\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://npm.quanziapp.com/tough-cookie/-/tough-cookie-2.5.0.tgz\",\n      \"integrity\": \"sha1-zZ+yoKodWhK0c72fuW+j3P9lreI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"psl\": \"^1.1.28\",\n        \"punycode\": \"^2.1.1\"\n      }\n    },\n    \"tslib\": {\n      \"version\": \"1.14.1\",\n      \"resolved\": \"https://npm.quanziapp.com/tslib/-/tslib-1.14.1.tgz\",\n      \"integrity\": \"sha1-zy04vcNKE0vK8QkcQfZhni9nLQA=\",\n      \"dev\": true\n    },\n    \"tty-browserify\": {\n      \"version\": \"0.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/tty-browserify/-/tty-browserify-0.0.0.tgz\",\n      \"integrity\": \"sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=\",\n      \"dev\": true\n    },\n    \"tunnel-agent\": {\n      \"version\": \"0.6.0\",\n      \"resolved\": \"https://npm.quanziapp.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz\",\n      \"integrity\": \"sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"safe-buffer\": \"^5.0.1\"\n      }\n    },\n    \"tweetnacl\": {\n      \"version\": \"0.14.5\",\n      \"resolved\": \"https://npm.quanziapp.com/tweetnacl/-/tweetnacl-0.14.5.tgz\",\n      \"integrity\": \"sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=\",\n      \"dev\": true\n    },\n    \"type-fest\": {\n      \"version\": \"0.11.0\",\n      \"resolved\": \"https://npm.quanziapp.com/type-fest/-/type-fest-0.11.0.tgz\",\n      \"integrity\": \"sha1-l6vwhyMQ/tiKXEZrJWgVdhReM/E=\",\n      \"dev\": true\n    },\n    \"type-is\": {\n      \"version\": \"1.6.18\",\n      \"resolved\": \"https://npm.quanziapp.com/type-is/-/type-is-1.6.18.tgz\",\n      \"integrity\": \"sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"media-typer\": \"0.3.0\",\n        \"mime-types\": \"~2.1.24\"\n      }\n    },\n    \"typedarray\": {\n      \"version\": \"0.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/typedarray/-/typedarray-0.0.6.tgz\",\n      \"integrity\": \"sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=\",\n      \"dev\": true\n    },\n    \"typedarray-to-buffer\": {\n      \"version\": \"3.1.5\",\n      \"resolved\": \"https://npm.quanziapp.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz\",\n      \"integrity\": \"sha1-qX7nqf9CaRufeD/xvFES/j/KkIA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-typedarray\": \"^1.0.0\"\n      }\n    },\n    \"uc.micro\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://npm.quanziapp.com/uc.micro/-/uc.micro-1.0.6.tgz\",\n      \"integrity\": \"sha1-nEEagCpAmpH8bPdAgbq6NLJEmaw=\",\n      \"dev\": true\n    },\n    \"uglify-js\": {\n      \"version\": \"3.4.10\",\n      \"resolved\": \"https://npm.quanziapp.com/uglify-js/-/uglify-js-3.4.10.tgz\",\n      \"integrity\": \"sha1-mtlWPY6zrN+404WX0q8dgV9qdV8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"commander\": \"~2.19.0\",\n        \"source-map\": \"~0.6.1\"\n      },\n      \"dependencies\": {\n        \"commander\": {\n          \"version\": \"2.19.0\",\n          \"resolved\": \"https://npm.quanziapp.com/commander/-/commander-2.19.0.tgz\",\n          \"integrity\": \"sha1-9hmKqE5bg8RgVLlN3tv+1e6f8So=\",\n          \"dev\": true\n        },\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"unicode-canonical-property-names-ecmascript\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz\",\n      \"integrity\": \"sha1-JhmADEyCWADv3YNDr33Zkzy+KBg=\",\n      \"dev\": true\n    },\n    \"unicode-match-property-ecmascript\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz\",\n      \"integrity\": \"sha1-jtKjJWmWG86SJ9Cc0/+7j+1fAgw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"unicode-canonical-property-names-ecmascript\": \"^1.0.4\",\n        \"unicode-property-aliases-ecmascript\": \"^1.0.4\"\n      }\n    },\n    \"unicode-match-property-value-ecmascript\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz\",\n      \"integrity\": \"sha1-DZH2AO7rMJaqlisdb8iIduZOpTE=\",\n      \"dev\": true\n    },\n    \"unicode-property-aliases-ecmascript\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz\",\n      \"integrity\": \"sha1-3Vepn2IHvt/0Yoq++5TFDblByPQ=\",\n      \"dev\": true\n    },\n    \"union-value\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/union-value/-/union-value-1.0.1.tgz\",\n      \"integrity\": \"sha1-C2/nuDWuzaYcbqTU8CwUIh4QmEc=\",\n      \"dev\": true,\n      \"requires\": {\n        \"arr-union\": \"^3.1.0\",\n        \"get-value\": \"^2.0.6\",\n        \"is-extendable\": \"^0.1.1\",\n        \"set-value\": \"^2.0.1\"\n      }\n    },\n    \"uniq\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/uniq/-/uniq-1.0.1.tgz\",\n      \"integrity\": \"sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=\",\n      \"dev\": true\n    },\n    \"uniqs\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/uniqs/-/uniqs-2.0.0.tgz\",\n      \"integrity\": \"sha1-/+3ks2slKQaW5uFl1KWe25mOawI=\",\n      \"dev\": true\n    },\n    \"unique-filename\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/unique-filename/-/unique-filename-1.1.1.tgz\",\n      \"integrity\": \"sha1-HWl2k2mtoFgxA6HmrodoG1ZXMjA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"unique-slug\": \"^2.0.0\"\n      }\n    },\n    \"unique-slug\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/unique-slug/-/unique-slug-2.0.2.tgz\",\n      \"integrity\": \"sha1-uqvOkQg/xk6UWw861hPiZPfNTmw=\",\n      \"dev\": true,\n      \"requires\": {\n        \"imurmurhash\": \"^0.1.4\"\n      }\n    },\n    \"unique-string\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/unique-string/-/unique-string-2.0.0.tgz\",\n      \"integrity\": \"sha1-OcZFH4GvsnSd4rIz4/fF6IQ72J0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"crypto-random-string\": \"^2.0.0\"\n      }\n    },\n    \"universalify\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/universalify/-/universalify-0.1.2.tgz\",\n      \"integrity\": \"sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=\",\n      \"dev\": true\n    },\n    \"unpipe\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/unpipe/-/unpipe-1.0.0.tgz\",\n      \"integrity\": \"sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=\",\n      \"dev\": true\n    },\n    \"unquote\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/unquote/-/unquote-1.1.1.tgz\",\n      \"integrity\": \"sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=\",\n      \"dev\": true\n    },\n    \"unset-value\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/unset-value/-/unset-value-1.0.0.tgz\",\n      \"integrity\": \"sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"has-value\": \"^0.3.1\",\n        \"isobject\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"has-value\": {\n          \"version\": \"0.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/has-value/-/has-value-0.3.1.tgz\",\n          \"integrity\": \"sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"get-value\": \"^2.0.3\",\n            \"has-values\": \"^0.1.4\",\n            \"isobject\": \"^2.0.0\"\n          },\n          \"dependencies\": {\n            \"isobject\": {\n              \"version\": \"2.1.0\",\n              \"resolved\": \"https://npm.quanziapp.com/isobject/-/isobject-2.1.0.tgz\",\n              \"integrity\": \"sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=\",\n              \"dev\": true,\n              \"requires\": {\n                \"isarray\": \"1.0.0\"\n              }\n            }\n          }\n        },\n        \"has-values\": {\n          \"version\": \"0.1.4\",\n          \"resolved\": \"https://npm.quanziapp.com/has-values/-/has-values-0.1.4.tgz\",\n          \"integrity\": \"sha1-bWHeldkd/Km5oCCJrThL/49it3E=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"upath\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/upath/-/upath-1.2.0.tgz\",\n      \"integrity\": \"sha1-j2bbzVWog6za5ECK+LA1pQRMGJQ=\",\n      \"dev\": true\n    },\n    \"update-notifier\": {\n      \"version\": \"4.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/update-notifier/-/update-notifier-4.1.3.tgz\",\n      \"integrity\": \"sha1-vobuE+jOSPtQBD/3IFe1vVmOHqM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"boxen\": \"^4.2.0\",\n        \"chalk\": \"^3.0.0\",\n        \"configstore\": \"^5.0.1\",\n        \"has-yarn\": \"^2.1.0\",\n        \"import-lazy\": \"^2.1.0\",\n        \"is-ci\": \"^2.0.0\",\n        \"is-installed-globally\": \"^0.3.1\",\n        \"is-npm\": \"^4.0.0\",\n        \"is-yarn-global\": \"^0.3.0\",\n        \"latest-version\": \"^5.0.0\",\n        \"pupa\": \"^2.0.1\",\n        \"semver-diff\": \"^3.1.1\",\n        \"xdg-basedir\": \"^4.0.0\"\n      },\n      \"dependencies\": {\n        \"ansi-styles\": {\n          \"version\": \"4.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ansi-styles/-/ansi-styles-4.3.0.tgz\",\n          \"integrity\": \"sha1-7dgDYornHATIWuegkG7a00tkiTc=\",\n          \"dev\": true,\n          \"requires\": {\n            \"color-convert\": \"^2.0.1\"\n          }\n        },\n        \"chalk\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/chalk/-/chalk-3.0.0.tgz\",\n          \"integrity\": \"sha1-P3PCv1JlkfV0zEksUeJFY0n4ROQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-styles\": \"^4.1.0\",\n            \"supports-color\": \"^7.1.0\"\n          }\n        },\n        \"color-convert\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/color-convert/-/color-convert-2.0.1.tgz\",\n          \"integrity\": \"sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"color-name\": \"~1.1.4\"\n          }\n        },\n        \"color-name\": {\n          \"version\": \"1.1.4\",\n          \"resolved\": \"https://npm.quanziapp.com/color-name/-/color-name-1.1.4.tgz\",\n          \"integrity\": \"sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=\",\n          \"dev\": true\n        },\n        \"has-flag\": {\n          \"version\": \"4.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/has-flag/-/has-flag-4.0.0.tgz\",\n          \"integrity\": \"sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=\",\n          \"dev\": true\n        },\n        \"supports-color\": {\n          \"version\": \"7.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/supports-color/-/supports-color-7.2.0.tgz\",\n          \"integrity\": \"sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=\",\n          \"dev\": true,\n          \"requires\": {\n            \"has-flag\": \"^4.0.0\"\n          }\n        }\n      }\n    },\n    \"upper-case\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://npm.quanziapp.com/upper-case/-/upper-case-1.1.3.tgz\",\n      \"integrity\": \"sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=\",\n      \"dev\": true\n    },\n    \"uri-js\": {\n      \"version\": \"4.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/uri-js/-/uri-js-4.4.0.tgz\",\n      \"integrity\": \"sha1-qnFCYd55PoqCNHp7zJznTobyhgI=\",\n      \"dev\": true,\n      \"requires\": {\n        \"punycode\": \"^2.1.0\"\n      }\n    },\n    \"urix\": {\n      \"version\": \"0.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/urix/-/urix-0.1.0.tgz\",\n      \"integrity\": \"sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=\",\n      \"dev\": true\n    },\n    \"url\": {\n      \"version\": \"0.11.0\",\n      \"resolved\": \"https://npm.quanziapp.com/url/-/url-0.11.0.tgz\",\n      \"integrity\": \"sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"punycode\": \"1.3.2\",\n        \"querystring\": \"0.2.0\"\n      },\n      \"dependencies\": {\n        \"punycode\": {\n          \"version\": \"1.3.2\",\n          \"resolved\": \"https://npm.quanziapp.com/punycode/-/punycode-1.3.2.tgz\",\n          \"integrity\": \"sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"url-loader\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/url-loader/-/url-loader-1.1.2.tgz\",\n      \"integrity\": \"sha1-uXHRkbg69pPF4/6kBkvp4fLX+Ng=\",\n      \"dev\": true,\n      \"requires\": {\n        \"loader-utils\": \"^1.1.0\",\n        \"mime\": \"^2.0.3\",\n        \"schema-utils\": \"^1.0.0\"\n      },\n      \"dependencies\": {\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        }\n      }\n    },\n    \"url-parse\": {\n      \"version\": \"1.4.7\",\n      \"resolved\": \"https://npm.quanziapp.com/url-parse/-/url-parse-1.4.7.tgz\",\n      \"integrity\": \"sha1-qKg1NejACjFuQDpdtKwbm4U64ng=\",\n      \"dev\": true,\n      \"requires\": {\n        \"querystringify\": \"^2.1.1\",\n        \"requires-port\": \"^1.0.0\"\n      }\n    },\n    \"url-parse-lax\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz\",\n      \"integrity\": \"sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=\",\n      \"dev\": true,\n      \"requires\": {\n        \"prepend-http\": \"^2.0.0\"\n      }\n    },\n    \"use\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/use/-/use-3.1.1.tgz\",\n      \"integrity\": \"sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=\",\n      \"dev\": true\n    },\n    \"util\": {\n      \"version\": \"0.11.1\",\n      \"resolved\": \"https://npm.quanziapp.com/util/-/util-0.11.1.tgz\",\n      \"integrity\": \"sha1-MjZzNyDsZLsn9uJvQhqqLhtYjWE=\",\n      \"dev\": true,\n      \"requires\": {\n        \"inherits\": \"2.0.3\"\n      },\n      \"dependencies\": {\n        \"inherits\": {\n          \"version\": \"2.0.3\",\n          \"resolved\": \"https://npm.quanziapp.com/inherits/-/inherits-2.0.3.tgz\",\n          \"integrity\": \"sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"util-deprecate\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/util-deprecate/-/util-deprecate-1.0.2.tgz\",\n      \"integrity\": \"sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=\",\n      \"dev\": true\n    },\n    \"util.promisify\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/util.promisify/-/util.promisify-1.0.1.tgz\",\n      \"integrity\": \"sha1-a693dLgO6w91INi4HQeYKlmruu4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.17.2\",\n        \"has-symbols\": \"^1.0.1\",\n        \"object.getownpropertydescriptors\": \"^2.1.0\"\n      },\n      \"dependencies\": {\n        \"es-abstract\": {\n          \"version\": \"1.17.7\",\n          \"resolved\": \"https://npm.quanziapp.com/es-abstract/-/es-abstract-1.17.7.tgz\",\n          \"integrity\": \"sha1-pN5hsvZpifx0IWdsHLl4dXOs5Uw=\",\n          \"dev\": true,\n          \"requires\": {\n            \"es-to-primitive\": \"^1.2.1\",\n            \"function-bind\": \"^1.1.1\",\n            \"has\": \"^1.0.3\",\n            \"has-symbols\": \"^1.0.1\",\n            \"is-callable\": \"^1.2.2\",\n            \"is-regex\": \"^1.1.1\",\n            \"object-inspect\": \"^1.8.0\",\n            \"object-keys\": \"^1.1.1\",\n            \"object.assign\": \"^4.1.1\",\n            \"string.prototype.trimend\": \"^1.0.1\",\n            \"string.prototype.trimstart\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"utila\": {\n      \"version\": \"0.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/utila/-/utila-0.4.0.tgz\",\n      \"integrity\": \"sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=\",\n      \"dev\": true\n    },\n    \"utils-merge\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/utils-merge/-/utils-merge-1.0.1.tgz\",\n      \"integrity\": \"sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=\",\n      \"dev\": true\n    },\n    \"uuid\": {\n      \"version\": \"3.4.0\",\n      \"resolved\": \"https://npm.quanziapp.com/uuid/-/uuid-3.4.0.tgz\",\n      \"integrity\": \"sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=\",\n      \"dev\": true\n    },\n    \"vary\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/vary/-/vary-1.1.2.tgz\",\n      \"integrity\": \"sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=\",\n      \"dev\": true\n    },\n    \"vendors\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://npm.quanziapp.com/vendors/-/vendors-1.0.4.tgz\",\n      \"integrity\": \"sha1-4rgApT56Kbk1BsPPQRANFsTErY4=\",\n      \"dev\": true\n    },\n    \"verror\": {\n      \"version\": \"1.10.0\",\n      \"resolved\": \"https://npm.quanziapp.com/verror/-/verror-1.10.0.tgz\",\n      \"integrity\": \"sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=\",\n      \"dev\": true,\n      \"requires\": {\n        \"assert-plus\": \"^1.0.0\",\n        \"core-util-is\": \"1.0.2\",\n        \"extsprintf\": \"^1.2.0\"\n      }\n    },\n    \"vm-browserify\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/vm-browserify/-/vm-browserify-1.1.2.tgz\",\n      \"integrity\": \"sha1-eGQcSIuObKkadfUR56OzKobl3aA=\",\n      \"dev\": true\n    },\n    \"vue\": {\n      \"version\": \"2.6.12\",\n      \"resolved\": \"https://npm.quanziapp.com/vue/-/vue-2.6.12.tgz\",\n      \"integrity\": \"sha1-9evU+mvShpQD4pqJau1JBEVskSM=\",\n      \"dev\": true\n    },\n    \"vue-hot-reload-api\": {\n      \"version\": \"2.3.4\",\n      \"resolved\": \"https://npm.quanziapp.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz\",\n      \"integrity\": \"sha1-UylVzB6yCKPZkLOp+acFdGV+CPI=\",\n      \"dev\": true\n    },\n    \"vue-loader\": {\n      \"version\": \"15.9.5\",\n      \"resolved\": \"https://npm.quanziapp.com/vue-loader/-/vue-loader-15.9.5.tgz\",\n      \"integrity\": \"sha1-epYNxCCjQ53qrN2gOP3Nv3xDJwY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@vue/component-compiler-utils\": \"^3.1.0\",\n        \"hash-sum\": \"^1.0.2\",\n        \"loader-utils\": \"^1.1.0\",\n        \"vue-hot-reload-api\": \"^2.3.0\",\n        \"vue-style-loader\": \"^4.1.0\"\n      }\n    },\n    \"vue-router\": {\n      \"version\": \"3.4.9\",\n      \"resolved\": \"https://npm.quanziapp.com/vue-router/-/vue-router-3.4.9.tgz\",\n      \"integrity\": \"sha1-wBb0IDCuKTLxTkdIs5odmg4lDmY=\",\n      \"dev\": true\n    },\n    \"vue-server-renderer\": {\n      \"version\": \"2.6.12\",\n      \"resolved\": \"https://npm.quanziapp.com/vue-server-renderer/-/vue-server-renderer-2.6.12.tgz\",\n      \"integrity\": \"sha1-qMucSUOe8gUpPLQcNdDSsFQWU6U=\",\n      \"dev\": true,\n      \"requires\": {\n        \"chalk\": \"^1.1.3\",\n        \"hash-sum\": \"^1.0.2\",\n        \"he\": \"^1.1.0\",\n        \"lodash.template\": \"^4.5.0\",\n        \"lodash.uniq\": \"^4.5.0\",\n        \"resolve\": \"^1.2.0\",\n        \"serialize-javascript\": \"^3.1.0\",\n        \"source-map\": \"0.5.6\"\n      },\n      \"dependencies\": {\n        \"ansi-styles\": {\n          \"version\": \"2.2.1\",\n          \"resolved\": \"https://npm.quanziapp.com/ansi-styles/-/ansi-styles-2.2.1.tgz\",\n          \"integrity\": \"sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=\",\n          \"dev\": true\n        },\n        \"chalk\": {\n          \"version\": \"1.1.3\",\n          \"resolved\": \"https://npm.quanziapp.com/chalk/-/chalk-1.1.3.tgz\",\n          \"integrity\": \"sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-styles\": \"^2.2.1\",\n            \"escape-string-regexp\": \"^1.0.2\",\n            \"has-ansi\": \"^2.0.0\",\n            \"strip-ansi\": \"^3.0.0\",\n            \"supports-color\": \"^2.0.0\"\n          }\n        },\n        \"serialize-javascript\": {\n          \"version\": \"3.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz\",\n          \"integrity\": \"sha1-i/OpFwcSZk7yVhtEtpHq/jmSFOo=\",\n          \"dev\": true,\n          \"requires\": {\n            \"randombytes\": \"^2.1.0\"\n          }\n        },\n        \"source-map\": {\n          \"version\": \"0.5.6\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.5.6.tgz\",\n          \"integrity\": \"sha1-dc449SvwczxafwwRjYEzSiu19BI=\",\n          \"dev\": true\n        },\n        \"supports-color\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/supports-color/-/supports-color-2.0.0.tgz\",\n          \"integrity\": \"sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"vue-style-loader\": {\n      \"version\": \"4.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz\",\n      \"integrity\": \"sha1-3t80mAbyXOtOZPOtfApE+6c1/Pg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"hash-sum\": \"^1.0.2\",\n        \"loader-utils\": \"^1.0.2\"\n      }\n    },\n    \"vue-template-compiler\": {\n      \"version\": \"2.6.12\",\n      \"resolved\": \"https://npm.quanziapp.com/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz\",\n      \"integrity\": \"sha1-lH7XGWdEyKUoXr4SM/6WBDf8xX4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"de-indent\": \"^1.0.2\",\n        \"he\": \"^1.1.0\"\n      }\n    },\n    \"vue-template-es2015-compiler\": {\n      \"version\": \"1.9.1\",\n      \"resolved\": \"https://npm.quanziapp.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz\",\n      \"integrity\": \"sha1-HuO8mhbsv1EYvjNLsV+cRvgvWCU=\",\n      \"dev\": true\n    },\n    \"vuepress\": {\n      \"version\": \"1.7.1\",\n      \"resolved\": \"https://npm.quanziapp.com/vuepress/-/vuepress-1.7.1.tgz\",\n      \"integrity\": \"sha1-uw4TnYxAegtaqWLPlXeDKlgIk34=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@vuepress/core\": \"1.7.1\",\n        \"@vuepress/theme-default\": \"1.7.1\",\n        \"cac\": \"^6.5.6\",\n        \"envinfo\": \"^7.2.0\",\n        \"opencollective-postinstall\": \"^2.0.2\",\n        \"update-notifier\": \"^4.0.0\"\n      }\n    },\n    \"vuepress-html-webpack-plugin\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/vuepress-html-webpack-plugin/-/vuepress-html-webpack-plugin-3.2.0.tgz\",\n      \"integrity\": \"sha1-IZvicq1RD6qHUNLU5w/QKL/RwW4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"html-minifier\": \"^3.2.3\",\n        \"loader-utils\": \"^0.2.16\",\n        \"lodash\": \"^4.17.3\",\n        \"pretty-error\": \"^2.0.2\",\n        \"tapable\": \"^1.0.0\",\n        \"toposort\": \"^1.0.0\",\n        \"util.promisify\": \"1.0.0\"\n      },\n      \"dependencies\": {\n        \"big.js\": {\n          \"version\": \"3.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/big.js/-/big.js-3.2.0.tgz\",\n          \"integrity\": \"sha1-pfwpi4G54Nyi5FiCR4S2XFK6WI4=\",\n          \"dev\": true\n        },\n        \"emojis-list\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/emojis-list/-/emojis-list-2.1.0.tgz\",\n          \"integrity\": \"sha1-TapNnbAPmBmIDHn6RXrlsJof04k=\",\n          \"dev\": true\n        },\n        \"json5\": {\n          \"version\": \"0.5.1\",\n          \"resolved\": \"https://npm.quanziapp.com/json5/-/json5-0.5.1.tgz\",\n          \"integrity\": \"sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=\",\n          \"dev\": true\n        },\n        \"loader-utils\": {\n          \"version\": \"0.2.17\",\n          \"resolved\": \"https://npm.quanziapp.com/loader-utils/-/loader-utils-0.2.17.tgz\",\n          \"integrity\": \"sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=\",\n          \"dev\": true,\n          \"requires\": {\n            \"big.js\": \"^3.1.3\",\n            \"emojis-list\": \"^2.0.0\",\n            \"json5\": \"^0.5.0\",\n            \"object-assign\": \"^4.0.1\"\n          }\n        },\n        \"util.promisify\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/util.promisify/-/util.promisify-1.0.0.tgz\",\n          \"integrity\": \"sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=\",\n          \"dev\": true,\n          \"requires\": {\n            \"define-properties\": \"^1.1.2\",\n            \"object.getownpropertydescriptors\": \"^2.0.3\"\n          }\n        }\n      }\n    },\n    \"vuepress-plugin-container\": {\n      \"version\": \"2.1.5\",\n      \"resolved\": \"https://npm.quanziapp.com/vuepress-plugin-container/-/vuepress-plugin-container-2.1.5.tgz\",\n      \"integrity\": \"sha1-N//wVmL+29Y//TpUY7JZLHp/MTM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@vuepress/shared-utils\": \"^1.2.0\",\n        \"markdown-it-container\": \"^2.0.0\"\n      }\n    },\n    \"vuepress-plugin-smooth-scroll\": {\n      \"version\": \"0.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/vuepress-plugin-smooth-scroll/-/vuepress-plugin-smooth-scroll-0.0.3.tgz\",\n      \"integrity\": \"sha1-bv8tTBhsypF8yfffKwr33nyMZDg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"smoothscroll-polyfill\": \"^0.4.3\"\n      }\n    },\n    \"watchpack\": {\n      \"version\": \"1.7.5\",\n      \"resolved\": \"https://npm.quanziapp.com/watchpack/-/watchpack-1.7.5.tgz\",\n      \"integrity\": \"sha1-EmfmxV4Lm1vkTCAjrtVDeiwmxFM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"chokidar\": \"^3.4.1\",\n        \"graceful-fs\": \"^4.1.2\",\n        \"neo-async\": \"^2.5.0\",\n        \"watchpack-chokidar2\": \"^2.0.1\"\n      },\n      \"dependencies\": {\n        \"anymatch\": {\n          \"version\": \"3.1.1\",\n          \"resolved\": \"https://npm.quanziapp.com/anymatch/-/anymatch-3.1.1.tgz\",\n          \"integrity\": \"sha1-xV7PAhheJGklk5kxDBc84xIzsUI=\",\n          \"dev\": true,\n          \"optional\": true,\n          \"requires\": {\n            \"normalize-path\": \"^3.0.0\",\n            \"picomatch\": \"^2.0.4\"\n          }\n        },\n        \"binary-extensions\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/binary-extensions/-/binary-extensions-2.1.0.tgz\",\n          \"integrity\": \"sha1-MPpAyef+B9vIlWeM0ocCTeokHdk=\",\n          \"dev\": true,\n          \"optional\": true\n        },\n        \"braces\": {\n          \"version\": \"3.0.2\",\n          \"resolved\": \"https://npm.quanziapp.com/braces/-/braces-3.0.2.tgz\",\n          \"integrity\": \"sha1-NFThpGLujVmeI23zNs2epPiv4Qc=\",\n          \"dev\": true,\n          \"optional\": true,\n          \"requires\": {\n            \"fill-range\": \"^7.0.1\"\n          }\n        },\n        \"chokidar\": {\n          \"version\": \"3.4.3\",\n          \"resolved\": \"https://npm.quanziapp.com/chokidar/-/chokidar-3.4.3.tgz\",\n          \"integrity\": \"sha1-wd84IxRI5FykrFiObHlXO6alfVs=\",\n          \"dev\": true,\n          \"optional\": true,\n          \"requires\": {\n            \"anymatch\": \"~3.1.1\",\n            \"braces\": \"~3.0.2\",\n            \"fsevents\": \"~2.1.2\",\n            \"glob-parent\": \"~5.1.0\",\n            \"is-binary-path\": \"~2.1.0\",\n            \"is-glob\": \"~4.0.1\",\n            \"normalize-path\": \"~3.0.0\",\n            \"readdirp\": \"~3.5.0\"\n          }\n        },\n        \"fill-range\": {\n          \"version\": \"7.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/fill-range/-/fill-range-7.0.1.tgz\",\n          \"integrity\": \"sha1-GRmmp8df44ssfHflGYU12prN2kA=\",\n          \"dev\": true,\n          \"optional\": true,\n          \"requires\": {\n            \"to-regex-range\": \"^5.0.1\"\n          }\n        },\n        \"fsevents\": {\n          \"version\": \"2.1.3\",\n          \"resolved\": \"https://npm.quanziapp.com/fsevents/-/fsevents-2.1.3.tgz\",\n          \"integrity\": \"sha1-+3OHA66NL5/pAMM4Nt3r7ouX8j4=\",\n          \"dev\": true,\n          \"optional\": true\n        },\n        \"glob-parent\": {\n          \"version\": \"5.1.1\",\n          \"resolved\": \"https://npm.quanziapp.com/glob-parent/-/glob-parent-5.1.1.tgz\",\n          \"integrity\": \"sha1-tsHvQXxOVmPqSY8cRa+saRa7wik=\",\n          \"dev\": true,\n          \"optional\": true,\n          \"requires\": {\n            \"is-glob\": \"^4.0.1\"\n          }\n        },\n        \"is-binary-path\": {\n          \"version\": \"2.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-binary-path/-/is-binary-path-2.1.0.tgz\",\n          \"integrity\": \"sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=\",\n          \"dev\": true,\n          \"optional\": true,\n          \"requires\": {\n            \"binary-extensions\": \"^2.0.0\"\n          }\n        },\n        \"is-number\": {\n          \"version\": \"7.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-number/-/is-number-7.0.0.tgz\",\n          \"integrity\": \"sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=\",\n          \"dev\": true,\n          \"optional\": true\n        },\n        \"readdirp\": {\n          \"version\": \"3.5.0\",\n          \"resolved\": \"https://npm.quanziapp.com/readdirp/-/readdirp-3.5.0.tgz\",\n          \"integrity\": \"sha1-m6dMAZsV02UnjS6Ru4xI17TULJ4=\",\n          \"dev\": true,\n          \"optional\": true,\n          \"requires\": {\n            \"picomatch\": \"^2.2.1\"\n          }\n        },\n        \"to-regex-range\": {\n          \"version\": \"5.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/to-regex-range/-/to-regex-range-5.0.1.tgz\",\n          \"integrity\": \"sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=\",\n          \"dev\": true,\n          \"optional\": true,\n          \"requires\": {\n            \"is-number\": \"^7.0.0\"\n          }\n        }\n      }\n    },\n    \"watchpack-chokidar2\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz\",\n      \"integrity\": \"sha1-OFAAcu5uzmbzdpk2lQ6hdxvhyVc=\",\n      \"dev\": true,\n      \"optional\": true,\n      \"requires\": {\n        \"chokidar\": \"^2.1.8\"\n      }\n    },\n    \"wbuf\": {\n      \"version\": \"1.7.3\",\n      \"resolved\": \"https://npm.quanziapp.com/wbuf/-/wbuf-1.7.3.tgz\",\n      \"integrity\": \"sha1-wdjRSTFtPqhShIiVy2oL/oh7h98=\",\n      \"dev\": true,\n      \"requires\": {\n        \"minimalistic-assert\": \"^1.0.0\"\n      }\n    },\n    \"webpack\": {\n      \"version\": \"4.44.2\",\n      \"resolved\": \"https://npm.quanziapp.com/webpack/-/webpack-4.44.2.tgz\",\n      \"integrity\": \"sha1-a/4rCvBVyLLR6Q7SzZNj+EEma3I=\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-module-context\": \"1.9.0\",\n        \"@webassemblyjs/wasm-edit\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\",\n        \"acorn\": \"^6.4.1\",\n        \"ajv\": \"^6.10.2\",\n        \"ajv-keywords\": \"^3.4.1\",\n        \"chrome-trace-event\": \"^1.0.2\",\n        \"enhanced-resolve\": \"^4.3.0\",\n        \"eslint-scope\": \"^4.0.3\",\n        \"json-parse-better-errors\": \"^1.0.2\",\n        \"loader-runner\": \"^2.4.0\",\n        \"loader-utils\": \"^1.2.3\",\n        \"memory-fs\": \"^0.4.1\",\n        \"micromatch\": \"^3.1.10\",\n        \"mkdirp\": \"^0.5.3\",\n        \"neo-async\": \"^2.6.1\",\n        \"node-libs-browser\": \"^2.2.1\",\n        \"schema-utils\": \"^1.0.0\",\n        \"tapable\": \"^1.1.3\",\n        \"terser-webpack-plugin\": \"^1.4.3\",\n        \"watchpack\": \"^1.7.4\",\n        \"webpack-sources\": \"^1.4.1\"\n      },\n      \"dependencies\": {\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        }\n      }\n    },\n    \"webpack-chain\": {\n      \"version\": \"6.5.1\",\n      \"resolved\": \"https://npm.quanziapp.com/webpack-chain/-/webpack-chain-6.5.1.tgz\",\n      \"integrity\": \"sha1-TycoTLu2N+PI+970Pu9YjU2GEgY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"deepmerge\": \"^1.5.2\",\n        \"javascript-stringify\": \"^2.0.1\"\n      },\n      \"dependencies\": {\n        \"javascript-stringify\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://npm.quanziapp.com/javascript-stringify/-/javascript-stringify-2.0.1.tgz\",\n          \"integrity\": \"sha1-bvNYA1MQ411mfGde1j0+t8GqGeU=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"webpack-dev-middleware\": {\n      \"version\": \"3.7.2\",\n      \"resolved\": \"https://npm.quanziapp.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz\",\n      \"integrity\": \"sha1-ABnD23FuP6XOy/ZPKriKdLqzMfM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"memory-fs\": \"^0.4.1\",\n        \"mime\": \"^2.4.4\",\n        \"mkdirp\": \"^0.5.1\",\n        \"range-parser\": \"^1.2.1\",\n        \"webpack-log\": \"^2.0.0\"\n      }\n    },\n    \"webpack-dev-server\": {\n      \"version\": \"3.11.0\",\n      \"resolved\": \"https://npm.quanziapp.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz\",\n      \"integrity\": \"sha1-jxVKO84bz9HMYY705wMniFXn/4w=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-html\": \"0.0.7\",\n        \"bonjour\": \"^3.5.0\",\n        \"chokidar\": \"^2.1.8\",\n        \"compression\": \"^1.7.4\",\n        \"connect-history-api-fallback\": \"^1.6.0\",\n        \"debug\": \"^4.1.1\",\n        \"del\": \"^4.1.1\",\n        \"express\": \"^4.17.1\",\n        \"html-entities\": \"^1.3.1\",\n        \"http-proxy-middleware\": \"0.19.1\",\n        \"import-local\": \"^2.0.0\",\n        \"internal-ip\": \"^4.3.0\",\n        \"ip\": \"^1.1.5\",\n        \"is-absolute-url\": \"^3.0.3\",\n        \"killable\": \"^1.0.1\",\n        \"loglevel\": \"^1.6.8\",\n        \"opn\": \"^5.5.0\",\n        \"p-retry\": \"^3.0.1\",\n        \"portfinder\": \"^1.0.26\",\n        \"schema-utils\": \"^1.0.0\",\n        \"selfsigned\": \"^1.10.7\",\n        \"semver\": \"^6.3.0\",\n        \"serve-index\": \"^1.9.1\",\n        \"sockjs\": \"0.3.20\",\n        \"sockjs-client\": \"1.4.0\",\n        \"spdy\": \"^4.0.2\",\n        \"strip-ansi\": \"^3.0.1\",\n        \"supports-color\": \"^6.1.0\",\n        \"url\": \"^0.11.0\",\n        \"webpack-dev-middleware\": \"^3.7.2\",\n        \"webpack-log\": \"^2.0.0\",\n        \"ws\": \"^6.2.1\",\n        \"yargs\": \"^13.3.2\"\n      },\n      \"dependencies\": {\n        \"is-absolute-url\": {\n          \"version\": \"3.0.3\",\n          \"resolved\": \"https://npm.quanziapp.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz\",\n          \"integrity\": \"sha1-lsaiK2ojkpsR6gr7GDbDatSl1pg=\",\n          \"dev\": true\n        },\n        \"schema-utils\": {\n          \"version\": \"1.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/schema-utils/-/schema-utils-1.0.0.tgz\",\n          \"integrity\": \"sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ajv\": \"^6.1.0\",\n            \"ajv-errors\": \"^1.0.0\",\n            \"ajv-keywords\": \"^3.1.0\"\n          }\n        },\n        \"semver\": {\n          \"version\": \"6.3.0\",\n          \"resolved\": \"https://npm.quanziapp.com/semver/-/semver-6.3.0.tgz\",\n          \"integrity\": \"sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=\",\n          \"dev\": true\n        },\n        \"supports-color\": {\n          \"version\": \"6.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/supports-color/-/supports-color-6.1.0.tgz\",\n          \"integrity\": \"sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"has-flag\": \"^3.0.0\"\n          }\n        }\n      }\n    },\n    \"webpack-log\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/webpack-log/-/webpack-log-2.0.0.tgz\",\n      \"integrity\": \"sha1-W3ko4GN1k/EZ0y9iJ8HgrDHhtH8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-colors\": \"^3.0.0\",\n        \"uuid\": \"^3.3.2\"\n      }\n    },\n    \"webpack-merge\": {\n      \"version\": \"4.2.2\",\n      \"resolved\": \"https://npm.quanziapp.com/webpack-merge/-/webpack-merge-4.2.2.tgz\",\n      \"integrity\": \"sha1-onxS6ng9E5iv0gh/VH17nS9DY00=\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash\": \"^4.17.15\"\n      }\n    },\n    \"webpack-sources\": {\n      \"version\": \"1.4.3\",\n      \"resolved\": \"https://npm.quanziapp.com/webpack-sources/-/webpack-sources-1.4.3.tgz\",\n      \"integrity\": \"sha1-7t2OwLko+/HL/plOItLYkPMwqTM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"source-list-map\": \"^2.0.0\",\n        \"source-map\": \"~0.6.1\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://npm.quanziapp.com/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha1-dHIq8y6WFOnCh6jQu95IteLxomM=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"webpackbar\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/webpackbar/-/webpackbar-3.2.0.tgz\",\n      \"integrity\": \"sha1-varRA/rRGk5hJQDnKqrpiwi6ST8=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-escapes\": \"^4.1.0\",\n        \"chalk\": \"^2.4.1\",\n        \"consola\": \"^2.6.0\",\n        \"figures\": \"^3.0.0\",\n        \"pretty-time\": \"^1.1.0\",\n        \"std-env\": \"^2.2.1\",\n        \"text-table\": \"^0.2.0\",\n        \"wrap-ansi\": \"^5.1.0\"\n      }\n    },\n    \"websocket-driver\": {\n      \"version\": \"0.6.5\",\n      \"resolved\": \"https://npm.quanziapp.com/websocket-driver/-/websocket-driver-0.6.5.tgz\",\n      \"integrity\": \"sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=\",\n      \"dev\": true,\n      \"requires\": {\n        \"websocket-extensions\": \">=0.1.1\"\n      }\n    },\n    \"websocket-extensions\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://npm.quanziapp.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz\",\n      \"integrity\": \"sha1-f4RzvIOd/YdgituV1+sHUhFXikI=\",\n      \"dev\": true\n    },\n    \"when\": {\n      \"version\": \"3.6.4\",\n      \"resolved\": \"https://npm.quanziapp.com/when/-/when-3.6.4.tgz\",\n      \"integrity\": \"sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=\",\n      \"dev\": true\n    },\n    \"which\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://npm.quanziapp.com/which/-/which-1.3.1.tgz\",\n      \"integrity\": \"sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=\",\n      \"dev\": true,\n      \"requires\": {\n        \"isexe\": \"^2.0.0\"\n      }\n    },\n    \"which-module\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/which-module/-/which-module-2.0.0.tgz\",\n      \"integrity\": \"sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=\",\n      \"dev\": true\n    },\n    \"widest-line\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/widest-line/-/widest-line-3.1.0.tgz\",\n      \"integrity\": \"sha1-gpIzO79my0X/DeFgOxNreuFJbso=\",\n      \"dev\": true,\n      \"requires\": {\n        \"string-width\": \"^4.0.0\"\n      },\n      \"dependencies\": {\n        \"ansi-regex\": {\n          \"version\": \"5.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ansi-regex/-/ansi-regex-5.0.0.tgz\",\n          \"integrity\": \"sha1-OIU59VF5vzkznIGvMKZU1p+Hy3U=\",\n          \"dev\": true\n        },\n        \"emoji-regex\": {\n          \"version\": \"8.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/emoji-regex/-/emoji-regex-8.0.0.tgz\",\n          \"integrity\": \"sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc=\",\n          \"dev\": true\n        },\n        \"is-fullwidth-code-point\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz\",\n          \"integrity\": \"sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0=\",\n          \"dev\": true\n        },\n        \"string-width\": {\n          \"version\": \"4.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/string-width/-/string-width-4.2.0.tgz\",\n          \"integrity\": \"sha1-lSGCxGzHssMT0VluYjmSvRY7crU=\",\n          \"dev\": true,\n          \"requires\": {\n            \"emoji-regex\": \"^8.0.0\",\n            \"is-fullwidth-code-point\": \"^3.0.0\",\n            \"strip-ansi\": \"^6.0.0\"\n          }\n        },\n        \"strip-ansi\": {\n          \"version\": \"6.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/strip-ansi/-/strip-ansi-6.0.0.tgz\",\n          \"integrity\": \"sha1-CxVx3XZpzNTz4G4U7x7tJiJa5TI=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-regex\": \"^5.0.0\"\n          }\n        }\n      }\n    },\n    \"worker-farm\": {\n      \"version\": \"1.7.0\",\n      \"resolved\": \"https://npm.quanziapp.com/worker-farm/-/worker-farm-1.7.0.tgz\",\n      \"integrity\": \"sha1-JqlMU5G7ypJhUgAvabhKS/dy5ag=\",\n      \"dev\": true,\n      \"requires\": {\n        \"errno\": \"~0.1.7\"\n      }\n    },\n    \"wrap-ansi\": {\n      \"version\": \"5.1.0\",\n      \"resolved\": \"https://npm.quanziapp.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz\",\n      \"integrity\": \"sha1-H9H2cjXVttD+54EFYAG/tpTAOwk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-styles\": \"^3.2.0\",\n        \"string-width\": \"^3.0.0\",\n        \"strip-ansi\": \"^5.0.0\"\n      },\n      \"dependencies\": {\n        \"ansi-regex\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://npm.quanziapp.com/ansi-regex/-/ansi-regex-4.1.0.tgz\",\n          \"integrity\": \"sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=\",\n          \"dev\": true\n        },\n        \"strip-ansi\": {\n          \"version\": \"5.2.0\",\n          \"resolved\": \"https://npm.quanziapp.com/strip-ansi/-/strip-ansi-5.2.0.tgz\",\n          \"integrity\": \"sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-regex\": \"^4.1.0\"\n          }\n        }\n      }\n    },\n    \"wrappy\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/wrappy/-/wrappy-1.0.2.tgz\",\n      \"integrity\": \"sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=\",\n      \"dev\": true\n    },\n    \"write-file-atomic\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://npm.quanziapp.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz\",\n      \"integrity\": \"sha1-Vr1cWlxwSBzRnFcb05q5ZaXeVug=\",\n      \"dev\": true,\n      \"requires\": {\n        \"imurmurhash\": \"^0.1.4\",\n        \"is-typedarray\": \"^1.0.0\",\n        \"signal-exit\": \"^3.0.2\",\n        \"typedarray-to-buffer\": \"^3.1.5\"\n      }\n    },\n    \"ws\": {\n      \"version\": \"6.2.1\",\n      \"resolved\": \"https://npm.quanziapp.com/ws/-/ws-6.2.1.tgz\",\n      \"integrity\": \"sha1-RC/fCkftZPWbal2P8TD0dI7VJPs=\",\n      \"dev\": true,\n      \"requires\": {\n        \"async-limiter\": \"~1.0.0\"\n      }\n    },\n    \"xdg-basedir\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://npm.quanziapp.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz\",\n      \"integrity\": \"sha1-S8jZmEQDaWIl74OhVzy7y0552xM=\",\n      \"dev\": true\n    },\n    \"xtend\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://npm.quanziapp.com/xtend/-/xtend-4.0.2.tgz\",\n      \"integrity\": \"sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q=\",\n      \"dev\": true\n    },\n    \"y18n\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://npm.quanziapp.com/y18n/-/y18n-4.0.1.tgz\",\n      \"integrity\": \"sha1-jbK4PDHF11CZu4kLI/MJSJHiR9Q=\",\n      \"dev\": true\n    },\n    \"yallist\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://npm.quanziapp.com/yallist/-/yallist-3.1.1.tgz\",\n      \"integrity\": \"sha1-27fa+b/YusmrRev2ArjLrQ1dCP0=\",\n      \"dev\": true\n    },\n    \"yargs\": {\n      \"version\": \"13.3.2\",\n      \"resolved\": \"https://npm.quanziapp.com/yargs/-/yargs-13.3.2.tgz\",\n      \"integrity\": \"sha1-rX/+/sGqWVZayRX4Lcyzipwxot0=\",\n      \"dev\": true,\n      \"requires\": {\n        \"cliui\": \"^5.0.0\",\n        \"find-up\": \"^3.0.0\",\n        \"get-caller-file\": \"^2.0.1\",\n        \"require-directory\": \"^2.1.1\",\n        \"require-main-filename\": \"^2.0.0\",\n        \"set-blocking\": \"^2.0.0\",\n        \"string-width\": \"^3.0.0\",\n        \"which-module\": \"^2.0.0\",\n        \"y18n\": \"^4.0.0\",\n        \"yargs-parser\": \"^13.1.2\"\n      },\n      \"dependencies\": {\n        \"find-up\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/find-up/-/find-up-3.0.0.tgz\",\n          \"integrity\": \"sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=\",\n          \"dev\": true,\n          \"requires\": {\n            \"locate-path\": \"^3.0.0\"\n          }\n        },\n        \"locate-path\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/locate-path/-/locate-path-3.0.0.tgz\",\n          \"integrity\": \"sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-locate\": \"^3.0.0\",\n            \"path-exists\": \"^3.0.0\"\n          }\n        },\n        \"p-locate\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/p-locate/-/p-locate-3.0.0.tgz\",\n          \"integrity\": \"sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-limit\": \"^2.0.0\"\n          }\n        },\n        \"path-exists\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://npm.quanziapp.com/path-exists/-/path-exists-3.0.0.tgz\",\n          \"integrity\": \"sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"yargs-parser\": {\n      \"version\": \"13.1.2\",\n      \"resolved\": \"https://npm.quanziapp.com/yargs-parser/-/yargs-parser-13.1.2.tgz\",\n      \"integrity\": \"sha1-Ew8JcC667vJlDVTObj5XBvek+zg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"camelcase\": \"^5.0.0\",\n        \"decamelize\": \"^1.2.0\"\n      },\n      \"dependencies\": {\n        \"camelcase\": {\n          \"version\": \"5.3.1\",\n          \"resolved\": \"https://npm.quanziapp.com/camelcase/-/camelcase-5.3.1.tgz\",\n          \"integrity\": \"sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"zepto\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://npm.quanziapp.com/zepto/-/zepto-1.2.0.tgz\",\n      \"integrity\": \"sha1-4Se9nmb9hGvl6rSME5SIL3wOT5g=\",\n      \"dev\": true\n    }\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"web-interview\",\n  \"version\": \"1.0.0\",\n  \"description\": \"web面试题库\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"dev\": \"vuepress dev docs\",\n    \"build\": \"vuepress build docs\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/febobo/web-interview.git\"\n  },\n  \"keywords\": [\n    \"web\",\n    \"interview\"\n  ],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/febobo/web-interview/issues\"\n  },\n  \"homepage\": \"https://github.com/febobo/web-interview#readme\",\n  \"devDependencies\": {\n    \"vuepress\": \"^1.7.1\"\n  }\n}\n"
  }
]