├── .editorconfig ├── .gitignore ├── .prettierrc ├── Algorithm └── README.md ├── CSS └── README.md ├── HTML └── README.md ├── JavaScript └── README.md ├── Network └── README.md ├── README.md ├── front-end-interview-handbook ├── 01.md ├── 02.md └── 03.md ├── 前端面试之道 ├── 01.md ├── 02.md ├── 03.md ├── 04.md ├── 05.md ├── 06.md ├── 07.md ├── 08.md ├── 09.md ├── 10.md ├── 11.md ├── 12.md ├── 13.md ├── 14.md ├── 15.md ├── 16.md ├── 17.md ├── 18.md ├── 19.md ├── 20.md ├── 21.md ├── 22.md ├── 23.md ├── 24.md ├── 25.md ├── 26.md ├── 27.md ├── 28.md ├── 29.md ├── 30.md ├── 31.md ├── 32.md ├── 33.md └── images │ ├── 001.png │ ├── 002.png │ ├── 003.png │ ├── 004.png │ ├── 005.png │ ├── 006.png │ ├── 007.png │ ├── 008.png │ ├── 009.png │ ├── 010.png │ ├── 011.webp │ ├── 012.webp │ ├── 013.webp │ ├── 014.webp │ ├── 015.gif │ ├── 016.webp │ ├── 017.webp │ ├── 018.webp │ ├── 020.webp │ ├── 021.webp │ ├── 022.webp │ ├── 023.webp │ ├── 024.webp │ ├── 025.webp │ ├── 026.webp │ ├── 027.webp │ ├── 028.webp │ ├── 029.webp │ ├── 030.webp │ ├── 031.webp │ ├── 032.webp │ ├── 033.webp │ ├── 034.webp │ ├── 035.webp │ ├── 036.webp │ ├── 037.webp │ ├── 038.webp │ ├── 039.webp │ ├── 040.webp │ ├── 041.webp │ ├── 042.webp │ ├── 043.webp │ ├── 044.webp │ ├── 045.webp │ ├── 046.webp │ ├── 047.webp │ ├── 048.gif │ ├── 049.webp │ ├── 050.webp │ ├── 051.webp │ ├── 052.webp │ ├── 053.webp │ ├── 054.webp │ ├── 055.webp │ ├── 056.webp │ ├── 057.webp │ ├── 058.webp │ ├── 059.webp │ ├── 060.webp │ ├── 061.webp │ ├── 062.webp │ ├── 063.webp │ ├── 064.webp │ ├── 065.webp │ ├── 066.webp │ ├── 067.webp │ ├── 068.webp │ ├── 069.webp │ ├── 070.webp │ ├── 071.webp │ ├── 072.webp │ ├── 073.webp │ ├── 074.webp │ ├── 075.webp │ ├── 076.webp │ ├── 077.webp │ ├── 078.webp │ ├── 079.webp │ ├── 080.webp │ ├── 081.webp │ ├── 082.webp │ ├── 083.webp │ ├── 084.webp │ ├── 085.webp │ ├── 086.webp │ ├── 087.webp │ ├── 088.webp │ ├── 089.webp │ ├── 090.webp │ ├── 091.webp │ ├── 092.webp │ ├── 093.webp │ ├── 094.gif │ ├── 095.gif │ ├── 096.gif │ ├── 097.gif │ ├── 098.gif │ ├── 099.gif │ ├── 100.webp │ ├── 101.webp │ ├── 102.webp │ ├── 103.webp │ └── 104.webp └── 重学前端 ├── README.md └── 开篇词+学习路线+架构图 ├── 01.md ├── 02.md ├── 03.md └── images ├── 01.png ├── 02.png ├── 03.png ├── 04.jpg └── 05.jpg /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "semi": true 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前端面试复习指南 2 | 3 | 此仓库是作者为准备跳槽而创建,收集和整理一些比较常见且重要的前端知识点和面试题,希望对大家有所帮助,共同进步,早日收获 Offer。 4 | 5 | ## 目录 6 | 7 | - [HTML](./HTML) 8 | 9 | - [CSS](./CSS) 10 | 11 | - [JavaScript](./JavaScript) 12 | 13 | - [计算机网络](./Network) 14 | 15 | - [算法](./Algorithm) 16 | 17 | - 前端知识总结(一) 18 | - [基础知识点及常考面试题(一)](./前端面试之道/01.md) 19 | - [基础知识点及常考面试题(二)](./前端面试之道/02.md) 20 | - [ES6 知识点及常考面试题](./前端面试之道/03.md) 21 | - [JS 异步编程及常考面试题](./前端面试之道/04.md) 22 | - [手写 Promise](./前端面试之道/05.md) 23 | - [Event Loop](./前端面试之道/06.md) 24 | - [JS 进阶知识点及常考面试题](./前端面试之道/07.md) 25 | - [JS 思考题](./前端面试之道/08.md) 26 | - [DevTools Tips](./前端面试之道/09.md) 27 | - [浏览器基础知识点及常考面试题](./前端面试之道/10.md) 28 | - [浏览器缓存机制](./前端面试之道/11.md) 29 | - [浏览器渲染原理](./前端面试之道/12.md) 30 | - [安全防范知识点](./前端面试之道/13.md) 31 | - [从 V8 中看 JS 性能优化](./前端面试之道/14.md) 32 | - [性能优化琐碎事](./前端面试之道/15.md) 33 | - [Webpack 性能优化](./前端面试之道/16.md) 34 | - [实现小型打包工具](./前端面试之道/17.md) 35 | - [React 和 Vue 两大框架之间的相爱相杀](./前端面试之道/18.md) 36 | - [Vue 常考基础知识点](./前端面试之道/19.md) 37 | - [Vue 常考进阶知识点](./前端面试之道/20.md) 38 | - [React 常考基础知识点](./前端面试之道/21.md) 39 | - [React 常考进阶知识点](./前端面试之道/22.md) 40 | - [监控](./前端面试之道/23.md) 41 | - [UDP](./前端面试之道/24.md) 42 | - [TCP](./前端面试之道/25.md) 43 | - [HTTP 及 TLS](./前端面试之道/26.md) 44 | - [HTTP/2 及 HTTP/3](./前端面试之道/27.md) 45 | - [输入 URL 到页面渲染的整个流程](./前端面试之道/28.md) 46 | - [设计模式](./前端面试之道/29.md) 47 | - [常见数据结构](./前端面试之道/30.md) 48 | - [常考算法题解析](./前端面试之道/31.md) 49 | - [CSS 常考面试题资料](./前端面试之道/32.md) 50 | - [如何写好一封简历](./前端面试之道/33.md) 51 | 52 | - 前端知识总结(二) 53 | - [HTML 问题](./front-end-interview-handbook/01.md) 54 | - [CSS 问题](./front-end-interview-handbook/02.md) 55 | - [JavaScript 问题](./front-end-interview-handbook/03.md) 56 | 57 | - [前端知识总结(三)](./重学前端/README.md) 58 | 59 | 60 | ## 排版 61 | 62 | 本仓库内容严格按照 [中文文案排版指北](https://xpoet.cn/2020/05/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%88%E6%8E%92%E7%89%88%E6%8C%87%E5%8C%97/) 进行排版,以保证内容的可读性。 63 | 64 | ## 贡献 65 | 66 | 仓库内容主要来源于网络,加上一个人的精力实在有限,难免会有一些错误,欢迎参与改进和贡献。 67 | 68 | ## 协议 69 | 70 | MIT 71 | -------------------------------------------------------------------------------- /front-end-interview-handbook/01.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: HTML 问题 3 | --- 4 | 5 | 本章节是[前端开发者面试问题 - HTML 部分](https://github.com/h5bp/Front-end-Developer-Interview-Questions/blob/master/src/questions/html-questions.md)的参考答案。 欢迎提出 PR 进行建议和指正! 6 | 7 | ## 目录 8 | 9 | - [`DOCTYPE` 有什么用?](#doctype有什么用) 10 | - [如何提供包含多种语言内容的页面?](#如何提供包含多种语言内容的页面) 11 | - [在设计开发多语言网站时,需要留心哪些事情?](#在设计开发多语言网站时需要留心哪些事情) 12 | - [什么是`data-`属性?](#什么是data-属性) 13 | - [将 HTML5 看作成开放的网络平台,什么是 HTML5 的基本构件(building block)?](#将-html5-看作成开放的网络平台什么是-html5-的基本构件building-block) 14 | - [请描述`cookie`、`sessionStorage`和`localStorage`的区别。](#请描述cookiesessionstorage和localstorage的区别) 15 | - [请描述` 86 | ``` 87 | 88 | 事件代理的方式相较于直接给目标注册事件来说,有以下优点: 89 | 90 | - 节省内存。 91 | - 不需要给子节点注销事件。 92 | 93 | ### 跨域 94 | 95 | > 涉及面试题:什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求嘛? 96 | 97 | 因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求会失败。 98 | 99 | 那么是出于什么安全考虑才会引入这种机制呢? 其实主要是用来防止 CSRF 攻击的。简单点说,CSRF 攻击是利用用户的登录态发起恶意请求。 100 | 101 | 也就是说,没有同源策略的情况下,A 网站可以被任意其他来源的 Ajax 访问到内容。如果你当前 A 网站还存在登录态,那么对方就可以通过 Ajax 获得你的任何信息。当然跨域并不能完全阻止 CSRF。 102 | 103 | 然后我们来考虑一个问题,请求跨域了,那么请求到底发出去没有? 请求必然是发出去了,但是浏览器拦截了响应。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会。因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。 104 | 105 | 接下来我们将来学习几种常见的方式来解决跨域的问题。 106 | 107 | #### JSONP 108 | 109 | JSONP 的原理很简单,就是利用 ` 113 | 118 | ``` 119 | 120 | JSONP 使用简单且兼容性不错,但是只限于 `get` 请求。 121 | 122 | 在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现 123 | 124 | ```js 125 | function jsonp(url, jsonpCallback, success) { 126 | let script = document.createElement('script') 127 | script.src = url 128 | script.async = true 129 | script.type = 'text/javascript' 130 | window[jsonpCallback] = function(data) { 131 | success && success(data) 132 | } 133 | document.body.appendChild(script) 134 | } 135 | jsonp('http://xxx', 'callback', function(value) { 136 | console.log(value) 137 | }) 138 | ``` 139 | 140 | #### CORS 141 | 142 | CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 `XDomainRequest` 来实现。 143 | 144 | 浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。 145 | 146 | 服务端设置 `Access-Control-Allow-Origin` 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。 147 | 148 | 虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为**简单请求和复杂请求**。 149 | 150 | ##### 简单请求 151 | 152 | 以 Ajax 为例,当满足以下条件时,会触发简单请求 153 | 154 | 1. 使用下列方法之一: 155 | 156 | - GET 157 | 158 | - HEAD 159 | 160 | - POST 161 | 162 | 2. Content-Type 的值仅限于下列三者之一: 163 | 164 | - text/plain 165 | 166 | - multipart/form-data 167 | 168 | - application/x-www-form-urlencoded 169 | 170 | 请求中的任意 `XMLHttpRequestUpload` 对象均没有注册任何事件监听器; `XMLHttpRequestUpload` 对象可以使用 `XMLHttpRequest.upload` 属性访问。 171 | 172 | ##### 复杂请求 173 | 174 | 那么很显然,不符合以上条件的请求就肯定是复杂请求了。 175 | 176 | 对于复杂请求来说,首先会发起一个预检请求,该请求是 `option` 方法的,通过该请求来知道服务端是否允许跨域请求。 177 | 178 | 对于预检请求来说,如果你使用过 Node 来设置 CORS 的话,可能会遇到过这么一个坑。 179 | 180 | 以下以 express 框架举例: 181 | 182 | ```js 183 | app.use((req, res, next) => { 184 | res.header('Access-Control-Allow-Origin', '*') 185 | res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS') 186 | res.header( 187 | 'Access-Control-Allow-Headers', 188 | 'Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials' 189 | ) 190 | next() 191 | }) 192 | ``` 193 | 194 | 该请求会验证你的 `Authorization` 字段,没有的话就会报错。 195 | 196 | 当前端发起了复杂请求后,你会发现就算你代码是正确的,返回结果也永远是报错的。因为预检请求也会进入回调中,也会触发 next 方法,因为预检请求并不包含 `Authorization` 字段,所以服务端会报错。 197 | 198 | 想解决这个问题很简单,只需要在回调中过滤 `option` 方法即可 199 | 200 | 201 | ```js 202 | res.statusCode = 204 203 | res.setHeader('Content-Length', '0') 204 | res.end() 205 | ``` 206 | 207 | ##### document.domain 208 | 209 | 该方式只能用于**二级域名**相同的情况下,比如 `a.test.com` 和 `b.test.com` 适用于该方式。 210 | 211 | 只需要给页面添加 `document.domain = 'test.com'` 表示二级域名都相同就可以实现跨域 212 | 213 | ##### postMessage 214 | 215 | 这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息 216 | 217 | ```js 218 | // 发送消息端 219 | window.parent.postMessage('message', 'http://test.com') 220 | // 接收消息端 221 | var mc = new MessageChannel() 222 | mc.addEventListener('message', event => { 223 | var origin = event.origin || event.originalEvent.origin 224 | if (origin === 'http://test.com') { 225 | console.log('验证通过') 226 | } 227 | }) 228 | ``` 229 | 230 | ### 存储 231 | 232 | > 涉及面试题:有几种方式可以实现存储功能,分别有什么优缺点?什么是 Service Worker? 233 | 234 | #### cookie,localStorage,sessionStorage,indexDB 235 | 236 | 我们先来通过表格学习下这几种存储方式的区别 237 | 238 | | 特性 | cookie | localStorage | sessionStorage | indexDB | 239 | |:---------|:--------|:--------|:--------|:--------| 240 | |数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 | 241 | |数据存储大小 | 4K | 5M | 5M | 无限 | 242 | |与服务端通信 | 每次都会携带在 header 中,对于请求性能影响 | 不参与 | 不参与 | 不参与 | 243 | 244 | 从上表可以看到,`cookie` 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 `localStorage` 和 `sessionStorage`。对于不怎么改变的数据尽量使用 `localStorage` 存储,否则可以用 `sessionStorage` 存储。 245 | 246 | 对于 `cookie` 来说,我们还需要注意安全性。 247 | 248 | | 属性 | 作用 | 249 | |------|------| 250 | | value | 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识 | 251 | | http-only | 不能通过 JS 访问 Cookie,减少 XSS 攻击 | 252 | | secure | 只能在协议为 HTTPS 的请求中携带 | 253 | | same-site | 规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击 | 254 | 255 | #### Service Worker 256 | 257 | Service Worker 是运行在浏览器背后的**独立线程**,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。 258 | 259 | Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 `install` 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。以下是这个步骤的实现: 260 | 261 | ```js 262 | // index.js 263 | if (navigator.serviceWorker) { 264 | navigator.serviceWorker 265 | .register('sw.js') 266 | .then(function(registration) { 267 | console.log('service worker 注册成功') 268 | }) 269 | .catch(function(err) { 270 | console.log('servcie worker 注册失败') 271 | }) 272 | } 273 | // sw.js 274 | // 监听 `install` 事件,回调中缓存所需文件 275 | self.addEventListener('install', e => { 276 | e.waitUntil( 277 | caches.open('my-cache').then(function(cache) { 278 | return cache.addAll(['./index.html', './index.js']) 279 | }) 280 | ) 281 | }) 282 | 283 | // 拦截所有请求事件 284 | // 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据 285 | self.addEventListener('fetch', e => { 286 | e.respondWith( 287 | caches.match(e.request).then(function(response) { 288 | if (response) { 289 | return response 290 | } 291 | console.log('fetch source') 292 | }) 293 | ) 294 | }) 295 | ``` 296 | 297 | 打开页面,可以在开发者工具中的 `Application` 看到 Service Worker 已经启动了 298 | 299 | ![](./images/034.webp) 300 | 301 | 在 Cache 中也可以发现我们所需的文件已被缓存 302 | 303 | ![](./images/035.webp) 304 | 305 | 当我们重新刷新页面可以发现我们缓存的数据是从 Service Worker 中读取的 306 | 307 | ![](./images/036.webp) 308 | 309 | ### 小结 310 | 311 | 以上就是浏览器基础知识点的内容了,如果大家对于这个章节的内容存在疑问,欢迎在评论区与我互动。 312 | 313 | 314 | 315 | 316 | 317 | -------------------------------------------------------------------------------- /前端面试之道/11.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 浏览器缓存机制 4 | 5 | > 注意:该知识点属于性能优化领域,并且整一章节都是一个面试题。 6 | 7 | 缓存可以说是性能优化中简单高效的一种优化方式了,它可以显著减少网络传输所带来的损耗。 8 | 9 | 对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。 10 | 11 | 接下来的内容中我们将通过以下几个部分来探讨浏览器缓存机制: 12 | 13 | - 缓存位置 14 | - 缓存策略 15 | - 实际场景应用缓存策略 16 | 17 | ### 缓存位置 18 | 19 | 从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络 20 | 21 | 1. Service Worker 22 | 2. Memory Cache 23 | 3. Disk Cache 24 | 4. Push Cache 25 | 5. 网络请求 26 | 27 | #### Service Worker 28 | 29 | 在上一章节中我们已经介绍了 Service Worker 的内容,这里就不演示相关的代码了。 30 | 31 | Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们**自由控制**缓存哪些文件、如何匹配缓存、如何读取缓存,并且**缓存是持续性**的。 32 | 33 | 当 Service Worker 没有命中缓存的时候,我们需要去调用 `fetch` 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。**但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。** 34 | 35 | #### Memory Cache 36 | 37 | Memory Cache 也就是内存中的缓存,读取内存中的数据肯定比磁盘快。**但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。**一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 38 | 39 | 当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存 40 | 41 | ![](./images/037.webp) 42 | 43 | 那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢? 44 | 45 | 先说结论,这是不可能的。首先计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。内存中其实可以存储大部分的文件,比如说 JSS、HTML、CSS、图片等等。但是浏览器会把哪些文件丢进内存这个过程就很玄学了,我查阅了很多资料都没有一个定论。 46 | 47 | 当然,我通过一些实践和猜测也得出了一些结论: 48 | 49 | - 对于大文件来说,大概率是不存储在内存中的,反之优先。 50 | - 当前系统内存使用率高的话,文件优先存储进硬盘。 51 | 52 | #### Disk Cache 53 | 54 | Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。 55 | 56 | 在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。 57 | 58 | #### Push Cache 59 | 60 | Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。 61 | 62 | Push Cache 在国内能够查到的资料很少,也是因为 HTTP/2 在国内不够普及,但是 HTTP/2 将会是日后的一个趋势。这里推荐阅读 HTTP/2 push is tougher than I thought 这篇文章,但是内容是英文的,我翻译一下文章中的几个结论,有能力的同学还是推荐自己阅读 63 | 64 | - 所有的资源都能被推送,但是 Edge 和 Safari 浏览器兼容性不怎么好。 65 | - 可以推送 no-cache 和 no-store 的资源。 66 | - 一旦连接被关闭,Push Cache 就被释放。 67 | - 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存。 68 | - Push Cache 中的缓存只能被使用一次。 69 | - 浏览器可以拒绝接受已经存在的资源推送。 70 | - 你可以给其他域名推送资源。 71 | 72 | #### 网络请求 73 | 74 | 如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。 75 | 76 | 那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,接下来我们就来学习缓存策略这部分的内容。 77 | 78 | ### 缓存策略 79 | 80 | 通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。 81 | 82 | #### 强缓存 83 | 84 | 强缓存可以通过设置两种 HTTP Header 实现:`Expires` 和 `Cache-Control`。强缓存表示在缓存期间不需要请求,`state code` 为 200。 85 | 86 | #### Expires 87 | 88 | ``` 89 | Expires: Wed, 22 Oct 2018 08:41:00 GMT 90 | ``` 91 | 92 | `Expires` 是 HTTP/1 的产物,表示资源会在 `Wed, 22 Oct 2018 08:41:00 GMT` 后过期,需要再次请求。并且 `Expires` 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。 93 | 94 | #### Cache-control 95 | 96 | ``` 97 | Cache-control: max-age=30 98 | ``` 99 | 100 | `Cache-Control` 出现于 HTTP/1.1,优先级高于 `Expires`。该属性值表示资源会在 30 秒后过期,需要再次请求。 101 | 102 | `Cache-Control` 可以在请求头或者响应头中设置,并且可以组合使用多种指令 103 | 104 | ![](./images/038.webp) 105 | 106 | 从图中我们可以看到,我们可以将多个指令配合起来一起使用,达到多个目的。比如说我们希望资源能被缓存下来,并且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等。 107 | 108 | 接下来我们就来学习一些常见指令的作用 109 | 110 | ![](./images/039.webp) 111 | 112 | #### 协商缓存 113 | 114 | 如果缓存过期了,就需要发起请求验证资源是否有更新。协商缓存可以通过设置两种 HTTP Header 实现:`Last-Modified` 和 `ETag`。 115 | 116 | 当浏览器发起请求验证资源时,如果资源没有做改变,那么服务端就会返回 304 状态码,并且更新浏览器缓存有效期。 117 | 118 | ![](./images/040.webp) 119 | 120 | #### Last-Modified 和 If-Modified-Since 121 | 122 | `Last-Modified` 表示本地文件最后修改日期,`If-Modified-Since` 会将 `Last-Modified` 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。 123 | 124 | 但是 `Last-Modified` 存在一些弊端: 125 | 126 | - 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 `Last-Modified` 被修改,服务端不能命中缓存导致发送相同的资源。 127 | 128 | - 因为 `Last-Modified` 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源。 129 | 130 | 因为以上这些弊端,所以在 HTTP / 1.1 出现了 `ETag`。 131 | 132 | #### ETag 和 If-None-Match 133 | 134 | `ETag` 类似于文件指纹,`If-None-Match` 会将当前 `ETag` 发送给服务器,询问该资源 `ETag` 是否变动,有变动的话就将新的资源发送回来。并且 `ETag` 优先级比 `Last-Modified` 高。 135 | 136 | 以上就是缓存策略的所有内容了,看到这里,不知道你是否存在这样一个疑问。**如果什么缓存策略都没设置,那么浏览器会怎么处理?** 137 | 138 | 对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。 139 | 140 | ### 实际场景应用缓存策略 141 | 142 | 单纯了解理论而不付诸于实践是没有意义的,接下来我们来通过几个场景学习下如何使用这些理论。 143 | 144 | #### 频繁变动的资源 145 | 146 | 对于频繁变动的资源,首先需要使用 `Cache-Control: no-cache` 使浏览器每次都请求服务器,然后配合 `ETag` 或者 `Last-Modified` 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。 147 | 148 | #### 代码文件 149 | 这里特指除了 HTML 外的代码文件,因为 HTML 文件一般不缓存或者缓存时间很短。 150 | 151 | 一般来说,现在都会使用工具来打包代码,那么我们就可以对文件名进行哈希处理,只有当代码修改后才会生成新的文件名。基于此,我们就可以给代码文件设置缓存有效期一年 `Cache-Control: max-age=31536000`,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。 152 | 153 | ### 小结 154 | 155 | 在这一章节中我们了解了浏览器的缓存机制,并且列举了几个场景来实践我们学习到的理论。如果大家对于这个章节的内容存在疑问,欢迎在评论区与我互动。 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /前端面试之道/12.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 浏览器渲染原理 4 | 5 | > 注意:注意:该章节都是一个面试题。 6 | 7 | 在这一章节中,我们将来学习浏览器渲染原理这部分的知识。你可能会有疑问,我又不是做浏览器研发的,为什么要来学习这个?其实我们学习浏览器渲染原理更多的是为了解决性能的问题,如果你不了解这部分的知识,你就不知道什么情况下会对性能造成损伤。并且渲染原理在面试中答得好,也是一个能与其他候选人拉开差距的一点。 8 | 9 | 我们知道执行 JS 有一个 JS 引擎,那么执行渲染也有一个渲染引擎。同样,渲染引擎在不同的浏览器中也不是都相同的。比如在 Firefox 中叫做 Gecko,在 Chrome 和 Safari 中都是基于 WebKit 开发的。在这一章节中,我们也会主要学习关于 WebKit 的这部分渲染引擎内容。 10 | 11 | ### 浏览器接收到 HTML 文件并转换为 DOM 树 12 | 13 | 当我们打开一个网页时,浏览器都会去请求对应的 HTML 文件。虽然平时我们写代码时都会分为 JS、CSS、HTML 文件,也就是字符串,但是计算机硬件是不理解这些字符串的,所以在网络中传输的内容其实都是 `0` 和 `1` 这些字节数据。当浏览器接收到这些**字节数据**以后,它会将这些**字节数据转换为字符串**,也就是我们写的代码。 14 | 15 | ![](./images/041.webp) 16 | 17 | 当数据转换为字符串以后,浏览器会先将这些字符串通过词法分析转换为标记(token),这一过程在词法分析中叫做标记化(tokenization)。 18 | 19 | ![](./images/042.webp) 20 | 21 | 那么什么是标记呢?这其实属于编译原理这一块的内容了。简单来说,标记还是字符串,是构成代码的最小单位。这一过程会将代码分拆成一块块,并给这些内容打上标记,便于理解这些最小单位的代码是什么意思。 22 | 23 | ![](./images/043.webp) 24 | 25 | 当结束标记化后,这些标记会紧接着转换为 Node,最后这些 Node 会根据不同 Node 之前的联系构建为一颗 DOM 树。 26 | 27 | ![](./images/044.webp) 28 | 29 | 以上就是浏览器从网络中接收到 HTML 文件然后一系列的转换过程。 30 | 31 | ![](./images/045.webp) 32 | 33 | 当然,在解析 HTML 文件的时候,浏览器还会遇到 CSS 和 JS 文件,这时候浏览器也会去下载并解析这些文件,接下来就让我们先来学习浏览器如何解析 CSS 文件。 34 | 35 | ### 将 CSS 文件转换为 CSSOM 树 36 | 37 | 其实转换 CSS 到 CSSOM 树的过程和上一小节的过程是极其类似的 38 | 39 | ![](./images/046.webp) 40 | 41 | 在这一过程中,浏览器会确定下每一个节点的**样式**到底是什么,并且这一过程其实是**很消耗资源**的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。 42 | 43 | 如果你有点不理解为什么会消耗资源的话,我这里举个例子 44 | 45 | ```html 46 |
47 | 48 |
49 | 57 | ``` 58 | 59 | 对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 `span` 标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的 `span` 标签,然后找到 `span` 标签上的 `a` 标签,最后再去找到 `div` 标签,然后给符合这种条件的 `span` 标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。 60 | 61 | ### 生成渲染树 62 | 63 | 当我们生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。 64 | 65 | ![](./images/047.webp) 66 | 67 | 在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 `display: none` 的,那么就不会在渲染树中显示。 68 | 69 | 当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用 GPU 绘制,合成图层,显示在屏幕上。对于这一部分的内容因为过于底层,还涉及到了硬件相关的知识,这里就不再继续展开内容了。 70 | 71 | 那么通过以上内容,我们已经详细了解到了浏览器从接收文件到将内容渲染在屏幕上的这一过程。接下来,我们将会来学习上半部分遗留下来的一些知识点。 72 | 73 | 74 | ### 为什么操作 DOM 慢 75 | 76 | 想必大家都听过操作 DOM 性能很差,但是这其中的原因是什么呢? 77 | 78 | 因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。 79 | 80 | > 经典面试题:插入几万个 DOM,如何实现页面不卡顿? 81 | 82 | 对于这道题目来说,首先我们肯定不能一次性把几万个 DOM 全部插入,这样肯定会造成卡顿,所以解决问题的重点应该是如何分批次部分渲染 DOM。大部分人应该可以想到通过 `requestAnimationFrame` 的方式去循环的插入 DOM,其实还有种方式去解决这个问题:虚拟滚动(virtualized scroller)。 83 | 84 | **这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。** 85 | 86 | ![](./images/048.gif) 87 | 88 | 从上图中我们可以发现,即使列表很长,但是渲染的 DOM 元素永远只有那么几个,当我们滚动页面的时候就会实时去更新 DOM,这个技术就能顺利解决这道经典面试题。如果你想了解更多的内容可以了解下这个 react-virtualized。 89 | 90 | ### 什么情况阻塞渲染 91 | 92 | 首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件**大小**,并且**扁平层级,优化选择器**。 93 | 94 | 然后当浏览器在解析到 `script` 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 `script` 标签放在 `body` 标签底部的原因。 95 | 96 | 当然在当下,并不是说 `script` 标签必须放在底部,因为你可以给 `script` 标签添加 `defer` 或者 `async` 属性。 97 | 98 | 当 `script` 标签加上 `defer` 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以对于这种情况你可以把 `script` 标签放在任意位置。 99 | 100 | 对于没有任何依赖的 JS 文件可以加上 `async` 属性,表示 JS 文件下载和解析不会阻塞渲染。 101 | 102 | ### 重绘(Repaint)和回流(Reflow) 103 | 104 | 重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。 105 | 106 | - 重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘 107 | - 回流是布局或者几何属性需要改变就称为回流。 108 | 109 | 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。 110 | 111 | 以下几个动作可能会导致性能问题: 112 | 113 | - 改变 window 大小。 114 | - 改变字体。 115 | - 添加或删除样式。 116 | - 文字改变。 117 | - 定位或者浮动。 118 | - 盒模型。 119 | 120 | 并且很多人不知道的是,重绘和回流其实也和 Eventloop 有关。 121 | 122 | 1. 当 Eventloop 执行完 Microtasks 后,会判断 `document` 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。 123 | 2. 然后判断是否有 `resize` 或者 `scroll` 事件,有的话会去触发事件,所以 `resize` 和 `scroll` 事件也是至少 16ms 才会触发一次,并且自带节流功能。 124 | 3. 判断是否触发了 media query。 125 | 4. 更新动画并且发送事件。 126 | 5. 判断是否有全屏操作事件。 127 | 6. 执行 `requestAnimationFrame` 回调。 128 | 7. 执行 `IntersectionObserver` 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好。 129 | 8. 更新界面。 130 | 9. 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 `requestIdleCallback` 回调。 131 | 132 | 133 | 以上内容来自于 HTML 文档。 134 | 135 | 既然我们已经知道了重绘和回流会影响性能,那么接下来我们将会来学习如何减少重绘和回流的次数。 136 | 137 | #### 减少重绘和回流 138 | 139 | - 使用 transform 替代 top。 140 | 141 | ```js 142 |
143 | 152 | 158 | ``` 159 | 160 | - 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)。 161 | 162 | - 不要把节点的属性值放在一个循环里当成循环里的变量。 163 | 164 | ```js 165 | for(let i = 0; i < 1000; i++) { 166 | // 获取 offsetTop 会导致回流,因为需要去获取正确的值 167 | console.log(document.querySelector('.test').style.offsetTop) 168 | } 169 | ``` 170 | 171 | - 不要使用 `table` 布局,可能很小的一个小改动会造成整个 `table` 的重新布局 172 | 173 | - 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 `requestAnimationFrame`。 174 | 175 | - CSS 选择符从右往左匹配查找,避免节点层级过多 176 | 177 | - 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 `video` 标签来说,浏览器会自动将该节点变为图层。 178 | 179 | ![](./images/049.webp) 180 | 181 | 设置节点为图层的方式有很多,我们可以通过以下几个常用属性可以生成新图层。 182 | 183 | - `will-change`。 184 | - `video`、`iframe` 标签。 185 | 186 | 187 | ### 思考题 188 | 189 | > 思考题:在不考虑缓存和优化网络协议的前提下,考虑可以通过哪些方式来最快的渲染页面,也就是常说的关键渲染路径,这部分也是性能优化中的一块内容。 190 | 191 | 首先你可能会疑问,那怎么测量到底有没有加快渲染速度呢 192 | 193 | ![](./images/050.webp) 194 | 195 | 当发生 `DOMContentLoaded` 事件后,就会生成渲染树,生成渲染树就可以进行渲染了,这一过程更大程度上和硬件有关系了。 196 | 197 | 提示如何加速: 198 | 199 | 1. 从文件大小考虑。 200 | 2. 从 `script` 标签使用上来考虑。 201 | 3. 从 CSS、HTML 的代码书写上来考虑。 202 | 4. 从需要下载的内容是否需要在首屏使用上来考虑。 203 | 204 | 以上提示大家都可以从文中找到,同时也欢迎大家踊跃在评论区写出你的答案。 205 | 206 | ### 小结 207 | 208 | 以上就是我们这一章节的内容了。在这一章节中,我们了解了浏览器如何将文件渲染为页面,同时也掌握了一些优化的小技巧。这部分的内容理解起来不大容易,如果大家对于这个章节的内容存在疑问,欢迎在评论区与我互动。 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /前端面试之道/13.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 安全防范知识点 4 | 5 | 这一章我们将来学习安全防范这一块的知识点。总的来说安全是很复杂的一个领域,不可能通过一个章节就能学习到这部分的内容。在这一章节中,我们会学习到常见的一些安全问题及如何防范的内容,在当下其实安全问题越来越重要,已经逐渐成为前端开发必备的技能了。 6 | 7 | ### XSS 8 | 9 | > 涉及面试题:什么是 XSS 攻击?如何防范 XSS 攻击?什么是 CSP? 10 | 11 | XSS 简单点来说,就是攻击者想尽一切办法将可以执行的代码注入到网页中。 12 | 13 | XSS 可以分为多种类型,但是总体上我认为分为两类:**持久型**和**非持久型**。 14 | 15 | 持久型也就是攻击的代码被服务端写入进数据库中,这种攻击危害性很大,因为如果网站访问量很大的话,就会导致大量正常访问页面的用户都受到攻击。 16 | 17 | 举个例子,对于评论功能来说,就得防范持久型 XSS 攻击,因为我可以在评论中输入以下内容 18 | 19 | ![](./images/051.webp) 20 | 21 | 这种情况如果前后端没有做好防御的话,这段评论就会被存储到数据库中,这样每个打开该页面的用户都会被攻击到。 22 | 23 | 非持久型相比于前者危害就小的多了,一般通过修改 URL 参数的方式加入攻击代码,诱导用户访问链接从而进行攻击。 24 | 25 | 举个例子,如果页面需要从 URL 中获取某些参数作为内容的话,不经过过滤就会导致攻击代码被执行 26 | 27 | ```html 28 | 29 |
{{name}}
30 | ``` 31 | 32 | 但是对于这种攻击方式来说,如果用户使用 Chrome 这类浏览器的话,浏览器就能自动帮助用户防御攻击。但是我们不能因此就不防御此类攻击了,因为我不能确保用户都使用了该类浏览器。 33 | 34 | ![](./images/052.webp) 35 | 36 | 对于 XSS 攻击来说,通常有两种方式可以用来防御。 37 | 38 | #### 转义字符 39 | 40 | 首先,对于用户的输入应该是永远不信任的。最普遍的做法就是转义输入输出的内容,对于引号、尖括号、斜杠进行转义 41 | 42 | ```js 43 | function escape(str) { 44 | str = str.replace(/&/g, '&') 45 | str = str.replace(//g, '>') 47 | str = str.replace(/"/g, '&quto;') 48 | str = str.replace(/'/g, ''') 49 | str = str.replace(/`/g, '`') 50 | str = str.replace(/\//g, '/') 51 | return str 52 | } 53 | ``` 54 | 55 | 通过转义可以将攻击代码 `` 变成 56 | 57 | ```js 58 | // -> <script>alert(1)</script> 59 | escape('') 60 | ``` 61 | 62 | 但是对于显示富文本来说,显然不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。对于这种情况,通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。 63 | 64 | ```js 65 | const xss = require('xss') 66 | let html = xss('

XSS Demo

') 67 | // ->

XSS Demo

<script>alert("xss");</script> 68 | console.log(html) 69 | ``` 70 | 71 | 以上示例使用了 `js-xss` 来实现,可以看到在输出中保留了 `h1` 标签且过滤了 `script` 标签。 72 | 73 | #### CSP 74 | 75 | CSP 本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击。 76 | 77 | 通常可以通过两种方式来开启 CSP: 78 | 79 | - 设置 HTTP Header 中的 Content-Security-Policy 80 | - 设置 meta 标签的方式 `` 81 | 82 | 这里以设置 HTTP Header 来举例 83 | 84 | - 只允许加载本站资源 85 | ```http 86 | Content-Security-Policy: default-src 'self' 87 | ``` 88 | 89 | - 只允许加载 HTTPS 协议图片 90 | ```http 91 | Content-Security-Policy: img-src https://* 92 | ``` 93 | 94 | - 允许加载任何来源框架 95 | ```http 96 | Content-Security-Policy: child-src 'none' 97 | ``` 98 | 99 | 当然可以设置的属性远不止这些,你可以通过查阅 文档 的方式来学习,这里就不过多赘述其他的属性了。 100 | 101 | 对于这种方式来说,只要开发者配置了正确的规则,那么即使网站存在漏洞,攻击者也不能执行它的攻击代码,并且 CSP 的兼容性也不错。 102 | 103 | ![](./images/053.webp) 104 | 105 | ### CSRF 106 | 107 | > 涉及面试题:什么是 CSRF 攻击?如何防范 CSRF 攻击? 108 | 109 | CSRF 中文名为跨站请求伪造。原理就是攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑。 110 | 111 | 举个例子,假设网站中有一个通过 `GET` 请求提交用户评论的接口,那么攻击者就可以在钓鱼网站中加入一个图片,图片的地址就是评论接口 112 | 113 | ```html 114 | 115 | ``` 116 | 117 | 那么你是否会想到使用 POST 方式提交请求是不是就没有这个问题了呢?其实并不是,使用这种方式也不是百分百安全的,攻击者同样可以诱导用户进入某个页面,在页面中通过表单提交 POST 请求。 118 | 119 | 120 | #### 如何防御 121 | 122 | 防范 CSRF 攻击可以遵循以下几种规则: 123 | 124 | 1. Get 请求不对数据进行修改。 125 | 2. 不让第三方网站访问到用户 Cookie。 126 | 3. 阻止第三方网站请求接口。 127 | 4. 请求时附带验证信息,比如验证码或者 Token。 128 | 129 | #### SameSite 130 | 131 | 可以对 Cookie 设置 `SameSite` 属性。该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。 132 | 133 | #### 验证 Referer 134 | 135 | 对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。 136 | 137 | #### Token 138 | 139 | 服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。 140 | 141 | ### 点击劫持 142 | 143 | > 涉及面试题:什么是点击劫持?如何防范点击劫持? 144 | 145 | 点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 `iframe` 嵌套的方式嵌入自己的网页中,并将 `iframe` 设置为透明,在页面中透出一个按钮诱导用户点击。 146 | 147 | ![](./images/054.webp) 148 | 149 | 对于这种攻击方式,推荐防御的方法有两种。 150 | 151 | #### X-FRAME-OPTIONS 152 | 153 | `X-FRAME-OPTIONS` 是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头 就是为了防御用 `iframe` 嵌套的点击劫持攻击。 154 | 155 | 该响应头有三个值可选,分别是 156 | 157 | - DENY,表示页面不允许通过 iframe 的方式展示。 158 | - SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示。 159 | - ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示。 160 | 161 | 162 | #### JS 防御 163 | 164 | 对于某些远古浏览器来说,并不能支持上面的这种方式,那我们只有通过 JS 的方式来防御点击劫持了。 165 | 166 | ```html 167 | 168 | 173 | 174 | 175 | 183 | 184 | ``` 185 | 186 | 以上代码的作用就是当通过 iframe 的方式加载页面时,攻击者的网页直接不显示所有内容了。 187 | 188 | ### 中间人攻击 189 | 190 | > 涉及面试题:什么是中间人攻击?如何防范中间人攻击? 191 | 192 | 中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。 193 | 194 | 通常来说不建议使用公共的 Wi-Fi,因为很可能就会发生中间人攻击的情况。如果你在通信的过程中涉及到了某些敏感信息,就完全暴露给攻击方了。 195 | 196 | 当然防御中间人攻击其实并不难,只需要增加一个安全通道来传输信息。HTTPS 就可以用来防御中间人攻击,但是并不是说使用了 HTTPS 就可以高枕无忧了,因为如果你没有完全关闭 HTTP 访问的话,攻击方可以通过某些方式将 HTTPS 降级为 HTTP 从而实现中间人攻击。 197 | 198 | ### 小结 199 | 200 | 在这一章中,我们学习到了一些常见的前端安全方面的知识及如何防御这些攻击。但是安全的领域相当大,这些内容只是沧海一粟,如果大家对于安全有兴趣的话,可以阅读 这个仓库的内容 来学习和实践这方面的知识。 -------------------------------------------------------------------------------- /前端面试之道/14.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 从 V8 中看 JS 性能优化 4 | 5 | > 注意:该知识点属于性能优化领域。 6 | 7 | 性能问题越来越成为前端火热的话题,因为随着项目的逐步变大,性能问题也逐步体现出来。为了提高用户的体验,减少加载时间,工程师们想尽一切办法去优化细节。 8 | 9 | 掘金之前已经出过一本关于性能的小册,我在写涉及性能优化的内容之前就特地去购买了这本小册阅读,目的是为了写出点不一样的东西。当然性能优化归结起来还是那几个点,我只能尽可能地写出那本小册没有提及的内容,部分内容还是会有重叠的。当然它通过了十五个章节去介绍性能,肯定会讲的比我细,有兴趣的可以同时购买还有本 「前端性能优化原理与实践 」小册,形成一个互补。 10 | 11 | 在这几个章节中不会提及浏览器、Webpack、网络协议这几块如何优化的内容,因为对应的模块中已经讲到了这部分的内容,如果你想学习这几块该如何性能优化的话,可以去对应的章节阅读。 12 | 13 | 在这一章节中我们将来学习如何让 V8 优化我们的代码,下一章节将会学习性能优化剩余的琐碎点,因为性能优化这个领域所涉及的内容都很碎片化。 14 | 15 | 在学习如何性能优化之前,我们先来了解下如何测试性能问题,毕竟是先有问题才会去想着该如何改进。 16 | 17 | ### 测试性能工具 18 | 19 | Chrome 已经提供了一个大而全的性能测试工具 **Audits** 20 | 21 | ![](./images/055.webp) 22 | 23 | 点我们点击 Audits 后,可以看到如下的界面 24 | 25 | ![](./images/056.webp) 26 | 27 | 在这个界面中,我们可以选择想测试的功能然后点击 Run audits,工具就会自动运行帮助我们测试问题并且给出一个完整的报告 28 | 29 | ![](./images/057.webp) 30 | 31 | 上图是给掘金首页测试性能后给出的一个报告,可以看到报告中分别为性能、体验、SEO 都给出了打分,并且每一个指标都有详细的评估 32 | 33 | ![](./images/058.webp) 34 | 35 | 评估结束后,工具还提供了一些建议便于我们提高这个指标的分数 36 | 37 | ![](./images/059.webp) 38 | 39 | 我们只需要一条条根据建议去优化性能即可。 40 | 41 | 除了 **Audits** 工具之外,还有一个 **Performance** 工具也可以供我们使用。 42 | 43 | ![](./images/060.webp) 44 | 45 | 在这张图中,我们可以详细的看到每个时间段中浏览器在处理什么事情,哪个过程最消耗时间,便于我们更加详细的了解性能瓶颈。 46 | 47 | ### JS 性能优化 48 | 49 | JS 是编译型还是解释型语言其实并不固定。首先 JS 需要有引擎才能运行起来,无论是浏览器还是在 Node 中,这是解释型语言的特性。但是在 V8 引擎下,又引入了 `TurboFan` 编译器,他会在特定的情况下进行优化,将代码编译成执行效率更高的 **Machine Code**,当然这个编译器并不是 JS 必须需要的,只是为了提高代码执行性能,所以总的来说 JS 更偏向于解释型语言。 50 | 51 | 那么这一小节的内容主要会针对于 Chrome 的 V8 引擎来讲解。 52 | 53 | 在这一过程中,JS 代码首先会解析为抽象语法树(AST),然后会通过解释器或者编译器转化为 **Bytecode** 或者 **Machine Code** 54 | 55 | ![](./images/061.webp) 56 | 57 | 从上图中我们可以发现,JS 会首先被解析为 AST,解析的过程其实是略慢的。代码越多,解析的过程也就耗费越长,这也是我们需要压缩代码的原因之一。另外一种减少解析时间的方式是预解析,会作用于未执行的函数,这个我们下面再谈。 58 | 59 | ![](./images/062.webp) 60 | 61 | 这里需要注意一点,对于函数来说,应该尽可能避免声明嵌套函数(类也是函数),因为这样会造成函数的重复解析。 62 | 63 | ```js 64 | function test1() { 65 | // 会被重复解析 66 | function test2() {} 67 | } 68 | ``` 69 | 70 | 然后 Ignition 负责将 AST 转化为 Bytecode,TurboFan 负责编译出优化后的 Machine Code,并且 Machine Code 在执行效率上优于 Bytecode 71 | 72 | ![](./images/063.webp) 73 | 74 | 那么我们就产生了一个疑问,**什么情况下代码会编译为 Machine Code?** 75 | 76 | JS 是一门动态类型的语言,并且还有一大堆的规则。简单的加法运算代码,内部就需要考虑好几种规则,比如数字相加、字符串相加、对象和字符串相加等等。这样的情况也就势必导致了内部要增加很多判断逻辑,降低运行效率。 77 | 78 | ```js 79 | function test(x) { 80 | return x + x 81 | } 82 | 83 | test(1) 84 | test(2) 85 | test(3) 86 | test(4) 87 | ``` 88 | 89 | 对于以上代码来说,如果一个函数被多次调用并且参数一直传入 `number` 类型,那么 V8 就会认为该段代码可以编译为 Machine Code,因为你固定了类型,不需要再执行很多判断逻辑了。 90 | 91 | 但是如果一旦我们传入的参数**类型改变**,那么 Machine Code 就会被 DeOptimized 为 Bytecode,这样就有性能上的一个损耗了。所以如果我们希望代码能多的编译为 Machine Code 并且 DeOptimized 的次数减少,就应该尽可能保证传入的**类型一致**。 92 | 93 | 那么你可能会有一个疑问,到底优化前后有多少的提升呢,接下来我们就来实践测试一下到底有多少的提升。 94 | 95 | ```js 96 | const { performance, PerformanceObserver } = require('perf_hooks') 97 | 98 | function test(x) { 99 | return x + x 100 | } 101 | // node 10 中才有 PerformanceObserver 102 | // 在这之前的 node 版本可以直接使用 performance 中的 API 103 | const obs = new PerformanceObserver((list, observer) => { 104 | console.log(list.getEntries()) 105 | observer.disconnect() 106 | }) 107 | obs.observe({ entryTypes: ['measure'], buffered: true }) 108 | 109 | performance.mark('start') 110 | 111 | let number = 10000000 112 | // 不优化代码 113 | %NeverOptimizeFunction(test) 114 | 115 | while (number--) { 116 | test(1) 117 | } 118 | 119 | performance.mark('end') 120 | performance.measure('test', 'start', 'end') 121 | ``` 122 | 123 | 以上代码中我们使用了 `performance` API,这个 API 在性能测试上十分好用。不仅可以用来测量代码的执行时间,还能用来测量各种网络连接中的时间消耗等等,并且这个 API 也可以在浏览器中使用。 124 | 125 | ![](./images/064.webp) 126 | 127 | 从上图中我们可以发现,优化过的代码执行时间只需要 9ms,但是不优化过的代码执行时间却是前者的二十倍,已经接近 200ms 了。在这个案例中,我相信大家已经看到了 V8 的性能优化到底有多强,只需要我们符合一定的规则书写代码,引擎底层就能帮助我们自动优化代码。 128 | 129 | 另外,编译器还有个骚操作 Lazy-Compile,当函数没有被执行的时候,会对函数进行一次预解析,直到代码被执行以后才会被解析编译。对于上述代码来说,`test` 函数需要被预解析一次,然后在调用的时候再被解析编译。但是对于这种函数马上就被调用的情况来说,预解析这个过程其实是多余的,那么有什么办法能够让代码不被预解析呢? 130 | 131 | 其实很简单,我们只需要给函数套上括号就可以了 132 | 133 | ```js 134 | (function test(obj) { 135 | return x + x 136 | }) 137 | ``` 138 | 139 | 但是不可能我们为了性能优化,给所有的函数都去套上括号,并且也不是所有函数都需要这样做。我们可以通过 optimize-js 实现这个功能,这个库会分析一些函数的使用情况,然后给需要的函数添加括号,当然这个库很久没人维护了,如果需要使用的话,还是需要测试过相关内容的。 140 | 141 | ### 小结 142 | 143 | 总结一下这一章节我们学习的知识 144 | 145 | - 可以通过 Audit 工具获得网站的多个指标的性能报告。 146 | - 可以通过 Performance 工具了解网站的性能瓶颈。 147 | - 可以通过 Performance API 具体测量时间。 148 | - 为了减少编译时间,我们可以采用减少代码文件的大小或者减少书写嵌套函数的方式。 149 | - 为了让 V8 优化代码,我们应该尽可能保证传入参数的类型一致。这也给我们带来了一个思考,这是不是也是使用 TypeScript 能够带来的好处之一。 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /前端面试之道/15.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 性能优化琐碎事 4 | 5 | > 注意:该知识点属于性能优化领域。 6 | 7 | 总的来说性能优化这个领域的很多内容都很碎片化,这一章节我们将来学习这些碎片化的内容。 8 | 9 | ### 图片优化 10 | 11 | #### 计算图片大小 12 | 13 | 对于一张 100 _ 100 像素的图片来说,图像上有 10000 个像素点,如果每个像素的值是 RGBA 存储的话,那么也就是说每个像素有 4 个通道,每个通道 1 个字节(8 位 = 1 个字节),所以该图片大小大概为 39KB(10000 _ 1 \* 4 / 1024)。 14 | 15 | 但是在实际项目中,一张图片可能并不需要使用那么多颜色去显示,我们可以通过减少每个像素的调色板来相应缩小图片的大小。 16 | 17 | 了解了如何计算图片大小的知识,那么对于如何优化图片,想必大家已经有 2 个思路了: 18 | 19 | - **减少像素点**。 20 | - **减少每个像素点能够显示的颜色**。 21 | 22 | #### 图片加载优化 23 | 24 | 1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。 25 | 26 | 2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。 27 | 28 | 3. 小图使用 base64 格式。 29 | 30 | 4. 将多个图标文件整合到一张图片中(雪碧图)。 31 | 32 | 5. 选择正确的图片格式: 33 | - 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好。 34 | - 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替。 35 | - 照片使用 JPEG。 36 | 37 | ### DNS 预解析 38 | 39 | DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。 40 | 41 | ```html 42 | 43 | ``` 44 | 45 | #### 节流 46 | 47 | 考虑一个场景,滚动事件中会发起网络请求,但是我们并不希望用户在滚动过程中一直发起请求,而是隔一段时间发起一次,对于这种情况我们就可以使用节流。 48 | 49 | 理解了节流的用途,我们就来实现下这个函数 50 | 51 | ```js 52 | // func是用户传入需要防抖的函数 53 | // wait是等待时间 54 | const throttle = (func, wait = 50) => { 55 | // 上一次执行该函数的时间 56 | let lastTime = 0 57 | return function(...args) { 58 | // 当前时间 59 | let now = +new Date() 60 | // 将当前时间和上一次执行函数时间对比 61 | // 如果差值大于设置的等待时间就执行函数 62 | if (now - lastTime > wait) { 63 | lastTime = now 64 | func.apply(this, args) 65 | } 66 | } 67 | } 68 | 69 | setInterval( 70 | throttle(() => { 71 | console.log(1) 72 | }, 500), 73 | 1 74 | ) 75 | ``` 76 | 77 | ### 防抖 78 | 79 | 考虑一个场景,有一个按钮点击会触发网络请求,但是我们并不希望每次点击都发起网络请求,而是当用户点击按钮一段时间后没有再次点击的情况才去发起网络请求,对于这种情况我们就可以使用防抖。 80 | 81 | 理解了防抖的用途,我们就来实现下这个函数 82 | 83 | ```js 84 | // func是用户传入需要防抖的函数 85 | // wait是等待时间 86 | const debounce = (func, wait = 50) => { 87 | // 缓存一个定时器id 88 | let timer = 0 89 | // 这里返回的函数是每次用户实际调用的防抖函数 90 | // 如果已经设定过定时器了就清空上一次的定时器 91 | // 开始一个新的定时器,延迟执行用户传入的方法 92 | return function(...args) { 93 | if (timer) clearTimeout(timer) 94 | timer = setTimeout(() => { 95 | func.apply(this, args) 96 | }, wait) 97 | } 98 | } 99 | ``` 100 | 101 | ### 预加载 102 | 103 | 在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。 104 | 105 | 预加载其实是声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载 106 | 107 | ```html 108 | 109 | ``` 110 | 111 | 预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。 112 | 113 | ### 预渲染 114 | 115 | 可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染 116 | 117 | ```html 118 | 119 | ``` 120 | 121 | 预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开,否则就是白白浪费资源去渲染。 122 | 123 | ### 懒执行 124 | 125 | 懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。 126 | 127 | ### 懒加载 128 | 129 | 懒加载就是将不关键的资源延后加载。 130 | 131 | 懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 `src` 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 `src` 属性,这样图片就会去下载资源,实现了图片懒加载。 132 | 133 | 懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。 134 | 135 | ### CDN 136 | 137 | CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。 138 | 139 | 因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。 140 | 141 | ### 小结 142 | 143 | 这些碎片化的性能优化点看似很短,但是却能在出现性能问题时简单高效的提高性能,并且好几个点都是面试高频考点,比如节流、防抖。如果你还没有在项目中使用过这些技术,可以尝试着用到项目中,体验下功效。 144 | -------------------------------------------------------------------------------- /前端面试之道/16.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## Webpack 性能优化 4 | 5 | 在这一的章节中,我不会浪费篇幅给大家讲如何写配置文件。如果你想学习这方面的内容,那么完全可以去官网学习。在这部分的内容中,我们会聚焦于以下两个知识点,并且每一个知识点都属于高频考点: 6 | 7 | - 有哪些方式可以减少 Webpack 的打包时间 8 | - 有哪些方式可以让 Webpack 打出来的包更小 9 | 10 | ### 减少 Webpack 打包时间 11 | 12 | #### 优化 Loader 13 | 14 | 对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,**转换代码越多,效率就越低**。当然了,我们是有办法优化的。 15 | 16 | 首先我们可以**优化 Loader 的文件搜索范围** 17 | 18 | ```js 19 | module.exports = { 20 | module: { 21 | rules: [ 22 | { 23 | // js 文件才使用 babel 24 | test: /\.js$/, 25 | loader: 'babel-loader', 26 | // 只在 src 文件夹下查找 27 | include: [resolve('src')], 28 | // 不会去查找的路径 29 | exclude: /node_modules/ 30 | } 31 | ] 32 | } 33 | } 34 | ``` 35 | 36 | 对于 Babel 来说,我们肯定是希望只作用在 JS 代码上的,然后 `node_modules` 中使用的代码都是编译过的,所以我们也完全没有必要再去处理一遍。 37 | 38 | 当然这样做还不够,我们还可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间 39 | 40 | ```js 41 | loader: 'babel-loader?cacheDirectory=true' 42 | ``` 43 | 44 | #### HappyPack 45 | 46 | 受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。 47 | 48 | **HappyPack 可以将 Loader 的同步执行转换为并行的**,这样就能充分利用系统资源来加快打包效率了 49 | 50 | ```js 51 | module: { 52 | loaders: [ 53 | { 54 | test: /\.js$/, 55 | include: [resolve('src')], 56 | exclude: /node_modules/, 57 | // id 后面的内容对应下面 58 | loader: 'happypack/loader?id=happybabel' 59 | } 60 | ] 61 | }, 62 | plugins: [ 63 | new HappyPack({ 64 | id: 'happybabel', 65 | loaders: ['babel-loader?cacheDirectory'], 66 | // 开启 4 个线程 67 | threads: 4 68 | }) 69 | ] 70 | ``` 71 | 72 | #### DllPlugin 73 | 74 | **DllPlugin 可以将特定的类库提前打包然后引入。**这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。 75 | 76 | 接下来我们就来学习如何使用 DllPlugin 77 | 78 | ```js 79 | // 单独配置在一个文件中 80 | // webpack.dll.conf.js 81 | const path = require('path') 82 | const webpack = require('webpack') 83 | module.exports = { 84 | entry: { 85 | // 想统一打包的类库 86 | vendor: ['react'] 87 | }, 88 | output: { 89 | path: path.join(__dirname, 'dist'), 90 | filename: '[name].dll.js', 91 | library: '[name]-[hash]' 92 | }, 93 | plugins: [ 94 | new webpack.DllPlugin({ 95 | // name 必须和 output.library 一致 96 | name: '[name]-[hash]', 97 | // 该属性需要与 DllReferencePlugin 中一致 98 | context: __dirname, 99 | path: path.join(__dirname, 'dist', '[name]-manifest.json') 100 | }) 101 | ] 102 | } 103 | ``` 104 | 105 | 然后我们需要执行这个配置文件生成依赖文件,接下来我们需要使用 `DllReferencePlugin` 将依赖文件引入项目中 106 | 107 | ```js 108 | // webpack.conf.js 109 | module.exports = { 110 | // ...省略其他配置 111 | plugins: [ 112 | new webpack.DllReferencePlugin({ 113 | context: __dirname, 114 | // manifest 就是之前打包出来的 json 文件 115 | manifest: require('./dist/vendor-manifest.json'), 116 | }) 117 | ] 118 | } 119 | ``` 120 | 121 | #### 代码压缩 122 | 123 | 在 Webpack3 中,我们一般使用 `UglifyJS` 来压缩代码,但是这个是单线程运行的,为了加快效率,我们可以使用 `webpack-parallel-uglify-plugin` 来并行运行 `UglifyJS`,从而提高效率。 124 | 125 | 在 Webpack4 中,我们就不需要以上这些操作了,只需要将 `mode` 设置为 `production` 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 `console.log` 这类代码的功能。 126 | 127 | #### 一些小的优化点 128 | 129 | 我们还可以通过一些小的优化点来加快打包速度 130 | 131 | - `resolve.extensions` 用来表明文件后缀列表,默认查找顺序是 `['.js', '.json']`,如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面。 132 | 133 | - `resolve.alias` 可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径。 134 | 135 | - `module.noParse` 如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助。 136 | 137 | ### 减少 Webpack 打包后的文件体积 138 | 139 | > 注意:该内容也属于性能优化领域。 140 | 141 | #### 按需加载 142 | 143 | 想必大家在开发 SPA 项目的时候,项目中都会存在十几甚至更多的路由页面。如果我们将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,我们肯定是希望首页能加载的文件体积越小越好,**这时候我们就可以使用按需加载,将每个路由页面单独打包为一个文件**。当然不仅仅路由可以按需加载,对于 `loadash` 这种大型类库同样可以使用这个功能。 144 | 145 | 按需加载的代码实现这里就不详细展开了,因为鉴于用的框架不同,实现起来都是不一样的。当然了,虽然他们的用法可能不同,但是底层的机制都是一样的。都是当使用的时候再去下载对应文件,返回一个 `Promise`,当 `Promise` 成功以后去执行回调。 146 | 147 | #### Scope Hoisting 148 | 149 | **Scope Hoisting 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。** 150 | 151 | 比如我们希望打包两个文件 152 | 153 | ```js 154 | // test.js 155 | export const a = 1 156 | // index.js 157 | import { a } from './test.js' 158 | ``` 159 | 160 | 对于这种情况,我们打包出来的代码会类似这样 161 | 162 | ```js 163 | [ 164 | /* 0 */ 165 | function (module, exports, require) { 166 | //... 167 | }, 168 | /* 1 */ 169 | function (module, exports, require) { 170 | //... 171 | } 172 | ] 173 | ``` 174 | 175 | 但是如果我们使用 Scope Hoisting 的话,代码就会尽可能的合并到一个函数中去,也就变成了这样的类似代码 176 | 177 | ```js 178 | [ 179 | /* 0 */ 180 | function (module, exports, require) { 181 | //... 182 | } 183 | ] 184 | ``` 185 | 186 | 这样的打包方式生成的代码明显比之前的少多了。如果在 Webpack4 中你希望开启这个功能,只需要启用 `optimization.concatenateModules` 就可以了。 187 | 188 | ```js 189 | module.exports = { 190 | optimization: { 191 | concatenateModules: true 192 | } 193 | } 194 | ``` 195 | 196 | #### Tree Shaking 197 | 198 | Tree Shaking 可以实现删除项目中未被引用的代码,比如 199 | 200 | ```js 201 | // test.js 202 | export const a = 1 203 | export const b = 2 204 | // index.js 205 | import { a } from './test.js' 206 | ``` 207 | 208 | 对于以上情况,`test` 文件中的变量 `b` 如果没有在项目中使用到的话,就不会被打包到文件中。 209 | 210 | 如果你使用 Webpack 4 的话,开启生产环境就会自动启动这个优化功能。 211 | 212 | 213 | ### 小结 214 | 215 | 在这一章节中,我们学习了如何使用 Webpack 去进行性能优化以及如何减少打包时间。 216 | 217 | Webpack 的版本更新很快,各个版本之间实现优化的方式可能都会有区别,所以我没有使用过多的代码去展示如何实现一个功能。这一章节的重点是学习到我们可以通过什么方式去优化,具体的代码实现可以查找具体版本对应的代码即可。 218 | -------------------------------------------------------------------------------- /前端面试之道/17.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 实现小型打包工具 4 | 5 | 原本小册计划中是没有这一章节的,Webpack 工作原理应该是上一章节包含的内容。但是考虑到既然讲到工作原理,必然需要讲解源码,但是 Webpack 的源码很难读,不结合源码干巴巴讲原理又没有什么价值。所以在这一章节中,我将会带大家来实现一个几十行的迷你打包工具,该工具可以实现以下两个功能 6 | 7 | - 将 ES6 转换为 ES5。 8 | - 支持在 JS 文件中 import CSS 文件。 9 | 10 | 通过这个工具的实现,大家可以理解到打包工具的原理到底是什么。 11 | 12 | ### 实现 13 | 14 | 因为涉及到 ES6 转 ES5,所以我们首先需要安装一些 Babel 相关的工具 15 | 16 | ``` 17 | yarn add babylon babel-traverse babel-core babel-preset-env 18 | ``` 19 | 20 | 接下来我们将这些工具引入文件中 21 | 22 | ```js 23 | const fs = require('fs') 24 | const path = require('path') 25 | const babylon = require('babylon') 26 | const traverse = require('babel-traverse').default 27 | const { transformFromAst } = require('babel-core') 28 | ``` 29 | 30 | 首先,我们先来实现如何使用 Babel 转换代码 31 | 32 | ```js 33 | function readCode(filePath) { 34 | // 读取文件内容 35 | const content = fs.readFileSync(filePath, 'utf-8') 36 | // 生成 AST 37 | const ast = babylon.parse(content, { 38 | sourceType: 'module' 39 | }) 40 | // 寻找当前文件的依赖关系 41 | const dependencies = [] 42 | traverse(ast, { 43 | ImportDeclaration: ({ node }) => { 44 | dependencies.push(node.source.value) 45 | } 46 | }) 47 | // 通过 AST 将代码转为 ES5 48 | const { code } = transformFromAst(ast, null, { 49 | presets: ['env'] 50 | }) 51 | return { 52 | filePath, 53 | dependencies, 54 | code 55 | } 56 | } 57 | ``` 58 | 59 | - 首先我们传入一个文件路径参数,然后通过 `fs` 将文件中的内容读取出来。 60 | 61 | - 接下来我们通过 `babylon` 解析代码获取 AST,目的是为了分析代码中是否还引入了别的文件。 62 | 63 | - 通过 `dependencies` 来存储文件中的依赖,然后再将 AST 转换为 ES5 代码。 64 | 65 | - 最后函数返回了一个对象,对象中包含了当前文件路径、当前文件依赖和当前文件转换后的代码。 66 | 67 | 接下来我们需要实现一个函数,这个函数的功能有以下几点: 68 | 69 | - 调用 readCode 函数,传入入口文件。 70 | - 分析入口文件的依赖。 71 | - 识别 JS 和 CSS 文件。 72 | 73 | ```js 74 | function getDependencies(entry) { 75 | // 读取入口文件 76 | const entryObject = readCode(entry) 77 | const dependencies = [entryObject] 78 | // 遍历所有文件依赖关系 79 | for (const asset of dependencies) { 80 | // 获得文件目录 81 | const dirname = path.dirname(asset.filePath) 82 | // 遍历当前文件依赖关系 83 | asset.dependencies.forEach(relativePath => { 84 | // 获得绝对路径 85 | const absolutePath = path.join(dirname, relativePath) 86 | // CSS 文件逻辑就是将代码插入到 `style` 标签中 87 | if (/\.css$/.test(absolutePath)) { 88 | const content = fs.readFileSync(absolutePath, 'utf-8') 89 | const code = ` 90 | const style = document.createElement('style') 91 | style.innerText = ${JSON.stringify(content).replace(/\\r\\n/g, '')} 92 | document.head.appendChild(style) 93 | ` 94 | dependencies.push({ 95 | filePath: absolutePath, 96 | relativePath, 97 | dependencies: [], 98 | code 99 | }) 100 | } else { 101 | // JS 代码需要继续查找是否有依赖关系 102 | const child = readCode(absolutePath) 103 | child.relativePath = relativePath 104 | dependencies.push(child) 105 | } 106 | }) 107 | } 108 | return dependencies 109 | } 110 | ``` 111 | 112 | - 首先我们读取入口文件,然后创建一个数组,该数组的目的是存储代码中涉及到的所有文件。 113 | 114 | - 接下来我们遍历这个数组,一开始这个数组中只有入口文件,在遍历的过程中,如果入口文件有依赖其他的文件,那么就会被 `push` 到这个数组中。 115 | 116 | - 在遍历的过程中,我们先获得该文件对应的目录,然后遍历当前文件的依赖关系。 117 | 118 | - 在遍历当前文件依赖关系的过程中,首先生成依赖文件的绝对路径,然后判断当前文件是 CSS 文件还是 JS 文件。 119 | 120 | - 如果是 CSS 文件的话,我们就不能用 Babel 去编译了,只需要读取 CSS 文件中的代码,然后创建一个 `style` 标签,将代码插入进标签并且放入 `head` 中即可。 121 | - 如果是 JS 文件的话,我们还需要分析 JS 文件是否还有别的依赖关系。 122 | - 最后将读取文件后的对象 `push` 进数组中。 123 | 124 | 现在我们已经获取到了所有的依赖文件,接下来就是实现打包的功能了 125 | 126 | ```js 127 | function bundle(dependencies, entry) { 128 | let modules = '' 129 | // 构建函数参数,生成的结构为 130 | // { './entry.js': function(module, exports, require) { 代码 } } 131 | dependencies.forEach(dep => { 132 | const filePath = dep.relativePath || entry 133 | modules += `'${filePath}': ( 134 | function (module, exports, require) { ${dep.code} } 135 | ),` 136 | }) 137 | // 构建 require 函数,目的是为了获取模块暴露出来的内容 138 | const result = ` 139 | (function(modules) { 140 | function require(id) { 141 | const module = { exports : {} } 142 | modules[id](module, module.exports, require) 143 | return module.exports 144 | } 145 | require('${entry}') 146 | })({${modules}}) 147 | ` 148 | // 当生成的内容写入到文件中 149 | fs.writeFileSync('./bundle.js', result) 150 | } 151 | ``` 152 | 153 | 这段代码需要结合着 Babel 转换后的代码来看,这样大家就能理解为什么需要这样写了 154 | 155 | ```js 156 | // entry.js 157 | var _a = require('./a.js') 158 | var _a2 = _interopRequireDefault(_a) 159 | function _interopRequireDefault(obj) { 160 | return obj && obj.__esModule ? obj : { default: obj } 161 | } 162 | console.log(_a2.default) 163 | // a.js 164 | Object.defineProperty(exports, '__esModule', { 165 | value: true 166 | }) 167 | var a = 1 168 | exports.default = a 169 | ``` 170 | 171 | Babel 将我们 ES6 的模块化代码转换为了 CommonJS(如果你不熟悉 CommonJS 的话,可以阅读这一章节中关于 模块化的知识点)的代码,但是浏览器是不支持 CommonJS 的,所以如果这段代码需要在浏览器环境下运行的话,我们需要自己实现 CommonJS 相关的代码,这就是 `bundle` 函数做的大部分事情。 172 | 173 | 接下来我们再来逐行解析 `bundle` 函数 174 | 175 | - 首先遍历所有依赖文件,构建出一个函数参数对象。 176 | 177 | - 对象的属性就是当前文件的相对路径,属性值是一个函数,函数体是当前文件下的代码,函数接受三个参数 `module`、`exports`、`require`。 178 | - `module` 参数对应 CommonJS 中的 `module`。 179 | - `exports` 参数对应 CommonJS 中的 `module.export`。 180 | - `require` 参数对应我们自己创建的 `require` 函数。 181 | 182 | - 接下来就是构造一个使用参数的函数了,函数做的事情很简单,就是内部创建一个 `require` 函数,然后调用 `require(entry)`,也就是 `require('./entry.js')`,这样就会从函数参数中找到 `./entry.js` 对应的函数并执行,最后将导出的内容通过 `module.export` 的方式让外部获取到。 183 | 184 | - 最后再将打包出来的内容写入到单独的文件中。 185 | 186 | 如果你对于上面的实现还有疑惑的话,可以阅读下打包后的部分简化代码 187 | 188 | ```js 189 | ;(function(modules) { 190 | function require(id) { 191 | // 构造一个 CommonJS 导出代码 192 | const module = { exports: {} } 193 | // 去参数中获取文件对应的函数并执行 194 | modules[id](module, module.exports, require) 195 | return module.exports 196 | } 197 | require('./entry.js') 198 | })({ 199 | './entry.js': function(module, exports, require) { 200 | // 这里继续通过构造的 require 去找到 a.js 文件对应的函数 201 | var _a = require('./a.js') 202 | console.log(_a2.default) 203 | }, 204 | './a.js': function(module, exports, require) { 205 | var a = 1 206 | // 将 require 函数中的变量 module 变成了这样的结构 207 | // module.exports = 1 208 | // 这样就能在外部取到导出的内容了 209 | exports.default = a 210 | } 211 | // 省略 212 | }) 213 | ``` 214 | 215 | ### 小结 216 | 217 | 虽然实现这个工具只写了不到 100 行的代码,但是打包工具的核心原理就是这些了。 218 | 219 | 1. 找出入口文件所有的依赖关系。 220 | 2. 然后通过构建 CommonJS 代码来获取 exports 导出的内容。 221 | 222 | 如果大家对于这个章节的内容存在疑问,欢迎在评论区与我互动。 -------------------------------------------------------------------------------- /前端面试之道/18.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## React 和 Vue 两大框架之间的相爱相杀 4 | 5 | React 和 Vue 应该是国内当下最火热的前端框架,当然 Angular 也是一个不错的框架,但是这个产品国内使用的人很少再加上我对 Angular 也不怎么熟悉,所以框架的章节中不会涉及到 Angular 的内容。 6 | 7 | 这一章节,我们将会来学习以下几个内容 8 | 9 | - MVVM 是什么。 10 | - Virtual DOM 是什么。 11 | - 前端路由是如何跳转的。 12 | - React 和 Vue 之间的区别。 13 | 14 | ### MVVM 15 | 16 | > 涉及面试题:什么是 MVVM?比之 MVC 有什么区别? 17 | 18 | 首先先申明一点,不管是 React 还是 Vue,它们都不是 MVVM 框架,只是有借鉴 MVVM 的思路。文中拿 Vue 举例也是为了更好地理解 MVVM 的概念。 19 | 20 | 接下来先说下 View 和 Model: 21 | 22 | - View 很简单,就是用户看到的视图。 23 | - Model 同样很简单,一般就是本地数据和数据库中的数据。 24 | 25 | 基本上,我们写的产品就是通过接口从数据库中读取数据,然后将数据经过处理展现到用户看到的视图上。当然我们还可以从视图上读取用户的输入,然后又将用户的输入通过接口写入到数据库中。但是,如何将数据展示到视图上,然后又如何将用户的输入写入到数据中,不同的人就产生了不同的看法,从此出现了很多种架构设计。 26 | 27 | 传统的 MVC 架构通常是使用控制器更新模型,视图从模型中获取数据去渲染。当用户有输入时,会通过控制器去更新模型,并且通知视图进行更新。 28 | 29 | ![](./images/065.webp) 30 | 31 | 但是 MVC 有一个巨大的缺陷就是**控制器承担的责任太大**了,随着项目愈加复杂,控制器中的代码会越来越臃肿,导致出现不利于维护的情况。 32 | 33 | 在 MVVM 架构中,引入了 ViewModel 的概念。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。 34 | 35 | ![](./images/066.webp) 36 | 37 | 以 Vue 框架来举例,ViewModel 就是组件的实例。View 就是模板,Model 的话在引入 Vuex 的情况下是完全可以和组件分离的。 38 | 39 | 除了以上三个部分,其实在 MVVM 中还引入了一个隐式的 Binder 层,实现了 View 和 ViewModel 的绑定。 40 | 41 | ![](./images/067.webp) 42 | 43 | 同样以 Vue 框架来举例,这个隐式的 Binder 层就是 Vue 通过解析模板中的插值和指令从而实现 View 与 ViewModel 的绑定。 44 | 45 | 对于 MVVM 来说,其实最重要的并不是通过双向绑定或者其他的方式将 View 与 ViewModel 绑定起来,**而是通过 ViewModel 将视图中的状态和用户的行为分离出一个抽象,这才是 MVVM 的精髓**。 46 | 47 | ### Virtual DOM 48 | 49 | > 涉及面试题:什么是 Virtual DOM?为什么 Virtual DOM 比原生 DOM 快? 50 | 51 | 大家都知道操作 DOM 是很慢的,为什么慢的原因已经在「浏览器渲染原理」章节中说过,这里就不再赘述了。 52 | 53 | 那么相较于 DOM 来说,操作 JS 对象会快很多,并且我们也可以通过 JS 来模拟 DOM 54 | 55 | ```js 56 | const ul = { 57 | tag: 'ul', 58 | props: { 59 | class: 'list' 60 | }, 61 | children: { 62 | tag: 'li', 63 | children: '1' 64 | } 65 | } 66 | ``` 67 | 68 | 上述代码对应的 DOM 就是 69 | 70 | ```js 71 | 74 | ``` 75 | 76 | 那么既然 DOM 可以通过 JS 对象来模拟,反之也可以通过 JS 对象来渲染出对应的 DOM。当然了,通过 JS 来模拟 DOM 并且渲染对应的 DOM 只是第一步,难点在于如何判断新旧两个 JS 对象的最小差异并且实现局部更新 DOM。 77 | 78 | 首先 DOM 是一个多叉树的结构,如果需要完整的对比两颗树的差异,那么需要的时间复杂度会是 O(n ^ 3),这个复杂度肯定是不能接受的。于是 React 团队优化了算法,实现了 O(n) 的复杂度来对比差异。 实现 O(n) 复杂度的关键就是只对比同层的节点,而不是跨层对比,这也是考虑到在实际业务中很少会去跨层的移动 DOM 元素。 所以判断差异的算法就分为了两步 79 | 80 | - 首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后渲染差异。 81 | - 一旦节点有子元素,就去判断子元素是否有不同。 82 | 83 | 在第一步算法中我们需要判断新旧节点的 `tagName` 是否相同,如果不相同的话就代表节点被替换了。如果没有更改 `tagName` 的话,就需要判断是否有子元素,有的话就进行第二步算法。 84 | 85 | 在第二步算法中,我们需要判断原本的列表中是否有节点被移除,在新的列表中需要判断是否有新的节点加入,还需要判断节点是否有移动。 86 | 87 | 举个例子来说,假设页面中只有一个列表,我们对列表中的元素进行了变更 88 | 89 | ```js 90 | // 假设这里模拟一个 ul,其中包含了 5 个 li 91 | [1, 2, 3, 4, 5] 92 | // 这里替换上面的 li 93 | [1, 2, 5, 4] 94 | ``` 95 | 96 | 从上述例子中,我们一眼就可以看出先前的 `ul` 中的第三个 `li` 被移除了,四五替换了位置。 97 | 98 | 那么在实际的算法中,我们如何去识别改动的是哪个节点呢?这就引入了 `key` 这个属性,想必大家在 Vue 或者 React 的列表中都用过这个属性。这个属性是用来给每一个节点打标志的,用于判断是否是同一个节点。 99 | 100 | 当然在判断以上差异的过程中,我们还需要判断节点的属性是否有变化等等。 101 | 102 | 当我们判断出以上的差异后,就可以把这些差异记录下来。当对比完两棵树以后,就可以通过差异去局部更新 DOM,实现性能的最优化。 103 | 104 | 当然了 Virtual DOM 提高性能是其中一个优势,其实最大的优势还是在于: 105 | 106 | - 将 Virtual DOM 作为一个兼容层,让我们还能对接非 Web 端的系统,实现跨端开发。 107 | - 同样的,通过 Virtual DOM 我们可以渲染到其他的平台,比如实现 SSR、同构渲染等等。 108 | - 实现组件的高度抽象化。 109 | 110 | ### 路由原理 111 | 112 | > 涉及面试题:前端路由原理?两种实现方式有什么区别? 113 | 114 | 前端路由实现起来其实很简单,本质就是**监听 URL 的变化**,然后匹配路由规则,显示相应的页面,并且无须刷新页面。目前前端使用的路由就只有两种实现方式 115 | 116 | - Hash 模式。 117 | - History 模式。 118 | 119 | #### Hash 模式 120 | 121 | `www.test.com/#/` 就是 Hash URL,当 `#` 后面的哈希值发生变化时,可以通过 `hashchange` 事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的 URL 请求永远是 `www.test.com`。 122 | 123 | ```js 124 | window.addEventListener('hashchange', () => { 125 | // ... 具体逻辑 126 | }) 127 | ``` 128 | 129 | Hash 模式相对来说更简单,并且兼容性也更好。 130 | 131 | #### History 模式 132 | 133 | History 模式是 HTML5 新推出的功能,主要使用 `history.pushState` 和 `history.replaceState` 改变 URL。 134 | 135 | 通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。 136 | 137 | ```js 138 | // 新增历史记录 139 | history.pushState(stateObject, title, URL) 140 | // 替换当前历史记录 141 | history.replaceState(stateObject, title, URL) 142 | ``` 143 | 144 | 当用户做出浏览器动作时,比如点击后退按钮时会触发 `popState` 事件 145 | 146 | ```js 147 | window.addEventListener('popstate', e => { 148 | // e.state 就是 pushState(stateObject) 中的 stateObject 149 | console.log(e.state) 150 | }) 151 | ``` 152 | 153 | #### 两种模式对比 154 | 155 | Hash 模式只可以更改 `#` 后面的内容,History 模式可以通过 API 设置任意的同源 URL 156 | History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值,也就是字符串 157 | Hash 模式无需后端配置,并且兼容性好。History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置 `index.html` 页面用于匹配不到静态资源的时候。 158 | 159 | ### Vue 和 React 之间的区别 160 | 161 | Vue 的表单可以使用 `v-model` 支持双向绑定,相比于 React 来说开发上更加方便,当然了 `v-model` 其实就是个语法糖,本质上和 React 写表单的方式没什么区别。 162 | 163 | 改变数据方式不同,Vue 修改状态相比来说要简单许多,React 需要使用 `setState` 来改变状态,并且使用这个 API 也有一些坑点。并且 Vue 的底层使用了依赖追踪,页面更新渲染已经是最优的了,但是 React 还是需要用户手动去优化这方面的问题。 164 | 165 | React 16 以后,有些钩子函数会执行多次,这是因为引入 Fiber 的原因,这在后续的章节中会讲到。 166 | 167 | React 需要使用 JSX,有一定的上手成本,并且需要一整套的工具链支持,但是完全可以通过 JS 来控制页面,更加的灵活。Vue 使用了模板语法,相比于 JSX 来说没有那么灵活,但是完全可以脱离工具链,通过直接编写 render 函数就能在浏览器中运行。 168 | 169 | 在生态上来说,两者其实没多大的差距,当然 React 的用户是远远高于 Vue 的。 170 | 171 | 在上手成本上来说,Vue 一开始的定位就是尽可能的降低前端开发的门槛,然而 React 更多的是去改变用户去接受它的概念和思想,相较于 Vue 来说上手成本略高。 172 | 173 | ### 小结 174 | 175 | 这一章节中我们学习了几大框架中的相似点,也对比了 React 和 Vue 之间的区别。其实我们可以发现,React 和 Vue 虽然是两个不同的框架,但是他们的底层原理都是很相似的,无非在上层堆砌了自己的概念上去。所以我们无需去对比到底哪个框架牛逼,引用尤大的一句话: 176 | 177 | > 说到底,就算你证明了 A 比 B 牛逼,也不意味着你或者你的项目就牛逼了... 比起争这个,不如多想想怎么让自己变得更牛逼吧。 178 | 179 | 180 | -------------------------------------------------------------------------------- /前端面试之道/19.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## Vue 常考基础知识点 4 | 5 | 这一章节我们将来学习 Vue 的一些经常考到的基础知识点。 6 | 7 | ### 生命周期钩子函数 8 | 9 | 在 `beforeCreate` 钩子函数调用的时候,是获取不到 `props` 或者 `data` 中的数据的,因为这些数据的初始化都在 `initState` 中。 10 | 11 | 然后会执行 `created` 钩子函数,在这一步的时候已经可以访问到之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。 12 | 13 | 接下来会先执行 `beforeMount` 钩子函数,开始创建 VDOM,最后执行 `mounted` 钩子,并将 VDOM 渲染为真实 DOM 并且渲染数据。组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。 14 | 15 | 接下来是数据更新时会调用的钩子函数 `beforeUpdate` 和 `updated`,这两个钩子函数没什么好说的,就是分别在数据更新前和更新后会调用。 16 | 17 | 另外还有 `keep-alive` 独有的生命周期,分别为 `activated` 和 `deactivated` 。用 `keep-alive` 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 `deactivated` 钩子函数,命中缓存渲染后会执行 `actived` 钩子函数。 18 | 19 | 最后就是销毁组件的钩子函数 `beforeDestroy` 和 `destroyed`。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 `destroyed` 钩子函数。 20 | 21 | ### 组件通信 22 | 23 | 组件通信一般分为以下几种情况: 24 | 25 | - 父子组件通信。 26 | - 兄弟组件通信。 27 | - 跨多层级组件通信。 28 | - 任意组件。 29 | 30 | 对于以上每种情况都有多种方式去实现,接下来就来学习下如何实现。 31 | 32 | #### 父子通信 33 | 34 | 父组件通过 `props` 传递数据给子组件,子组件通过 `emit` 发送事件传递数据给父组件,这两种方式是最常用的父子通信实现办法。 35 | 36 | 这种父子通信方式也就是典型的单向数据流,父组件通过 `props` 传递数据,子组件不能直接修改 `props`, 而是必须通过发送事件的方式告知父组件修改数据。 37 | 38 | 另外这两种方式还可以使用语法糖 `v-model` 来直接实现,因为 `v-model` 默认会解析成名为 `value` 的 `prop` 和名为 `input` 的事件。这种语法糖的方式是典型的双向绑定,常用于 UI 控件上,但是究其根本,还是通过事件的方法让父组件修改数据。 39 | 40 | 当然我们还可以通过访问 `$parent` 或者 `$children` 对象来访问组件实例中的方法和数据。 41 | 42 | 另外如果你使用 Vue 2.3 及以上版本的话还可以使用 `$listeners` 和 `.sync` 这两个属性。 43 | 44 | `$listeners` 属性会将父组件中的 (不含 `.native` 修饰器的) `v-on` 事件监听器传递给子组件,子组件可以通过访问 `$listeners` 来自定义监听器。 45 | 46 | `.sync` 属性是个语法糖,可以很简单的实现子组件与父组件通信 47 | 48 | ```html 49 | 50 | 51 | 52 | 53 | 54 | 57 | ``` 58 | 59 | #### 兄弟组件通信 60 | 61 | 对于这种情况可以通过查找父组件中的子组件实现,也就是 `this.$parent.$children`,在 `$children` 中可以通过组件 `name` 查询到需要的组件实例,然后进行通信。 62 | 63 | #### 跨多层次组件通信 64 | 65 | 对于这种情况可以使用 Vue 2.2 新增的 API `provide / inject`,虽然文档中不推荐直接使用在业务中,但是如果用得好的话还是很有用的。 66 | 67 | 假设有父组件 A,然后有一个跨多层级的子组件 B 68 | 69 | ```js 70 | // 父组件 A 71 | export default { 72 | provide: { 73 | data: 1 74 | } 75 | } 76 | // 子组件 B 77 | export default { 78 | inject: ['data'], 79 | mounted() { 80 | // 无论跨几层都能获得父组件的 data 属性 81 | console.log(this.data) // => 1 82 | } 83 | } 84 | ``` 85 | 86 | #### 任意组件 87 | 88 | 这种方式可以通过 Vuex 或者 Event Bus 解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况 89 | 90 | ### extend 能做什么 91 | 92 | 这个 API 很少用到,作用是扩展组件生成一个构造器,通常会与 `$mount` 一起使用。 93 | 94 | ```js 95 | // 创建组件构造器 96 | let Component = Vue.extend({ 97 | template: '
test
' 98 | }) 99 | // 挂载到 #app 上 100 | new Component().$mount('#app') 101 | // 除了上面的方式,还可以用来扩展已有的组件 102 | let SuperComponent = Vue.extend(Component) 103 | new SuperComponent({ 104 | created() { 105 | console.log(1) 106 | } 107 | }) 108 | new SuperComponent().$mount('#app') 109 | ``` 110 | 111 | ### mixin 和 mixins 区别 112 | 113 | `mixin` 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。 114 | 115 | ```js 116 | Vue.mixin({ 117 | beforeCreate() { 118 | // ...逻辑 119 | // 这种方式会影响到每个组件的 beforeCreate 钩子函数 120 | } 121 | }) 122 | ``` 123 | 124 | 虽然文档不建议我们在应用中直接使用 `mixin`,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 `ajax` 或者一些工具函数等等。 125 | 126 | `mixins` 应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 `mixins` 混入代码,比如上拉下拉加载数据这种逻辑等等。 127 | 128 | 另外需要注意的是 `mixins` 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并,具体可以阅读文档。 129 | 130 | ### computed 和 watch 区别 131 | 132 | `computed` 是计算属性,依赖其他属性计算值,并且 `computed` 的值有缓存,只有当计算值变化才会返回内容。 133 | 134 | `watch` 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。 135 | 136 | 所以一般来说需要依赖别的属性来动态获得值的时候可以使用 `computed`,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 `watch`。 137 | 138 | 另外 `computed` 和 `watch` 还都支持对象的写法,这种方式知道的人并不多。 139 | 140 | ```js 141 | vm.$watch('obj', { 142 | // 深度遍历 143 | deep: true, 144 | // 立即触发 145 | immediate: true, 146 | // 执行的函数 147 | handler: function(val, oldVal) {} 148 | }) 149 | var vm = new Vue({ 150 | data: { a: 1 }, 151 | computed: { 152 | aPlus: { 153 | // this.aPlus 时触发 154 | get: function () { 155 | return this.a + 1 156 | }, 157 | // this.aPlus = 1 时触发 158 | set: function (v) { 159 | this.a = v - 1 160 | } 161 | } 162 | } 163 | }) 164 | ``` 165 | 166 | ### keep-alive 组件有什么作用 167 | 168 | 如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 `keep-alive` 组件包裹需要保存的组件。 169 | 170 | 对于 `keep-alive` 组件来说,它拥有两个独有的生命周期钩子函数,分别为 `activated` 和 `deactivated`。用 `keep-alive `包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 `deactivated` 钩子函数,命中缓存渲染后会执行 `actived` 钩子函数。 171 | 172 | ### v-show 与 v-if 区别 173 | 174 | `v-show `只是在 `display: none` 和 `display: block` 之间切换。无论初始条件是什么都会被渲染出来,后面只需要切换 CSS,DOM 还是一直保留着的。所以总的来说 `v-show` 在初始渲染时有更高的开销,但是切换开销很小,更适合于频繁切换的场景。 175 | 176 | `v-if` 的话就得说到 Vue 底层的编译了。当属性初始为 `false` 时,组件就不会被渲染,直到条件为 `true`,并且切换条件时会触发销毁/挂载组件,所以总的来说在切换时开销更高,更适合不经常切换的场景。 177 | 178 | 并且基于 `v-if` 的这种惰性渲染机制,可以在必要的时候才去渲染组件,减少整个页面的初始渲染开销。 179 | 180 | 181 | ### 组件中 data 什么时候可以使用对象 182 | 183 | 这道题目其实更多考的是 JS 功底。 184 | 185 | 组件复用时所有组件实例都会共享 `data`,如果 `data` 是对象的话,就会造成一个组件修改 `data` 以后会影响到其他所有组件,所以需要将 `data` 写成函数,每次用到就调用一次函数获得新的数据。 186 | 187 | 当我们使用 `new Vue()` 的方式的时候,无论我们将 `data` 设置为对象还是函数都是可以的,因为 `new Vue()` 的方式是生成一个根组件,该组件不会复用,也就不存在共享 `data` 的情况了。 188 | 189 | ### 小结 190 | 191 | 总的来说这一章节的内容更多的偏向于 Vue 的基础,下一章节我们将来了解一些原理性方面的知识。 -------------------------------------------------------------------------------- /前端面试之道/20.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## Vue 常考进阶知识点 4 | 5 | 这一章节我们将来学习 Vue 的一些经常考到的进阶知识点。这些知识点相对而言理解起来会很有难度,可能需要多次阅读才能理解。 6 | 7 | ### 响应式原理 8 | 9 | Vue 内部使用了 `Object.defineProperty()` 来实现数据响应式,通过这个函数可以监听到 `set` 和 `get` 的事件。 10 | 11 | ```js 12 | var data = { name: 'yck' } 13 | observe(data) 14 | let name = data.name // -> get value 15 | data.name = 'yyy' // -> change value 16 | 17 | function observe(obj) { 18 | // 判断类型 19 | if (!obj || typeof obj !== 'object') { 20 | return 21 | } 22 | Object.keys(obj).forEach(key => { 23 | defineReactive(obj, key, obj[key]) 24 | }) 25 | } 26 | 27 | function defineReactive(obj, key, val) { 28 | // 递归子属性 29 | observe(val) 30 | Object.defineProperty(obj, key, { 31 | // 可枚举 32 | enumerable: true, 33 | // 可配置 34 | configurable: true, 35 | // 自定义函数 36 | get: function reactiveGetter() { 37 | console.log('get value') 38 | return val 39 | }, 40 | set: function reactiveSetter(newVal) { 41 | console.log('change value') 42 | val = newVal 43 | } 44 | }) 45 | } 46 | ``` 47 | 48 | 以上代码简单的实现了如何监听数据的 `set` 和 `get` 的事件,但是仅仅如此是不够的,因为自定义的函数一开始是不会执行的。只有先执行了依赖收集,才能在属性更新的时候派发更新,所以接下来我们需要先触发依赖收集。 49 | 50 | ```js 51 |
52 | {{name}} 53 |
54 | ``` 55 | 56 | 在解析如上模板代码时,遇到 `{{name}}` 就会进行依赖收集。 57 | 58 | 接下来我们先来实现一个 `Dep` 类,用于解耦属性的依赖收集和派发更新操作。 59 | 60 | ```js 61 | // 通过 Dep 解耦属性的依赖和更新操作 62 | class Dep { 63 | constructor() { 64 | this.subs = [] 65 | } 66 | // 添加依赖 67 | addSub(sub) { 68 | this.subs.push(sub) 69 | } 70 | // 更新 71 | notify() { 72 | this.subs.forEach(sub => { 73 | sub.update() 74 | }) 75 | } 76 | } 77 | // 全局属性,通过该属性配置 Watcher 78 | Dep.target = null 79 | ``` 80 | 81 | 以上的代码实现很简单,当需要依赖收集的时候调用 `addSub`,当需要派发更新的时候调用 `notify`。 82 | 83 | 接下来我们先来简单的了解下 Vue 组件挂载时添加响应式的过程。在组件挂载时,会先对所有需要的属性调用 `Object.defineProperty()`,然后实例化 ``Watcher``,传入组件更新的回调。在实例化过程中,会对模板中的属性进行求值,触发依赖收集。 84 | 85 | 因为这一小节主要目的是学习响应式原理的细节,所以接下来的代码会简略的表达触发依赖收集时的操作。 86 | 87 | ```js 88 | class Watcher { 89 | constructor(obj, key, cb) { 90 | // 将 Dep.target 指向自己 91 | // 然后触发属性的 getter 添加监听 92 | // 最后将 Dep.target 置空 93 | Dep.target = this 94 | this.cb = cb 95 | this.obj = obj 96 | this.key = key 97 | this.value = obj[key] 98 | Dep.target = null 99 | } 100 | update() { 101 | // 获得新值 102 | this.value = this.obj[this.key] 103 | // 调用 update 方法更新 Dom 104 | this.cb(this.value) 105 | } 106 | } 107 | ``` 108 | 109 | 以上就是 `Watcher` 的简单实现,在执行构造函数的时候将 `Dep.target` 指向自身,从而使得收集到了对应的 `Watcher`,在派发更新的时候取出对应的 `Watcher` 然后执行 `update` 函数。 110 | 111 | 接下来,需要对 `defineReactive` 函数进行改造,在自定义函数中添加依赖收集和派发更新相关的代码。 112 | 113 | ```js 114 | function defineReactive(obj, key, val) { 115 | // 递归子属性 116 | observe(val) 117 | let dp = new Dep() 118 | Object.defineProperty(obj, key, { 119 | enumerable: true, 120 | configurable: true, 121 | get: function reactiveGetter() { 122 | console.log('get value') 123 | // 将 Watcher 添加到订阅 124 | if (Dep.target) { 125 | dp.addSub(Dep.target) 126 | } 127 | return val 128 | }, 129 | set: function reactiveSetter(newVal) { 130 | console.log('change value') 131 | val = newVal 132 | // 执行 watcher 的 update 方法 133 | dp.notify() 134 | } 135 | }) 136 | } 137 | ``` 138 | 139 | 以上所有代码实现了一个简易的数据响应式,核心思路就是手动触发一次属性的 `getter` 来实现依赖收集。 140 | 141 | 现在我们就来测试下代码的效果,只需要把所有的代码复制到浏览器中执行,就会发现页面的内容全部被替换了。 142 | 143 | ```js 144 | var data = { name: 'yck' } 145 | observe(data) 146 | function update(value) { 147 | document.querySelector('div').innerText = value 148 | } 149 | // 模拟解析到 `{{name}}` 触发的操作 150 | new Watcher(data, 'name', update) 151 | // update Dom innerText 152 | data.name = 'yyy' 153 | ``` 154 | 155 | #### Object.defineProperty 的缺陷 156 | 157 | 以上已经分析完了 Vue 的响应式原理,接下来说一点 `Object.defineProperty` 中的缺陷。 158 | 159 | 如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为 `Object.defineProperty` 不能拦截到这些操作,更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。 160 | 161 | 对于第一个问题,Vue 提供了一个 API 解决 162 | 163 | ```js 164 | export function set (target: Array | Object, key: any, val: any): any { 165 | // 判断是否为数组且下标是否有效 166 | if (Array.isArray(target) && isValidArrayIndex(key)) { 167 | // 调用 splice 函数触发派发更新 168 | // 该函数已被重写 169 | target.length = Math.max(target.length, key) 170 | target.splice(key, 1, val) 171 | return val 172 | } 173 | // 判断 key 是否已经存在 174 | if (key in target && !(key in Object.prototype)) { 175 | target[key] = val 176 | return val 177 | } 178 | const ob = (target: any).__ob__ 179 | // 如果对象不是响应式对象,就赋值返回 180 | if (!ob) { 181 | target[key] = val 182 | return val 183 | } 184 | // 进行双向绑定 185 | defineReactive(ob.value, key, val) 186 | // 手动派发更新 187 | ob.dep.notify() 188 | return val 189 | } 190 | ``` 191 | 192 | 对于数组而言,Vue 内部重写了以下函数实现派发更新 193 | 194 | ```js 195 | // 获得数组原型 196 | const arrayProto = Array.prototype 197 | export const arrayMethods = Object.create(arrayProto) 198 | // 重写以下函数 199 | const methodsToPatch = [ 200 | 'push', 201 | 'pop', 202 | 'shift', 203 | 'unshift', 204 | 'splice', 205 | 'sort', 206 | 'reverse' 207 | ] 208 | methodsToPatch.forEach(function (method) { 209 | // 缓存原生函数 210 | const original = arrayProto[method] 211 | // 重写函数 212 | def(arrayMethods, method, function mutator (...args) { 213 | // 先调用原生函数获得结果 214 | const result = original.apply(this, args) 215 | const ob = this.__ob__ 216 | let inserted 217 | // 调用以下几个函数时,监听新数据 218 | switch (method) { 219 | case 'push': 220 | case 'unshift': 221 | inserted = args 222 | break 223 | case 'splice': 224 | inserted = args.slice(2) 225 | break 226 | } 227 | if (inserted) ob.observeArray(inserted) 228 | // 手动派发更新 229 | ob.dep.notify() 230 | return result 231 | }) 232 | }) 233 | ``` 234 | 235 | ### 编译过程 236 | 237 | 想必大家在使用 Vue 开发的过程中,基本都是使用模板的方式。那么你有过「模板是怎么在浏览器中运行的」这种疑虑嘛? 238 | 239 | 首先直接把模板丢到浏览器中肯定是不能运行的,模板只是为了方便开发者进行开发。Vue 会通过编译器将模板通过几个阶段最终编译为 `render` 函数,然后通过执行 `render` 函数生成 Virtual DOM 最终映射为真实 DOM。 240 | 241 | 接下来我们就来学习这个编译的过程,了解这个过程中大概发生了什么事情。这个过程其中又分为三个阶段,分别为: 242 | 243 | - 将模板解析为 AST。 244 | - 优化 AST。 245 | - 将 AST 转换为 render 函数。 246 | 247 | 在第一个阶段中,最主要的事情还是通过各种各样的正则表达式去匹配模板中的内容,然后将内容提取出来做各种逻辑操作,接下来会生成一个最基本的 AST 对象 248 | 249 | ```js 250 | { 251 | // 类型 252 | type: 1, 253 | // 标签 254 | tag, 255 | // 属性列表 256 | attrsList: attrs, 257 | // 属性映射 258 | attrsMap: makeAttrsMap(attrs), 259 | // 父节点 260 | parent, 261 | // 子节点 262 | children: [] 263 | } 264 | ``` 265 | 266 | 然后会根据这个最基本的 AST 对象中的属性,进一步扩展 AST。 267 | 268 | 当然在这一阶段中,还会进行其他的一些判断逻辑。比如说对比前后开闭标签是否一致,判断根组件是否只存在一个,判断是否符合 HTML5 Content Model 规范等等问题。 269 | 270 | 接下来就是优化 AST 的阶段。在当前版本下,Vue 进行的优化内容其实还是不多的。只是对节点进行了静态内容提取,也就是将永远不会变动的节点提取了出来,实现复用 Virtual DOM,跳过对比算法的功能。在下一个大版本中,Vue 会在优化 AST 的阶段继续发力,实现更多的优化功能,尽可能的在编译阶段压榨更多的性能,比如说提取静态的属性等等优化行为。 271 | 272 | 最后一个阶段就是通过 AST 生成 `render` 函数了。其实这一阶段虽然分支有很多,但是最主要的目的就是遍历整个 AST,根据不同的条件生成不同的代码罢了。 273 | 274 | ### NextTick 原理分析 275 | 276 | `nextTick` 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。 277 | 278 | 在 Vue 2.4 之前都是使用的 `microtasks`,但是 `microtasks` 的优先级过高,在某些情况下可能会出现比事件冒泡更快的情况,但如果都使用 `macrotasks` 又可能会出现渲染的性能问题。所以在新版本中,会默认使用 `microtasks`,但在特殊情况下会使用 `macrotasks`,比如 `v-on`。 279 | 280 | 对于实现 `macrotasks`,会先判断是否能使用 `setImmediate`,不能的话降级为 `MessageChannel`,以上都不行的话就使用 `setTimeout` 281 | 282 | ```js 283 | if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { 284 | macroTimerFunc = () => { 285 | setImmediate(flushCallbacks) 286 | } 287 | } else if ( 288 | typeof MessageChannel !== 'undefined' && 289 | (isNative(MessageChannel) || 290 | // PhantomJS 291 | MessageChannel.toString() === '[object MessageChannelConstructor]') 292 | ) { 293 | const channel = new MessageChannel() 294 | const port = channel.port2 295 | channel.port1.onmessage = flushCallbacks 296 | macroTimerFunc = () => { 297 | port.postMessage(1) 298 | } 299 | } else { 300 | macroTimerFunc = () => { 301 | setTimeout(flushCallbacks, 0) 302 | } 303 | } 304 | ``` 305 | 306 | 以上代码很简单,就是判断能不能使用相应的 API。 307 | 308 | ### 小结 309 | 310 | 以上就是 Vue 的几个高频核心问题了,如果你还想了解更多的源码相关的细节,强烈推荐黄老师的 Vue 技术揭秘。 -------------------------------------------------------------------------------- /前端面试之道/21.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## React 常考基础知识点 4 | 5 | 这一章节我们将来学习 React 的一些经常考到的基础知识点。 6 | 7 | ### 生命周期 8 | 9 | 在 V16 版本中引入了 Fiber 机制。这个机制一定程度上的影响了部分生命周期的调用,并且也引入了新的 2 个 API 来解决问题,关于 Fiber 的内容将会在下一章节中讲到。 10 | 11 | 在之前的版本中,如果你拥有一个很复杂的复合组件,然后改动了最上层组件的 state,那么调用栈可能会很长 12 | 13 | ![](./images/068.webp) 14 | 15 | 调用栈过长,再加上中间进行了复杂的操作,就可能导致长时间阻塞主线程,带来不好的用户体验。Fiber 就是为了解决该问题而生。 16 | 17 | Fiber 本质上是一个虚拟的堆栈帧,新的调度器会按照优先级自由调度这些帧,从而将之前的同步渲染改成了异步渲染,在不影响体验的情况下去分段计算更新。 18 | 19 | ![](./images/069.webp) 20 | 21 | 对于如何区别优先级,React 有自己的一套逻辑。对于动画这种实时性很高的东西,也就是 16 ms 必须渲染一次保证不卡顿的情况下,React 会每 16 ms(以内) 暂停一下更新,返回来继续渲染动画。 22 | 23 | 对于异步渲染,现在渲染有两个阶段:`reconciliation` 和 `commit`。前者过程是可以打断的,后者不能暂停,会一直更新界面直到完成。 24 | 25 | Reconciliation 阶段 26 | 27 | - `componentWillMount`。 28 | - `componentWillReceiveProps`。 29 | - `shouldComponentUpdate`。 30 | - `componentWillUpdate`。 31 | 32 | Commit 阶段 33 | 34 | - `componentDidMount`。 35 | - `componentDidUpdate`。 36 | - `componentWillUnmount`。 37 | 38 | 因为 Reconciliation 阶段是可以被打断的,所以 Reconciliation 阶段会执行的生命周期函数就可能会出现调用多次的情况,从而引起 Bug。由此对于 Reconciliation 阶段调用的几个函数,除了 `shouldComponentUpdate` 以外,其他都应该避免去使用,并且 V16 中也引入了新的 API 来解决这个问题。 39 | 40 | `getDerivedStateFromProps` 用于替换 `componentWillReceiveProps`,该函数会在初始化和 `update` 时被调用 41 | 42 | ```js 43 | class ExampleComponent extends React.Component { 44 | // Initialize state in constructor, 45 | // Or with a property initializer. 46 | state = {}; 47 | 48 | static getDerivedStateFromProps(nextProps, prevState) { 49 | if (prevState.someMirroredValue !== nextProps.someValue) { 50 | return { 51 | derivedData: computeDerivedState(nextProps), 52 | someMirroredValue: nextProps.someValue 53 | }; 54 | } 55 | 56 | // Return null to indicate no change to state. 57 | return null; 58 | } 59 | } 60 | ``` 61 | 62 | `getSnapshotBeforeUpdate` 用于替换 `componentWillUpdate`,该函数会在 `update` 后 DOM 更新前被调用,用于读取最新的 DOM 数据。 63 | 64 | ### setState 65 | 66 | setState 在 React 中是经常使用的一个 API,但是它存在一些的问题经常会导致初学者出错,核心原因就是因为这个 API 是异步的。 67 | 68 | 首先 setState 的调用并不会马上引起 state 的改变,并且如果你一次调用了多个 setState ,那么结果可能并不如你期待的一样。 69 | 70 | ```js 71 | handle() { 72 | // 初始化 `count` 为 0 73 | console.log(this.state.count) // -> 0 74 | this.setState({ count: this.state.count + 1 }) 75 | this.setState({ count: this.state.count + 1 }) 76 | this.setState({ count: this.state.count + 1 }) 77 | console.log(this.state.count) // -> 0 78 | } 79 | ``` 80 | 81 | 第一,两次的打印都为 0,因为 `setState` 是个异步 API,只有同步代码运行完毕才会执行。`setState` 异步的原因我认为在于,`setState` 可能会导致 DOM 的重绘,如果调用一次就马上去进行重绘,那么调用多次就会造成不必要的性能损失。设计成异步的话,就可以将多次调用放入一个队列中,在恰当的时候统一进行更新过程。 82 | 83 | 第二,虽然调用了三次 `setState`,但是 `count` 的值还是为 1。因为多次调用会合并为一次,只有当更新结束后 `state` 才会改变,三次调用等同于如下代码 84 | 85 | ```js 86 | Object.assign( 87 | {}, 88 | { count: this.state.count + 1 }, 89 | { count: this.state.count + 1 }, 90 | { count: this.state.count + 1 }, 91 | ) 92 | ``` 93 | 94 | 当然你也可以通过以下方式来实现调用三次 `setState` 使得 `count` 为 3 95 | 96 | ```js 97 | handle() { 98 | this.setState((prevState) => ({ count: prevState.count + 1 })) 99 | this.setState((prevState) => ({ count: prevState.count + 1 })) 100 | this.setState((prevState) => ({ count: prevState.count + 1 })) 101 | } 102 | ``` 103 | 104 | 如果你想在每次调用 `setState` 后获得正确的 `state`,可以通过如下代码实现 105 | 106 | ```js 107 | handle() { 108 | this.setState((prevState) => ({ count: prevState.count + 1 }), () => { 109 | console.log(this.state) 110 | }) 111 | } 112 | ``` 113 | 114 | ### 性能优化 115 | 116 | 这小节内容集中在组件的性能优化上,这一方面的性能优化也基本集中在 `shouldComponentUpdate` 这个钩子函数上做文章。 117 | 118 | > PS:下文中的 state 指代了 state 及 props。 119 | 120 | 在 `shouldComponentUpdate` 函数中我们可以通过返回布尔值来决定当前组件是否需要更新。这层代码逻辑可以是简单地浅比较一下当前 `state` 和之前的 `state` 是否相同,也可以是判断某个值更新了才触发组件更新。一般来说不推荐完整地对比当前 `state` 和之前的 `state` 是否相同,因为组件更新触发可能会很频繁,这样的完整对比性能开销会有点大,可能会造成得不偿失的情况。 121 | 122 | 当然如果真的想完整对比当前 `state` 和之前的 `state` 是否相同,并且不影响性能也是行得通的,可以通过 `immutable` 或者 `immer` 这些库来生成不可变对象。这类库对于操作大规模的数据来说会提升不错的性能,并且一旦改变数据就会生成一个新的对象,对比前后 `state` 是否一致也就方便多了,同时也很推荐阅读下 `immer` 的源码实现。 123 | 124 | 另外如果只是单纯的浅比较一下,可以直接使用 `PureComponent`,底层就是实现了浅比较 `state`。 125 | 126 | ```js 127 | class Test extends React.PureComponent { 128 | render() { 129 | return ( 130 |
131 | PureComponent 132 |
133 | ) 134 | } 135 | } 136 | ``` 137 | 138 | 这时候你可能会考虑到函数组件就不能使用这种方式了,如果你使用 16.6.0 之后的版本的话,可以使用 `React.memo` 来实现相同的功能。 139 | 140 | ```js 141 | const Test = React.memo(() => ( 142 |
143 | PureComponent 144 |
145 | )) 146 | ``` 147 | 148 | 通过这种方式我们就可以既实现了 `shouldComponentUpdate` 的浅比较,又能够使用函数组件。 149 | 150 | ### 通信 151 | 152 | 其实 React 中的组件通信基本和 Vue 中的一致。同样也分为以下三种情况: 153 | 154 | - 父子组件通信。 155 | - 兄弟组件通信。 156 | - 跨多层级组件通信。 157 | - 任意组件。 158 | 159 | #### 父子通信 160 | 161 | 父组件通过 `props` 传递数据给子组件,子组件通过调用父组件传来的函数传递数据给父组件,这两种方式是最常用的父子通信实现办法。 162 | 163 | 这种父子通信方式也就是典型的单向数据流,父组件通过 `props` 传递数据,子组件不能直接修改 `props`, 而是必须通过调用父组件函数的方式告知父组件修改数据。 164 | 165 | #### 兄弟组件通信 166 | 167 | 对于这种情况可以通过共同的父组件来管理状态和事件函数。比如说其中一个兄弟组件调用父组件传递过来的事件函数修改父组件中的状态,然后父组件将状态传递给另一个兄弟组件。 168 | 169 | #### 跨多层次组件通信 170 | 171 | 如果你使用 16.3 以上版本的话,对于这种情况可以使用 Context API。 172 | 173 | ```js 174 | // 创建 Context,可以在开始就传入值 175 | const StateContext = React.createContext() 176 | class Parent extends React.Component { 177 | render () { 178 | return ( 179 | // value 就是传入 Context 中的值 180 | 181 | 182 | 183 | ) 184 | } 185 | } 186 | class Child extends React.Component { 187 | render () { 188 | return ( 189 | 190 | // 取出值 191 | {context => ( 192 | name is { context } 193 | )} 194 | 195 | ); 196 | } 197 | } 198 | ``` 199 | 200 | #### 任意组件 201 | 202 | 这种方式可以通过 Redux 或者 Event Bus 解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况。 203 | 204 | ### 小结 205 | 206 | 总的来说这一章节的内容更多的偏向于 React 的基础,另外 React 的面试题还会经常考到 Virtual DOM 中的内容,所以这块内容大家也需要好好准备。 207 | 208 | 下一章节我们将来了解一些 React 的进阶知识内容。 209 | 210 | 211 | -------------------------------------------------------------------------------- /前端面试之道/22.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## React 常考进阶知识点 4 | 5 | 这一章节我们将来学习 React 的一些经常考到的进阶知识点,并且这章节还需要配合第十九章阅读,其中的内容经常会考到。 6 | 7 | ### HOC 是什么?相比 mixins 有什么优点? 8 | 9 | 很多人看到高阶组件(HOC)这个概念就被吓到了,认为这东西很难,其实这东西概念真的很简单,我们先来看一个例子。 10 | 11 | ```js 12 | function add(a, b) { 13 | return a + b 14 | } 15 | ``` 16 | 17 | 现在如果我想给这个 `add` 函数添加一个输出结果的功能,那么你可能会考虑我直接使用 `console.log` 不就实现了么。说的没错,但是如果我们想做的更加优雅并且容易复用和扩展,我们可以这样去做: 18 | 19 | ```js 20 | function withLog (fn) { 21 | function wrapper(a, b) { 22 | const result = fn(a, b) 23 | console.log(result) 24 | return result 25 | } 26 | return wrapper 27 | } 28 | const withLogAdd = withLog(add) 29 | withLogAdd(1, 2) 30 | ``` 31 | 32 | 其实这个做法在函数式编程里称之为高阶函数,大家都知道 React 的思想中是存在函数式编程的,高阶组件和高阶函数就是同一个东西。我们实现一个函数,传入一个组件,然后在函数内部再实现一个函数去扩展传入的组件,最后返回一个新的组件,这就是高阶组件的概念,作用就是为了更好的复用代码。 33 | 34 | 其实 HOC 和 Vue 中的 `mixins` 作用是一致的,并且在早期 React 也是使用 `mixins` 的方式。但是在使用 `class` 的方式创建组件以后,`mixins` 的方式就不能使用了,并且其实 `mixins` 也是存在一些问题的,比如: 35 | 36 | - 隐含了一些依赖,比如我在组件中写了某个 `state` 并且在 `mixin` 中使用了,就这存在了一个依赖关系。万一下次别人要移除- 它,就得去 `mixin` 中查找依赖。 37 | - 多个 `mixin` 中可能存在相同命名的函数,同时代码组件中也不能出现相同命名的函数,否则就是重写了,其实我一直觉得命名- 真的是一件麻烦事。 38 | - 雪球效应,虽然我一个组件还是使用着同一个 `mixin`,但是一个 `mixin` 会被多个组件使用,可能会存在需求使得 `mixin` 修改原本的函数或者新增更多的函数,这样可能就会产生一个维护成本。 39 | 40 | HOC 解决了这些问题,并且它们达成的效果也是一致的,同时也更加的政治正确(毕竟更加函数式了)。 41 | 42 | ### 事件机制 43 | 44 | React 其实自己实现了一套事件机制,首先我们考虑一下以下代码: 45 | 46 | ```js 47 | const Test = ({ list, handleClick }) => ({ 48 | list.map((item, index) => ( 49 | {index} 50 | )) 51 | }) 52 | ``` 53 | 54 | 以上类似代码想必大家经常会写到,但是你是否考虑过点击事件是否绑定在了每一个标签上?事实当然不是,JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 `document` 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。 55 | 56 | 另外冒泡到 `document` 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件`(SyntheticEvent)`。因此我们如果不想要事件冒泡的话,调用 `event.stopPropagation` 是无效的,而应该调用 `event.preventDefault`。 57 | 58 | 那么实现合成事件的目的是什么呢?总的来说在我看来好处有两点,分别是: 59 | 60 | - 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力。 61 | - 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。 62 | 63 | ### 小结 64 | 65 | 你可能会惊讶于这一章节的内容并不多的情况,其实你如果将两章 React 以及第十九章的内容全部学习完后,基本上 React 的大部分面试问题都可以解决。 66 | 67 | 当然你可能会觉得看的还不过瘾,这不需要担心。我已经决定写一个免费专栏「React 进阶」,专门讲解有难度的问题。比如组件的设计模式、新特性、部分源码解析等等内容。当然这些内容都是需要好好打磨的,所以更新的不会很快,有兴趣的可以持续关注,都会更新链接在这一章节中。 68 | -------------------------------------------------------------------------------- /前端面试之道/23.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 监控 4 | 5 | 前端监控一般分为三种,分别为页面埋点、性能监控以及异常监控。 6 | 7 | 这一章节我们将来学习这些监控相关的内容,但是基本不会涉及到代码,只是让大家了解下前端监控该用什么方式实现。毕竟大部分公司都只是使用到了第三方的监控工具,而不是选择自己造轮子。 8 | 9 | ### 页面埋点 10 | 11 | 页面埋点应该是大家最常写的监控了,一般起码会监控以下几个数据: 12 | 13 | - PV / UV。 14 | - 停留时长。 15 | - 流量来源。 16 | - 用户交互。 17 | 18 | 对于这几类统计,一般的实现思路大致可以分为两种,分别为手写埋点和无埋点的方式。 19 | 20 | 相信第一种方式也是大家最常用的方式,可以自主选择需要监控的数据然后在相应的地方写入代码。这种方式的灵活性很大,但是唯一的缺点就是工作量较大,每个需要监控的地方都得插入代码。 21 | 22 | 另一种无埋点的方式基本不需要开发者手写埋点了,而是统计所有的事件并且定时上报。这种方式虽然没有前一种方式繁琐了,但是因为统计的是所有事件,所以还需要后期过滤出需要的数据。 23 | 24 | ### 性能监控 25 | 26 | 性能监控可以很好的帮助开发者了解在各种真实环境下,页面的性能情况是如何的。 27 | 28 | 对于性能监控来说,我们可以直接使用浏览器自带的 Performance API 来实现这个功能。 29 | 30 | 对于性能监控来说,其实我们只需要调用 `performance.getEntriesByType('navigation')` 这行代码就行了。对,你没看错,一行代码我们就可以获得页面中各种详细的性能相关信息。 31 | 32 | ![](./images/070.webp) 33 | 34 | 我们可以发现这行代码返回了一个数组,内部包含了相当多的信息,从数据开始在网络中传输到页面加载完成都提供了相应的数据。 35 | 36 | ![](./images/071.webp) 37 | 38 | ### 异常监控 39 | 40 | 对于异常监控来说,以下两种监控是必不可少的,分别是代码报错以及接口异常上报。 41 | 42 | 对于代码运行错误,通常的办法是使用 `window.onerror` 拦截报错。该方法能拦截到大部分的详细报错信息,但是也有例外 43 | 44 | 对于跨域的代码运行错误会显示 `Script error.` 对于这种情况我们需要给 `script` 标签添加 `crossorigin` 属性 45 | 对于某些浏览器可能不会显示调用栈信息,这种情况可以通过 `arguments.callee.caller` 来做栈递归 46 | 对于异步代码来说,可以使用 `catch` 的方式捕获错误。比如 `Promise` 可以直接使用 `catch` 函数,`async await` 可以使用 `try catch`。 47 | 48 | 但是要注意线上运行的代码都是压缩过的,需要在打包时生成 `sourceMap` 文件便于 `debug`。 49 | 50 | 对于捕获的错误需要上传给服务器,通常可以通过 `img` 标签的 `src` 发起一个请求。 51 | 52 | 另外接口异常就相对来说简单了,可以列举出出错的状态码。一旦出现此类的状态码就可以立即上报出错。接口异常上报可以让开发人员迅速知道有哪些接口出现了大面积的报错,以便迅速修复问题。 53 | 54 | ### 小结 55 | 56 | 这一章节内容虽然不多,但是这类监控的知识网上的资料确实不多,相信能给大家一个不错的思路。 -------------------------------------------------------------------------------- /前端面试之道/24.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## UDP 4 | 5 | 网络协议是每个前端工程师都必须要掌握的知识,我们将先来学习传输层中的两个协议:UDP 以及 TCP。对于大部分工程师来说最常用的协议也就是这两个了,并且面试中经常会提问的也是关于这两个协议的区别。 6 | 7 | 我们先来解答这个常考面试题关于 UDP 部分的内容,然后再详细去学习这个协议。 8 | 9 | > 常考面试题:UDP 与 TCP 的区别是什么? 10 | 11 | 首先 UDP 协议是面向无连接的,也就是说不需要在正式传递数据之前先连接起双方。然后 UDP 协议只是数据报文的搬运工,不保证有序且不丢失的传递到对端,并且UDP 协议也没有任何控制流量的算法,总的来说 UDP 相较于 TCP 更加的轻便。 12 | 13 | ### 面向无连接 14 | 15 | 首先 UDP 是不需要和 TCP 一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。 16 | 17 | 并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。 18 | 19 | 具体来说就是: 20 | 21 | - 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了。 22 | 23 | - 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作。 24 | 25 | ### 不可靠性 26 | 27 | 首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。 28 | 29 | 并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。 30 | 31 | 再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。 32 | 33 | ### 高效 34 | 35 | 虽然 UDP 协议不是那么的可靠,但是正是因为它不是那么的可靠,所以也就没有 TCP 那么复杂了,需要保证数据不丢失且有序到达。 36 | 37 | 因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的。 38 | 39 | ![](images/072.webp) 40 | 41 | UDP 头部包含了以下几个数据 42 | 43 | - 两个十六位的端口号,分别为源端口(可选字段)和目标端口。 44 | - 整个数据报文的长度。 45 | - 整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误。 46 | 47 | ### 传输方式 48 | 49 | UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。 50 | 51 | ### 适合使用的场景 52 | 53 | UDP 虽然对比 TCP 有很多缺点,但是正是因为这些缺点造就了它高效的特性,在很多实时性要求高的地方都可以看到 UDP 的身影。 54 | 55 | #### 直播 56 | 57 | 想必大家都看过直播吧,大家可以考虑下如果直播使用了基于 TCP 的协议会发生什么事情? 58 | 59 | TCP 会严格控制传输的正确性,一旦有某一个数据对端没有收到,就会停止下来直到对端收到这个数据。这种问题在网络条件不错的情况下可能并不会发生什么事情,但是在网络情况差的时候就会变成画面卡住,然后再继续播放下一帧的情况。 60 | 61 | 但是对于直播来说,用户肯定关注的是最新的画面,而不是因为网络条件差而丢失的老旧画面,所以 TCP 在这种情况下无用武之地,只会降低用户体验。 62 | 63 | #### 王者荣耀 64 | 65 | 虽然我具体不知道王者荣耀底层使用了什么协议,但是对于这类实时性要求很高的游戏来说,UDP 是跑不了的。 66 | 67 | 为什么这样说呢?首先对于王者荣耀来说,用户体量是相当大的,如果使用 TCP 连接的话,就可能会出现服务器不够用的情况,因为每台服务器可供支撑的 TCP 连接数量是有限制的。 68 | 69 | 再者,因为 TCP 会严格控制传输的正确性,如果因为用户网络条件不好就造成页面卡顿然后再传输旧的游戏画面是肯定不能接受的,毕竟对于这类实时性要求很高的游戏来说,最新的游戏画面才是最需要的,而不是老旧的画面,否则角色都不知道死多少次了。 70 | 71 | ### 小结 72 | 73 | 这一章节的内容就到这里,因为 UDP 协议相对简单,所以内容并不是很多,但是下一章节会呈现很多关于 TCP 相关的内容,请大家做好准备。 74 | 75 | 最后总结一下这一章节的内容: 76 | 77 | - UDP 相比 TCP 简单的多,不需要建立连接,不需要验证数据报文,不需要流量控制,只会把想发的数据报文一股脑的丢给对端。 78 | - 虽然 UDP 并没有 TCP 传输来的准确,但是也能在很多实时性要求高的地方有所作为。 -------------------------------------------------------------------------------- /前端面试之道/25.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## TCP 4 | 5 | 首先还是先来解答这个常考面试题关于 TCP 部分的内容,然后再详细去学习这个协议。 6 | 7 | > 常考面试题:UDP 与 TCP 的区别是什么? 8 | 9 | TCP 基本是和 UDP 反着来,建立连接断开连接都需要先需要进行握手。在传输数据的过程中,通过各种算法保证数据的可靠性,当然带来的问题就是相比 UDP 来说不那么的高效。 10 | 11 | ### 头部 12 | 13 | 从这个图上我们就可以发现 TCP 头部比 UDP 头部复杂的多。 14 | 15 | ![](./images/073.webp) 16 | 17 | 对于 TCP 头部来说,以下几个字段是很重要的 18 | 19 | - Sequence number,这个序号保证了 TCP 传输的报文都是有序的,对端可以通过序号顺序的拼接报文。 20 | 21 | - Acknowledgement Number,这个序号表示数据接收端期望接收的下一个字节的编号是多少,同时也表示上一个序号的数据已经收到。 22 | 23 | - Window Size,窗口大小,表示还能接收多少字节的数据,用于流量控制。 24 | 25 | - 标识符 26 | 27 | - URG=1:该字段为一表示本数据报的数据部分包含紧急信息,是一个高优先级数据报文,此时紧急指针有效。紧急数据一定位于当前数据包数据部分的最前面,紧急指针标明了紧急数据的尾部。 28 | 29 | - ACK=1:该字段为一表示确认号字段有效。此外,TCP 还规定在连接建立后传送的所有报文段都必须把 ACK 置为一。 30 | 31 | - PSH=1:该字段为一表示接收端应该立即将数据 push 给应用层,而不是等到缓冲区满后再提交。 32 | 33 | - RST=1:该字段为一表示当前 TCP 连接出现严重问题,可能需要重新建立 TCP 连接,也可以用于拒绝非法的报文段和拒绝连接请求。 34 | 35 | - SYN=1:当 SYN=1,ACK=0 时,表示当前报文段是一个连接请求报文。当 SYN=1,ACK=1 时,表示当前报文段是一个同意建立连接的应答报文。 36 | 37 | - FIN=1:该字段为一表示此报文段是一个释放连接的请求报文。 38 | 39 | ### 状态机 40 | 41 | TCP 的状态机是很复杂的,并且与建立断开连接时的握手息息相关,接下来就来详细描述下两种握手。 42 | 43 | ![](./images/074.webp) 44 | 45 | 在这之前需要了解一个重要的性能指标 RTT。该指标表示发送端发送数据到接收到对端数据所需的往返时间。 46 | 47 | #### 建立连接三次握手 48 | 49 | ![](./images/075.webp) 50 | 51 | 首先假设主动发起请求的一端称为客户端,被动连接的一端称为服务端。不管是客户端还是服务端,TCP 连接建立完后都能发送和接收数据,所以 TCP 是一个全双工的协议。 52 | 53 | 起初,两端都为 CLOSED 状态。在通信开始前,双方都会创建 TCB。 服务器创建完 TCB 后便进入 LISTEN 状态,此时开始等待客户端发送数据。 54 | 55 | ##### 第一次握手 56 | 57 | 客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。 58 | 59 | ##### 第二次握手 60 | 61 | 服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。 62 | 63 | ##### 第三次握手 64 | 65 | 当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。 66 | 67 | PS:第三次握手中可以包含数据,通过快速打开(TFO)技术就可以实现这一功能。其实只要涉及到握手的协议,都可以使用类似 TFO 的方式,客户端和服务端存储相同的 cookie,下次握手时发出 cookie 达到减少 RTT 的目的。 68 | 69 | > 常考面试题:为什么 TCP 建立连接需要三次握手,明明两次就可以建立起连接 70 | 71 | 因为这是为了防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误。 72 | 73 | 可以想象如下场景。客户端发送了一个连接请求 A,但是因为网络原因造成了超时,这时 TCP 会启动超时重传的机制再次发送一个连接请求 B。此时请求顺利到达服务端,服务端应答完就建立了请求,然后接收数据后释放了连接。 74 | 75 | 假设这时候连接请求 A 在两端关闭后终于抵达了服务端,那么此时服务端会认为客户端又需要建立 TCP 连接,从而应答了该请求并进入 ESTABLISHED 状态。但是客户端其实是 CLOSED 的状态,那么就会导致服务端一直等待,造成资源的浪费。 76 | 77 | PS:在建立连接中,任意一端掉线,TCP 都会重发 SYN 包,一般会重试五次,在建立连接中可能会遇到 SYN Flood 攻击。遇到这种情况你可以选择调低重试次数或者干脆在不能处理的情况下拒绝请求。 78 | 79 | 80 | 81 | 82 | #### 断开链接四次握手 83 | 84 | ![](./images/076.webp) 85 | 86 | TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。 87 | 88 | ##### 第一次握手 89 | 90 | 若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。 91 | 92 | ##### 第二次握手 93 | 94 | B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。 95 | 96 | ##### 第三次握手 97 | 98 | B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。 99 | 100 | PS:通过延迟确认的技术(通常有时间限制,否则对方会误认为需要重传),可以将第二次和第三次握手合并,延迟 ACK 包的发送。 101 | 102 | ##### 第四次握手 103 | 104 | A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。 105 | 106 | 为什么 A 要进入 TIME-WAIT 状态,等待 2MSL 时间后才进入 CLOSED 状态? 107 | 108 | 为了保证 B 能收到 A 的确认应答。若 A 发完确认应答后直接进入 CLOSED 状态,如果确认应答因为网络问题一直没有到达,那么会造成 B 不能正常关闭。 109 | 110 | ### ARQ 协议 111 | 112 | ARQ 协议也就是超时重传机制。通过确认和超时机制保证了数据的正确送达,ARQ 协议包含停止等待 ARQ 和连续 ARQ 两种协议。 113 | 114 | #### 停止等待 ARQ 115 | 116 | ##### 正常传输过程 117 | 118 | 只要 A 向 B 发送一段报文,都要停止发送并启动一个定时器,等待对端回应,在定时器时间内接收到对端应答就取消定时器并发送下一段报文。 119 | 120 | ##### 报文丢失或出错 121 | 122 | 在报文传输的过程中可能会出现丢包。这时候超过定时器设定的时间就会再次发送丢失的数据直到对端响应,所以需要每次都备份发送的数据。 123 | 124 | 即使报文正常的传输到对端,也可能出现在传输过程中报文出错的问题。这时候对端会抛弃该报文并等待 A 端重传。 125 | 126 | PS:一般定时器设定的时间都会大于一个 RTT 的平均时间。 127 | 128 | ##### ACK 超时或丢失 129 | 130 | 对端传输的应答也可能出现丢失或超时的情况。那么超过定时器时间 A 端照样会重传报文。这时候 B 端收到相同序号的报文会丢弃该报文并重传应答,直到 A 端发送下一个序号的报文。 131 | 132 | 在超时的情况下也可能出现应答很迟到达,这时 A 端会判断该序号是否已经接收过,如果接收过只需要丢弃应答即可。 133 | 134 | 从上面的描述中大家肯定可以发现这肯定不是一个高效的方式。假设在良好的网络环境中,每次发送数据都需要等待片刻肯定是不能接受的。那么既然我们不能接受这个不那么高效的协议,就来继续学习相对高效的协议吧。 135 | 136 | #### 连续 ARQ 137 | 138 | 在连续 ARQ 中,发送端拥有一个发送窗口,可以在没有收到应答的情况下持续发送窗口内的数据,这样相比停止等待 ARQ 协议来说减少了等待时间,提高了效率。 139 | 140 | #### 累计确认 141 | 142 | 连续 ARQ 中,接收端会持续不断收到报文。如果和停止等待 ARQ 中接收一个报文就发送一个应答一样,就太浪费资源了。通过累计确认,可以在收到多个报文以后统一回复一个应答报文。报文中的 ACK 标志位可以用来告诉发送端这个序号之前的数据已经全部接收到了,下次请发送这个序号后的数据。 143 | 144 | 但是累计确认也有一个弊端。在连续接收报文时,可能会遇到接收到序号 5 的报文后,并未接收到序号 6 的报文,然而序号 7 以后的报文已经接收。遇到这种情况时,ACK 只能回复 6,这样就会造成发送端重复发送数据的情况。 145 | 146 | ### 滑动窗口 147 | 148 | 在上面小节中讲到了发送窗口。在 TCP 中,两端其实都维护着窗口:分别为发送端窗口和接收端窗口。 149 | 150 | 发送端窗口包含已发送但未收到应答的数据和可以发送但是未发送的数据。 151 | 152 | ![](./images/077.webp) 153 | 154 | 发送端窗口是由接收窗口剩余大小决定的。接收方会把当前接收窗口的剩余大小写入应答报文,发送端收到应答后根据该值和当前网络拥塞情况设置发送窗口的大小,所以发送窗口的大小是不断变化的。 155 | 156 | 当发送端接收到应答报文后,会随之将窗口进行滑动 157 | 158 | ![](./images/078.webp) 159 | 160 | 滑动窗口是一个很重要的概念,它帮助 TCP 实现了流量控制的功能。接收方通过报文告知发送方还可以发送多少数据,从而保证接收方能够来得及接收数据,防止出现接收方带宽已满,但是发送方还一直发送数据的情况。 161 | 162 | #### Zero 窗口 163 | 164 | 在发送报文的过程中,可能会遇到对端出现零窗口的情况。在该情况下,发送端会停止发送数据,并启动 persistent timer 。该定时器会定时发送请求给对端,让对端告知窗口大小。在重试次数超过一定次数后,可能会中断 TCP 链接。 165 | 166 | ### 拥塞处理 167 | 168 | 拥塞处理和流量控制不同,后者是作用于接收方,保证接收方来得及接受数据。而前者是作用于网络,防止过多的数据拥塞网络,避免出现网络负载过大的情况。 169 | 170 | 拥塞处理包括了四个算法,分别为:慢开始,拥塞避免,快速重传,快速恢复。 171 | 172 | #### 慢开始算法 173 | 174 | 慢开始算法,顾名思义,就是在传输开始时将发送窗口慢慢指数级扩大,从而避免一开始就传输大量数据导致网络拥塞。想必大家都下载过资源,每当我们开始下载的时候都会发现下载速度是慢慢提升的,而不是一蹴而就直接拉满带宽。 175 | 176 | 慢开始算法步骤具体如下 177 | 178 | 1. 连接初始设置拥塞窗口(Congestion Window) 为 1 MSS(一个分段的最大数据量)。 179 | 2. 每过一个 RTT 就将窗口大小乘二。 180 | 3. 指数级增长肯定不能没有限制的,所以有一个阈值限制,当窗口大小大于阈值时就会启动拥塞避免算法。 181 | 182 | #### 拥塞避免算法 183 | 184 | 拥塞避免算法相比简单点,每过一个 RTT 窗口大小只加一,这样能够避免指数级增长导致网络拥塞,慢慢将大小调整到最佳值。 185 | 186 | 在传输过程中可能定时器超时的情况,这时候 TCP 会认为网络拥塞了,会马上进行以下步骤: 187 | 188 | - 将阈值设为当前拥塞窗口的一半。 189 | - 将拥塞窗口设为 1 MSS。 190 | - 启动拥塞避免算法。 191 | 192 | #### 快速重传 193 | 194 | 快速重传一般和快恢复一起出现。一旦接收端收到的报文出现失序的情况,接收端只会回复最后一个顺序正确的报文序号。如果发送端收到三个重复的 ACK,无需等待定时器超时而是直接启动快速重传算法。具体算法分为两种: 195 | 196 | ##### TCP Taho 实现如下 197 | 198 | - 将阈值设为当前拥塞窗口的一半。 199 | - 将拥塞窗口设为 1 MSS。 200 | - 重新开始慢开始算法。 201 | 202 | ##### TCP Reno 实现如下 203 | 204 | - 拥塞窗口减半。 205 | - 将阈值设为当前拥塞窗口。 206 | - 进入快恢复阶段(重发对端需要的包,一旦收到一个新的 ACK 答复就退出该阶段),这种方式在丢失多个包的情况下就不那么好了。 207 | - 使用拥塞避免算法。 208 | 209 | #### TCP New Ren 改进后的快恢复 210 | 211 | TCP New Reno 算法改进了之前 TCP Reno 算法的缺陷。在之前,快恢复中只要收到一个新的 ACK 包,就会退出快恢复。 212 | 213 | 在 TCP New Reno 中,TCP 发送方先记下三个重复 ACK 的分段的最大序号。 214 | 215 | 假如我有一个分段数据是 1 ~ 10 这十个序号的报文,其中丢失了序号为 3 和 7 的报文,那么该分段的最大序号就是 10。发送端只会收到 ACK 序号为 3 的应答。这时候重发序号为 3 的报文,接收方顺利接收的话就会发送 ACK 序号为 7 的应答。这时候 TCP 知道对端是有多个包未收到,会继续发送序号为 7 的报文,接收方顺利接收并会发送 ACK 序号为 11 的应答,这时发送端认为这个分段接收端已经顺利接收,接下来会退出快恢复阶段。 216 | 217 | ### 小结 218 | 219 | 这一章节内容很多,充斥了大量的术语,适合大家反复研读,已经把 TCP 中最核心最需要掌握的内容全盘托出了,如有哪里不明白的欢迎提问。 220 | 221 | 总结一下这一章节的内容: 222 | 223 | - 建立连接需要三次握手,断开连接需要四次握手。 224 | - 滑动窗口解决了数据的丢包、顺序不对和流量控制问题。 225 | - 拥塞窗口实现了对流量的控制,保证在全天候环境下最优的传递数据。 -------------------------------------------------------------------------------- /前端面试之道/26.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## HTTP 及 TLS 4 | 5 | 这一章节我们将来学习 HTTP 及 TLS 协议中的内容。 6 | 7 | ### HTTP 请求中的内容 8 | 9 | HTTP 请求由三部分构成,分别为: 10 | 11 | - 请求行。 12 | - 首部。 13 | - 实体。 14 | 15 | 请求行大概长这样 `GET /images/logo.gif HTTP/1.1`,基本由请求方法、URL、协议版本组成,这其中值得一说的就是请求方法了。 16 | 17 | 请求方法分为很多种,最常用的也就是 `Get` 和 `Post` 了。虽然请求方法有很多,但是更多的是传达一个语义,而不是说 `Post` 能做的事情 `Get` 就不能做了。如果你愿意,都使用 `Get` 请求或者 `Post` 请求都是可以的。更多请求方法的语义描述可以阅读文档。 18 | 19 | > 常考面试题:Post 和 Get 的区别? 20 | 21 | 首先先引入副作用和幂等的概念。 22 | 23 | 副作用指对服务器上的资源做改变,搜索是无副作用的,注册是副作用的。 24 | 25 | 幂等指发送 M 和 N 次请求(两者不相同且都大于 1),服务器上资源的状态一致,比如注册 10 个和 11 个帐号是不幂等的,对文章进行更改 10 次和 11 次是幂等的。因为前者是多了一个账号(资源),后者只是更新同一个资源。 26 | 27 | 在规范的应用场景上说,Get 多用于无副作用,幂等的场景,例如搜索关键字。Post 多用于副作用,不幂等的场景,例如注册。 28 | 29 | 在技术上说: 30 | 31 | - Get 请求能缓存,Post 不能。 32 | - Post 相对 Get 安全一点点,因为 Get 请求都包含在 URL 里(当然你想写到 body 里也是可以的),且会被浏览器保存。历史纪录。Post 不会,但是在抓包的情况下都是一样的。 33 | - URL 有长度限制,会影响 Get 请求,但是这个长度限制是浏览器规定的,不是 RFC 规定的。 34 | - Post 支持更多的编码类型且不对数据类型限制。 35 | 36 | #### 首部 37 | 38 | 首部分为请求首部和响应首部,并且部分首部两种通用,接下来我们就来学习一部分的常用首部。 39 | 40 | ##### 通用首部 41 | 42 | | 通用字段 | 作用 | 43 | | :---------------: | :---------------------------------------------: | 44 | | Cache-Control | 控制缓存的行为 | 45 | | Connection | 浏览器想要优先使用的连接类型,比如 `keep-alive` | 46 | | Date | 创建报文时间 | 47 | | Pragma | 报文指令 | 48 | | Via | 代理服务器相关信息 | 49 | | Transfer-Encoding | 传输编码方式 | 50 | | Upgrade | 要求客户端升级协议 | 51 | | Warning | 在内容中可能存在错误 | 52 | 53 | ##### 请求首部 54 | 55 | | 请求首部 | 作用 | 56 | | :----------------------------------: | :--------------------------: | 57 | | Accept | 能正确接收的媒体类型 | 58 | | Accept-Charset | 能正确接收的字符集 | 59 | | Accept-Encoding | 能正确接收的编码格式列表 | 60 | | Accept-Language | 能正确接收的语言列表 | 61 | | Expect | 期待服务端的指定行为 | 62 | | From | 请求方邮箱地址 | 63 | | Host | 服务器的域名 | 64 | | If-Match | 两端资源标记比较 | 65 | | If-Modified-Since 本地资源未修改返回 | 304(比较时间) | 66 | | If-None-Match 本地资源未修改返回 | 304(比较标记) | 67 | | User-Agent | 客户端信息 | 68 | | Max-Forwards | 限制可被代理及网关转发的次数 | 69 | | Proxy-Authorization | 向代理服务器发送验证信息 | 70 | | Range | 请求某个内容的一部分 | 71 | | Referer | 表示浏览器所访问的前一个页面 | 72 | | TE | 传输编码方式 | 73 | 74 | ##### 响应首部 75 | 76 | | 响应首部 | 作用 | 77 | | :-------------------------: | :------------------------: | 78 | | Accept-Ranges | 是否支持某些种类的范围 | 79 | | Age | 资源在代理缓存中存在的时间 | 80 | | ETag | 资源标识 | 81 | | Location 客户端重定向到某个 | URL | 82 | | Proxy-Authenticate | 向代理服务器发送验证信息 | 83 | | Server | 服务器名字 | 84 | | WWW-Authenticate | 获取资源需要的验证信息 | 85 | 86 | ##### 实体首部 87 | 88 | | 实体首部 | 作用 | 89 | | :-------------------------------: | :----------------: | 90 | | Allow | 资源的正确请求方式 | 91 | | Content-Encoding | 内容的编码格式 | 92 | | Content-Language | 内容使用的语言 | 93 | | Content-Length request body | 长度 | 94 | | Content-Location | 返回数据的备用地址 | 95 | | Content-MD5 Base64 加密格式的内容 | MD5 检验值 | 96 | | Content-Range | 内容的位置范围 | 97 | | Content-Type | 内容的媒体类型 | 98 | | Expires | 内容的过期时间 | 99 | | Last_modified | 内容的最后修改时间 | 100 | 101 | #### 常见状态码 102 | 103 | 状态码表示了响应的一个状态,可以让我们清晰的了解到这一次请求是成功还是失败,如果失败的话,是什么原因导致的,当然状态码也是用于传达语义的。如果胡乱使用状态码,那么它存在的意义就没有了。 104 | 105 | 状态码通常也是一道常考题。 106 | 107 | ##### 2XX 成功 108 | 109 | - 200 OK,表示从客户端发来的请求在服务器端被正确处理。 110 | - 204 No content,表示请求成功,但响应报文不含实体的主体部分。 111 | - 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于要求请求方重置内容。 112 | - 206 Partial Content,进行范围请求。 113 | 114 | 115 | ##### 3XX 重定向 116 | 117 | - 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL。 118 | - 302 found,临时性重定向,表示资源临时被分配了新的 URL。 119 | - 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源。 120 | - 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况。 121 | - 307 temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求。 122 | 123 | ##### 4XX 客户端错误 124 | 125 | - 400 bad request,请求报文存在语法错误。 126 | - 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息。 127 | - 403 forbidden,表示对请求资源的访问被服务器拒绝。 128 | - 404 not found,表示在服务器上没有找到请求的资源。 129 | 130 | ##### 5XX 服务器错误 131 | 132 | - 500 internal sever error,表示服务器端在执行请求时发生了错误。 133 | - 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能。 134 | - 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求。 135 | 136 | ### TLS 137 | 138 | HTTPS 还是通过了 HTTP 来传输信息,但是信息通过 TLS 协议进行了加密。 139 | 140 | TLS 协议位于传输层之上,应用层之下。首次进行 TLS 协议传输需要两个 RTT ,接下来可以通过 Session Resumption 减少到一个 RTT。 141 | 142 | 在 TLS 中使用了两种加密技术,分别为:对称加密和非对称加密。 143 | 144 | #### 对称加密 145 | 146 | 对称加密就是两边拥有相同的秘钥,两边都知道如何将密文加密解密。 147 | 148 | 这种加密方式固然很好,但是问题就在于如何让双方知道秘钥。因为传输数据都是走的网络,如果将秘钥通过网络的方式传递的话,一旦秘钥被截获就没有加密的意义的。 149 | 150 | #### 非对称加密 151 | 152 | 有公钥私钥之分,公钥所有人都可以知道,可以将数据用公钥加密,但是将数据解密必须使用私钥解密,私钥只有分发公钥的一方才知道。 153 | 154 | 这种加密方式就可以完美解决对称加密存在的问题。假设现在两端需要使用对称加密,那么在这之前,可以先使用非对称加密交换秘钥。 155 | 156 | 简单流程如下:首先服务端将公钥公布出去,那么客户端也就知道公钥了。接下来客户端创建一个秘钥,然后通过公钥加密并发送给服务端,服务端接收到密文以后通过私钥解密出正确的秘钥,这时候两端就都知道秘钥是什么了。 157 | 158 | #### TLS 握手过程如下图: 159 | 160 | ![](./images/079.webp) 161 | 162 | 客户端发送一个随机值以及需要的协议和加密方式。 163 | 164 | 服务端收到客户端的随机值,自己也产生一个随机值,并根据客户端需求的协议和加密方式来使用对应的方式,并且发送自己的证书(如果需要验证客户端证书需要说明) 165 | 166 | 客户端收到服务端的证书并验证是否有效,验证通过会再生成一个随机值,通过服务端证书的公钥去加密这个随机值并发送给服务端,如果服务端需要验证客户端证书的话会附带证书 167 | 168 | 服务端收到加密过的随机值并使用私钥解密获得第三个随机值,这时候两端都拥有了三个随机值,可以通过这三个随机值按照之前约定的加密方式生成密钥,接下来的通信就可以通过该密钥来加密解密 169 | 170 | 通过以上步骤可知,在 TLS 握手阶段,两端使用非对称加密的方式来通信,但是因为非对称加密损耗的性能比对称加密大,所以在正式传输数据时,两端使用对称加密的方式通信。 171 | 172 | PS:以上说明的都是 TLS 1.2 协议的握手情况,在 1.3 协议中,首次建立连接只需要一个 RTT,后面恢复连接不需要 RTT 了。 173 | 174 | ### 小结 175 | 176 | 总结一下内容: 177 | 178 | - HTTP 经常考到的内容包括:请求方法、首部的作用以及状态码的含义。 179 | - TLS 中经常考到的内容包括:两种加密方式以及握手的流程。 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /前端面试之道/27.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## HTTP/2 及 HTTP/3 4 | 5 | 这一章节我们将来学习 HTTP/2 及 HTTP/3 的内容。 6 | 7 | HTTP/2 很好的解决了当下最常用的 HTTP/1 所存在的一些性能问题,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何优雅降级应该是国内还不普遍使用的原因之一。 8 | 9 | 虽然 HTTP/2 已经解决了很多问题,但是并不代表它已经是完美的了,HTTP/3 就是为了解决 HTTP/2 所存在的一些问题而被推出来的。 10 | 11 | ### HTTP/2 12 | 13 | HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能。 14 | 15 | 在 HTTP/1 中,为了性能考虑,我们会引入雪碧图、将小图内联、使用多个域名等等的方式。这一切都是因为浏览器限制了同一个域名下的请求数量(Chrome 下一般是限制六个连接),当页面中需要请求很多资源的时候,队头阻塞(Head of line blocking)会导致在达到最大请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。 16 | 17 | 在 HTTP/2 中引入了多路复用的技术,这个技术可以只通过一个 TCP 连接就可以传输所有的请求数据。多路复用很好的解决了浏览器限制同一个域名下的请求数量的问题,同时也接更容易实现全速传输,毕竟新开一个 TCP 连接都需要慢慢提升传输速度。 18 | 19 | 大家可以通过 该链接 感受下 HTTP/2 比 HTTP/1 到底快了多少。 20 | 21 | ![](./images/080.webp) 22 | 23 | 在 HTTP/1 中,因为队头阻塞的原因,你会发现发送请求是长这样的 24 | 25 | ![](./images/081.webp) 26 | 27 | 在 HTTP/2 中,因为可以复用同一个 TCP 连接,你会发现发送请求是长这样的 28 | 29 | ![](./images/082.webp) 30 | 31 | ### 二进制传输 32 | 33 | HTTP/2 中所有加强性能的核心点在于此。在之前的 HTTP 版本中,我们是通过文本的方式传输数据。在 HTTP/2 中引入了新的编码机制,所有传输的数据都会被分割,并采用二进制格式编码。 34 | 35 | ![](./images/083.webp) 36 | 37 | ### 多路复用 38 | 39 | 在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。 40 | 41 | 帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。 42 | 43 | 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。 44 | 45 | ![](./images/084.webp) 46 | 47 | ### Header 压缩 48 | 49 | 在 HTTP/1 中,我们使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。 50 | 51 | 在 HTTP /2 中,使用了 HPACK 压缩格式对传输的 header 进行编码,减少了 header 的大小。并在两端维护了索引表,用于记录出现过的 header ,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值。 52 | 53 | ### 服务端 Push 54 | 55 | 在 HTTP/2 中,服务端可以在客户端某个请求后,主动推送其他资源。 56 | 57 | 可以想象以下情况,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间。当然在浏览器兼容的情况下你也可以使用 prefetch。 58 | 59 | ### HTTP/3 60 | 61 | 虽然 HTTP/2 解决了很多之前旧版本的问题,但是它还是存在一个巨大的问题,虽然这个问题并不是它本身造成的,而是底层支撑的 TCP 协议的问题。 62 | 63 | 因为 HTTP/2 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。当这个连接中出现了丢包的情况,那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1 了。 64 | 65 | 因为在出现丢包的情况下,整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。但是对于 HTTP/1 来说,可以开启多个 TCP 连接,出现这种情况反到只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。 66 | 67 | 那么可能就会有人考虑到去修改 TCP 协议,其实这已经是一件不可能完成的任务了。因为 TCP 存在的时间实在太长,已经充斥在各种设备中,并且这个协议是由操作系统实现的,更新起来不大现实。 68 | 69 | 基于这个原因,Google 就更起炉灶搞了一个基于 UDP 协议的 QUIC 协议,并且使用在了 HTTP/3 上,当然 HTTP/3 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC,接下来我们就来学习关于这个协议的内容。 70 | 71 | #### QUIC 72 | 73 | 之前我们学习过 UDP 协议的内容,知道这个协议虽然效率很高,但是并不是那么的可靠。QUIC 虽然基于 UDP,但是在原本的基础上新增了很多功能,比如多路复用、0-RTT、使用 TLS1.3 加密、流量控制、有序交付、重传等等功能。这里我们就挑选几个重要的功能学习下这个协议的内容。 74 | 75 | #### 多路复用 76 | 77 | 虽然 HTTP/2 支持了多路复用,但是 TCP 协议终究是没有这个功能的。QUIC 原生就实现了这个功能,并且传输的单个数据流可以保证有序交付且不会影响其他的数据流,这样的技术就解决了之前 TCP 存在的问题。 78 | 79 | 并且 QUIC 在移动端的表现也会比 TCP 好。因为 TCP 是基于 IP 和端口去识别连接的,这种方式在多变的移动端网络环境下是很脆弱的。但是 QUIC 是通过 ID 的方式去识别一个连接,不管你网络环境如何变化,只要 ID 不变,就能迅速重连上。 80 | 81 | #### 0-RTT 82 | 83 | 通过使用类似 TCP 快速打开的技术,缓存当前会话的上下文,在下次恢复会话的时候,只需要将之前的缓存传递给服务端验证通过就可以进行传输了。 84 | 85 | #### 纠错机制 86 | 87 | 假如说这次我要发送三个包,那么协议会算出这三个包的异或值并单独发出一个校验包,也就是总共发出了四个包。 88 | 89 | 当出现其中的非校验包丢包的情况时,可以通过另外三个包计算出丢失的数据包的内容。 90 | 91 | 当然这种技术只能使用在丢失一个包的情况下,如果出现丢失多个包就不能使用纠错机制了,只能使用重传的方式了。 92 | 93 | ### 小结 94 | 95 | 总结一下内容: 96 | 97 | - HTTP/2 通过多路复用、二进制流、Header 压缩等等技术,极大地提高了性能,但是还是存在着问题的。 98 | - QUIC 基于 UDP 实现,是 HTTP/3 中的底层支撑协议,该协议基于 UDP,又取了 TCP 中的精华,实现了即快又可靠的协议。 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /前端面试之道/28.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 输入 URL 到页面渲染的整个流程 4 | 5 | 之前我们学了那么多章节的内容,是时候找个时间将它们再次复习消化了。就借用这道经典面试题,将之前学习到的浏览器以及网络几章节的知识联系起来。 6 | 7 | 首先是 DNS 查询,如果这一步做了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来,这部分的内容之前没有写过,所以就在这里讲解下。 8 | 9 | ### DNS 10 | 11 | DNS 的作用就是通过域名查询到具体的 IP。 12 | 13 | 因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个 IP 的别名,DNS 就是去查询这个别名的真正名称是什么。 14 | 15 | 在 TCP 握手之前就已经进行了 DNS 查询,这个查询是操作系统自己做的。当你在浏览器中想访问 www.google.com 时,会进行一下操作: 16 | 17 | 1. 操作系统会首先在本地缓存中查询 IP。 18 | 2. 没有的话会去系统配置的 DNS 服务器中查询。 19 | 3. 如果这时候还没得话,会直接去 DNS 根服务器查询,这一步查询会找出负责 com 这个一级域名的服务器。 20 | 4. 然后去该服务器查询 google 这个二级域名。 21 | 5. 接下来三级域名的查询其实是我们配置的,你可以给 www 这个域名配置一个 IP,然后还可以给别的三级域名配置一个 IP。 22 | 23 | 以上介绍的是 DNS 迭代查询,还有种是递归查询,区别就是前者是由客户端去做请求,后者是由系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。 24 | 25 | PS:DNS 是基于 UDP 做的查询,大家也可以考虑下为什么之前不考虑使用 TCP 去实现。 26 | 27 | 接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了。 28 | 29 | 在这一部分中,可以详细说下 TCP 的握手情况以及 TCP 的一些特性。 30 | 31 | 当 TCP 握手结束后就会进行 TLS 握手,然后就开始正式的传输数据。 32 | 33 | 在这一部分中,可以详细说下 TLS 的握手情况以及两种加密方式的内容。 34 | 35 | 数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件。 36 | 37 | 首先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错。 38 | 39 | 浏览器开始解析文件,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件。 40 | 41 | 文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行。 42 | 43 | 如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP/2 协议的话会极大的提高多图的下载效率。 44 | 45 | CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西 46 | 47 | 在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了。 48 | 49 | 这一部分就是渲染原理中讲解到的内容,可以详细的说明下这一过程。并且在下载文件时,也可以说下通过 HTTP/2 协议可以解决队头阻塞的问题。 50 | 51 | 总的来说这一章节就是带着大家从 DNS 查询开始到渲染出画面完整的了解一遍过程,将之前学习到的内容连接起来。 52 | 53 | 当来这一过程远远不止这些内容,但是对于大部分人能答出这些内容已经很不错了,你如果想了解更加详细的过程,可以阅读这篇文章。 54 | -------------------------------------------------------------------------------- /前端面试之道/29.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 设计模式 4 | 5 | 设计模式总的来说是一个抽象的概念,前人通过无数次的实践总结出的一套写代码的方式,通过这种方式写的代码可以让别人更加容易阅读、维护以及复用。 6 | 7 | 这一章节我们将来学习几种最常用的设计模式。 8 | 9 | ### 工厂模式 10 | 11 | 工厂模式分为好几种,这里就不一一讲解了,以下是一个简单工厂模式的例子 12 | 13 | ```js 14 | class Man { 15 | constructor(name) { 16 | this.name = name 17 | } 18 | alertName() { 19 | alert(this.name) 20 | } 21 | } 22 | 23 | class Factory { 24 | static create(name) { 25 | return new Man(name) 26 | } 27 | } 28 | 29 | Factory.create('yck').alertName() 30 | ``` 31 | 32 | 当然工厂模式并不仅仅是用来 new 出实例。 33 | 34 | 可以想象一个场景。假设有一份很复杂的代码需要用户去调用,但是用户并不关心这些复杂的代码,只需要你提供给我一个接口去调用,用户只负责传递需要的参数,至于这些参数怎么使用,内部有什么逻辑是不关心的,只需要你最后返回我一个实例。这个构造过程就是工厂。 35 | 36 | 工厂起到的作用就是隐藏了创建实例的复杂度,只需要提供一个接口,简单清晰。 37 | 38 | 在 Vue 源码中,你也可以看到工厂模式的使用,比如创建异步组件 39 | 40 | ```js 41 | export function createComponent ( 42 | Ctor: Class | Function | Object | void, 43 | data: ?VNodeData, 44 | context: Component, 45 | children: ?Array, 46 | tag?: string 47 | ): VNode | Array | void { 48 | 49 | // 逻辑处理... 50 | 51 | const vnode = new VNode( 52 | `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, 53 | data, undefined, undefined, undefined, context, 54 | { Ctor, propsData, listeners, tag, children }, 55 | asyncFactory 56 | ) 57 | 58 | return vnode 59 | } 60 | ``` 61 | 62 | 在上述代码中,我们可以看到我们只需要调用 `createComponent` 传入参数就能创建一个组件实例,但是创建这个实例是很复杂的一个过程,工厂帮助我们隐藏了这个复杂的过程,只需要一句代码调用就能实现功能。 63 | 64 | ### 单例模式 65 | 66 | 单例模式很常用,比如全局缓存、全局状态管理等等这些只需要一个对象,就可以使用单例模式。 67 | 68 | 单例模式的核心就是保证全局只有一个对象可以访问。因为 JS 是门无类的语言,所以别的语言实现单例的方式并不能套入 JS 中,我们只需要用一个变量确保实例只创建一次就行,以下是如何实现单例模式的例子 69 | 70 | ```js 71 | class Singleton { 72 | constructor() {} 73 | } 74 | 75 | Singleton.getInstance = (function() { 76 | let instance 77 | return function() { 78 | if (!instance) { 79 | instance = new Singleton() 80 | } 81 | return instance 82 | } 83 | })() 84 | 85 | let s1 = Singleton.getInstance() 86 | let s2 = Singleton.getInstance() 87 | console.log(s1 === s2) // true 88 | ``` 89 | 90 | 在 Vuex 源码中,你也可以看到单例模式的使用,虽然它的实现方式不大一样,通过一个外部变量来控制只安装一次 Vuex 91 | 92 | ```js 93 | let Vue // bind on install 94 | 95 | export function install (_Vue) { 96 | if (Vue && _Vue === Vue) { 97 | // 如果发现 Vue 有值,就不重新创建实例了 98 | return 99 | } 100 | Vue = _Vue 101 | applyMixin(Vue) 102 | } 103 | ``` 104 | 105 | ### 适配器模式 106 | 107 | 适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。 108 | 109 | 以下是如何实现适配器模式的例子 110 | 111 | ```js 112 | class Plug { 113 | getName() { 114 | return '港版插头' 115 | } 116 | } 117 | 118 | class Target { 119 | constructor() { 120 | this.plug = new Plug() 121 | } 122 | getName() { 123 | return this.plug.getName() + ' 适配器转二脚插头' 124 | } 125 | } 126 | 127 | let target = new Target() 128 | target.getName() // 港版插头 适配器转二脚插头 129 | ``` 130 | 131 | 在 Vue 中,我们其实经常使用到适配器模式。比如父组件传递给子组件一个时间戳属性,组件内部需要将时间戳转为正常的日期显示,一般会使用 `computed` 来做转换这件事情,这个过程就使用到了适配器模式。 132 | 133 | ### 装饰模式 134 | 135 | 装饰模式不需要改变已有的接口,作用是给对象添加功能。就像我们经常需要给手机戴个保护套防摔一样,不改变手机自身,给手机添加了保护套提供防摔功能。 136 | 137 | 以下是如何实现装饰模式的例子,使用了 ES7 中的装饰器语法 138 | 139 | ```js 140 | function readonly(target, key, descriptor) { 141 | descriptor.writable = false 142 | return descriptor 143 | } 144 | 145 | class Test { 146 | @readonly 147 | name = 'yck' 148 | } 149 | 150 | let t = new Test() 151 | 152 | t.yck = '111' // 不可修改 153 | ``` 154 | 155 | 在 React 中,装饰模式其实随处可见 156 | 157 | ```js 158 | import { connect } from 'react-redux' 159 | class MyComponent extends React.Component { 160 | // ... 161 | } 162 | export default connect(mapStateToProps)(MyComponent) 163 | ``` 164 | 165 | ### 代理模式 166 | 167 | 代理是为了控制对对象的访问,不让外部直接访问到对象。在现实生活中,也有很多代理的场景。比如你需要买一件国外的产品,这时候你可以通过代购来购买产品。 168 | 169 | 在实际代码中其实代理的场景很多,也就不举框架中的例子了,比如事件代理就用到了代理模式。 170 | 171 | ```html 172 |
    173 |
  • 1
  • 174 |
  • 2
  • 175 |
  • 3
  • 176 |
  • 4
  • 177 |
  • 5
  • 178 |
179 | 185 | ``` 186 | 187 | 因为存在太多的 `li`,不可能每个都去绑定事件。这时候可以通过给父节点绑定一个事件,让父节点作为代理去拿到真实点击的节点。 188 | 189 | ### 发布-订阅模式 190 | 191 | 发布-订阅模式也叫做观察者模式。通过一对一或者一对多的依赖关系,当对象发生改变时,订阅方都会收到通知。在现实生活中,也有很多类似场景,比如我需要在购物网站上购买一个产品,但是发现该产品目前处于缺货状态,这时候我可以点击有货通知的按钮,让网站在产品有货的时候通过短信通知我。 192 | 193 | 在实际代码中其实发布-订阅模式也很常见,比如我们点击一个按钮触发了点击事件就是使用了该模式 194 | 195 | ```html 196 |
    197 | 203 | ``` 204 | 205 | 在 Vue 中,如何实现响应式也是使用了该模式。对于需要实现响应式的对象来说,在 get 的时候会进行依赖收集,当改变了对象的属性时,就会触发派发更新。 206 | 207 | ### 外观模式 208 | 209 | 外观模式提供了一个接口,隐藏了内部的逻辑,更加方便外部调用。 210 | 211 | 举个例子来说,我们现在需要实现一个兼容多种浏览器的添加事件方法 212 | 213 | ```js 214 | function addEvent(elm, evType, fn, useCapture) { 215 | if (elm.addEventListener) { 216 | elm.addEventListener(evType, fn, useCapture) 217 | return true 218 | } else if (elm.attachEvent) { 219 | var r = elm.attachEvent("on" + evType, fn) 220 | return r 221 | } else { 222 | elm["on" + evType] = fn 223 | } 224 | } 225 | ``` 226 | 227 | 对于不同的浏览器,添加事件的方式可能会存在兼容问题。如果每次都需要去这样写一遍的话肯定是不能接受的,所以我们将这些判断逻辑统一封装在一个接口中,外部需要添加事件只需要调用 addEvent 即可。 228 | 229 | ### 小结 230 | 231 | 这一章节我们学习了几种常用的设计模式。其实设计模式还有很多,有一些内容很简单,我就没有写在章节中了,比如迭代器模式、原型模式,有一些内容也是不经常使用,所以也就不一一列举了。 -------------------------------------------------------------------------------- /前端面试之道/32.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## CSS 常考面试题资料 4 | 5 | 其实笔者在面试的时候这方面的内容完全没有被问到,并且自己也基本没有准备这一部分的内容。 6 | 7 | 但是鉴于小册面向的群体是大众,肯定会有人被问到这方面的内容,因此我在这一章节会总结一些面试资料给大家,我就不班门弄斧了。 8 | 9 | - [50道CSS基础面试题(附答案)](https://segmentfault.com/a/1190000013325778) 10 | - [《50道CSS基础面试题(附答案)》中的答案真的就只是答案吗?](https://segmentfault.com/a/1190000013860482) 11 | - [CSS 面试题总结](https://segmentfault.com/a/1190000014459893) 12 | - [front-end-interview-handbook](https://github.com/yangshun/front-end-interview-handbook) -------------------------------------------------------------------------------- /前端面试之道/33.md: -------------------------------------------------------------------------------- 1 | # 前端面试之道 2 | 3 | ## 如何写好一封简历 4 | 5 | 简历不是一份记流水账的东西,而是让用人方了解你的亮点的。 6 | 7 | 平时有在做修改简历的收费服务,也算看过蛮多简历了。很多简历都有以下几个特征: 8 | 9 | - 喜欢说自己的特长、优点,用人方真的不关注你的性格是否阳光等等。 10 | - 喜欢列举一大堆个人技能,生怕用人方不知道你会些什么,造成的结果就是好多简历的技能都是差不多。 11 | - 项目经验流水账,比如使用了什么框架,什么 API 做了什么业务。 12 | - 简历页数过多。 13 | 14 | 以上类似简历可以说用人方也看了无数份,完全抓不到你的亮点。 15 | 16 | 简历其实就是**推销自己**,如果你的简历和别人千篇一律,没有亮点,用人方就不会对你产生兴趣。 17 | 18 | 以下是我经常给别人修改简历的意见: 19 | 20 | - 简历页数控制在 2 页以下。 21 | - 技术名词注意大小写。 22 | - 突出个人亮点。比如在项目中如何找到 Bug,解决 Bug 的过程;比如如何发现的性能问题,如何解决性能问题,最终提升了多少性能;比如为何如此选型,目的是什么,较其他有什么优点等等。总体思路就是不写流水账,突出你在项目中具有不错的解决问题的能力和独立思考的能力。 23 | - 斟酌熟悉、精通等字眼,不要给自己挖坑。 24 | - 确保每一个写上去的技术点自己都能说出点什么,杜绝面试官问你一个技术点,你只能答出会用 API 这种减分的情况。 25 | - 拿事实说话。你说你学习能力强,那么请列举你能力强的事实;你说你成绩好,那么请写出你专业的排名。 26 | 27 | 做到以上内容,然后在投递简历的过程中加上一份求职信,对你的求职之路相信能帮上很多忙,当然了,一般来说我推荐尽量走内推通道投递简历。在网上多花点心思就能找到很多内推,比如 V2EX、脉脉等等。 28 | 29 | 说了这么多,我们还是实战来修改一封简历吧,该简历的作者是一名两年经验的前端开发,关键信息已经全部隐去。 30 | 31 | ![](./images/102.webp) 32 | 33 | 这算是简历最先被别人看到的地方,最黄金的广告位必然要放自己最闪亮的东西。 34 | 35 | 比如你是 985、211 毕业的,有不错的成绩、专业排名都可以在这个位置暴露出来。但是如果你学历并不怎么好,可以考虑把教育经历移到简历的最后,尽量把这块黄金位置让出来。 36 | 37 | 然后个人优势这块,一般来说就是个人技能。首先杜绝任何精通的字眼,因为百分之 99 的人都做不到精通,如果你真的精通了,就是一堆工作找你了。一般来说在个人技能这个区域我推荐写上几个前端必备的技能就可以了,然后根据投递的公司可以选择性的添加几个对方需求且自己也会的技能栈,最后个人技能这块内容同样也可以调整到简历的后半部分,没有必要占据一大块的简历第一页内容。 38 | 39 | ![](./images/103.webp) 40 | 41 | 这封简历的工作经历这块写的基本没有什么问题,大家在写这一部分的时候需要注意以下几点: 42 | 43 | - 在工作中有什么不错的结果都可以在这一块表现出来,比如文中的绩效前端小组 7 人最好等等。 44 | - 在工作中解决过什么很困难的问题也可以在这里提一下。 45 | - 最后需要注意一点,以上划红线的内容可能会被面试官问到,要做到心中有数,知道该如何回答,否则还不如不写。就比如说简历中写到了使用了新的架构节省了开发时间,那么这个架构是怎么样的,你对这个架构有什么看法等等这些问题都可能会被问到,要准备好一个通用的回答。 46 | 47 | ![](./images/104.webp) 48 | 49 | 这封简历的工作经历这块写的就触及到很多我之前提到过的问题了。 50 | 51 | 首先两个项目的经验介绍都很流水账,都是属于使用了某某技术实现了某某功能。如果写简历的时候实在想不出平时工作中有遇到什么困难或者解决了什么问题的话,就要确保以上写到的技术栈都能很好的回答出来。 52 | 53 | 以上划红线的地方可能都会是面试官会重点提问的技术栈。 54 | 55 | 其实一封简历写的好,一般需要做到以下两点: 56 | 57 | - 你让用人方了解到你比其他候选者强。 58 | - 不过分夸大,确保简历里写的每一个技术点都心中有数。 59 | 60 | 毕竟简历写的很华丽,只是敲开了公司的第一扇门,如果过分夸大了事实,那么其实就是浪费双方的时间了。 61 | 62 | 大家在写简历的时候可以多多注意以上我提到的几个点,然后在写完以后找出简历中涉及到的所有技术点,并且确保自己能够说个所以然,这样简历这关就没什么问题了。 63 | 64 | 65 | -------------------------------------------------------------------------------- /前端面试之道/images/001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/001.png -------------------------------------------------------------------------------- /前端面试之道/images/002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/002.png -------------------------------------------------------------------------------- /前端面试之道/images/003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/003.png -------------------------------------------------------------------------------- /前端面试之道/images/004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/004.png -------------------------------------------------------------------------------- /前端面试之道/images/005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/005.png -------------------------------------------------------------------------------- /前端面试之道/images/006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/006.png -------------------------------------------------------------------------------- /前端面试之道/images/007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/007.png -------------------------------------------------------------------------------- /前端面试之道/images/008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/008.png -------------------------------------------------------------------------------- /前端面试之道/images/009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/009.png -------------------------------------------------------------------------------- /前端面试之道/images/010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/010.png -------------------------------------------------------------------------------- /前端面试之道/images/011.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/011.webp -------------------------------------------------------------------------------- /前端面试之道/images/012.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/012.webp -------------------------------------------------------------------------------- /前端面试之道/images/013.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/013.webp -------------------------------------------------------------------------------- /前端面试之道/images/014.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/014.webp -------------------------------------------------------------------------------- /前端面试之道/images/015.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/015.gif -------------------------------------------------------------------------------- /前端面试之道/images/016.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/016.webp -------------------------------------------------------------------------------- /前端面试之道/images/017.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/017.webp -------------------------------------------------------------------------------- /前端面试之道/images/018.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/018.webp -------------------------------------------------------------------------------- /前端面试之道/images/020.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/020.webp -------------------------------------------------------------------------------- /前端面试之道/images/021.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/021.webp -------------------------------------------------------------------------------- /前端面试之道/images/022.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/022.webp -------------------------------------------------------------------------------- /前端面试之道/images/023.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/023.webp -------------------------------------------------------------------------------- /前端面试之道/images/024.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/024.webp -------------------------------------------------------------------------------- /前端面试之道/images/025.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/025.webp -------------------------------------------------------------------------------- /前端面试之道/images/026.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/026.webp -------------------------------------------------------------------------------- /前端面试之道/images/027.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/027.webp -------------------------------------------------------------------------------- /前端面试之道/images/028.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/028.webp -------------------------------------------------------------------------------- /前端面试之道/images/029.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/029.webp -------------------------------------------------------------------------------- /前端面试之道/images/030.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/030.webp -------------------------------------------------------------------------------- /前端面试之道/images/031.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/031.webp -------------------------------------------------------------------------------- /前端面试之道/images/032.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/032.webp -------------------------------------------------------------------------------- /前端面试之道/images/033.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/033.webp -------------------------------------------------------------------------------- /前端面试之道/images/034.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/034.webp -------------------------------------------------------------------------------- /前端面试之道/images/035.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/035.webp -------------------------------------------------------------------------------- /前端面试之道/images/036.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/036.webp -------------------------------------------------------------------------------- /前端面试之道/images/037.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/037.webp -------------------------------------------------------------------------------- /前端面试之道/images/038.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/038.webp -------------------------------------------------------------------------------- /前端面试之道/images/039.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/039.webp -------------------------------------------------------------------------------- /前端面试之道/images/040.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/040.webp -------------------------------------------------------------------------------- /前端面试之道/images/041.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/041.webp -------------------------------------------------------------------------------- /前端面试之道/images/042.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/042.webp -------------------------------------------------------------------------------- /前端面试之道/images/043.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/043.webp -------------------------------------------------------------------------------- /前端面试之道/images/044.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/044.webp -------------------------------------------------------------------------------- /前端面试之道/images/045.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/045.webp -------------------------------------------------------------------------------- /前端面试之道/images/046.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/046.webp -------------------------------------------------------------------------------- /前端面试之道/images/047.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/047.webp -------------------------------------------------------------------------------- /前端面试之道/images/048.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/048.gif -------------------------------------------------------------------------------- /前端面试之道/images/049.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/049.webp -------------------------------------------------------------------------------- /前端面试之道/images/050.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/050.webp -------------------------------------------------------------------------------- /前端面试之道/images/051.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/051.webp -------------------------------------------------------------------------------- /前端面试之道/images/052.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/052.webp -------------------------------------------------------------------------------- /前端面试之道/images/053.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/053.webp -------------------------------------------------------------------------------- /前端面试之道/images/054.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/054.webp -------------------------------------------------------------------------------- /前端面试之道/images/055.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/055.webp -------------------------------------------------------------------------------- /前端面试之道/images/056.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/056.webp -------------------------------------------------------------------------------- /前端面试之道/images/057.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/057.webp -------------------------------------------------------------------------------- /前端面试之道/images/058.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/058.webp -------------------------------------------------------------------------------- /前端面试之道/images/059.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/059.webp -------------------------------------------------------------------------------- /前端面试之道/images/060.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/060.webp -------------------------------------------------------------------------------- /前端面试之道/images/061.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/061.webp -------------------------------------------------------------------------------- /前端面试之道/images/062.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/062.webp -------------------------------------------------------------------------------- /前端面试之道/images/063.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/063.webp -------------------------------------------------------------------------------- /前端面试之道/images/064.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/064.webp -------------------------------------------------------------------------------- /前端面试之道/images/065.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/065.webp -------------------------------------------------------------------------------- /前端面试之道/images/066.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/066.webp -------------------------------------------------------------------------------- /前端面试之道/images/067.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/067.webp -------------------------------------------------------------------------------- /前端面试之道/images/068.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/068.webp -------------------------------------------------------------------------------- /前端面试之道/images/069.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/069.webp -------------------------------------------------------------------------------- /前端面试之道/images/070.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/070.webp -------------------------------------------------------------------------------- /前端面试之道/images/071.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/071.webp -------------------------------------------------------------------------------- /前端面试之道/images/072.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/072.webp -------------------------------------------------------------------------------- /前端面试之道/images/073.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/073.webp -------------------------------------------------------------------------------- /前端面试之道/images/074.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/074.webp -------------------------------------------------------------------------------- /前端面试之道/images/075.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/075.webp -------------------------------------------------------------------------------- /前端面试之道/images/076.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/076.webp -------------------------------------------------------------------------------- /前端面试之道/images/077.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/077.webp -------------------------------------------------------------------------------- /前端面试之道/images/078.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/078.webp -------------------------------------------------------------------------------- /前端面试之道/images/079.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/079.webp -------------------------------------------------------------------------------- /前端面试之道/images/080.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/080.webp -------------------------------------------------------------------------------- /前端面试之道/images/081.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/081.webp -------------------------------------------------------------------------------- /前端面试之道/images/082.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/082.webp -------------------------------------------------------------------------------- /前端面试之道/images/083.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/083.webp -------------------------------------------------------------------------------- /前端面试之道/images/084.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/084.webp -------------------------------------------------------------------------------- /前端面试之道/images/085.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/085.webp -------------------------------------------------------------------------------- /前端面试之道/images/086.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/086.webp -------------------------------------------------------------------------------- /前端面试之道/images/087.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/087.webp -------------------------------------------------------------------------------- /前端面试之道/images/088.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/088.webp -------------------------------------------------------------------------------- /前端面试之道/images/089.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/089.webp -------------------------------------------------------------------------------- /前端面试之道/images/090.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/090.webp -------------------------------------------------------------------------------- /前端面试之道/images/091.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/091.webp -------------------------------------------------------------------------------- /前端面试之道/images/092.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/092.webp -------------------------------------------------------------------------------- /前端面试之道/images/093.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/093.webp -------------------------------------------------------------------------------- /前端面试之道/images/094.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/094.gif -------------------------------------------------------------------------------- /前端面试之道/images/095.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/095.gif -------------------------------------------------------------------------------- /前端面试之道/images/096.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/096.gif -------------------------------------------------------------------------------- /前端面试之道/images/097.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/097.gif -------------------------------------------------------------------------------- /前端面试之道/images/098.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/098.gif -------------------------------------------------------------------------------- /前端面试之道/images/099.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/099.gif -------------------------------------------------------------------------------- /前端面试之道/images/100.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/100.webp -------------------------------------------------------------------------------- /前端面试之道/images/101.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/101.webp -------------------------------------------------------------------------------- /前端面试之道/images/102.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/102.webp -------------------------------------------------------------------------------- /前端面试之道/images/103.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/103.webp -------------------------------------------------------------------------------- /前端面试之道/images/104.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/前端面试之道/images/104.webp -------------------------------------------------------------------------------- /重学前端/README.md: -------------------------------------------------------------------------------- 1 | # 重学前端 2 | 3 | ## 开篇词+学习路线+架构图 (3讲) 4 | 5 | - [开篇词 | 从今天起,重新理解前端](./开篇词+学习路线+架构图/01.md) 6 | - [明确你的前端学习路线与方法](./开篇词+学习路线+架构图/02.md) 7 | - [列一份前端知识架构图](./开篇词+学习路线+架构图/03.md) -------------------------------------------------------------------------------- /重学前端/开篇词+学习路线+架构图/01.md: -------------------------------------------------------------------------------- 1 | # 开篇词 | 从今天起,重新理解前端 2 | 3 | ## 前端发展史:从青铜到黄金时代 4 | 5 | 我自己是在 2006 年开始接触前端的。现在回想起来,那会儿前端还处于史前的“青铜时代”,甚至网页的主要交互都还是依靠切换超链接来完成的。 6 | 7 | ### 1. 前端的史前记忆:“青铜时代” 8 | 9 | 那时候,谷歌刚刚基于 Ajax 发布的 Gmail 也没多久,虽然这项伟大的技术标志着 Web 1.0(静态网页)到 Web 2.0(动态网页)的迈进,但在国内依然少有人懂,如果当时谁可以对这项技术侃侃而谈,那简直就是大神的级别了。 10 | 11 | 当时我还是个学生,喜欢前端纯粹是兴趣使然。那时我混黑白棋社区,想着给黑白棋界面写插件,但自己又不懂界面相关的知识,于是开始通过各种方式学习前端。 12 | 13 | 真想学的时候才发现网络上的前端资料很是稀缺,所以我基本上都是先从图书馆借书,然后再在电脑上跑案例验证这样的方式来学习的,现在想起来,还真是一段艰难的岁月。 14 | 15 | 当然,这段经历也为我日后的前端生涯悄悄埋下了一颗种子,我逐渐开始把自己的职业规划路线放在了前端上。 16 | 17 | 这在当时是个不可思议的想法,因为那时的前端岗位不论从收入上还是在职责上,都远落后于其他岗位。但是,我基于对技术发展趋势的判断,认为前端在未来会越来越重要。 18 | 19 | ### 2. 进入发展期的前端:“白银时代” 20 | 21 | 2008 年,我毕业了,也很幸运地得到了一个既能发挥我的 C++ 长处,又能兼顾前端发展规划的职位:微软北京的软件开发工程师,恰好负责的是 Windows CE 上的 IE 浏览器开发,在这里,我接触到了当时最先进的软件工程体系,并且积累了很多 UI 架构经验。 22 | 23 | 两年后,我加入了盛大做电子书,负责电子书的文本排版工作,这个工作是一个既写底层又写 JavaScript 的岗位,同时排版引擎也是浏览器的重要组成部分,也让我对浏览器的工作原理有了更深入的理解。 24 | 25 | 在盛大后期,我加入了 WebOS 项目,负责前端框架,我开始基于移动的角度思考前端交互和框架,这份工作让我离前端又近了一步。 26 | 27 | 但是很遗憾,因为种种原因,我在微软和盛大的几个项目都不算成功,除了电子书实际上市但销量不高,Windows CE 7.0 和盛大的 WebOS 都在公司内部夭折。 28 | 29 | 自己亲手构建的产品,却因为非技术原因没有服务到最终用户,对我来说,是件非常遗憾的事情。不过,这段时间,也让我更加确信前端技术的价值。 30 | 31 | 回过来看,那几年,前端技术开始了它的大踏步发展,那一段时间,可以说是前端的“白银时代”。最直观的表现之一就是前端逐步从后端分离了出来,它的代码也变得复杂了起来,还需要保存数据、处理数据、生成视图等等。 32 | 33 | 悄然之间,我发现前端已经从零散的“工序”逐步发展成为有体系和发展目标的职能,同时,在越来越大的前端团队中,工程化的思想也逐渐萌芽。我深有感触,前端已经不再是别人眼中的“小菜一碟”了。 34 | 35 | ### 3. 从前端到“全端”:“黄金时代” 36 | 37 | 在这样的行业背景里,从盛大离职后,我加入了阿里巴巴做手机淘宝开发,这也是我首次从事真正的前端工作。 38 | 39 | 在手机淘宝,前端团队的各种基础设施也逐渐建立了起来,从最开始的多屏适配方案、基础库、工具链到页面搭建平台和性能体系,最后到客户端融合方案 Weex,我随着团队一起经历了业务发展、团队自身成长和行业变革。 40 | 41 | 与此同时,在我加入阿里巴巴后的这段时间里,随着移动时代的到来,前端也开启了自己的“黄金时代”,它的职责变得更加重要,有了独立的发布权限,技术也变得更加复杂。 42 | 43 | 一些传统软件开发和互联网服务端的方法论逐步移植到前端开发中,并形成了前端自己的工程体系,诸如持续集成、前后端分离、线上监控…… 44 | 45 | 架构方面,前端架构的任务也从简单的解决兼容和风格问题,逐步过渡到提倡组件化和 UI 架构模式,最后形成了新一代的前端框架 React、Vue 和 Angular,他们也在竞争和互相学习中成长。 46 | 47 | ## 前端开发之痛:散点自学 + 基础不牢 48 | 49 | 正当处于“黄金时代”的前端技术在全力以赴极速前行之时,我却发现,前端开发者们的步伐似乎渐渐有些跟不上了。 50 | 51 | 因为在我职业发展的后半段,面试和培养前端工程师已经成为我的长期工作职责。在这期间,我意识到,目前的前端教育几乎是完全缺失的。 52 | 53 | 在面试应届生过程中,我会习惯性地问表现比较好的同学“你是如何学习前端的”,而我得到的答案多是“自学”“在社团学习”,却从未听到过“在学校学习过”这样的答案。 54 | 55 | 而对于工作之后的前端开发者来说,没有系统学习的问题仍然存在,常常有一些具有多年从业经验的工程师,仍然会在看到一些用法时惊呼:“还可以这样!”。 56 | 57 | 在我看来,这些用法都是一些基础的不能再基础的知识点,但是他们却浑然不知。 58 | 59 | **如果深入进去了解,你会发现,表面上看他们可能是一时忘记了,或者之前没注意,但实际上是他们对于前端的知识体系和底层原理没有真正系统地理解。** 60 | 61 | 在阿里工作的时候,我戏称很多同学学前端的方式是“土法学前端”,他们对于知识的理解基本都停留在点上,从来没有大范围把这些点串成线,形成自己的知识体系,因此才会出现上面说的遗漏和盲点。 62 | 63 | 这个问题在一些一直在小公司工作的前端工程师身上非常突出。 64 | 65 | 经常能看到一些案例,一些有技术追求、有热情的工程师,因为技术敏感度和主观能动性都不错,所以工作了五六年之后,逐步开始在自己的公司做一些技术管理相关的事情了。 66 | 67 | 但是,由于他们所在公司的业务并不复杂,也没有技术积累,所以他们自身的技术水平其实并不高,可以说还处于非常初级的阶段(可能面试连阿里 P6 都过不了)。 68 | 69 | **做了管理,技术没跟上,并且还错过了最佳的学习时间,这个境遇可想而知,他们在工作中大概率只能是被动地接受需求解决问题,然后也同时焦虑着自己的未来,焦虑着自己的竞争力。** 70 | 71 | 关于前端工程师成长,我认为需要两个视角。一是立足标准,系统性总结和整理前端知识,建立自己的认知和方法论;二是放眼团队,从业务和工程角度思考前端团队的价值和发展需要。只有这样做,才能够持续发展,在高速发展的技术和工程浪潮中稳稳立足。 72 | 73 | 这也正是“重学前端”这个专栏的初衷,我希望提供一些视角,带你以完备、体系化的方式理解和思考前端的基础知识和工程实践。 74 | 75 | 除此之外,前端工程师也是开发工程师的一员,除了前端自身的领域知识和工程特点外,你还需要了解程序员通用的编程能力和架构能力。 76 | 77 | 所以,想要成为优秀的前端工程师,我觉得你需要通过系统地学习和总结获取知识,通过练习获取编程能力,通过工作经验来获取架构和工程能力。 78 | 79 | 当然,一个为期 3 个月的专栏无法穷尽前端庞杂的知识,讲知识点也不是我们的目标。知识点讲的再好再全,也不一定能记得住。 80 | 81 | 我们专栏的目标是帮助你建立自己的知识体系,根据你自己的理解把前端的领域知识链接起来,形成结构,这样做,不但能帮助你记忆知识,还能在其中发现自己知识的缺失,甚至可以凭借知识体系来判断知识的重要性,来决定是否要深入学习。 82 | 83 | 在这个专栏里,我将知识分成了四个模块来讲解: 84 | 85 | - JavaScript; 86 | - CSS 和 HTML; 87 | - 浏览器实践; 88 | - 前端综合应用。 89 | 90 | 前三个模块是前端的基础知识,是个人的前端能力提升,而模块四则是前端团队发展相关的内容,有助于你和团队的整体提高。 91 | 92 | 在 JavaScript 部分中,我主要会从文法和运行时的角度去讨论 JavaScript 语言。它们是互相关联的,而语义就是文法到运行时之间的桥梁;它们分别又是完备的,任何语言特性都离不开两者,所以从语法和运行时的角度,我们都可以了解完整的 JavaScript。 93 | 94 | CSS 和 HTML 部分,会侧重从语言和设计思想的角度来讲解,我们同样可以对两者的全貌建立一些认知。 95 | 96 | 浏览器部分,包含了浏览器工作的原理和一些重要的 API,包括 BOM、DOM、CSSOM 和其他一些内容。了解了这些知识,你才能把 JavaScript 和 HTML、CSS 连接起来,用 JavaScript 来实现功能。 97 | 98 | 前端综合应用部分,主要是我的一些工作经验,我会选择我在手淘和淘宝工作中的一些案例来辅助讲解。 99 | 100 | 前面,我说到前端是一个非常年轻的职业,但我仍然认为前端具有很多空间和机会,一些基础设施仍然简陋,前端的能力可以带来更多的业务场景,这些有待于我们去发掘。 101 | 102 | 前端社区非常活跃,新技术也在不断出现。在这样的环境下,机会和竞争并存,学习也犹如逆水行舟,不进则退,建立自己的知识体系和方法论,你才能够保持领先优势。 103 | 104 | 我希望从我的经验出发,给你一些启发和帮助,并借由这个专栏帮你建立自己的前端知识体系。同时,我也相信,在你们中间一定会产生更多能够带领前端领域取得突破的、优秀的前端工程师。 105 | 106 | 最后,也希望你能和我分享你的前端故事和经历,你是怎么走上前端道路的?你希望将来成为怎样的前端工程师?欢迎在留言区与我分享。 107 | 108 | -------------------------------------------------------------------------------- /重学前端/开篇词+学习路线+架构图/02.md: -------------------------------------------------------------------------------- 1 | # 明确你的前端学习路线与方法 2 | 3 | 在“开篇词”中,我和你简单回顾了前端行业的发展,到现在为止,前端工程师已经成为研发体系中的重要岗位之一。可是,与此相对的是,我发现极少或者几乎没有大学的计算机专业愿意开设前端课程,更没有系统性的教学方案出现。大部分前端工程师的知识,其实都是来自于实践和工作中零散的学习。 4 | 5 | 这样的现状就引发了一系列的问题。 6 | 7 | 首先是前端的基础知识,常常有一些工作多年的工程师,在看到一些我认为很基础的 JavaScript 语法的时候,还会惊呼“居然可以这样”。是的,基础知识的欠缺会让你束手束脚,更限制你解决问题的思路。 8 | 9 | 其次,技术上存在短板,就会导致前端开发者的上升通道不甚顺畅。特别是一些小公司的程序员,只能靠自己摸索,这样就很容易陷入重复性劳动的陷阱,最终耽误自己的职业发展。 10 | 11 | 除此之外,前端工程师也会面临技术发展问题带来的挑战。前端社区高度活跃,前端标准也在快速更新,这样蓬勃发展对技术来说无疑是好事,但是副作用也显而易见,它使得前端工程师的学习压力变得很大。 12 | 13 | 我们就拿 JavaScript 标准来说,ES6 中引入的新特性超过了过去十年的总和,新特性带来的实践就更多了,仅仅是一个 Proxy 特性的引入,就支持了 VueJS 从 2.0 到 3.0 的内核原理完全升级。 14 | 15 | 缺少系统教育 + 技术快速革新,在这样的大环境下,前端工程师保持自学能力就显得尤其重要了。 16 | 17 | 那么,前端究竟应该怎么学呢?我想,我可以简单分享一下自己的经验。 18 | 19 | ## 学习路径与学习方法 20 | 21 | 首先是 0 基础入门的同学,你可以读几本经典的前端教材,比如《JavaScript 高级程序设计》《精通 CSS》等书籍,去阅读一些参考性质的网站也是不错的选项,比如[MDN](https://developer.mozilla.org/zh-CN/)。 22 | 23 | 如果你至少已经有了 1 年以上的工作经验,希望在技术上有一定突破,那么,这个专栏就可以是你技术进阶的一个选项了。 24 | 25 | 在这个专栏中,我希望传达的不仅仅是具体的知识点,还有体系架构和学习方法。我希望达到三个目标: 26 | 27 | - 带你摸索出适合自己的前端学习方法; 28 | - 帮助你建立起前端技术的知识架构; 29 | - 让你理解前端技术背后的核心思想。 30 | 31 | 在开始具体的知识讲解之前,这篇文章中,我想先来谈两个前端学习方法。 32 | 33 | ### 第一个方法:建立知识架构 34 | 35 | 第一个方法是建立自己的知识架构,并且在这个架构上,不断地进行优化。 36 | 37 | 我们先来讲讲什么叫做知识架构?我们可以把它理解为知识的“目录”或者索引,**它能够帮助我们把零散的知识组织起来,也能够帮助我们发现一些知识上的盲区。** 38 | 39 | 当然,知识的架构是有优劣之分的,最重要的就是逻辑性和完备性。 40 | 41 | 我们来思考一个问题,如果我们要给 JavaScript 知识做一个顶层目录,该怎么做呢? 42 | 43 | 如果我们把一些特别流行的术语和问题,拼凑起来,可能会变成这样: 44 | 45 | - 类型转换; 46 | - this 指针; 47 | - 闭包; 48 | - 作用域链; 49 | - 原型链; 50 | - …… 51 | 52 | 这其实不是我们想要的结果,因为这些知识点之间,没有任何逻辑关系。它们既不是并列关系,又不是递进关系,合在一起,也就没有任何意义。这样的知识架构,无法帮助我们去发现问题和理解问题。 53 | 54 | 如果让我来做,我会这样划分: 55 | 56 | - 文法 57 | - 语义 58 | - 运行时 59 | 60 | 为什么这样分呢,因为对于任何计算机语言来说,必定是“用规定的文法,去表达特定语义,最终操作运行时的”一个过程。 61 | 62 | 这样,JavaScript 的任何知识都不会出现在这个范围之外,这是知识架构的完备性。我们再往下细分一个层级,就变成了这个样子: 63 | 64 | - 文法 65 | - 词法 66 | - 语法 67 | - 语义 68 | - 运行时 69 | - 类型 70 | - 执行过程 71 | 72 | 我来解释一下这个划分。 73 | 74 | 文法可以分成词法和语法,这来自编译原理的划分,同样是完备的。语义则跟语法具有一一对应关系,这里暂时不区分。 75 | 76 | 对于运行时部分,这个划分保持了完备性,**我们都知道:程序 = 算法 + 数据结构,那么,对运行时来说,类型就是数据结构,执行过程就是算法。** 77 | 78 | 当我们再往下细分的时候,就会看到熟悉的概念了,词法中有各种直接量、关键字、运算符,语法和语义则是表达式、语句、函数、对象、模块,类型则包含了对象、数字、字符串等…… 79 | 80 | 这样逐层向下细分,知识框架就初见端倪了。在顶层和大结构上,我们通过逻辑来保持完备性。如果继续往下,就需要一些技巧了,我们可以寻找一些线索。 81 | 82 | 比如在 JavaScript 标准中,有完整的文法定义,它是具有完备性的,所以我们可以根据它来完成,我们还可以根据语法去建立语义的知识架构。实际上,因为 JavaScript 有一份统一的标准,所以相对来说不太困难。 83 | 84 | 如果是浏览器中的 API,那就困难了,它们分布在 w3c 的各种标准当中,非常难找。但是我们要想找到一些具有完备性的线索,也不是没有办法。我喜欢的一个办法,就是用实际的代码去找:for in 遍历 window 的属性,再去找它的内容。 85 | 86 | 我想,学习的过程,实际上就是知识架构不断进化的过程,通过知识架构的自然延伸,我们可以更轻松地记忆一些原本难以记住的点,还可以发现被忽视的知识盲点。 87 | 88 | 建立知识架构,同样有利于面试,没人能够记住所有的知识,当不可避免地谈到一个记不住的知识,如果你能快速定位到它在知识架构中的位置,把一些相关的点讲出来,我想,这也能捞回不少分。(关于前端具体的知识架构,我会在 02 篇文章中详细讲解。) 89 | 90 | ### 第二个方法:追本溯源 91 | 92 | 第二个方法,我把它称作追本溯源。 93 | 94 | 有一些知识,背后有一个很大的体系,例如,我们对比一下 CSS 里面的两个属性: 95 | 96 | - opacity; 97 | - display。 98 | 99 | 虽然都是“属性”,但是它们背后的知识量完全不同,opacity 是个非常单纯的数值,表达的意思也很清楚,而 display 的每一个取值背后都是一个不同的布局体系。我们要讲清楚 display,就必须关注正常流(Normal Flow)、关注弹性布局系统以及 grid 这些内容。 100 | 101 | 还有一些知识,涉及的概念本身经历了各种变迁,变得非常复杂和有争议性,比如 MVC,从 1979 年至今,概念变化非常大,MVC 的定义几乎已经成了一段公案,我曾经截取了 MVC 原始论文、MVP 原始论文、微软 MSDN、Apple 开发者文档,这些内容里面,MVC 画的图、箭头和解释都完全不同。 102 | 103 | 这种时候,就是我们做一些考古工作的时候了。追本溯源,其实就是关注技术提出的背景,关注原始的论文或者文章,关注作者说的话。 104 | 105 | 操作起来也非常简单:翻翻资料(一般 wiki 上就有)找找历史上的文章和人物,再顺藤摸瓜翻出来历史资料就可以了,如果翻出来的是历史人物(幸亏互联网的历史不算悠久),你也可以试着发封邮件问问。 106 | 107 | 这个过程,可以帮助我们理解一些看上去不合理的东西,有时候还可以收获一些趣闻,比如 JavaScript 之父 Brendan Eich 曾经在 Wikipedia 的讨论页上解释 JavaScript 最初想设计一个带有 prototype 的 scheme,结果受到管理层命令把它弄成像 Java 的样子(如果你再挖的深一点,甚至能找到他对某位“尖头老板”的吐槽)。 108 | 109 | 根据这么一句话,我们再去看看 scheme,看看 Java,再看看一些别的基于原型的语言,我们就可以理解为什么 JavaScript 是现在这个样子了:函数是一等公民,却提供了 new this instanceof 等特性,甚至抄来了 Java 的 getYear 这样的 Bug。 110 | 111 | ## 结语 112 | 113 | 今天我带你探索了前端的学习路径,并提出了两个学习方法:你要试着建立自己的知识架构,除此之外,还要学会追本溯源,找到知识的源头。 114 | 115 | 这个专栏中,我并不奢望通过短短的 40 篇专栏,事无巨细地把前端的所有知识都罗列清楚,这本身是 MDN 这样的参考手册的工作。但是,我希望通过这个专栏,把前端技术背后的设计原理和知识体系讲清楚,让你能对前端技术产生整体认知,这样才能够在未来汹涌而来的新技术中保持领先的状态。 116 | 117 | 在你的认识中,前端知识的结构是怎样的?欢迎留言告诉我,我们一起讨论。 118 | 119 | 120 | -------------------------------------------------------------------------------- /重学前端/开篇词+学习路线+架构图/03.md: -------------------------------------------------------------------------------- 1 | # 列一份前端知识架构图 2 | 3 | 在上一篇文章中,我们简要地总结了前端的学习路径与方法,我们提到的第一个学习方法就是:建立知识框架。那么,今天我们就一起来列一份前端的知识框架图。 4 | 5 | 在开始列框架之前,我想先来谈谈我们的目标。实际上,我们在网上可以找到很多参考资料,比如 MDN 这样的参考手册,又比如一份语言标准,但是我们的课程既不是一本参考手册,也不是一份语言标准。参考手册希望做到便于查阅、便于理解和全面,语言标准的目标是严谨、无遗漏、无歧义。 6 | 7 | 而我们的课程有什么不同呢?我认为,作为一个课程,有两个目标:一个是把无法通过查阅解决的原理和背景讲清楚,另一个是把不方便查阅和记忆的内容整理好。 8 | 9 | 我会尽量避免像前面提到的两种文档一样逐条目罗列知识点和细节,当然,这不是在说两种文档没有价值,而是我们各有分工,参考手册和语言标准做的事情,我们没必要重复去做,即使做了也不一定能做得更好。 10 | 11 | **在这个课程里,我希望能和你一起打造一个前端知识的框架,再把知识点做个遍历,这其中,有原理和背景的部分,我去讲解知识的原理和背景。如果没有的话,我们就去讲整理和记忆这部分知识的方法,这样,即使你遇见无法一下子记住的知识,也可以很容易地查阅参考手册和标准来解决。** 12 | 13 | 如果让我做一个划分,前端的知识在总体上分成基础部分和实践部分,基础部分包含了 JavaScript 语言(模块一)、CSS 和 HTML(模块二)以及浏览器的实现原理和 API(模块三),这三个模块涵盖了一个前端工程师所需要掌握的全部知识。 14 | 15 | 学完这三个部分,你再结合基本的编程能力,就可以应对基本的前端开发工作了。实践部分(模块四)重点会介绍我在工作过程中遇到的问题和解决方案,希望这块内容能够帮助你和你的前端团队找到可能的发展方向和着力点。 16 | 17 | ## JavaScript 18 | 19 | ![](./images/01.png) 20 | 21 | 上面是我整理的 JavaScript 知识架构图,下面我们来具体解释一下。 22 | 23 | 在 JavaScript 的模块中,首先我们可以把语言按照文法、语义和运行时来拆分,这符合编程语言的一般规律:**用一定的词法和语法,表达一定语义,从而操作运行时。** 24 | 25 | 接下来,我们又按照程序的一般规律,把运行时分为数据结构和算法部分:数据结构包含类型和实例(JavaScript 的类型系统就是它的 7 种基本类型和 7 种语言类型,实例就是它的内置对象部分)。所谓的算法,就是 JavaScript 的执行过程。 26 | 27 | 类型部分中,对象比其它所有类型加起来都要更为复杂,所以我们会用较长的篇幅来讲解对象,包括它的一些历史和设计思路。 28 | 29 | 执行过程我们则需要按照从大结构到小结构的角度讲解,从最顶层的程序与模块、事件循环和微任务,到函数、再到语句级的执行。我们从粗到细地了解执行过程。 30 | 31 | 实例部分,对 JavaScript 来说类似基础库,JavaScipt 的内置对象多达 150 以上,考虑到我们即使逐次讲解也必定不如 MDN 更加细致全面,所以我们会从应用和机制的角度,挑选其中几个体系来讲解。 32 | 33 | 文法中的语法和语义基本是一一对应关系,在 JavaScript 标准中有一份语法定义表,它同样不适合一一讲解,我们会从 JavaScript 语法中特别的地方,以及与日常开发比较相关的地方来重点讲解,剩下的内容和词法部分,我们会带领大家做一些数据挖掘工作,从这份表格中找到一些和我们日常开发息息相关的内容。 34 | 35 | 语义的大部分内容我们会在运行时的讲解中透出,同时它又跟语法有对应的关系,所以我们不再单独拿出来讲解。 36 | 37 | ## HTML 和 CSS 38 | 39 | ![](./images/02.png) 40 | 41 | 上面是我整理的 HTML 和 CSS 的知识架构图,我们来具体解释一下。 42 | 43 | 在 HTML 的部分,我们会按照功能和语言来划分它的知识,HTML 的功能主要由标签来承担,所以我们首先会把标签做一些分类,并对它们分别进行讲解。 44 | 45 | 我们都知道 HTML 的标签可以分为很多种,head 里面的我们称为元信息类标签,诸如 title、meta、style、link、base 这些,它们用来描述文档的一些基本信息。还有一类是一些诸如 section、nav 的标签,它们在视觉表现上跟 div 并没有区别,但是各有各的适用场景,我们把它们称作语义类标签。另外一类是 img、video、audio 之类的替换型媒体类标签,用来引入外部内容,平常开发中你也会经常用到。再有就是表单类的,比如 input、button。 46 | 47 | 所以,基于这样的分类,我把标签分成下面几种。 48 | 49 | 1. 文档元信息:通常是出现在 head 标签中的元素,包含了描述文档自身的一些信息; 50 | 1. 语义相关:扩展了纯文本,表达文章结构、不同语言要素的标签; 51 | 1. 链接:提供到文档内和文档外的链接; 52 | 1. 替换型标签:引入声音、图片、视频等外部元素替换自身的一类标签; 53 | 1. 表单:用于填写和提交信息的一类标签; 54 | 1. 表格:表头、表尾、单元格等表格的结构。 55 | 56 | 我们的重点会放在前四种标签上,表单和表格较少用到,而且基本以查阅型知识为主,这里就不拿出来讲解了。 57 | 58 | 除了标签之外,我们还应该把 HTML 当作一门语言来了解下,当然,标记语言跟编程语言不太一样,没有编程语言那么严谨,所以,我们会简要介绍 HTML 的语法和几个重要的语言机制:实体、命名空间。 59 | 60 | 最后我们会介绍下 HTML 的补充标准:ARIA,它是 HTML 的扩展,在可访问性领域,它有至关重要的作用。 61 | 62 | CSS 部分,按照惯例,我们也会从语言和功能两个角度去介绍。在语言部分,我们会从大到小介绍 CSS 的各种语法结构,比如 @rule、选择器、单位等等。功能部分,我们大致可以分为布局、绘制和交互类。 63 | 64 | 在布局类我们介绍两个最常用的布局:正常流和弹性布局。绘制类我们则会分成图形相关的和文字相关的绘制。最后我们会介绍动画和其它交互。 65 | 66 | 67 | ## 浏览器的实现原理和 API 68 | 69 | ![](./images/03.png) 70 | 71 | 上面是我整理的浏览器知识架构图,我们来具体看一下。 72 | 73 | 浏览器部分我们会先介绍下浏览器的实现原理,这是我们深入理解 API 的基础。 74 | 75 | 我们会从一般的浏览器设计出发,按照解析、构建 DOM 树、计算 CSS、渲染、合成和绘制的流程来讲解浏览器的工作原理。 76 | 77 | 在 API 部分,我们会从 W3C 零散的标准中挑选几个大块的 API 来详细讲解,主要有:事件、DOM、CSSOM 几个部分,它们分别覆盖了交互、语义和可见效果,这是我们工作中用到的主要内容。 78 | 79 | 其他的 API 怎么办呢,别着急,在最后,我会给出一份 Chrome 已经实现的 API 跟 W3C 标准的对应关系和它的生成过程,来覆盖其它部分。 80 | 81 | ## 前端工程实践 82 | 83 | ![](./images/04.png) 84 | 85 | 最后一个模块是前端工程实践。我们在掌握了前面的基础知识之后,也就基本掌握了做一个前端工程师的底层能力。在这个模块中,我选择了性能、工具链、持续集成、搭建系统、架构与基础库这几个方向的前端工程实践案例,来与你一起分享我的经验。 86 | 87 | ### 性能 88 | 89 | 首先我们会谈谈性能。对任何一个前端团队而言,性能是它价值的核心指标,从早年“重构”的实践开始,前端有通过性能证明自己价值的传统。 90 | 91 | 但是性能并非细节的堆砌,也不是默默做优化,所以,我会从团队的角度来跟你一起探讨性能的方法论和技术体系。 92 | 93 | ### 工具链 94 | 95 | 下一个案例是工具链。这一部分,我将会探讨企业中工具链的建设思路。对一个高效又合作良好的前端团队来说,一致性的工具链是不可或缺的保障,作为开发阶段的入口,工具链又可以和性能、发布、持续集成等系统链接到一起,成为团队技术管理的基础。 96 | 97 | ### 持续集成 98 | 99 | 接下来还会给大家介绍前端的持续集成,持续集成并非一个新概念,但是过去持续集成概念和理论都主要针对软件开发,而对前端来说,持续集成是一个新的课题(当然对持续集成来说,前端也是一个新课题),比如 daily build 就完全不适用前端,前端代码必须是线上实时可用的。这一部分内容将会针对前端的持续集成提出一些建设的思路。 100 | 101 | ### 搭建系统 102 | 103 | 接下来的案例是搭建系统,前端工作往往多而繁杂,针对高重复性、可模块化的业务需求,传统的人工开发不再适用,搭建系统是大部分大型前端团队的选择。这一部分内容我将会介绍什么是搭建系统,以及一些常见的搭建系统类型。 104 | 105 | ### 架构与基础库 106 | 107 | 最后一个部分,会给大家介绍前端架构和基础库的知识。软件架构师主要解决功能复杂性的问题,服务端架构师主要解决高流量问题,而前端是页面间天然解耦,分散在用户端运行的系统,但是前端架构也有自己要解决的问题。 108 | 109 | 110 | 前端需求量大、专业人才稀缺,更因为前端本身运行在浏览器中,有大量兼容工作要做。所以前端架构的主要职责是兼容性、复用和能力扩展。这一部分文章我将会介绍前端架构工作的一些思路和切入点。 111 | 112 | 上面的这些案例来自我在领导手淘前端团队时的经验,和我在阿里巴巴工作参与晋升面试时听到的案例,这些内容几乎是每一个年轻的前端团队成长过程中都会需要的基础设施。 113 | 114 | 好了,前端的知识体系我们大致列出来了。你可能发现了,知识体系图中的每一个知识点,专栏里都有与之对应的文章,这也是我的初衷:希望借由讲解这 40 余个知识点,帮你建立起前端的知识框架。 115 | 116 | ![](./images/05.jpg) 117 | 118 | ### 讲述形式 119 | 120 | 基于这份知识框架图,我们的课程主要采用两种讲述形式:一种是重点讲解的课程,一种是知识图谱型的课程。 121 | 122 | 重点讲解的课程我们会从技术的背景、原理和设计出发,把知识的内容呈现出来。这种形式适用于有体系和源流的知识,比较适合系统学习和理解,比如 JavaScript 中的对象、CSS 的排版。 123 | 124 | 知识图谱型的课程则提供一些方法,用表格或者脑图的形式来整理知识的结构。这种形式适用于零散的知识,比较适合记住大概,用到时去查阅,比如 JavaScript 的词法、HTML 中的所有标签、以及浏览器中的 API 就十分适合这样的讲解方式。 125 | 126 | ## 结语 127 | 128 | 今天我带你一起划分了前端的知识内容,前端的基础知识分成 JavaScript、HTML、CSS 以及浏览器四大重点模块,每个模块也分别有自己的技术重点。你可以在框架中,挑选你最需要的前端知识,按需学习。 129 | 130 | 当然,这篇文章最重要的是,我希望能帮你建立一个理解前端的全景图。这样,任何时候,你都能够体系地思考问题,分析问题。 131 | 132 | 你觉得你的划分跟我一样吗,你还有其他的想法,你觉得是否有想了解的知识不在其中,欢迎给我留言。 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /重学前端/开篇词+学习路线+架构图/images/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/重学前端/开篇词+学习路线+架构图/images/01.png -------------------------------------------------------------------------------- /重学前端/开篇词+学习路线+架构图/images/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/重学前端/开篇词+学习路线+架构图/images/02.png -------------------------------------------------------------------------------- /重学前端/开篇词+学习路线+架构图/images/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/重学前端/开篇词+学习路线+架构图/images/03.png -------------------------------------------------------------------------------- /重学前端/开篇词+学习路线+架构图/images/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/重学前端/开篇词+学习路线+架构图/images/04.jpg -------------------------------------------------------------------------------- /重学前端/开篇词+学习路线+架构图/images/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfe/front-end-interview-guide/3d489c6cb59d2068459c38effaf16b01bfa978e9/重学前端/开篇词+学习路线+架构图/images/05.jpg --------------------------------------------------------------------------------