[译] 经过阅读代码来提高你的 JavaScript 水平

写在前面的话

原文地址: Improve Your JavaScript Knowledge By Reading Source Codejavascript

为了符合阅读习惯,本篇采用意译。html

前言:在你的早期程序生涯中,弄清楚开源库和框架的源码是一个不小的挑战。这篇文章中,做者卡尔给咱们分享了他是如何客服恐惧开始阅读源码的,这对提升知识水平有帮助。他也用 Redux 做为一个例子来展现如何攻克一个库。前端

你还记得第一次去深度阅读源码时的状况吗?对我而言,是三年前的事了。java

当时咱们已经完成了内部遗留框架的重写。在重写之初,咱们花时间去调查了多种解决方案包括 Mithril,Inferno, Angular, React, Aurelia, Vue 和 Polymer。做为一个刚刚重新闻行业转行的我,我还记得被每种框架的复杂性支配的恐惧,同时不理解它们是如何工做的。node

当我开始研究选择的框架时,个人理解也就在增加了。从那时起,我深刻研究那些在工做或我的项目中使用的库,在数个小时以内,个人 Javascript 知识和编程技巧也都受益不浅。在这片文章中,我会分享几种方式,你能够找一个你喜欢的库或者框架,而后使用它做为学习工具。react

阅读源码的好处

学习源码的主要好处是能学到不少东西。当我第一次看到Mithril的代码库时,我对虚拟DOM的含义有一个模糊的概念。 当我完成时,我知道虚拟DOM是一种技术,它涉及建立描述用户界面应该是什么样的对象树。 而后使用DOM API(例如document.createElement)将该树转换为DOM元素。 经过建立描述用户界面的将来状态的新树,而后将其与旧树中的对象进行比较来执行更新。git

我在各类文章和教程中已经阅读了全部这些内容,虽然它颇有帮助,可是在咱们发布的应用程序的上下文中可以观察它对我来讲很是有启发性。 它还告诉我在比较不一样的框架时要问哪些问题。而不是盯着 GitHub 关注量,好比 “每一个框架执行更新的方式如何影响性能和用户体验?”等问题。github

另外一个好处是增长对良好应用程序架构的理解和理解。 虽然大多数开源项目一般遵循相同的结构,但每一个项目都包含差别。 Mithril 的结构很是平坦,若是你熟悉它的API,你能够对文件夹中的代码进行有根据的猜想,例如渲染,路由器和请求。 另外一方面,React的结构反映了它的新架构。 维护者将负责UI更新的模块(react-reconciler)与负责呈现DOM元素的模块(react-dom)分开。express

这样作的好处之一是,如今开发人员能够经过挂钩 react-reconciler 软件包来编写本身的自定义渲染器。 我最近研究过的模块打包工具 Parcel, 也有像React这样的包文件夹。 关键模块名命名为 parcel-bundler,它包含负责建立捆绑包,启动热模块服务器和命令行工具的代码。编程

另外一个好处 - 令我感到惊讶的是 - 能够更轻松地阅读官方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存储库。当我遇到错误或有趣的功能时,一般会发生这种状况。在GitHub上阅读代码时,请确保正在阅读最新版本。能够经过单击用于更改分支的按钮并选择“tags”来查看具备最新版本标记的提交中的代码。库和框架永远在进行更改,所以不须要了解可能在下一版本中删除的内容。

另外一种不那么简单的阅读源代码的方式是我喜欢称之为“粗略一瞥”的方法。在我开始阅读代码的早期,我安装了express.js,打开了它的/ node_modules文件夹并完成了它的依赖项。若是自述文件没有给我一个使人满意的解释,我会阅读源代码。这样作让我获得了如下有趣的发现:

  • Express依赖于两个模块,这两个模块合以很是不一样的方式并对象。 merge-descriptors只添加直接在原对象上直接找到的属性,它还合并了不可枚举的属性,而utils-merge 只迭代对象的可枚举属性以及在其原型链中找到的属性。 merge-descriptors使用 Object.getOwnPropertyNames()Object.getOwnPropertyDescriptor(),而utils-merge使用for..in;
  • setprototypeof 模块提供了一种设置实例化对象原型的跨平台方式;
  • escape-html是一个78行模块,用于转义一串内容。能够在HTML内容中进行插值。

虽然这些发现不太可能当即有用,可是库或框架使用的依赖关系有一个大体的了解是有用的。

在调试前端代码时,浏览器的调试工具是最好的朋友。除此以外,它们容许您随时中止程序并检查其状态,跳过函数的执行或进入或退出程序。有时这不能当即生效,由于代码被压缩。我倾向于将其解压并将解压的代码复制到 /node_modules文件夹中的相关文件中。

案例研究:Redux 的 Connent Function

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 是一个关于 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: Cannot destructure property 'connectHOC' of 'undefined' or 'null'.
复制代码

这是由于方法没有默认参数去回调。

注意:有关这方面的更多信息,能够阅读David Walsh的文章。 基于对语言理解的不一样,一些学习的机会可能看起来微不足道,所以最好将注意力放在之前从未见过或须要了解更多信息的事情上。

createConnect自己在其函数体中不执行任何操做。它返回一个名为 connect 的函数,在这里以下使用:

export default connect(null, mapDispatchToProps)(MarketContainer)
复制代码

它须要四个可选参数,前三个参数都经过一个匹配函数来根据参数是否存在及其值类型来定义它们的行为。由于如今提供匹配的第二个参数是导入 connect 的三个函数之一,我必须决定要遵循哪一个线程。

若是这些参数是函数,是检查普通对象的实用程序或者是设置断点来调试暴露错误的警告模块,那么如今就是学习代理函数的时候,这个函数包裹了第一个参数给 connect。在匹配函数以后,来看看 connectHOC,这个函数接受咱们的 React 组件并将它链接到 Redux。它是另外一个函数调用,它返回 wrapWithConnect,它实际上处理将组件链接到存储的函数。

看看 connectHOC 的实现,我能够理解为何它须要链接来隐藏它的实现细节。它是 React-Redux 的核心,包含不须要经过链接公开的逻辑。即便这里的探讨即将结束,若是继续,这将是查阅以前发现的参考资料的最佳时机,由于它包含对代码库的很是详细的解释。

总结

读取源代码起初很困难,但与任何事情同样,随着时间的推移变得更容易。咱们的目标不是理解一切,而是要得到不一样的视角和新知识。关键是要对整个过程进行深思熟虑,并对全部事情充满好奇。

举个例子,我发现 isPlainObject 函数颇有趣,由于它使用了if(typeof obj!=='object'|| obj === null) return false以确保给定的参数是普通对象。当我第一次阅读它的实现时,我想知道为何它没有使用 Object.prototype.toString.call(opts)!=='[object Object]',这是用更少的代码并区分对象和对象子类型,好比 Date 对象。可是,阅读下一行显示,例如,在使用 connect 的开发人员返回 Date 对象的极不可能的事件中,这将由 Object.getPrototypeOf(obj)=== null 检查处理。

isPlainObject 的另外一个吸引人的是这段代码:

whileObject.getPrototypeOf(baseProto)!== null){
 baseProto = Object.getPrototypeOf(baseProto)
}
复制代码

谷歌一下能够找到相关话题,StackOverflow下的Redux问题,解释该代码如何检查源自iFrame的对象。

参考

pic
相关文章
相关标签/搜索