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

209 阅读5分钟

本文参加了由公众号@若川视野发起的每周源码共读活动,点击了解详情一起参与。 这是源码共读的第 36 期,链接:juejin.cn/post/711878…

前言

这是我参加源码共读活动的第 3 次笔记。

学习任务

  1. 主要学会通过测试用例调试源码。
  2. 可以多关注怎么发布 npm 包的、commonjs、esm、测试用例 、ts 等(也可以不关注)。
  3. 加油。

环境准备

  • 拉取项目
  • 安装依赖
  • 调试、理解代码
  • 快速调试、理解代码访问:访问 github1s 快速调试
  • 在某些项目中,通过.github/contributing.md ,来了解目录结构

开始

一、omit.js 文档:如何使用?

  1. 安装
  • 工具函数创建一个被删除属性的对象的浅副本。
npm i --save omit.js
  1. 用法
var omit = require("omit.js");
omit({ name: "Benjy", age: 18 }, ["name"]); // => { age:18 }
  1. API
omit(obj: Object, fields: string[]): Object
  • 返回已删除字段的浅副本

二、omit.js 运行测试用例

// clone project
npm i
npm run test

三、omit.js 源码

  1. 源代码

    src/index.js

    /**
     * 返回已删除字段的浅副本
     * 不懂什么意思看注释
     */
    function omit(obj, fields) {
      // eslint-disable-next-line prefer-object-spread
      const shallowCopy = Object.assign({}, obj); // 把 obj 浅拷贝成 shallowCopy
      for (let i = 0; i < fields.length; i += 1) {
        // i 加 1 后赋值给自己
        const key = fields[i];
        delete shallowCopy[key];
      } // 依次删掉 obj 的浅拷贝 shallowCopy 的属性(根据属性列表 fields)
      return shallowCopy; // 并返回
    }
    
    export default omit;
    
    • Object.assign
      1. what
        • 所有可枚举(?) 的 自有(?) 属性,
          • 所有可枚举(?):Object.propertyIsEnumerable() 返回 true
          • 自有(?):Object.hasOwnProperty() 返回 true
        • 一/多个 源对象 复制到 目标对象,
        • 返回修改后的对象。
      2. 示例
        const target = { a: 1, b: 2 };
        const source = { b: 4, c: 5 };
        const returnedTarget = Object.assign(target, source);
        console.log(target);
        // Expected output: Object { a: 1, b: 4, c: 5 }
        console.log(returnedTarget === target);
        // Expected output: true
        
        • 右边的 source 扔进左边的 target 里,相同属性的值用 source 里的,从而得到新的 target ;target 和 Object.assign() 方法返回值 returnedTarget 相等。
      3. 语法
        const returnedTarget = Object.assign(target, ...sources);
        
        • target :目标对象,接收源对象属性的对象,也是修改后的返回值。
        • sources :源对象,包含将被合并的属性。
        • returnedTarget :返回值,目标对象。
        • target 与 sources 有同 key,则 target 属性将被 sources 属性覆盖,后面 sources 属性,类似覆盖,前面 sources 属性。
        • Object.assign 方法只会拷贝源对象 可枚举的 和 自身的 属性到目标对象。
        • 为了将属性定义(包括其可枚举性)复制到原型,应使用 Object.getOwnPropertyDescriptor() 和 Object.defineProperty(),基本类型 String 和 Symbol 的属性会被复制。
        • 尽力而为、可能只会完成部分复制的方法。赋值期间出错,属性不可写,则会抛出 TypeError,抛出异常前添加任何属性,会修改 target 对象。
        • 备注:Object.assign() 不会在 source 对象值为 null 或 undefined 时抛出错误。
        • 深拷贝问题:Object.assign() 只复制属性值,so 针对深拷贝, 需要使用其他办法。
      4. 参考链接 更多见 mdn - Object.assign()
  2. 测试用例

    tests/index.test.js

    import assert from "assert"; // 工具函数 断言。
    import omit from "../src"; // 返回删掉原对象 obj 某些属性的后的浅拷贝副本 shallowCopy 。
    
    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"]), {});
      });
    });
    
    • 解释:
    //  - 测试 omit 的用例(describe):
    
    //  1. 应该创建一个浅拷贝(first it):
    
    //     - benjy = { name: "Benjy" }
    //     - benjy 不删属性,其浅拷贝为 copy
    //     - 断言:copy 和 benjy 深度相等
    //     - 断言:copy !== benjy
    
    //  2. 应该删掉通过的属性(second it):
    
    //     - benjy = { name: "Benjy", age: 18 }
    //     - 断言:benjy 的浅拷贝小红,小红删掉 age 属性后,小红和 { name: "Benjy" } 深度相等。
    //     - 断言:benjy 的浅拷贝小绿,小绿删掉 name & age 属性后,小绿和 {} 深度相等。
    

四、assert 断言依赖里的几个工具函数

node_modules/assert/assert.js

  • fail

    // At present only the three keys mentioned above are used and
    // understood by the spec. Implementations or sub modules can pass
    // other keys to the AssertionError's constructor - they will be
    // ignored.
    
    // 3. All of the following functions must throw an AssertionError
    // when a corresponding condition is not met, with a message that
    // may be undefined if not provided.  All assertion methods provide
    // both the actual and expected values to the assertion error for
    // display purposes.
    
    function fail(actual, expected, message, operator, stackStartFunction) {
      throw new assert.AssertionError({
        message: message,
        actual: actual,
        expected: expected,
        operator: operator,
        stackStartFunction: stackStartFunction,
      });
    }
    
    // EXTENSION! allows for well behaved errors defined elsewhere.
    assert.fail = fail;
    
  • ok

    // 4. Pure assertion tests whether a value is truthy, as determined
    // by !!guard.
    // assert.ok(guard, message_opt);
    // This statement is equivalent to assert.equal(true, !!guard,
    // message_opt);. To test strictly for the value true, use
    // assert.strictEqual(true, guard, message_opt);.
    
    function ok(value, message) {
      if (!value) fail(value, true, message, "==", assert.ok);
    }
    assert.ok = ok;
    
  • equal

    // 5. The equality assertion tests shallow, coercive equality with
    // ==.
    // assert.equal(actual, expected, message_opt);
    
    assert.equal = function equal(actual, expected, message) {
      if (actual != expected) fail(actual, expected, message, "==", assert.equal);
    };
    
  • notEqual

    // 6. The non-equality assertion tests for whether two objects are not equal
    // with != assert.notEqual(actual, expected, message_opt);
    
    assert.notEqual = function notEqual(actual, expected, message) {
      if (actual == expected) {
        fail(actual, expected, message, "!=", assert.notEqual);
      }
    };
    
  • deepEqual

    // 7. The equivalence assertion tests a deep equality relation.
    // assert.deepEqual(actual, expected, message_opt);
    
    assert.deepEqual = function deepEqual(actual, expected, message) {
      if (!_deepEqual(actual, expected, false)) {
        fail(actual, expected, message, "deepEqual", assert.deepEqual);
      }
    };
    
    ...
    
    
  • notDeepEqual

    // 8. The non-equivalence assertion tests for any deep inequality.
    // assert.notDeepEqual(actual, expected, message_opt);
    
    assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
      if (_deepEqual(actual, expected, false)) {
        fail(actual, expected, message, "notDeepEqual", assert.notDeepEqual);
      }
    };
    
    ...
    
    

总结、收获、感受

  • Object.assign() 的使用。
  • 最近在学 前端自动化测试 Jest 刚入门,那以后写自动化测试用例就可以用这个 assert 断言工具。
  • 前段时间算是自己半吊子半截接手初次负责一个项目,改 bug 改了 8 天!改 🤮 了,一些感觉不重要的不想改,上头压着让赶紧弄完了下一个项目拖一天公司损失一天钱,测试又提 bug,改到后面边吐槽然后还是得改。最后周四晚上测试也留下加班必须得上线,后来她们说,该提的 bug 必须得提,不然到了客户那里发现问题客户就该说,这么简单的问题你们都没发现,那也许就晚了,嗯,是我疏忽了,经验不足,实力不足,还不想该 bug ,不行呀。然后就想,我得学会自动化测试,要是会了可能就规避掉了许多 bug 的出现,还有一些是改了之后出现的 bug。
  • 到了接手新项目做完并测试完,这周已经交付客户在改客户新提出的新问题和需求了,有一个登录完弹框提示的流程,有一些限制条件,所以有些登录的用户是不会弹框的。而登录后用户信息等接口调用了两次,被后台大哥提了出来(那时还想多说,嗯,还是赶紧闭住嘴干活吧。),这是因为异步调用的问题(我的解决加了个调用时 pending 状态的标记,也可能以后会有其他更优解),解决了此问题,那个登录弹框的流程也被我改没了,今天发现改回来了。这不就是一个大问题么,必须得学会自动化测试,更加坚定了信心。
  • 这种手动的容错率比较低,学自动化测试,交给机器来做吧。
  • 此期源码阅读活动就适合我初次接触一下外面的世界-github 上优质项目,简单、易学、学会了有信心继续。
  • 自动化测试很重要,加油吧!

最后

感谢 everyone ,踏踏实实,沉下心来,好好加油。

参考链接

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

mdn - Object.assign()

往期回顾

【若川视野 x 源码共读】第 24 期 | vue2 工具函数

【若川视野 x 源码共读】第 21 期 | await-to-js 如何优雅的捕获 await 的错误