tags:

views:

126

answers:

4

I saw the example below explained on this site and thought both answers would be 20 and not the 10 that is returned. He wrote that both the comma and assignment returns a value, not a reference. I don't quite understand what that means.

I understand it in relation to passing variables into functions or methods i.e primitive types are passed in by value and objects by reference but I'm not sure how it applies in this case.

I also understand about context and the value of 'this' (after help from stackoverflow) but I thought in both cases I would still be invoking it as a method, foo.bar() which would mean foo is the context but it seems both result in a function call bar().

Why is that and what does it all mean?

var x = 10;
var foo = {
  x: 20,
  bar: function () {return this.x;}
};

(foo.bar = foo.bar)();//returns 10
(foo.bar, foo.bar)();//returns 10
+2  A: 

You're misunderstanding it.

Both examples are returning the window's x property, since they aren't being directly invoked on foo.

The value of the this keyword inside a function depends on the context in which the function was called.

In an ordinary function call (eg, myFunc()), this will be the global object, which is usually window.
In an object method call (eg, foo.bar()), this will be the object on which the function was invoked. (in this case, foo)

You can set the context explicitly by calling myFunc.call(context, arg1, arg2) or myFunc.apply(context, argArray).

Both of your examples are normal invocations of an expression that evaluates to foo.bar.
Therefore, this is the window.

They are equivalent to

var func = (some expression);
func();
SLaks
@SLaks - I understand everything you've written including your example but in my head (foo.bar = foo.bar) still equates to foo.bar() and not bar() as your telling me (which I know is correct). Both you, CMS and TJ have given me good responses but I need to get my head around it as I'm a bit simple.
Nick Lowman
+3  A: 

It doesn't have to do with values vs. references, it has to do with this values (as you suspected). In JavaScript, this is set entirely by how a function is called, not where it's defined. You set the this value in one of three ways:

  1. Call the function via an object property using dotted notation.
  2. Call the function via an object property using a with statement (really just a variant of #1, but worth calling out separately, particularly as it's not obvious from the source code)
  3. Use the apply or call features of the function instance.

In your examples above, you're not doing any of those, so you end up calling the function with the default this value, the global object, and so x comes from there rather than from your foo object. Here's another way to think about what that code is doing:

var f = foo.bar; // Not calling it, getting a reference to it
f();             // Calls the function with `this` referencing the global object

If you don't directly use a property to actually make the call (instead retrieving the value of the property and then making the call with that), the this handling doesn't kick in.

T.J. Crowder
It has to do with the Reference Type, since both, the comma operator and the simple assignment internally use `GetValue`, and this causes that the *base object* of the reference to be lost.
CMS
@CMS: Right, that's the underlying mechanism behind the behavior I described. I didn't think delving into those depths was needed. (I didn't talk about the [[Call]] method or [[Scope]] property, either :-) ).
T.J. Crowder
Nick Lowman
@TJ -I forgot to say thanks for taking the time respond to me. I appreciate the help.
Nick Lowman
@Nick, I tried to elaborate more about the behavior of the simple assignment operator, give a look to my edit, and feel free to ask any doubts...
CMS
@Nick: The key to `(foo.bar = foo.bar)()` is to remember that you're calling the *result of the expression*. What's the result of that expression? Right, a function reference -- completely unbound from any property, exactly like `(f = foo.bar)()`. You don't have to delve into the plumbing to understand it, that's the key part. If you *want* to, though, CMS has started you off -- but there's nothing like reading the spec (http://www.ecma-international.org/publications/standards/Ecma-262.htm, §8.7). It's heavy going, though; unless you really study it, you may end up more confused, not less. :-)
T.J. Crowder
@Nick: Continuing, maybe thinking about `(f = foo.bar = foo.bar)()` might help. What's the thing travelling through that series of assignments? The point is that once you've dereferenced `foo.bar` *without* calling it, you just have a function reference, disconnected from the property. The only time it remains connected to the property is if you refer to the property and make the call without an intermediate expression (like an assignment). And kudos, BTW, for hanging out on the fringes of the esoterica of the language. ;-)
T.J. Crowder
Nick Lowman
@Nick: Yup, because the call isn't made directly through dotted notation. The value is retrieved from the property, assigned to another property and then used (not via either of those properties) when making the call. Have fun!
T.J. Crowder
+4  A: 

You should understand how the internal Reference Type works.

Note: This is not a language data type, is an internal mechanism to handle references.

A reference is composed of two elements, the base object and a property name.

In your example, the foo.bar reference looks like this.

// pseudo-code
foo.bar = {
  baseObject: foo,
  propertyName: 'bar'
}

Both, the comma operator and a simple assignment, rely on getting the value of the property name, that causes the base object to be lost, since a single value is returned (this is made through the internal GetValue operation).

This is how the internal GetValue operation works:

// pseudo-code
GetValue(V) :
  if (Type(V) != Reference) return V;

  baseObject = GetBase(V); // in your example foo
  if (baseObject === null) throw ReferenceError;

  return baseObject.[[Get]](GetPropertyName(V)); 
  // equivalent to baseObject[v.PropertyName];

As you see, a value is returned, so the original reference is lost.

Edit: The key to understand why (foo.bar = foo.bar)(); is not equivalent to foo.bar(); relies in the Simple Assignment Operator, let's see the algorithm:

11.13.1 Simple Assignment (`=`)
The production `AssignmentExpression` :
               `LeftHandSideExpression` = `AssignmentExpression`

is evaluated as follows:

1. Evaluate LeftHandSideExpression.

2. Evaluate AssignmentExpression.

3.Call GetValue(Result(2)).

4.Call PutValue(Result(1), Result(3)).

5.Return Result(3).

Basically when you make (foo.bar = foo.bar) the actual assignment (Step 4.) has no effect because PutValue will only get the value of the reference and will place it back, with the same base object.

The key is that the assignment operator returns (Step 5) the value obtained in the Step 3 and as I said before in the GetValue pseudo-code, this internal method returns a value which doesn't really have a base object.

CMS
@CMS - That'll teach me. I said to T.J. Crowder make it as technical as you like but now I can't understand it. I will need to think about what you've written and come back to you if that's OK.
Nick Lowman
@Nick, sure, think about it, we are dealing with a completely abstract type, that exist in the specification purely for *expository purposes*. That can make it a little bit tough to gasp. Read about it and about the `GetBase`, `GetPropertyName` and `GetValue` internal methods, then we can go one step further and I can explain you how the Reference type is used to set the `this` value when you make a function call.
CMS
@CMS - Thanks man. Your understanding of JavaScript is incredible. I will take away what you've said and try to understand it, with some research as well. At this point I have to award the correct answer which I will give to TJ, not because his answer was better as all the responses were great but because I have to choose one. I'll +1 you and the others as I thought they we're all good, helpful answers. Thanks again for your help.
Nick Lowman
@Nick - You're welcome, try to understand it and if you encounter any questions on the way, don't hesitate to ask! ;)
CMS
+2  A: 

It may help to think of the dot operator as behaving similarly to a with statement. When you run foo.bar(), the result is much the same as if you ran:

with (foo) {
    bar(); // returns 20
}

This will run your bar function with foo overlaid on top of the global object, allowing it to find the x in foo and thus return 20.

If you don't call bar immediately, though, as in (foo.bar = foo.bar), you instead get:

with (foo) {
    bar; // returns "bar" itself
}

The result is then passed out of the parentheses, producing an intermediate statement like <reference to bar>(), which has no dot operator, so no with statement, so no access to foo, just to the global value of x.

(The dot operator doesn't actually convert to a with statement, of course, but the behavior is similar.)

Ben Blank
@Ben - Thanks man - That helps me a lot. Hadn't actually used the with statement before but it give me something to research. So in this example (nick.foo.low.man = foo.man.tan.bar)() it would be the same aswith (foo.man.tan) { bar; // returns "bar" itself}
Nick Lowman