4

my code is:

var length = 20;
function fn(){
    console.log(this.length);
}

var o = {
    length:10,
    e:function (fn){
       fn();
       arguments[0]();
    }
}

o.e(fn);

the output is 20,1,who can tell me why?

artwl
  • 3,502
  • 6
  • 38
  • 53
  • Do some reading on [`call`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/call) and [`apply`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply) – mu is too short Sep 03 '12 at 03:44
  • @muistooshort you're saying of `fn()` and `arguments[0]()`, one calls `call` and the other calls `apply`? – McGarnagle Sep 03 '12 at 03:46
  • 1
    In the first call where you get 20 `this === window`, in the second `this===arguments`. Change console.log to `console.log(this.length,this);` and the call to `o.e(fn,'test');` and you see that you get the arguments array. – some Sep 03 '12 at 03:52
  • 1
    @dbaseman: Those were more about doing `fn()` with an explicit `this`. The `arguments[0]()` one behaves as it does because there is a hidden dot so it is sort of like saying `arguments.0()` (if the latter was valid JavaScript of course). – mu is too short Sep 03 '12 at 05:13

2 Answers2

6

When the this keyword occurs inside a function, its value depends on how the function is called.

In your case, fn() is called without providing the a this value, so the default value is window. With arguments[0](), the context is the arguments object, whose length is 1.

The point is it does not matter where the function is called, but it matters how the function is called.

var length = 20;
function fn(){
    console.log(this.length);
}

var o = {
    length:10,
    e:function (fn){
       fn(); // this will be the window.
       arguments[0](); // this will be arguments object.
    }
}

o.e(fn);

Further more, if you want this to be the object o, you could use call or apply, or bind an object first.

var length = 20;
function fn(){
    console.log(this.length);
}

var o = {
    length:10,
    e:function (fn){
       var fn2 = fn.bind(this);
       fn.call(this); // this in fn will be the object o.
       fn.apply(this); // this in fn will be the object o.
       fn2(); // this also will be the object o.
    }
}

o.e(fn);
xdazz
  • 158,678
  • 38
  • 247
  • 274
  • `this` is not context, it is a component of an execution context (or lexical environment in ES5). You should have written `fn() is called without providing a this value`. Calling `this` context is just confusing. – RobG Sep 03 '12 at 04:00
  • Oh, and if the code was in strict mode, then a Type error would be thrown as it will not set `this` to `window` if a value isn't provided (it will be undefined). – RobG Sep 03 '12 at 04:06
  • @RobG Yes, in strict mode, this will be undefined when do `fn();` – xdazz Sep 03 '12 at 04:07
2

Let's expand your code a little bit:

var length = 20;
function fn() {
    console.log(this, this.length);
}

var o = {
    length: 10,
    e: function(fn) {
        fn();
        fn.call(this);
        arguments[0]();
    }
}

o.e(fn);​

Demo: http://jsfiddle.net/ambiguous/Ckf2b/

Now we can see what this is (and thus where this.length comes from) when fn is called. This gives me the following output:

DOMWindow 0
Object 10
[function fn() { console.log(this, this.length); }] 1

We also have three ways of calling the function fn:

  1. fn(): just call it like any old function.
  2. fn.call(this): Use call to force a specific context (AKA this).
  3. arguments[0](): Call fn through the arguments object.

When you say fn(), there is no explicit value of this anywhere in sight so, in a browser, you get window as your this. The global window happens to have a length property:

Returns the number of frames (either frame or iframe elements) in the window.

That's where the zero (in my output) comes from, your window.length may be different.

We call e as o.e(fn) so this inside e is o, that's what o.e(...) means (barring bound functions and related complexities). So the this in fn.call(this) is o and that makes fn.call(this) the same (more or less) as o.fn = fn; o.fn() and we get o and 10 in the console. Notice that dot showing up again?

fn.call(o) is like o.fn = fn; o.fn()

The third one, arguments[0](), contains a hidden dot as p = 'm'; o[p] is (more or less) the same as o.m so arguments[0]() is like fn = arguments[0]; arguments.fn = fn; arguments.fn().

mu is too short
  • 426,620
  • 70
  • 833
  • 800