本期精读的文章是: [Glossary of Modern JavaScript Concepts: Part 1](https://auth0.com/blog/glossary-of-modern-javascript-concepts/) [Glossary of Modern JavaScript Concepts: Part 2](https://auth0.com/blog/glossary-of-modern-javascript-concepts-part-2/) # 1 引言 我为什么要选这篇文章呢? 之所以选这篇文章, 是因为非常认同作者写这两篇文章的原因. 作者在文中说, 现代 JavaScript 的很多概念和思想在快速被传播和扩展, 很多新概念出现在前端相关的博客和文档中, 这些概念对于很多前端开发人员来说, 仍然很陌生. 因此我们有必要来学习一下现代的这些 JavaScript 的概念, 看这些概念在现在 JavaScript 的库或应用中是怎么被使用的. # 2 内容概要 文章讲了很多现代 JavaScript 中的概念, 罗列如下: ## 纯函数和副作用 在了解纯函数之前, 首先要了解副作用. 副作用是指改变了其作用域外的状态. 副作用的举例有调用了一个 API, 操作了一个 DOM 节点, 弹出了一个弹窗, 或者改变了一条数据等. 而纯函数则是指 函数的返回值仅仅由参数决定, 当给同样的参数时, 返回值是固定的. ## Stateful 和 Stateless (有状态和无状态) Stateless 无状态, 有点像纯函数, 不管理自己的数据或状态, 结果取决于参数. 而 Stateful, 有状态, 指的是函数自己有自己的运行状态, 可以修改自己的状态. 在现代 JavaScript 开发中, 处理状态, 显得很重要. ## 可变对象与不可变对象 可变对象与不可变对象概念很清楚, 可变对象指的是在创建后值仍可以被改变, 不可变对象指的是创建后值无法被改变. 相比于其他语言, 可变对象与不可变对象在 JavaScript 中更加模糊, 当你了解函数式编程时, 你会听到很多不可变对象的好处. 在 JavaScript 中, 你可以通过 Object.freeze(obj), 让一个对象变得不可变, 但是注意这是浅层的冻结对象, 如果有一个属性的值是个对象, 那这个对象中的属性是可以被修改的. 现在 JavaScript 也出现了 npm deep-freeze , Immutable.js 这些库来帮助你在 JavaScript 中实现不可变对象. ## Imperative and Declarative Programming(命令式和声明式编程) 命令式编程, 描述一段代码的逻辑怎么被显式调用去改变程序的状态. 声明式编程, 描述一段代码的逻辑, 而不需要描述如何完成这段逻辑. JavaScript 可以同时被写为命令式和声明式编程方式, 但是随着函数式编程的兴起, 声明式编程将变得更加普遍. ## 高阶函数 函数作为 JavaScript 的一等公民, 可以跟普通数据类型一样, 被存储, 或者被作为值传参. 而高阶函数就是一种函数 可以接收另外一个函数作为入参, 或者返回一个函数作为结果. ## 函数式编程 FP 上面我们了解的 纯函数, 无状态, 不可变对象, 命令式编程, 和高阶函数, 都是很重要的函数式编程组成. 函数式编程通过以下方式包含上述概念: - 关键函数实现使用纯函数, 没有副作用. - 数据不可变 - 函数 无状态 - 声明式代码去管理副作用和执行命令式编程 ## Hot and Cold Observables Observables 和数组类似, 只不过数组是被保存在内存中, 而 Observables 的每一个元素则是异步加入进来. 我们可以订阅这些 observables. Hot Observables 容易会被执行, 即使我们没有订阅它们. 比如说 用户的操作界面的 按钮点击事件, 鼠标移动, 窗口大小改变, 这些都是 Hot Observables. 而 cold observable 则是需要我们去订阅, 并且会在我们订阅的时候开始执行. ## 响应式编程 RP 响应式编程, 可以看作是面向异步事件流的编程, 声明式的, 表述去做什么, 而不是怎么做. ## 函数式响应型编程 FRP 函数式响应型编程简而言之,就是对事件或者行为给予声明式的反馈. FRP 具有两个很明显的特点: - 函数或者类型有明确的定义 - 操作的是连续变化的值 ## 作用域和闭包 闭包作为最常见的面试题经常被提及, 但是很多资深的前端开发都解释不清楚闭包, 即使他们理解闭包. 作者首先介绍了全局作用域和局部作用域, 作用域作为许多 JS 开发人员最开始学习的知识, 理解作用域对于编写优秀的代码至关重要. 闭包的形成在于, 当一个在函数内声明的函数可以引用外部函数的局部变量. 就形成了闭包. ## 单向数据流和双向数据流 随着现在各种 SPA 框架的兴起, 理解数据流概念, 对于现在 JS 开发者越来越重要, React 被认为是单向数据流的典范, 使用 Model 作为唯一的数据来源, 控制 View 的渲染. 在 View 层用事件的方式通知 Model 更新, 在反应到 View 层的变化上. 数据沿着一个方向流动, UI 永远不会更新 Model, 而是通过事件或者 setState 方法. 在双向数据绑定中, 数据是在两个方向上流动的, JS 可以更新 Model 数据, View 层 也可以更新 Model 数据. AngularJs 的 1.x 版本是双向数据流的典型实现. 早在 2009 年, 双向绑定是 Angualr 最受欢迎的特性之一, 但是 Angular 把这一特性抛弃了. 现在很多流行的框架和库都使用了单向数据流(React,Angular,Inferno,Redux 等). 单向数据流倡导的是清晰的架构, 数据流动更加清晰和易管理. 对于单向数据流来说说了点 View 自动更新数据的便利, 但也得到了清晰的数据流. ## JS 框架中的变化侦测: 脏检查, getter 和 setter, 虚拟 DOM 变化侦测对于现代 SPA 应用来说很重要. 当用户更新一些内容时, 应用必须以一种方法知道这种变化, 并做出反应更新. AngularJS 1.x 使用的是脏检查的方式, 具体做法是对 View 中涉及到的 Model 进行深度比较. 脏检查的优点在于它的简单和可预测, 不涉及到 API 和对象的变更. 但是正因为涉及到大量比较, 也很低效. Ember 和 Backbone 是使用 getters 和 setters 来做变化侦测, 这样涉及到数据修改时, 都会触发变更事件. 而 React 是使用了虚拟 Dom 来做变化侦测, React 通过 setState 方法来通知变更, 使用虚拟 Dom 来比较是否发生了数据变化. ## Web Components 组件 Web 组件是 Web 平台上可复用的基础组件, 而 Web Components 则定义了一些规范来实现这些可复用组件. 规范包括: - 自定义元素 - HTML Template - Shadow Dom - HTML imports 引入 Web Components 本身并不能代替 SPA 框架的功能, 但是它的想法和核心概念, 在很多 SPA 框架中都有体现. ## Smart 和 Dumb 组件 现在 Web 的开发严重依赖组件, 而很多时候我们把组件分成 Smart 组件和 Dumb 组件. Smart 组件, 又叫容器组件, 在组件内处理各种业务逻辑, 通常也管理 Dumb 组件,响应 Dumb 组件的事件. Dumb 组件, 又叫展示组件, 通常被写成纯函数, 依赖于外部的数据和方法, 专注于展现数据. ## JIT 编译 Just-In-time(JIT)编译指的是代码的运行时, 被编译成机器代码的过程. 在 JavaScript 运行时, JIT 能够找到代码的特定模式, 而这些模式可以让 JavaScript 更快的被执行. ## AOT 编译 Ahead-Of-Time(AOT), 指的是编写的代码在运行之前, 被翻译成机器代码的过程. AOT 给 tree shaking 带来了可能, 使用 AOT 预编译, 对于生产环境下的代码有以下好处: - 更少的异步请求, 模板和样式内联在 JS 内 - 更小的体积 - 更早的检查到模板错误 - 更好的安全性 ## Tree Shaking Tree Shaking 是指打包 JS 模块时, 通过对代码的静态分析, 排除掉不用的代码的机制. Tree Shaking 技术建立在 ES2015 模块的, import 和 export 上, 支持我们导入特定的内容,而不是整个库. ```plain import { BehaviorSubject } from 'rxjs/BehaviorSubject'; ``` 这样我们只导入了 BehaviorSubject, 而没有导入整个 Rxjs 库. # 3 精读 文中讲到的现代 JavaScript 已经很多了, 再对理解的现代 JavaScript 补充几条: ## Dependent injection(依赖注入) 通过控制反转,父级不需要关心子实现细节,将子类可能用到的实例都初始化好,由子类决定引入哪些依赖。还有一个好处是维持了单实例,这一点在数据流中尤为重要,如果 store 不是单例的,那数据流必然乱了套,既希望传给子类使用,又要维持单例,依赖注入是很好的解决方案。 ## Symbol Reflect Proxy Symbol 是 ES6 中加入的一种新的数据类型, 每一个 Symbol 都是独一无二的, 不与其它 Symbol 重复. ES6 中的 Proxy , 则是通过 Proxy 方法, 实现对于对象的一层拦截. 提供一种机制, 代理对象的操作. 而 Reflect 是一个内置的对象,它提供可拦截 JavaScript 操作的方法。方法与代理处理程序的方法相同。 这三篇文章非常详细介绍了这三位 API:[symbol](https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/) [reflect](https://www.keithcirkel.co.uk/metaprogramming-in-es6-part-2-reflect/) [proxy](https://www.keithcirkel.co.uk/metaprogramming-in-es6-part-3-proxies/) ## Server rendering 前端对后端渲染的热度降了很多,主要是盲目跟风的氛围消停了,真正需要的团队已经稳定的用起来了。后端渲染的理念很新颖,一定程度帮助了 html 认识到自己的不足,就像 Angular, React, Vue 对 webComponents 的冲击一样,或许未来十年可以用上 ECMAScript 标准提供的功能,但业务不能等待技术,现在唯有不断折腾,直到被消灭或者招安。 # 4 总结 伴随着各种框架的热度, 理解这些现代 JavaScript 概念变得越来越重要, 大家可以以这个作为概览, 详细去学习和了解现代 JavaScript 的概念. > 讨论地址是:[精读《现代 JavaScript 概览》 · Issue #35 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/35) > 如果你想参与讨论,请[点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,每周五发布。