Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I don't understand Crockford on JavaScript: The Way Forward

In a lecture called "The Way Forward", Douglass Crockford shares that he no longer uses "new" in his JavaScript, and is weaning himself off of "this". He says JavaScript's good feature was not so much prototypical inheritance as it was class-free inheritance. He offers the following snippet, which is how he defines his objects:

function constructor(init) {
    var that = other_constructor(init),
        member,
        method = function () {
            // init, member, method
        };
    that.method = method;
    return that;
}

He explains:


I've got a function which will take some value to initialize it. I recommend that to be an object, (that way you can have a JSON text you can use to create new instances). You can call another constructor if you want to inherit it's stuff; in any case, you're going to create an object and put it in a variable called "that". You will create all of your member variables (things that are going to become properties of the object, of your method variables, things that are going to act on the object...) those methods will be functions which will close over the initialization value over all the member variables and all the method variables.

It could also use "this" and "that", but I recommend not. The reason for not doing that is, if it doesn't use "this" or "that", then you can take any of the functions out of the object and call them independantly and they still do exactly the same thing. So that increases the reliability of the language. Also means you can pass any of those things, use it as a call back without having to bind anything (It just becomes more reliable). Then any methods that need to be public or privilaged you simply attach them to the object and return the object. This is a really flexible pattern-- you can get multiple inheritance, you can get aspects, you can get factories-- lots of things you can do with this basic pattern.


Why does this snippet make the following assignment?

that.method = method

What purpose does method serve? How is it initialized? What is the purpose of declaring objects in this way?

His lecture was quite general, and he only spent a minute on this portion of it without elaborating on his reasoning or citing resources. Can anyone shed some light on the motivation for this style of programming?

Link to video: https://www.youtube.com/watch?v=3WgVHE5Augc Begin around the 37 minute mark

like image 339
ridthyself Avatar asked Jun 16 '14 16:06

ridthyself


3 Answers

Why does this snippet make the following assignment?

that.method = method

what purpose does method serve? How is it initialized?

It's initialized as a variable a couple of lines further up:

method = function () { ... }

Then the line you've quoted is assigning the value of that variable (a reference to the function) to a property on the object that refers to, so it can be used as a "method" of the object. So you might do this:

var x = constructor(42);
x.method(); // <== Here's where we use the function assigned to the property as a method

More (on my blog): Mythical methods

What is the purpose of declaring objects in this way?

Crockford doesn't like JavaScript's constructor functions, and so doesn't use them. So he does this instead. One of the really great things about JavaScript is how flexible it is. You can use it as an almost purely functional language, you can use it as a prototypical language, and you can even use it a lot like a class-based language even though it isn't class-based, because its prototypical features give you everything necessary for that (as of ES2015+; before ES2015, it was only nearly everything). And you can mix those approaches whenever you think it's appropriate to do so. It's just that flexible.

Unlike Crockford, I like constructor functions and the new keyword. But I also really like not having to use them when they're not the right tool, which they frequently aren't.


Re your comment below:

Would you by any chance be able to provide an example of Crockford's constructor function snippet in actual use? Is that.method used to initialize that.member, or am I totally off here?

There is no that.member. member in the code you showed is not a property of the object, it's just a variable. The method function created within the call to constructor has access to that variable because it's a closure over the context of the call to constructor, but nothing that only has access to the returned object can see member. So member is truly private to the functions created within constructor. Two more articles that may be useful here: Closures are not complicated from my blog which explains what a "closure" is, and Crockford's Private Members in JavaScript, which describes the way of having private information associated with objects that he's using in the example you quoted. (I mention a different way of having private information on objects at the end of this answer.)

What the example you've quoted is doing is demonstrating two largely unrelated things:

  1. A means of creating augmented (not derived) objects

  2. A means of having truly private information associated with those objects

The pattern he shows is not the only way to do those things, but that's what it's showing.

Re a concrete example:

Let's say we want to create "thing" objects. It doesn't really matter what they are, but they have a property called "name" (which doesn't have to be private). (They'd probably also have methods, but those don't matter so we'll leave them out for brevity and clarity.) So we'd start with something like this:

// Very simple Crockford-style constructor
function createThing(name) {
    return {name: name}; // Again, there'd probably be more to it, this is simple on purpose
}

// Usage
var t = createThing("foo");
console.log(t.name); // "foo"

So far so good. Now, we want to also have the ability to create things that we can add a counter to, and a method that "uses" the thing and counts that use, returning the new count of uses. (Yes, this is a contrived example.) A naive version, again using something akin to Crockford's way of doing this, might look like this:

// Naive approach
function createThingWithCounter(name) {
    var that = createThing(name);
    that.useCounter = 0;
    that.use = function() {
        // ...do something with `that`...

        // Return the new number of times we've "used" the thing
        return ++that.useCounter;
    };
    return that;
}

// Usage
var t = createThingWithCounter("foo");
console.log(t.name);  // "foo"
console.log(t.use()); // 1
console.log(t.use()); // 2

Again, so far so good. But the problem is, useCounter is a public property on the object. So we can mess about with it from outside the createThingWithCounter code:

var t = createThingWithCounter("foo");
console.log(t.name);  // "foo"
console.log(t.use()); // 1
t.useCounter = 0;
console.log(t.use()); // 1 -- uh oh!

We don't want useCounter to be public. Now, there are various approaches to making it private, including not making it private at all but using a naming convention (typically starting it with an underscore, e.g. _useCounter) meaning "leave this alone or else!", but the pattern we're looking at lets us make useCounter truly private by making use of the fact that the use method is a closure over the context of the call to createThingWithCounter. That, plus a bit of rearranging the source to better fit the quoted pattern, gives us this:

function createThingWithCounter(name) {
    var that = createThing(name),
        useCounter = 0,
        use = function() {
            // ...do something with `that`...

            // Return the new number of times we've "used" the thing
            return ++useCounter;
        };
    that.use = use;
    return that;
}

Now, useCounter is not a property on the object at all. It's truly private, nothing outside of createThingWithCounter can see or change it:

var t = createThingWithCounter("foo");
console.log(t.name);  // "foo"
console.log(t.use()); // 1
t.useCounter = 0;     // <== Has absolutely no effect on the real counter
console.log(t.use()); // 2

So that's our concrete (if contrived) example. Here's how it maps to the quoted pattern:

  • constructor = createThingWithCounter
  • otherConstructor = createThing
  • member = useCounter
  • method = use

Now, I want to emphasize that there's nothing in the above that you can't do with normal constructor functions using new instead. It doesn't even look that different:

// Doing the same thing with normal constructor functions and `new`
function Thing(name) {
    this.name = name;
}

// Usage
var t = new Thing("foo");
console.log(t.name); // "foo"

// Augmented things
function ThingWithCounter(name) {
    var useCounter = 0;

    Thing.call(this, name);
    this.use = function() {
        // ...do something with `this`...

        // Return the new number of times we've "used" the thing
        return ++useCounter;
    };
}

// Usage of augmented things
var t = new ThingWithCounter("foo");
console.log(t.name);  // "foo"
console.log(t.use()); // 1
t.useCounter = 0;     // <== Has absolutely no effect on the real counter
console.log(t.use()); // 2

They're just different ways of reaching similar goals.

There's another way as well: Derivation, rather than augmentation. And like augmentation, derivation can be done Crockford-style or via standard constructor functions. It's that wonderful flexibility of the language again. :-)

A final note about private information: In both styles of the above, the useCounter is truly private, but it's not a property of the object. It's not on the object at all. And there are a couple of costs associated with how we get that privacy: First, we have to create a use function for each instance. Those functions aren't shared between instances like functions attached to prototypes (and various others ways) can be. The cost there is fairly minimal here in 2014, modern engines are quite smart about optimizing that; it would have been a lot more costly 15 years or so back.

The other cost is that the use function can't be reused elsewhere, which puts it at odds with the vast majority of functions in JavaScript. If you look at the spec, you see this language on just about every predefined JavaScript method:

NOTE: The xyz function is intentionally generic; it does not require that its this value be a Whatsit object. Therefore it can be transferred to other kinds of objects for use as a method.

So if I have something that's a lot like an array but isn't an array (like the JavaScript arguments object), I can put methods from Array.prototype on it and, provided my object is array-like enough, they'll work just fine:

var arrayLikeObject = {
    length: 2,
    0: "one",
    1: "two",
    indexOf: Array.prototype.indexOf
};
console.log(arrayLikeObject.indexOf("two")); // 1

Our use method cannot be reused that way. It's locked to the "thing with counter" instance it relates to. If we did try to use it that way, we'd end up with weird cross-talk between the object we'd put it on and the original "thing with counter" object we took it from. Weird cross-talk of that kind is a great way to have really unpleasant, time-consuming, awkward bugs in projects of any size.

The next version of the JavaScript standard, ECMAScript6, gives us a way to have private properties. That is, actual properties on objects that are private (or as private as information gets in modern languages/environments). Because the properties are really properties of the object, we don't have to rely on the use function being a closure, and we can define it on a prototype object and/or reuse it on other objects — that is, neither of the costs above applies.

And even better, the quite clever pattern they're using to add that feature can be used right now to get ~90% of the benefits it offers. So if that's a topic that interests you, a final blog post for you: Private properties in ES6 -- and ES3, and ES5.

like image 133
T.J. Crowder Avatar answered Nov 09 '22 20:11

T.J. Crowder


The purpose of the assignment is to make the method "public". Without this assignment, the method is "private" to the "class".

Maybe i can try to make the code more understandable :

function constructor(init) {
    // Call the mother constructor. Or just do that = init.
    var that = other_constructor(init);

    // Private members
    var member1, member2;

    // Methods
    var method1 = function() {
        do_stuff();
    };

    var method2 = function() {
        do_stuff();
    };

    // Make some method public.
    that.method1 = method1;

    return that;
}

Then in you code, you can use your constructor like this :

var obj = constructor(other_obj);
obj.method1();
like image 21
Magus Avatar answered Nov 09 '22 20:11

Magus


Obviously some other good answers here, but TL;DR:

Why does this snippet make the following assignment?

that.method = method

He's "extending" the that object, adding a locally defined method (unfortunately named) method. It's no more complex than that.

function animal_constructor(init){
    return {};
}

function dog_constructor(init) {
    // Create a generic animal object
    var dog = animal_constructor(init),
        // Define a local function variable called 'bark'
        bark = function() {
            alert('woof!');
        };

    // Add the 'bark' function as a property of the generic animal
    dog.bark = bark;
    // Return our now-fancy quadriped
    return dog;
}

// Try it out
var init = {},
    animal = animal_constructor(init),  // generic animals can't bark
    yeller = dog_constructor(init);     // fancy dog-animals can!

yeller.bark();  // > woof!
animal.bark();  // > Uncaught TypeError: undefined is not a function 
like image 1
Madbreaks Avatar answered Nov 09 '22 20:11

Madbreaks