簡體   English   中英

為Node CommonJS模塊創建不可變的類

[英]Creating immutable class for Node CommonJS module

我正在Node項目中創建一個基本類,我想使用Jest對其進行測試。 我收到一個錯誤,暗示要在測試中使用“嚴格”模式,我想避免/修復該錯誤


〜/ lib / LogRecord.js

let LogRecord = require('../lib/logRecord');

describe('Test LogRecord functionality', () => {
    test('LogRecord constructor', () => {
        let timestamp = Date.now();
        let logRecord = new LogRecord('INFO', 'Test Message', timestamp);
        expect(logRecord.level).toBe('INFO');
        expect(logRecord.message).toBe('Test Message');
        expect(logRecord.timestamp).toBe(timestamp);
    });
    test('LogRecord is read-only', () => {
        let timestamp = Date.now();
        let logRecord = new LogRecord('INFO', 'Test Message', timestamp);

        logRecord.level = 'WARN'
        logRecord.message = 'New Message'
        logRecord.timestamp = Date.now();

        expect(logRecord.level).toBe('INFO');
        expect(logRecord.message).toBe('Test Message');
        expect(logRecord.timestamp).toBe(timestamp);
    });
});

我正在對此進行測試:

Test LogRecord functionality › LogRecord constructor

TypeError: Cannot set property level of #<LogRecord> which has only a getter

  1 | module.exports = class LogRecord {
  2 |     constructor(level, message, timestamp) {
> 3 |         this.level = level;
    |         ^
  4 |         this.message = message;
  5 |         this.timestamp = timestamp ? timestamp : Date.now();
  6 |     }

  at new LogRecord (lib/logRecord.js:3:9)
  at Object.test (test/logRecord.test.js:6:25)

當我運行npm test ,兩個LogRecord測試都出現以下錯誤:

const data = new WeakMap();

let levelKey = {id:'level'};
let messageKey = {id:'message'};
let timestampKey = {id:'timestamp'};

module.exports = class LogRecord {
    constructor(level, message, timestamp) {
        data.set(levelKey, level);
        data.set(messageKey, message);
        data.set(timestampKey, timestamp ? timestamp : Date.now());
    }

    get level () {
        return data.get(levelKey)
    }

    get message () {
        return data.get(messageKey)
    }

    get timestamp () {
        return data.get(timestampKey)
    }
}

編輯-工人階級

 const data = new WeakMap(); let levelKey = {id:'level'}; let messageKey = {id:'message'}; let timestampKey = {id:'timestamp'}; module.exports = class LogRecord { constructor(level, message, timestamp) { data.set(levelKey, level); data.set(messageKey, message); data.set(timestampKey, timestamp ? timestamp : Date.now()); } get level () { return data.get(levelKey) } get message () { return data.get(messageKey) } get timestamp () { return data.get(timestampKey) } } 

測試是要確保您的代碼能夠實現您認為的功能。 考慮以下類別:

class Foo {
  constructor (bar) {
    this._bar = bar;
  }

  get bar () {
    return this._bar;
  }
}

這里的bar只讀的 ,無法設置bar屬性:

let foo = new Foo('a foo');
foo.bar; // 'a foo'
foo.bar = 'am not'; // TypeError!

模塊問題並不是真正相關的:因為注釋類主體中鏈接的Logar 始終是嚴格模式,無論如何。

因此,如果您希望屬性是只讀的,則不必擔心編寫它。 工作流程可能如下所示:

  1. 編寫空類Foo class Foo {}並構造一個實例foo = new Foo()
  2. 編寫測試以檢查因為我們有一個空的類而導致bar失敗的測試
  3. 添加構造函數參數和getter
  4. 檢查測試現在通過了
  5. 添加測試以確保嘗試設置欄會引發預期錯誤*

如果您不希望使用只讀屬性,則可以添加一個setter:

class Foo {
  constructor (bar) {
    this._bar = bar;
  }

  get bar () {
    return this._bar;
  }

  set bar (value) {
    this._bar = value;
  }
}

在這種情況下,您將添加一個測試,該測試會設置bar並讀取更改后的值。

*您可能想知道為什么規范會保證這種行為時為什么要進行此測試,並且我認為該測試是必需的,因為有人(對於調用者而言)可以將該類重構為老式構造函數並創建一個向量錯誤:

// post refactor Foo
const Foo = function Foo(bar) {
  this.bar = bar; // danger! now writable!
};

希望有經驗的審閱者會發現這種情況,但是我還是會編寫測試。

更新資料

如果您想要的是在構造函數中設置的有保證的只讀屬性,則可以使用以下方法:

const data = new WeakMap();

module.exports = class Foo () {
  constructor (bar) {
    data.set(this, bar);
  }

  get bar () {
    return data.get(this);
  }
};

因為沒有導出data ,所以沒有外部代碼可以更改它。 嘗試設置實例的bar屬性將引發異常。 只是使用getter和setter定義下划線屬性,這有點復雜,但是如果您要使用它,那么……我知道這種模式,因為我已經使用過它。

更新2

您只能為每個模塊 (而不是每個類或實例)創建一個weakmap。 弱映射存儲一個唯一的數據條目,該條目鍵入到各個實例(即this ):

const data = new WeakMap();

module.exports = {
  Foo: class Foo () {
    constructor (bar) {
      data.set(this, bar);
    }

    get bar () {
      return data.get(this);
    }
  },

  Bar: class Bar () {
    constructor (prop1, prop2) {

      // for multiple props we'll store an object...
      data.set(this, { prop2, prop1 });
    }

    get prop1 () {
      // ...and retrieve it and access it's props to return
      return data.get(this).prop1;
    }

    get prop2 () {
      return data.get(this).prop2;
    }
  }
};

請注意,用吸氣劑設置道具,但不會有設置器會拋出...

// in someotherfile.js
const { Foo } = require('path/to/file/with/foo.js');
const foo = new Foo('imma foo');
foo.bar; // 'imma foo'
foo.bar = 'no not really'; // TypeError!

// you can still set random properties that don't have a getter:
foo.baz = 'I do not throw';
foo.baz; // 'I do not throw'

如果希望僅在對象初始化之后讀取屬性,則可以在構造函數中使用Object.freeze ,並刪除您的getter:

class LogRecord {
    constructor(level, message, timestamp) {
        this.level = level;
        this.message = message;
        this.timestamp = timestamp ? timestamp : Date.now();
        Object.freeze(this);
    }
}

但這將凍結對象的所有屬性。 之后,您將無法修改,刪除或添加任何內容。 沒有深入研究這一點,因此它也可能存在一些缺陷

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM