OO Javascript constructor pattern: neo-classical vs prototypal
Asked Answered
P

7

43

I watched a talk by Douglas Crockford on the good parts in Javascript and my eyes were opened. At one point he said, something like, "Javascript is the only language where good programmers believe they can use it effectively, without learning it." Then I realized, I am that guy.

In that talk, he made some statements that for me, were pretty surprising and insightful. For example, JavaScript is the most important programming language on the planet. Or it is the most popular language on the planet. And, that it is broken in many serious ways.

The most surprising statement he made, for me, was "new is dangerous". He doesn't use it any more. He doesn't use this either.

He presented an interesting pattern for a constructor in Javascript, one that allows for private and public member variables, and relies on neither new, nor this. It looks like this:

// neo-classical constructor
var container =  function(initialParam) {
    var instance = {}; // empty object 

    // private members
    var privateField_Value = 0;
    var privateField_Name = "default";

    var privateMethod_M1 = function (a,b,c) {
        // arbitrary
    }; 

    // initialParam is optional
    if (typeof initialParam !== "undefined") {
        privateField_Name= initialParam;
    }

    // public members
    instance.publicMethod = function(a, b, c) {
        // because of closures,
        // can call private methods or
        // access private fields here. 
    };

    instance.setValue = function(v) {
        privateField_Value = v;
    };

    instance.toString = function(){
        return "container(v='" + privateField_Value + "', n='" + privateField_Name + "')";
    };

    return instance;
}


// usage
var a = container("Wallaby");
WScript.echo(a.toString()); 
a.setValue(42);
WScript.echo(a.toString()); 

var b = container();
WScript.echo(b.toString()); 

EDIT: code updated to switch to lowercase class name.

This pattern has evolved from Crockford's earlier usage models.

Question: Do you use this kind of constructor pattern? Do you find it understandable? Do you have a better one?

Parrotfish answered 27/11, 2009 at 17:38 Comment(8)
Genius... less like a constructor, more like a factory!Iridis
Make sure that you guard against usage like var c = new Container();, which might involve checking the value of this.Lorentz
I agree with @Kevin Hakanson, Douglas Crockford actually recommends using an initial in lower case for this kind of method, and an initial in upper case for constructor methods intended to be used with new.Stutzman
@Kevin, does it matter if someone calls it with new or not? There's no reference to this in the constructor, so... does it matter? Whether I use new or not, (rv instanceof Container) returns false, where rv is the return value. I think the converse is true: if new is assumed then you need to test. In this case new is not assumed, it looks like there is no test needed. true?Parrotfish
@Eric - thanks, I updated the code to use lower-case.Parrotfish
This is a great discussion, thanks.Peachy
Yes. I use this pattern as opposed to creating constructors whenever possible. Whenever possible means whenever not constrained by a framework which expects to be passed a constructor.Clatter
There is one big issue with that pattern. If you create 100 instances, you'll have 600 methods instead of 6. At least, defines the function outside the "constructor". You can use a static WeakMap to retrieve the private scoped properties.Gastroenterostomy
T
15

This looks like the non-singleton version of the module pattern, whereby private variables can be simulated by taking advantage of JavaScript's "closures".

I like it (kinda...). But I don't really see the advantage in private variables done in this way, especially when it means that any new methods added (after initialisation) do not have access to the private variables.

Plus, it doesn't take advantage of JavaScript's prototypal model. All your methods and properties must be initialised EVERY time the constructor is called - this doesn't happen if you have methods stored in the constructor's prototype. The fact is, using the conventional constructor/prototype pattern is much faster! Do you really think private variables make the performance hit worth it?

This kind of model makes sense with the module pattern because it's only being initialised once (to create a pseudo-singleton), but I'm not so sure it makes sense here.

Do you use this kind of constructor pattern?

No, although I do use its singleton variant, the module pattern...

Do you find it understandable?

Yes, it's readable and quite clear, but I don't like the idea of lumping everything inside a constructor like that.

Do you have a better one?

If you really need private variables, then stick with it by all means. Otherwise just use the conventional constructor/prototype pattern (unless you share Crockford's fear of the new/this combo):

function Constructor(foo) {
    this.foo = foo;
    // ...
}

Constructor.prototype.method = function() { };

Other similar questions relating to Doug's views on the topic:

Thuythuya answered 27/11, 2009 at 18:15 Comment(10)
What's the conventional constructor pattern? Like I said, I'm that guy who uses the lamguage before learning it. Also, what's the perf hit? Not initializing the functions every call to the ctor/factory ... I think that eliminates private variables completely. Is that what you mean by asking "is it worth it" ? It seemed to me that a major concern from Crockford is lack of modularity and the possibility of disparate js libraries to trample on each others variables unexpectedly. His module pattern, his refusal to use new or this - they are all intended to directly address that.Parrotfish
By "conventional constructor pattern", I mean having a constructor that utilises the implicitly created instance of itself (created via new), which it can refer to as this. And then, any methods can be added to the constructor's prototype - any methods added to this prototype will become immediately available to all current and future instances of the "class". Look at the code sample I added to my answer. The perf hit is more than you might think. Compare them here: gist.github.com/244166Thuythuya
I got a 10x difference in perf speed, comparing the conventional versus crockford's proposal.Parrotfish
Doesn't that worry you? In FF, I'm getting 72ms for Crockford's versus just 2ms for conventional.Thuythuya
Sure! 10x is not insignificant. I had never measured it, is all. remember, I'm that guy.Parrotfish
"Plus, it doesn't take advantage of JavaScript's prototypal model." +1 That is, by far, my biggest issue with that approach.Distiller
So, private data eh? On data that is run by the client? That is one piece here that doesn't really add up for me.Primogenial
@Gregg Lind - it's "private" in an OO sense that other code can't see or access it. Just as "private" members of my Java classes running on the server can still be seen by me the developer reading or debugging, but can't be accessed by other classes.Stolon
999 wrote: "But I don't really see the advantage in private variables done in this way, especially when it means that any new methods added (after initialisation) do not have access to the private variables..." This is not necessarily true. Please check my query about 'protected' members using the module pattern (and more importantly the accepted answer). #8704198Elkeelkhound
In Chrome 47 (current as of 2015-12-18) there is no noticeable difference using the gist above for testing. I set up a JSPerf page and in my Chrome it tells me Crockford's method is faster! Click on the link to run in your browser. jsperf.com/crockford-s-module-patternScriabin
H
10

I avoid this pattern as most people find it harder to read. I generally follow two approaches:

  1. If I only have one of something, then I use anonymous objects:

    var MyObject = {
        myMethod: function() {
            // do something
        }
    };
    
  2. For more than one of something I use standard javascript prototypal inheritance

    var MyClass = function() {
    };
    MyClass.prototype.myMethod = function() {
        // do something
    };
    
    var myObject = new MyClass();
    

(1) is much easier to read, understand, and write. (2) is more efficient when there are multiple objects. Crockford's code will create a new copy of the functions inside the constructor every time. Closure also has the downside of being more difficult to debug.

Although you lose truly private variables, you prefix supposed-to-be-private members with _ as a convention.

this is an admittedly difficult problem in javascript, but can be worked around using .call and .apply to set it up properly. I also often use var self = this; to create a closure variable to use as this inside functions defined within a member function.

MyClass.prototype.myMethod = function() {
    var self = this;

    // Either
    function inner1() {
        this.member();
    }
    inner1.call(this);

    // Or
    function inner2() {
        self.member();
    }
    inner2();
};
Harlandharle answered 27/11, 2009 at 18:23 Comment(1)
Anonymous objects? Do you mean object literals?Shari
S
7

Do you use this kind of constructor pattern?

Nope

Do you find it understandable?

Yes, its very straight forward.

Do you have a better one?

I haven't watched the talk yet, but I will be getting to it shortly. Until then, I don't see the danger of using new and this, and here's why:

Without having heard his points, I can only assume that he suggests staying away from such things because of the nature of this, and how it is prone to change depending on the context in which a particular method is executed (directly upon the original object or as a callback, etc). As an educator, he may be teaching avoidance of these keywords because of the demographic of largely unaware and inexperienced developers who dabble in JavaScript without first grokking the nature of the language. For experienced developers who are intimately acquainted with the language, I'm not convinced that it's necessary to avoid this feature of the language, which grant it an amazing amount of flexibility (which is completely different from avoiding things like with). All that said, I'll be watching it now.

At any rate, when not using some sort of framework that supports automagic inheritance (like dojo.declare), or when writing a framework independent object, I currently take the following approach.

Definition:

var SomeObject = function() {
    /* Private access */
    var privateMember = "I am a private member";
    var privateMethod = bindScope(this, function() {
        console.log(privateMember, this.publicMember);
    });

    /* Public access */
    this.publicMember = "I am a public member";

    this.publicMethod = function() {
        console.log(privateMember, this.publicMember);
    };
    this.privateMethodWrapper = function() {
        privateMethod();
    }
};

Usage:

var o = new SomeObject();
o.privateMethodWrapper();

Where bindScope is a utility function similar to Dojo's dojo.hitch or Prototype's Function.prototype.bind.

Shari answered 27/11, 2009 at 23:38 Comment(0)
P
6

In his book it's called functional inheritance (page 52). I don't use it yet. It's understandable if you learn javascript from Douglas. I don't think there is a better approach. This one is good because it:

  • enables a programmer to create private members

  • secures private members and eliminates this opposed to pretend-privacy (private members start with _)

  • makes inheritance smooth and without unnecessary code

However, it has some drawbacks. You can read more about it here: http://www.bolinfest.com/javascript/inheritance.php

Polymyxin answered 27/11, 2009 at 18:12 Comment(0)
K
1

Do you use this kind of constructor pattern?

I've used this patter earlier on when I was first learning JavaScript and stumbled on to Douglas Crockford's literature.

Do you find it understandable?

As long as you understand closures, this method is clear.

Do you have a better one?

It depends on what you are trying to accomplish. If you're trying to write a library that will be as idiot proof as possible, then I can completely understand the use of private variables. It may help prevent users from inadvertently disturbing the integrity of your object. Yes, the cost in time might be slightly larger (about 3 times as larger), but the cost of time is a moot argument until it's actually affecting your application. I noticed the test mentioned in a previous post (test) does not account for how long it takes to access an object's functions.

I've found that the prototype/constructor method usually leads to a quicker construction time, yes, but it doesn't necessarily save time in retrieval. There is an additional cost of using the prototype chain to find a function versus having a function attached directly to the object you're using. So if you're calling a lot of prototype functions and not constructing a lot of objects, it might make more sense to use Crockford's pattern.

I tend to not like to use private variables, though, if I'm not writing my code for a large audience. If I'm with a team of people who are all capable coders, I can generally, trust that they'll know how to use my code if I code and comment well myself. Then I use something akin to Crockford's pattern sans private members/methods.

Kerwinn answered 27/11, 2009 at 20:44 Comment(0)
C
1

If we need to create always the same kind of object, I rather use the Prototypal constructor pattern, since it allows share methods and properties through automatic delegation via prototype chain.

Also we can keep the private objects; look at this approach:

var ConstructorName = (function() { //IIFE
    'use strict';

    function privateMethod (args) {}

    function ConstructorName(args) {
        // enforces new (prevent 'this' be the global scope)
        if (!(this instanceof ConstructorName)) {
            return new ConstructorName(args);
        }
        // constructor body
    }

    // shared members (automatic delegation)
    ConstructorName.prototype.methodName = function(args) {
        // use private objects
    };

    return ConstructorName;
}());

I recommend reviewing this answer where we can find an interesting way to create a constructor function: Different way of creating a Javascript Object

This is an example where you can use the approach of prototypal constructor: How do I create a custom Error in JavaScript?

Consecration answered 6/5, 2015 at 5:23 Comment(0)
T
0

Think that can help the discussion to report here what is the main point about this pattern as explained by Douglas Crockford in his “Advanced JavaScript” presentation video .

The main point is to avoid the use of the new operator. Because when someone forget to use the new operator when calling an object constructor function, then object members added in the constructor all end up into the global namespace. Note that in this case there is no run-time warning or error that inform of the mistake, the global namespace simply get polluted. And this has proven to be have serious security implications and to be source of many hard to diagnose bugs.

That's is the main point of this Powerconstructor pattern.

The private/privileged part is secondary. It's ok to do that in a different way. So is that prototype members are not used.

HTH luKa

Twobyfour answered 7/10, 2011 at 20:13 Comment(3)
There's been some time since I first posed the question, and with time I have some perspective. Seems to me Crockford advocates a lot of work to protect against a small subset of errors - omitting the new operator. Why go to all the bother? We have no similar protections against other common error cases: eg, omitting the if statement, or the return statement. You say this is the source of hard-to-diagnose bugs. Really?Parrotfish
I haven't seen evidence of that, and I am dubious that "omitting new" causes a large proportion of pain in Javascript development. Surely the initial unit tests will show the developer pretty quickly, hey! you forgot to call new here! and then the problem is solved. Static analysis (JSLINT) can also be employed to assist. Enforcing the Crockford convention everywhere in order to fix this seems like an overreaction, to me.Parrotfish
To avoid forgetting the new, just begin the constructor with if (!(this instanceof ConstructorName)) { throw new Error("Invalid call of the constructor"); }Gastroenterostomy

© 2022 - 2024 — McMap. All rights reserved.