- 原文地址:Improve Your JavaScript Knowledge By Reading Source Code
- 原文做者:Carl Mungazi
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:MarchYuanx
- 校对者:imononoke, Baddyo
快速摘要:当你还处于编程生涯的初期阶段时,深刻研究开源库和框架的源代码多是一项艰巨的任务。在本文中,Carl Mungazi 分享了他如何克服恐惧,并开始用源码来提升他的知识水平和专业技能。他还使用了 Redux 来演示他如何解构一个代码库。javascript
你还记得你第一次深刻研究你经常使用的库或框架的源码时的情景吗?对我来讲,这一刻发生在三年前我做为前端开发者的第一份工做中。html
当时咱们刚刚完成了用于建立网络学习课程的内部遗留框架的重构。在重构开始时,咱们花时间研究了许多不一样的解决方案,包括 Mithril、Inferno、Angular、React、Aurelia、Vue 和 Polymer。那时我仅仅只是个小萌新(我刚重新闻工做转向 web 开发),我记得我对每一个框架的复杂性感到恐惧,不理解它们是如何工做的。前端
随着对咱们所选择的 Mithril 框架研究的深刻,我对它的理解也逐渐加深了。从那之后,我花了不少时间深刻钻研那些在工做或我的项目中平常使用的库的内部结构,这显著地提高了我对 JavaScript —— 以及通用编程思想 —— 的了解。在这篇文章中,我将分享一些方法给你,你可使用本身喜欢的库或框架,并将其做为学习工具。java
我要介绍的第一个源码阅读示例是 Mithril 的 hyperscript 函数。(高清预览)node
阅读源代码的一个主要好处是能够学到不少东西。在我第一次读 Mithril 代码库时,我对虚拟 DOM 的概念还很模糊。当我读完后,我了解到虚拟 DOM 是一种技术,它建立一个对象树,用于描述用户界面的外观。而后使用 DOM APIs(如 document.createElement
)将对象树转换为 DOM 元素。经过建立描述用户界面的更新状态的新对象树,而后将其与旧对象树进行比较来执行更新。react
我在各类文章和教程中已经阅读了全部这些内容,虽然这颇有帮助,但对我来讲,可以在咱们提供的应用程序的环境中观察到它工做是很是有启发性的。它还教会我在比较不一样框架时应该考虑哪些因素。例如,我如今知道要考虑这样的问题,“每一个框架执行更新的方式如何影响性能和用户体验?”,而不是只看框架在 GitHub 上 star 的数量。android
另外一个好处是你对优秀的程序架构的理解和鉴赏能力提高了。虽然大多数开源项目的存储库一般遵循相同的结构,但每一个项目都包含差别。Mithril 的结构很是简单,若是你熟悉它的 API,你能够根据文件夹名称推测出其中的代码的功能,如 render
、router
和 request
。另外一方面,React 的结构反映了它的新架构。维护人员将负责 UI 更新的模块(react concerner
)与负责呈现 DOM 元素的模块(react dom
)分开。ios
这样作的好处之一是,开发人员如今更容易经过挂进 react-reconciler
包来编写本身的自定义渲染器。我最近研究过的模块打包工具 Parcel 也有像 React 这样的 packages
文件夹。主模块名为 parcel-bundler
,它包含负责建立包、启动热模块服务器和命令行工具的代码。git
不久以后,你所阅读的源码将引导你找到 JavaScript 规范。(高清预览)github
另外一个好处 —— 令我感到惊讶的是 —— 你能够更轻松地阅读定义语言如何工做的官方 JavaScript 规范。我第一次阅读规范是在研究 throw Error
与 throw new Error
(剧透警告 —— 两者没有区别)之间的区别时。我研究这个问题是由于我注意到 Mithril 在其 m
函数的实现中使用了 throw Error
,我想知道这种用法是否比使用 throw new Error 更好。从那之后,我还了解了逻辑运算符 &&
和 ||
不必定返回布尔值,找到了控制 ==
等于运算符如何强制转换值的规则和 Object.prototype.toString.call({})
返回 '[object Object]'
的缘由。
有不少方法能够处理源码。我发现最简单的方法是从你选择的库中选择一个方法,并记录当你调用它时会发生什么。不要每个步骤都记录,而是尝试理解它的总体流程和结构。
我最近用这个方法阅读了 ReactDOM.render 的源码,所以学到了不少关于 React Fiber 及其实现背后的一些缘由。谢天谢地,因为 React 是一个流行的框架,在一样的问题上,我找到了不少其余开发者撰写的文章,这让个人学习进程快了许多。
此次深刻研究还让我明白了合做调度的概念、window.requestIdleCallback
方法和一个连接列表的实际示例(React 经过将更新放入一个队列来处理它们,这个队列是一个按优先级排列的连接列表)。在研究过程当中,建议使用库建立很是基本的应用程序。这使得调试更容易,由于你没必要处理由其余库引发的堆栈跟踪。
若是我不打算进行深刻研究,我会打开正在开发的项目中的 /node_modules 文件夹,或者到 GitHub 仓库中去查看源码。这一般发生在我遇到一个 bug 或有趣的特性时。在 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 应用程序状态的库。在处理这些流行的库时,我首先搜索有关其实现的文章。在这个案例研究中,我找到了这篇文章。这是阅读源码的另外一个好处。研究阶段一般会引导你阅读这样的信息性文章,这些文章会提升你的思考与理解。
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 is a facade over connectAdvanced。没走多远,咱们就有了第一个学习的时刻:一个观察 facade 设计模式的机会。在文件末尾,咱们看到 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: Cannot destructure property 'connectHOC' of 'undefined' or 'null'.
。这是由于函数没有可供回调的默认参数。
注意:有关这方面的更多信息,您能够阅读 David Walsh 的文章。根据你对语言的了解,一些学习时刻可能看起来微不足道,所以最好将注意力放在您之前从未见过的事情上,或须要了解更多信息的事情上。
createConnect
在其函数内部并不执行任何操做。它只是返回一个名为 connect 的函数,也就是我在这里用到的:
export default connect(null, mapDispatchToProps)(MarketContainer)
复制代码
它须要四个参数,都是可选的,前三个参数都经过 match 函数来帮助根据参数是否存在以及它们的值类型来定义它们的行为。如今,由于提供给 match
的第二个参数是导入 connect
的三个函数之一,我必须决定要遵循哪一个线程。
若是那些参数是函数,代理函数被用来将第一个参数包装为 connect
,这是也一个学习的时刻。isPlainObject 用于检查普通对象或 warning 模块,它揭示了如何将调试器设置为中断全部异常。在匹配函数以后,咱们来看 connectHOC
,这个函数接受咱们的 React 组件并将它链接到 Redux。它是另外一个函数调用,返回 wrapWithConnect,该函数实际处理将组件链接到存储的操做。
看看 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 问答和这个在 GitHub 仓库中的 Redux issue,解释该代码如何处理诸如检查源自 iFrame 的对象这类状况。
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。