【源码阅读】omit.js|omit的实现与库的快速搭建
本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
这是源码共读的第36期,链接:juejin.cn/post/711878…
单词 omit 有「从中剔除,不包含」的释义,而 omit 工具函数的工作是「从对象中剔除指定属性作为新对象返回」,举一个例子
// 从哆啦A梦的信息中剔除掉「物种」和「生日」信息
const info = { name: "Doraemon", species: "Robot Cat", birthdate: "2112/09/03" }
const basicInfo = omit(info, ["species", "birthdate"]);
console.log(basicInfo); // { name: "Doraemon" }
console.log(info); // { name: "Doraemon", species: "Robot Cat", birthdate: "2112/09/03" }
omit 是一个不可或缺的工具函数,lodash 和 underscore 中都有他的影子,今天阅读的 omit.js 则是对 omit 做了单独实现。
omit.js 的基本情况
以2023年1月17日为时间节点,omit.js 最近版本发布在 2020年7月8日,周下载量30w以上。
查看 Github 的贡献者列表和依赖 omit.js 的 package 列表,可以分析出 omit.js 是一个由蚂蚁集团前端大佬维护的包,并且仍旧应用于 @ant-design/pro-table
@ant-design/pro-table
等项目。
如此一来可以得出结论:omit.js 是一个仍在维护中并处于长期稳定状态的项目。
omit 功能的实现
omit.js 的实现代码在 omit.js/index.js at…
function omit(obj, fields) {
// eslint-disable-next-line prefer-object-spread
const shallowCopy = Object.assign({}, obj);
for (let i = 0; i < fields.length; i += 1) {
const key = fields[i];
delete shallowCopy[key];
}
return shallowCopy;
}
export default omit;
非常简单,三个步骤
- 使用
Object.assign
生成一个浅拷贝对象shalldowCopy
- 遍历指定属性列表,将其从
shalldowCopy
中删除 - 返回
shalldowCopy
在大多数的认知中 Object.assign
是通过浅拷贝合并生成对象,但这种说法其实并不准确。
Object.assign
真正的作用是将源对象上的所有可枚举自有属性复制到目标对象上。
实质上是调用源对象的 getter
拿到属性值,调用目标对象的 setter
设置属性值。
因此,Object.assign
并不是原来认知的全面复制,它不会去复制
- 对象的原型
- 对象实例上的不可枚举属性
- 对象上的
getter
和setter
访问器
同时它也会被目标对象的属性状况影响,例如
- 设有
setter
的属性 - 不可写的属性
举个例子
const obj = Object.create({ foo: 1 }, // 原型
{
bar: {
value: 2 // 不可枚举
},
baz: {
value: 3,
enumerable: true // 可枚举
},
qux: {
get () {
return 4
},
enumerable: true // 可枚举
}
});
Object.assign({}, obj); // {baz: 3, qux: 4}
如果不考虑复杂结构,只需要复制不可枚举的自有属性,那Object.assign
完全可以满足。
为什么不使用 lodash 和 underscore
lodash 和 underscore 有更丰富的参数支持
const object = { "a": 1, "b": { c: 2, d: 3 } }
lodash.omit(object, "a"); // 单个属性
lodash.omit(object, ["a", "b"]); // 属性列表
lodash.omit(object, [["b", "c"]]); // 多层属性
underscore.omit(object, "a"); // 单个属性
underscore.omit(object, ["a", "b"]); // 属性列表
underscore.omit(object, function (val, key, obj) { // 筛选函数
return val !== null && typeof val === "object";
});
为了实现更丰富的使用方式,同时支持处理各种类型的数据,lodash 和 underscore 的源码里做了大量额外工作。
如果输入类型单一并且只处理不可枚举的自有属性,那么没有必要使用 lodash、underscore 徒增复杂度。
omit.js包的开发和发布流程
从 omit.js 目录可以看出个大概,项目依赖 eslint、father、Travis CI,内置 TS 类型声明 index.d.ts,关键还是看 package.json
{
"name": "omit.js",
"version": "2.0.2",
"description": "Utility function to create a shallow copy of an object which had dropped some fields.",
"main": "lib/index.js", // npm 包的入口:打包生成的 lib/index.js
"module": "es/index.js", // npm 包 ESM 规范的入口:打包生成的 es/index.js
"types": "index.d.ts", // npm 包的 TS 类型声明文件:index.d.ts
"files": [ // npm 包中包含以下目录和文件
"lib",
"es",
"dist",
"index.d.ts"
],
"scripts": {
"start": "father doc dev --storybook",
"build": "father doc build --storybook",
"compile": "father build",
"gh-pages": "father doc deploy",
"prepublishOnly": "npm run compile && np --yolo --no-publish",
"lint": "eslint .",
"test": "father test",
"coverage": "father test --coverage"
}
}
脚本命令表明 omit.js 的开发、打包构建、测试都离不开 father 包,我们先来研究一下 father 是何方神圣。
father v2:库开发脚手架
设想你要从零创建并维护一个优秀的前端库,考虑到高质量、可维护、高效开发,你的脑中冒出一些需求:
- 开发流程要稳定又高效
- 这个库得有靠谱的兼容性
- 迭代开发过程中要尽量降低代码出 bug 的概率
- 最好还能有易读的文档,特别是涉及 UI 的包(组件库之类)能让用户直接体验
然后对应到:开发环境、打包构建、代码规范、TypeScript 支持、单元测试环境、文档生成。
一系列基建的安装和配置横亘在面前,更要命的是当你开启下一个库的开发,一切又要再来一遍。
有没有简化这个过程的工具呢,有没有一个针对库开发的脚手架?
father2.x 将这些零散的需求合而为一,基于 rollup, docz, storybook, jest, prettier 和 eslint 提供全套基础服务。
father 目前已经迭代到 4.x 版本,但功能支持、使用方式相较 2.x 版本都有改变。这篇文章聚焦于 omit.js 项目,因此只关注 father2.x 版本的使用
father 的命令
father build # 打包 npm 包,rollup 作为打包器
father doc dev # 使用 docz 启动多用例独立的开发环境,非常适合组件库开发
father doc build # 使用 docz 编译生成文档
father doc deploy # 将文档部署到 github.io 上
father doc dev --storybook # 可选用 storybook 替代 docz
father test # 执行单元测试,由 umi-test 提供 jest 支持
father test --coverage # 执行单元测试并统计覆盖率
另外,在首次执行 father
命令时,father 会往 git pre-commit hook 上挂载 lint 操作,这样在调用 git commit
时会主动走一遍 lint。
np:包发布辅助工具
手动的发包流程大致是:
- 编译打包生成产物
- 跑测试确认无误
- 修改
package.json
中的版本号 git add + git commit
提交暂存区中的所有代码- 运行
npm publish
发包
然而这个过程,既不方便又容易出错,例如忘记跑测试、版本推算出错、忘记修改版本号、目录中还有未提交的代码;
还有一些意料之外的疏忽,例如远程仓库同一分支上有未拉取的代码、node_modules 中的项目依赖并不是最新的;
还有一些不好的发布习惯,例如在临时分支上发包、新版本发布后不打 tag。
np 包的工作就是修补这些问题,将上面这些需要留心的工作都交给工具,减少错误发生的概率。
omit.js 的脚本命令里,在 prepublishOnly
钩子上挂了 npm run compile && np --yolo --no-publish
--yoyo
和 --no-publish
是 np 给的两个参数,分别表示:跳过依赖更新和代码测试、不要自动发包。
omit.js 这么做也能理解,跑 npm publish
进入 prepublishOnly
→ 编译代码 → 交给 np 做版本设置和一系列检查 → 检查通过进入 publish
发包
omit.js 开发构建和发布流程
"scripts": {
"compile": "father build", // 编译 index.js 生成不同规范的代码
"prepublishOnly": "npm run compile && np --yolo --no-publish", // npm publish 触发编译和包发布流程
"lint": "eslint .",
"test": "father test",
"coverage": "father test --coverage"
},
- 编写
src/index.js
代码 - 运行
npm run test
跑测试 - 运行
git commit
本地 lint 后提交代码,远程走 CI 跑 lint、test 和 coverage - 运行
npm publish
打包和发布
我学到了什么
omit.js 的作用是「从对象中剔除指定的可枚举自有属性」,使用 Object.assign
和属性删除做简单实现。
Object.assign
真正的作用是将源对象上的所有可枚举自有属性复制到目标对象上。实质上是调用源对象的 getter
拿到属性值,调用目标对象的 setter
设置属性值。
omit.js 项目的基建依赖于 father,包发布使用 np
- father 是库开发的脚手架,father v4 相较于 father v2 有更多现代化的支持,例如加入 Bundleless。在创建一个新库时或许可以直接用 father 试试。
- np 是包发布流程的辅助工具,它提供了版本设置和各种细致的检查,将发布流程中的各种细节自动化,是一个非常实用的工具。
转载自:https://juejin.cn/post/7189870079318687802