I don't understand Crockford on [removed] The Way Forward

后端 未结 4 1057
北恋
北恋 2021-01-30 05:45

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&quo

相关标签:
4条回答
  • 2021-01-30 06:11

    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();
    
    0 讨论(0)
  • 2021-01-30 06:22

    I guess I would use this constructor somewhat along these lines:

    Constructor for animal:

    function Animal (specs) {
      // starting with an empty object, since I don´t want to inherit
      var that = {},
    
          // private attribute(s)
          name = specs.name,
    
          // public method(s)
          setName = function (newName) {
            name = newName;
          },
          getName = function () {
            return name;
          };
    
      // setting up my public interface
      // everything else is private
      that.setName = setName;
      that.getName = getName;
    
      return that;
    }
    

    Creating my animal:

    var anymal = Animal({ name: "Yoshi" });
    console.log(anymal.name); // undefined, since it´s private
    console.log(anymal.getName()); // "Yoshi"
    anymal.setName("Sanic");
    console.log(anymal.getName()); // "Sanic"
    

    Constructor for dog:

    function Dog (specs) {
      // now I want to inherit from Animal
      // the constructor will return the public interface for Animal
      var that = Animal(specs),
    
          // private attribute(s)
          bark = specs.bark,
    
          // public method(s)
          setBark = function (newBark) {
            bark = newBark;
          },
          getBark = function () {
            return bark;
          },
          barks = function () {  
            console.log(bark);
          };
    
      // these methods will be added to the Animal public interface
      that.setBark = setBark;
      that.getBark = getBark;
      that.barks = barks;
    
      return that;
    }
    

    Creating my dog:

    var anydog = new Dog({ name: "Klonoa", bark: "Wah who!" });
    console.log(anydog.getName()); // "Klonoa"
    anydog.barks(); // "Wah who!"
    

    It´s interesting to notice that an instance of Dog will reference two separate closure scopes, one from the function Animal and the other from the function Dog.

    Hope it helps!

    0 讨论(0)
  • 2021-01-30 06:30

    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.

    0 讨论(0)
  • 2021-01-30 06:30

    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 

    0 讨论(0)
提交回复
热议问题