11

I have this simple code :

var o = {
    a: 1,
    b: 2,
    f1: function ()
    {
        alert(this.b);
    }
}

var o2 = {
    a: 11,
    b: 22,
    f2: function (j)
    {
        j();
    }
}

But running this code :

o2.f2(o.f1) yields undefined. ( while im expecting "22" as a result)

Now, I know that the context has gone somewhere. and hence If I change the code in o2 to :

 f2: function (j)
    {
        j.apply(this);
    }

It does work obviously.

But my question is :

  • In what stage did i lose the context ?

I don't understand : when j() is running , there is a b property in the o2 object.

What am I missing ?

jsbin

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • 11
    When you call it as `f()` - JavaScript methods are *"unbound functions"* (that is, unlike methods in other languages they are *not* associated with a particular object/instance) and it is the call-site that determines the `this` when invoked. (Of course, see [`Function.bind`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind) or equivalent emulations.) – user2246674 May 05 '13 at 07:01
  • You lose it as soon as you pass around `o.f1`. If you do `var x = o.f1` and call `x()`, `f1` isn't bound anymore. – Blender May 05 '13 at 07:05
  • @Blender I know that. but why `this` is not regarding `o2` ? this is what i dont understand – Royi Namir May 05 '13 at 07:07
  • @RoyiNamir `f()` is roughly equivalent to `window.f = f; window.f()` in how it affects `this` inside the invoked function. [Here are the gory details](http://es5.github.io/#x10.4.3). – user2246674 May 05 '13 at 07:08
  • @RoyiNamir: Why should it? It's unbound. Calling it doesn't magically bind it to the current context. – Blender May 05 '13 at 07:15

3 Answers3

8

I found Crockford had an excellent description of the way this works. Functions in JavaScript can be invoked in 4 styles :

  1. The "function" style
  2. The "method" style
  3. The "Constructor" style
  4. The "call or apply" style.

I might be getting the exact names wrong there, but the spirit is the same. You should definitely get the book "JavaScript: The Good Parts" if you don't have it.

So anyway - on to your question. The key thing is that the value if "this" is dependant on which style you use.

// function invocation style, 
var f = function() { console.debug(this); }
f(); // "this" is bound to the global object.

// "method" invocation style
var obj = {
    f: function() { console.debug(this); }
};

obj.f(); // "this" is bound to "obj", the object on which the function was invoked

// so important bit is :

var f = obj.f;
f(); // "this" is global object
obj.f() // "this" is obj

In your example you are losing "this" because of the way you are invoking the function.

Woody
  • 7,578
  • 2
  • 21
  • 25
1

If you do it like as follows,

function will be called in o2 context

var o2 = {
    a: 11,
    b: 22,
    f2: function (j){
      this.temp = j;
      this.temp();
    }
};

also these will work too:

f2: function (j){
      j.apply(this);
}

f2: function (j){
      j.apply(o2);
}

Otherwise you call it just like an ordinary function out of context.

j Is ripped out of its context and you did no tricky closures on it(which is not your intent) so for making "this" work in it you need a scope. The this scope in your question for j is window, which has no "b" in it therefore you get an "undefined".

Volkan
  • 2,212
  • 1
  • 14
  • 14
0

Check this tests:

o.f1(); // alerts 2

var f3 = o.f1; // (*)

f3(); // alerts undefined

o2.f2(f3); // alerts undefined

f3.apply(o2); // alerts 22

I realize that when you pass a function as parameter the context is lost exactly the same way it's lost in the (*) pointed in the code above.

What happening is j = arguments[0] = o.f1 and at this point you lose the context.

When passes a function as a parameter you are just passing the reference in memory to that function. Without bind the context you'll fail the simple j() call. That's why you need to use apply or the this trick showed by Ihsan.

Gustavo Carvalho
  • 2,798
  • 1
  • 19
  • 25