2

I am trying to apply module pattern in JavaScript when I have my custom constructor. Let us take this example:

// Assume I need `name` as private
function Dog(name){
    this.name = name;
}

I can rewrite above as:

function Dog(name) {

    let dogName = name;

    return {

        getName : function () {
            return dogName;
        },

        setName : function (name) {
            dogName = name;
        }
    }
}

The above code does not have right constructor property and Dog.prototype and the prototype of object returned does not match.

To fix this I made 2 Fixes:

function Dog(name) {

    let dogName = name;

    return {

        // Fix 1
        constructor : Dog,

        getName : function () {
            return dogName;
        },

        setName : function (name) {
            dogName = name;
        }
    }
}


let d2 = new Dog("Tony");
// Fix 2 : This is painful as every time I need to set this up
Object.setPrototypeOf(d2 , Dog.prototype);

As you can see Fix 2 is painful. Every time I have to create an object, I need to do this. Is there any better way?

Let us not deviate our discussion that getter and setter should be moved to prototype. Above is just a quick example I am using.

Number945
  • 4,631
  • 8
  • 45
  • 83
  • 1
    Are you open to using classes instead of functions? – Matthew Herbst Feb 26 '20 at 21:26
  • You should make use of classes, create it once and every other time you just have instantiate an instance of the class – maestro.inc Feb 26 '20 at 21:41
  • What about adding properties to `this.prototype` directly in function? – m1k1o Feb 26 '20 at 21:44
  • You're not even using the `Dog` prototype. What exactly does `Object.setPrototypeOf(d2 , Dog.prototype)` achieve? You've implemented the constructor to return an object, object literals have the `Object` prototype, you shouldn't be returning in the constructor. – Jake Holzinger Feb 26 '20 at 21:48
  • 1
    If you need to make `name` "private", why would you provide `getName` and `setName` accessor methods for it? – Bergi Feb 26 '20 at 22:04
  • @Bergi A private member by definition can only be accessed from inside the same class. This does not mean that any public method like getter and setter cannot use it. Now why would some use getter and setter on a private field - encapsulation! Have a look here - https://stackoverflow.com/questions/8995867/whats-the-point-of-accessing-private-variables-through-getter-and-setter-access Also, here I am admitting that my example is too simple to convey this. – Number945 Feb 26 '20 at 22:44
  • @Number945 That question is about C++, C#, Obj-C etc. In JavaScript, like Python, you do not need to encapsulate normal properties, and the convention is not to use getter/setter methods at all. Rather, for restricting access from the public (and only then!) you use [getter/setter properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters). – Bergi Feb 26 '20 at 22:56

2 Answers2

2

The problem here is you're returning an object literal in your "constructor", an object literal has the Object prototype. You should not be returning in the constructor, you should be assigning things to this like you did in your first code snippet.

function Dog(name) {

    let dogName = name;

    this.getName = function () {
        return dogName;
    };

    this.setName = function (name) {
        dogName = name;
    };
}

This isn't a common pattern for implementing classes though, you're not even using the prototype so it's unclear why the prototype is important to you. Obviously this approach allows you to close over a variable to make it "private" which wouldn't be possible using the prototype. However, this approach has more overhead because each instance of Dog will have it's own function for getName and setName, the standard convention for "private" variables and methods is to prefix them with an underscore so you can use the prototype as intended.

function Dog(name) {
    this._name = name;
}

Dog.prototype.getName = function () {
    return this._name;
};

Dog.prototype.setName = function (name) {
    this._name = name;
};
Jake Holzinger
  • 5,783
  • 2
  • 19
  • 33
2

Disclaimer: I don't advocate encapsulation in javascript. But here are some ugly ways to do it.

If all you're looking for is reduce code duplication, then you could easily just set everything up in the "constructor" by making it its own factory:

function Dog(name) {

    let dogName = name;

    let dog = Object.create(Dog.prototype);

    dog.getName = function () {
        return dogName;
    };

    dog.setName = function (name) {
        dogName = name;
    };

    return dog;
}


let d2 = new Dog("Tony");

If you also want to make actual use of the prototype you could use symbols to pretty much hide the name property:

Dog = function() {

    const nameSymbol = Symbol("name");

    function Dog(name) {
        Object.defineProperty(this, nameSymbol, {
            configurable: true,
            enumerable: false,
            writable: true,
            value: name
        });
    }

    Object.assign(Dog.prototype, {
        getName : function () {
            return this[nameSymbol];
        },

        setName : function (name) {
            this[nameSymbol] = name;
        }
    });

    return Dog;
}();

let d2 = new Dog("Tony");

But the symbol can still be retrieved via Object.getOwnPropertySymbols. So if you're looking for a way to really make it impossible to access the name without using the getter/setter, I think you will have to use a WeakMap:

Dog = function() {

    const dogNames = new WeakMap()

    function Dog(name) {
        dogNames.set(this, name);
    }

    Object.assign(Dog.prototype, {
        getName : function () {
            return dogNames.get(this);
        },

        setName : function (name) {
            dogNames.set(this, name);
        }
    });

    return Dog;
}();

let d2 = new Dog("Tony");
Robert
  • 2,603
  • 26
  • 25