【源码阅读】omit.js 剔除对象中的属性

275 阅读6分钟

omit.js 介绍

  1. npm 地址

  2. 库的作用

    1. 删除目标对象中的一个或多个 key
    2. 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() 两个方法,可以简单理解为,调用的方法名就代表了这个测试用例的预期结果,调用之后得到最终结果,最终结果和预期相等则通过,反之则报错。

  1. assert.deepEqual(): 深度相等: 基本类型的话对比值是否相等,引用类型则是对比引用类型内的属性值是否相等,并不是对比索引
  2. 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;

源码内容也很简单

  1. 定义一个浅拷贝对象
  2. 遍历第二个参数,删除参数一中的 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 字段

  1. browser 环境和 node 环境均可使用。
  2. 路径基于项目根目录

包的入口,指向入口文件。

browser 字段

路径基于项目根目录

字段描述了浏览器环境下的处理方式

  1. 如果是字符串则替换掉 main 字段,该字段指向入口文件

  2. 如果是一个对象则替换部分文件,不需要创建新的入口

    • 第一种情况: 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 字段

  1. browser 环境和 node 环境均可使用。
  2. 路径基于项目根目录

最早是 rollup 提出的,构建方式为 ES Module 的入口文件。

main、module、browser 三者的优先级

  1. 如果 npm 包导出的是 ESM 规范的包,使用 module
  2. 如果 npm 包只在 web 端使用,并且严禁在 server 端使用,使用 browser
  3. 如果 npm 包只在 server 端使用,使用 main
  4. 如果 npm 包在 web 端和 server 端都允许使用,使用 browsermain
  5. 其他更加复杂的情况,如 npm 包需要提供 commonJS 与 ESM 等多个规范的多个代码文件,请参考流程图

webpack加载ESM模块顺序

types 字段

路径基于项目根目录

定义类型声明入口文件

files 字段

  1. 当该 npm 包作为依赖项被安装的时候,会被安装到项目中的文件及文件目录,格式是文件正则的数组,如果没有该字段则默认所有。
  2. 也可以使用 npmignore 来忽略个别文件,files 字段优先级最大,不会被 npmignore.gitignore 覆盖。
  3. 除了 files 字段中包含的也有一些总是被包含的文件以及一些肯定不会包含的文件,详见官网描述

bin 字段

可以理解为自定义命令,作用是给依赖包内的可执行文件(xx.js)定义一个名称,并将这些可执行文件定义到电脑的 PATH 中,这样就可以在终端中通过定义的名称来执行对应的可执行文件。

原理

当用户安装带有 bin 字段的依赖包时:

  1. 如果是全局安装 npm 将会把 bin 字段中描述的文件链接到 /usr/local/node_modules/.bin/
  2. 如果是本地安装 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 来做个兼容。

注意

  1. 如果只有一个可执行文件则可以将 bin 定义为字符串,可执行文件映射的 name 就是 package.json 中的 name
  2. 有多个可执行文件则可以将 bin 定义为对象。

type 字段

"type": "module"

指定 .js 结尾或没有任何扩展名的文件以什么方式处理。

  1. type 字段的产生用于定义 package.json 文件和该文件所在目录根目录中 .js 文件和无拓展名文件的处理方式。值为 'module'则当作 es 模块处理;值为 'commonjs' 则被当作 commonJs 模块处理
  2. 目前 node 默认的是如果 pacakage.json 没有定义 type 字段,则按照 commonJs 规范处理
  3. node 官方建议包的开发者明确指定 package.jsontype 字段的值
  4. 无论 package.json 中的 type 字段为何值,.mjs 的文件都按照 es 模块来处理,.cjs 的文件都按照 commonJs 模块来处理

engines 字段

"engines": {
  "node": "^14.18.0 || >=16.0.0"
}
  1. 指定项目的 node 版本,如果不指定则默认是任意版本
  2. 也可以指定 npm 版本: npm: "~1.0.20"
  3. 这些都是建议性的。

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 文件的代码重新格式化。

参考资料

  1. package.json中的browser字段
  2. npm - package.json
  3. [package.json字段详解]
  4. package.json中的browser/module/main…
  5. npm-package.json-bin
  6. 常用的package.json,还有这多你不知道的骚技巧
  7. package.json中的type字段含义—— node官方翻译
  8. lint-staged 使用教程