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