详解 omit.js 源码

215 阅读3分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与

【若川视野 x 源码共读】第36期 | 可能是历史上最简单的一期 omit.js 剔除对象中的属性,点击参与本期源码共读

源码地址

github.com/benjycui/om…

omit 用法

参考 omit.js 包仓库的用例:

var omit = require('omit.js');
omit({ name: 'Benjy', age: 18 }, [ 'name' ]); // => { age: 18 }

可见,omit 函数的作用是删除对象的某个属性,并返回删除属性后的对象。

源码解析

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;

整个源码就是omit函数里面的代码。函数接受两个参数,objfieldsobj表示要剔除属性的对象,fields是一个字符串数组,每一项表示要剔除的属性。

因为对象是引用数据类型,所以当对象作为参数传递给函数时,函数的参数名obj是对原对象的一个指向,而不是对值的复制。如果直接对 obj 参数进行属性值的修改,那么会同样影响到原对象上。例如:

function changeAge(obj) {
    obj.age = 22;
}
const user = {name: 'Tom', age: 20};
changeAge(user)
console.log(user);  // {name: 'Tom', age: 22}

因此使用 Object.assign() 方法对原对象进行浅拷贝,得到一个具有与原对象相同属性的新对象。如果原对象里面没有嵌套任何引用数据类型(对象,数组,函数等),那么新对象的任何操作都不会影响到原数组,否则新对象对引用数据类型的属性值进行操作就会改变原数组。例如:

原对象没有嵌套引用数据类型的情况

const dog = {color: 'black', age: 10};
const otherDog = Object.assign({}, dog);
otherDog.color = 'white';

console.log(dog);  // {color: 'black', age: 10}
console.log(otherDog);  // {color: 'white', age: 10}

dog 对象的 color 属性值是基本数据类型而不是引用数据类型,所以 otherDog 对象改变 name 属性值时不会改变原对象的 color 属性值。

原对象嵌套引用数据类型的情况

const father = {
  name: 'Tom',
  age: 40,
  son: {
    name: 'Small Tom',
    age: 12
  }
}
const otherFather = Object.assign({}, father)
otherFather.name = 'Jim'
otherFather.son.name = 'Small Jim'

console.log(father.name); // 'Tom'
console.log(otherFather.name);  // 'Jim

console.log(father.son.name);  // 'Small Jim'
console.log(otherFather.son.name);  // 'Small Jim'

可以看到由于 father 对象的 name 属性值是基本数据类型而不是引用数据类型,所以新对象改变 name 属性值不会影响原对象。

father 对象的 son 属性值是一个对象,在进行浅拷贝的时候,otherFatherson属性值其实依然是对 fatherson 属性值的一个指向而不是复制。所以当改变 otherFather.son.name 的时候同时也改变了 father.son.name

如何解决这个问题呢?

要对这个嵌套对象的数据结构进行深拷贝。这里先说明一下一种简单的深拷贝方法,先用JSON.stringify()方法将对象转换为 JSON 字符串,再用 JSON.parse() 方法解析JSON字符串,得到新对象,该新对象做任何操作都不会影响原对象。将上诉代码改为:

const father = {
  name: 'Tom',
  age: 40,
  son: {
    name: 'Small Tom',
    age: 12
  }
}
const otherFather = JSON.parse(JSON.stringify(father))
otherFather.son.name = 'Small Jim'
console.log(father.son.name)  // 'Small Tom'
console.log(otherFather.son.name)  // 'Small Jim'

对于深拷贝和浅拷贝的详解,可以参考这篇文章 如何写出一个惊艳面试官的深拷贝? - 掘金 (juejin.cn)

Object.assign() 方法的详解可参考 mdn文档

解析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",
  "module": "es/index.js",
  "types": "index.d.ts",
  "files": [
    "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"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/benjycui/omit.js.git"
  },
  "keywords": [
    "object",
    "omit"
  ],
  "author": "Benjy Cui<benjytrys@gmail.com>",
  "license": "MIT",
  "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"
  }
}

package.json 文件是该项目的清单,描述了该项目的一系列信息,比如名称,版本,作者,项目依赖的其他包等等。

字段解释

name:当前项目的名称。

version:当前项目的版本号,初始版本为1.0.0。

description:当前项目的简短描述,用于在 npm 搜索包时参考的信息。

keywords:当前项目的一些关键词,也是用于在 npm 搜索包时参考的信息。

main:定义了CommonJS的入口文件,项目默认执行的文件,当使用者是通过require的方式引入包时,就会默认加载该字段指定的文件。

module:定义了ES Module 的入口文件,当使用者是通过import的方式引入包时,就会加载该字段指定的文件。

types:Typescript的声明文件,当使用者是在Typescript中引入该项目时,就需要这个声明文件。

files:将项目发到 npm 上时,包含的文件。

scripts:定义了一组可运行的 node 脚本。

repository:项目的源码地址。

author:项目的作者。

license:开源许可证,发包时,需要提供的证书,一般都是在 Github 上产生的 MIT 证书。

bugs:项目bug的提交地址,常用的是 GitHub 的 issue 页面。

homepage:项目的主页。

devDependencies:开发或测试环境下,项目所需的依赖包。使用 npm install <package_name> --save-dev 命令安装的依赖包。

创建 package.json 文件

package.json 文件有两种创建方式,手动创建和自动创建。

手动创建

在项目的根目录下新建一个叫 package.json 的文件,然后自行输入相关字段内容。

自动创建

打开 VSCode 的终端,确保终端显示文件路径是当前项目的路径,执行 npm init,然后根据提示一步步输入相应的内容就可以完成自动创建了,如果只想用默认值,执行 npm init -y

开发环境下的依赖

father:是一个组件打包工具库,源码地址

@umijs/fabric:包含 prettier,eslint,stylelint 的规范配置,如果觉得不合适个人开发,那可以在项目根目录的配置文件修改相对应的配置,源码地址

assert:单元测试包,跟jest差不多,不过没jest功能那么强大,源码地址

np:npm 发包时用到的工具库,源码地址

记录发包过程

  1. npm 官网 注册账号,记住注册邮箱,因为发布包的时候需要。
  2. 在命令行终端输入 npm login 或者 npm adduser 命令。
  3. 最后输入 npm publish

总结

  1. 虽然 omit.js 的源码比较少,但是还是能扩展出来很多知识点,如:引用数据类型在传参时不是复制而是指向,如果直接在函数内部修改参数,则会影响到原数组;对象浅拷贝与深拷贝的区别。
  2. package.json 中各个字段的含义。
  3. 如何发包。