Karl Giesing
Karl Giesing

Reputation: 1654

Solution to JavaScript private variables?

I'm asking this because I think I found a solution to a JavaScript problem, and I'd like to know if I'm way off base. I hope that asking this here is appropriate.

As I'm sure you know, JavaScript does not have private properties. This issue is usually solved using one of two patterns.

The first is to create privileged methods inside the constructor, using closures, and bind them to the object using the this keyword. This is the technique used by Douglas Crockford. Here's an example:

function Person(name) {
    function getName() {
        return name;
    }
    this.who = getName;
}

The problem with this pattern is that there's a big danger of polluting the global namespace:

var me = Person("Karl"); // Oops! no "new"
me.who(); // TypeError: Cannot read property 'who' of undefined
who(); // "Karl" - "who" is in global namespace!

The second solution is to use the module pattern. Instead of binding the privileged method to this, you bind it to a new object:

function Person(name) {
    function getName() {
        return name;
    }
    return {
        who: getName
    }
}
var me = Person("Karl"); // No "new," but that's OK
me.who(); // "Karl"
who(); // TypeError: undefined is not a function

The problem with that pattern is that the object does not have Person's prototype chain. So, you can't properly check its type, and you can't extend the object using the constructor's prototype:

console.log(me instanceof Person); // "false"
Person.prototype.what = function() {
    return this.constructor.name + ": " + this.who();
}
me.what(); // TypeError: undefined is not a function

I've found a solution to this. Use a temporary constructor that has Person's prototype chain, and return an object constructed with the temporary constructor:

function Person(name) {
    function getName() {
        return name;
    }
    // Temporary constructor
    function F() {
        this.who = getName;
    }
    F.prototype = Person.prototype;
    return new F();    
}
// Can't pollute the global namespace
var me = Person("Karl");
me.who(); // "Karl"
who(); // TypeError: undefined is not a function

// Proper prototype chain: correct type checking, extensible
console.log(me instanceof Person); // "true"
Person.prototype.what = function() {
    return this.constructor.name + ": " + this.who();
}
me.what(); // "Person: Karl"

I have a couple of related questions about this solution:

  1. Are there any drawbacks to this technique?
  2. Though the module pattern and the temporary constructor pattern have been around for a while now, I've never seen anyone put them together. Is this a pattern that I just didn't know about before?
  3. If the answer to number 2 is "no," can I get mad props for this? :)

Upvotes: 1

Views: 193

Answers (1)

Volune
Volune

Reputation: 4339

I don't have an answer at your questions, but a suggestion that should solve all your problems in a simpler way:

function Person(name) {
    "use strict";
    if (!this) return new Person(name);
    function getName() {
        return name;
    }
    this.who = getName;
}
Person.prototype.what = function() {
    return this.constructor.name + ": " + this.who();
}

"use strict"; compatibility

MDN documentation about function context


Alternative that works in all browsers

function Person(name) {
    if (!(this instanceof Person)) return new Person(name);
    function getName() {
        return name;
    }
    this.who = getName;
}
Person.prototype.what = function() {
    return this.constructor.name + ": " + this.who();
}

Upvotes: 1

Related Questions