Reputation: 10068
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
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
Reputation: 28870
When I want to understand something like this, I find it helpful to break it down step by step.
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
.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
.=
sign, or p.foo
, which is (as a reminder) still a reference to the foo
function.()
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.foo
function without setting this
to any particular object. Because of that, when we make the call, this
is set to undefined
.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.var a = 2;
. So the window
or global
object actually now has a property named a
, and the value of that property is 2
.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