- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第36期,链接:【若川视野 x 源码共读】第36期 | 可能是历史上最简单的一期 omit.js 剔除对象中的属性。
omit.js 介绍
-
库的作用
- 删除目标对象中的一个或多个 key
omit
接收两个参数,第一个参数是目标对象,第二个参数是目标对象中将要被删除 key 组成的数组。
读源码整体流程
从单元测试入手,一步一步跟着断点进行调试来阅读源码,在读的过程中产生的问题,如果不是阻塞性的问题都将其记下来,先将整体流程完成,然后再去解决之前产生的问题。
单元测试
单元测试在 tests/index.test.js
文件中
import assert from 'assert';
import omit from '../src';
describe('omit', () => {
it('should create a shallow copy', () => {
const benjy = { name: 'Benjy' };
const copy = omit(benjy, []);
assert.deepEqual(copy, benjy); // 通过
assert.notEqual(copy, benjy); // 通过
});
it('should drop fields which are passed in', () => {
const benjy = { name: 'Benjy', age: 18 };
assert.deepEqual(omit(benjy, ['age']), { name: 'Benjy' });
assert.deepEqual(omit(benjy, ['name', 'age']), {});
});
});
这里引入的是 assert
断言测试依赖包
断言测试: 如果不满足条件则抛出错误
单测中调用了 assert.deepEqual()
和 assert.notEqual()
两个方法,可以简单理解为,调用的方法名就代表了这个测试用例的预期结果,调用之后得到最终结果,最终结果和预期相等则通过,反之则报错。
assert.deepEqual()
: 深度相等: 基本类型的话对比值是否相等,引用类型则是对比引用类型内的属性值是否相等,并不是对比索引assert.notEqual()
: 浅层不相等: 期望是结果不相等,基本类型对比值是否相等,引用类型则是对比索引是否相等
源码
从单测中也可以看到 omit
函数源码是在 src/index.js
文件中
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;
源码内容也很简单
- 定义一个浅拷贝对象
- 遍历第二个参数,删除参数一中的 key
这里源码就看完了,但是感觉和这个源码有点简单了,所以决定去学习一下 package.json
中的配置
package.json
的配置项
这个项目的 package.json
也比较简单,然后就可以结合之前项目中遇到的一些配置,一起学习一下各个配置的作用
{
"name": "omit.js", // 包名称
"version": "2.0.2", // 包版本号
"description": "xxxxx", // 包描述信息
"main": "lib/index.js",
"module": "es/index.js",
"types": "index.d.ts",
"files": [
"lib",
"es",
"dist",
"index.d.ts"
],
"browser": "xxxx",
"bin": "",
"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"
},
"repository": { // 仓库地址
"type": "git",
"url": "git+https://github.com/benjycui/omit.js.git"
},
"keywords": [ // 关键字,方便在 npm 中搜索
"object",
"omit"
],
"author": "Benjy Cui<benjytrys@gmail.com>",
"license": "MIT", // 许可证信息,也可以是一个对象
"bugs": { // 项目反馈问题的地址,可以有 url 和 email,如果值有 url 可以直接将 url 地址赋值给 bugs,不需要声明为对象
"url": "https://github.com/benjycui/omit.js/issues"
},
"homepage": "https://github.com/benjycui/omit.js#readme", // 项目主页地址
"devDependencies": {
"@umijs/fabric": "^2.2.2",
"assert": "^1.4.1",
"eslint": "^7.4.0",
"father": "^2.29.5",
"np": "^6.3.1",
"rc-tools": "^6.3.3",
// 补充
"lint-staged": "^13.0.3"
},
// 补充
"type": "module",
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"lint-staged": {
"*.{js,ts,vue,json}": [
"prettier --write"
]
}
}
main
字段
browser
环境和node
环境均可使用。- 路径基于项目根目录
包的入口,指向入口文件。
browser
字段
路径基于项目根目录
字段描述了浏览器环境下的处理方式
-
如果是字符串则替换掉
main
字段,该字段指向入口文件 -
如果是一个对象则替换部分文件,不需要创建新的入口
-
第一种情况: key 是被替换的文件或 module 名称,value 是新的文件
"browser": { "module-a": "./browser/module-a.js", "./server/module-b.js": "./browser/module-b.js" }
-
第二种情况: module-a 不会被打包
"browser": { "module-a":false, "./server/only.js":"./shims/server-only.js" }
-
module
字段
browser
环境和node
环境均可使用。- 路径基于项目根目录
最早是 rollup 提出的,构建方式为 ES Module
的入口文件。
main、module、browser
三者的优先级
- 如果 npm 包导出的是 ESM 规范的包,使用
module
- 如果 npm 包只在 web 端使用,并且严禁在 server 端使用,使用
browser
- 如果 npm 包只在 server 端使用,使用
main
- 如果 npm 包在 web 端和 server 端都允许使用,使用
browser
和main
- 其他更加复杂的情况,如 npm 包需要提供 commonJS 与 ESM 等多个规范的多个代码文件,请参考流程图
types
字段
路径基于项目根目录
定义类型声明入口文件
files
字段
- 当该 npm 包作为依赖项被安装的时候,会被安装到项目中的文件及文件目录,格式是文件正则的数组,如果没有该字段则默认所有。
- 也可以使用
npmignore
来忽略个别文件,files
字段优先级最大,不会被npmignore
和.gitignore
覆盖。 - 除了
files
字段中包含的也有一些总是被包含的文件以及一些肯定不会包含的文件,详见官网描述 。
bin
字段
可以理解为自定义命令,作用是给依赖包内的可执行文件(xx.js)定义一个名称,并将这些可执行文件定义到电脑的 PATH 中,这样就可以在终端中通过定义的名称来执行对应的可执行文件。
原理
当用户安装带有 bin
字段的依赖包时:
- 如果是全局安装 npm 将会把
bin
字段中描述的文件链接到/usr/local/node_modules/.bin/
- 如果是本地安装 npm 会链接到
./node_modules/.bin/
demo
在项目中使用
如果项目中有一个依赖项有如下配置
"bin": {
"my-app-cli": "./bin/cli.js"
}
则在项目中可以这样用
"scripts": {
start: 'node node_modules/.bin/my-app-cli'
}
在终端中使用
在终端中直接使用 my-app-cli
命令即可。这也归功于 bin 的定义。
#!/usr/bin/env 的作用
#!/usr/bin/env
后面跟的是解释器,使用 #!/usr/bin/env [[解释器名称]]
的好处是可以避免在不同系统上使用不同的解释器路径。比如: #!/usr/bin/env node
因为在不同的系统上,node 解释器的路径可能会不同。而使用 #!/usr/bin/env node
,就可以保证在任何系统上都可以找到可用的 node 解释器。
总的来说,my-app-cli
可以在终端执行,并且是通过 node 执行的,但是在不同系统中 node 的安装路径及全局环境变量 PATH 路径可能不一样,所以用 #!/usr/bin/env
来做个兼容。
注意
- 如果只有一个可执行文件则可以将
bin
定义为字符串,可执行文件映射的name
就是package.json
中的name
。 - 有多个可执行文件则可以将
bin
定义为对象。
type
字段
"type": "module"
指定 .js
结尾或没有任何扩展名的文件以什么方式处理。
type
字段的产生用于定义package.json
文件和该文件所在目录根目录中.js
文件和无拓展名文件的处理方式。值为'module'
则当作es
模块处理;值为'commonjs'
则被当作commonJs
模块处理- 目前
node
默认的是如果pacakage.json
没有定义type
字段,则按照commonJs
规范处理 node
官方建议包的开发者明确指定package.json
中type
字段的值- 无论
package.json
中的type
字段为何值,.mjs
的文件都按照es
模块来处理,.cjs
的文件都按照commonJs
模块来处理
engines
字段
"engines": {
"node": "^14.18.0 || >=16.0.0"
}
- 指定项目的 node 版本,如果不指定则默认是任意版本
- 也可以指定 npm 版本:
npm: "~1.0.20"
- 这些都是建议性的。
lint-staged
字段
"devDependencies": {
"lint-staged": "^13.0.3"
},
"lint-staged": {
"*.{js,ts,vue,json}": [
"prettier --write"
]
}
lint-staged
是一个 npm 包,在 devDependencies 也会体现。
它将根据 package.json
依赖项中的代码质量工具来安装和配置 husky 和 lint-staged
,因此请确保在此之前安装(npm install --save-dev
)并配置所有代码质量工具,如 Prettier 和 ESlint。
对应的这里是配置了对 js,ts,vue,json
文件的代码重新格式化。