Thor
Thor

Reputation: 10068

what is the javascript mechanism/rules that allow `p.foo = o.foo` to return a reference to the function `foo`?

I'm currently studying javascript by following the book "you dont know js" series.

In the "this & object prototype" section, when discussing "indirect references to functions", the author states

function foo() {
  console.log( this.a );
}

var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // 2

The result value of the assignment expression p.foo = o.foo is a reference to just the underlying function object. As such, the effective call-site is just foo(), not p.foo() or o.foo() as you might expect. Per the rules above, the default binding rule applies.

So apparently, (p.foo = o.foo) return a reference to the function foo. But what is the mechanism/rules that allow (p.foo = o.foo) return a reference to the function foo? In other words, why a simple assignment return a reference to foo function?

Upvotes: 3

Views: 321

Answers (2)

nem035
nem035

Reputation: 35501

The assignment operator = is an expression in JavaScript that produces (returns) the assigned value. Because it is an expression it can be used anywhere an expression is allowed, such as inside parenthesis.

For example:

let test = (a = b = c = { name: 'test' })

The code above would first evaluate the expression in the parenthesis and point the variables c, b, and a to the test object (in that order), then it would point test to the produced value from this expression. After that line is executed, a, b, c, and test will all point to the same object.

Similarly,

(p.foo = o.foo)

Would produce o.foo back (technically it would produce whatever o.foo is pointing to, which is the function foo).

As far as

(p.foo = o.foo)()

By adding the additional () after the parenths, we are telling the engine that we want to invoke whatever the expression (p.foo = o.foo) ends up producing. Thus we end up invoking the function foo. Similar patterns is used in IIFEs.

A helpful rewrite would be to think of the line above as doing this:

let produced = (p.foo = o.foo)
produced()

Further reading about statements vs expressions.

Upvotes: 1

Michael Geary
Michael Geary

Reputation: 28870

When I want to understand something like this, I find it helpful to break it down step by step.

  1. o.foo looks at the o object and finds a property named foo. It returns a reference that property, whatever it might be. In this case, the o.foo property is a reference to the function foo.
  2. p.foo = o.foo takes the result from above (a reference to the function foo), creates a property in the p object which is also named foo. So now p.foo is also a reference to the foo function, exactly the same thing as o.foo.
  3. That expression is wrapped in parentheses, so now you have whatever was on the left side of the = sign, or p.foo, which is (as a reminder) still a reference to the foo function.
  4. Now we find the () at the end. This calls whatever function we have on hand at this moment. That is the foo function. Note in particular that we are not calling p.foo(). That would be a method call to the function that p.foo is a reference to, so inside that function, this would be set to p. But we're not doing that. We're just calling whatever function was returned by ( p.foo = o.foo ). As before, this is the same foo function, but we've now lost any connection it may have ever had to the o object or the p object.
  5. So, when we make that call at the end, we are merely calling the foo function without setting this to any particular object. Because of that, when we make the call, this is set to undefined.
  6. But we're not running in strict mode, so JavaScript "helpfully" doesn't want to give us an undefined this, so it sets this to the window object in a browser or the global object in Node.
  7. Previously we did var a = 2;. So the window or global object actually now has a property named a, and the value of that property is 2.
  8. So now when we do the console.log(this.a), we pick up the a property from the window or global object. That value is 2.

What if all this code was not running at the global level, but instead it was inside a function? What would happen then?

function test() {
  function foo() {
    console.log( this.a );
  }
  
  var a = 2;
  var o = { a: 3, foo: foo };
  var p = { a: 4 };

  o.foo(); // 3
  (p.foo = o.foo)(); // was 2, but now is undefined
}

test();

Now when we call console.log( this.a ); inside foo, this still refers to the window or global object. But when we set var a = 2;, we aren't setting a global property any more. We're just creating a local variable. window.a or global.a is undefined (unless some other code previously set it).

Strict mode avoids some this weirdness. If we put a 'use strict'; at the top of the code, it will compile in strict mode. And now when we make that last function call at the end, where we're calling the foo function (again not as a method!), this is now set to undefined instead of window or global. Therefore, when we try to call console.log(this.a), the code fails because this is the same as undefined, and undefined does not (and couldn't) have an a property.

Let's try it:

'use strict';

function foo() {
  console.log( this.a );
}

var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // was 2 in the original, but now throws an exception

The bottom line, at least for this particular example: always use strict mode! It is your friend.

Upvotes: 2

Related Questions