1

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? :)
18
  • 4
    "The problem with this pattern is that there's a big danger of polluting the global namespace:" That's not a problem with that pattern, it's a "problem" with all "normal" constructor functions. If that's what you are worried about, then a simple solution is to make an instanceof check: if (!(this instanceof Person)) { return new Person(name); }. Personally I think that trying to implement "private properties" is not worth the effort and drawbacks. We'll get Symbols soon with which this should be easier. Commented Aug 18, 2014 at 22:49
  • "this.who = getname" would add `who() to the global namespace? Really? Commented Aug 18, 2014 at 22:52
  • 1
    @GolezTrol: Yes, indeed it would. If the function is called without the "new" keyword, it's not a constructor, and this binds to the global object. Commented Aug 18, 2014 at 22:55
  • @FelixKling: Your solution seems very simple, which makes me question why I haven't run across it in the JavaScript books I've read. Are you sure the instanceof operator works in the constructor (i.e. that the prototype chain has already been bound)? What about if your function is called as a "super constructor?" Any edge cases? I'll readily admit my ignorance here. Commented Aug 18, 2014 at 23:08
  • 1
    Yes instanceof works inside constructors. When you call a function with new, basically the following happens: var newObj = Object.create(Constr.prototype); Constr.apply(newObj, arguments); return newObj;. Super calls should be no problem since you should make this test inside the child constructor, not in the parent. It doesn't work properly if this test is only made inside parents. The other disadvantage is if you have a variadic constructor. You can't use .apply together with the new operator (but see stackoverflow.com/q/1606797/218196). Commented Aug 18, 2014 at 23:13

1 Answer 1

1

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();
}
Sign up to request clarification or add additional context in comments.

5 Comments

That would be awesome, but "use strict" isn't supported in older browsers. @FelixKling suggested using instanceof inside the constructor (instead of your if (!this) test) to achieve the same result.
if (this.Array===Array) return new Person(name); works in old browsers
@dandavis: that will work in old browsers, but not in strict mode (where this would be undefined). Bad idea if you're mixing strict and non-strict code, or if you want your code to be forward-compatible to the time when all JavaScript engines use strict mode. Edit: Also bad if your object is purposefully designed to extend the global object.
@KarlGiesing: i don't push strict, but point made. make that: if (!this || this.Math) return new Person(name); to work in 100% of cases. unless you defined "Math" on the prototype, in which case use another quack.
@dandavis: That takes care of strict mode, but not the edge case where your object is designed to extend the global object. In that case, this.Array === Array will always return true, and you'll end up in an infinite loop. But that is an edge case, and if you're knowledgeable enough to do it, you probably are knowledgeable enough to know why that check is bad.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.