2

In this example why does pushing the function reference onto the array not change the scope execution context of this whereas assigning the function reference to a new variable does change the scope execution context of this?

(function() {
    function Car(year) {
        this.year = year;
    }

    Car.prototype = {
        logYear: function() {
            console.log(this.year);
        }
    };

    var ford = new Car('Ford', 1999);

    ford.logYear();

    var cbs = [];

    cbs.push(ford.logYear);

    for(var x = 0; x < cbs.length; x++) {
        cbs[x](); // -> 1999 #1
        var callback = cbs[x];
        callback(); // -> undefined #2
    }
})()

Is this because the reference to the function in the array points to the original function on the ford object where this is still defined as the instance (#1) and the variable assignment changes the this to point to the wrapping IIFE block (#2)?

Is this a good pattern if I need to collect of bunch of methods like this from various objects and call them somewhere else?

How does this referential calling relate to examples like console.error.bind(console)?

Thanks!

-- Update

Thanks to @Satyajeet for the nice explanation and clarification.

Typos & copying errors aside the reason I was asking this question in the first place is that I suspected what Satyajeet confirmed but an application I am working on does not reflect this behavior (or so I thought).

It turns out that in the application I am performing a similar process to the above code where I call cbs.push(ford.logYear);, adding multiple methods from different objects to an array.

At a point in the app I call all these methods and expect them to behave in the same way as when they are called in the execution context scope of the original object... and they do, because the values the methods interact with are not attached to this, they are captured in closures.

See example plunker here

-- Update 2

Fixed usage of execution context and scope to be accurate for each part of the question/answer.

Community
  • 1
  • 1
seangwright
  • 17,245
  • 6
  • 42
  • 54
  • 1
    ` cbs[x](); // -> 1999 #1` will not print anything `Ford` or `1999`, it is printing `undefined` – gurvinder372 Feb 15 '16 at 06:02
  • JS is blocked scoped. So `this` will only change when called in a function. Your `cbs` array is in the same scope as `Car` – colecmc Feb 15 '16 at 06:04
  • 1
    Actually your first console log is from you doing `ford.logYear();` you get undefined on all other log messages. So you are changing the scope of `this`, you are just misinterpreting the log messages – Patrick Evans Feb 15 '16 at 06:04
  • @PatrickEvans Ah, thanks! That's what I get for coding late and copypasta. – seangwright Feb 15 '16 at 14:18

1 Answers1

4

Ignoring your late night copy paste mistake, and redefining your Car function to

function Car(name, year) {
    this.name = name;
    this.year = year
}

#1 indeed more of a question about how Array works rather then about the context and scope(*#2)

You are calling a function inside an array, So in javascript each element of an array is a property of that Array Object. for example

var a = [];
a.push('Some value'); // a.0 is a property 

but you just can not call a.0 because in javascript properties that begin with a digit cannot be referenced with dot notation, and must be accessed using bracket notation

So when you are calling cbx[0]() it is essentially same as cbx.0(). And here comes the role of Scope and Execution context in javascript. There are already many good articles and answers about Scope and Context in javascript. Just like this, So i think its not worth to explain all that here.

But fundamentally this is not defined till the execution of function(this depends on Execution context rather then scope).

So your #1 will print undefined because cbs[0]() is equivalent to cbs.0(), And this(Execution context) is set to Array itself, which is [function]. (You only have a function inside it).

And your #2 will also print undefined because Execution context is global(window object in case of browser) there. Answer to your 3rd question is, You need to Explicitly hard bind Execution context to your function.

cbs.push(ford.logYear.bind(ford));

And your 4th question is i think just an another use case of ES5's bind method, Nothing special. It is normally used because browser implementations requires that execution context(this) of console.error must be set to window.console.

Satyajeet
  • 2,004
  • 15
  • 22
  • Thanks for clarifying. I have a better understanding of `bind` now. It turns out the pattern I am using in the app I'm working on uses closures to maintain context when passing methods around, which is why adding methods to an array still works in my app's implementation. See my update to my question. – seangwright Feb 15 '16 at 23:36
  • 1
    Ohh.. seems like you were having problem with Scope and i explained Context.. :) – Satyajeet Feb 16 '16 at 04:15
  • I updated my question to fix my use of `execution context` and `scope` to be consistent and accurate when referring to `closures` vs `this`. – seangwright Feb 16 '16 at 05:15