skip to Main Content

I have a following code:

const myFunc = () => {
  let var1, var2, var3;

  const setVar1 = (val) => {
    var1 = val
  }

  return {
    setVar1
  }
}

Can you please tell me how this pattern is called(I read about it couple of days ago and can’t remember)?

As far as I understand, at the moment I will produce each time new object with setVar1 method in it and if I’ll produce 1000 objects, I will get 1000 copies of setVar1(not sure, because I have only pointer to the function)?

Can/Should I refactor this code with prototypical inheritance? Can you please explain your reasoning behind your answer, it’s very hard to understand this concept.

Thanks.

3

Answers


  1. This is what you can do with prototypal inheritance. One of the main con is you can assign __proto__ only one time.

    const object1 = {
      var: 'a',
      setVar: function(value) {
        this.var = value;
      },
      getVar: function() {
        return this.var;
      }
    }
    
    let object2 = {
      var: 'd'
    }
    
    object2.__proto__ = object1;
    
    console.log(object2.getVar()); // Prints object2.var or 'd'
    object2.setVar('e');           // Sets object2.var to 'e'
    console.log(object2.getVar()); // Prints 'e'

    I don’t think you can access your var1 or setVar1 outside of the code of myFunc because it is scoped by the language and will create new instances of it every time you run this code.

    What you might be looking for is an object-oriented class approach

    Login or Signup to reply.
  2. Well, how about:

    class MyClass {
        constructor() {
            this._var1 = ''
        }
    
        get var1() {
            return this._var1
        }
    
        set var1(value) {
            this._var1 = value
        }
    }
    

    This does use prototypes, but behind the scenes. Dealing with prototypes explicitly is generally frowned upon, buf if you must:

    function MyClass() {
        this.var1 = ''
    }
    
    MyClass.prototype.setVar1 = function(value) {
        this.var1 = value
    }
    

    When using closures, as in your post, all objects, including functions, are created from scratch, so yes, you’ll end up with 1000 copies of the same function.

    Login or Signup to reply.
  3. It is true that you are creating a new setVar() every time you invoke myFunc(). Due to closures, myFunc creates a new variable, a setter function, and returns a new object everytime it is called. These look like the same, but under the hood they are not, as can be confirmed by ===, which checks for identity when comparing objects (functions are also objects). We can verify this quite directly:

    console.log(myFunc().setVar1 === myFunc().setVar1); // false
    

    Try it:

    /*<ignore>*/ console.config({ maximize: true }); /*</ignore>*/
    
    const myFunc = () => {
      let var1;
    
      const setVar1 = (val) => {
        var1 = val;
      };
    
      return {
        setVar1
      };
    }
    
    const foo = myFunc();
    const bar = myFunc();
    
    console.log(foo.setVar1 === bar.setVar1); // false
    <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

    With .prototype, whenever you do a instance.property lookup, JS will first search its own keys set, then its prototypes. Since foo (and bar) has no own key named setVar1 (the object constructed by myFunc has no setVar1, just var1), myFunc.prototype.setVar1 will be returned instead:

    function myFunc() {
      this.var1 = undefined;
    };
    
    myFunc.prototype.setVar1 = function(val) {
      this.var1 = val;
    };
    
    console.log((new myFunc()).setVar1 === (new myFunc()).setVar1); // true
    

    Try it:

    /*<ignore>*/ console.config({ maximize: true }); /*</ignore>*/
    
    function myFunc() {
      this.var1 = undefined;
    };
    
    myFunc.prototype.setVar1 = function(val) {
      this.var1 = val;
    };
    
    const foo = new myFunc();
    const bar = new myFunc();
    
    console.log(foo.setVar1 === bar.setVar1); // true
    <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

    Since JS does have syntactic sugar for classes and private properties, you can and should just use that for ease of maintenance and readability:

    class myFunc {
      #var1;
      
      get var1() {
        return this.#var1;
      }
      
      set var1(val) {
        this.#var1 = val;
      }
    }
    

    Try it:

    /*<ignore>*/ console.config({ maximize: true }); /*</ignore>*/
    
    class myFunc {
      #var1;
      
      constructor() {
        /* Do something... */
      }
      
      get var1() {
        console.log('getter');
        return this.#var1;
      }
      
      set var1(val) {
        console.log('setter');
        this.#var1 = val;
      }
    }
    
    const foo = new myFunc();
    
    console.log(foo);        // Nothing from outside: {}
    console.log(foo.var1);   // Logs 'getter', then returns undefined.
    
    foo.var1 = 42;           // Logs 'setter'
    
    console.log(foo.var1);   // Logs 'getter', then returns 42
    <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

    You can also use Proxy to mimic the behaviour of private properties:

    function myFunc() {
      let var1;
      
      return new Proxy({}, {
        get(target, property) {
          if (property === 'var1') {
            return var1;
          } else {
             // Do something else...
          }
        },
        set(target, property, value) {
          if (property === 'var1') {
            var1 = value;
          } else {
             // Do something else...
          }
        }
      })
    };
    

    …or, to avoid recreating the handler object:

    const myFunc = (() => {
      const handler = {
        get() { /* ... */ },
        set() { /* ... */ }
      };
      return function myFunc() {
        // ...
        return new Proxy({}, handler);
      }
    })();
    

    Try it:

    /*<ignore>*/ console.config({ maximize: true }); /*</ignore>*/
    
    function myFunc() {
      let var1;
      
      return new Proxy({}, {
        get(target, property) {
          if (property === 'var1') {
            console.log('getter');
            return var1;
          } else {
             // Do something else...
          }
        },
        set(target, property, value) {
          if (property === 'var1') {
            console.log('setter');
            var1 = value;
          } else {
             // Do something else...
          }
        }
      })
    };
    
    const foo = new myFunc();
    const bar = new myFunc();
    
    console.log(foo, bar);           // {}, {}
    console.log(foo.var1);           // undefined
    
    foo.var1 = 42;
    
    console.log(foo.var1, bar.var1); // 42, undefined
    <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search