您还记得您第一次深入研究经常使用的库或框架的源代码吗?对我来说,那一刻是三年前我担任前端开发人员的第一份工作。
我们刚刚完成了用于创建电子学习课程的内部遗留框架的重写。在重写的开始,我们花了很多时间研究许多不同的解决方案,包括Mithril,Inferno,Angular,React,Aurelia,Vue和Polymer。因为我还是一个初学者(我刚刚从新闻业转向Web开发),所以我记得每个框架的复杂性都让我感到恐惧,并且不了解每个框架的工作原理。
当我开始更深入地研究我们选择的框架Mithril时,我的理解就增强了。从那时起,我花了很多时间来深入研究每天在工作中或在自己的项目中使用的库的精髓,这极大地帮助了我对JavaScript(以及一般而言,编程)的了解。在本文中,我将分享一些您喜欢的库或框架并将其用作教育工具的方法。
阅读源代码的主要好处之一就是可以学到很多东西。当我第一次研究Mithril的代码库时,我对虚拟DOM是什么还是个模糊的想法。完成后,我不知道虚拟DOM是一种技术,它涉及创建对象树来描述用户界面的外观。然后使用DOM API(例如)将那棵树变成DOM元素document.createElement
。通过创建描述用户界面未来状态的新树,然后将其与旧树中的对象进行比较来执行更新。
我已经在各种文章和教程中阅读了所有这些内容,尽管它很有帮助,但是能够在我们已经交付的应用程序的上下文中工作时对其进行观察对我来说非常具有启发性。它还教会了我在比较不同框架时要问哪些问题。例如,我现在不去看GitHub上的明星,而是问一些问题,例如“每个框架执行更新的方式如何影响性能和用户体验?”
另一个好处是,您对好的应用程序体系结构的欣赏和了解会增加。尽管大多数开放源代码项目的存储库通常都采用相同的结构,但每个项目都有不同之处。Mithril的结构相当平坦,如果您熟悉它的API,则可以对render
,router
和等文件夹中的代码进行有根据的猜测request
。另一方面,React的结构反映了它的新架构。维护人员已将负责UI更新react-reconciler
的模块()与负责呈现DOM元素的模块()分开react-dom
。
这样做的好处之一是,开发人员现在可以通过挂接到程序包中来更轻松地编写自己的自定义渲染器react-reconciler
。我最近研究的模块打包器Parcel也有一个packages
类似React 的文件夹。密钥模块被命名parcel-bundler
,并且包含负责创建包,旋转热模块服务器和命令行工具的代码。
另一个好处(使我感到惊讶)是,您可以更轻松地阅读定义语言工作方式的官方JavaScript规范。我第一次读规范是在我调查的区别throw Error
和throw new Error
(扰流警报-有没有)。我对此进行了研究,因为我注意到Mithril throw Error
在实现其m
功能时使用了它,我想知道使用它是否有好处throw new Error
。从那以后,我还了解到,逻辑运算符&&
和||
不一定返回布尔值,发现该规则支配如何==
平等的经营者强制转换价值和理由 Object.prototype.toString.call({})
退货'[object Object]'
。
处理源代码有很多方法。我发现最简单的开始方法是从所选的库中选择一个方法,并记录调用时发生的情况。不要记录每个步骤,而是尝试确定其总体流程和结构。
我最近做了这个工作ReactDOM.render
,因此了解了很多有关React Fiber以及实现它的一些原因。幸运的是,由于React是一个流行的框架,我遇到了许多其他开发人员针对同一问题撰写的文章,这加快了整个过程。
这次深入学习还向我介绍了合作调度的概念,window.requestIdleCallback
方法和链接列表的实际示例(React通过将更新放入优先更新的链接列表作为队列来处理更新)。这样做时,建议使用该库创建一个非常基本的应用程序。这使调试时更容易,因为您不必处理其他库引起的堆栈跟踪。
如果我不进行深入审查,则将/node_modules
在我正在处理的项目中打开该文件夹,否则将转到GitHub存储库。当我遇到错误或有趣的功能时,通常会发生这种情况。在GitHub上阅读代码时,请确保您正在阅读最新版本。您可以通过单击用于更改分支的按钮并选择“标签”,从带有最新版本标签的提交中查看代码。库和框架永远在发生变化,因此您不想了解下一版本中可能删除的内容。
另一种较少涉及的读取源代码的方式是我所谓的“ cursory glance”方法。在开始阅读代码的早期,我安装了express.js,打开了它的/node_modules
文件夹并检查了它的依赖项。如果README
不能为我提供满意的解释,请阅读资料。这样做使我得出以下有趣的发现:
Express依赖于两个模块,这两个模块都合并对象,但是合并方式却非常不同。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存储。怎么样?好吧,根据docs,它执行以下操作:
“ ...返回一个新的,已连接的组件类,该类包装您传入的组件。”
阅读此书后,我会问以下问题:
我是否知道任何模式或概念,其中函数接受输入然后返回包含附加功能的相同输入?
如果我知道任何这样的模式,我将如何根据文档中给出的说明实施此模式?
通常,下一步是创建一个使用的非常基本的示例应用程序connect
。但是,这次我选择使用我们在Limejump上构建的新React应用程序,因为我想了解connect
最终将进入生产环境的应用程序的上下文。
我关注的组件如下所示:
它是一个容器组件,其中包装了四个较小的连接组件。此注释是您在导出方法文件中遇到的第一件事connect
:connect是connectAdvanced上的外观。不用走远,我们就有了第一个学习时刻:一个观察实际中的外墙设计模式的机会。在文件末尾,我们看到connect
导出了一个名为的函数的调用createConnect
。它的参数是一堆默认值,这些默认值已按如下方式进行结构分解:
再一次,我们遇到了另一个学习时刻:导出调用的函数并解构默认函数参数。解构部分是一个学习时刻,因为代码是这样编写的:
可能会导致此错误,Uncaught TypeError: Cannot destructure property 'connectHOC' of 'undefined' or 'null'.
这是因为该函数没有默认参数可以使用。
注意:有关此的更多信息,您可以阅读David Walsh的文章。取决于您对语言的了解,有些学习时机可能显得微不足道,因此最好集中精力于以前从未见过的事情或需要学习更多的知识。
createConnect
本身在其功能主体中不执行任何操作。它返回一个名为的函数connect
,我在这里使用了一个函数:
它包含四个参数,所有参数都是可选的,并且前三个参数每个都通过一个match
函数,该函数根据参数是否存在及其值类型来帮助定义其行为。现在,由于提供给第二个参数match
是导入到中的三个函数之一connect
,因此我必须决定要遵循哪个线程。
代理函数用于包装第一个参数(包括connect
那些参数是否是函数),isPlainObject
用于检查纯对象或warning
模块的实用程序有很多学习时机,它们揭示了如何设置调试器以在所有异常时都中断。在match函数之后,我们来到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
是这段代码:
Google的一些搜索将我带到了这个 StackOverflow线程和Redux 问题,解释了该代码如何处理诸如检查源自iFrame的对象之类的情况。