速读《现代 JavaScript 库开发》,快速掌握前端基础库开发逻辑

4,138 阅读26分钟

不想看文字?没有关系,博客提供视频版了!点击直接进入

这是一个专门讲解 前端库 开发的一本书,如果你想要开发一个属于自己的库,或者想要了解库开发的技术,那么这本书肯定可以帮助到你。

前言

Hello,各位小伙伴大家好,我是 Sunday

在现在的技术团队之中,我们普遍觉得,能够在 github 上开发和维护各种 的工程师都是 高手

E596B6E5-010C-4576-8D72-1AEC41AD7F8D.jpeg

8D156D07-D884-49D8-AEAA-8F8A4DA23563.png

64C05598-E42D-41CF-8EE9-93A711262B19.png

毕竟能够对企业项目提供底层支持的人,在技术领域总会存在一定的优越感。

那么开发一个库真的很难吗?其实不是的。

只要我们掌握了一定的 “方式和技巧” ,那么任何一名开发者都可以开发出属于自己的库。

《现代 JavaScript 库开发》是今年 1月(2023 年 1 月),最新发布的一本教我们如何开发一个 前端库 的书籍,作者是 颜海镜、候策

作者在书中提到: “每一个开发者都拥有两个世界:1. 业务世界、 2. 开源世界。”

很多开发者非常熟悉业务世界,但是对开源世界却非常陌生。其实开源世界并没有那么神秘,我们只需要花费一点时间,就可以掌握进入开源世界大门的钥匙,甚至可以在里面自由翱翔。

那么下面,就让我们进入到 《现代 JavaScript 库开发》之中,一起来看看,开源世界是什么样子。

正文

对于整本书中的内容而言,我们先把第十二章排除在外。这样的话,整本书中内容共分为三个大部分,共十一个章节。

  • 首先是第一大部分 原理:这一部分包含第一到第五章的内容,主要是讲解了我们应该如何 从零到一开发一个库。在这一块内容里面,作者从一个 深拷贝的clone 开始,为我们讲解了一个库如何 开发、构建、测试、开源以及维护 的详细流程,以及注意事项。这一块内容,也是我们这次所讲解的一个重点。
  • 第二大部分 技术:这一部分主要包含 第六、第七 两个章节,在这两个章节中,作者为我们介绍了 JavaScript 库设计的最佳实践 以及 安全的最佳实践。整本书中内容也开始 从原理讲解逐步转化为实战操作
  • 第三大部分 实战:包含了从 第八章到第十一章 四个章节,这里就是一个纯实战环节,里面提供了 九个 不同类型的基础库构建代码,比如 jslib-basetemplate.js 等等。里面会涉及到大量的代码,所以不在咱们这次的重点讲解范围之内。

最后就是 第十二章 ,这一章其实可以理解为 后置的前言。为什么这么说呢?因为在十二章中,作者提供了 知识全景图技术全景图 这两个东西:

image-20230301173545650.png

其中知识全景图,为我们描述了整本书中所涉及到的所有知识点。如图所示,我们可以知道整本书中所设计到的所有知识大体可以分为 4 类:

  • 最佳实践
  • 工程化
  • 技术方案
  • 开源

其中每一大类都分成了若干小类,而在每一个小类的前面都有一个数字,这个数字表示了 涉及到这一块知识点的章节。 比如:兼容性就分别在第二章和第六章中进行了涉猎。

image-20230301173703131.png

接下来是技术全景图,在咱们的 脑图 中,我为每一个技术库都附上了对应的链接,大家可以直接点击链接跳转。整本书中涉及到的技术库也是分成了四大块:

  • 构建
  • 环境语言
  • 测试
  • 工具链

这些技术库在本书中会多次出现,所以我在这里截个图,把它钉在右上角位置。大家可以对照着来去看。

第一章:从零开发一个 JavaScript 库

那么明确好了,整个书中一个大体的逻辑之后,接下来咱们就来看一下 《第一章:从零开发一个 JavaScript 库》。

中国有句古话,叫做万事开头难,所以如果大家想要开发一个自己的 JavaScript 库 的话,那么开始的时候是最难的。

为此,作者为我们指定了 4 个步骤,来告诉我们应该如何开始构建一个自己的库:

想法

首先第一步叫做 我有一个想法:我们回忆自己到目前为止的职场生涯,有没有出现过 “我有一个想法” 这样的场景。

B997FBB0-1AF1-4892-BD1B-49F7BF78B9B4-7721397.jpeg

我相信大多数的小伙伴应该都会时不时的冒出过各种各样的想法,只不过可能很多想法冒出来之后,又被放弃掉了。放弃掉的原因可能有很多,但是 一个好的想法就是一个好的开始

那么构建库同样如此,一个好的库一定是从一个好的想法开始的,所以当你有了一个好的想法之后,别着急放弃它,也许它可能帮助你走上职业的巅峰。

目标

当我们有了一个想法之后,那么我们其实就已经迈出了第一步了。接下来要做的就是 把这个想法,变成一个可以具体实现的目标。

这个目标并非要是 一成不变 的,它也并不一定要是一个多么宏大的目标。

它可以很小,也可以不断进行调整。就像 facebook 的创始人扎克伯克,在一开始创建 facebook(最初叫做 the facebook) 的时候,也只是想要做一个哈佛大学内部通讯系统。尤大(尤雨溪)在一开始构建 vue 的时候,也只是想做一个内部的小型视图渲染工具。

但是无论如何,这个目标一定要存在,因为只有有了目标之后,我们才可以有前进的动力。

设计

那么,当我们有了目标之后,接下来就是如何实现这个目标,也就是 设计

所谓的设计,指的是 把目标进行拆解多个可以实现的小目标,让我们可以一步一步的往前走。通过设计,我们可以把一个朦胧的大目标变成多个可以具体落地的小目标。

DF2BC2B2-4769-4A31-9353-285CFA07B7BC.png

这样的小目标,不光可以让我们对今后要做的事情认知更加清晰,还可以增强我们的信心,让我们不至于轻易放弃。

编码

万事具备之后,最后剩下的就是 编写代码

很多小伙伴,因为没有库开发经验,所以可能会 以写业务代码的逻辑去编写库代码。那么编写库代码有什么不同的地方,和注意事项呢?

这正是咱们后面要做的内容。

开发一个深拷贝的库

那么现在让我们从一个 深拷贝的库 做实验。

大家假想一下,现在我们有了一个 想法: 要做一个深拷贝的工具库。

同时我们为这个想法指定了 目标: 该库可以完成复杂数据类型的深拷贝。

然后我们针对该目标,进行了对应的 设计: 第一步要创建一个 clone 的函数,接收一个复杂数据类型作为参数,返回深拷贝之后的数据。

那么根据我们的设计,得出了如下代码:

function type(data) {
    return Object.prototype.toString.call(data).slice(8, -1).toLowerCase()
}
​
function clone(source) {
    const t = type(source);
    if (t !== 'object' && t !== 'array') {
        return source;
    }
​
    let target ;
​
    if (t === 'object') {
        target = {};
        for(let i in source) {
            if (source.hasOwnProperty(i)) {
                target[i] = clone(source[i]); // 注意这里
            }
        }
    } else {
        target = [];
        for(let i = 0; i < source.length; i++) {
            target[i] = clone(source[i]); // 注意这里
        }
    }
​
    return target;
}

那么现在,代码已经有了,并且代码是完全可用的。

但是大家要记住,现在我们要开发的是一个基础工具库,而这个库是要给其他的开发者进行使用的。

所以,如果代码仅仅只是如此的话,那么很快我们就会遇到一些问题:

  • A 使用了 CommonJS 模块,但是不知道该如何引用这个库
  • B 说这个库在 IE 浏览器上面会报错

这就是我们在实际库开发中,经常会遇到的 issue

image-20230302103945497.png

身为库的开发者,那么这样的 issue 是我们必须要解决的。但是具体应该怎么做呢?

第二章:构建

出现以上两个问题的原因,本质上是因为库的构建方式导致的。所以想要让我们的代码可以被开发者成功引用,那么我们必须要了解一定的构建方案。

作者在第二章中,描述了前端库构建必备的基础知识,让我们从 模块化 开始来看一下。

模块化

目前的前端模块化分为两大类:

  • UMDUMD 内部主要包含两种

    • AMD:异步模块定义,适用于 RequireJS 等模块加载器(用的已经不多了)
    • CJS:适用于 Node 环境和其他打包工具
  • ESM(ES Module):主要适用于浏览器端环境

通常情况下以上两大类的模块化方案,可以适用于大多数的模块化场景。

除此之外,还有一个叫做 iife 的,它主要用来构建自执行函数,可以适用于 <script> 标签 导入的形式。

打包体系

明确好了现在常用的几种前端模块化方案之后,那么下面咱们来看下如何把项目打包成对应的模块化代码,也就是 打包体系

现在常见的打包体系一共有三种 传统体系、Node 体系、工程化体系

对于这三种体系而言,作者给出了具体的对比图:

image-20230302113037875.png

Rollup

在实际的库开发中,最常用的就是 Rollup 进行打包。

作者在书中列举出了使用 Rollup 进行打包的流程,但是因为 Rollup 这种库存在升级的情况。所以为了避免在未来出现 Rollup 打包方式变化的问题,我在脑图中放了官网的文档链接,大家可以根据文档链接来查看最新的 Rollup 打包方式。

对于 Rollup 而言,除了可以完成基础的打包功能之外,还可以实现其他的功能。

比如,添加 bannertreeshaking、以及 配合 babel 实现 ES5 兼容

以上四种书中提到的注意事项,我都为大家提供了对应的官方文档链接。大家可以按需进行查看。

第二章总结

那么到目前为止,我们已经把一个想法,变成了一个可以被开发者使用的基础库了。

但是大家需要注意的是,像我们这种基础库,将来可能会在很多的项目中被使用,所以为了自身的声誉考虑,我们必须要能够保证我们代码的严格质量。

那么这就要求,我们的代码需要通过多维度的单元测试才可以。那么单元测试怎么做呢?

第三章:测试

作者在第三章中提供了测试方案,其中主要以单元测试为主。内容大体为 如何设计测试用例、如何验证测试覆盖率、如何在浏览器环境中进行测试、已经如何进行自动化测试 这四部分。咱们来看一下。

首先,咱们先来了解下单元测试。单元测试可以被分为两大类:

  1. TDD(测试驱动开发):所谓测试驱动开发,指的 是一种软件开发过程中的应用方法。 以 “戴两顶帽子” 作为开发方式:先戴上实现功能的帽子,在测试的辅助下,快速实现其功能;再戴上重构的帽子,在测试的保护下,通过去除冗余的代码,提高代码品质。

f_99ad9fd069179b329afbcb4bc5a26f4c.jpg 3. BDD(行为驱动开发):而行为驱动开发指的 是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协作。 BDD 的重点是 通过与利益相关者的讨论取得对预期的软件行为的清醒认识

而我们接下来要说的就是 BDD(行为驱动开发) 的方案。

要完成 BDD(行为驱动开发) 书中为我们介绍了两个依赖库 1. 测试库 Mocha@3.5.3 、2. 断言库 expect.js@0.3.1 ,为了避免库升级带来的不兼容性,我们在这里指定了对应的库版本。

Mocha 和 expect.js 基本使用流程,咱们这里不做过多介绍,官网和书上有详细的文档。

所以接下来咱们主要来看下保证质量的问题。作者在书中提到了测试质量保证的问题,主要分为 4 部分:

  1. 如何设计测试用例
  2. 如何验证测试覆盖率
  3. 如何在浏览器环境中进行测试
  4. 如何进行自动化测试

如何设计测试用例

测试用例的设计逻辑分为三部分:设计思路、编码方式、通过测试

首先咱们先来看设计思路。当我们去设计一个测试用例时,需要遵循 以参数为组进行测试 的方案。

比如下面这段代码:

image-20230302151626465.png

这段代码中包含三个参数,所以我们在设计测试用例的时候,就可以分成三组来进行测试,每个参数一组,在对一个参数进行测试时,保证其他参数无影响

image-20230302151735076.png

同样的道理,在我们之前的 clone 库 中,因为只包含一个参数,所以设计测试用例只需要分为一组即可:

image-20230302151829782.png

明确好了设计思路之后,下面就可以写对应的编码内容:

var expect = require("expect.js");
var clone = require("../src/index.js").clone;
​
describe("function clone", function () {
    describe("param data", function () {
        it("正确用例", function () {
            // 基本数据类型
            expect(clone("abc")).to.equal("abc");
​
            // 数组
            var arr = [1, [2]];
            var cloneArr = clone(arr);
            expect(cloneArr).not.to.equal(arr);
            expect(cloneArr).to.eql(arr);
​
            // 对象
            var obj = { a: { b: 1 } };
            var cloneObj = clone(obj);
            expect(cloneObj).not.to.equal(obj);
            expect(cloneObj).to.eql(obj);
        });
​
        it("边界值用例", function () {
            expect(clone(1)).to.equal(undefined);
​
            expect(clone(undefined)).to.equal(undefined);
​
            expect(clone(null)).to.equal(null);
        });
    });
});
​

这个代码是我从随书代码中截取下来的,给大家作为参考,代码内容咱们这里不做讨论。

有了代码之后,最后执行测试代码逻辑就可以了。

如何验证测试覆盖率

在设计完基础的测试用例之后,接下来我们还需要做一件非常重要的事情,那就是验证 单元测试覆盖率。因为对于单元测试而言,它的目的是 测试代码的运行情况。 所以我们必须要尽量保证 项目中的每行代码最好都被测试过一次,即覆盖率尽量去靠近100%

那么想要实现这一点,作者给我们推荐了一个工具 nyc(Istanbul) ,利用它可以帮助我们完成代码覆盖率测试。

image-20230302153341345.png

其中每个维度代表的含义:

  • File:文件
  • Stmts:语句覆盖率
  • Branch:分支覆盖率
  • Funcs:函数覆盖率
  • Line:行覆盖率
  • uncovered line:未覆盖的行号

如何在浏览器环境中进行测试

在前面的测试中主要是针对 Node.js 的环境进行的测试。但是很多时候我们可能需要借助浏览器环境来进行一些兼容性测试。

那么想要针对浏览器环境进行测试主要有两种方式:

  • 第一种是 模拟浏览器场景:想要模拟浏览器场景测试,那么可以通过 mocha-jsdom 来完成。

  • 第二章是 真实浏览器场景Mocha 支持在浏览器环境下运行,但是我们需要做一些事情才可以:

    • 首先,我们需要创建一个 index.html 文件,以方便在浏览器环境中运行
    • 其次,因为浏览器环境中不包含 CJSrequire 方法,所以我们需要手动加上这个 “垫子”,即:添加一个 require 函数,以防止出现 require is not defined 的错误

image-20230302160013743.png

如何进行自动化测试

到目前为止,其实我们已经拥有了一个完善的测试方案,但是美中不足的是 目前我们仍需人工在浏览器中打开并查看结果

而对于 Chrome 浏览器而言,它提供了 Chrome Headless 的特性,我们可以在 Node 中借助 Puppeteer 工具,来直接启动 Chrome Headless,以实现 自动化测试 的效果。从而无需人工在浏览器中打开并查看结果。

第三章总结

这一章,主要讲解了测试相关的逻辑,里面涉及到了很多测试相关的库或者工具。

针对于目前的前端测试场景而言,除了作者所提到的工具之外,还是一些其他的工具也有了很高的使用率,比如 单元测试库:JestUI 自动化测试框架:Cypress 等等......

对于没有测试经验的同学来说,这一章的内容可能会有些晦涩难懂。但是好处在于我们并不需要一次性的掌握作者所提到的所有内容,只需要关注自己当下的测试用例场景即可。

第四章:开源

代码写好了,测试通过了。那么接下来就是:发布自己的库,也就是 开源

目前开源所指的主要有两部分:

  • 将你的代码发布到 Github 上,以供开发者查阅和贡献源代码
  • 将打包后的代码发布到 npm 上,以供开发者使用

但是当我们决定把代码进行开源时,也有一些坑,一个不慎可能也会给我们带来一些损失。作者在本章中,主要通过 4 个方面,来为我们介绍了开源的注意事项。

协议

首先第一个就是 开源协议。目前市面上常用的开源协议主要有三种 MIT、BSD、Apache,这三协议的对比如下:

image-20230302161843102.png

目前这三种协议都有很多开源库在使用,下面是影响力比较大的项目以及它们的开源协议:

image-20230302162020484.png

对于我们的库而言,通常情况下选择 MIT 协议即可。

文档

除了协议之外,第二个是文档,文档主要分为 4 类:

  • 首先是 README.mdREADME.md 文档应该是大家最为熟悉的了。每一个 Githubnpm 的库中,对当前库的介绍部分,就是 README.md 文档
  • 其次是 TODO.md 待办清单:它表示未来要添加的新功能和已经完成的新功能添加
  • 然后是 变更日志:CHANGELOG.md :它表示库更新的记录,每个版本的发布日期以及对应变化
  • 最后就是 API 文档:它介绍了当前库的一些基本用法。如果你的库比较简单,那么也可以把 API 文档 合并在 README.md

发布

当我们准备好协议、文档之后,下面我们就可以发布项目到 githubnpm 了。

把项目发布到 github 的流程和我们平时提交 git 其实是一样的,如果大家不知道如何提交 git 的话,那么可以搜索一下对应的文档,咱们这里就不去多说了。

第二个是 npm ,把项目发布到 npm 的流程可能有很多小伙伴没有操作过,所以我在这里给大家放了一个 链接,在书中作者也对这个发布进行了介绍。

统计

整个开源最后一块是统计。当我们发布了一个库之后,其实都会非常关心开发者对它的使用情况。就像我发布了一个视频之后,也会经常来看看播放量一样。

因为我们在发布的时候,其实是发布到了 githubnpm 两个不同的平台,所以在查看统计的时候,也需要根据不同的平台来进行查看。

首先是 github,在 github 不存在使用率这样的情况,衡量一个库好不好的指标只有一个,那就是 starstar 才是王道。

而对于 npm 而言,则存在下载量的概念。但是对于 npm 来说,它默认的统计只会统计从 npm i 的下载量。但是在国内很多小伙伴是使用淘宝镜像(cnpm)来下载的,这部分流量它是统计不上去的。

所以说如果想要统计一个准确的下载数据,那么作者为我们提供了一种方式,就是 自定义统计

npm 为每个命令都提供了两个钩子 prepost

比如,当我们使用 npm install 时,就存在 preinstallpostinstall 两个钩子,分别表示 install 之前和 install 之后。

那么我们就可以依据这个钩子,来实现自定义统计:

  1. postinstall 钩子中,指定执行文件

image-20230302170921509.png 2. 在 postinstall.js 中通过 axios 完成统计

image-20230302170946225.png 那么按照如上方式,当开发者 npm install 库 之后,我们就可以手动记录一次下载数据。

第四章总结

在这一章中,作者主要讲解了我们去开源项目时的一些注意事项。整体算是比较简单。

那么到现在为止,我们就已经完成了 开发、构建、测试、发布 这四步流程了,也就是说,现在我们已经拥有了一个属于自己的基础库。

但是开源库发布成功,并不代表就万事大吉了。库的开源并不是一个一劳永逸的事情,它需要我们持续的迭代和维护。

所以接下来我们就来看看,我们应该如何维护一个开源库。

第五章:维护

整个库的维护,作者从 4 个方面进行了阐述。

社区协作

首先是社区协作,这里的社区主要指的就是 github。而在 github 上的社区协作主要就是分为 3 种:

  • issue
  • 代码贡献
  • 捐赠

首先是 issue ,这个应该是大多数开发者最了解的一个东西了。它是 当前库的问题集合,大体可以分为三类 求助类:help wanted、故障类:bug、建议类:enhancement

身为库的开发者,对于 issue 必须要及时处理才可以。

当你的库在行业内拥有了一定的影响力之后,那么可能会有很多人为你的库贡献代码。贡献代码的人员可以大致被分为两类:非库开发人员库开发人员。 非库开发人员主要通过 Fork + Pull Request 的方式进行代码贡献。而库开发人员则不需要那么麻烦。

最后是捐赠,这个一般很少,大家最好不要寄予过高的希望。

编码规范

在刚才我们说过,当你的库拥有一定的影响力之后,可能会有很多人为你的库进行代码贡献。

那么一旦涉及到多人的合作开发,那么我们就需要控制编码规范。书中为我们提到了很多编码规范的处理方案,其中会涉及到大量的代码和逻辑。所以我们在这里就不去说了。

在这里顺道给我在慕课网上的课程打一个广告,我在慕课网上发布过 全新升级,基于Vue3新标准,打造后台综合解决方案 这样一门课程,在这门课程中也详细了编码规范的问题,大家可以根据需要进行查看。

持续集成

完全依靠 Git hook 进行编码规范处理,有的时候可能并没有那么可靠。所以作者也在书中提到了持续集成的概念。

目前在开源社区中常用的持续集成工具主要有三款 Github Actions、CircleCI、Travis CI。这三款都可以满足我们的开源需求,大家可以自行选择。

分支处理

最后就是 git 分支处理。

作者根据功能,把分支分为三类,分别是:

  • 主分支:稳定、没有 bug 的代码,并保证随时可以发布的状态
  • 功能分支:新功能开发时
  • 故障分支:出现 bug

除此之外,还有两种特殊情况:

  • 第一种是 Pull request:它表示其他人给开源项目提交的代码。 在 Github 上会提示我们如何进行操作,大多数时候确认无误,直接合并即可。
  • 第二种是 创建标签与历史:这个主要针对于发布新版本,或者特殊版本时的场景。主要用来记录当前的版本意义。

第五章总结

那么到这里为止,我们就已经讲完了从零开发一个库的流程以及注意事项,其中涉及到 开发、构建、测试、发布、维护 的一整套流程。

那么掌握到这里为止,其实大家就已经可以利用上述知识完成一个基础库的开发了。

但是如果你想要做的更好,并且可以及时规避一些风险的话,那么可以继续往下看。

第六章:设计更好的JavaScript库

从这一章开始,我们将会去讲解 开源库中的注意事项与风险点,以帮助大家更好地规避风险,从而设计更好的JavaScript 库,取得最大收益。

那么首先咱们来看一下,如何构建出一个更好地 JS 库。想要构建出一个更好的 JS 库,那么作者给出了 4 个方向 函数化、健壮性、兼容性、TS ,咱们一个一个来说。

函数很重要

其实我们在之前的时候说过很多次,函数在 JS 中被称之为第一公民。那么想要构建出一个可维护性更强的 JS 库,咱们就需要好好利用函数。

作者从两个维度对函数进行了介绍。

  • 首先是 命名:函数的命名以 见名知意 为第一要素,尽量不要使用 “少为人知”的缩写,如果不知道如何为函数命名,可以试用下 CodeIf (速度可能会有点慢)
  • 其次是 参数:作者在书中提到,参数的个数最好不要超过三个。如果你确实需要很多参数的话,那么可以使用 options 的模式,这种方式我们之前在 JavaScript 语言精粹 中也提到过

提高健壮性

开源库和业务代码的一个很大的不同在于 开源库会被很多人使用,会在各种各样业务场景中运行。根据使用者的不同,开源库的代码会比普通的业务代码存在更多的健壮性问题。

这个健壮性指的是:程序在遇到规范以外的输入,错误和异常时,仍能正常运行。

在这一小节中,作者分别从 参数防御、副作用处理、异常捕获 三个方面进行了描述。

参数防御

所谓参数防御指的是 使用者,未按照约定,传入了预料之外的参数。 那么在这种情况下,作为开源库的开发者就要通过 参数防御 的形式,保证程序不会出现崩溃的问题。

副作用处理

所谓副作用指的是:会引起副作用的代码。比如:

let name = '张三'
function setName (newName) {
  name = newName
}

在这段代码中,setName 方法的触发就会修改 全局变量 name 的值。此时的 setName 就被叫做会引起副作用的代码,也就是一个 副作用函数

而对于库中的代码而言,如果会引起大量的副作用,那么就会给使用者带来很大的麻烦。

异常捕获

程序在运行的过程中出现异常是非常正常的事情。但是这些异常的报错需要给用户明确的提示才可以。

浏览器兼容

再往后是浏览器兼容的问题。作者在这里给出了一个推荐的浏览器兼容目标,这个兼容目标是非常低的。大家可以根据自己的情况来选择。

image-20230303120922759.png

TypeScript

最后就是 TypeScript 。在现在的库开发中,个人建议大家都应该使用 TypeScript,因为它可以在维护时,带来更高的可维护性。

第七章:安全防护

第七章主要介绍了安全防护的问题。这里的安全防护指的是 库代码的安全性

作者在这一章中,举了一个例子:

2019 年的时候 Lodash 库爆出过一个原型污染漏洞,叫做 CEV-2019-10744

这个漏洞主要存在于 LodashdefaultsDeep 方法中,这个方法会将第二个参数的可枚举属性合并到第一个参数的属性上。

但是如果我们按照这样的方式传递参数的话,就会出现原型污染的问题

image-20230303121818400.png 而要想理解什么是原型污染,大家需要首先搞清楚 JS 中原型链的概念,我在这里截了一张图:

image-20230303121913215.png

这张图描述了 JS 中原型链的逻辑。

由此可知一旦我们修改了顶层的原型对象,那么就会响应到所有的底层对象实例。

而如果想要避免这种问题的出现,那么作者给出了 4 种方案:

  1. 利用 Object.freeze 冻结 Object.prototype
  2. 规避不安全的递归
  3. 利用 Object.create(null) 规避,因为这样实例不会连接到 Object.prototype.
  4. 利用 Map 代替 {},从而避免原型链接的问题

除此之外,作者还在本章中提到了 依赖的安全性问题 的问题,所谓的依赖安全性指的是 你的库所依赖的其他库,是否是安全的。

作者给我们提供了 4 个维度,来尽量保证依赖安全性:

  • 首先第一点是:尽量选择 star 多issue 少,且处理及时 的库,进行依赖。
  • 第二是:对库依赖的区分,主要说明了 dependencies:生产与开发环境、devDependencies:开发环境、peerDependencies:库依赖于其他的库 三者之间的区别
  • 第三是版本区分:依赖库版本号和前缀的概念
  • 最后是一个安全检查的命令 安全检查:npm audit

最后在本章中,作者还介绍了一些 防护意外 的处理逻辑,所谓的意外防护指的是 避免使用者修改库内部的属性,大体分为三类:

  • 不相关的功能不应该对外暴露
  • 最小的参数设计
  • 属性冻结

第七章总结

那么到这里为止,库开发的注意事项与风险点,咱们就说的差不多了。

第八章 - 第十一章

在第八章到第十一章的内容中,作者主要介绍了 9 个基础库的实现,所以这 4 个章节可以被叫做 实战 环节。

实战环节中,涉及到了大量的代码内容,其中理论相关的东西就比较少了。

如果大家对这一块的代码比较感兴趣的话,那么可以看一下具体书中所写的内容。

总结

OK,那么到这里咱们整个的《现代 JavaScript 库开发》就已经全部说完了。

《现代 JavaScript 库开发》 是一个非常适合想要开发开源库的同学进行学习的书籍。里面涉及到的很多内容,会让刚进入该领域的同学少走很多的弯路。