通过阅读源码,提高你的 JS 水平
栏目:网络资讯 发布时间:2025-02-12
通过阅读源码,提高你的 JS 水平(给前端大全加星标,提升前端技能)英文:Carl Mungazi  译文:MarchYuanxhttps://juejin.i

通过阅读源码,提高你的 JS 水平

(给前端大全加星标,提升前端技能

英文:Carl Mungazi  译文:MarchYuanx

https://juejin.im/post/5d3c56c26fb9a07efd475414

当你还处于编程生涯的初期阶段时,深入研究开源库和框架的源代码可能是一项艰巨的任务。在本文中,Carl Mungazi 分享了他如何克服恐惧,并开始用源码来提高他的知识水平和专业技能。他还使用了 Redux 来演示他如何解构一个代码库。

你还记得你第一次深入研究你常用的库或框架的源码时的情景吗?对我来说,这一刻发生在三年前我作为前端开发者的第一份工作中。

当时我们刚刚完成了用于创建网络学习课程的内部遗留框架的重构。在重构开始时,我们花时间研究了许多不同的解决方案,包括 Mithril、Inferno、Angular、React、Aurelia、Vue 和 Polymer。那时我仅仅只是个小萌新(我刚从新闻工作转向 web 开发),我记得我对每个框架的复杂性感到恐惧,不理解它们是如何工作的。

随着对我们所选择的 Mithril 框架研究的深入,我对它的理解也逐渐加深了。从那以后,我花了很多时间深入钻研那些在工作或个人项目中日常使用的库的内部结构,这显著地提升了我对 JavaScript —— 以及通用编程思想 —— 的了解。在这篇文章中,我将分享一些方法给你,你可以使用自己喜欢的库或框架,并将其作为学习工具。

我要介绍的第一个源码阅读示例是 Mithril 的 hyperscript 函数。

阅读源码的好处

阅读源代码的一个主要好处是可以学到很多东西。在我第一次读 Mithril 代码库时,我对虚拟 DOM 的概念还很模糊。当我读完后,我了解到虚拟 DOM 是一种技术,它创建一个对象树,用于描述用户界面的外观。然后使用 DOM APIs(如 document.createElement)将对象树转换为 DOM 元素。通过创建描述用户界面的更新状态的新对象树,然后将其与旧对象树进行比较来执行更新。

我在各种文章和教程中已经阅读了所有这些内容,虽然这很有帮助,但对我来说,能够在我们提供的应用程序的环境中观察到它工作是非常有启发性的。它还教会我在比较不同框架时应该考虑哪些因素。例如,我现在知道要考虑这样的问题,“每个框架执行更新的方式如何影响性能和用户体验?”,而不是只看框架在 GitHub 上 star 的数量。

另一个好处是你对优秀的程序架构的理解和鉴赏能力提升了。虽然大多数开源项目的存储库通常遵循相同的结构,但每个项目都包含差异。Mithril 的结构非常简单,如果你熟悉它的 API,你可以根据文件夹名称推测出其中的代码的功能,如 render、router 和 request。另一方面,React 的结构反映了它的新架构。维护人员将负责 UI 更新的模块(react concerner)与负责呈现 DOM 元素的模块(react dom)分开。

这样做的好处之一是,开发人员现在更容易通过挂进 react-reconciler 包来编写自己的自定义渲染器。我最近研究过的模块打包工具 Parcel 也有像 React 这样的 packages 文件夹。主模块名为 parcel-bundler,它包含负责创建包、启动热模块服务器和命令行工具的代码。

不久之后,你所阅读的源码将引导你找到 JavaScript 规范

另一个好处 —— 令我感到惊讶的是 —— 你可以更轻松地阅读定义语言如何工作的官方 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 没有给我一个满意的解释,我就会阅读源码。这样做让我得到了这些有趣的发现:

  • Express 依赖于两个模块,两个模块都合并对象,但以非常不同的方式进行合并。merge-descriptors 只添加直接在源对象上直接找到的属性,它还合并了不可枚举的属性,而 utils-merge 只迭代对象的可枚举属性以及在其原型链中找到的属性。merge-descriptors 使用 Object.getOwnPropertyNames() 和 Object.getOwnPropertyDescriptor() 而 utils-merge 使用 for..in;

  • setprototypeof 模块提供了一种设置实例化对象原型的跨平台方式;

  • escape-html 是一个有 78 行代码的模块,用于转义一系列内容,可以在 HTML 内容中进行插值。

虽然这些发现不可能立即有用,但是对库或框架所使用的依赖关系有一个大致的了解是有用的。

在调试前端代码时,浏览器的调试工具是你最好的朋友。除此之外,它们允许你随时停止程序并检查其状态,跳过函数的执行或进入或退出程序。有时这不能立即生效,因为代码已经压缩。我倾向于将它解压并将解压的代码复制到 /node_modules 文件夹中的对应文件中。

像处理任何其他应用程序一样处理调试。形成一个假设,然后测试它。

研究案例:Redux 的 Connect 函数

React-Redux 是一个用于管理 React 应用程序状态的库。在处理这些流行的库时,我首先搜索有关其实现的文章。在这个案例研究中,我找到了这篇文章。这是阅读源码的另一个好处。研究阶段通常会引导你阅读这样的信息性文章,这些文章会提高你的思考与理解。

connect 是一个将 React 组件连接到应用程序的 Redux 存储的 React-Redux 函数。怎么连?好的,根据文档,它执行以下操作:

“…返回一个新的连接的组件类,它包装您传入的组件。

看完之后,我会问下列问题:

  • 我是否知道哪些模式或概念,其函数能够接受一个输入并将输入封装、加上附加功能再返回输出?

  • 如果我知道这样的模式,我如何根据文档中给出的解释来实现它?

通常,下一步是创建一个使用 connect 的非常基础的示例应用程序。但是,在这种情况下,我选择使用我们在 limejump 上构建的新的 React 应用程序,因为我希望在最终要进入生产环境的应用程序的上下文环境中理解 connect。

我关注的组件看起来像这样:

classMarketContainerextendsComponent{// 简洁起见,省略代码(code omitted for brevity)}const mapDispatchToProps = dispatch =>{return{
updateSummary
:(summary, start, today)=> dispatch(updateSummary(summary, start, today))}}exportdefault connect(null, mapDispatchToProps)(MarketContainer);

它是一个容器组件,包裹着四个较小的连接的组件。在导出 connect 方法的文件中,你首先看到的是这个注释:connect is a facade over connectAdvanced。没走多远,我们就有了第一个学习的时刻:一个观察 facade 设计模式的机会。在文件末尾,我们看到 connect 导出了对名为 createConnect 的函数的调用。它的参数是一组默认值,这些默认值被这样解构:

exportfunction createConnect({
connectHOC
= connectAdvanced,
mapStateToPropsFactories
= defaultMapStateToPropsFactories,
mapDispatchToPropsFactories
= defaultMapDispatchToPropsFactories,
mergePropsFactories
= defaultMergePropsFactories,
selectorFactory
= defaultSelectorFactory
}={})

同样,我们遇到了另一个学习时刻:导出调用函数和解构默认函数参数。解构部分是一个学习时刻,因为它的代码编写如下:

exportfunction 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 的函数,也就是我在这里用到的:

exportdefault 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 的对象这类情况。

其它的阅读源码的参考链接

  • “How To Reverse Engineer Frameworks,” Max Koretskyi, Medium

  • “How To Read Code,” Aria Stewart, GitHub

推荐阅读

(点击标题可跳转阅读)

详解 ES 2018 新特性

你必须懂的前端性能优化

前端进阶必须知道的正则表达式知识

觉得本文对你有帮助?请分享给更多人

关注「前端大全」加星标,提升前端技能

好文章,我在看❤️


# 第一个  # 更多信息  # 你对  # 并将  # 这样做  # 你还  # 在这个  # 我会  # 连接到  # 我想  # 更容易  # 这是  # 它是  # 通过阅读源码  # 在我  # 的是  # 我对  # 你可以  # 我在  # 应用程序  # 是一个  # 软件教程  # 水平  # JS  # 提高你的 


相关文章: 通达信主力资金源码:看到“紫色锁仓”拿住别动,主升开始了!  三部精品短剧齐开机  熬夜必追短剧《循环热恋》完整版1-100集/大结局/合集  超好看!热门短剧丨大结局《姐是病娇太阳花,谁见都要笑哈哈》全集在线看  不用插件,CAD出图与批量打印也很香!  2026年1月民生薅羊毛必读:全民生活APP领出行立减包,月月刷达标狂撒立减金!  紧急通知!这款安卓“隐藏”APP,竟能免费看全网*?内部已流出…  贱人工具箱【短小精悍免费插件】  短剧《爱在等风不等你》1-74集完整版(大结局/后续/全集)  精彩短剧《朝阳似我》(合集/完整版/大结局)1-106集  知函博士|计算机软件源代码可否作为商业秘密获得保护?  插件满足不了OpenAI,他们还要搞“App Store”  爆款预定!精品短剧《元宇宙·恋语》象山开机  远控软件AnyDesk确认遭受网络攻击,源代码和代码签名证书被盗  短剧《月满故里》1-62集完整版(大结局/后续/全集)  App免Root加载Xposed插件工具Xpatch源码解析(二)  详解Socket状态机源码  又封中国APP,这次号称“永久”!  软件、插件大全·2025 版  久等了!「PS/AE/PR全套插件合集」一键安装,极度舒服!  日语APP猩听译|NHK、CRI、ANN、TED日语、CATTI 双语+音频 听译朗读+视频学习全搞定!  iOS 11.3上的这个细节 让你更新APP更方便  短剧《奇葩亲戚当月嫂》完整版  【功能截图】CAD&CASS实用插件合集含400多个功能,持续更新中…  [S463]摄影师必装磨皮插件2019升级版,附送独家磨皮参数  很好的热门短剧《欲吻桑枝》1-74(完整版/后续/大结局)没删减版  【软件律师】客户欠款,开发方能否拒绝交付源代码?知识产权归谁?——定制开发中的权利博弈与风险防范  短剧完整版【明月照晚秋(全集)】全集大结局,在线看(全剧后续/完整版)  超实用!这两款APP是小编的压箱底神器,功能强大到离谱!  逆袭!短剧【疾驰人生】完整版  炸裂!短剧大结局《放手的风筝线》完整版/全剧终/合集  精彩短剧《寒门》1-85集(合集/完整版/大结局)  恶意软件团队解散,10万美元拍卖源代码  短剧《放手的风筝线》完整版  短剧完整版《长生阙》大结局全剧终  热播短剧《原来你也是假千金》完整版1-88集(合集/大合集)  超好看!热门短剧丨大结局《霍太太她听得见心声》全集在线看  App 监控聊天记录实锤?iOS 14 立功了…  iOS 16.1 B1 发布,新 App 来了~  警惕!这5类APP正在偷偷读取你的身份证信息  短剧《家里家外2》完整版  百度网盘资源免费领取 软件大全(一)  幼小衔接App天花板!太绝了!妈妈们快看过来!  超好看!热门短剧丨大结局《权臣的软肋》全集在线看  Grasshopper常用插件汇总  2025看剧App最新版本iOS安卓双端可用资源全面  1月2日必追短剧预告(高质量精选)  熬夜必追短剧《暖巷人家》完整版1-73集/大结局/合集  国浩律师代理的腾讯诉微源码“数据精灵”软件不正当竞争纠纷案入选最高人民法院十大互联网典型案例  热门短剧《消失的拳王》完整版1-92集(完整版/后续/大结局)后续剧情 


相关栏目: 【 网络营销44070 】 【 网络推广122852 】 【 网络优化116010 】 【 网址导航102054 】 【 网络技术82194 】 【 网络资讯43554