Search code examples
javascriptoopjavascript-objectses6-classweakmap

Do we change states when setting new values in weakmaps?


Suppose we've the following code:

const _timeStamp = new WeakMap();
const _running = new WeakMap();

export class Stopwatch {
  constructor() {
    _timeStamp.set(this, 0);
    _running.set(this, false);
  }

  start() {
    // start stopwatch
    _running.set(this, true)
    _timeStamp.set(this, Date.now())
  }

  stop() {
    // stop stopwatch
    _running.set(this, false);
    return (Date.now() - _timeStamp.get(this))/1000
  }
}

In this example we're trying to hide some information from the end-user using weakmaps. Depending on which method is called, we change the values in both weakmaps. When we reassign variables, we change states.

let x = 0;
x = 10

EDIT:


As a best practice we shouldn't mutate objects or change states in JS

However, don't we change states when updating values in weakmaps like as we do in case of variables? What's the 'best practice' when using private props that need to be rewritable?

Any clarification is well appreciated!


Solution

  • To answer your question about WeakMaps, I think you are talking about side effects. When using a WeakMap you can imagine that there is no userland side effect to setting a value because WeakMaps invert the usual relationship between item and collection. In a WeakMap, for reasons of garbage collection, the item maintains a hidden link to the collection (rather than the collection maintaining a reference to the item), so we can pretend there is no side-effect.

    But, this question is also about privacy in JavaScript.

    As it happens, JavaScript has always supplied an outstanding (and impenetrable as far as I know - unlike private fields in languages like C#) mechanism for robust privacy: closures. It's just that most developers come from a class-based object-orientation background and take time to learn the "JavaScript way".

    That said, syntax for marking fields on classes as private is currently at Stage 3 of the feature development process, and is supported by transpilers.

    So the answer is: there are multiple ways to ensure privacy, depending on the precise use case and the style of code your team is using.

    Teams using classes will probably use the # private class field syntax.

    class MyClass {
       #myPrivateField = 'foo'
       bar(s) {
           return this.#myPrivateField + s
       }
    }
    

    Teams using functions will use closures.

    function createObject() {
        let myPrivateVariable = 'foo'
        return {
            bar(s) {
                return myPrivateField + s
            }
        }
    }
    

    You can nit-pick this and say that the function-object for bar is created on a per-instance basis. In the vast majority of cases this won't matter.

    Symbols will sometimes be used, although these offer lesser privacy because they can be viewed by reflection.

    function createObject() {    
        let secretSymbol = Symbol('secret')
        return ({
            [secretSymbol]: 'foo',
            bar(s) {
                return this[secretSymbol] + s
            }
        })
    }
    

    WeakMaps and modules can also be used. For more details see the links below.

    Note that you could also use a class with a closure to ensure a variable remained private.

    See also, and.