{"id":28966168,"url":"https://github.com/hblvsjtu/effectivefe-engineering","last_synced_at":"2026-01-31T07:03:53.324Z","repository":{"id":125647204,"uuid":"207056506","full_name":"hblvsjtu/EffectiveFE-Engineering","owner":"hblvsjtu","description":"工作了一段时间后，发现自己在代码高效化和工程化方面欠债太多，所以想记录和总结用以提升效率的最佳实践^_ ^","archived":false,"fork":false,"pushed_at":"2020-11-01T02:02:09.000Z","size":1639,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-24T07:11:28.588Z","etag":null,"topics":["effective","engineering","front-end"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hblvsjtu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-09-08T03:28:52.000Z","updated_at":"2023-05-15T07:13:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"60100910-3b95-423c-bfd4-7158d079c3bd","html_url":"https://github.com/hblvsjtu/EffectiveFE-Engineering","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hblvsjtu/EffectiveFE-Engineering","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hblvsjtu%2FEffectiveFE-Engineering","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hblvsjtu%2FEffectiveFE-Engineering/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hblvsjtu%2FEffectiveFE-Engineering/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hblvsjtu%2FEffectiveFE-Engineering/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hblvsjtu","download_url":"https://codeload.github.com/hblvsjtu/EffectiveFE-Engineering/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hblvsjtu%2FEffectiveFE-Engineering/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28932604,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T04:05:25.756Z","status":"ssl_error","status_checked_at":"2026-01-31T04:02:35.005Z","response_time":128,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["effective","engineering","front-end"],"created_at":"2025-06-24T07:10:59.658Z","updated_at":"2026-01-31T07:03:53.314Z","avatar_url":"https://github.com/hblvsjtu.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EffectiveFE-Engineering\n\n## 作者：冰红茶\n\n---\n\n工作了一段时间后，发现自己在代码高效化和工程化方面欠债太多，所以想记录和总结用以提升效率的最佳实践^\\_ ^\n\n---\n\n## 参考书籍：\n\n### 高效前端：《Web 高效编程与优化实践》作者：李银城\n\n### 《前端工程化 体系设计与实践》作者：周俊鹏\n\n---\n\n- [EffectiveFE-Engineering](#effectivefe-engineering)\n  - [作者：冰红茶](#作者冰红茶)\n  - [参考书籍：](#参考书籍)\n    - [高效前端：《Web 高效编程与优化实践》作者：李银城](#高效前端web-高效编程与优化实践作者李银城)\n    - [《前端工程化 体系设计与实践》作者：周俊鹏](#前端工程化-体系设计与实践作者周俊鹏)\n  - [一、HTML/CSS 优化](#一htmlcss-优化)\n    - [遵循几条大原则：](#遵循几条大原则)\n    - [1.1 巧用伪类](#11-巧用伪类)\n      - [1) hover](#1-hover)\n      - [2) checked](#2-checked)\n      - [3) 前向伪类选择器 nth-last-of-type(n)](#3-前向伪类选择器-nth-last-of-typen)\n    - [1.2 HTML 标签](#12-html-标签)\n      - [1) 画一个三角形](#1-画一个三角形)\n      - [2) 尽可能使用伪元素](#2-尽可能使用伪元素)\n  - [二、js 优化](#二js-优化)\n    - [2.1 几个原则和模式](#21-几个原则和模式)\n      - [1) 避免使用全局变量](#1-避免使用全局变量)\n      - [2) 改变样式](#2-改变样式)\n      - [3) 避免使用重复代码](#3-避免使用重复代码)\n      - [3) 访问者模式](#3-访问者模式)\n      - [4) 不要滥用闭包](#4-不要滥用闭包)\n    - [2.2 其他优化策略](#22-其他优化策略)\n      - [1) 其他优化策略](#1-其他优化策略)\n      - [1) Array方法](#1-array方法)\n  - [三、Vue](#三vue)\n    - [3.1 八股文](#31-八股文)\n      - [1) ``compute`` 和 ``watch``有什么区别](#1-compute-和-watch有什么区别)\n      - [2) diff 算法](#2-diff-算法)\n      - [3) 生命周期](#3-生命周期)\n      - [4) 双向绑定](#4-双向绑定)\n      - [5) 预编译](#5-预编译)\n      - [6) 组件间通讯](#6-组件间通讯)\n      - [7) 指令](#7-指令)\n      - [8) 自定义指令](#8-自定义指令)\n      - [9) 事件修饰符](#9-事件修饰符)\n      - [10) 混入 mixins](#10-混入-mixins)\n      - [11) 自定义插件](#11-自定义插件)\n      - [12) 过滤器](#12-过滤器)\n      - [13) nextTick 与更新循环](#13-nexttick-与更新循环)\n      - [13) vue-loader 是什么](#13-vue-loader-是什么)\n    - [3.2 性能优化](#32-性能优化)\n      - [1) 在 map 循环中添加不同的 key 值，就地复用](#1-在-map-循环中添加不同的-key-值就地复用)\n      - [2) 对于不变的对象使用 Object.freeze](#2-对于不变的对象使用-objectfreeze)\n      - [3) ` v-cloak`解决页面闪烁问题](#3-v-cloak解决页面闪烁问题)\n      - [4) `v-once` 和 `v-pre` 提升性能](#4-v-once-和-v-pre-提升性能)\n      - [5) 使用函数式组件](#5-使用函数式组件)\n    - [3.3 原则与规范](#33-原则与规范)\n      - [1) 数据与视图分离](#1-数据与视图分离)\n    - [3.4 小技巧](#34-小技巧)\n      - [1) 父子组件透传](#1-父子组件透传)\n      - [2) 作用域插槽](#2-作用域插槽)\n      - [3) 动态指令参数](#3-动态指令参数)\n      - [4) `hookEvent `的使用](#4-hookevent-的使用)\n      - [5) `watch`](#5-watch)\n      - [6) 渲染函数中使用 JSX](#6-渲染函数中使用-jsx)\n    - [3.5 vue3.0 的特点](#35-vue30-的特点)\n      - [1) 性能比 2.0 快 1.3~2 倍](#1-性能比-20-快-132-倍)\n      - [2) 使用`typescript`重构](#2-使用typescript重构)\n      - [3) `Tree shaking support`](#3-tree-shaking-support)\n      - [4) `Composition API`](#4-composition-api)\n      - [5) 自定义渲染 API `Custom Renderer API`](#5-自定义渲染-api-custom-renderer-api)\n      - [6) 更先进的组件](#6-更先进的组件)\n      - [7) `v-model`统一双向数据流，删除`.sync`](#7-v-model统一双向数据流删除sync)\n      - [8) `v-if`、`v-for`优先级问题](#8-v-ifv-for优先级问题)\n      - [9) 去掉`functional: true`](#9-去掉functional-true)\n      - [10) vue 文件结构](#10-vue-文件结构)\n      - [11) `Teleport` 传送门](#11-teleport-传送门)\n      - [12) Fragments](#12-fragments)\n  - [四、React](#四react)\n    - [4.1 八股文](#41-八股文)\n      - [1) 单向数据流](#1-单向数据流)\n      - [2) `setState`是同步还是异步](#2-setstate是同步还是异步)\n      - [3) 通讯](#3-通讯)\n      - [4) 为什么使用框架而不是原生](#4-为什么使用框架而不是原生)\n      - [5) `redux`的`middleware`机制](#5-redux的middleware机制)\n      - [6) thunk](#6-thunk)\n      - [7) react-redux](#7-react-redux)\n      - [8) 组件/逻辑复用以及各自优缺点](#8-组件逻辑复用以及各自优缺点)\n      - [9) `HOC`的理解](#9-hoc的理解)\n      - [9) `React.forwardRef`](#9-reactforwardref)\n      - [10) `fiber`如何理解](#10-fiber如何理解)\n      - [11) 生命周期](#11-生命周期)\n    - [4.2 性能优化](#42-性能优化)\n    - [4.3 原则与规范](#43-原则与规范)\n    - [4.4 小技巧](#44-小技巧)\n      - [1) `Portal`](#1-portal)\n      - [2) Fragment](#2-fragment)\n      - [3) StrictMode](#3-strictmode)\n  - [五、webpack](#五webpack)\n    - [5.1 八股文](#51-八股文)\n      - [1) 相关概念](#1-相关概念)\n      - [2) 构建过程](#2-构建过程)\n      - [3) 配置属性](#3-配置属性)\n      - [4) sourceMap](#4-sourcemap)\n    - [5.2 构建速度优化](#52-构建速度优化)\n      - [1) 多线程压缩](#1-多线程压缩)\n      - [2) DLLPlugin预编译](#2-dllplugin预编译)\n      - [3) 开启缓存](#3-开启缓存)\n      - [4) 缩小构建目标](#4-缩小构建目标)\n    - [5.3 优化使用体验](#53-优化使用体验)\n      - [1) 监听文件自动刷新 watch](#1-监听文件自动刷新-watch)\n      - [2) 开启模块热更新](#2-开启模块热更新)\n    - [5.4 优化输出质量](#54-优化输出质量)\n      - [1) 区分环境](#1-区分环境)\n      - [2) 压缩代码](#2-压缩代码)\n      - [3) 使用tree shaking](#3-使用tree-shaking)\n      - [4) 提取公共代码](#4-提取公共代码)\n      - [5) 分割代码按需加载](#5-分割代码按需加载)\n      - [6) Scope Hoisting](#6-scope-hoisting)\n      - [7) 输出分析](#7-输出分析)\n  - [六、Axios](#六axios)\n    - [6.1 八股文](#61-八股文)\n      - [1) 相关概念](#1-相关概念-1)\n      - [2) 拦截器](#2-拦截器)\n  - [七、web性能优化](#七web性能优化)\n    - [7.1 css 优化](#71-css-优化)\n      - [1) 概念](#1-概念)\n      - [2) 减少reflow对性能的影响的建议](#2-减少reflow对性能的影响的建议)\n    - [7.2 图片延迟](#72-图片延迟)\n  - [八、lerna](#八lerna)\n    - [8.1 介绍](#81-介绍)\n      - [1) 用于管理多个存在依赖关系的包](#1-用于管理多个存在依赖关系的包)\n      - [2) 目录结构](#2-目录结构)\n      - [2) 基本工作流](#2-基本工作流)\n\n## 一、HTML/CSS 优化\n\n### 遵循几条大原则：\n\n\u003e - 能用 HTML/CSS 优化结束战斗的勿用 JS\n\u003e - 尽量简练\n\n### 1.1 巧用伪类  \n\n#### 1) hover\n\n\u003e - 高亮：hover 与 opacity 配合\n\n```html\n.title:hover { opacity: 0.5; }\n\n\u003ch1 class=\"title\"\u003e你好\u003c/h1\u003e\n```\n\n\u003e - 显示子菜单\n\u003e   \u003e - 这里有一个问题，两个组件需要紧邻着，否则如果存在间隙的话两个组件 hover 的过程变得不连续，显示就会变得失效。\n\u003e   \u003e - 但是实际业务中，需要两个紧邻组件中的是需要缝隙的，这时候可以使用透明伪元素解决问题\n\n```html\nul, li { display: inline-block; margin: 0; padding: 0; } li { margin-right:\n10px; } ul li:last-of-type { margin-right: 0; } .select { display: none; }\n.select::before { display: block; content: ''; height: 10px; opacity: 0; }\n.select:hover { display: block; } .title:hover + .select{ display: block; }\n\n\u003cdiv class=\"title\"\u003e你好\u003c/div\u003e\n\u003cul class=\"select\"\u003e\n  \u003cli\u003e选择1\u003c/li\u003e\n  \u003cli\u003e选择2\u003c/li\u003e\n  \u003cli\u003e选择3\u003c/li\u003e\n  \u003cli\u003e选择4\u003c/li\u003e\n\u003c/ul\u003e\n```\n\n#### 2) checked\n\n\u003e - 修改 radio/checkbox 的样式\n\n```html\n        input[type=\"radio\"] + span {\n            display: inline-block;\n            padding: 3px;\n            width: 6px;\n            height: 6px;\n            border: 1px solid #000;\n            border-radius: 50%;\n            background: transparent;\n            background-clip: content-box;\n            transition: all 0.5s;\n        }\n        input[type=\"radio\"]:checked + span{\n            background: #000;\n            background-clip: content-box;\n        }\n        input[type=\"radio\"]  + span + label {\n            display: inline-block;\n            font-size: 12px;\n        }\n\n        \u003cinput id=\"radio1\" type=\"radio\" name=\"singleSelect\"\u003e\u003c/input\u003e\n        \u003cspan\u003e\u003c/span\u003e\n        \u003clabel for=\"radio1\"\u003e选择1\u003c/label\u003e\n        \u003cinput id=\"radio2\" type=\"radio\" name=\"singleSelect\"\u003e\u003c/input\u003e\n        \u003cspan\u003e\u003c/span\u003e\n        \u003clabel for=\"radio2\"\u003e选择2\u003c/label\u003e\n```\n\n#### 3) 前向伪类选择器 nth-last-of-type(n)\n\n\u003e - 多列宽度自适应\n\n```html\nul, li { display: inline-block; margin: 0; padding: 0; } ul { width: 100%; }\nli:first-of-type:nth-last-of-type(2), li:first-of-type:nth-last-of-type(2) ~ li\n{ width: 50%; } li:first-of-type:nth-last-of-type(3),\nli:first-of-type:nth-last-of-type(3) ~ li { width: 33.3%; }\nli:first-of-type:nth-last-of-type(4), li:first-of-type:nth-last-of-type(4) ~ li\n{ width: 25%; }\n\n\u003cul class=\"select\"\u003e\n  \u003cli\u003e选择1\u003c/li\u003e\n  \u003cli\u003e选择2\u003c/li\u003e\n  \u003cli\u003e选择3\u003c/li\u003e\n  \u003cli\u003e选择4\u003c/li\u003e\n\u003c/ul\u003e\n```\n\n### 1.2 HTML 标签\n\n#### 1) 画一个三角形\n\n\u003e - 利用不同 border 边的透明度\n\n```html\n.triangle { width: 0; height: 0; border-left: 10px solid transparent;\nborder-right: 10px solid transparent; border-bottom: 10px solid red; }\n\n\u003cdiv class=\"triangle\"\u003e\u003c/div\u003e\n```\n\n#### 2) 尽可能使用伪元素\n\n\u003e - 伪元素原生计算值是 inline\n\u003e - 输入框的不可读可以使用伪元素进行覆盖\n\u003e - CSS 计数器 count\n\n```html\n        .counterReset {\n            counter-reset: fruit 1;\n        }\n        .counterReset input:checked {\n            counter-increment: fruit;\n        }\n        .total::after {\n            content: counter(fruit);\n            font-size: 14px;\n            color: red;\n        }\n\n        \u003cdiv class=\"counterReset\"\u003e\n            \u003clabel\u003e\u003cinput type=\"checkbox\"\u003e\u003c/input\u003e香蕉\u003c/label\u003e\n            \u003clabel\u003e\u003cinput type=\"checkbox\"\u003e\u003c/input\u003e苹果\u003c/label\u003e\n        \u003c/div\u003e\n        \u003cp\u003e你选择了\u003cspan class=\"total\"\u003e\u003c/span\u003e个水果\u003c/p\u003e\n```\n\n---\n\n## 二、js 优化\n\n### 2.1 几个原则和模式  \n\n#### 1) 避免使用全局变量\n\n\u003e -\n\n#### 2) 改变样式\n\n\u003e - 常见的方法是直接使用 getComputedStyle, 添加内联 style 的方式，但是这种方式不好，每次都要添加多个样式，而且不能复用，最佳实践是先把需要实现的样式用 class 实现，然后再用 JS addClass 的方式进行实现\n\n#### 3) 避免使用重复代码\n\n\u003e - 重复代码 -\u003e 封装成函数 -\u003e 封装成模块 -\u003e 封装成库 -\u003e 封装成 SDK\n\u003e - 使用策略模式有利于高内聚低耦合，也能体现开闭原则（即对拓展是开放的，对修改是封闭的）\n\n```js\n        model: {\n            low: function() {\n                // low speed\n            },\n            middle: function() {\n                // middle speed\n            },\n            high: function() {\n                // high speed\n            }\n        }\n        use(model['middle']);\n```\n\n#### 3) 访问者模式\n\n\u003e -\n\n```js\nfunction vistor() {}\nvistor.prototype.eventName = [];\nvistor.prototype.registry = {};\nvistor.prototype.on = function () {\n  this.eventName.push(arguments[0]);\n  this.registry[arguments[0]] = arguments[1];\n};\nvistor.prototype.emit = function () {\n  let eventName = arguments[0];\n  let a = Array.from(arguments);\n  a.shift();\n  this.registry[eventName](...a);\n};\n```\n\n#### 4) 不要滥用闭包\n\n\u003e - 闭包的作用是可以使子作用域访问父作用域的变量，同时不用闭包内的变量不可见。\n\u003e - 子作用域访问上层的作用域需要花费较多的时间，做好直接把父作用域的变量作为函数的参数传进去\n\n### 2.2 其他优化策略  \n\n#### 1) 其他优化策略\n\n\u003e - 使用三目运算符\n\u003e - 不要出现魔数，即函数的参数含义不明显，可以先在函数前面把参数重新定义一下名称再传进去\n\u003e - Object.assign()合并对象\n\u003e - 减少使用 forEach，map 等遍历函数，多使用 includes(), filter(), find()等数组方法\n\u003e - 使用 async/await 替代 promise 和 callback hell, 对于一些 callback hell 可以先包装成 promise 再使用 async/await\n\n\u003ch3 id='2.3'\u003e2.3 lodash的使用\u003c/h3\u003e  \n        \n#### 1) Array方法\n\u003e - chunk(array, [size=1]) 根据数量分割数组\n\u003e - difference(array, [values]) 筛选不相同的元素\n\u003e - 升级版 加了一个迭代器 differenceBy(array, [values], [iteratee=_.identity])\n\u003e \n\u003e - 不要出现魔数，即函数的参数含义不明显，可以先在函数前面把参数重新定义一下名称再传进去\n\u003e - Object.assign()合并对象\n\u003e - 减少使用forEach，map等遍历函数，多使用includes(), filter(), find()等数组方法\n\u003e - 使用async/await 替代promise和callback hell, 对于一些callback hell可以先包装成promise再使用async/await\n        \n--------        \n     \n## 三、Vue\n        \n### 3.1 八股文\n#### 1) ``compute`` 和 ``watch``有什么区别\n项目|``compute``|``watch``\n-|-|-\n异步|不支持|支持\n缓存|支持|不支持\n流|*一个数据 \u003c- 多个数据*|*行为 \u003c- 一个数据*\n属性|``get``(默认)和``set``|``handler``、``immediate``、``deep``\n参数|无|``curVal``、``prevVal``\n\n注意：当依赖的属性变化时，computed 不会立即重新计算生成新的值，而是先标记为脏数据，当下次 computed 被获取时候，才会进行重新计算并返回。\n\n#### 2) diff 算法\n\n\u003e - 是否是相同的节点，如果节点不同(key 和 sel 节点的选择器)，直接替换\n\u003e - 如果节点相同，分析子节点的 5 种情况，进行不同的处理\n\u003e   \u003e - `oldVnode === vnode`\n\u003e   \u003e - `oldVnode`有子节点`vnode`没有\n\u003e   \u003e - `oldVnode`没有子节点`vnode`有\n\u003e   \u003e - 都有文本节点\n\u003e   \u003e - 都有子节点\n\u003e - 递归处理子节点\n\u003e - 比较时为同层级比较，直接把时间复杂度从 O(3) -\u003e O(1)\n\u003e - 比较的时候是从首尾向中间进行，一旦`StartIdx \u003e EndIdx`表明 oldCh 和 newCh 至少有一个已经遍历完了，就会结束比较。如果有 key，还会从用 key 生成的对象 oldKeyToIdx 中查找匹配的节点，所以为节点设置 key 可以更高效的利用 dom\n\n#### 3) 生命周期\n\n执行链：父`beforeCreate` =\u003e 父`created` =\u003e 父`beforeMount` =\u003e 子`beforeCreate` =\u003e 子`created` =\u003e 子`beforeMount` =\u003e 子`mounted` =\u003e 父`mounted`\n父`beforeUpdate` =\u003e 子`beforeUpdate` =\u003e 子`updated` =\u003e 父`updated`\n\n| 周期            | 执行顺序 | 特点                                                                                                                                                                               |\n| --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `beforeCreate`  | 先父后子 | 可以访问`vm.$parent`和`vm.$createElement`                                                                                                                                          |\n| `created`       | 先父后子 | 可以访问`data`、`props`、`methods`、`computed`、`watch`、`inject`                                                                                                                  |\n| `beforeMount`   | 先父后子 | 获取并可以访问`vm.$el`(el 提供的真实节点)，在这之前 template 模板已导入渲染函数编译。而当前阶段虚拟 Dom 已经创建完成，即将开始渲染。在此时也可以对数据进行更改，不会触发 updated。 |\n| `mounted`       | 先子后父 | `render`函数 -\u003e `vnode` -\u003e 真实节点                                                                                                                                                |\n| `beforeDestory` | 先父后子 |\n| `destoryed`     | 先子后父 | 删除`vm`, 销毁`vm._watcher`，删除数据`observer`中的引用                                                                                                                            |\n| `beforeUpdate`  | 先父后子 |\n| `updated`       | 先子后父 |\n\n#### 4) 双向绑定\n\n\u003e - 观察者模式 一个主题多个观察者\n\n```js\n// 主题，接收状态变化，触发每个观察者\nclass Subject {\n  constructor(state) {\n    this.state = state;\n    this.observers = [];\n  }\n  getState() {\n    return this.state;\n  }\n  setState(state) {\n    this.state = state;\n    this.notifyAllObservers();\n  }\n  attach(observer) {\n    this.observers.push(observer);\n  }\n  notifyAllObservers() {\n    this.observers.forEach((observer) =\u003e {\n      observer.update();\n    });\n  }\n}\n\n// 观察者，等待被触发\nclass Observer {\n  constructor(name, subject) {\n    this.name = name;\n    this.subject = subject;\n    this.subject.attach(this);\n  }\n  update() {\n    console.log(`${this.name} update, state: ${this.subject.getState()}`);\n  }\n}\n\n// 测试代码\nlet s = new Subject();\nlet o1 = new Observer(\"o1\", s);\nlet o2 = new Observer(\"o2\", s);\nlet o3 = new Observer(\"o3\", s);\n\ns.setState(1);\ns.setState(2);\ns.setState(3);\n```\n\n\u003e - 发布订阅者模式\n\n```js\n    var pubsub = (() =\u003e {\n        var topics = {};\n        function on(topic,fn){\n            if (!topics[topic]) topics[topic] = [];\n            topics[topic].push(fn);\n        }\n        function emit(topic,...args){\n            if (!topics[topic]) return;\n            topics[topic].forEach(fn =\u003e fn(...args);\n        }\n        return {\n            on,\n            emit\n        }\n    })()\n```\n\n| 模式           | 特点                                                         |\n| -------------- | ------------------------------------------------------------ |\n| 观察者模式     | 主题和观察者需要相互关联，观察者拥有 update 方法 一对多      |\n| 发布订阅者模式 | 发布者和订阅者不需要直接联系 多对多 比较简单，多作为库来使用 |\n\n\u003e - 对象监听方法\n\n```js\nfunction activeObject(obj) {\n  Object.keys(obj).forEach((key) =\u003e {\n    let val = obj[key];\n    let subject = null,\n      watcher = null;\n    Object.defineProperty(obj, key, {\n      enumerable: true,\n      configurable: true,\n      get: () =\u003e {\n        if (!subject) {\n          subject = new Subject(val);\n          watcher = new Observer(key, subject);\n        }\n        return subject.getState();\n      },\n      set: (value) =\u003e {\n        if (val !== value) {\n          val = value;\n          subject \u0026\u0026 subject.setState(val);\n        }\n      },\n    });\n  });\n}\n```\n\n\u003e - 数组窃听方法\n\n```js\nconst methods = [\"push\", \"pop\"];\n\nfunction activeArray(obj) {\n  const wrapArrayPrototype = Object.create(Array.prototype);\n  subject = new Subject(obj);\n  watcher = new Observer(obj, subject);\n  methods.forEach((method) =\u003e {\n    wrapArrayPrototype[method] = function (...args) {\n      const result = Array.prototype[method].call(this, ...args);\n      subject.setState(result);\n      return result;\n    };\n  });\n  obj.__proto__ = wrapArrayPrototype;\n}\n```\n\n\u003e - 综合\n\n```js\nfunction activeData(obj) {\n  const type = Object.prototype.toString.call(obj).slice(8, -1);\n  if (type === \"Object\") {\n    activeObject(obj);\n    Object.values(obj).forEach((child) =\u003e activeData(child));\n  } else if (type === \"Array\") {\n    activeArray(obj);\n    obj.forEach((child) =\u003e activeData(child));\n  }\n}\n```\n\n\u003e - Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁，主要做的事情是:\n\u003e   \u003e - 实例化时往主题 subject 里面添加自己\n\u003e   \u003e - 必须有一个 update()方法\n\u003e   \u003e - 待属性变动 subject.notice()通知时，能调用自身的 update()方法，并触发 Compile 中绑定的回调。\n\u003e - 缺点：无法监听对象的属性的创建和删除，可以使用`this.$set`\n\n#### 5) 预编译\n\n\u003e - render 函数 \u003e templates 模板 \u003e el 属性挂载元素 outerHTML\n\u003e - 在包含单文件组件的项目中，使用 webpack 打包时已经将单文件组件中的模板预先编译成了渲染函数\n\u003e - 也存在实例化 vue 但是没有 render、templates、el 的情况，就是使用 vue 作为 eventbus 使用时\n\u003e - 编译时 先转化为 AST 树，在转化为渲染函数，最后返回 Vnode 节点\n\n| 构建模式   | 运行时机                                       | webpack 配置                                                | 特点                                                          |\n| ---------- | ---------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------- |\n| 运行时构建 | vue 实例化创建节点且存在 render 函数属性时     | 默认或者`alias: {'vue$': 'vue/dist/vue.runtime.common.js'}` | 删除了模板的编译功能，无法支持带`template`属性的 Vue 实例选项 |\n| 独立构建   | vue 实例化创建节点并且不存在 render 函数属性时 | `alias: {'vue$': 'vue/dist/vue.common.js'}`                 | 需要完整的模板编译功能                                        |\n\n#### 6) 组件间通讯\n\n| 对象                             | 方法                                                                                                      |\n| -------------------------------- | --------------------------------------------------------------------------------------------------------- |\n| 父子                             | props 和\\$emit                                                                                            |\n| 多层嵌套                         | `provide`和`inject` 或者`eventbus`（` = new vue()`）                                                      |\n| 状态共享`Vue.observable`         | `const store = Vue.observable({ count: 0 }); const mutations = {setCount(count) {store.count = count;}};` |\n| vue 实例(`$on `和 `$emit`)       | `vue.$on` `vue.$emit` `vue.$off`                                                                          |\n| 其他`$ref`/`$parent`/`$children` | `this.$refs.list.getList()`                                                                               |\n\n#### 7) 指令\n\n| 名称         | 正常写法                    | 缩写     | 特点                                                                                                                                                                                                                                                |\n| ------------ | --------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 组件数据绑定 | `v-bind:props`              | `:props` |\n| 插槽         | `v-slot:name`               | `#name`  | 获取插槽作用域 `v-slot:name=\"scope\"`                                                                                                                                                                                                                |\n| 方法绑定     | `v-on:func`                 | `@func`  | 获取额外参数和子组件通讯参数 `@callback=handleChange(index, $event)`                                                                                                                                                                                |\n| 双向绑定     | `v-model`                   | -        | 语法糖，等同于 `\u003cChild :value=\"value\" @input=\"handleInputValue\"\u003e\u003c/Child\u003e` 子组件必须 emit input 事件：`props: {value: Number} $emit('input', value) `，当然了，你也可以手动修改参数名和方法名，使用`model`字段: `{prop: 'checked',event: 'change'}` |\n| 只渲染一次   | v-once                      | -        | -                                                                                                                                                                                                                                                   |\n| 循环         | v-for                       | -        |\n| 判断         | `v-if` `v-else-if` `v-else` | -        | 根据表达式的值的真假条件，销毁或重建元素                                                                                                                                                                                                            |\n| 是否显示     | `v-show`                    | -        | 根据表达式之真假值，切换元素的 `display` CSS 属性节点还在文档中                                                                                                                                                                                     |\n| innerHTML    | `v-html`                    | -        | 更新元素的 `innerHTML`                                                                                                                                                                                                                              |\n| textContent  | `v-text`                    | -        | 更新元素的 `textContent`                                                                                                                                                                                                                            |\n\n#### 8) 自定义指令\n\n```js\n// 入口\nimport Auth from './utils/auth';\nVue.use(Auth);\n\n// auth.js 提供给install方法\nconst AUTH_LIST = ['admin']\n\nfunction checkAuth(auths) {\n    return AUTH_LIST.some(item =\u003e auths.includes(item))\n}\n\nfunction install(Vue, options = {}) {\n    Vue.directive('auth', {\n        componentUpdated(el, binding) {\n            if (!checkAuth(binding.value)) {\n                el.parentNode \u0026\u0026 el.parentNode.removeChild(el)\n            }\n        }\n        // bind：只调用一次，指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。\n\n        // inserted：被绑定元素插入父节点时调用 (仅保证父节点存在，但不一定已被插入文档中)。\n\n        // update：所在组件的 VNode 更新时调用，但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变，也可能没有。\n\n        // componentUpdated：指令所在组件的 VNode 及其子 VNode 全部更新后调用。\n\n        // unbind：只调用一次，指令与元素解绑时调用。\n    })\n}\n\nexport default { install }\n\n// 组件使用时\n\u003cbutton v-auth=\"['user']\"\u003e提交\u003c/button\u003e\n```\n\n#### 9) 事件修饰符\n\n| 名称                                  | 特点                                                                                                                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| .stop                                 | 阻止事件冒泡                                                                                                                                                                               |\n| .capture                              | 使用事件捕获模式                                                                                                                                                                           |\n| .prevent                              | 阻止默认事件                                                                                                                                                                               |\n| .self                                 | 事件只在自己身上发生时才触发，如果触发其他元素通过冒泡或者捕获等方式不会被触发，当自身触发后依然会往外进行冒泡                                                                             |\n| .once                                 | 事件只发生一次                                                                                                                                                                             |\n| .sync                                 | 数据双向绑定，父组件`\u003cChild :value=\"total\" v-on:update:change=\"total = $event\"/\u003e`子组件`$emit('update:change', value)`                                                                     |\n| 表单修饰符`.lazy`, `.trim`, `.number` | 配合 v-model 使用, `.number`如果输入的第一个字符是数字，那就只能输入数字，否则他输入的就是普通字符串。                                                                                     |\n| .passive                              | 当页面滚动的时候就会一直触发 onScroll 事件，这个其实是存在性能问题的，尤其是在移动端，当给他加上 .passive 后触发的就不会那么频繁了。                                                       |\n| 鼠标按钮修饰符                        | ：鼠标左键点击；`.right`：鼠标右键点击；`.middle`：鼠标中键点击；                                                                                                                          |\n| 键盘按键修饰符                        | `.enter` `.tab` `.delete` (捕获“删除”和“退格”键) `.esc` `.space` `.up` `.down` `.left` `.right`,`.exact `修饰符允许你控制由精确的系统修饰符组合触发的事件。                                |\n| 串联事件修饰符                        | 串联使用事件修饰符的时候，需要注意其顺序，同样 2 个修饰符进行串联使用，顺序不同，结果大不一样。@click.prevent.self 会阻止所有的点击事件，而 @click.self.prevent 只会阻止对自身元素的点击。 |\n\n#### 10) 混入 mixins\n\n\u003e - 混入的先被执行，组件数据部分后执行，如果有重复属性以组件数据为准\n\n#### 11) 自定义插件\n\n```js\n        MyPlugin.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#### 12) 过滤器\n\n```html\n\u003c!-- 在双花括号中 --\u003e\n{{ message | filterA | filterB }}\n\n\u003c!-- 在 `v-bind` 中 --\u003e\n\u003cdiv v-bind:id=\"rawId | formatId\"\u003e\u003c/div\u003e\n```\n\n```js\n    // 局部\n    filters: {\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    Vue.filter('capitalize', function (value) {\n        if (!value) return ''\n        value = value.toString()\n        return value.charAt(0).toUpperCase() + value.slice(1)\n    })\n\n    new Vue({\n        // ...\n    })\n```\n\n#### 13) nextTick 与更新循环\n\n\u003e - 在 Vue 更新数据的时候，视图不会立即更新，因为在数据更新过程中同一变量可能被修改多次，所以会有一个批处理的过程，保留最后一次修改变量的结果，并把最终结果更新视图。\n\u003e - 步骤\n\u003e   \u003e - 同步修改数据, Vue 开启一个异步队列，并缓冲在此事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发，只会被推入到队列中一次\n\u003e   \u003e - 查找异步队列，推入执行栈，执行 callback[事件循环]并更新视图, （`promise.then`或者 HTML5 的`MutationObserver`，如果环境不支持就使用`setTimeout(fn, 0)`）\n\u003e   \u003e - nextTick 拿到更新后视图，在同一事件循环中，如果存在多个 nextTick，将会按最初的执行顺序进行调用；\n\u003e - 官方文档说明：注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕，可以用 vm.\\$nextTick\n\n```js\n        mounted: function () {\n            this.$nextTick(function () {\n                // Code that will run only after the\n                // entire view has been rendered\n            })\n        }\n```\n\n#### 13) vue-loader 是什么\n\n\u003e - vue 文件的一个加载器，跟 template/js/style 转换成 js 模块。\n\n### 3.2 性能优化\n\n#### 1) 在 map 循环中添加不同的 key 值，就地复用\n\n#### 2) 对于不变的对象使用 Object.freeze\n\n#### 3) ` v-cloak`解决页面闪烁问题\n\n\u003e - v-cloak 指令保持在元素上直到关联实例结束编译，利用它的特性，结合 CSS 的规则 `[v-cloak] { display: none }` 一起使用就可以隐藏掉未编译好的 Mustache 标签，直到实例准备完毕，但是个人认为加个 loading 体验会更好\n\n```html\n// template 中\n\u003cdiv class=\"#app\" v-cloak\u003e\n  \u003cp\u003e{{value.name}}\u003c/p\u003e\n\u003c/div\u003e\n\n// css 中 [v-cloak] { display: none; }\n```\n\n#### 4) `v-once` 和 `v-pre` 提升性能\n\n\u003e - `v-pre` 给我们去决定要不要跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。\n\u003e - `v-once` 只会渲染一次，后面的重新渲染都会被跳过\n\n#### 5) 使用函数式组件\n\n\u003e - 无状态，无数据响应，无生命周期，没有 instance 实例, 只会根据传进来的 props 进行数据渲染，基本的骨架如下\n\n```js\nVue.component(\"my-component\", {\n  functional: true, // 必要\n  // Props 是可选的\n  props: {\n    // ...\n  },\n  // 为了弥补缺少的实例\n  // 提供第二个参数作为上下文\n  render(createElement, context) {\n    return createElement(\"div\", context.data, [\n      context.scopedSlots.default({\n        a: 1, // 作为插槽的作用域参数\n      }),\n    ]);\n  },\n});\n```\n\n// 或者\n\n```html\n\u003ctemplate functional\u003e\n  \u003cbutton class=\"btn btn-primary\" v-bind=\"data.attrs\" v-on=\"listeners\"\u003e\n    \u003cp v-for=\"item in props.items\" @click=\"props.handleClick(item);\"\u003e\n      {{ item }}\n    \u003c/p\u003e\n    \u003cslot /\u003e\n  \u003c/button\u003e\n\u003c/template\u003e\n\u003cscript\u003e\n  export default {\n    props: [\"level\"],\n  };\n\u003c/script\u003e\n```\n\n```js\n// 或者 https://juejin.im/post/6872128694639394830\n// 根据不同的情况渲染不同的组件\nvar EmptyList = {\n  /* ... */\n};\nvar TableList = {\n  /* ... */\n};\nvar OrderedList = {\n  /* ... */\n};\nvar UnorderedList = {\n  /* ... */\n};\n\nVue.component(\"smart-list\", {\n  functional: true, // 声明 functional: true，表明它是一个函数式组件\n  props: {\n    items: {\n      type: Array,\n      required: true,\n    },\n    isOrdered: Boolean,\n  },\n  // 为了弥补缺少的实例\n  // 提供第二个参数作为上下文\n  render: function (createElement, context) {\n    // 组件中所有的一切都是通过 context 传递的\n    // 根据不同的情况渲染不同的组件\n    function appropriateListComponent() {\n      var items = context.props.items;\n\n      if (items.length === 0) return EmptyList;\n      if (typeof items[0] === \"object\") return TableList;\n      if (context.props.isOrdered) return OrderedList;\n\n      return UnorderedList;\n    }\n\n    return createElement(\n      appropriateListComponent(),\n      context.data, // 传递给组件的整个数据对象\n      context.children // `VNode` 子节点的数组\n    );\n  },\n});\n```\n\n### 3.3 原则与规范\n\n#### 1) 数据与视图分离\n\n### 3.4 小技巧\n\n#### 1) 父子组件透传\n\n\u003e - 属性透传`v-bind=\"$props\"`或者`v-bind=\"$attrs\"`\n\n```html\n\u003ctemplate\u003e\n  \u003cchild-component v-bind=\"$props\" /\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\n  import ChildComponent from \"@/components/ChildComponent\";\n\n  export default {\n    props: {\n      // 注意这里的校验props\n      ...ChildComponent.options.props,\n    },\n  };\n\u003c/script\u003e\n```\n\n\u003e - 对象透传 也可传递某一特定对象的属性，与`provide`和`inject`的区别：`provide`和`inject`绑定并不是可响应的\n\n```html\n\u003c!-- obj = {name: '', id: ''} --\u003e\n\u003cChild v-bind=\"obj\"\u003e\u003c/Child\u003e\n\u003c!-- 等价于 --\u003e\n\u003cChild :name=\"obj.name\" :id=\"obj.id\"\u003e\u003c/Child\u003e\n```\n\n\u003e - 事件监听透传 `v-bind=\"$listeners\"` 但不包括.native 修饰器的\n\n#### 2) 作用域插槽\n\n```html\n    \u003c!-- 子组件 --\u003e\n    \u003cdiv\u003e\n        \u003cslot name=\"head\" :id=\"id\"\u003e\u003cslot\u003e\n        \u003cslot name=\"footer\" :item=\"item\"\u003e\u003cslot\u003e\n    \u003c/div\u003e\n\n    \u003c!-- 父组件 --\u003e\n    \u003cchild\u003e\n        \u003ctemplate v-slot:head=\"scope\"\u003e{{scope.id}}\u003ctemplate\u003e\n        \u003ctemplate v-slot:footer=\"{item}\"\u003e{{item}}\u003ctemplate\u003e\n    \u003c/child\u003e\n```\n\n#### 3) 动态指令参数\n\n\u003e - `\u003cdiv @[event]=\"handleChange\"\u003e\u003c/div\u003e`\n\n#### 4) `hookEvent `的使用\n\n\u003e - 可以在模板中监听子组件的生命周期钩子，好处是可以不破坏第三方的源码的同时监听其生命周期\n\u003e - `\u003cThirdPart @hook:updated=\"handleUpdated\"\u003e\u003c/ThirdPart\u003e`\n\u003e - 也可以使用`vm.$on('hooks:beforeDestory', cb)` 或者 `vm.$once('hooks:beforeDestory', cb)`，可以使代码的可读性更好\n\n#### 5) `watch`\n\n\u003e - watch 有一个特点，初始化变量的是时候是不会执行回调的，可以使用`immediate: true`\n\u003e - `deep: true``可以进行深度监听，但有时 ☝🏻 监听某一层，可以这样写\n\n```js\n        watch: {\n            'obj.a': {\n                handler(newVal, oldVal) {\n                },\n            }\n        }\n```\n\n#### 6) 渲染函数中使用 JSX\n\n### 3.5 vue3.0 的特点\n\n#### 1) 性能比 2.0 快 1.3~2 倍\n\n\u003e - diff 算法优化\n\u003e   \u003e - vue2.0 的 VNode 比较是全量的，vue3.0 只比较 PatchFlag 标记标记节点，静态节点不比较\n\u003e   \u003e - cachehandlers 事件侦听缓存 vue2.0 的事件绑定是动态的，每次都会重新创建，vue3.0 会缓存不变的事件\n\n#### 2) 使用`typescript`重构\n\n#### 3) `Tree shaking support`\n\n#### 4) `Composition API`\n\n#### 5) 自定义渲染 API `Custom Renderer API`\n\n#### 6) 更先进的组件\n\n\u003e - `Fragment` `Teleport(Protal)` `Suspense`\n\n#### 7) `v-model`统一双向数据流，删除`.sync`\n\n#### 8) `v-if`、`v-for`优先级问题\n\n\u003e - 在 2.x 是`v-for`优先级高，在 3.0 中`v-if`的优先级高\n\n#### 9) 去掉`functional: true`\n\n```js\nimport { h } from \"vue\";\n\nconst FuncComp = (props, context) =\u003e {\n  return h(`h${props.name}`, context.attrs, context.slots);\n};\n\nFuncComp.props = [\"level\"];\n\nexport default FuncComp;\n```\n\n#### 10) vue 文件结构\n\n\u003e - `beforeCreate`和`created`钩子使用`setup`函数替代\n\u003e - props 解构会使其丧失响应式的\n\u003e - 一个组件中可写多个 v-model 指令\n\n```html\n\u003c!-- \n            作者：宫小白\n            链接：https://juejin.im/post/6874314855281590280\n            来源：掘金\n            著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。\n         --\u003e\n\u003c!-- 父组件 --\u003e\n\u003ctest01 v-model:foo=\"a\" v-model:bar=\"b\"\u003e\u003c/test01\u003e\n\u003c!-- 子组件 --\u003e\n\u003ctemplate\u003e\n  \u003cdiv\u003e{{num2}}\u003c/div\u003e\n  \u003cinput\n    type=\"text\"\n    :value=\"foo\"\n    @input=\"$emit('update:foo',$event.target.value)\"\n  /\u003e\n  \u003cinput\n    type=\"text\"\n    :value=\"bar\"\n    @input=\"$emit('update:bar',$event.target.value)\"\n  /\u003e\n\u003c/template\u003e\n\u003cscript\u003e\n  import { ref, reactive, computed, watch, onMounted, onUpdated, onUnmounted, provide, inject } from \"vue\";\n  export default {\n      props: {\n          data: String,\n      },\n      emits: [\"update:foo\", \"update:bar\"],, // 用于v-model\n      setup (props, context) {\n          provide('xx','1234')\n    const data=inject('xx', 该参数为默认值);\n          const num = ref(1);\n          const obj = reactive({\n              name: \"gxb\",\n              age: 18,\n              num,\n          });\n          const num2 = computed(() =\u003e num.value + 1);\n          const num3 = computed({\n              get: () =\u003e num,\n              set: value =\u003e num.value = value\n          });\n          watch(num, (name, preName) =\u003e {\n              console.log(`new ${name}---old ${preName}`);\n          });\n          // 监听多个\n          watch([num, ()=\u003eobj.name], ([newNum, newName], [oldNum, oldName]) =\u003e {\n              console.log(`new ${(newNum)},${(newName)}---old ${(oldNum)},${oldName}`);\n          });\n\n          // 生命周期\n          onBeforeMounted(() =\u003e {\n              console.log('beforeMounted!')\n          });\n          onMounted(() =\u003e {\n              console.log('mounted!')\n          });\n          onUpdated(() =\u003e {\n              console.log('updated!')\n          });\n          onUnmounted(() =\u003e {\n              console.log('unmounted!')\n          });\n          return { num, obj, num2, num3 };\n      },\n  };\n\u003c/script\u003e\n```\n\n#### 11) `Teleport` 传送门\n\n\u003e - 把节点挂载到 body 上\n\n```html\n\u003cteleport to=\"body\"\u003e\n  \u003cdiv v-if=\"flag\"\u003e\n    \u003cdiv\u003e模态框\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/teleport\u003e\n```\n\n#### 12) Fragments\n\n\u003e - 原来 template 节点下只能放一个节点，现在可以放多个\n\n---\n\n## 四、React\n\n### 4.1 八股文\n\n#### 1) 单向数据流\n\n\u003e - `view` -\u003e `action` -\u003e `store` -\u003e `reducer` -\u003e `store` -\u003e `view`\n\u003e - `view` `dispatch` 一个 `action`，`store`根据`action`的类型`reducer`一个`new state`，`store`拿到`new state`后更新`view`\n\u003e - redux 更新视图使用了订阅发布模式\n\n#### 2) `setState`是同步还是异步\n\n\u003e - `setState`只在合成事件和钩子函数中是“异步”的，在原生事件和 `setTimeout` 中都是同步的。\n\u003e - setState 的“异步”并不是说内部由异步代码实现，其实本身执行的过程和代码都是同步的，只是合成事件和钩子函数的调用顺序在更新之前，导致在合成事件和钩子函数中没法立马拿到更新后的值，形式了所谓的“异步”，此外可以通过 `setState(newState, cb)` 中的 cb 拿到更新后的结果。\n\u003e - 一句话总结：`react`管得到的就是异步 管不到的就是同步\n\n| 发生时机     | 特点                                                                                                                                                                                    |\n| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 批量更新     | 创建一个异步队列`updateQueue`，通过 `firstUpdate` 、 `lastUpdate` 、` lastUpdate.next` 去维护这个队列，相同的`key`会被覆盖，只保留最后一个更新，这样的话就可以避免多次更新同一个`state` |\n| 合成事件     | 合成事件的代码放在`try`里面执行，此时去读`state`里面的值还是以前的，所以就会造成异步的错觉，最后执行`finally`的时候次啊回执行`performSyncWork`方法，更新`state`并渲染视图               |\n| 生命周期     | 如果在`componentDidMount`中执行`SetState`，需要在执行完`componentDidmount`后才去`commitUpdateQueue`更新                                                                                 |\n| 原生事件     | 没有走合成事件的逻辑，并不像合成事件或钩子函数中被`return`，而直接走`performSyncWork`去更新，所以当在原生事件中`setState`后，能同步拿到更新后的`state`值                                |\n| `setTimeout` | 基于`event Loop`的模型下，没有被 react 包装过，`setTimeout`中里去`setState`总能拿到最新的`state`值                                                                                      |\n\n#### 3) 通讯\n\n\u003e - 方式|特点\n\u003e   父子|props\n\u003e   兄弟|父 state 子 props\n\u003e   跨层级通信|`Provider`，`Consumer`和`Context`\n\u003e   发布订阅模式|`eventbus` `on` `emit`\n\u003e   全局状态管理工具|`Redux`或者`Mobx`\n\n```js\n// util.js\nimport React from \"react\";\nlet { Consumer, Provider } = React.createContext(); //创建 context 并暴露Consumer和Provider模式\nexport { Consumer, Provider };\n```\n\n```html\n\u003c!-- 父组件 --\u003e\n\u003c!-- 导入 Provider --\u003e\nimport {Provider} from \"../../utils/context\"\n\n\u003cProvider value=\"{name}\"\u003e\n  \u003cdiv\u003e\n    \u003cp\u003e父组件定义的值:{name}\u003c/p\u003e\n    \u003cChild /\u003e\n  \u003c/div\u003e\n\u003c/Provider\u003e\n```\n\n```js\n// 导入Consumer\nimport { Consumer } from \"../../utils/context\";\nfunction Son(props) {\n  return (\n    //Consumer容器,可以拿到上文传递下来的name属性,并可以展示对应的值\n    \u003cConsumer\u003e\n      {(name) =\u003e (\n        \u003cdiv\n          style={{\n            border: \"1px solid blue\",\n            width: \"60%\",\n            margin: \"20px auto\",\n            textAlign: \"center\",\n          }}\n        \u003e\n          // 在 Consumer 中可以直接通过 name 获取父组件的值\n          \u003cp\u003e子组件。获取父组件的值:{name}\u003c/p\u003e\n        \u003c/div\u003e\n      )}\n    \u003c/Consumer\u003e\n  );\n}\nexport default Son;\n```\n\n#### 4) 为什么使用框架而不是原生\n\n\u003e - _组件化_ `react`的组件化可以做到函数级别的原子组件\n\u003e - _天然分层_ `MVVM`模式，代码解耦更容易读写\n\u003e - _开发效率_ 不必手动更新 DOM，提高开发效率\n\u003e - _生态_ 数据流管理结构和 UI 库都有成熟的解决方案\n\n#### 5) `redux`的`middleware`机制\n\n\u003e - 使用`applyMiddleware` `API`\n\u003e - 借鉴 koa 的洋葱圈模型\n\n```js\n        // 手动包装dispatch\n        getDispatchWrapper(store) {\n            let next = store.dispatch;\n            return action =\u003e {\n                // before TODO\n                const result = next(action);\n                // after TODO\n                return result;\n            }\n        }\n\n        // middlewares = [getDispatchWrapper1, getDispatchWrapper2, ...];\n        function applyMiddleware(middlewares) {\n            middlewares\n            .reverse()\n            .forEach(getDispatchWrapper =\u003e store.dispatch = getDispatchWrapper(store));\n        }\n```\n\n\u003e - 上面的做法是每次更新 store.dispatch 方法的引用，只想一个新的函数，此外还有一种方式进行链式调用，使用 next 作为传参代替 store.dispatch\n\n```js\n// 改进 克里希化getDispatchWrapper\nconst middle = (store) =\u003e (next) =\u003e (action) =\u003e {\n  // before TODO\n  console.log(\"dispatching\", action);\n\n  const result = next(action);\n\n  // after TODO\n  console.log(\"next state\", store.getState());\n\n  return result;\n};\n\n// middlewares = [getDispatchWrapper1, getDispatchWrapper2, ...];\nfunction applyMiddleware(middlewares) {\n  middlewares\n    .reverse()\n    .reduce((ret, middle) =\u003e middle(store)(ret), store.dispatch);\n}\n```\n\n#### 6) thunk\n\n\u003e - 判断`action`：如果是`function`类型，就调用这个`function`（并传入`dispatch`和`getState` 及`extraArgument` 为参数），而不是任由让它到达 `reducer`，因为 `reducer` 是个纯函数，`Redux` 规定到达 `reducer` 的 `action` 必须是一个 `plain object` 类型。\n\n```js\nfunction createThunkMiddleware(extraArgument) {\n  return ({ dispatch, getState }) =\u003e (next) =\u003e (action) =\u003e {\n    if (typeof action === \"function\") {\n      return action(dispatch, getState, extraArgument);\n    }\n\n    return next(action);\n  };\n}\n\nconst thunk = createThunkMiddleware();\nthunk.withExtraArgument = createThunkMiddleware;\n\nexport default thunk;\n```\n\n#### 7) react-redux\n\n\u003e - 工作原理\n\u003e   \u003e - 获取 state, connect 通过 context 获取 Provider 中的 store, store.getState()获取整个 store tree 上所有 state\n\u003e   \u003e - 包装原组件，将`mapStateToProps`, `mapDispatchToProps`已属性的形式传入`WrappedComponent`，`mapStateToProps`订阅更新，`mapDispatchToProps`发布更新\n\u003e   \u003e - 监听 store tree，如果 state 变化了就调用 this.setState()触发视图更新\n\u003e - 从 `dispatch` -\u003e `reduce` -\u003e `getState` 这条流里面如果没有使用异步控制的话，可以同步拿到最新的`state`\n\u003e - 从 `dispatch` -\u003e `reduce` -\u003e `connect` -\u003e `initSubscription` -\u003e `trySubscribe`-\u003e `props` 这条流里面，使用了`setState`的方法，所以会表现出【异步】\n\n#### 8) 组件/逻辑复用以及各自优缺点\n\n| 方式          | 优点                                                                                                                         | 缺点                                                                                                                                                                                                 |\n| ------------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `mixin`       | -                                                                                                                            | `mixin`跟组件之间存在隐式依赖，依赖关系不透明，增加维护成本，特别是多个`mixin`共存的情况下，状态增加不可预测性；属性之间会进行打平，增加不可预测性                                                   |\n| `HOC`         | 通过从外层传`props`到组件的方式，不更改组件的 state，降低耦合度;传入的参数跟返回组件自身的参数具有天然的层级结构，降低复杂度 | 扩展性限制:无法从外部访问子组件的 state，因此无法通过`shouldComponentUpdate`过滤掉不必要的更新（`React.PureComponent`可以解决这个问题）;`Ref` 传递问题被阻断（`React.forwardRef`可以解决）；命名冲突 |\n| `React Hooks` | 简洁、解耦、组合、函数友好                                                                                                   | 学习成本、写法上有限制（不能出现在条件、循环中）, `React.memo`并不能完全替代`shouldComponentUpdate`（因为拿不到 `state change`，只针对 `props change`）                                              |\n\n#### 9) `HOC`的理解\n\n\u003e - `HOC`本身不是一个`component`, 而是一个`function`\n\u003e - 输入的参数是`component`，返回也是一个`component`\n\u003e - 不是`react`的 API，而是一种基于 React 特性形成的设计模式\n\u003e - 使用的场景`redux`中的`connect`，`react-router`中的`withRouter`\n\u003e - 应用\n\u003e   \u003e - props 的增强\n\u003e   \u003e - 鉴权\n\u003e   \u003e - 生命周期劫持\n\n```js\nimport React, { createContext } from \"react\";\nconst { Provider, Consumer } = createContext();\nconst getNewComp = (Comp, newProps) =\u003e {\n  return (props) =\u003e\n    props.login ? (\n      \u003cConsumer\u003e\n        {(value) =\u003e \u003cComp {...{ ...props, ...newProps, ...value }} /\u003e}\n      \u003c/Consumer\u003e\n    ) : (\n      \u003cNoRight /\u003e\n    );\n};\n\n// Search是一个子组件\nconst SuperSearch = getNewComp(Search, { a: 1 });\nconst SuperInput = getNewComp(Input, { a: 2 });\n```\n\n```html\n\u003cProvider value=\"{{b:\" 3}}\u003e\n  \u003cSuperSearch name=\"search\" login=\"{true}\" /\u003e\n  \u003cSuperInput name=\"input\" /\u003e\n\u003c/Provider\u003e\n```\n\n\u003e - 缺点：多层嵌套调试会很麻烦，可以劫持 props，如果不约定可能会造成冲突\n\n#### 9) `React.forwardRef`\n\n\u003e - 一般来讲，ref 不能用于函数组件，因为函数组件没有实例，不能获取组件对象\n\u003e - 但是现在有需求：获取函数组件内部某个元素的 dom，那咋办？`React.forwardRef`应运而生\n\n```js\n    import React, {PureComponent, forwardRef, createRef} from 'react';\n    const Comp = forwardRef((props, ref) =\u003e \u003cspan ref={ref}\u003enihao\u003c/span\u003e;\n    export default class extends PureComponent {\n        constructor(props) {\n            super(props);\n            this.title = createRef();\n        }\n\n        componentDidMount() {\n            this.props.init();\n            console.log(this.title.current);\n        }\n        render() {\n            return \u003cComp ref={this.title} /\u003e\n        }\n    }\n```\n\n#### 10) `fiber`如何理解\n\n\u003e - 单线程调度算法\n\u003e - `React 16`以前使用`reconcilation`用的是递归，中断困难，而`fiber`用的是循环\n\u003e - 一种将 `recocilation`分拆成多个小任务，可以随时停止，恢复。停止恢复的时机取决于当前的一帧（16ms）内，还有没有足够的时间允许计算。\n\u003e - 时间分片正是基于可随时打断、重启的 Fiber 架构,可打断当前任务,优先处理紧急且重要的任务,保证页面的流畅运行。\n\n#### 11) 生命周期\n\n\u003e - 16.0 版本以前渲染是同步的，16.0 版本以后是异步的，这意味着在 render 函数之前的所有函数都有可能被执行多次，所以这也是`UNSAVE_componentWillMount`，`UNSAFE_componentWillReceiveProps`，`UNSAFE_componentWillUpdate`，被标注为不安全的原因\n\n| 生命周期                           | 特点                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |\n| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `constructor`                      | `super(props)`，否则我们无法在构造函数里拿到 `this`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n| `getDerivedStateFromProps`         | 静态函数，无法获取 this，根据新的 props 和当前的 state 来调整新的 state。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| `UNSAVE_componentWillMount`        | 在 reader 之前，同步调用 setState 不会引发渲染，此方法是服务端渲染唯一会调用的生命周期函数。常用于当支持服务器渲染时，需要同步获取数据的场景。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |\n| `render`                           | 期望是一个纯函数，任何跟数据相关的逻辑请放在 componentDidMount 和 componentDidUpdate 中                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |\n| `React Updates DOM and refs`       | -                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n| `componentDidMount`                | 适合网络请求和添加订阅。如果直接调用`setState`。它将触发额外渲染，但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 `render`两次调用的情况下，用户也不会看到中间状态。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| `UNSAFE_componentWillReceiveProps` | 考虑到因为父组件引发渲染可能要根据 props 更新 state 的需要而设立的，会在已挂载的组件接收新的 `props` 之前被调用                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |\n| `getDerivedStateFromProps`         | 替代了`UNSAFE_componentWillReceiveProps`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |\n| `shouldComponentUpdate`            | `shouldComponentUpdate(nextProps, nextState) {}`根据此函数的返回值来判断是否进行重新渲染，`true` 表示重新渲染，`false` 表示不重新渲染，默认返回 `true`，可以作为性能优化的手段。但是官方提倡我们使用内置的 `PureComponent` 来减少重新渲染的次数，而不是手动编写 `shouldComponentUpdate` 代码。`PureComponent` 内部实现了对 `props` 和 `state` 进行浅层比较。                                                                                                                                                                                                                                                                                                                                                    |\n| `UNSAFE_componentWillUpdate`       | 初始渲染不会调用此方法。但是你不能此方法中调用 `this.setState`，否则就无限循环了                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |\n| `getSnapshotBeforeUpdate`          | 替代`UNSAFE_componentWillUpdate`，在 render 之后，在更新之前（如：更新 DOM 之前）被调用。给了一个机会去获取 DOM 信息，计算得到并返回一个 snapshot，这个 snapshot 会作为 `componentDidUpdate` 的第三个参数传入。如果你不想要返回值，请返回 `null`，不写的话控制台会有警告。 `getSnapshotBeforeUpdate` 方法是在` UNSAFE_componentWillUpdate` 后（如果存在的话），在 React 真正更改 DOM 前调用的，它获取到组件状态信息更加可靠。还有一个十分明显的好处：它调用的结果会作为第三个参数传入 `componentDidUpdate`，避免了` UNSAFE_componentWillUpdate` 和`componentDidUpdate` 配合使用时将组件临时的状态数据存在组件实例上浪费内存，getSnapshotBeforeUpdate 返回的数据在 componentDidUpdate 中用完即被销毁，效率更高。 |\n| `componentDidUpdate`               | -                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n| `componentWillUnmount`             | 执行一些清理操作，如定时器，订阅，网络请求，不要`setState`，因为没有效果                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |\n| `componentDidCatch`                | `componentDidCatch(error, info) {}`如果发生错误，你可以通过调用 `setState`使用 `componentDidCatch`渲染降级 UI，但在未来的版本中将不推荐这样做。可以使用静态 `getDerivedStateFromError`来处理降级渲染                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |\n| `getDerivedStateFromError`         | `static getDerivedStateFromError(error) {}`此生命周期会在后代组件抛出错误后被调用。它将抛出的错误作为参数，并返回一个值以更新 `state`。渲染阶段调用，因此不允许出现副作用                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n\n```js\n        // 作者：LeviDing\n        // 链接：https://juejin.im/post/6844904199923187725\n        // 来源：掘金\n        // 著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。\n        getSnapshotBeforeUpdate(prevProps, prevState) {\n            console.log('#enter getSnapshotBeforeUpdate');\n            return 'foo';\n        }\n\n        componentDidUpdate(prevProps, prevState, snapshot) {\n            console.log('#enter componentDidUpdate snapshot = ', snapshot);\n        }\n```\n\n### 4.2 性能优化\n\n\u003e -\n\n### 4.3 原则与规范\n\n\u003e - import 顺序\n\u003e   \u003e - 标准模块\n\u003e   \u003e - 第三方模块\n\u003e   \u003e - 自己代码导入（组件）\n\u003e   \u003e - 特定于模块的导入（例如 CSS，PNG 等）\n\u003e   \u003e - 仅用于测试的代码\n\n### 4.4 小技巧\n\n#### 1) `Portal`\n\n\u003e - 将组件挂载于父组件以外的组件或者节点\n\u003e - `ReactDom.createProtal(Comp, targetCom);`\n\n#### 2) Fragment\n\n\u003e - 此节点作为容器不渲染，可以简写为`\u003c\u003e\u003c/\u003e`\n\u003e - 不支持 key 和属性。\n\n#### 3) StrictMode\n\n\u003e - 仅在开发模式下运行的检查工具\n\u003e - 检查过时的 API，不安全的生命周期，意外的副作用, 使用废弃的 findDOMNode `\u003cStrictMode\u003e\u003c/StrictMode\u003e`\n\n## 五、webpack\n\n### 5.1 八股文\n\n#### 1) 相关概念\n\u003e - ``Entry`` 打包入口\n\u003e - ``Module`` 模块 一切文件皆可视为模块 从入口开始递归所有模块\n\u003e - ``Chunk`` 代码块 一个``chunk``由多个模块组合而成，用于代码的合并与分割\n\u003e - ``Loader`` 模块转换器 用于将模块的原内容按照去求转换成新内容\n\u003e - ``Plugin`` 拓展插件\n\u003e - ``Output`` 输出\n\n#### 2) 构建过程\n\u003e - 从``Entry``出发依次递归寻找``Module``，利用``Loader``并辅助以``plugin``对``Module``进行转换，最后以``entry``为单位进行分组，其依赖会被打到同一个``chunk``，并输出文件\n\n#### 3) 配置属性\n\u003e - ``entry`` 入口\n\u003e\u003e - 可以是字符串、数组或者对象，如果是字符串、数组，最后只会输出一个``chunk``，且使用``Output.library``时只有最后一个入口文件的模块被导出\n\u003e\u003e - 也可以写成同步函数或者返回``promise``的异步函数\n\u003e - ``output`` 配置如何输出\n\u003e\u003e - ``filename`` vs ``chunkFilename`` ``Entry``的键值对键值，``chunkFilename`` 非``Entry``入口的``chunk``名称，比如动态加载或者``CommonChunkPlugin``(提取第三方库和公共模块)\n\u003e\u003e - ``path`` vs ``publishPath`` ``path``表示打包出来的目录 ``publishPath``表示打包后需要上传服务器的地址\n\u003e\u003e - ``library`` vs ``libraryTarget`` ``library``表示导出库的名称 ``libraryTarget``导出方式，比如``var``/``commonjs``/``commonjs2``/``this``/``window``/``global``/``umd``/``libraryExport`` 表示导出的子模块，默认``default``\n\u003e - ``module``\n\u003e\u003e - 使用``loader``的``test`` ``include`` 和``exclude``可以减少搜索范围加快速度\n\u003e\u003e - 使用noParse可以避免递归一些没有依赖模块的文件，比如``jQuery``, ``noParse: /jquery/, //不去解析jquery中的依赖库``\n\u003e - ``resolve`` 配置寻找模块的代码\n\u003e\u003e - ``alias`` 路径别名\n\u003e\u003e - ``mainFields`` 优先使用那份模块的代码（在``package.json``里面对应目录）比如：``mainFields: ['jsnext:main', 'browser', 'main'];``\n\u003e\u003e - ``extensions`` 文件路径后缀优先级 ``extensions: ['.ts', '.js', 'json'];``\n\u003e\u003e - ``modules`` 配置``webpack``在哪里寻找第三方模块，默认只会在``node_modules``里面找，如果有很多需要导入的文件在``src/components``文件夹中，可以配置``modules: ['node_modules', 'src/components']``，这样可以直接使用``import button from 'Button'``进行导入\n\u003e - ``plugin`` 配置拓展插件\n\u003e\u003e - \n\u003e - ``devServer``\n\n#### 4) sourceMap\n\u003e - ``cheap`` 不包含列信息，且不包含loader信息\n\u003e - ``cheap-mudule`` 不包含列信息，包含loader信息\n\u003e - ``inline`` 把``sourceMap``以``hash``字符串的形式写进文件中，一般不会在生产环境中使用\n\u003e - 在开发环境中，``webpack``是不支持``sourceMap``的，需要使用``source-map-loader``进行加载，且要写在最前面避免其他``loader``对``sourcemap``进行转换 ``enforce: 'pre'``\n\n### 5.2 构建速度优化\n\n#### 1) 多线程压缩\n\u003e - webpack3 happy-pack\n\u003e - webpack4 uglifyjs-webpack-plugin | parallel-uglify-plugin | terser-webpack-plugin\n\n#### 2) DLLPlugin预编译\n\u003e - 创建一个manifest.json文件，DllReferencePlugin使用它来映射依赖项\n\n#### 3) 开启缓存\n\u003e - 开启``babel-loader``缓存（``babel-loader?cacheDirectory=true``)\n\u003e - 开启terser-webpack-plugin缓存\n\u003e - 使用``hard-source-webpack-plugin``提升模块转换阶段缓存\n\n#### 4) 缩小构建目标\n\u003e - include\n\u003e - resolve - alias\n\u003e - resolve - modules\n\u003e - resolve - extensions\n\u003e - resolve - mainFields: ['main'] // package.json指定的入口文件 ``jsnext:main`` ``browser`` ``main``\n\n### 5.3 优化使用体验\n#### 1) 监听文件自动刷新 watch\n\u003e - 原理 定时获取文件的最后编辑时间，每次保存最新的最后编辑时间，下次更新的时候与上次比较，如果不相同则认为文件发生了变化。但是文件发生了变化也不会第一时间告知监听者，而是先缓存起来，收集一段时间后再一次性告诉监听者，而这个时间可以设置，避免频繁更新。\n\u003e - 自动刷新浏览器的原理\n\u003e\u003e - 借助浏览器拓展去通过浏览器的接口去刷新，比如LiveEdit插件\n\u003e\u003e - 向要开发的网页中注入客户端代码，通过代理客户端刷新整个页面\n\u003e\u003e - 将要开发的网页装进一个iframe中，通过刷新iframe去看到最新的效果\n\n\n#### 2) 开启模块热更新\n\u003e - 在不刷新页面的情况下更新目标节点\n\u003e - 原理：源码发生变化的时候，只需要重新编译发生变化的模块，再替换掉相应的老模块\n\u003e - HMR的优点在于可以保存应用的状态，提高开发效率\n\u003e - 底层原理 Server端使用webpack-dev-server去启动本地服务，内部实现主要使用了webpack、express、websocket。\n\u003e\u003e - 使用express启动本地服务，当浏览器访问资源时对此做响应。\n\u003e\u003e - 服务端和客户端使用websocket实现长连接\n\u003e\u003e - webpack监听源文件的变化，即当开发者保存文件时触发webpack的重新编译。每次编译都会生成hash值、已改动模块的json文件、已改动模块代码的js文件。编译完成后通过socket向客户端推送当前编译的hash戳，客户端的websocket监听到有文件改动推送过来的hash戳，会和上一次对比。一致则走缓存，不一致则通过ajax和jsonp向服务端获取最新资源\n\u003e\u003e - 使用内存文件系统去替换有修改的内容实现局部刷新\n\u003e\u003e - 为什么使用JSONP而不用socke通信获取更新过的代码？因为通过socket通信获取的是一串字符串需要再做处理。而通过JSONP获取的代码可以直接执行。\n\n### 5.4 优化输出质量\n#### 1) 区分环境\n#### 2) 压缩代码\n#### 3) 使用tree shaking\n#### 4) 提取公共代码\n\u003e - 好处：base.js一旦被用户浏览器缓存，那么在任何页面都不需要重新下载一份，提升客户体验\n\u003e - 业务代码.js\n\u003e - common.js\n\u003e - base.js 所有页面都会用的到的基础库，例如react和react.dom\n#### 5) 分割代码按需加载\n\u003e - ``import(*)`` 语法\n\u003e - 用在路由切换的场合用得比较多\n\n#### 6) Scope Hoisting\n#### 7) 输出分析\n\n## 六、Axios\n\n### 6.1 八股文\n\n#### 1) 相关概念\n\u003e - Axios 是一个基于 Promise 的 HTTP 客户端，拥有以下特性：\n\u003e\u003e - 支持promise API\n\u003e\u003e - 能够拦截请求和响应\n\u003e\u003e - 能够转换请求和相应数据\n\u003e\u003e - 能够取消请求和自动转换JSON数据\n\u003e\u003e - 客户端支持防御CSRF攻击\n\u003e\u003e - 同时支持浏览器和node环境\n\n#### 2) 拦截器\n\u003e - ``axios.interceptors.request``和``axios.interceptors.response``对象提供的``use``方法\n```js\n        // 添加请求拦截器\n        axios.interceptors.request.use(function (config) {\n            config.headers.token = 'added by interceptor';\n            return config;\n        });\n\n        // 添加响应拦截器\n        axios.interceptors.response.use(function (data) {\n            data.data = data.data + ' - modified by interceptor';\n            return data;\n        });\n```\n\u003e - 实现原理\n\u003e\u003e - 任务注册\n```js\n        // lib/core/Axios.js\n        function Axios(instanceConfig) {\n            this.defaults = instanceConfig;\n            this.interceptors = {\n                request: new InterceptorManager(),\n                response: new InterceptorManager()\n            };\n        }\n\n        // lib/core/InterceptorManager.js\n        function InterceptorManager() {\n            this.handlers = [];\n        }\n\n        InterceptorManager.prototype.use = function use(fulfilled, rejected) {\n            this.handlers.push({\n                fulfilled: fulfilled,\n                rejected: rejected\n            });\n            // 返回当前的索引，用于移除已注册的拦截器\n            return this.handlers.length - 1;\n        };\ns\n```\n\u003e\u003e - 任务编排 请求拦截是倒序，相应拦截是顺序\n```js\n        // lib/core/Axios.js\n        Axios.prototype.request = function request(config) {\n            config = mergeConfig(this.defaults, config);\n\n            // 省略部分代码\n            var chain = [dispatchRequest, undefined];\n            var promise = Promise.resolve(config);\n\n            // 任务编排\n            this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {\n                chain.unshift(interceptor.fulfilled, interceptor.rejected);\n            });\n\n            this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {\n                chain.push(interceptor.fulfilled, interceptor.rejected);\n            });\n\n            // 任务调度\n            while (chain.length) {\n                promise = promise.then(chain.shift(), chain.shift());\n            }\n\n            return promise;\n        };\n```\n\u003e\u003e - 任务调度\n```js\n    // lib/core/Axios.js\n    Axios.prototype.request = function request(config) {\n        // 省略部分代码\n        var promise = Promise.resolve(config);\n        while (chain.length) {\n            promise = promise.then(chain.shift(), chain.shift());\n        }\n    }\n```\n\n## 七、web性能优化\n\n### 7.1 css 优化\n\n#### 1) 概念\n\u003e - 是指一个元素外观的改变所触发的浏览器行为，浏览器会根据元素的新属性重新绘制，使元素呈现新的外观。这个过程就是重绘。重排必定会引发重绘，但重绘不一定会引发重排\n\u003e - 常见的会引起重绘的属性 color、border-style、visibility、background、text-decoration、background-image、background-position、background-repeat、outline-color、outline、outline-style、border-radius、outline-width、box-shadow、background-size\n\n#### 2) 减少reflow对性能的影响的建议\n\u003e - 不要一条一条地修改 DOM 的样式，预先定义好 class，然后修改 DOM 的 className\n\u003e - 把 DOM 离线后修改，比如：先把 DOM 给 display:none (有一次 Reflow)，然后你修改100次，然后再把它显示出来\n\u003e - 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量\n\u003e - 尽可能不要修改影响范围比较大的 DOM\n\u003e - 为动画的元素使用绝对定位 absolute / fixed\n\u003e - 不要使用 table 布局，可能很小的一个小改动会造成整个 table 的重新布局\n\n### 7.2 图片延迟\n```html\n        \u003c!DOCTYPE html\u003e\n        \u003chtml lang=\"en\"\u003e\n        \u003chead\u003e\n            \u003cmeta charset=\"UTF-8\"\u003e\n            \u003ctitle\u003eLazyload 1\u003c/title\u003e\n            \u003cstyle\u003e\n                img {\n                display: block;\n                margin-bottom: 50px;\n                height: 200px;\n            }\n            \u003c/style\u003e\n        \u003c/head\u003e\n        \u003cbody\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/1.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/2.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/3.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/4.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/5.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/6.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/7.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/8.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/9.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/10.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/11.png\"\u003e\n            \u003cimg src=\"images/loading.gif\" data-src=\"images/12.png\"\u003e\n            \u003cscript\u003e\n                function lazyload() {\n                    var images = document.getElementsByTagName('img');\n                    var len    = images.length;\n                    var n      = 0;      //存储图片加载到的位置，避免每次都从第一张图片开始遍历\t\t\n                    return function() {\n                        var seeHeight = document.documentElement.clientHeight;\n                        var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;\n                        for (var i = n; i \u003c len; i++) {\n                            if (images[i].offsetTop \u003c seeHeight + scrollTop) {\n                                if (images[i].getAttribute('src') === 'images/loading.gif') {\n                                images[i].src = images[i].getAttribute('data-src');\n                            }\n                            n = n + 1;\n                            }\n                        }\n                    }\n                }\n                var loadImages = lazyload();\n                loadImages();          //初始化首页的页面图片\n                window.addEventListener('scroll', loadImages, false);\n            \u003c/script\u003e\n        \u003c/body\u003e\n        \u003c/html\u003e\n```\n\n## 八、lerna\n\n### 8.1 介绍\n\n#### 1) 用于管理多个存在依赖关系的包\n#### 2) 目录结构\n- packages(目录)\n - lerna.json(配置文件)\n - package.json(工程描述文件)\n - packages\n   - module-1\n      - package.json(工程描述文件)\n   - module-2\n      - package.json(工程描述文件)\n#### 2) 基本工作流\n\u003e - ``lerna init``","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhblvsjtu%2Feffectivefe-engineering","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhblvsjtu%2Feffectivefe-engineering","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhblvsjtu%2Feffectivefe-engineering/lists"}