Suppose I have a function Foo
, and I want objects constructed from it to have a bar
property:
function Foo() {}
Foo.prototype.bar = 'baz'
console.log('new Foo().bar: ' + new Foo().bar)
new Foo().bar: baz
Now suppose I bind Foo
in some way. Bound functions can still be used in constructor calls, and the bound this
is ignored:
const Bound = Foo.bind(42)
console.log('new Bound().bar: ' + new Bound().bar)
new Bound().bar: baz
Proxies are supposed to be general and transparent. However...
const PFoo = new Proxy(Foo, { })
console.log('new PFoo().bar: ' + new PFoo().bar)
const PBound = new Proxy(Bound, { })
console.log('new PBound().bar: ' + new PBound().bar)
new PFoo().bar: baz
new PBound().bar: undefined
I would expect the second proxy to behave exactly as Bound
, since I am using an empty handler. In other words, I would expect the last output to be baz
.
Why is it not the case?
(complete snippet follows)
function Foo() {}
Foo.prototype.bar = 'baz'
console.log('new Foo().bar: ' + new Foo().bar)
const Bound = Foo.bind(42)
console.log('new Bound().bar: ' + new Bound().bar)
const PFoo = new Proxy(Foo, { })
console.log('new PFoo().bar: ' + new PFoo().bar)
const PBound = new Proxy(Bound, { })
console.log('new PBound().bar: ' + new PBound().bar)
When using new F
, new.target
is set to F
unless F
is a bound function, in that case new.target
becomes the original function. This does not happen with proxies.
Ok, I think I got it. This comment was a good starting point. Key ingredients:
new.target
prototype
propertyNote: in constructor calls, the new object's prototype is set to new.target.prototype
. This is step 5 from this specification.
Starting point: when doing new F()
, new.target
is initially set to F
(follow links). However, this can change during the process of construction...
new Foo()
Nothing weird here, new.target
is Foo
and the newly created object prototype is Foo.prototype
.
new Bound()
This is interesting. At the beginning, new.target
is Bound
. However, step 5 of bound functions [[Construct]] internal method does the following: if new.target
is set to the bound function, then it is changed to the target function, which is Foo
. Thus, Foo.prototype
is used again.
new PFoo()
new.target
is always PFoo
, but it is a proxy for Foo
, so when PFoo.prototype
is requested Foo.prototype
is given, again.
new PBound()
new.target
is set to PBound
. This time, when the [[Construct]] internal method of the bound function is called, new.target
is not equal to the bound function, so it is not changed, and we end up using PBound.prototype
, which is forwarding to Bound.prototype
. Indeed...
function Foo() { }
Foo.prototype.iAm = 'Foo'
const Bound = Foo.bind(42)
Bound.prototype = {iAm: 'Bound'}
const Proxied = new Proxy(Bound, { })
console.log(new Proxied().iAm)
Personal opinion: I understand that, when constructing a bound function, new.target
is changed so that everything works as expected. Similarly, I would expect the construction of proxy objects to set new.target
to the proxied function before going on. This is a naive thought, and maybe there are corner cases I am not considering.