├── .gitignore ├── README.md ├── animation.md ├── assets ├── 0_-IMsNws_L1g0Syla.png ├── 0_-OqUfzpRtgDJQjXY.png ├── 0_1QVfw-_HSz22ZtJS.png ├── 0_3R6-AyKY1831P10d.jpeg ├── 0_3SRCEtv7rMhWpB5s.png ├── 0_60xiqW7kPsQg5ssn.png ├── 0_66kSmyuCNeD7Oiaq.png ├── 0_9KPehy4mUb8f-hSp.png ├── 0_A5ucCHOZsxXyHMfN.jpeg ├── 0_AsoHzlowoLItnUEL.png ├── 0_B4g8Jpu1dZOF1QMI.png ├── 0_Ci-Jl1Mlki03m3A-.png ├── 0_D586dpehfQtaG2wY.png ├── 0_EoQo41lZY6_RXAii.png ├── 0_GDU4GguTzk8cSAYk.png ├── 0_HP66Xm7oe9u8Ofk1.png ├── 0_IN688nPbgu8zYETe.png ├── 0_Nm9r_NLcAhJernmo.png ├── 0_PTDs1BkbMgekizit.png ├── 0_QbI-hozmSVibF6DW.png ├── 0_QhRSzkngh6Aty-nD.png ├── 0_QphcOVaiVC2YL7Jd.png ├── 0_RRgTDdRfLGEhuR7U.png ├── 0_SXRTlnVxy2-hE9ZX.png ├── 0_SufKRGfPZIDlw1OG.png ├── 0_VKQINIYfu2O7d7BH.png ├── 0_Vqa1yAeYJio0RF5a.png ├── 0_W4wHvVAeUmc45vA2.png ├── 0_XZ2U-ztABhWJOSky.png ├── 0_avLiOV_zXLxOgBee.png ├── 0_bIg_WMaJvjHWSc5J.png ├── 0_bN9YVBLw_tT1Xvte.png ├── 0_c2HpOiUYMimMXHv2.png ├── 0_eEArxn147Ev8xf5n.png ├── 0_g6rpKt5YynDCQ5n3.png ├── 0_hZbijxS0vXu8vUmz.png ├── 0_hxC_NUPNycUBhj-L.png ├── 0_j3zkSjnrL4fnCK3A.png ├── 0_jJuHfRMipW8PPcb0.png ├── 0_k0vSOmvdDkRJzcpW.png ├── 0_kGDQYE70_z58D7na.png ├── 0_l-QdpBfjwNfPDTyh.png ├── 0_mSOIiWpkctkD0Gfg.png ├── 0_ndU4N8xQF6QEQmSY.png ├── 0_nlOmrsfy-Y1XoR8B.png ├── 0_oWsMRoltriNxmRNe.png ├── 0_oqlhISJ0TrPjoG74.png ├── 0_pohqKvj9psTPRlOv.png ├── 0_qelaH45ZWG3UybLO.jpg ├── 0_v56OyrPtg_cZzzaZ.png ├── 0_wzuQ9LYv7CAUICOC.png ├── 0_z-A-JIe5OWFtgyd2.png ├── 1_-vHNuJsJVXvqq5dLHPt7cQ.png ├── 1_0B-dAUOH7NrcCDP6GhKHQw.jpeg ├── 1_0REL14sYPR34hY7yua6-PA.png ├── 1_0e8X3UTBpsiBSZKa3l1hXA.png ├── 1_1Mc_9bnkR74BnACCbP1Q5A.jpeg ├── 1_1NAeDnEv6DWFewX_C-L8mg.png ├── 1_2EduxBOtJ7ENgIna-aU4WA.png ├── 1_2v7G1ZJ1C-y_mWHOYQfQKQ.png ├── 1_48tGIboHxgLeKEjMTGkUGg.png ├── 1_4lHHyfEhVB0LnQ3HlhSs8g.png ├── 1_5YU1su2mdzHEQ5iDisKUyw.png ├── 1_5aBou4onl1B8xlgwoGTDOg.png ├── 1_6o2TRDmrJlS97vh1wEjLYw.png ├── 1_81mCsOzyJj-HfQ1lP_033w.png ├── 1_9b1uEMcZLWuGPuYcIn7ZXQ.png ├── 1_9fbOuFXJHwhqa6ToCc_v2A.png ├── 1_9ryMUEZhtbTg7lECHVz0fw.png ├── 1_AKKvE3QmN_ZQmEzSj16oXg.png ├── 1_AycFMDy9tlDmNoc5LXd9-g.png ├── 1_C1VWSKOx89vqdiSiflDRJw.png ├── 1_FbbOG9mcqWZtNajjDO6SaA.png ├── 1_GF3p99CQPZkX3UkgyVKSHw.png ├── 1_HIn-BxIP38X6mF_65snMKg.png ├── 1_Jyyot22aRkKMF3LN1bgE-w.png ├── 1_KGAppeqpUwv8OgPKkT0Ujw.jpeg ├── 1_KGBiAxjeD9JT2j6KDo0zUg.png ├── 1_M5htfOGgza04ISv_l-69zg.png ├── 1_McIb2yWGRG6PoDUSavp86g.jpeg ├── 1_NVT6WbNrH_mQL64--b-l1Q.png ├── 1_ONNxJHqmMTXB1Nuq3qTNXQ.png ├── 1_OnH_DlbNAPvB9KLxUCyMsA.png ├── 1_OsLmWRogydrW0gG1YlYklQ.jpeg ├── 1_P5nzyldL4rg36dZmt2RViQ.png ├── 1_PgclyCPqxWc1rENfAOesag.png ├── 1_Qm9OFPq3siW0tCKfa03DqQ.png ├── 1_QsVUE3snZD9abYXccg6Sgw.png ├── 1_SEZ_0CFc4zoHp_g7A0KKNw.png ├── 1_Sz4wI2ukt91vRrgf8UonWw.png ├── 1_T-W_ihvl-9rG4dn18kP3Qw.png ├── 1_TozSrkk92l8ho6d8JxqF_w.gif ├── 1_UwtM7DmK1BmlBOUUYEopGQ.png ├── 1_UxkSstuyCvmKkBTnjbezNw.png ├── 1_VDWQl67cmbyAFC5xL9Og4g.png ├── 1_WHR_08AD8APDITQ-4CFDgg.png ├── 1_WVtok3BV0NgU95mpxk9CNg.gif ├── 1_WlMXK3rs_scqKTRV41au7g.jpeg ├── 1_WqInzMPQGGcMX9AOONN76g.jpeg ├── 1_YFr59cEF2qxzjjleebvbcQ.png ├── 1_Yp1KOt_UJ47HChmS9y7KXw.png ├── 1_Zf4reZZJ9DCKsXf5CSXghg.png ├── 1__LdkAVTcSDkoIUbqanj2gg.jpeg ├── 1__nYLhoZPKD_HPhpJtQeErA.png ├── 1_a4lA5FYDkjA9mv53NPKtOg.png ├── 1_dvrghQCVQIZOfNC27Jrtlw.png ├── 1_e0nEd59RPKz9coyY8FX-uw.png ├── 1_eOj6NVwGI2N78onh6CuCbA.png ├── 1_eyaMLcORDVsCFIf5h_ygjA.png ├── 1_ezFoXqgf91umls9FqO0HsQ.png ├── 1_hTMGxzZrOmxxIfaQU7nKig.png ├── 1_hpyVeL1zsaeHaqS7mU4Qfw.png ├── 1_iBedryNbqtixYTKviPC1tA.png ├── 1_iHfI6MQ-YKQvWvo51J-P0w.png ├── 1_jQMQ9BEKPycs2wFC233aNg.png ├── 1_lMBu87MtEsVFqqbfMum-kA.png ├── 1_lvOtCg75ObmUTOxIS6anEQ.png ├── 1_lzOIevUBVy5eWyf2kHf--w.png ├── 1_mVOrpKC9pFTMg4EXPozoog.png ├── 1_oOcY2Gn-LVt1h-e9xOv5oA.jpeg ├── 1_pSh7IORJoUXbwCjyJ7fM9A.png ├── 1_pVnIrMZiB9iAz5sW28AixA.png ├── 1_qY-yRQWGI-DLS3zRHYHm9A.png ├── 1_rWh8YlBn8SypiMduLiYDhA.png ├── 1_rjBdCBwOx5Gp_A6b6FQgfw.png ├── 1_slxXgq_TO38TgtoKpWa_jQ.png ├── 1_spJ8v7GWivxZZzTAzqVPtA.png ├── 1_t2Btfb_tBbBxTvyVgKX0Qg.png ├── 1_tGXhNroe8KxGN7r4UTVSHw.png ├── 1_tLntRcKj-gIVaCcSusDpLQ.jpeg ├── 1_vd3X2O_qRfqaEpW4AfZM4w.png ├── 1_x8P3OcgcgKrEEDpgT2IKkQ.jpeg ├── 1_xhB8ceUNM6vb8s0ZQKMHNg.png ├── 1_ya4zMDfbNUflXhzKz9EBIw.png ├── 1_yn9Y4PXNP8XTz6mtCAzDZQ.png ├── 25_cover.jpeg ├── 26_cover.jpeg ├── 26_shapes.png ├── 27_cover.png ├── 28_cover.jpeg └── 29_cover.jpeg ├── ast.md ├── cryptography.md ├── csrf.md ├── custom-element.md ├── deno.md ├── design-patterns.md ├── event-loop.md ├── exceptions.md ├── functional-programing.md ├── http.md ├── iterators.md ├── memory-management.md ├── mutation-observer.md ├── networking.md ├── overview.md ├── polymorphism.md ├── push-notifications.md ├── regexp.md ├── rendering.md ├── service-worker.md ├── shadow-dom.md ├── storage.md ├── transpiling.md ├── v8.md ├── webassembly.md ├── webrtc.md ├── worker.md └── xss-attack.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Numerous always-ignore extensions 2 | *.diff 3 | *.err 4 | *.log 5 | *.orig 6 | *.rej 7 | *.swo 8 | *.swp 9 | *.vi 10 | *.zip 11 | *~ 12 | 13 | # OS or Editor folders 14 | ._* 15 | .cache 16 | .DS_Store 17 | .idea 18 | .project 19 | .settings 20 | .tmproj 21 | *.esproj 22 | *.sublime-project 23 | *.sublime-workspace 24 | nbproject 25 | Thumbs.db 26 | 27 | # Folders to ignore 28 | node_modules 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # how-javascript-works 2 | 3 | > Contributors: [@Troland](https://github.com/Troland/) [@三月](https://github.com/heaven2049) [@chkaos](https://github.com/chkaos)。 4 | > 5 | > 本文是翻译介绍 JavaScript 的工作原理的,该系列原文还在更新中,原文见[这里](https://blog.sessionstack.com/tagged/tutorial)。 6 | 7 | ## 目录结构 8 | 9 | - 第一章:[语言引擎,运行时和调用栈概述](overview.md) 10 | - 第二章:[如何在 V8 引擎中书写最优代码的 5 条小技巧](v8.md) 11 | - 第三章:[内存管理及如何处理 4 类常见的内存泄漏](memory-management.md) 12 | - 第四章:[事件循环及异步编程的出现和 5 种更好的 async/await 编程方式](event-loop.md) 13 | - 第五章:[深入理解 WebSockets 和带有 SSE 机制的 HTTP/2 以及正确的使用姿势](http.md) 14 | - 第六章:[WebAssembly 对比 JavaScript 及其使用场景](webassembly.md) 15 | - 第七章:[Web Workers 分类及 5 个使用场景](worker.md) 16 | - 第八章:[Service Workers,生命周期及其使用场景](service-worker.md) 17 | - 第九章:[网页消息推送通知机制](push-notifications.md) 18 | - 第十章:[使用 MutationObserver 监测 DOM 变化](mutation-observer.md) 19 | - 第十一章:[渲染引擎及性能优化小技巧](rendering.md) 20 | - 第十二章:[网络层探秘及如何提高其性能和安全性](networking.md) 21 | - 第十三章:[CSS 和 JS 动画底层原理及如何优化其性能](animation.md) 22 | - 第十四章:[解析,语法抽象树及最小化解析时间的 5 条小技巧](ast.md) 23 | - 第十五章:[类和继承及 Babel 和 TypeScript 代码转换探秘](transpiling.md) 24 | - 第十六章:[存储引擎及如何选择合适的存储 API](storage.md) 25 | - 第十七章:[Shadow DOM 内部构造及如何构建独立组件](shadow-dom.md) 26 | - 第十八章:[WebRTC 及点对点网络通信机制](webrtc.md) 27 | - 第十九章:[自定义元素探秘及构建可复用组件最佳实践](custom-element.md) 28 | - 第二十章:[异常及同步/异步代码异常处理最佳实践](exceptions.md) 29 | - 第二十一章:[5 种跨站脚本攻击及防御措施](xss-attack.md) 30 | - 第二十二章:[跨站请求伪造攻击及 7 种缓解策略](csrf.md) 31 | - 第二十三章:[迭代器及生成器高阶控制技巧](iterators.md) 32 | - 第二十四章:[密码术及中间人攻击处理](cryptography.md) 33 | - 第二十五章:[函数式编程及与其他范式对比](functional-programing.md) 34 | - 第二十六章:[3 种多态](polymorphism.md) 35 | - 第二十七章:[正则表达式](regexp.md) 36 | - 第二十八章:[Deno 简介](deno.md) 37 | - 第二十八章:[创建型/结构型/行为型设计模式及 4 种最佳实践](design-patterns.md) 38 | 39 | ## 版权说明 40 | 41 | 未经允许禁止任何形式的转载。 42 | 43 | 除非另行注明,这个项目中的所有内容采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 44 | 45 | 欢迎通过 Issue 或者 Pull Request 提建议。 46 | 47 | ## 来杯咖啡 48 | 49 | [赞助我](https://user-images.githubusercontent.com/1475173/39091700-c211409e-462c-11e8-8531-90261c9a7b73.png) 50 | -------------------------------------------------------------------------------- /animation.md: -------------------------------------------------------------------------------- 1 | # CSS 和 JS 动画底层原理及如何优化其性能 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-under-the-hood-of-css-and-js-animations-how-to-optimize-their-performance-db0e79586216),本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理的第十三章。** 6 | 7 | ## 概述 8 | 9 | 正如你所知,动画在创建令人叹服的网络应用中扮演着一个关键角色。由于用户越来越注重用户体验,企业开始意识到完美,令人愉悦的用户体验的重要性,结果网络应用变得越来越臃肿并且拥有更多动态交互的功能。这就要求网络应用于用户使用过程中提供更加复杂的动画来实现平滑的状态过渡。现在,这已经司空见惯。用户变得越来越挑剔,他们默认期许可以体验高度响应能力和交互良好的用户界面。 10 | 11 | 然而,让界面具有动画效果不一定必须的。哪些需要使用动画,动画的时机,方面及采用何种动画效果都是复杂的问题。 12 | 13 | ## JavaScript 和 CSS 动画比较 14 | 15 | JavaScript 和 CSS 是创建网页动画的两条主要途径。两种不分好赖,看情况用吧。 16 | 17 | ## CSS 动画 18 | 19 | 使用 CSS 动画是让元素在屏幕上移动的最简单方法。 20 | 21 | 我们将会以如何让元素在 X 和 X 座标上移动元素 50 像素作为小示例开始。通过持续 1 秒的 CSS 过渡来移动元素。 22 | 23 | ``` 24 | .box { 25 | -webkit-transform: translate(0, 0); 26 | -webkit-transition: -webkit-transform 1000ms; 27 | 28 | transform: translate(0, 0); 29 | transition: transform 1000ms; 30 | } 31 | 32 | .box.move { 33 | -webkit-transform: translate(50px, 50px); 34 | transform: translate(50px, 50px); 35 | } 36 | ``` 37 | 38 | 当为元素添加 `move` 类的时候,改变 `transform` 的值然后开发发生过渡效果。 39 | 40 | 除了过渡持续时间,还有 **easing** 参数,它主要负责动画体验。该参数会在之后详细介绍。 41 | 42 | 如果通过以上的代码片段可以创建单独的样式类来操作动画,那么也可以使用 JavaScript 来切换每个动画。 43 | 44 | 如下元素: 45 | 46 | ``` 47 |
48 | Sample content. 49 |
50 | ``` 51 | 52 | 然后,使用 JavaScript 来切换每个动画。 53 | 54 | ``` 55 | var boxElements = document.getElementsByClassName('box'), 56 | boxElementsLength = boxElements.length, 57 | i; 58 | 59 | for (i = 0; i < boxElementsLength; i++) { 60 | boxElements[i].classList.add('move'); 61 | } 62 | ``` 63 | 64 | 以上代码片段为每个包含 `box` 类的元素添加 `move` 类来触发动画。 65 | 66 | 这样做可以很好为你的网络应用提供很好的平衡。你就可以专注于使用 JavaScript 来操作应用状态,然后只需为目标元素设置合适的类,让浏览器来处理动画。如若你选择这么处理,就可以监听元素的 `transitionend` 事件,不过须放弃支持 IE 老版本浏览器。 67 | 68 | ![](https://user-images.githubusercontent.com/1475173/42127232-1d296300-7cc7-11e8-8842-e9a7b92eb183.png) 69 | 70 | 如下监听 `transitioned` 事件,该事件会在动画结束时触发。 71 | 72 | ``` 73 | var boxElement = document.querySelector('.box'); // 获取第一个包含 box 类的元素 74 | boxElement.addEventListener('transitionend', onTransitionEnd, false); 75 | 76 | function onTransitionEnd() { 77 | // Handle the transition finishing. 78 | } 79 | ``` 80 | 81 | 除了使用 CSS 过渡,还可以使用 CSS 动画,CSS 动画可以让你更好地控制单独的动画关键帧,持续时间以及循环次数。 82 | 83 | > 关键帧是用来通知浏览器在规定的时间点上应有的 CSS 属性值,然后填充空白。 84 | 85 | 看一个例子: 86 | 87 | ``` 88 | /** 89 | * 该示例是没有包含浏览器前缀的精简版。加上以后会更加准确些。 90 | * 91 | */ 92 | .box { 93 | /* 选择动画名称 */ 94 | animation-name: movingBox; 95 | 96 | /* 动画时长 */ 97 | animation-duration: 2300ms; 98 | 99 | /* 动画循环次数 */ 100 | animation-iteration-count: infinite; 101 | 102 | /* 每次奇数次循环时反转动画方向 */ 103 | animation-direction: alternate; 104 | } 105 | 106 | @keyframes movingBox { 107 | 0% { 108 | transform: translate(0, 0); 109 | opacity: 0.4; 110 | } 111 | 112 | 25% { 113 | opacity: 0.9; 114 | } 115 | 116 | 50% { 117 | transform: translate(150px, 200px); 118 | opacity: 0.2; 119 | } 120 | 121 | 100% { 122 | transform: translate(40px, 30px); 123 | opacity: 0.8; 124 | } 125 | } 126 | ``` 127 | 128 | 效果示例- 129 | 130 | 通过使用 CSS 动画定义独立于目标元素的动画本身,然后设置元素的 animation-name 属性来使用想要的动画效果。 131 | 132 | CSS 动画仍然是需要加浏览器前缀的,在 Safari, Safari 移动浏览器和 Android 端添加 `-webkit-` 前缀。Chrome, Opera, Internet Explorer, and Firefox 端全部不需要添加前缀。有很多工具可以用来创建包含任意前缀的样式,这样就不需要在源文件中带样式前缀。 133 | 134 | **可以使用 autoprefixer 或者 cssnext 来自动为样式添加前缀。** 135 | 136 | ## JavaScript 动画 137 | 138 | 和 CSS 过渡或者 CSS 动画相比,使用 JavaScript 来创建动画要更加复杂些,但是一般而言,它会为开发者提供引人注目且强大的功能。 139 | 140 | 一般情况下,可以内联 JavaScript 动画作为代码的一部分。也可以把它们封装在其它对象之中。以下为复现之前描述的 CSS 过渡的 JavaScript 代码: 141 | 142 | ``` 143 | var boxElement = document.querySelector('.box'); 144 | var animation = boxElement.animate([ 145 | {transform: 'translate(0)'}, 146 | {transform: 'translate(150px, 200px)'} 147 | ], 500); 148 | animation.addEventListener('finish', function() { 149 | boxElement.style.transform = 'translate(150px, 200px)'; 150 | }); 151 | ``` 152 | 153 | 默认情况下,网页动画只是修改了元素的展示效果。如果想要让元素停留在指定的移动位置,那么就得在动画结束的时候修改其底层样式。这也是为什么在以上的示例中监听 `finish` 事件然后设置` box.style.transform` 属性为 `translate(150px, 200px)` 的原因,该属性值和 CSS 动画执行的第二个样式转换是一样的。 154 | 155 | 通过使用 JavaScript 动画,开发者可以完全控制每一步元素的样式。这意味着可以随心所欲地减速,暂停,停止或者翻转动画以控制目标元素。由于可以适当地封装动画行为,所以当在构建复杂的、面向对象的应用程序的时候会特别有用。 156 | 157 | ## Easing 定义 158 | 159 | 自然平滑地移动会让网络应用拥有更好的用户交互体验。 160 | 161 | 自然条件下,没有事物可以线性地从一个点运动到另一个点。现实生活中,在我们周围的物理世界中物体在移动的时候会加速或减速,因为我们并不生活在真空状态下且有不同的因素来影响事物的运行状态。人类的大脑会本能地感受到这样的移动,所以当为网络应用制作动画的时候,利用此类知识会有好处。 162 | 163 | 这是你所应该理解的术语: 164 | 165 | * 『ease in』-开始移动缓慢而后加速 166 | * 『ease out』-开始移动迅速而后减速 167 | 168 | 可以合并两个动画,比如 『ease in out』。 169 | 170 | Easing 可以使得动画更加自然平滑。 171 | 172 | ### Easing 关键字 173 | 174 | 可以为 CSS 过渡和动画选择任意的 easing 方法。不同的关键字会影响动画的 easing。你也可以完全自定义 easing 方法。 175 | 176 | 以下为可以选择用来控制 easing 的 CSS 关键字: 177 | 178 | * `linear` 179 | * `ease-in` 180 | * `ease-out` 181 | * `ease-in-out` 182 | 183 | 让我们深入了解并查看他们的效果。 184 | 185 | ### Linear 动画 186 | 187 | 不使用任何的 easing 方法的动画即为 **linear**。 188 | 189 | 以下为 linear 过渡效果的图示: 190 | 191 | ![](https://user-images.githubusercontent.com/1475173/42127233-1d6c06a6-7cc7-11e8-8036-34c69bc6c478.png) 192 | 193 | 值随着时间流逝,值等比增加。使用 linear 动效,会让动画不自然。一般来说,避免使用 linear 动效。 194 | 195 | 使用如下代码实现一个简单的线性动画: 196 | 197 | `transition: transform 500ms linear;` 198 | 199 | ### Ease-out 动画 200 | 201 | 正如前所述,和 linear 对比,easing out 让动画快速启动,结束时会减速。以下为图示: 202 | 203 | ![](https://user-images.githubusercontent.com/1475173/42127238-1ecfce6a-7cc7-11e8-9c99-9b9ccece2841.png) 204 | 205 | 总之,easing out 是最适合做界面体验的,因为快速地启动会给人以快速响应的动画的感觉,而结束时由于不一致的移动速度让人感觉平滑。 206 | 207 | **打个比喻,比如那些跑车,首先启动速度相当的快,这就给人以愉悦的感觉。这个就比较符合人类对于动画的感知。** 208 | 209 | 有很多的方法来实现 ease out 动画效果,而最简单的即为 CSS 中的 `ease-out` 关键字。 210 | 211 | `transition: transform 500ms ease-out;` 212 | 213 | ### Ease-in 动画 214 | 215 | 和 ease-out 动画相反-其启动慢然后结束时变快。图示如下: 216 | 217 | ![](https://user-images.githubusercontent.com/1475173/42127234-1dbac69c-7cc7-11e8-803d-db8d6b34109c.png) 218 | 219 | 和 ease-out 动画比较,由于他们启动缓慢给人以反应卡顿的感觉,所以 ease-in 让人感觉动画不自然。动画结束时很快给人一种奇怪的感觉,因为整个动画一直在加速,而现实世界中当事物突然停止运动的时候会减速而不是加速。 220 | 221 | 和 ease-out 和 linear 动画类似,使用 CSS 关键字来实现 ease-in 动画: 222 | 223 | `transition: transform 500ms ease-in;` 224 | 225 | ### Ease-in-out 动画 226 | 227 | 该动画为 ease-in 和 ease-out 的合集。图示如下: 228 | 229 | ![](https://user-images.githubusercontent.com/1475173/42127239-1f1ff37c-7cc7-11e8-876e-84d06ccbc88a.png) 230 | 231 | 不要设置动画持续时间过长,否则会给人一种界面不响应的感觉。 232 | 233 | 使用 `ease-in-out` CSS 关键字来实现 ease-in-out 动画: 234 | 235 | `transition: transform 500ms ease-in-out;` 236 | 237 | ### 自定义 easing 238 | 239 | 你可以自定义自己的 easing 曲线,这样就更有效地控制项目中的动画。 240 | 241 | 实际上, `ease-in`,`linear` 及 `ease` 关键字映射到预定义[贝塞尔曲线 ](https://en.wikipedia.org/wiki/B%C3%A9zier_curve),可以在 [CSS transitions specification](http://www.w3.org/TR/css3-transitions/) 和 [Web Animations specification](https://w3c.github.io/web-animations/#scaling-using-a-cubic-bezier-curve) 中查找更多关于贝塞尔曲线的内容。 242 | 243 | ## 贝塞尔曲线 244 | 245 | 让我们看一下贝塞尔曲线的运行原理。一条贝塞尔曲线包含四个点,或者准确地说是包含两组数值。每一对数值内包含表示三次贝塞尔曲线控制点的 X 和 Y 坐标。贝塞尔曲线的起点坐标为 (0, 0) ,终点坐标为 (1, 1)。可以设置两组数值对。每个控制点的 X 轴值必须在 [0, 1] 之间,而 Y 轴值可以超过 [0, 1],虽然规范并没有明确允许超过的数值。 246 | 247 | 248 | 249 | 即使每个控制点的 X 和 Y 值的微小差异都会输出完全不同的贝塞尔曲线。 250 | 251 | 查看维基百科关于[贝塞尔曲线](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)的说明,通俗一点讲即,现在所说的即三次贝塞尔曲线,该曲线由四个点组成,P0, P1, P2, P3 组成,那么,P0 和 P1 组成一对,P2 和 P3 组成一对,P1 和 P2 即为控制点,P0 和 P3 即为起始和结束节点。如下图所示: 252 | 253 | ![](https://user-images.githubusercontent.com/1475173/42132929-7d8a85e4-7d53-11e8-85bb-20a96954809c.png) 254 | 255 | 看下两张拥有相近但不同坐标的控制结点的贝塞尔曲线图。 256 | 257 | ![](https://user-images.githubusercontent.com/1475173/42127235-1dff5262-7cc7-11e8-9625-40aa3326b9df.png) 258 | 259 | 和 260 | 261 | ![](https://user-images.githubusercontent.com/1475173/42127236-1e45015e-7cc7-11e8-81e9-9fa6728dea26.png) 262 | 263 | 如你所见,两张图有很大不同。第一个控制点向量差为 (0.045, 0.183),而第二个控制点向量差为 (-0.427, -0.054)。 264 | 265 | 第二条曲线的样式为: 266 | 267 | `transition: transform 500ms cubic-bezier(0.465, 0.183, 0.153, 0.946);` 268 | 269 | 第一组数值为起始控制点的 X 和 Y 坐标而第二组数值为第二个控制点的 X 和 Y 坐标。 270 | 271 | ## 性能优化 272 | 273 | 你得维持动画帧数为 60 帧每秒,否则会影响到用户体验。 274 | 275 | 和世界上其它事物一样,动画会有性能开销。一些属性的动画性能开销相比其它属性要小。比如,为元素的 `width` 和 `height` 做动画会更改其几何结构并且可能会造成页面上的其它元素移动或者大小的改变。这一过程被称为布局。之前的[文章](https://github.com/Troland/how-javascript-works/blob/master/rendering.md)中有详细介绍过布局和渲染。 276 | 277 | 总之,应该尽量避免为会引起布局和绘制的属性做动画。对于大多数现代浏览器而言,即把动画局限于 `opacity` 和 `transform` 属性。 278 | 279 | ## Will-change 280 | 281 | 可以使用 [will-change](https://dev.w3.org/csswg/css-will-change/) 来通知浏览器将会更改某个元素的属性。这会允许浏览器当更改某个元素属性的时候,事先进行最合理的优化。但不要滥用 `will-change`,因为这样做会适得其反,使得浏览器浪费更多的资源,从而造成更多的性能问题。 282 | 283 | 为 transforms 和 opacity 添加 `will-change` 代码如下: 284 | 285 | ``` 286 | .box { 287 | will-change: transform, opacity; 288 | } 289 | ``` 290 | 291 | 该属性在 Chrome, Firefox,Opera 得到很好的兼容。 292 | 293 | ![](https://user-images.githubusercontent.com/1475173/42127237-1e8970a0-7cc7-11e8-9f96-c4b441bdbef3.png) 294 | 295 | ## 如何选择 JavaScript 和 CSS 来执行动画 296 | 297 | 这个问题是无解的。只需谨记以下原则: 298 | 299 | * 基于 CSS 的动画和原生支持的网页动画一般都是由被称为『合成线程』的线程来处理的。这不同于浏览器的主线程,主线程是用来执行计算样式,布局,绘制及 JavaScript 代码的。这即意味着如果浏览器在主线程上运行耗时的任务,不会中断动画的运行。 300 | * 很多时候,也可以由合成线程来处理 `transforms` 和 `opacity` 属性值的更改。 301 | * 如果有任何动画触发绘制,布局或者同时触发两者,『主线程』将不得不来进行处理。事实是基于 CSS 和 JavaScript 的动画和布局或者绘制的性能开销将很有可能会阻塞所有和 CSS 或者 JavaScript 运行相关的工作,致命动画问题变得毫无意义。 302 | 303 | ## 正确使用动画 304 | 305 | 良好的动画为项目添加一层令人愉快和互动的用户体验。你可以随意使用动画,不管是宽度,调试,定位,颜色或背景色,但必须注意潜在的性能瓶颈。糟糕的动画选择会影响用户体验,所以动画必须是高效且合理的。尽可能减少使用动画。只使用动画来让用户体验流畅自然而不是滥用。 306 | 307 | ## 使用动画进行交互 308 | 309 | 不要因为只是为了用而去使用动画。相反,有策略性地使用动画来加强用户交互体验。避免使用不必要的动画来打断或者阻碍用户的使用。 310 | 311 | ## 避免为性能开销大的属性做动画 312 | 313 | 比糟糕的动画使用更糟的是那些会引起页面卡顿的动画。这类动画让用户感到懊丧和不快。 314 | 315 | ## 引用资源 316 | 317 | - 318 | - 319 | - 320 | 321 | ## 疑问 322 | 323 | 可以看下网上的这篇介绍[贝塞尔曲线](http://www.html-js.com/article/1628)的文章,那么可以如何使用贝塞尔曲线来做出令人惊叹的动画呢? -------------------------------------------------------------------------------- /assets/0_-IMsNws_L1g0Syla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_-IMsNws_L1g0Syla.png -------------------------------------------------------------------------------- /assets/0_-OqUfzpRtgDJQjXY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_-OqUfzpRtgDJQjXY.png -------------------------------------------------------------------------------- /assets/0_1QVfw-_HSz22ZtJS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_1QVfw-_HSz22ZtJS.png -------------------------------------------------------------------------------- /assets/0_3R6-AyKY1831P10d.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_3R6-AyKY1831P10d.jpeg -------------------------------------------------------------------------------- /assets/0_3SRCEtv7rMhWpB5s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_3SRCEtv7rMhWpB5s.png -------------------------------------------------------------------------------- /assets/0_60xiqW7kPsQg5ssn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_60xiqW7kPsQg5ssn.png -------------------------------------------------------------------------------- /assets/0_66kSmyuCNeD7Oiaq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_66kSmyuCNeD7Oiaq.png -------------------------------------------------------------------------------- /assets/0_9KPehy4mUb8f-hSp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_9KPehy4mUb8f-hSp.png -------------------------------------------------------------------------------- /assets/0_A5ucCHOZsxXyHMfN.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_A5ucCHOZsxXyHMfN.jpeg -------------------------------------------------------------------------------- /assets/0_AsoHzlowoLItnUEL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_AsoHzlowoLItnUEL.png -------------------------------------------------------------------------------- /assets/0_B4g8Jpu1dZOF1QMI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_B4g8Jpu1dZOF1QMI.png -------------------------------------------------------------------------------- /assets/0_Ci-Jl1Mlki03m3A-.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_Ci-Jl1Mlki03m3A-.png -------------------------------------------------------------------------------- /assets/0_D586dpehfQtaG2wY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_D586dpehfQtaG2wY.png -------------------------------------------------------------------------------- /assets/0_EoQo41lZY6_RXAii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_EoQo41lZY6_RXAii.png -------------------------------------------------------------------------------- /assets/0_GDU4GguTzk8cSAYk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_GDU4GguTzk8cSAYk.png -------------------------------------------------------------------------------- /assets/0_HP66Xm7oe9u8Ofk1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_HP66Xm7oe9u8Ofk1.png -------------------------------------------------------------------------------- /assets/0_IN688nPbgu8zYETe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_IN688nPbgu8zYETe.png -------------------------------------------------------------------------------- /assets/0_Nm9r_NLcAhJernmo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_Nm9r_NLcAhJernmo.png -------------------------------------------------------------------------------- /assets/0_PTDs1BkbMgekizit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_PTDs1BkbMgekizit.png -------------------------------------------------------------------------------- /assets/0_QbI-hozmSVibF6DW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_QbI-hozmSVibF6DW.png -------------------------------------------------------------------------------- /assets/0_QhRSzkngh6Aty-nD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_QhRSzkngh6Aty-nD.png -------------------------------------------------------------------------------- /assets/0_QphcOVaiVC2YL7Jd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_QphcOVaiVC2YL7Jd.png -------------------------------------------------------------------------------- /assets/0_RRgTDdRfLGEhuR7U.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_RRgTDdRfLGEhuR7U.png -------------------------------------------------------------------------------- /assets/0_SXRTlnVxy2-hE9ZX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_SXRTlnVxy2-hE9ZX.png -------------------------------------------------------------------------------- /assets/0_SufKRGfPZIDlw1OG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_SufKRGfPZIDlw1OG.png -------------------------------------------------------------------------------- /assets/0_VKQINIYfu2O7d7BH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_VKQINIYfu2O7d7BH.png -------------------------------------------------------------------------------- /assets/0_Vqa1yAeYJio0RF5a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_Vqa1yAeYJio0RF5a.png -------------------------------------------------------------------------------- /assets/0_W4wHvVAeUmc45vA2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_W4wHvVAeUmc45vA2.png -------------------------------------------------------------------------------- /assets/0_XZ2U-ztABhWJOSky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_XZ2U-ztABhWJOSky.png -------------------------------------------------------------------------------- /assets/0_avLiOV_zXLxOgBee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_avLiOV_zXLxOgBee.png -------------------------------------------------------------------------------- /assets/0_bIg_WMaJvjHWSc5J.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_bIg_WMaJvjHWSc5J.png -------------------------------------------------------------------------------- /assets/0_bN9YVBLw_tT1Xvte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_bN9YVBLw_tT1Xvte.png -------------------------------------------------------------------------------- /assets/0_c2HpOiUYMimMXHv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_c2HpOiUYMimMXHv2.png -------------------------------------------------------------------------------- /assets/0_eEArxn147Ev8xf5n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_eEArxn147Ev8xf5n.png -------------------------------------------------------------------------------- /assets/0_g6rpKt5YynDCQ5n3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_g6rpKt5YynDCQ5n3.png -------------------------------------------------------------------------------- /assets/0_hZbijxS0vXu8vUmz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_hZbijxS0vXu8vUmz.png -------------------------------------------------------------------------------- /assets/0_hxC_NUPNycUBhj-L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_hxC_NUPNycUBhj-L.png -------------------------------------------------------------------------------- /assets/0_j3zkSjnrL4fnCK3A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_j3zkSjnrL4fnCK3A.png -------------------------------------------------------------------------------- /assets/0_jJuHfRMipW8PPcb0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_jJuHfRMipW8PPcb0.png -------------------------------------------------------------------------------- /assets/0_k0vSOmvdDkRJzcpW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_k0vSOmvdDkRJzcpW.png -------------------------------------------------------------------------------- /assets/0_kGDQYE70_z58D7na.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_kGDQYE70_z58D7na.png -------------------------------------------------------------------------------- /assets/0_l-QdpBfjwNfPDTyh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_l-QdpBfjwNfPDTyh.png -------------------------------------------------------------------------------- /assets/0_mSOIiWpkctkD0Gfg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_mSOIiWpkctkD0Gfg.png -------------------------------------------------------------------------------- /assets/0_ndU4N8xQF6QEQmSY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_ndU4N8xQF6QEQmSY.png -------------------------------------------------------------------------------- /assets/0_nlOmrsfy-Y1XoR8B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_nlOmrsfy-Y1XoR8B.png -------------------------------------------------------------------------------- /assets/0_oWsMRoltriNxmRNe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_oWsMRoltriNxmRNe.png -------------------------------------------------------------------------------- /assets/0_oqlhISJ0TrPjoG74.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_oqlhISJ0TrPjoG74.png -------------------------------------------------------------------------------- /assets/0_pohqKvj9psTPRlOv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_pohqKvj9psTPRlOv.png -------------------------------------------------------------------------------- /assets/0_qelaH45ZWG3UybLO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_qelaH45ZWG3UybLO.jpg -------------------------------------------------------------------------------- /assets/0_v56OyrPtg_cZzzaZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_v56OyrPtg_cZzzaZ.png -------------------------------------------------------------------------------- /assets/0_wzuQ9LYv7CAUICOC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_wzuQ9LYv7CAUICOC.png -------------------------------------------------------------------------------- /assets/0_z-A-JIe5OWFtgyd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/0_z-A-JIe5OWFtgyd2.png -------------------------------------------------------------------------------- /assets/1_-vHNuJsJVXvqq5dLHPt7cQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_-vHNuJsJVXvqq5dLHPt7cQ.png -------------------------------------------------------------------------------- /assets/1_0B-dAUOH7NrcCDP6GhKHQw.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_0B-dAUOH7NrcCDP6GhKHQw.jpeg -------------------------------------------------------------------------------- /assets/1_0REL14sYPR34hY7yua6-PA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_0REL14sYPR34hY7yua6-PA.png -------------------------------------------------------------------------------- /assets/1_0e8X3UTBpsiBSZKa3l1hXA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_0e8X3UTBpsiBSZKa3l1hXA.png -------------------------------------------------------------------------------- /assets/1_1Mc_9bnkR74BnACCbP1Q5A.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_1Mc_9bnkR74BnACCbP1Q5A.jpeg -------------------------------------------------------------------------------- /assets/1_1NAeDnEv6DWFewX_C-L8mg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_1NAeDnEv6DWFewX_C-L8mg.png -------------------------------------------------------------------------------- /assets/1_2EduxBOtJ7ENgIna-aU4WA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_2EduxBOtJ7ENgIna-aU4WA.png -------------------------------------------------------------------------------- /assets/1_2v7G1ZJ1C-y_mWHOYQfQKQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_2v7G1ZJ1C-y_mWHOYQfQKQ.png -------------------------------------------------------------------------------- /assets/1_48tGIboHxgLeKEjMTGkUGg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_48tGIboHxgLeKEjMTGkUGg.png -------------------------------------------------------------------------------- /assets/1_4lHHyfEhVB0LnQ3HlhSs8g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_4lHHyfEhVB0LnQ3HlhSs8g.png -------------------------------------------------------------------------------- /assets/1_5YU1su2mdzHEQ5iDisKUyw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_5YU1su2mdzHEQ5iDisKUyw.png -------------------------------------------------------------------------------- /assets/1_5aBou4onl1B8xlgwoGTDOg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_5aBou4onl1B8xlgwoGTDOg.png -------------------------------------------------------------------------------- /assets/1_6o2TRDmrJlS97vh1wEjLYw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_6o2TRDmrJlS97vh1wEjLYw.png -------------------------------------------------------------------------------- /assets/1_81mCsOzyJj-HfQ1lP_033w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_81mCsOzyJj-HfQ1lP_033w.png -------------------------------------------------------------------------------- /assets/1_9b1uEMcZLWuGPuYcIn7ZXQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_9b1uEMcZLWuGPuYcIn7ZXQ.png -------------------------------------------------------------------------------- /assets/1_9fbOuFXJHwhqa6ToCc_v2A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_9fbOuFXJHwhqa6ToCc_v2A.png -------------------------------------------------------------------------------- /assets/1_9ryMUEZhtbTg7lECHVz0fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_9ryMUEZhtbTg7lECHVz0fw.png -------------------------------------------------------------------------------- /assets/1_AKKvE3QmN_ZQmEzSj16oXg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_AKKvE3QmN_ZQmEzSj16oXg.png -------------------------------------------------------------------------------- /assets/1_AycFMDy9tlDmNoc5LXd9-g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_AycFMDy9tlDmNoc5LXd9-g.png -------------------------------------------------------------------------------- /assets/1_C1VWSKOx89vqdiSiflDRJw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_C1VWSKOx89vqdiSiflDRJw.png -------------------------------------------------------------------------------- /assets/1_FbbOG9mcqWZtNajjDO6SaA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_FbbOG9mcqWZtNajjDO6SaA.png -------------------------------------------------------------------------------- /assets/1_GF3p99CQPZkX3UkgyVKSHw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_GF3p99CQPZkX3UkgyVKSHw.png -------------------------------------------------------------------------------- /assets/1_HIn-BxIP38X6mF_65snMKg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_HIn-BxIP38X6mF_65snMKg.png -------------------------------------------------------------------------------- /assets/1_Jyyot22aRkKMF3LN1bgE-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_Jyyot22aRkKMF3LN1bgE-w.png -------------------------------------------------------------------------------- /assets/1_KGAppeqpUwv8OgPKkT0Ujw.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_KGAppeqpUwv8OgPKkT0Ujw.jpeg -------------------------------------------------------------------------------- /assets/1_KGBiAxjeD9JT2j6KDo0zUg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_KGBiAxjeD9JT2j6KDo0zUg.png -------------------------------------------------------------------------------- /assets/1_M5htfOGgza04ISv_l-69zg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_M5htfOGgza04ISv_l-69zg.png -------------------------------------------------------------------------------- /assets/1_McIb2yWGRG6PoDUSavp86g.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_McIb2yWGRG6PoDUSavp86g.jpeg -------------------------------------------------------------------------------- /assets/1_NVT6WbNrH_mQL64--b-l1Q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_NVT6WbNrH_mQL64--b-l1Q.png -------------------------------------------------------------------------------- /assets/1_ONNxJHqmMTXB1Nuq3qTNXQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_ONNxJHqmMTXB1Nuq3qTNXQ.png -------------------------------------------------------------------------------- /assets/1_OnH_DlbNAPvB9KLxUCyMsA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_OnH_DlbNAPvB9KLxUCyMsA.png -------------------------------------------------------------------------------- /assets/1_OsLmWRogydrW0gG1YlYklQ.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_OsLmWRogydrW0gG1YlYklQ.jpeg -------------------------------------------------------------------------------- /assets/1_P5nzyldL4rg36dZmt2RViQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_P5nzyldL4rg36dZmt2RViQ.png -------------------------------------------------------------------------------- /assets/1_PgclyCPqxWc1rENfAOesag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_PgclyCPqxWc1rENfAOesag.png -------------------------------------------------------------------------------- /assets/1_Qm9OFPq3siW0tCKfa03DqQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_Qm9OFPq3siW0tCKfa03DqQ.png -------------------------------------------------------------------------------- /assets/1_QsVUE3snZD9abYXccg6Sgw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_QsVUE3snZD9abYXccg6Sgw.png -------------------------------------------------------------------------------- /assets/1_SEZ_0CFc4zoHp_g7A0KKNw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_SEZ_0CFc4zoHp_g7A0KKNw.png -------------------------------------------------------------------------------- /assets/1_Sz4wI2ukt91vRrgf8UonWw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_Sz4wI2ukt91vRrgf8UonWw.png -------------------------------------------------------------------------------- /assets/1_T-W_ihvl-9rG4dn18kP3Qw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_T-W_ihvl-9rG4dn18kP3Qw.png -------------------------------------------------------------------------------- /assets/1_TozSrkk92l8ho6d8JxqF_w.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_TozSrkk92l8ho6d8JxqF_w.gif -------------------------------------------------------------------------------- /assets/1_UwtM7DmK1BmlBOUUYEopGQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_UwtM7DmK1BmlBOUUYEopGQ.png -------------------------------------------------------------------------------- /assets/1_UxkSstuyCvmKkBTnjbezNw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_UxkSstuyCvmKkBTnjbezNw.png -------------------------------------------------------------------------------- /assets/1_VDWQl67cmbyAFC5xL9Og4g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_VDWQl67cmbyAFC5xL9Og4g.png -------------------------------------------------------------------------------- /assets/1_WHR_08AD8APDITQ-4CFDgg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_WHR_08AD8APDITQ-4CFDgg.png -------------------------------------------------------------------------------- /assets/1_WVtok3BV0NgU95mpxk9CNg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_WVtok3BV0NgU95mpxk9CNg.gif -------------------------------------------------------------------------------- /assets/1_WlMXK3rs_scqKTRV41au7g.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_WlMXK3rs_scqKTRV41au7g.jpeg -------------------------------------------------------------------------------- /assets/1_WqInzMPQGGcMX9AOONN76g.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_WqInzMPQGGcMX9AOONN76g.jpeg -------------------------------------------------------------------------------- /assets/1_YFr59cEF2qxzjjleebvbcQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_YFr59cEF2qxzjjleebvbcQ.png -------------------------------------------------------------------------------- /assets/1_Yp1KOt_UJ47HChmS9y7KXw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_Yp1KOt_UJ47HChmS9y7KXw.png -------------------------------------------------------------------------------- /assets/1_Zf4reZZJ9DCKsXf5CSXghg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_Zf4reZZJ9DCKsXf5CSXghg.png -------------------------------------------------------------------------------- /assets/1__LdkAVTcSDkoIUbqanj2gg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1__LdkAVTcSDkoIUbqanj2gg.jpeg -------------------------------------------------------------------------------- /assets/1__nYLhoZPKD_HPhpJtQeErA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1__nYLhoZPKD_HPhpJtQeErA.png -------------------------------------------------------------------------------- /assets/1_a4lA5FYDkjA9mv53NPKtOg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_a4lA5FYDkjA9mv53NPKtOg.png -------------------------------------------------------------------------------- /assets/1_dvrghQCVQIZOfNC27Jrtlw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_dvrghQCVQIZOfNC27Jrtlw.png -------------------------------------------------------------------------------- /assets/1_e0nEd59RPKz9coyY8FX-uw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_e0nEd59RPKz9coyY8FX-uw.png -------------------------------------------------------------------------------- /assets/1_eOj6NVwGI2N78onh6CuCbA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_eOj6NVwGI2N78onh6CuCbA.png -------------------------------------------------------------------------------- /assets/1_eyaMLcORDVsCFIf5h_ygjA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_eyaMLcORDVsCFIf5h_ygjA.png -------------------------------------------------------------------------------- /assets/1_ezFoXqgf91umls9FqO0HsQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_ezFoXqgf91umls9FqO0HsQ.png -------------------------------------------------------------------------------- /assets/1_hTMGxzZrOmxxIfaQU7nKig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_hTMGxzZrOmxxIfaQU7nKig.png -------------------------------------------------------------------------------- /assets/1_hpyVeL1zsaeHaqS7mU4Qfw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_hpyVeL1zsaeHaqS7mU4Qfw.png -------------------------------------------------------------------------------- /assets/1_iBedryNbqtixYTKviPC1tA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_iBedryNbqtixYTKviPC1tA.png -------------------------------------------------------------------------------- /assets/1_iHfI6MQ-YKQvWvo51J-P0w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_iHfI6MQ-YKQvWvo51J-P0w.png -------------------------------------------------------------------------------- /assets/1_jQMQ9BEKPycs2wFC233aNg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_jQMQ9BEKPycs2wFC233aNg.png -------------------------------------------------------------------------------- /assets/1_lMBu87MtEsVFqqbfMum-kA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_lMBu87MtEsVFqqbfMum-kA.png -------------------------------------------------------------------------------- /assets/1_lvOtCg75ObmUTOxIS6anEQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_lvOtCg75ObmUTOxIS6anEQ.png -------------------------------------------------------------------------------- /assets/1_lzOIevUBVy5eWyf2kHf--w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_lzOIevUBVy5eWyf2kHf--w.png -------------------------------------------------------------------------------- /assets/1_mVOrpKC9pFTMg4EXPozoog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_mVOrpKC9pFTMg4EXPozoog.png -------------------------------------------------------------------------------- /assets/1_oOcY2Gn-LVt1h-e9xOv5oA.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_oOcY2Gn-LVt1h-e9xOv5oA.jpeg -------------------------------------------------------------------------------- /assets/1_pSh7IORJoUXbwCjyJ7fM9A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_pSh7IORJoUXbwCjyJ7fM9A.png -------------------------------------------------------------------------------- /assets/1_pVnIrMZiB9iAz5sW28AixA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_pVnIrMZiB9iAz5sW28AixA.png -------------------------------------------------------------------------------- /assets/1_qY-yRQWGI-DLS3zRHYHm9A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_qY-yRQWGI-DLS3zRHYHm9A.png -------------------------------------------------------------------------------- /assets/1_rWh8YlBn8SypiMduLiYDhA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_rWh8YlBn8SypiMduLiYDhA.png -------------------------------------------------------------------------------- /assets/1_rjBdCBwOx5Gp_A6b6FQgfw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_rjBdCBwOx5Gp_A6b6FQgfw.png -------------------------------------------------------------------------------- /assets/1_slxXgq_TO38TgtoKpWa_jQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_slxXgq_TO38TgtoKpWa_jQ.png -------------------------------------------------------------------------------- /assets/1_spJ8v7GWivxZZzTAzqVPtA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_spJ8v7GWivxZZzTAzqVPtA.png -------------------------------------------------------------------------------- /assets/1_t2Btfb_tBbBxTvyVgKX0Qg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_t2Btfb_tBbBxTvyVgKX0Qg.png -------------------------------------------------------------------------------- /assets/1_tGXhNroe8KxGN7r4UTVSHw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_tGXhNroe8KxGN7r4UTVSHw.png -------------------------------------------------------------------------------- /assets/1_tLntRcKj-gIVaCcSusDpLQ.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_tLntRcKj-gIVaCcSusDpLQ.jpeg -------------------------------------------------------------------------------- /assets/1_vd3X2O_qRfqaEpW4AfZM4w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_vd3X2O_qRfqaEpW4AfZM4w.png -------------------------------------------------------------------------------- /assets/1_x8P3OcgcgKrEEDpgT2IKkQ.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_x8P3OcgcgKrEEDpgT2IKkQ.jpeg -------------------------------------------------------------------------------- /assets/1_xhB8ceUNM6vb8s0ZQKMHNg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_xhB8ceUNM6vb8s0ZQKMHNg.png -------------------------------------------------------------------------------- /assets/1_ya4zMDfbNUflXhzKz9EBIw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_ya4zMDfbNUflXhzKz9EBIw.png -------------------------------------------------------------------------------- /assets/1_yn9Y4PXNP8XTz6mtCAzDZQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/1_yn9Y4PXNP8XTz6mtCAzDZQ.png -------------------------------------------------------------------------------- /assets/25_cover.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/25_cover.jpeg -------------------------------------------------------------------------------- /assets/26_cover.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/26_cover.jpeg -------------------------------------------------------------------------------- /assets/26_shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/26_shapes.png -------------------------------------------------------------------------------- /assets/27_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/27_cover.png -------------------------------------------------------------------------------- /assets/28_cover.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/28_cover.jpeg -------------------------------------------------------------------------------- /assets/29_cover.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Troland/how-javascript-works/a01d67580d007e07844e2564a541639a169a8af0/assets/29_cover.jpeg -------------------------------------------------------------------------------- /ast.md: -------------------------------------------------------------------------------- 1 | # 解析,抽象语法树及最小化解析时间的 5 条小技巧 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-parsing-abstract-syntax-trees-asts-5-tips-on-how-to-minimize-parse-time-abfcf7e8a0c8),本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理的第十四章。** 6 | 7 | ## 概述 8 | 9 | 我们都知道运行一大段 JavaScript 代码性能会变得很糟糕。代码不仅仅需要在网络中传输而且还需要解析,编译为字节码,最后运行。之前的文章讨论了诸如 JS 引擎,运行时及调用栈,还有为 Google Chrome 和 NodeJS 广泛使用的 V8 引擎的话题。它们都在整个 JavaScript 的运行过程中扮演着重要的角色。 10 | 11 | 今天所讲的主题也非常重要:了解到大多数的 JavaScript 引擎是如何把文本解析为机器能够理解的代码,转换之后发生的事情以及开发者如何利用这一知识。 12 | 13 | ## 编程语言原理 14 | 15 | 那么,首先让我们回顾一下编程语言原理。无论使用何种编程语言,你经常需要一些软件来处理源码以便让计算机按照代码实际去做些事情。该软件可以是解释器或编译器。不管是使用解释型语言(JavaScript, Python, Ruby) 或者编译型语言(C#, Java, Rust),它们都有一个共同点:把源码作为纯文本解析为抽象语法树(AST)的数据结构。AST 不仅要以结构化地方式展示源码,而且在语义分析中扮演了重要的角色,编译器检查验证程序和语言元素的语法使用是否正确及适当。之后, 使用 AST 来生成实际的字节码或者机器码。 16 | 17 | ## AST 程序 18 | 19 | AST 不止应用于语言解释器和编译器,在计算机世界中,还有其它用途。最为常见的用途之一即静态代码分析。静态代码分析并不会运行输入的代码。但是,它们仍然需要理解代码的结构。比如,实现一个工具来找出常见的代码结构以便用来代码重构减少重复代码。或许你可以使用字符串比较来实现,但是工具会相当简单且有局限性。当然了,如果你有兴趣实现这样的工具,你不必自己动手去编写解析器,有许多完美兼容于 Ecmascript 规范的开源项目。Esprima 和 Acorn 即是黄金搭档。还有其它工具可以用来帮助解析器输出代码,即 ASTs.ASTs 被广泛应用于代码转换。举个栗子,你可能想实现一个转换器用来转换 Python 代码为 JavaScript.大致的思路即使用 Python 代码转换器来生成 AST,然后使用该 AST 来生成 JavaScript 代码。你可能会觉得难以置信。事实是 ASTs 只是部分语言的不同表示法。在解析之前,它表现为文本,该文本遵守着构成语言的一些语法规则。解析之后,它表现为一种树状结构,该结构所包含的信息和输入文本几乎一样。因此,也可以进行反向解析然后回到文本。 20 | 21 | ## JavaScript 解析 22 | 23 | 让我们看一下 AST 的构造。以如下一个简单 JavaScript 函数为例子: 24 | 25 | ``` 26 | function foo(x) { 27 | if (x > 10) { 28 | var a = 2; 29 | return a * x; 30 | } 31 | 32 | return x + 10; 33 | } 34 | ``` 35 | 36 | 解析器会产生如下的 AST。 37 | 38 | ![](https://user-images.githubusercontent.com/1475173/42731809-9307a628-8847-11e8-86fa-5f36610d3a44.png) 39 | 40 | 请注意,这里为了展示用只是解析器输出的简化版本。实际的 AST 要更加复杂。然而,这里的意思即了解一下运行源码之前的第一个步骤。可以访问 [AST Explorer](https://astexplorer.net/) 来查看实际的 AST 树。这是一个在线工具,你可以在上面写 JavaScript 代码,然后网站会输出目标代码的 AST。 41 | 42 | 也许你会问为什么我得学习 JavaScript 解析器的工作原理。反正,浏览器会负责运行 JavaScript 代码。你有那么一丁点是正确的。以下图表展示了 JavaScript 运行过程中不同阶段的耗时。瞪大眼睛瞅瞅,也许你可以发现点有趣的东西。 43 | 44 | ![](https://user-images.githubusercontent.com/1475173/42731808-92b9c048-8847-11e8-9f15-97a489246843.png) 45 | 46 | 发现没?通常情况下,浏览器大概消耗了 15% 到 20% 的总运行时间来解析 JavaScript.我没有具体统计过这些数值。这些统计数据来自于现实世界中程序和网站的各种 JavaScript 使用方式。 现在也许 15% 看起来不是很多,但相信我,很多的。一个典型的单页程序会加载大约 0.4M 的 JavaScript 代码,然后消耗掉浏览器大概 370ms 的时间来进行解析。也许你会又说,这也不是很多嘛。本身花费的时间并不多。但记住了,这只是把 JavaScript 代码转化为 ASTs 所消耗的时间。其中不包含运行本身的时间或者页面加载期间其它诸如 [CSS 和 HTML](https://blog.sessionstack.com/how-javascript-works-the-rendering-engine-and-tips-to-optimize-its-performance-7b95553baeda) 渲染的过程的耗时。这仅仅只是桌面浏览器所面临的问题。移动浏览器的情况会更加复杂。一般情况下,手机移动浏览器解析代码的时间是桌面浏览器的 2-5 倍。 47 | 48 | ![](https://user-images.githubusercontent.com/1475173/42731812-93f43a24-8847-11e8-8f46-ed0b3cf0f021.jpeg) 49 | 50 | 以上图表展示了不同移动和桌面浏览器解析 1MB JavaScript 代码所消耗的时间。 51 | 52 | 另外,为了获得更多类原生的用户体验而把越来越多的业务逻辑堆积在前端,网页程序变得越来越复杂。网页程序越来越胖,都快走不动了。你可以轻易地想到网络应用受到的性能影响。只需打开浏览器开发者工具,然后使用该工具来检测解析,编译及其它发生于浏览器中直到页面完全加载所消耗的时间。 53 | 54 | ![](https://user-images.githubusercontent.com/1475173/42731813-944806e0-8847-11e8-8410-4dee15303e3a.jpeg) 55 | 56 | 不幸的是,移动浏览器没有开发者工具来进行性能检测。不用担心。因为有 [DeviceTiming](https://github.com/danielmendel/DeviceTiming) 工具。它可以用来帮助检测受控环境中脚本的解析和运行时间。它通过植入代码来封装本地代码,这样每当从不同设备访问的时候,可以本地测量解析和运行时间。 57 | 58 | 好事即 JavaScript 引擎做了大量的工作来避免冗余工作及更加高效。以下为主流浏览器使用的技术。 59 | 60 | 例如,V8 实现了 script 流和代码缓存技术。Script 流即当脚本开始下载的时候,async 和 deferred 的脚本在单独的线程中进行解析。这意味着解析会在脚本下载完成时立即完成。这会提升 10% 的页面加载速度。 61 | 62 | 每当访问页面的时候,JavaScript 代码通常会被编译为字节码。但是,当用户访问另一个页面的时候,该字节码会作废。这是因为编译的代码严重依赖于编译阶段机器的状态和上下文。从 Chrome 42 开始带来了字节码缓存。该技术会本地缓存编译过的代码,这样当用户返回到同一页面的时候,诸如下载,解析和编译等所有步骤都会被跳过。这样就会为 Chrome 节约大概 40% 的代码解析和编译时间。另外,这同样会节省手机电量。 63 | 64 | Opera 中,[Carakan](https://dev.opera.com/blog/carakan/) 引擎可以复用另一个程序最近编译过的输出。不要求代码在同一页面或是相同域名下。该缓存技术非常高效且可以完全跳过编译步骤。它依赖于典型的用户行为和浏览场景:每当用户在程序/网站上遵循某个用户浏览习惯,则会加载相同的 JavaScript 代码。然而,Carakan 早就被谷歌 V8 引擎所取代。 65 | 66 | Firefox 使用的 [SpiderMonkey](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey) 引擎没有使用任何的缓存技术。它可以过渡到监视阶段,在那里记录脚本运行次数。基于此计算,它推导出频繁使用而可以被优化的代码部分。 67 | 68 | 很明显地,一些人选择不做任何处理。Safari 首席开发者 [Maciej Stachowiak](http://en.wikipedia.org/wiki/Maciej_Stachowiak) 指出 Safari 不缓存编译的字节码。他们可能已经想到了缓存技术但并没付诸实施,因为生成代码的耗时小于总运行时间的 2%。 69 | 70 | 这些优化措施没有直接影响 JavaScript 源码的解析时间,但是会尽可能完全避免。毕竟了胜于无。 71 | 72 | 有许多方法可以用来减少程序的初始化加载时间。最小化加载的 JavaScript 数量:代码越少,解析耗时越少,运行时间越少。为了达到此目的,可以用特殊的方法加载必需的代码而不是一股劳地加载一大坨代码。比如,[PRPL](https://developers.google.com/web/fundamentals/performance/prpl-pattern/) 模式即表示该种代码传输类型。或者,可以检查依赖然后查看是否有无用、冗余的依赖导致代码库的膨胀。然而,这些东西需要很大的篇幅来进行讨论。 73 | 74 | 本文的目标即开发者如何帮助加快 JavaScript 解析器的解析速度。现代 JavaScript 解析器使用 heuristics(启发法) 来决定是否立即运行指定的代码片段或者推迟在未来的某个时候运行。基于这些 heuristics,解析器会进行立即或者懒解析。立即解析会运行需要立即编译的函数。其主要做三件事:构建 AST,构建作用域层级,然后检查所有的语法错误。而懒解析只用于未编译的函数,它不构建 AST和检查任何语法错误。只构建作用域层级,这样相对于立即解析会节省大约一半的时间。 75 | 76 | **React即是使用的启发式来进行 AST diff,从而获得性能的提升。** 77 | 78 | 显然,这并不是一个新概念。甚至像 IE9 这样老掉牙的浏览器也支持该优化技术,虽然和现代解析器的工作方式相比是以一种简陋的方式实现的。 79 | 80 | 举个栗子吧。假设有如下代码片段: 81 | 82 | ``` 83 | function foo() { 84 | function bar(x) { 85 | return x + 10; 86 | } 87 | 88 | function baz(x, y) { 89 | return x + y; 90 | } 91 | 92 | console.log(baz(100, 200)); 93 | } 94 | ``` 95 | 96 | 和之前代码类似,把代码输入解析器进行语法分析然后输出 AST。这样表述如下: 97 | 98 | 声明 bar 函数接收 x 参数。有一个返回语句。函数返回 x 和 10 相加的结果。 99 | 100 | 101 | 102 | 声明 baz 函数接收两个参数(x 和 y)。有一个返回语句。函数函数 x 和 y 相加结果。 103 | 104 | 105 | 106 | 调用 bar 函数传入 40 和 2。 107 | 108 | 109 | 110 | 调用 console.log 参数为之前函数调用的返回值。 111 | 112 | ![](https://user-images.githubusercontent.com/1475173/42731811-93a3a834-8847-11e8-9935-aeee8592ee85.png) 113 | 114 | 那么期间发生了什么呢?解析器发现了 foo 函数声明, baz 函数声明,调用 baz 函数及 console.log 函数。然而,解析器做了完全不相关的额外无用功即解析 bar 函数。为何不相关?因为函数 bar 从未被调用(或者至少不是在对应时间点上)。这只是一个简单示例及可能有些不同寻常,但是在现实生活的许多程序中,许多函数声明从未被调用过。 115 | 116 | 这里不解析 bar 函数,该函数声明了却没有指出其用途。只在需要的时候在函数运行前进行真正的解析。懒解析仍然只需要找出整个函数体然后为其声明。它不需要语法树因其将不会被处理。另外,它不从内存堆中获得内存,而这通常会消耗相当一部分系统资源。简而言之,跳过这些步骤可以有巨大的性能提升。 117 | 118 | 所以之前的例子,解析器实际上会像如下这样解析: 119 | 120 | ![](https://user-images.githubusercontent.com/1475173/42731810-9353f140-8847-11e8-86ee-934c4129865f.png) 121 | 122 | 注意到这里仅仅只是确认函数 bar 声明。没有进入 bar 函数体。当前情况下,函数体只有一句简单的返回语句。然而,正如现代世界中的大多数程序那样,函数体可能会更加庞大,包含多个返回语句,条件语句,循环,变量声明甚至嵌套函数声明。由于函数从未被调用,这完全是在浪费时间和系统资源。 123 | 124 | 实际上这是一个相当简单的概念,然而其实现是非常难的。不局限于以上示例。整个方法还可以应用于函数,循环,条件语句,对象等等。一般情况下,所有代码都需要解析。 125 | 126 | 例如,以下是一个实现 JavaScript 模块的相当常见的模式。 127 | 128 | ``` 129 | var myModule = (function() { 130 | // 整个模块的逻辑 131 | // 返回模块对象 132 | })(); 133 | ``` 134 | 135 | 该模式可以被大多数现代 JavaScript 解析器识别且标识里面的代码需要立即解析。 136 | 137 | 那么为何解析器不都使用懒解析呢?如果懒解析一些代码,而该代码必须立即运行,这样实际上会降低代码运行速度。需要运行一次懒解析之后进行另一个立即解析。和立即解析相比,运行速度会降低 50%。 138 | 139 | 现在,对解析器底层原理有了大致的理解,是时候考虑如何帮助提高解析器的解析速度了。可以以这样的方式编写代码,这样就可以在正确的时间解析函数。这里有一个为大多数解析器所识别的模式:使用括号封装函数。这样会告诉解析器需要立即函数。如果解析器看到一个左括号且之后为函数声明,它会立即解析该函数。可以通过显式声明立即运行函数来帮助解析器加快解析速度。 140 | 141 | 假设有一个 foo 函数 142 | 143 | ``` 144 | function foo(x) { 145 | return x * 10; 146 | } 147 | ``` 148 | 149 | 因为没有明显地标识表明需要立即运行该函数所以浏览器会进行懒解析。然而,我们确定这是不对的,那么可以运行两个步骤。 150 | 151 | 首先,把函数存储为一变量。 152 | 153 | ``` 154 | var foo = function foo(x) { 155 | return x * 10; 156 | }; 157 | ``` 158 | 159 | 注意,在 function 关键字和函数参数的左括号之间的函数名。这并不是必要的,但推荐这样做,因为当抛出异常错误的时候,堆栈追踪会包含实际的函数名而不是 。 160 | 161 | 解析器仍然会做懒解析。可以做一个微小的改动来解决这一问题:用括号封装函数。 162 | 163 | ``` 164 | var foo = (function foo(x) { 165 | return x * 10; 166 | }); 167 | ``` 168 | 169 | 现在,解析器看见 function 关键字前的左括号便会立即进行解析。 170 | 171 | 因需要知道解析器在何种情况下懒解析或者立即解析代码,进行手动操作会相当困难。同样地,开发者需要花时间考虑指定的函数是否需要立即解析。肯定没人想费力地这么做。最后同样重要的是,这肯定会让代码难以阅读和理解。可以使用 Optimize.js 来处理此类情况。该工具只是用来优化 JavaScript 源代码的初始加载时间。他们对代码运行静态分析,然后通过使用括号封装需要立即运行的函数以便浏览器立即解析并准备运行它们。 172 | 173 | 那么,可以如平常杂编码然后一小段代码如下: 174 | 175 | ``` 176 | (function() { 177 | console.log('Hello, World!'); 178 | })(); 179 | ``` 180 | 181 | 一切看起来很美好,因为在函数声明前添加了左括号。当然,在进入生产环境之前需要进行代码压缩。以下为压缩工具的输出: 182 | 183 | ``` 184 | !function(){console.log('Hello, World!')}(); 185 | ``` 186 | 187 | 看起来一切正常。代码如期运行。然而好像少了什么。压缩工具移除了封装函数的括号代之以一个感叹号。这意味着解析器会跳过该代码且将会运行懒解析。总之,为了运行该函数,解析器会在懒解析之后进行立即解析。这会导致代码运行变慢。幸运的是,可以利用 Optimize.js 来解决此类问题。传给 Optimize.js 压缩过的代码会输出如下代码: 188 | 189 | ``` 190 | !(function(){console.log('Hello, World!')})(); 191 | ``` 192 | 193 | 现在,充分利用了各自的优势:压缩代码且解析器正确地识别懒解析和立即解析的函数。 194 | 195 | ## 预编译 196 | 197 | 但是为何不在服务端进行这些工作呢?总之,比强制各个客户端重复做该项事情更好的做法是只在服务端运行一次并在客户端输出结果。那么,有一个正在进行的讨论即引擎是否需要提供一个运行预编译代码的功能以节省浏览器的运行时间。本质上,该思路即使用服务端工具来生成字节码,这样就只需要传输字节码并在客户端运行。之后,将会看到启动时间上的一些主要差异。这听起来很有诱惑性但实现起来会很难。可能会有反效果,因为它将会很庞大且由于安全原因很有可能需要进行签名和处理。例如,V8 团队已经在内部解决重复解析问题,这样预编译有可能实际上没啥鸟用。 198 | 199 | ## 一些提升网络应用速度的建议 200 | 201 | * 检查依赖。减少不必要的依赖。 202 | * 分割代码为更小的块而不是一整块。如 webpack 的 code-spliting 功能。 203 | * 尽可能延迟加载 JavaScript 代码。可以只加载当前路由所要求的代码片段。比如只在点击某个元素的时候引入 某段代码模块。 204 | * 使用开发者工具和 DeviceTiming 来检测性能瓶颈。 205 | * 使用像 Optimize.js 的工具来帮助解析器选择立即解析或者懒解析以加快解析速度。 206 | 207 | ## 拓展 208 | 209 | 有时候,特别是手机端浏览器,比如当你点击前进/后退按钮的时候,浏览器会进行缓存。但是在有些场景下,你可能不需要浏览器的这种功能。有如下解决办法: 210 | 211 | ``` 212 | window.addEventListener('pageshow', (event) => { 213 | // 检查前进/后退缓存,是否从缓存加载页面 214 | if (event.persisted || window.performance && 215 | window.performance.navigation.type === 2) { 216 | // 进行相应的逻辑处理 217 | } 218 | }; 219 | ``` -------------------------------------------------------------------------------- /csrf.md: -------------------------------------------------------------------------------- 1 | # 跨站请求伪造攻击及7种缓解策略 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-csrf-attacks-7-mitigation-strategies-757dfb08e7a6),本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理第二十二章。** 6 | 7 | ## 概述 8 | 9 | 跨站请求伪造(CSRF:Cross-site request forgery, 发音为“sea-surf”),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是 Web 应用程序或网页的一种恶意攻击方法。在这类型的攻击中,攻击者伪装成受害者执行恶意请求。 10 | 11 | 恶意 Web 程序可以通过多种方式发起请求,例如特制图像标签,隐藏表单,AJAX 请求等。它们可以在用户不参与甚至不知情的情况下运行。 12 | 13 | 与[跨站脚本](https://blog.sessionstack.com/how-javascript-works-5-types-of-xss-attacks-tips-on-preventing-them-e6e28327748a)(XSS)相比,XSS 利用的是用户对特定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。 14 | 15 | ## 探索跨站请求伪造攻击 16 | 17 | 当 CSRF 攻击工作时,受害​​者会提交他们不知情的恶意请求。这可能会导致 Web 应用程序执行一些操作,包括客户端或服务器数据泄漏,会话状态的更改和操纵终端用户帐户等。 18 | 19 | CSRF 攻击是针对浏览器的责任混淆攻击的一个例子,因为低权限的攻击者欺骗了浏览器从而提交伪造的请求。 20 | 21 | CSRF 通常具有以下特征: 22 | 23 | * 涉及依赖用户认证体系的网站或 Web 应用。 24 | * 利用网站对该认证的信任。 25 | * 欺骗用户浏览器向目标站点发送 HTTP 请求。 26 | * 涉及存在副作用的 HTTP 请求。 27 | 28 | 跨站请求伪造攻击步骤如下: 29 | * 受害者执行由攻击者控制的动作,例如访问网页,点击链接等。 30 | * 此操作伪装成受害者的身份发送 HTTP 请求到 Web 应用。 31 | * 如果受害者在 Web 应用上具有通过身份验证的活动会话,则该请求将作为合法请求进行处理。 32 | 33 | 重要的是,受害者与网络应用间必须保持有效会话,而该会话就是 CSRF 攻击的目标。 34 | 35 | 大多数情况下,CSRF 攻击并不会窃取私人信息,而是触发与受害者帐户相关的某种形式上的变更,例如修改凭据甚至进行购物行为。强迫受害者从服务器获取数据不会对攻击者产生受益,因为攻击方没有接收到服务端的响应。但受害人可以,正因如此,CSRF攻击的目标主要锁定在执行更改的请求上。 36 | 37 | Web 应用程序中的会话管理通常是基于 cookies。每次向服务器发送请求时,浏览器都为请求携带上相关 cookie,用于标识当前用户的会话。即使请求源来自其他 Web 应用和域,通常也会发生这种情况。这正是攻击者利用的漏洞。 38 | 39 | 尽管我们通常将 CSRF 描述为基于 cookie 会话处理相关,它也出现在应用程序自动向请求添加一些用户凭据等其他情况下,例如 HTTP [基本认证](https://en.wikipedia.org/wiki/Basic_access_authentication)和基于证书的认证。 40 | 41 | ### 例子 42 | 43 | 让我们看下面的示例,为社交网络应用上的一个简单的“个人资料页面”: 44 | 45 | ```html 46 | 47 | 48 | 49 | 50 | 56 | 57 | 58 |
59 |
60 | Your Profile: 61 | 62 |

63 | 64 | 65 |

66 | 67 | 68 |
69 |
70 | 71 | 72 | 73 | 该页面只是简单从服务器加载用户配置文件数据,并填充到表单中。如果表单数据被编辑,则发送请求到服务器更新数据。 74 | 75 | 只有当前用户通过身份验证时,服务器才会接受提交的数据。 76 | 77 | 让我们看一下执行 CSRF 攻击的恶意页面。页面由攻击者创建,并且位于一个不同的域上。该页面的目标是根据通过身份验证的受害者向社交网络应用发送请求: 78 | 79 | ```html 80 | 81 | 82 | 83 | 84 |
85 | 86 | 87 |
88 | 89 | 92 | 93 | 94 | ``` 95 | 96 | 该页面包含一个带有隐藏字段的表单。其操作指向与社交网络的个人资料页面相同的端点。 97 | 98 | 一旦受害者打开了恶意网站,该表单就会通过页面脚本自动将数据提交至服务器。 99 | 100 | 当社交应用的用户未通过身份验证时表单没有危害。由于缺少身份验证数据,易受攻击的 Web 应用程序将拒绝更改用户的个人资料。但如果用户通过了身份验证,则修改将被应用认为是合法请求。 101 | 102 | 由于浏览器上的 Cookie 跟踪了用户在社交网络上的会话。当易受攻击的 Web 程序收到更新请求时,由于它具有正确的会话 cookie,因此是合法的。 103 | 104 | ![CSRF 攻击](./assets/1_1Mc_9bnkR74BnACCbP1Q5A.jpeg) 105 | 106 | 因此,即使攻击者无法直接访问到 Web 应用程序,也可以利用受害者和 CSRF 漏洞来执行未经授权的操作。实际上,与 XSS 攻击不同,攻击者不会直接读取 cookie 并窃取。 107 | 108 | 此示例是一种过于简单但成熟的攻击,实际上它可能更复杂且不易察觉。例如,CSRF 攻击可以嵌入 iframe,且受害者根本不会意识到攻击的发生。 109 | 110 | 为了降低受到 CSRF 攻击的风险,下面是应遵守的一系列方法。 111 | 112 | ## 基于令牌的措施 113 | 114 | 这种防御措施是缓解 CSRF 攻击的最受欢迎和推荐的方法之一。可以通过两种方法来实现: 115 | 116 | * 有状态—同步器令牌模式 117 | * 无状态-基于加密或散列的令牌模式 118 | 119 | 许多流行框架提供了现成的实现技术。 120 | 121 | ### CSRF 内置实现 122 | 123 | 强烈建议开发者在尝试构建自定义系统前,先研究所使用的框架是否内置 CSRF 保护的选项。 124 | 125 | 即使有这样的系统,仍有一些配置需要开发者正确处理,例如密钥管理和令牌管理。 126 | 127 | 如果使用的框架中没有内置的 CSRF 保护机制,才考虑造轮子。 128 | 129 | 让我们看一下 `Express` 中的内置 CSRF 实现。`Express` 提供了一个名为 `csurf` 的中间件,仅此而已。 130 | 131 | 在本文中,我们不会讨论有关 `Express` 的知识或安装软件包的详细步骤。 132 | 133 | 这是我们的 `index.js` 文件: 134 | 135 | ```js 136 | const express = require('express'); 137 | const bodyParser = require('body-parser'); 138 | const csrf = require('csurf') 139 | const cookieParser = require('cookie-parser') 140 | const app = express(); 141 | const csrfProtection = csrf({ cookie: true }); 142 | 143 | app.use(cookieParser()); 144 | app.use(bodyParser.urlencoded({ extended: true })); 145 | app.set('view engine', 'ejs'); 146 | 147 | app.get('/', csrfProtection, (req, res) => { 148 | res.render('index', { csrfToken: req.csrfToken() }); 149 | }); 150 | 151 | app.post('/profile', csrfProtection, (req, res, next) => { 152 | res.send(req.body.name); 153 | }); 154 | 155 | app.listen(3000); 156 | ``` 157 | 158 | 然后在 `views` 目录下添加 `index.ejs`,如下所示: 159 | 160 | ```html 161 |
162 | 163 | 164 | 165 | 166 |
167 | ``` 168 | 169 | 根路由将使用模板中的 `csrfToken` 变量来渲染 `index.ejs` 模板。在 `index.ejs` 中,`csrfToken` 将被设置为隐藏字段的值。 170 | 171 | 提交表单后,请求将发送至受 CSRF 保护的`/profile`路由。如果没有令牌,将抛出无效的`CSRF`令牌错误。 172 | 173 | ### 同步器令牌模式 174 | 175 | 同步器令牌模式允许服务器验证请求并确保它们来自合法的源。该模式的工作原理是在服务器上为每个用户会话或每个请求生成令牌。 176 | 177 | 客户端发送请求时,服务器必须对比用户会话中的令牌并验证请求中令牌的存在和有效性。 178 | 179 | 大多数 Web 应用中,服务器使用 HTTP 会话对象来标识已登录用户。这种情况下服务器先生成会话,并将会话 ID 传递给客户端。该会话 ID 大部分时间都保存在客户端 cookie 中。 180 | 181 | 如果存储会话 ID 的 cookie 不受高级配置(httponly、samesite、secure 等)的保护,则可以从浏览器中打开的另一个页面访问此 cookie。 182 | 183 | 每个请求生成令牌的方法更安全,因为攻击者有较少的时间来干扰和利用令牌。但这种方法会降低用户体验。如果用户单击浏览器中的“后退”按钮,则上一页可能包含失效令牌。这意味着与上一页的交互将导致服务器无法验证令牌导致请求失败。 184 | 185 | CSRF 令牌应具有以下特征: 186 | * 每个会话具有唯一性 187 | * 难以预测-安全生成的随机值 188 | 189 | CSRF 令牌可以缓解 CSRF 攻击,如果没有令牌,攻击者将无法创建能在服务器上执行的有效请求。 190 | 191 | 由于攻击者可能会拦截或访问 CSRF 令牌,因此请勿使用 cookies 进行传输。 192 | 193 | 另外,也不建议通过 `GET` 请求传输 CSRF 令牌,因为如果受保护的站点链接到外部站点,则可能会在多个位置泄漏,例如浏览器历史记录,日志文件,`Referrer headers` 等。 194 | 195 | CSRF 令牌应通过以下方式传输: 196 | 197 | * 表单中使用的隐藏字段 198 | * AJAX 请求头 199 | 200 | 将 CSRF 令牌添加到表单的方法如下: 201 | 202 | ```html 203 |
204 | 205 |
206 | ``` 207 | 208 | 与上面 `Express` 示例类似,在服务器上生成作为输入字段值的令牌。 209 | 210 | ### 基于加密或散列的令牌模式 211 | 212 | 顾名思义,基于加密的令牌模式基于加密。对于现代 Web 应用,这是一种更合适的方法,程序在服务器上不维护任何状态。 213 | 214 | 令牌由服务器生成的,一般是由用户的会话 ID 和时间戳组成。这使用密钥对其加密。令牌生成后,将其返回给客户端。与同步令牌一样,基于加密的令牌既可以存储在隐藏字段中,也可以添加到 AJAX 请求头。 215 | 216 | 使用令牌发出请求后,服务器将尝试使用密钥对其进行解密。如果服务器无法解密令牌,则意味着存在某种形式的入侵,并且该请求被视为恶意或无效。 217 | 218 | 如果服务器成功解码,则将提取会话 ID 和时间戳。首先比较会话 ID 与当前经过身份验证的用户,并将时间戳与当前服务器时间进行比较,以确认其未超出预设到期时间。 219 | 220 | 如果会话 ID 与当前用户匹配,并且时间戳未过期,则该请求被视为安全请求。 221 | 222 | ## SameSite Cookie 223 | 224 | Cookie 的 `SameSite` 属性用来限制第三方 Cookie,从而减少安全风险。 225 | 226 | 此属性使浏览器可以决定是否与跨站点请求一起发送cookie。可选值为: 227 | 228 | * **Strict**- Cookies 仅在第一方上下文中发送,不会与第三方网站发起的请求一起发送。这意味着,如果某个网站上有指向 GitHub 私仓的链接,则单击链接后 GitHub 不会收到会话 cookie,用户将无法访问该存储库。 229 | * **Lax**- Cookies 不会通过 CSRF 倾向的请求方法(例如 POST)发送。当用户导航到原始站点时,将发送 Cookie。如果在最近的浏览器版本中未明确指定 SameSite,则这是默认的 cookie 值。如果某个网站上有指向私有GitHub存储库的链接,则 GitHub将收到会话 cookie,且用户将能够访问该存储库。 230 | * **None**-Cookies将在所有情况下(例如第一方请求和跨域请求)发送。此外,将需要 `secure flag`(SSL/HTTPS)。 231 | 232 | 所有台式机浏览器和绝大部分移动端浏览器都支持 SameSite 属性。 233 | 234 | 此属性不是用来完全替代 CSRF 令牌。相反,应该与令牌共存,以便以更可靠的方式保护用户。 235 | 236 | ## Origins 校验 237 | 238 | 此措施包括两个步骤,这些步骤依赖于检查 HTTP 请求头的值: 239 | 240 | * 确定来源域 - 可以通过 `Origin` 或者 `Referer` 头判断请求来自哪里。 241 | * 确定目标域 - 请求发送至哪里。 242 | 243 | 服务器必须验证来源域和目标域是否匹配。如果存在匹配项,则该请求被视为合法并被接受。如果不匹配,由于跨域该请求将被丢弃。 244 | 245 | 可以默认这些头信息是可靠的,因为它们属于[禁止头信息](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name)(只能由浏览器修改),无法通过 JavaScript 对其进行修改。 246 | 247 | ## 双重 Cookie 验证 248 | 249 | 双重 Cookie 验证是 CSRF 令牌的替代方法。这是一种无状态的方法。 250 | 251 | 当用户访问应用时,应生成加密强度高的伪随机值,并将其设置为用户计算机上的一个 cookie,与会话 ID 分开。 252 | 253 | 然后,服务器要求每个请求都包含该值(通过隐藏的表单或请求参数)。如果它们在服务器端都匹配,则服务器将其接受为合法请求,否则,服务器将拒绝该请求。 254 | 255 | > 补充:作用较弱。任何可以设置 cookie 的攻击者都可以破解此方式(通过漏洞注入 cookie 或者中间人攻击 MITM)。 256 | 257 | ## 自定义请求头 258 | 259 | 这种方法非常适合于大量使用 AJAX 请求并依赖 API 访问的 Web 应用程序。 260 | 261 | 此方法使用[同源策略](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy),该策略限制了仅 JavaScript 可以添加自定义头信息,并且只能在同源内使用。默认情况下,浏览器不允许 JavaScript 使用自定义请求头进行跨域请求。 262 | 263 | 此解决方案的效率性要求具有鲁棒性的 CORS 配置,因为来自其他域的自定义请求头会触发 CORS 预检查(用于获知服务端是否允许该跨源请求)。 264 | 265 | 这允许开发者向请求中添加自定义头信息,只需在服务器验证其存在和值即可。 266 | 267 | 该技术适用于 AJAX 请求,但`
`元素应另外由令牌保护。 268 | 269 | ## 基于交互的防御 270 | 271 | 用户行为是一种防止未授权操作非常有效的机制(例如 CSRF 攻击)。有两种常见的方法: 272 | * 再验证-强制用户在执行请求之前进行验证 273 | * [CAPTCHA](https://en.wikipedia.org/wiki/CAPTCHA)-验证码 274 | 275 | 尽管此类方法对 CSRF 攻击非常有效,但它们会对用户体验产生影响。它们应主要应用于汇款等关键业务。 276 | 277 | ## 预认证防御 278 | 279 | 即使在用户仍未通过身份验证的页面(例如登录页)上,也可能发生 CSRF 攻击。但攻击对预认证页面的影响是完全不同的。 280 | 281 | 假设现在有一个电子商务网站,受害者正以未验证状态在浏览商品。攻击者可以在该网站上使用 CSRF 攻击,以攻击者的帐户对受害者进行身份验证。当受害者输入信用卡信息时,攻击者将能够使用受害者的信用卡卡购买商品。 282 | 283 | 为了减少此类攻击,开发者可以在用户尚未通过身份验证时创建会话。遵循上面基于令牌的预防部分中提到的技术,登录表单必须包含 CSRF 令牌。 284 | 285 | 一旦用户通过身份验证,预会话应转换为真实会话。 286 | 287 | ## 参考资源 288 | * 289 | * -------------------------------------------------------------------------------- /design-patterns.md: -------------------------------------------------------------------------------- 1 | # 创建型/结构型/行为型设计模式及 4 种最佳实践 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-creational-structural-and-behavioural-design-patterns-4-best-practices-2e8beeba744c),略有删减,本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理的第二十九章。** 6 | 7 | ![](./assets/29_cover.jpeg) 8 | 9 | 设计模式通常是大多数开发者遇到的问题的标准化解决方案。这些问题很常见、已有记录,且已经被解决过,让其他软件工程师不必再担心解决方案。有人认为这些设计模式已成为行业标准的可重用架构。工程师都应该在提高语言能力的同时学习它们。 10 | 11 | 大多数学习设计模式的工程师都在寻找更好的方法来编写整洁且可维护的代码。研究这些模式可以让你成为更好的软件工程师,因为开发者会更理解框架是如何以及为什么被创建。大多数框架都是使用特定的设计模式构建,越了解设计模式,上手新框架的速度就越快。 12 | 13 | 设计模式可以在任何编程语言中被任何人使用。设计模式具有灵活性,这意味着开发者可以使用它们并扩展它们以满足自定义需求。记住,它们只是模式。 14 | 15 | 本文中将着使用 JavaScript 来探索这些设计模式,软件需要这些模式的原因,介绍并解释不同类型的设计模式,每个类别都附带描述示例。 16 | 17 | 顺便说一句,理解本文并不需要 JavaScript 知识,所有设计模式可以在任何其他特定语言使用。 18 | 19 | ## 什么是设计模式,为什么需要它们 ? 20 | 21 | 设计模式是针对常见问题的行业标准、良好的架构方法。可以将这些模式视为“模板”。使用任意设计模式能享受到的避免过度重复代码是一种解脱。 22 | 23 | 构建软件使用设计模式有以下几个原因: 24 | 25 | - 有益于编写整洁且组织良好的代码。由于设计模式是行业标准且具有简洁的架构,基于此模式的代码更易于调试和维护。 26 | - 解决熟悉的问题。尝试从头开始构建软件会各种出现问题,比如如何正确创建类、编写解耦代码、与对象交互甚至编写可重用代码。将来想要进行更改时,拥有良好解耦的代码将有助于防止中断和错误。这些是开发者经常面临的问题,且可以从特定设计模式找到很好解决这些问题的思路。 27 | - 设计模式使用得当可以节省大量时间。由于它们是行业标准并解决了常见问题,因此开发时长会显着减少。 28 | 29 | ## 不同种类的设计模式 30 | 31 | 设计模式主要分为三大类。有一些其他类型但最常用的还是:创建型,结构型,行为型。 32 | 33 | ### 创建型设计模式 34 | 35 | 此模式的主要面向对象创建。它们为特定场景创建特定对象,对使用者只暴露接口,隐藏创建逻辑或类实现的细节。创建型设计模式可以细分为对象创建和类创建。 36 | 37 | - 单例 Singleton 38 | - 工厂方法 Factory 39 | - 抽象工厂 Abstract Factory 40 | - 生成者 Builder 41 | - 原型 Prototype 42 | 43 | 这是创建型设计模式的五种著名模式,本小节研究单例模式并了解它的用例以及它在 JavaScript 中的工作原理。 44 | 45 | #### 单例设计模式 46 | 47 | 单例确保负责创建对象的类只有一个实例。此类无需多次实例化就可以使用其对象。 48 | 49 | 对于单例人们会有过不少带有误解的争论,但它仍然是最容易实现的设计模式之一。其主要步骤是: 50 | 51 | 1. 单例类创建一个对象 52 | 2. 对象实例化 53 | 3. 防止应用程序中的其他地方创建另一个新实例 54 | 5. 将实例化的对象作为资源共享。 55 | 56 | 让我们直接进入代码环节。下面的例子实现足够简单和清晰,以便读者可以了解单例的要点。 57 | 58 | 第一步需要创建一个类,然后将其改造为单例。 59 | 60 | Step 1: 创建一个工厂类。 61 | 62 | ```js 63 | class Database { 64 | constructor() { 65 | this.connectionURL = { 66 | name: "", 67 | options: {} 68 | } 69 | } 70 | 71 | // connect 方法接收两个参数 72 | connect(name, options) { 73 | this.connectionURL.name = name; 74 | this.connectionURL.options = options; 75 | console.log(`DB: ${name} connected!`); 76 | } 77 | 78 | // disconnect 方法 79 | disconnect() { 80 | console.log(`${this.connectionURL.name} is disconnected!`); 81 | } 82 | } 83 | 84 | // 类实例化 85 | const db = new Database() 86 | console.log(db.connect("Facebook")) 87 | ``` 88 | 89 | Step 2: 实例化后让属性变为不允许修改的状态。 90 | 91 | ```js 92 | class Database { 93 | constructor() { 94 | this.connectionURL = { 95 | name: "", 96 | options: {} 97 | } 98 | 99 | // 实例化不允许修改类 100 | Object.freeze(this); 101 | } 102 | 103 | connect(name, options) { 104 | this.connectionURL.name = name; 105 | this.connectionURL.options = options; 106 | console.log(`DB: ${name} connected!`); 107 | } 108 | 109 | disconnect() { 110 | console.log(`${this.connectionURL.name} is disconnected!`); 111 | } 112 | } 113 | 114 | const db = new Database(); 115 | console.log(db.connect("Facebook")); 116 | ``` 117 | 118 | 上面代码中,实例化后不再允许添加或更改属性。在其他语言(如 Java)中将会创建一个 `getInstance()` 方法用于访问 Singleton 实例。在 JavaScript 中执行同样的操作区别在于使用 `constructor()` 而不是 `getInstance()` 方法。 119 | 120 | Step 3. `Database` 成为自身的实例并添加类是否已经被实例化的检查。 121 | 122 | ```js 123 | class Database { 124 | constructor() { 125 | // 查看实例是否已经创建 126 | if (Database.instance instanceof Database) { 127 | return Database.instance; 128 | } 129 | this.connectionURL = { 130 | name: "", 131 | options: {} 132 | } 133 | 134 | Object.freeze(this); 135 | 136 | // 使类成为自身一个实例 137 | Database.instance = this; 138 | } 139 | 140 | connect(name, options) { 141 | this.connectionURL.name = name; 142 | this.connectionURL.options = options; 143 | console.log(`DB: ${name} connected!`); 144 | } 145 | 146 | disconnect() { 147 | console.log(`${this.connectionURL.name} is disconnected!`); 148 | } 149 | } 150 | 151 | const db = new Database(); 152 | console.log(db.connect("Facebook")); 153 | ``` 154 | 155 | ```js 156 | if (Database.instance instanceof Database) { 157 | return Database.instance; 158 | } 159 | ``` 160 | 161 | 上面代码中,每次创建实例都会立即检查类是否存在着之前创建的实例,如果有,则返回该实例。这一点就是来保证使用者不能创建多个实例。 162 | 163 | 可以通过创建两个实例来进一步验证两者是否相同: 164 | 165 | ```js 166 | const db1 = new Database() 167 | 168 | const db2 = new Database() 169 | 170 | console.log(db1 === db2) 171 | // true 172 | ``` 173 | 174 | 使用单例模式后尝试创建另一个实例是不可能的。这就是为什么两个实例比较的结果返回 `true`。 175 | 176 | 单例模式的缺点: 177 | - 竞争冒险。当多组件或多线程尝试访问单例中的此共享资源时,会遇到可能无法正确读取数据的问题。该模式在多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。 178 | - 单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法,所以你需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代码,或者不使用单例模式。 179 | 180 | ### 结构型设计模式 181 | 182 | 此设计模式用于处理实体间的关系。主要处理类和对象的组合,这意味着特定对象将在另一个类中被使用,从而提供新功能。不要忘记这种关系与[继承](polymorphism.md)有关,其中类继承现有类的成员。两个主要关键词是组合和继承。 183 | 184 | 总之这是创建类并找到关联它们或使用组合建立关系的方法。使用这种方法的设计模式有: 185 | 186 | - 适配器 Adapter 187 | - 外观 Facade 188 | - 桥接 Bridge 189 | - 代理 Proxy 190 | - 享元 Flyweight 191 | 192 | 为了提供更详细的说明,下面基于适配器模式的用例来了解在 JavaScript 中的工作原理。 193 | 194 | #### 适配器设计模式 195 | 196 | 适配器作为两个互不兼容的类间的契约或桥梁。我喜欢将此模式作为包装器,因为它包装了两个单独接口(比如两个类被连接在一起)并将它们连接在一起。 197 | 198 | 让我们以 APIs 为例。我们创建了一个 API 充当两个类之间的桥梁。就像生活中人们可以使用插座来适配各种不兼容连接到墙上插座的的插头。 API 或桥接器就是适配器模式。第三方库就是很好的例子。 199 | 200 | 由于适配器模式与外观模式等其他模式有相似之处,毫无疑问它们容易让人混淆。 201 | 202 | ```js 203 | import { first, middle, last } from "random-name"; 204 | 205 | class randomName { 206 | generateFirstName() { 207 | return first(); 208 | } 209 | 210 | generateMiddleName() { 211 | return middle(); 212 | } 213 | 214 | generateLastName() { 215 | return last(); 216 | } 217 | } 218 | 219 | export default new randomName(); 220 | ``` 221 | 222 | 上面实例代码就是一个适配器,理论上可以使用想要的任何库,例子中的是 `randomName`。接着在下面代码中使用这个适配器: 223 | 224 | ```js 225 | import name from "./random-name"; 226 | 227 | class PlugComponent { 228 | constructor() { 229 | this.firstName = name.generateFirstName(); 230 | this.middleName = name.generateMiddleName(); 231 | this.lastName = name.generateLastName(); 232 | } 233 | 234 | generateFullName() { 235 | return `${this.firstName} ${this.middleName} ${this.lastName}` 236 | } 237 | } 238 | 239 | const names = new PlugComponent() 240 | console.log(names.generateFullName()) // Victor Victor Jonah 241 | ``` 242 | 243 | 这种模式通常有助于提高代码复用性和灵活性。当开发者倾向使用过多适配器时可能会出现大问题。 244 | 245 | ### 行为型设计模式 246 | 247 | 此模式关注对象之间的通信。它们为开发者提供了解耦和灵活代码的对象通信解决方案。 248 | 249 | 总之每当需要处理对象之间的通信时都应该选择此类别。使用这种通信方法的模式有: 250 | 251 | - 责任链 Chain of Responsibility 252 | - 命令 Command 253 | - 解释器 Interpreter 254 | - 观察者 Observer 255 | - 空对象 Null object 256 | 257 | 要了解有关此类别的更多信息,让我们来看看空对象设计模式。 258 | 259 | #### 空对象设计模式 260 | 261 | 这种模式帮助开发者避免在代码中返回 null 值。需要明确的是,该模式封装了 null 的行为并返回客户端期望的期望结果。大多数时候 null 引用是不被允许的,这也就是必须进行 null 检查的原因。空检查可以让代码里的 if/else 语句杂乱无章。只需将空对象添加到项目中,它有助于保持客户端的代码的整洁度,因为开发者不再需要添加逻辑来检查空值。 262 | 263 | 当不想向客户端返回空值时,空对象模式很有效,因为它封装了我们期望的逻辑。这是我最常用的模式用来捕获特定的异常。 264 | 265 | 下面来看看如何在 JavaScript 中使用这种模式处理 null 值: 266 | 267 | ```js 268 | class Cat { 269 | sound() { 270 | return 'meoow'; 271 | } 272 | } 273 | 274 | class NullAnimal { 275 | sound() { 276 | return "not an animal"; 277 | } 278 | } 279 | 280 | const getAnimal = (type) => { 281 | return type === 'cat' ? new Cat() : new NullAnimal(); 282 | } 283 | 284 | const results = ['cat', null]; 285 | 286 | const response = results.map((animal) => getAnimal(animal).sound()); 287 | // ["meoow", "not an animal"] 288 | ``` 289 | 290 | 示例不使用空引用,而是使用对象将预期结果返回给客户端。通过避免客户端编写空检查来简化代码。 291 | 292 | ### 设计模式最佳实践 293 | 294 | 看过了设计模式的类别和每个类别的几个示例,开发者可能会想它们是否可以在代码被使用。设计模式也有最佳实践,每个软件工程师都必须了解这些最佳实践: 295 | 296 | - 设计先行:在编码前必须采取的第一个实践。开工前点设计提供了更好的优势。 297 | - 保持简单愚蠢(KISS):如果你不能解释它,它就不简单。设计模式的目的是使代码简单易懂。 298 | - 不要重复自己(DRY):使方法具有可重用性,将它们分解或分组而不是到处重写相同的方法。 299 | - 关注点分离:每个服务都必须分以便哪里调试。单独的子程序应具有单一原则。 300 | 301 | 这些是在代码中使用设计模式时遵循的一些最佳实践。 302 | 303 | 通常,一个项目会使用多种模式,但没有必须选择哪些模式的黄金法则。项目需求定义了模式的选择。 304 | 305 | ## 结论 306 | 307 | 本文主要介绍了设计模式及为什么开发者需要设计模式的原因。设计模式主要分为三类创建型,结构型和行为型,就三大模式的各一子类单例、适配器和空对象提供了代码示例。 308 | 309 | 创建型模式侧重于对象的创建,结构型模式依赖对象之间的关系,而行为型模式侧重于对象间的通信的建立。 310 | 311 | 这些模式能帮助开发者写出更好、更整洁的架构,这正是研究设计模式的主要原因。 312 | 313 | 建议编程新手在更精通所使用的编程语言前无需过度关注设计模式。 314 | 315 | > 本文仅详细介绍了三大设计模式中的各一种子类,这里推荐一个图文并茂的设计模式介绍网站 [refactoringguru](https://refactoringguru.cn/design-patterns)。 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /exceptions.md: -------------------------------------------------------------------------------- 1 | # 异常及同步/异步代码异常处理最佳实践 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-exceptions-best-practices-for-synchronous-and-asynchronous-environments-39f66b59f012),本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理的第二十章。** 6 | 7 | ![浏览器控制台中的报错](./assets/0_oWsMRoltriNxmRNe.png) 8 | 9 | ## 概述 10 | 在计算中,错误侦测是一种在程序执行过程中实现可靠流的技术。 11 | 12 | 错误侦测的一种方法是错误检查。此方法通过特定的返回值,辅助全局变量或浮点状态标等显式异常检查来维护正常的程序流。 13 | 14 | 异常指的是在程序运行过程中发生的影响程序流的异常事件。这种中断会触发预注册异常处理程序的执行。 15 | 16 | 异常在软件和硬件层上都可能发生。 17 | 18 | ## JavaScript 中的异常 19 | 一个 JavaScript 应用程序有可能在不同的操作系统,浏览器加插件和设备上运行。无论开发者编写了多少测试,这种充满不确定性的环境最终都很有可能会导致错误发生。 20 | 21 | 在最终使用者的视角上看,JavaScript 只是通过静默失败来处理错误。但底层的处理机制其实比想象中复杂。 22 | 23 | 当特定语句产生错误时,JavaScript 代码将引发异常。[JavaScript 引擎](https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e)将检查是否存在异常处理代码,而不是继续执行下一代码语句。 24 | 25 | 如果没有异常处理程序被定义,则引擎会从抛出异常的函数返回。对于调用栈上的每个函数会重复执行此过程直到发现异常处理程序为止。如果[调用栈](https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf)上已经没有函数仍未找到异常处理程序,那么事件循环会将回调队列中下一个函数推入栈中。 26 | 27 | 当异常发生时,一个错误对象(`Error Object`)会被创建并抛出。 28 | 29 | ## 错误对象的种类 30 | JavaScript中有九种类型的内置错误对象,它们是异常处理的基础: 31 | * **Error** - 表示通用异常, 常用于用户自定义异常的实现。 32 | * **EvalError** - `eval()` 函数使用不当时发生。 33 | * **RangeError** - 用于数值型变量或参数超出其有效范围时发生的错误。 34 | * **ReferenceError** - 当访问不存在的变量时发生的引用异常。 35 | * **SyntaxError** - 语法异常。JavaScript 语法规则被破坏时发生。对于静态类型的语言,会在编译时发生。而在 JavaScript 中,此异常发生在运行时。 36 | * **TypeError** - 当值与预期类型不匹配时发生。调用不存在的对象方法是此类异常的常见原因。 37 | * **URIError** - 当 `encodeURI()` 和 `decodeURI()` 遇到格式错误的URI时发生。。 38 | * **AggregateError** - 当一个操作需要上报多个错误时。比如 `Promise.any()`。 39 | * **InternalError** - 在 JavaScript 引擎中引发内部错误时发生。比如太多的递归导致栈溢出。在撰写本文时,此[API](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/InternalError)尚未标准化。 40 | 41 | 开发者可以通过继承某些内置错误类型来自定义错误类型。 42 | 43 | ## 异常抛出 44 | 45 | JavaScript 允许开发者主动通过 `throw` 语句抛出异常。 46 | 47 | ```js 48 | if (denominator === 0) { 49 | throw new RangeError("Attempted division by zero"); 50 | } 51 | ``` 52 | 所有内置错误对象都有一个可选的 `message`参数,用于提供可读的错误描述。 53 | 54 | 注意: 可以抛出任何类型的对象作为异常,例如数字,字符串,数组等。 55 | 56 | ```js 57 | throw true; 58 | throw 113; 59 | throw 'error message'; 60 | throw null; 61 | throw undefined; 62 | throw {x: 1}; 63 | throw new SyntaxError('hard to debug'); 64 | ``` 65 | 66 | 以上均属于有效的 JavaScript 语句。 67 | 68 | 但是使用内置错误类型而不是其他对象有很多好处,因为某些浏览器对它们进行了特殊处理,例如导致异常的文件名、行号和栈追踪。某些浏览器厂商(如 Firefox)正在为所有类型的对象填充这些属性。 69 | 70 | ## 异常处理 71 | 72 | 现在,我们将了解如何确保异常不会导致应用程序崩溃。 73 | 74 | ### try 子句 75 | 76 | 类似其他编程语言, JavaScript 也有 `try`,`catch`,`finally`语句,使开发者可以控制代码中的异常。 77 | 78 | 举个例子: 79 | 80 | ```js 81 | try { 82 | // 一个可能抛出错误的函数 83 | someFunction(); 84 | } catch (err) { 85 | // 这段代码处理异常 86 | console.log(e.message); 87 | } finally { 88 | // 这段代码始终会被执行 89 | console.log(finally’); 90 | } 91 | ``` 92 | `try` 子句具有强制性,它包装着一个可能抛出错误的代码块。 93 | 94 | ### catch 子句 95 | 96 | 随后紧跟着的是 `catch` 块,包装了处理错误的 JavaScript 代码。 97 | 98 | `catch` 子句阻止异常在调用栈中传播,并允许应用程序流继续进行。错误本身会被作为参数传递给 `catch` 子句。 99 | 100 | 通常,某些代码块会抛出不同类型的异常,并且你的应用程序应该要能根据不同的异常采取不同的处理方式。 101 | 102 | JavaScript 的 `instanceof` 运算符,可用于区分异常类型: 103 | 104 | ```js 105 | try { 106 | If (typeof x !== ‘number’) { 107 | throw new TypeError(‘x is not a number’); 108 | } else if (x <= 0) { 109 | throw new RangeError(‘x should be greater than 0’); 110 | } else { 111 | // 一些有用的操作 112 | } 113 | } catch (err) { 114 | if (err instanceof TypeError) { 115 | // 处理 TypeError 异常 116 | } else if (err instanceof RangeError) { 117 | // 处理 RangeError 异常 118 | } else { 119 | // 处理其他类型异常 120 | } 121 | } 122 | ``` 123 | 124 | 一种有效的使用场景是重新抛出已捕获的异常。例如在上下文中捕获到一种对你来说无关紧要的类型错误。 125 | 126 | ### finally 子句 127 | 128 | 无论最终是否有异常,都会在 `try` 和 `catch` 子句之后执行 `finally` 代码块。`finally`对于包含清理代码(例如关闭 WebSocket 或其他资源的连接)的执行非常有帮助。 129 | 130 | 请注意,即使未捕获到抛出的异常,`finally` 块也会执行。然后引擎继续按顺序遍历调用栈中的函数,直到正确处理异常或程序终止。 131 | 132 | 需要注意的另外一点是,即使 `try` 或 `catch` 块执行了 `return` 语句,`finally` 块也会继续执行。 133 | 134 | 请看下面的例子: 135 | 136 | ```js 137 | function foo1() { 138 | try { 139 | return true; 140 | } finally { 141 | return false; 142 | } 143 | } 144 | ``` 145 | 通过调用 `foo1()` 函数,即使 `try` 块具有 `return` 语句,我们也会得到`false`。 146 | 147 | 即使是在 `catch` 块中有 `return` 语句,此规则同样适用: 148 | 149 | ```js 150 | function foo2() { 151 | try { 152 | throw new Error(); 153 | } catch { 154 | return true; 155 | } finally { 156 | return false; 157 | } 158 | } 159 | ``` 160 | 161 | 调用 `foo2()` 函数, 同样会返回 `false`。 162 | 163 | ## 异步代码的异常处理 164 | 165 | 在这我们不再赘述[异步编程](https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5), 但我们将介绍如何通过回调函数,`Promise` 和 `async/await` 来处理异常。 166 | 167 | ### async/await 168 | 169 | 首先我们来定义一个会抛出异常的标准异步函数。 170 | 171 | ```js 172 | async function foo() { 173 | throw new Error(); 174 | } 175 | ``` 176 | 177 | 当在异步函数中抛出错误时,将与异常一起返回一个已拒绝的 `promise`,等同于: 178 | 179 | ```js 180 | return Promise.Reject(new Error()) 181 | ``` 182 | 183 | 让我们看看调用 `foo()` 时会发生什么: 184 | 185 | ```js 186 | try { 187 | foo(); 188 | } catch(err) { 189 | // 这一代码块不会被访问 190 | } finally { 191 | // 这一代码块会在 Promise 被拒绝前被访问 192 | } 193 | ``` 194 | 195 | 由于 `foo()` 是异步的,因此将触发一个 `Promise`。该代码不等待异步函数,因此这时候并没有任何实际异常被捕获。`finally` 块执行后 `Promise` 被拒绝。 196 | 197 | 此时我们没有任何代码用于处理被拒绝的 `Promise`。 198 | 199 | 我们可以通过在调用 `foo()` 时添加 `await` 关键字并将代码囊括在 `async` 函数中来解决: 200 | 201 | ```js 202 | async function run() { 203 | try { 204 | await foo(); 205 | } catch(err) { 206 | // 这一代码块现在可被正常访问 207 | } finally { 208 | // 这一代码块最终会被访问 209 | } 210 | } 211 | run(); 212 | ``` 213 | 214 | ### Promises 215 | 216 | 让我们定义一个在 `Promise` 外抛出错误的函数。 217 | 218 | ```js 219 | function foo(x) { 220 | if (typeof x !== 'number') { 221 | throw new TypeError('x is not a number'); 222 | } 223 | 224 | return new Promise((resolve, reject) => { 225 | resolve(x); 226 | }); 227 | } 228 | ``` 229 | 现在,我们使用字符串而不是数字作为参数来调用 `foo()` : 230 | 231 | ```js 232 | foo('test') 233 | .then(x => console.log(x)) 234 | .catch(err => console.log(err)); 235 | ``` 236 | 237 | 由于 `Promise` 无法捕获到 `Promise` 之外引发的异常,所以这会产生` Uncaught TypeError: x is not a number `的报错。 238 | 239 | 要捕获此类错误,需要使用标准的 `try` 和 `catch` 子句: 240 | 241 | ```js 242 | try { 243 | foo('test') 244 | .then(x => console.log(x)) 245 | .catch(err => console.log(err)); 246 | } catch(err) { 247 | // 现在异常会被处理 248 | } 249 | ``` 250 | 如果将 `foo` 修改为在 `Promise` 中抛出错误: 251 | 252 | ```js 253 | function foo(x) { 254 | return new Promise((resolve, reject) => { 255 | if (typeof x !== 'number') { 256 | throw new TypeError('x is not a number'); 257 | } 258 | resolve(x); 259 | }); 260 | } 261 | ``` 262 | 263 | 这样 `Promise` 里的 `catch` 语句也能正确处理异常。 264 | 265 | ```js 266 | try { 267 | foo('test') 268 | .then(x => console.log(x)) 269 | .catch(err => console.log(err)); // 错误在这被处理. 270 | } catch(err) { 271 | // 由于错误在 Promise 里被处理,这段代码块不会被访问 272 | } 273 | ``` 274 | 275 | 要注意的是在 `Promise` 中抛出错误与使用 `reject` 回调相同。因此最好这样定义 `foo`: 276 | 277 | ```js 278 | function foo(x) { 279 | return new Promise((resolve, reject) => { 280 | if (typeof x !== 'number') { 281 | reject('x is not a number'); 282 | } 283 | resolve(x); 284 | }); 285 | } 286 | ``` 287 | 288 | 如果 `Promise` 中没有 `catch` 方法来处理错误,则回调队列中的下一个函数将添加到栈中。 289 | 290 | ### 回调函数 291 | 292 | 使用错误优先回调方法有两个主要规则: 293 | 294 | 1. 回调的第一个参数用于错误对象。如果发生错误,它将由第一个 `err` 参数返回。如果未发生错误,则 `err` 将设置为 `null`。 295 | 296 | 2. 回调的第二个参数为结果, 即响应数据。 297 | 298 | ```js 299 | function asyncFoo(x, callback) { 300 | // 一些异步代码 301 | } 302 | 303 | asyncFoo('testParam', (err, result) => { 304 | If (err) { 305 | // 错误处理 306 | } 307 | // 执行其他操作 308 | }); 309 | ``` 310 | 如果存在错误对象,最好不要去动结果参数。 311 | 312 | ## 解决未处理的异常 313 | 314 | 如果应用程序使用了第三方库,则无法控制它们如何处理异常。在某些情况下,开发者可能希望能够解决未处理的异常。 315 | 316 | ### 浏览器 317 | 318 | 浏览器暴露了一个 `window.onerror` 的事件处理器可用于此目的。 319 | 320 | 使用方法如下: 321 | 322 | ```js 323 | window.onerror = (msg, url, line, column, err) => { 324 | // ... 错误处理 … 325 | return false; 326 | }; 327 | ``` 328 | 329 | 参数说明如下: 330 | 331 | * **msg** - 与错误相关的信息,例如 `Uncaught ReferenceError: foo is not defined`。 332 | * **url** - 与错误关联的脚本或文档的地址。 333 | * **lineNo** - 行号(如果存在的话)。 334 | * **columnNo** - 列号(如果存在的话)。 335 | * **err** - 与错误相关的 `Error` 对象(如果存在的话)。 336 | 337 | 当函数返回 `true` 时,这将防止触发默认事件处理程序。 338 | 339 | 注意只能将**一**个事件处理程序分配给 `window.onerror`,因为这是函数赋值,一个事件只能同时只能指定一个函数。 340 | 341 | 这意味着,如果指定了自己的 `window.onerror`,则可能覆盖第三方库本身分配的异常处理程序。这可能是一个巨大隐患,尤其是错误跟踪器之类的工具而言,因为它们很可能会完全停止工作。 342 | 343 | 开发者可以使用以下技巧轻松解决此问题。 344 | 345 | ```js 346 | var oldOnErrorHandler = window.onerror; 347 | window.onerror = (msg, url, line, column, err) => { 348 | If (oldOnErrorHandler) { 349 | // 调用之前声明的处理器 350 | oldOnErrorHandler.apply(this, arguments); 351 | } 352 | 353 | // 你的其他代码 354 | } 355 | ``` 356 | 357 | 上面的代码先检查是否存在先前定义的 `window.onerror`,并在继续操作之前简单地调用它。使用此模式,您可以继续添加其他处理程序。 358 | 359 | 这种方法在浏览器间高度兼容(即使是 IE6)。 360 | 361 | 另一种不需要替换异常处理程序的方法是将事件监听器添加到 `window` 对象: 362 | 363 | ```js 364 | window.addEventListener('error', e => { 365 | // 从错误事件对象中获取错误的属性 366 | const { message, filename, lineno, colno, error } = e; 367 | }); 368 | ``` 369 | 370 | 这种方法更优雅,并且从 IE9 之后得到了广泛支持。 371 | 372 | ### Node.js 373 | 374 | `Event Emmiter` 模块中的 `process `对象提供了两个用于处理错误的事件。 375 | 376 | 1. **uncaughtException** : 当未捕获的异常一直冒泡至[事件循环](https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5)时触发。 377 | 378 | 默认情况下,Node.js 通过将栈追踪打印到 stderr 并使用 `code 1` 退出来处理此类异常。 379 | 380 | 为此事件添加处理程序将覆盖默认行为。该事件的正确用法是在关闭进程之前执行分配资源的同步清理(例如文件描述符,处理程序等)。此后恢复正常运行的行为是不安全的。 381 | 382 | 2. **unhandledRejection** : 每当一个 `Promise` 被拒绝且在事件循环内没有错误处理程序附加到 `Promise` 时触发。 383 | 384 | `unhandledRejection` 事件对于检测和跟踪已被拒绝的 `Promise` 以及尚未处理的拒绝很有用。 385 | 386 | ```js 387 | process 388 | .on('unhandledRejection', (reason, promise) => { 389 | // 处理失败的 Promise 390 | }) 391 | .on('uncaughtException', err => { 392 | // 处理失败的错误 393 | process.exit(1); 394 | }); 395 | ``` 396 | 397 | 在你的代码中妥善处理错误非常重要。了解未处理的错误同样重要,这样你就可以确定优先级并相应地对它们进行处理。 398 | 399 | ## 参考资源 400 | * https://www.sitepoint.com/exceptional-exception-handling-in-javascript/#:~:text=When%20a%20JavaScript%20statement%20generates,whatever%20function%20threw%20the%20exception. 401 | * https://www.tutorialspoint.com/es6/es6_error_handling.htm 402 | * https://www.javascripttutorial.net/es6/promise-error-handling/ -------------------------------------------------------------------------------- /functional-programing.md: -------------------------------------------------------------------------------- 1 | # 函数式编程及与其他范式对比 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-functional-style-and-how-it-compares-to-other-approaches-8a1398c73919),略有删减,本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理的第二十五章。** 6 | 7 | ![](./assets/25_cover.jpeg) 8 | 9 | ## 概述 10 | 11 | 在此之前,已经有程序员使用逻辑式,过程式以及最常见的面向对象范式来编写代码。这些范式涵盖了用于解决计算问题的编码风格,框架,特性和模式,也有助于各种编程语言的发展。 12 | 13 | 比如过程式语言(COBOL)是基于过程式原理,即每个语句可被解释为子程序或函数。 14 | 15 | 另一方面,函数式编程是本文的基石,涉及到项目构建,问题解决或使用数学函数来处理计算。我们将使用数据作为“输入”的函数来编写代码,该数据经过函数计算并作为“输出”返回。 16 | 17 | 函数式编程的美丽之处在于这些函数无法更改我们的数据或输入(数据不可变性)并可以观察到任何副作用。语句以函数来表示:接受输入,产生输出。 18 | 19 | 本文将讨论更多有关函数式编程的内容,包括它如何在 JavaScript 中工作以及一些重要概念,以便更好理解 JavaScript 中的函数式编程。 20 | 21 | ## JavaScript 面向对象基础概述 22 | 23 | 毋庸置疑 JavaScript 是基于原型的语言,而不是基于类,并且 JavaScript 开发者经常会混淆或误解此模型。基于类的语言(例如C#,Java等)在两个主要概念:类和实例。类定义了该对象在实例化时应该具有的所有属性。 24 | 25 | 在 JavaScript 中,引入的 OOP 语法使其看起来是面向对象,但实际上并非如此。这意味着当开发者使用类语法创建对象时,它会自动获取其原型。另外,如果有不存在的方法或属性,JavaScript 将检查原型以查看其是否存在(原型链)。 26 | 27 | 让我们看一个代码示例,该例子说明 JavaScript 中的 `class` 语法: 28 | 29 | ```js 30 | let Person = class { 31 | constructor(name, age) { 32 | this.name = name; 33 | this.age = age; 34 | } 35 | 36 | speak() { 37 | return `Hello, my name is ${this.name} and I am ${this.age}` 38 | } 39 | } 40 | ``` 41 | 42 | 请注意,`Person` 类具有一个有两个参数的构造函数。`this` 关键字指向属性 `Person`。 43 | 44 | 方法 `speak()` 可以在以下创建新示例的例子中一样使用: 45 | 46 | ```js 47 | let victor = new Person('Victor', 23); 48 | console.log(victor.speak()); 49 | ``` 50 | 51 | `new Person` 调用构造函数,并传入将设置至 `victor` 对象的参数。在底层方法 `speak()` 将被添加至构造函数的原型中。 52 | 53 | 同样,我们可以基于 `Person` 类创建一个新的类,以扩展其中已存在的属性和方法。如下: 54 | 55 | ```js 56 | let Work = class extends Person { 57 | constructor(name, age, work) { 58 | super(name, age); 59 | this.work = work 60 | } 61 | 62 | getInfo() { 63 | return `Hey! It's ${this.name}, I am age ${this.age} and work for ${this.work}.` 64 | } 65 | } 66 | 67 | let alex = new Work('Alex', 30, 'SessionStack'); 68 | console.log(alex.getInfo()); 69 | ``` 70 | 71 | 这样我们就创建了一个对象并将其扩展以支持定义更多属性。在基于类的编程语言中,这种概念称为子类。 72 | 73 | 那么在 JavaScript 旧语法中是应该实现呢? 记住 `class` 语法在底层仍然只是原型制作。一起来看下面这个例子: 74 | 75 | ```js 76 | let Person = function(name, age) { 77 | this.name = name; 78 | this.age = age; 79 | } 80 | 81 | Person.prototype.speak = function() { 82 | return `Hello, my name is ${this.name} and I am ${this.age}` 83 | } 84 | 85 | let victor = new Person(`Victor`, 23) 86 | console.log(victor.speak()) 87 | ``` 88 | 89 | 上面的代码中,我们调用方法 `Person()`, 注意方法前的 `new` 关键字改变了函数的上下文从而调用了构造函数。基于 `Person` 对象创建的示例返回并赋值给 `victor`。 90 | 91 | 就像在上面 `Work` 扩展 `Person` 类语法那样扩展对象,可以与以原型方法完成: 92 | 93 | ```js 94 | let Work = function(name, age, work) { 95 | Person.call(this, name, age); 96 | this.work = work; 97 | } 98 | ``` 99 | 100 | 注意我们并没有使用 `super()` 关键字因为它并不存在与 JavaScript 原型上下文中。`call()` 方法接收 `this` 和参数,并将 `Person` 的属性添加到 `Work` 。`call()` 方法将构造函数从 `Person` 链接到 `Work`,换句话说,我们从 `Person` 借用属性并将其添加到 `Work` 上。 101 | 102 | 同样的,`class` 语法里的 `extend` 关键字也不是原型的一个属性。然而 `Person` 和 `Work` 可以通过以下方式连接: 103 | 104 | ```js 105 | Object.setPropertyOf(Work.prototype, Person.prototype); 106 | ``` 107 | 108 | `Work` 原型现在将使用 `Person` 原型。下面的例子是向新原型`Work`中添加方法: 109 | 110 | ```js 111 | Work.prototype.getInfo = function() { 112 | return `Hey! It's ${this.name}, I am age ${this.age} and work for ${this.work}.` 113 | } 114 | 115 | let alex = new Work("Alex", 40, 'SessionStack'); 116 | console.log(alex.getInfo()); 117 | ``` 118 | 119 | 现在我们应当更了解基于类和基于原型的概念。应该清楚的是 JavaScript 是一种基于对象的语言,它通过原型链接起来。大多数开发者使用的类语法只是 ES6 的语法糖。 120 | 121 | 通过这基本概述,现在我们可以继续介绍 JavaScript 中的函数式编程了。 122 | 123 | ## 什么是 JavaScript 函数式编程? 124 | 125 | 对于大多数开发者来说,在 JavaScript 中使用函数式编程的思想似乎会更轻松。为什么?众所周知,JavaScript 是一门基于原型的语言,而这些 `prototype inheritance`,`this`,`setPropertyOf` 等确实令人困惑,而且大多都容易被误解。 126 | 127 | 不过,与在基于原型的编码中使用错误的 `this` 绑定相比,我们有 JavaScript 的函数式方法使工作变得更简单,更少的 bug 且易于维护。 128 | 129 | 使用函数式编程思想的 JavaScript 开发者社区日益壮大,并且大多数库都允许在项目中使用这种范式。这意味着在遇到问题时,StackOverflow 或其他任何地方都将提供足够的帮助。 130 | 131 | JavaScript 中的函数式代码如下: 132 | 133 | ```js 134 | const sayHello = function(name) { 135 | return `Hello ${name}`; 136 | } 137 | 138 | sayHello('Victor'); 139 | 140 | # => Hello Victor 141 | ``` 142 | 143 | 我们声明了一个带有参数 `name` 的函数,当该函数被调用时,如果参数值是 `Victor`,则返回字符串 `Hello Victor`。这是使用函数编写代码时一种更简洁的自解释方法。 144 | 145 | 要了解 JavaScript 函数式编程的更多信息,需要了解某些重要的概念比如:纯函数,高阶函数,不变性,一等函数等。我们还将在本文中讨论它们。 146 | 147 | ## JavaScript 函数式编程中的概念 148 | 149 | ### 纯函数 150 | 151 | 函数编程式的一个主要目标是 - 避免副作用并使用纯函数。避免副作用意味着函数应仅通过接受参数(输入)并对其进行处理来进行计算。函数应该是纯粹的。让我们看一个具有副作用的非纯函数示例。 152 | 153 | ```js 154 | let surname = 'Jonah'; 155 | 156 | const sayHi = function() { 157 | return `Hi ${surname}`; 158 | } 159 | ``` 160 | 161 | 上面代码中的函数没有输入(参数),但从函数外部接收到一个全局变量 `surname`。这可能会产生副作用。 162 | 163 | 纯函数代码实例应当如下: 164 | 165 | ```js 166 | const sayHi = function(surname) { 167 | return `Hi ${surname}`; 168 | } 169 | ``` 170 | 171 | 函数仅关心输入的 `surname` 并将对进行计算。这就是纯函数。当谈到函数式编程时,这是主要且最重要的概念。 172 | 173 | ### 高阶函数 174 | 175 | 围绕这个概念,我们将更好地理解函数式编程的风格。高阶函数是将函数作为参数或返回函数的函数。请记住,函数是值,这意味着可以传递这些值。例如: 176 | 177 | ```js 178 | const getSum = function(num) { 179 | return num + num; 180 | } 181 | 182 | getSum(9); 183 | ``` 184 | 185 | 我们创建了一个函数并将其分配给变量 `getSum`,现在我们可以将变量传递给另一个值(变量)。像这样: 186 | 187 | ```js 188 | const addNum = getSum; 189 | 190 | addNum(9); 191 | ``` 192 | 193 | 我们可以继续将函数(值)传递到另一个函数中,以帮助我们编写或将许多较小的函数引入较大的函数。这就产生了组合。 194 | 195 | ```js 196 | function a(x) { 197 | return x * 2; 198 | } 199 | 200 | function b(x) { 201 | return x + 1; 202 | } 203 | 204 | function c(x) { 205 | return x * 3; 206 | } 207 | 208 | const d = c(b(a(2))); 209 | console.log(d) // 15 210 | ``` 211 | 212 | 我们可以传递每个函数返回的值,并将其传递给下一个函数。这就是我们使用高阶函数的原因,因为它可以进行组合,使我们的代码更整洁且具有鲁棒性。 213 | 214 | 数组的 `filter()` 方法是最常用的高阶函数示例之一。当另一个函数作为参数传递时,此法为我们生成一个新数组。例如: 215 | 216 | ```js 217 | function isLarge(value) { 218 | return value > 10; 219 | } 220 | 221 | const dataArray = [10, 11, 12, 3, 4]; 222 | 223 | 224 | const filteredArray = dataArray.filter(isLarge); 225 | console.log(filteredArray); // [11, 12] 226 | ``` 227 | 228 | `filter()` 方法遍历数组 `dataArray`, 每个元素都将作为参数传入回调函数 `isLarge`。 回调函数应当返回一个布尔值。如果为 `true`, 值就会被添加至新数组。这简单又不失优雅。其他受欢迎的示例还有是 `map()` 和 `reduce()`。我们不需要在这里任何地方使用 for 循环。在界山下一个概念之前,让我们看一下 `map()` 的一个例子: 229 | 230 | ```js 231 | function squareRoot(value) { 232 | return Math.sqrt(value) 233 | } 234 | 235 | const dataArray = [4, 9, 16]; 236 | 237 | 238 | const mappedArray = dataArray.map(squareRoot); 239 | console.log(mappedArray); // [2, 3, 4] 240 | ``` 241 | 242 | ### 不变性 243 | 244 | 函数式编程中的另一个概念是强调了避免改变的重要性。当我们说 `mutation` 时,我们的意思是改变状态或数据。因此,当某些东西是不可变的,一旦它被设置,它就保持不变,而当我们要进行更改时,我们使用新更改的数据来设置一个新对象。让我们来看下面的代码: 245 | 246 | ```js 247 | let data = [1, 2, 3, 4, 4]; 248 | 249 | data[4] = 5; 250 | 251 | console.log(data); // [1, 2, 3, 4, 5] 252 | ``` 253 | 254 | 请注意,我们如何将数据从 `1、2、3、4、4` 更改为 `1、2、3、4、5`,这看起来并不好,因为我们可能在代码中引入错误。想象一下我们在代码库中的某个位置计算了数据数组,并且在这时已经被更改,使用函数式编程是可以避免这种情况的。那么,不变性如何在 JavaScript 中起作用?下面我用代码来解释一下。 255 | 256 | ```js 257 | const names = ["Alex", "Victor", "John", "Linda"] 258 | 259 | const newNamesArray = names.slice(1, 3) // ["Victor", "John"] 260 | ``` 261 | 262 | 原来的数组 `name` 没有被修改,并且返回了新数组。 263 | 264 | 对象也可以使用方法 `Object.freeze()` 变成不可变。此方法冻结对象并使其不允许删除数据或添加到对象属性。比如: 265 | 266 | ```js 267 | const employee = { 268 | name: "Victor", 269 | designation: "Writer", 270 | address: { 271 | city: "Lagos" 272 | } 273 | }; 274 | 275 | Object.freeze(employee); 276 | 277 | employee.name = "Max" 278 | // Outputs: Cannot assign to read-only property 'name' 279 | 280 | // Checks if our object is immutable or not 281 | Object.isFrozen(employee); // === true 282 | ``` 283 | 284 | 这样对象变得不可变并且不会受到干扰,这绝对是我们期待的结果。 285 | 286 | 不变性一直存在一个问题,即当需要更改时,必须一遍又一遍地复制数组。比如有一个包含 1000 个子元素的 `names` 数组,并且我们需要不断对其进行修改并创建新数组,那么最终可能会占用过多内存,从而出现时间复杂度或持久化的问题。当然社区也存在解决方案,并有对应的 JavaScript 库比如[immutable.js](https://immutable-js.github.io/immutable-js/)和[Mori](https://swannodette.github.io/mori/#immutability),但本文不对此进行拓展。 287 | 288 | 通过这几个概念应该可以了解到 JavaScript 函数式编程的思想和美。我们可以看到代码更具可读性和简洁性,可用于执行快速和可操作的流程如数据处理和并发。 289 | 290 | ## 声明式与命令式 JavaScript 291 | 292 | JavaScript 不仅可以进行函数式编程,还可以采用声明式和命令式方法编写代码。我们直接来命令式方法的代码风格。命令式方法像是陈述解决问题所需的所有步骤。例如,如果想吃意大利面,那么必要的步骤如下: 293 | 294 | - 煮意大利面 295 | - 混合原料 296 | - 蒸意大利面及原料 297 | - 装盘 298 | 299 | 我确信使用这些步骤来会得到差劲意大利面,不过举这个例子的重点是使用了命令式方法来实现。 300 | 301 | 而声明式方法只是声明或说出想做什么。比如要煮意大利面,仅此而已。当涉及到声明式方法时,不需要表达控制流。这是两种方法的简单描述用于对比它们之间的差异,而不要指导开发者该使用其中的某一种。 302 | 303 | ### JavaScript 中的命令式方法: 304 | 305 | 这里我们将直接告诉计算机明确的对应操作。 306 | 307 | ```js 308 | // Function to filter an array; return greater than 5 numbers 309 | 310 | const filterArray = (array) => { 311 | let filteredArray = []; 312 | for(let i = 0; i < array.length; i++) { 313 | if(array[i] > 5) { 314 | filteredArray.push(array[i]); 315 | } 316 | } 317 | return filteredArray; 318 | } 319 | 320 | const array = [1, 2, 3, 4, 5, 6, 7, 8] 321 | filterArray(array) 322 | ``` 323 | 324 | 与其告诉计算机想要什么,我们只是逐步说明要实现的目标。步骤包括: 325 | 326 | - 声明一个空数组 327 | - 遍历给定数组 328 | - if/else,如果数值大于5 329 | - 将通过测试的元素推入先前声明的空数组中 330 | - 显示新数组 331 | 332 | ### JavaScript 中的声明式方法: 333 | 334 | ```js 335 | // Filter method to give us a new array 336 | 337 | const filterArray = array => array.filter(x => x > 5); 338 | 339 | const array = [1, 2, 3, 4, 5, 6, 7, 8]; 340 | 341 | console.log(filterArray(array)); // [6, 7, 8] 342 | ``` 343 | 344 | ## 函数式编程的优势 345 | 346 | 开发人员选择函数式编程的常见原因有几个。虽然对初学者来说会比较棘手,但是当掌握这些概念时,它也会变得更加容易。主要原因如下: 347 | 348 | - 为我们的代码库提供了模块化;功能之间可以结合甚至分离。它将函数视为值,使我们可以将函数作为参数传递。这也提供了代码可复用性,其中函数是可组合的(被视为组件),如 React 一样。 349 | - 调试更加容易:更少的 bug 和更轻松的维护。高阶函数可以更安全地进行编程,因为它更容易调试和维护更少的代码。 350 | - 任何开发人员都可以快速阅读和理解您的代码。因为编写的是你直观的想法,而不是计算机该如何替你思考。 351 | - 函数式编程中开发者可以更快地编写整洁的代码。用高阶函数 `map(),filter()` 替换 for 循环之类的迭代代码会带来简洁性。 352 | 353 | ## 结论 354 | 355 | 本文通过一些示例对 JavaScript 面向对象的工作方式进行了基本概述,及 `class` 语法(语法糖)和基于原型的 JavaScript 间的区别。还了解了 ES6 类语法仍然在底层实现了基于原型的语法。 356 | 357 | JavaScript 中的一些函数式编程概念,可以帮助开发者更好地判断是否选择这种范式。纯函数是这种范式的重中之重。我们还介绍了声明式和命令式方法及对应的代码示例。 358 | 359 | 编程范式没有孰是孰非,本文也不是对此的辩论而旨在介绍函数式编程的概念以助你更好的理解。 360 | 361 | ## 更多关于函数式编程的内容 362 | 363 | - [Why Functional programming matters](https://www.youtube.com/watch?v=XrNdvWqxBvA) 364 | - [An introduction to functional programming in JavaScript](https://opensource.com/article/17/6/functional-javascript) 365 | - [Master the Coding interview: What is functional programming](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-functional-programming-7f218c68b3a0) 366 | -------------------------------------------------------------------------------- /iterators.md: -------------------------------------------------------------------------------- 1 | # 迭代器及生成器高阶控制技巧 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-iterators-tips-on-gaining-advanced-control-over-generators-41dc3eb3bc20),本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理第二十三章。** 6 | 7 | ![](./assets/0_qelaH45ZWG3UybLO.jpg) 8 | 9 | ## 概述 10 | 11 | 无论在什么编程语言中,处理集合中的每个项是很常见的操作。JavaScript 也不例外,它提供了许多迭代集合的方法,从简单的 for 循环到更复杂的 `map()` 和 `filter()` 方法等。 12 | 13 | 迭代器和生成器将迭代的概念直接带入语言核心,并提供了一种自定义 `for...of` 循环的行为的机制。 14 | 15 | ## 迭代器 16 | 17 | 在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 18 | 19 | 迭代器可以是实现 `Iterator` 接口的任意对象。这意味着它必须有一个 `next()` 方法,该方法会返回一个具有以下两项属性的对象: 20 | 21 | 更具体地说,迭代器是通过使用 `next()` 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。 22 | 23 | * `value`: 序列中的 `next` 值 24 | * `done`:如果已经迭代到序列中的最后一个值,则它为 `true` 。如果 `value` 和 `done` 同时存在,则它是迭代器的返回值。 25 | 26 | 一旦创建,迭代器对象可以通过重复调用 `next()`显式地迭代。在终止值产生后,对 `next()`的额外调用应该继续返回 `{done:true}`。 27 | 28 | ### 迭代器的使用 29 | 30 | 可能有时需要很多资源才能为数组分配值并遍历每个值。迭代器则只在必要时使用。这为迭代器遍历无限大小的序列提供了可能性。 31 | 32 | 这是一个创建简单的迭代器的示例,该迭代器生成[斐波那契数列](https://en.wikipedia.org/wiki/Fibonacci_number): 33 | 34 | ```js 35 | function makeFibonacciSequenceIterator(endIndex = Infinity) { 36 | let currentIndex = 0; 37 | let previousNumber = 0; 38 | let currentNumber = 1; 39 | 40 | return { 41 | next: () => { 42 | if (currentIndex >= endIndex) { 43 | return { value: currentNumber, done: true }; 44 | } 45 | 46 | let result = { value: currentNumber, done: false }; 47 | let nextNumber = currentNumber + previousNumber; 48 | previousNumber = currentNumber; 49 | currentNumber = nextNumber; 50 | currentIndex++; 51 | 52 | return result; 53 | } 54 | ``` 55 | `makeFibonacciSequenceIterator` 开始生成斐波那契数,在到达 `endIndex` 时停止。迭代器在每次迭代时返回当前的斐波那契数,并在完成后继续返回最后生成的数。 56 | 57 | 这是通过上面的迭代器生成斐波那契数的输出: 58 | 59 | ```js 60 | let fibonacciSequenceIterator = makeFibonacciSequenceIterator(5); // Generates the first 5 numbers. 61 | let result = fibonacciSequenceIterator.next(); 62 | while (!result.done) { 63 | console.log(result.value); // 1 1 2 3 5 8 64 | result = fibonacciSequenceIterator.next(); 65 | } 66 | ``` 67 | 68 | ### 定义可迭代目标 69 | 70 | 上述例子中迭代器的创建可能会产生某些问题因为没有办法事先校验这是否是一个有效迭代器。你可能会说返回的对象不是包含 `next()` 方法可以用来验证,但也存在一些非迭代对象本身就定义了 `next()` 。 71 | 72 | 这也是为什么 `JavaScript` 在定义一个可迭代对象时有更多要求的原因。 73 | 74 | 上面的斐波那契例子不会被 `JavaScript` 判断为一个可迭代对象。开发者可以用 `for...of` 循环对其进行遍历来测验。 75 | 76 | ```js 77 | let fibonacciSequenceIterator = makeFibonacciSequenceIterator(5); 78 | 79 | for (let x of fibonacciSequenceIterator) { 80 | console.log(x); 81 | } 82 | ``` 83 | 84 | 上面代码会抛出异常 `Uncaught TypeError: fibonacciSequenceIterator is not iterable` 。 85 | 86 | 一些内置类型,如 `Array` 或 `Map` 拥有默认的迭代行为,而其他类型(比如`Object`)则没有。 87 | 88 | 为了实现可迭代,一个对象必须实现 `@@iterator` 方法,这意味着这个对象(或其原型链中的任意一个对象)必须具有一个带 `Symbol.iterator` 键(key)的属性。属性的定义是一个返回待迭代元素的函数 89 | 90 | 让我们来来看看在斐波那契例子中应该进行如何的改造: 91 | 92 | ```js 93 | function makeFibonacciSequenceIterator(endIndex = Infinity) { 94 | let currentIndex = 0; 95 | let previousNumber = 0; 96 | let currentNumber = 1; 97 | 98 | let iterator = {}; 99 | iterator[Symbol.iterator] = () => { 100 | return { 101 | next: () => { 102 | if (currentIndex >= endIndex) { 103 | return { value: currentNumber, done: true }; 104 | } 105 | 106 | const result = { value: currentNumber, done: false }; 107 | const nextNumber = currentNumber + previousNumber; 108 | previousNumber = currentNumber; 109 | currentNumber = nextNumber; 110 | currentIndex++; 111 | 112 | return result; 113 | } 114 | } 115 | }; 116 | ``` 117 | 118 | 现在我们可以用 `for...of` 循环对其进行遍历了。 119 | 120 | ```js 121 | let fibonacciSequenceIterator = makeFibonacciSequenceIterator(5); 122 | 123 | for (let x of fibonacciSequenceIterator) { 124 | console.log(x); //1 1 2 3 5 8 125 | } 126 | ``` 127 | 128 | ## 生成器 129 | 130 | 自定义迭代器非常有用,在某些场景下能极大提高效率,但由于需要显式地维护其内部状态,因此需要谨慎地创建及维护代码。 131 | 132 | 生成器函数提供另一种强大的思路:它允许你定义一个包含自有迭代算法的函数,同时它可以自动维护自己的状态。生成器函数使用 `function*` 语法。 133 | 134 | 调用时,生成器函数在一开始不会执行其代码。相反,它们返回一种特殊的迭代器类型,称为生成器。当通过调用生成器的 `next` 方法消耗一个值时,`Generator` 函数将一直执行直到遇到 `yield` 关键字。 135 | 136 | 一个生成器可以视为连续调用的生成一系列值的函数而返回单个值的函数。 137 | 138 | 生成器的语法包括一个称为 `yield` 的运算符,该运算符允许函数暂停直到请求下一个值。 139 | 140 | ```js 141 | function* makeFibonacciSequenceGenerator(endIndex = Infinity) { 142 | let previousNumber = 0; 143 | let currentNumber = 1; 144 | 145 | for (let currentIndex = 0; currentIndex < endIndex; currentIndex++) { 146 | yield currentNumber; 147 | let nextNumber = currentNumber + previousNumber; 148 | previousNumber = currentNumber; 149 | currentNumber = nextNumber; 150 | } 151 | } 152 | 153 | let fibonacciSequenceGenerator = makeFibonacciSequenceGenerator(5); 154 | 155 | for (let x of fibonacciSequenceGenerator) { 156 | console.log(x); 157 | } 158 | ``` 159 | 160 | 可以看出生成器语法更易于实现和维护。 161 | 162 | ### 生成器高阶控制 163 | 164 | 迭代器显式定义 `next()` 函数,以便通过 JavaScript 实现需要的接口。使用生成器时,将隐式添加 `next()` 函数,但该函数仍然存在。这就是生成器生成有效的可迭代对象的方式。 165 | 166 | 生成器隐式定义的 `next()` 函数接受可用于修改内部状态的参数。传递给 `next()` 的值将被 `yield` 语句接收。 167 | 168 | 让我们进一步修改斐波那契示例,以便控制在遍历序列的每个步骤中可以控制跳过多少个数字: 169 | 170 | ```js 171 | function* makeFibonacciSequenceGenerator(endIndex = Infinity) { 172 | let previousNumber = 0; 173 | let currentNumber = 1; 174 | let skipCount = 0; 175 | 176 | for (let currentIndex = 0; currentIndex < endIndex; currentIndex++) { 177 | if (skipCount === 0) { 178 | skipCount = yield currentNumber; // skipCount is the parameter passed through the invocation of `fibonacciSequenceGenerator.next(value)` below. 179 | skipCount = skipCount === undefined ? 0 : skipCount; // makes sure that there is an input 180 | } else if (skipCount > 0){ 181 | skipCount--; 182 | } 183 | 184 | let nextNumber = currentNumber + previousNumber; 185 | previousNumber = currentNumber; 186 | currentNumber = nextNumber; 187 | } 188 | } 189 | 190 | let fibonacciSequenceGenerator = makeFibonacciSequenceGenerator(50); 191 | 192 | console.log(fibonacciSequenceGenerator.next().value); // prints 1 193 | console.log(fibonacciSequenceGenerator.next(3).value); // prints 5 since 1, 2, and 3 are skipped. 194 | ``` 195 | 196 | 请注意,传递给 `next()` 首次调用的参数始终会被忽略。 197 | 198 | 另一个重要功能是能够通过调用生成器的 `throw()` 方法传递应抛出的异常值来强制生成器抛出异常。调用后将从生成器的当前挂起上下文中抛出此异常,就好像当前挂起的 `yield` 语句会被 `throw` 代替。 199 | 200 | 如果异常未在生成器中被捕获,它将通过 `throw()` 的外部调用向上传播,随后对 `next()` 的调用将导致 `done` 属性设为 `true`。让我们看下面的例子: 201 | 202 | ```js 203 | function* makeFibonacciSequenceGenerator(endIndex = Infinity) { 204 | let previousNumber = 0; 205 | let currentNumber = 1; 206 | let skipCount = 0; 207 | 208 | try { 209 | for (let currentIndex = 0; currentIndex < endIndex; currentIndex++) { 210 | if (skipCount === 0) { 211 | skipCount = yield currentNumber; 212 | skipCount = skipCount === undefined ? 0 : skipCount; 213 | } else if (skipCount > 0){ 214 | skipCount--; 215 | } 216 | 217 | let nextNumber = currentNumber + previousNumber; 218 | previousNumber = currentNumber; 219 | currentNumber = nextNumber; 220 | } 221 | } catch(err) { 222 | console.log(err.message); // will print ‘External throw’ on the fourth iteration. 223 | } 224 | } 225 | 226 | let fibonacciSequenceGenerator = makeFibonacciSequenceGenerator(50); 227 | 228 | console.log(fibonacciSequenceGenerator.next(1).value); 229 | console.log(fibonacciSequenceGenerator.next(3).value); 230 | console.log(fibonacciSequenceGenerator.next().value); 231 | fibonacciSequenceGenerator.throw(new Error('External throw')); 232 | console.log(fibonacciSequenceGenerator.next(1).value); // undefined will be printed since the generator is done. 233 | ``` 234 | 235 | 也可以通过调用返回给定值的 `return(value)` 方法来终止生成器: 236 | 237 | ```js 238 | let fibonacciSequenceGenerator = makeFibonacciSequenceGenerator(50); 239 | 240 | console.log(fibonacciSequenceGenerator.next().value); // 1 241 | console.log(fibonacciSequenceGenerator.next(3).value); // 5 242 | console.log(fibonacciSequenceGenerator.next().value); // 8 243 | console.log(fibonacciSequenceGenerator.return(374).value); // 374 244 | console.log(fibonacciSequenceGenerator.next(1).value); // undefined 245 | ``` 246 | 247 | ### 异步生成器 248 | 249 | 可以在异步上下文中定义并使用生成器。异步生成器可以异步生成一系列值。 250 | 251 | 语法非常直接。关键字 `async` 需要位于生成器的 `function*` 前。 252 | 253 | 当遍历生成的序列时,需要在 `for…of` 构造中使用 `await` 关键字。 254 | 255 | 我们将再次修改斐波那契示例,使其根据预定义超时来生成序列: 256 | 257 | ```js 258 | async function* makeFibonacciSequenceGenerator(endIndex = Infinity) { 259 | let previousNumber = 0; 260 | let currentNumber = 1; 261 | 262 | for (let currentIndex = 0; currentIndex < endIndex; currentIndex++) { 263 | await new Promise(resolve => setTimeout(resolve, 1000)); // a simple timeout as an example. 264 | yield currentNumber; 265 | let nextNumber = currentNumber + previousNumber; 266 | previousNumber = currentNumber; 267 | currentNumber = nextNumber; 268 | } 269 | } 270 | 271 | (async () => { 272 | const fibonacciSequenceGenerator = makeFibonacciSequenceGenerator(6); 273 | for await (let x of fibonacciSequenceGenerator) { 274 | console.log(x); // 1, then 1, then 2, then 3, then 5, then 8 (with delay in between). 275 | } 276 | ``` 277 | 278 | 由于生成器是异步的,因此我们可以内部使用 `await`,依赖 `promise` 执行网络请求等异步操作。这里生成器的 `next()` 方法返回一个 `Promsie`。 279 | 280 | 如果出于某种原因不想使用生成器但又想定义一个可迭代的对象,则必须使用 `Symbol.asyncIterator` 而不是上面的 `Symbol.iterator`。 281 | 282 | 尽管与迭代器相比,生成器更易于创建和维护,但与普通函数相比,它们会更难调试。在异步上下文中尤其如此。可能有很多原因。一个例子是假如在外部调用 `throw()` 方法时,栈跟踪作用可能非常有限。在这种情况下几乎不可能正常调试,开发者可能需要要求用户提供更多的信息。 283 | 284 | ## 参考资源 285 | * 286 | * -------------------------------------------------------------------------------- /mutation-observer.md: -------------------------------------------------------------------------------- 1 | # 使用 MutationObserver 监测 DOM 变化 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401),略有删减,本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理的第十章。** 6 | 7 | 网络应用在客户端日益复杂,这是由很多因素的造成的,比如需要更加丰富的界面交互以提供更加复杂的应用功能,实时计算等等。 8 | 9 | 网络应用的日益复杂导致无法知晓其生命周期中指定时刻准确的交互界面状态。 10 | 11 | 如果你正在构建一些框架或者一个库,这会更加的困难,比如,你无法通过监测 DOM 来响应并执行一些特定的操作。 12 | 13 | ## 概述 14 | 15 | [MutationObserver ](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) 是现代浏览器提供的用来检测 DOM 变化的网页接口。你可以使用这个接口来监听新增或者删除的节点,属性更改,或者文本节点的内容更改。 16 | 17 | 可以干点啥好呢? 18 | 19 | 你可以在以下几种情况信手拈来 MutationObserver 接口。比如: 20 | 21 | * 通知用户当前所在的页面所发生的一些变化。 22 | * 通过使用一些新的很棒的 JavaScript 框架来根据 DOM 的变化来动态加载 JavaScript 模块。 23 | * 可能当你在开发一个所见即所得编辑器的时候,使用 MutationObserver 接口来收集任意时间点上的更改,从而轻松地实现撤消/重做功能。 24 | 25 | ![](https://user-images.githubusercontent.com/1475173/41054347-b4ad6ecc-69f0-11e8-9ecb-dfe18497b393.png) 26 | 27 | 这只是几个 MutationObserver 的使用场景。 28 | 29 | ## 如何使用 MutationObserver 30 | 31 | 在应用中集成 MutationObserver 是相当简单的。通过往构造函数 `MutationObserver` 中传入一个函数作为参数来初始化一个 MutationObserver 实例,该函数会在每次发生 DOM 发生变化的时候调用。`MutationObserver` 的函数的第一个参数即为单个批处理中的 所有的 DOM 变化集合。每个变化包含了变化的类型和所发生的更改。 32 | 33 | ``` 34 | var mutationObserver = new MutationObserver(function(mutations) { 35 | mutations.forEach(function(mutation) { 36 | console.log(mutation); 37 | }); 38 | }); 39 | ``` 40 | 41 | 创建的实例对象拥有三个方法: 42 | 43 | * `observe`-开始进行监听 DOM 更改。接收两个参数-要观察的 DOM 节点以及一个配置对象。 44 | * `disconnect`-停止监听变化。 45 | * `takeRecords`-触发回调前返回最新的批量 DOM 更改。 46 | 47 | 以下为开始监听的代码片段: 48 | 49 | ``` 50 | // 开始监听页面根元素 HTML 变化。 51 | mutationObserver.observe(document.documentElement, { 52 | attributes: true, 53 | characterData: true, 54 | childList: true, 55 | subtree: true, 56 | attributeOldValue: true, 57 | characterDataOldValue: true 58 | }); 59 | ``` 60 | 61 | 现在,假设你写了一个简单的 `div` 元素: 62 | 63 | ``` 64 |
Simple div
65 | ``` 66 | 67 | 可以使用 jQuery 来移除 div 的 `class` 属性: 68 | 69 | ``` 70 | $("#sample-div").removeAttr("class"); 71 | ``` 72 | 73 | 当调用 `mutationObserver.observe(…)` 就可以开始监听 DOM 变化。 74 | 75 | 当每次发生 DOM 变化的时候,会打印出各个 [MutationRecord](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) 日志信息: 76 | 77 | ![](https://user-images.githubusercontent.com/1475173/41054356-b715da3c-69f0-11e8-9248-cf5171efa6f0.png) 78 | 79 | 这一更改是由移除 `class` 属性所引起的。 80 | 81 | 最后,如果想停止监听 DOM 变化可以使用如下方法: 82 | 83 | ``` 84 | // MutationObserver 停止监听 DOM 变化 85 | mutationObserver.disconnect(); 86 | ``` 87 | 88 | 现在,`MutationObserver` 浏览器兼容情况很好: 89 | 90 | ![](https://user-images.githubusercontent.com/1475173/41054350-b5bedb8e-69f0-11e8-9a13-2a34c469aecf.png) 91 | 92 | ## 替代方法 93 | 94 | 然而,之前 `MutationObserver` 并没有被广泛使用。那么,当没有 `MutationObserver` 的时候,开发者是如何解决监听 DOM 变化的呢? 95 | 96 | 有几下几种可用的方法: 97 | 98 | * **轮询** 99 | * **MutationEvents** 100 | * **CSS 动画** 101 | 102 | ## 轮询 103 | 104 | 最简单且粗糙的方法即使用轮询。使用浏览器内置的 setInterval 网页接口你可以创建一个定时任务来定时检查是否 DOM 发生变化。当然了,这个方法会显著地减弱网络应用/网站的性能。 105 | 106 | 其实,这是可以理解为脏检查,如果有使用过 AngularJS 应该会有看过其脏检查所导致的性能问题。在我的另一个系列里面有稍微介绍了下,具体可以查看[这里](https://github.com/Troland/writing-a-javascript-framework/wiki/4.%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%E7%AE%80%E4%BB%8B)。 107 | 108 | ## MutationEvents 109 | 110 | 早在 2000 年,就推出了 [MutationEvents API](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events) 。虽然挺管用的,但是每个单一的 DOM 变化都会触发 mutation 事件,结果又会造成性能问题。现在,`MutationEvents` 接口已经被废弃,不久的将来,现代浏览器将全都停止支持该接口。 111 | 112 | 以下是 `MutationEvents` 的浏览器兼容情况: 113 | 114 | ![](https://user-images.githubusercontent.com/1475173/41054352-b63b227a-69f0-11e8-8208-c009ab04b428.png) 115 | 116 | ## CSS 动画 117 | 118 | 依靠 [CSS 动画](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations) 是一个有点令人感到新奇的替代方案。这听起来会让人有些困惑。大体上,实现思路是这样的,创建一个动画,一旦在 DOM 中添加一个元素就会触发该动画。开始执行 CSS 动画的时候就会触发 `animationstart` 事件:假设为该事件添加事件监听器,就可以准确知晓 DOM 中添加元素的时机。动画的运行时间周期必须非常的短几乎让用户感知不到,即体验更佳。 119 | 120 | 首先,需要一个父级元素,在里面监听节点添加事件: 121 | 122 | ``` 123 |
124 | ``` 125 | 126 | 为了处理节点的添加,需要创建关键帧序列动画,该序动画在添加节点的时候启动: 127 | 128 | ``` 129 | @keyframes nodeInserted { 130 | from { opacity: 0.99; } 131 | to { opacity: 1; } 132 | } 133 | ``` 134 | 135 | 创建好关键帧之后,在需要监听的元素上应用动画。注意到那个短暂的持续时间-在浏览器端动画痕迹会非常平滑(即用户会感觉不到有动画发生): 136 | 137 | ``` 138 | #container-element * { 139 | animation-duration: 0.001s; 140 | animation-name: nodeInserted; 141 | } 142 | ``` 143 | 144 | 这样会为 `container-element` 的所有后代节点添加动画。当动画结束,触发 insertion 事件。 145 | 146 | 我们需要创建一个函数作为事件监听器。在函数内部,开始必须使用 `event.animationName` 代码进行检查,确保是我们所监听的动画。 147 | 148 | ``` 149 | var insertionListener = function(event) { 150 | // 确保是所监听的动画 151 | if (event.animationName === "nodeInserted") { 152 | console.log("Node has been inserted: " + event.target); 153 | } 154 | } 155 | ``` 156 | 157 | 为父元素绑定事件监听器: 158 | 159 | ``` 160 | document.addEventListener(“animationstart”, insertionListener, false); // standard + firefox 161 | document.addEventListener(“MSAnimationStart”, insertionListener, false); // IE 162 | document.addEventListener(“webkitAnimationStart”, insertionListener, false); // Chrome + Safari 163 | ``` 164 | 165 | **这里采用了事件委托。** 166 | 167 | CSS 动画浏览器支持情况: 168 | 169 | ![](https://user-images.githubusercontent.com/1475173/41054354-b6bbc696-69f0-11e8-8782-051c474bcf54.png) 170 | 171 | 相比以上几种替代方案 `MutationObserver` 有几点优势。本质上,它会监听 DOM 可能发生的每个变化并且性能更优,因其会在批量 DOM 变化之后才触发回调事件。总之,`MutationObserver` 的兼容性很好,并且还有一些垫片,这些垫片底层使用 `MutationEvents` 。 172 | 173 | -------------------------------------------------------------------------------- /networking.md: -------------------------------------------------------------------------------- 1 | # 网络层探秘及如何提高其性能和安全性 2 | 3 | > 原文请查阅[这里](https://blog.sessionstack.com/how-javascript-works-inside-the-networking-layer-how-to-optimize-its-performance-and-security-f71b7414d34c),略有改动,本文采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)共享,BY [Troland](https://github.com/Troland)。 4 | 5 | **这是 JavaScript 工作原理的第十二章。** 6 | 7 | 正如在之前关于[渲染引擎的文章](https://github.com/Troland/how-javascript-works/blob/master/rendering.md)中所讲的那样,我们相信好的和伟大的 JavaScript 开发者之间的差别在于后者不仅仅只是理解了语言的具体细节还了解其内部构造和运行环境。 8 | 9 | ## 网络简史 10 | 11 | 49 年前,ARPAnet 诞生了。它是早期的[报文分组交换网络](https://en.wikipedia.org/wiki/Packet_switching)及第一个实现 [TCP/IP 协议套件](https://en.wikipedia.org/wiki/Internet_protocol_suite)的网络。该网络连通了加利福亚大学和斯坦福研究所。20 年后,Tim Berners-Lee 提出了一个后来为人所熟知的万维网 『Mesh』草案。在 49 年的时间里,网络走过了一段漫长的旅程,从仅仅只是是两台电脑间交换数据报文到至少 7500 万台服务器,38 亿人使用互联网以及 13 亿个网站。 12 | 13 | ![](https://user-images.githubusercontent.com/1475173/41820447-a58217aa-7804-11e8-9fec-aa47a938ec42.jpeg) 14 | 15 | 本文将试着分析现代浏览器使用哪些技术来自动提升应用性能(有些你甚至不了解),然后着重介绍浏览器网络层。最后,提供一些让浏览器提升网络应用程序性能的技巧。 16 | 17 | ## 概述 18 | 19 | 现代浏览器早已是专门为快速,高效和安全数据传输的网络应用/网站而设计开发的。拥有数以百计的组件运行于各个不同的层级,从进程管理和安全沙箱到 GPU 管线,音频和视频及其它更多等等,网络浏览器更类似于一个操作系统而不仅仅只是一个软件。 20 | 21 | 浏览器的整体性能是由一些大型的组件所决定的,这些组件包括:解析,布局,样式计算,JavaScript 和 WebAssembly 执行,渲染,当然还有网络堆栈。 22 | 23 | 一般情况下,工程师们会把网络堆栈看成是一个性能瓶颈。经常会发生这样的情况因为从网络抓取所有的资源会堵塞渲染剩下的步骤。为了更加高效的网络层,它需要不仅仅只是扮演简单的套接字管理员的角色。在我们看来获取资源是一个非常简单的机制,但是实际上它是集成自身的优化准则,接口和服务的整套平台。 24 | 25 | ![](https://user-images.githubusercontent.com/1475173/41820448-a5ce4454-7804-11e8-816f-790d4776866a.jpeg) 26 | 27 | 网页开发者不需要担心单独的 TCP 或者 UDP 数据包,请求格式化,缓存以及其它正在发生的一切。浏览器会处理这些复杂的玩意,这样就可以专注开发自己的程序。但是,知道其内部的原理可以帮助开发者开发出更加高效和安全的程序。 28 | 29 | 本质上,当用户开始和浏览器发生交互的情况如下: 30 | 31 | * 用户在浏览器地址栏中输入 URL 地址。 32 | * 给定网络上的 URL 资源,浏览器开始检查本地和应用程序缓存并试着使用本地副本来满足资源请求。 33 | * 当缓存不可用,浏览器使用 URL 中的域名然后根据域名从 [DNS](https://en.wikipedia.org/wiki/Domain_Name_System) 处获取服务器的 IP 地址。如果域名有缓存,将不需要进行 DNS 查询。 34 | * 浏览器创建一个 HTTP 报文表明其请求远程服务器的某个网页。 35 | * 报文被传输到 TCP 层,该层会在 HTTP 报文头部添加其自身的信息。该信息是用来保持所创建的会话的必要信息。 36 | * 然后在 IP 层处理报文,该层的主要职责即找出从用户发送报文到远程服务器的路径。在 HTTP 报文头部添加该路径信息。 37 | * 传输报文到远程服务器。 38 | * 一旦接收到报文,以类似的方式返回响应数据。 39 | 40 | W3C [Navigation Timing specification](http://www.w3.org/TR/navigation-timing/) 提供了浏览器接口及浏览器中每个请求生命周期背后的可视化计时和性能数据。让我们浏览下这些组件,因为每个组件在实现最佳用户体验方面扮演了重要的角色。 41 | 42 | ![](https://user-images.githubusercontent.com/1475173/41820446-a53255da-7804-11e8-9c28-2bd1a5c2ccb8.png) 43 | 44 | 整个网络请求过程是相当复杂的并且有许多的层次结构,每一层都有可能成为性能瓶颈。这就是为什么浏览器使用各种技术努力提升其性能,以便把整个网络通信的性能损耗降至最低。 45 | 46 | ## 套接字管理 47 | 48 | 看些新技术吧: 49 | 50 | * **源**-由应用程序协议,域名和端口号的三个部分组成(比如 https, www.example.com, 443) 51 | * **套接字池**-属于同源的一组套接字(所有的主流浏览器都限制套接字池最多只能有 6 个套接字) 52 | 53 | JavaScript 和 WebAssembly 禁止开发者操作单独的网络套接字的生命周期,这样是相当的明智的。这样不仅仅可以让你头发少掉点而且可以让浏览器自动优化大量的性能,这些性能包括套接字重用,请求优化和延迟绑定,协议协商,强制连接限制及其它的优化措施。 54 | 55 | 实际上,现代浏览器更一步地将请求管理循环从套接字管理中剥离了出来。用套接字池来组织套接字,以源进行分组,每个套接字池强制限制其连接数和安全约束。对待完成的请求进行入列,设置优先级等,然后和套接字池中的单个套接字绑定。如果不是服务器主动关闭这些连接,多个请求可以自动重用相同的套接字。 56 | 57 | ![](https://user-images.githubusercontent.com/1475173/41820445-a4e4d9b8-7804-11e8-9ff7-488b4d2278f2.png) 58 | 59 | 由于创建一个新的 TCP 连接会带来额外的性能开销,重用连接会为其自带来极大的性能提升。默认情况下,当发起请求的时候,浏览器使用所谓的 『keepalive』机制以节省创建到服务器的新连接所耗费的时间。创建一个新的 TCP 连接的平均时间为: 60 | 61 | * 本地请求-23 毫秒 62 | * Transcontinental 请求-120 毫秒 63 | * Intercontinental 请求-225 毫秒 64 | 65 | 这样的架构衍生出了一些其它的优化方法。请求可以依据优先级来以不同的顺序执行。浏览器可以优化所有套接字间的带宽分配或者它可以创建套接字以等待预期的请求。 66 | 67 | 如上所述,这些都是浏览器所控制而不用开发者进行干预。但这并不意味着我们无所事事了。选择正确的网络通信模式,类型和频率,正确的协议类型以及服务器堆栈隧道/优化对于提升整个程序的性能至关重要。 68 | 69 | 一些浏览器甚至更进一步。例如,当你使用 Chrome 的时候,当用户使用的时候它会进行自我学习从而变得更加快速。它基于访问过的网页和典型的浏览器模式来进行学习,这样就可以预期可能的用户行为且在用户进行任意操作之前进行操作。最简单的例子即当用户悬停在某个链接上的时候预渲染页面。如果你想学习更多关于 Chrome 优化技术的文章,可以查看 [High-Performance Browser Networking](https://hpbn.co/) 这本书的 章节。 70 | 71 | ## 网络安全和沙箱 72 | 73 | 允许浏览器操作单独的套接字有另一个非常重要的目的即:浏览器就可以针对不被信任的程序资源强制实施一套一致的安全和政策约束措施。例如,浏览器禁止通过 API 直接访问原始网络套接字,因为这样会导致任意可疑的程序随意连接任意主机。浏览器也强制连接数限制以保护服务器及客户端免受资源枯竭的困扰。 74 | 75 | 浏览器格式化所有流出的请求以强制一致和符合规则的协议语义来保护服务器。同样地,浏览器会自动解码响应内容以保护用户免受可疑服务器的攻击。 76 | 77 | ## TSL 协商 78 | 79 | [Transport Layer Security (TLS)](https://en.wikipedia.org/wiki/Transport_Layer_Security) 是一个为计算机网络提供通信安全的加密协议。它广泛应用于大量应用程序,其中之一即浏览网页。网站可以使用 TLS 来保证服务器和网页浏览器之间的所有通信安全。 80 | 81 | 整个 TLS 握手过程包含以下几个步骤: 82 | 83 | 1. 客户端向服务器发送 『Client hello』 信息,附带着客户端随机值和支持的密码组合。 84 | 2. 服务器返回给客户端 『Server hello』信息,附带着服务器随机值。 85 | 3. 服务器返回给客户端认证证书及或许要求客户端返回一个类似的证书。服务器返回『Server hello done』信息。 86 | 4. 如果服务器要求客户端发送一个证书,客户端进行发送。 87 | 5. 客户端创建一个随机的 Pre-Master 密钥然后使用服务器证书中的公钥来进行加密,向服务器发送加密过的 Pre-Master 密钥。 88 | 6. 服务器收到 Pre-Master 密钥。服务器和客户端各自生成基于 Pre-Master 密钥的主密钥和会话密钥。 89 | 7. 客户端给服务器发送一个 『Change cipher spec』的通知,表明客户端将会开始使用新的会话密钥来哈希和加密消息。客户端也发送了一个 『Client finished』的消息。 90 | 8. 服务器接收到『Change cipher spec』的通知然后使用会话钥匙来切换其记录层安全状态为对称加密状态。服务器返回客户端一个 『Server finished』消息。 91 | 9. 客户端和服务器现在可以通过建立的安全通道来交换程序数据。所有客户端和服务器之间发送的信息都会使用会话密钥进行加密。 92 | 93 | 每当发生任何验证失败的时候,用户会收到警告。比如服务器使用自签名的证书。 94 | 95 | ## 同源策略 96 | 97 | 当两个页面的协议,端口(如果有指定)以及主机名都是一样的则称为同源。 98 | 99 | 以下为一些可能包含跨域的资源示例: 100 | 101 | * `` 里面的 JavaScript 代码。语法错误的错误信息仅适用于同源脚本。 102 | * `` 的 CSS。由于 CSS 的松散语法规则,跨域 CSS 要求正确的 Content-Type 头。各个浏览器的限制不同。 103 | * `` 图片 104 | * `