26

Recently one of my friends asked me the output of following code

var length = 10;

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

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1);

I thought the answer would be 10 10 but surprisingly for second call i.e. arguments[0](); the value comes out to be 2 which is length of the arguments passed. In other words it seems arguments[0](); has been converted into fn.call(arguments);.

Why this behavior? Is there a link/resource for such a behavior?

Alex K.
  • 159,548
  • 29
  • 245
  • 267
Pankaj Shukla
  • 2,435
  • 2
  • 9
  • 17
  • 3
    Well it calls the `0` function of thr `arguments` object. So thats similar to `a.b()` were `b` is called in `a`s context. Still interesting... – Jonas Wilms Feb 16 '18 at 18:33
  • the `arguments[0]` does not hold a reference to the function `fn` declared before it's limited to it's own scope where `this` refers to the `arguments` array. – Niladri Feb 16 '18 at 18:36
  • Here's a resource to look at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#Function_context – musicnothing Feb 16 '18 at 18:39
  • 2
    @floor yes thats somehow related, but no, this is not a dupe. – Jonas Wilms Feb 16 '18 at 18:43
  • Both calls are returning the number of arguments given to fn(), one is doing it through this, the other is executing the function given as argument[0], if you were to add arguments to the line `obj.method(fn, 1);` it would be reflected in the console output. The this keyword uses function scope so the function scope of this when invoked is the containing function method() so any arguments passed to method would be included in the this.length property. – Matthew Lagerwey Feb 16 '18 at 18:46
  • 1
    Agree with @Jonas that this is not a duplicate. – Furqan Shaikh Feb 20 '18 at 08:52

4 Answers4

24

The difference is because of the this context of each method call.

In the first instance, because the call is merely fn();, the this context is Window. The var length = 10; variable declaration at the top happens in the root/Window context, so window.length should be 10, hence the 10 in the console from the first function call.

Because arguments is not an array but is actually an Object of type Arguments, calling arguments[0]() means that the this context of the function call will be of the parent Object, so this.length is equivalent to arguments.length, hence the 2 (since there are 2 arguments). (See @Travis J's answer for a more thorough explanation of this part.)

If you were to add

this.fn = fn;
this.fn();

to the method() function, the result would be 5.

musicnothing
  • 3,899
  • 21
  • 42
  • I would like to add to this answer this screenshot as a proof: https://imgur.com/a/OEOIs `this` in this case is the argument – Amr Elgarhy Feb 16 '18 at 18:38
  • I think just to be clearer maybe you can mention arguments[0] is actually `arguments.0` and hence due to dot notation this refers to arguments object. – Pankaj Shukla Feb 16 '18 at 19:44
  • "dot notation" has nothing to do with this, and you cannot use `.0` like that anyway. I fully agree with this answer as written, the ExecutionContext which holds the this binding is that of the Arguments object. It is explained very well in my opinion. – Travis J Feb 16 '18 at 20:07
  • @TravisJ I know you cannot use `.0`. However, my point is that arguments being an object with a property 0 is being referred to using bracket notation arguments[0]. – Pankaj Shukla Feb 16 '18 at 20:18
  • @PankajShukla - The values are not properties of the arguments object. Please see my answer for an expansion on this topic. – Travis J Feb 16 '18 at 22:38
  • @TravisJ I just added a link to your answer for clarity's sake – musicnothing Feb 19 '18 at 18:30
5

It is due to how this works within the various scopes in which it is referenced.

Notice the outputs of the this.toString() and you will see what the referenced target is.

Starting with calling the function f directly from the Window, this will reference Window and thus the length will be Window.length which has been declared to be 10.

Moving on to if we assign f directly as a method of obj, then this would reference obj and thus the length will be obj.length which has been declared to be 5.

Where it gets interesting/confusing is when you pass in f as a parameter to function method of obj.

NOTE: The outcome here will be browser specific. Run it in Safari and Chrome and observe the different outputs.

On both browsers: arguments[0]() is pseudo equivalent to arguments.0() (although not syntacticly allowed for arguments) which is exactly the same behavior observed earlier with obj.fn() which means arguments is the reference target. Which as noticed is the number of arguments passed to obj.method.

The execution of fn inside of method is how a callback function works for which you can find a more expansive answer here.

var length = 10;

function f() {
    console.log(this.toString());
    console.log(this.length);
}

var obj = {
  length: 5,
  fn: f,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

f()
f(1);
obj.fn();
obj.fn(1);
obj.method(f, 1);
obj.method(f, 1, 2);
Jason Cust
  • 9,627
  • 2
  • 27
  • 42
1

@musicnothing is fully correct, the this binding is different between the two different calls to fn.

However, there still seems to be some confusion as to the reasoning for why arguments is now the this target which I will address.

thisMDN bindings are held inside of Execution ContextsECMA, which are essentially what manages scope in JavaScript.

When a function is called, an arguments object is constructed. The arguments object has its own Execution Context, which means it has its own this binding, its own Variable Environment, and its own Lexical Environment. When constructed, the arguments object values are stored in its Variable Environment, making any references from that point relative to the arguments object's Execution Context.

By design, the arguments object is array-like, which basically means it agrees to have a length property, and more vaguely that accessing an index which is less than the length property should have a value. As a result you can access its value references with indexes through a facade, however, it is important to keep in mind where they are scoped to at that point.

Travis J
  • 77,009
  • 39
  • 185
  • 250
0

Answer is 10 ,2 due to different values of "this".

  1. when fn() called , that time this will refer window object . and in window object length is a property which value is 10.
  2. when arguments0 called , this refers method . where method is a function and every function have a length property which value is number of parameters passed at the time of call.
surjeet
  • 1
  • 1
  • The answer 10 is never given as an output, because the this keyword binds to a function, the only time in this code 10 would be returned is if you included a line outside a function definition `this.length` in order to access the global length inside a function you would have to use window.length. So your answer is half right. – Matthew Lagerwey Feb 16 '18 at 18:54
  • When fn() called , this was referring to window object. And here length is a property in window object. So its value is 10. "So your answer is half right" , i did not get this , can you explain ? – surjeet Feb 17 '18 at 06:25
  • Yes, what was important was the binding of the 'this' statement. But, the code has been changed now so all arguments are moot, now it's time to close the question or remove your answer or it will get down voted when someone happens upon it and it doesn't match up to the code. There is a whole page on MDN on the binding of 'this' and it also varies between using strict mode and not using strict mode. – Matthew Lagerwey Feb 17 '18 at 07:09