做者:Carl Mungazijavascript
翻译:疯狂的技术宅html
原文:www.smashingmagazine.com/2019/07/jav…前端
未经容许严禁转载java
你还记得本身第一次深刻挖掘经常使用的库或框架的源代码时的情景吗?对我而言,那一刻是我三年前做为前端开发人员的第一份工做。node
咱们刚刚完成了用于建立在线课程的内部遗留框架的重写。在开始重写时,咱们花时间研究了许多不一样的解决方案,包括 Mithril、Inferno、Angular、React、Aurelia、Vue 和 Polymer。由于我是一个萌新(我刚重新闻转向网络开发),我记得每一个框架的复杂性都让人感到惧怕,并且不理解框架的工做方式。react
当我开始更深刻地研究咱们选择的 Mithril 框架时,个人能力增加了。从那之后,我对 JavaScript 的了解以及通常的编程方式获得了很大的提升,我花了不少时间深刻研究天天在工做种或在本身的项目中使用的库。在本文中,我将分享一些分析库或框架的方法。git
经过 Mithril 的 hyperscript 功能介绍如何去阅读源代码。github
阅读源代码的好处之一是可使你学到更多的东西。当我第一次看到 Mithril 的代码库时,对虚拟 DOM 的含义只有一个模糊的概念。当我读完时,就知道了虚拟 DOM 是一种技术,它涉及建立描述用户界面的对象树应该是什么样的。而后使用 DOM API(例如 document.createElement
)将该树转换为 DOM 元素。经过建立描述用户界面将来状态的新树,而后将其与旧树中的对象进行比较来执行更新。web
以前我已经在各类文章和教程中读到过这些内容,虽然颇有帮助,可是在程序的上下文中可以观察它对我来讲是很是有启发性的。它还告诉我在比较不一样的框架时要问哪些问题。例如我如今不是去查看 GitHub 上的 star 数量,而是会问“每一个框架执行更新的方式如何影响性能和用户体验?”这样的问题。chrome
另外一个好处是增长你对良好应用架构的理解。虽然大多数开源项目一般与其存储库遵循相同的结构,但每一个项目都包含差别。Mithril 的结构很是扁平,若是你熟悉它的 API,能够对文件夹中的代码进行有根据的猜想,好比render
、router
和 request
等。另外一方面,React 的结构也反映了它的新架构。维护者将负责 UI 更新的模块(react-reconciler
)与负责渲染 DOM 元素的模块(react-dom
)分开。
这样作的好处之一是,开发人员如今能够经过 hook 到 react-reconciler
包来编写本身的自定义渲染器。我最近研究过的模块捆绑包 Parcel 也有像 React 这样的 packages
文件夹。密钥模块名为 parcel-bundler
,它包含负责建立捆绑包、热启动模块服务器和命令行工具的代码。
不久以后,你正在阅读的源代码将引导你进入 JavaScript 规范。
另外一个令我感到惊讶的好处是:你能够更轻松地阅读官方 JavaScript 规范,该规范定义了语言的工做方式。我第一次阅读规范的时候是在分析 throw Error
和 throw new Error
之间的区别。之因此要分析这个,是由于我注意到 Mithril 在其 m
函数的实现中使用了 throw Error
,我想知道这样是否是比 throw new Error
更好。从那之后,我也学会了逻辑运算符 &&
和 ||
不必定返回布尔值,找到了控制 ==
等式运算符如何强制赋值的[规则](http ://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison)和Object.prototype.toString.call({})
返回 '[object Object]'
的[缘由](http://www.ecma- international.org/ecma-262/#sec-object.prototype.tostring) 。
有不少方法能够处理源代码。我发现最简单的方法是,从你选择的库中挑选一种方法,并去记录调用它时会发生什么。不是去记录每一步,而是要尝试肯定其总体流程和结构。
我最近用这种方法阅读了 ReactDOM.render
的代码 ,所以学到了不少关于 React Fibre 及其实现背后的一些原理。值得庆幸的是,因为 React 是一个流行的框架,我在同一个问题上看到过不少其余开发人员撰写的文章,这也加快了这个过程。
这深刻探讨并向我介绍了co-operative scheduling,window.requestIdleCallback
方法和真实的链表的示例(React 经过把更新放入队列来处理更新,这是优先更新的链表)。执行此类操做时,建议用库建立一个很是基本的程序。这可使得调试时更容易,由于你不用去处理由其余库引发的栈跟踪信息。
若是没有对代码进行深刻研究,我会正在处理的项目中打开 /node_modules
文件夹,或者转到 GitHub 存储库。当我遇到错误或有趣的功能时,一般会发生这种状况。在 GitHub 上阅读代码时,请确保你正在阅读最新版本。你能够经过单击用于更改分支的按钮,并选择 “tags” 来查看带有最新版本标记的代码。库和框架永远在持续更新,因此你不但愿把精力花费在下一版本中可能会删除的内容。
还有另外一种阅读源代码的方式,我喜欢称之为“粗略一瞥”,这种方法并不那么简单。在我刚刚开始阅读代码的时候安装了 express.js,我打开了它的 /node_modules
文件夹并浏览了它的依赖项。若是 README
没有给我一个满意的解释,我就会阅读源代码。这样作让我获得了一些有趣的发现:
merge-descriptors
只添加在源对象上直接找到的属性,它还合并了不可枚举的属性,而 utils-merge
只迭代对象的可枚举属性以及在其原型链中找到的属性。 merge-descriptors
使用 Object.getOwnPropertyNames()
和 Object.getOwnPropertyDescriptor()
,而 utils-merge
使用了 for..in
;setprototypeof
模块提供了一种跨平台设置实例化对象原型的方式;escape-html
是一个有 78 行代码的模块,用于转义字符串内容,所以能够用它在 HTML 内容中进行插值。虽然阅读源代码的结果不太可能当即就能用得上,可是可以使你对本身使用的库或框架的依赖关系有一个大体的了解,这是很是有用的。
在调试前端代码时,浏览器的调试工具是你最好的朋友。除此以外,它们容许你随时暂停程序并检查其状态、跳过函数的执行、进入或退出程序。不过有时这不可能当即作到,由于代码有可能已经被压缩过。我倾向于取消它们的通知,并将未经压缩的代码复制到 /node_modules
目录中的相关文件里。
像其余程序同样进行调试。造成一个假设,而后进行测试。
React-Redux 是一个用于管理 React 应用状态的库。在处理诸如此类的库时,我首先会搜索已经编写过有关其实现的文章。在这个案例研究中,我遇到了这篇文章(blog.isquaredsoftware.com/2018/11/rea…
connect
是一个 React-Redux 函数,它将 React 组件链接到应用程序的 Redux 存储。怎么样?好吧,根据官方文档的说明,它执行如下操做:
“...返回一个新的链接组件类,它将会包装你传入的组件。”
看完以后,我会问下列问题:
一般,下一步是建立一个使用 connect
的很是基本的示例程序。可是在这种状况下,我选择使用咱们在 Limejump 上构建的新 React 程序,由于我想在程序的上下文中理解 connect
,最终再进入生产环境。
我关注的组件看起来像这样:
class MarketContainer extends Component {
// code omitted for brevity
}
const mapDispatchToProps = dispatch => {
return {
updateSummary: (summary, start, today) => dispatch(updateSummary(summary, start, today))
}
}
export default connect(null, mapDispatchToProps)(MarketContainer);
复制代码
它是一个容器组件,包裹着四个较小的链接组件。你在导出 connect
方法的文件中遇到的第一件事就是这个评论:connect 是 connectAdvanced 的外观。这时咱们就有了第一个学习的点:有机会观察外观设计模式。在文件的末尾,咱们看到 connect
导出了一个名为 createConnect
的函数的调用。它的参数是一堆默认值,它们已被解构,以下所示:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) 复制代码
一样,咱们遇到了另外一个学习的点:导出调用函数和解构默认函数参数。解构部分是一个学习的点,由于代码编写以下:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory }) 复制代码
它会致使这个错误 Uncaught TypeError
:没法解析 undefined
或 null
的属性 connectHOC
。这是由于该函数没有默认参数能够依赖。
注意:有关此内容的更多信息,请阅读 David Walsh 的文章(davidwalsh.name/destructuri…
createConnect
自己在其函数体中没有任何功能。它返回一个名为 connect
的函数,我在代码里使用的函数:
export default connect(null, mapDispatchToProps)(MarketContainer)
复制代码
它须要四个参数,都是可选的,前三个参数根据参数是否存在及其值类型来定义它们的行为,这是经过 match
函数来实现的。如今由于提供给 match
的第二个参数是导入 connect
的三个函数之一,我必须决定应该遵循哪一个线程。
在这里学习的重点是:若是这些参数是函数,用于将第一个参数包装为 connect
的代理函数,isPlainObject
用于检查普通对象或 warning
模块,它揭示了如何将调试器设置为中断全部异常。在匹配函数以后,咱们来到 connectHOC
,这个函数接受咱们的 React 组件并将它链接到 Redux。它是另外一个函数调用,返回 wrapWithConnect
,实际上它用来处理将组件链接到 store 的函数。
看一看 connectHOC
的实现,我能够理解为何它须要 connect
来隐藏它的实现细节。它是 React-Redux 的核心,其中包含不须要经过 connect
公开的逻辑。我将结束这里的深度探讨,若是我继续的话,将是查阅我以前发现的参考资料的最佳时机,由于它包含了对代码库的很是详细的解释。
刚开始阅读源代码时很困难,但与全部的事情同样,随着时间的推移会变得更容易。咱们的目标不是理解一切,而是要得到不一样的思路和新知识。关键是要对整个过程进行深思熟虑,并对全部事物充满好奇心。
例如,我发现 isPlainObject
函数颇有趣,由于它用 if (typeof obj !== 'object' || obj === null) return false
来确保给定的参数是普通对象。当我第一次阅读它的代码实现时,想知道为何它没有用Object.prototype.toString.call(opts)!=='[object Object]'
,这能够用更少的代码来区分对象和对象子类型,例如 Date 对象。可是阅读下一行就会发现,当开发人员在使用 connect
返回 Date 对象的极不可能的事件中,将由 Object.getPrototypeOf(obj)=== null
检查来进行处理。
isPlainObject
的另外一个吸引人的地方是这段代码:
while (Object.getPrototypeOf(baseProto) !== null) {
baseProto = Object.getPrototypeOf(baseProto)
}
复制代码
谷歌搜索引导我找到这个 StackOverflow 帖子和 Redux issue,它们解释了该代码如何进行处理的案例,例如检查源自 iFrame 的对象。