Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does Reflect.construct let us do that wasn't doable before?

I'm trying to find a good reason to use Reflect.construct to achieve something noteworthy that I could not achieve before.

I'm not looking for an answer like the one here, because that example don't seem to be very useful. For example, why would I write

function greetingFactory(name) {
  return Reflect.construct(Greeting, [name]);
}

when I can just write

function greetingFactory(name) {
  return new Greeting(name);
}

?

Do you know any notable use cases for Reflect.construct?

EDIT: Seems like I may have found a use case myself, but I'm not sure if it is solid and if it won't fall apart, but basically it seems like I can make new.target work with ES5-style classes by writing them like this:

function Foo() {
  console.log('Foo, new.target:', new.target)
  this.name = "foo"
}

Foo.prototype.sayHello = function sayHello() {
  return this.name
}

function Bar() {
  console.log('Bar, new.target:', new.target)
  let _ = Reflect.construct(Foo, [], new.target)
  _.name = _.name + " bar"
  return _
}

Bar.prototype = Object.create(Foo.prototype)

Bar.prototype.sayHello = function() {
  return "Hello " + Foo.prototype.sayHello.call(this) + "!"
}

function Baz() {
  console.log('Baz, new.target:', new.target)
  let _ = Reflect.construct(Bar, [], new.target)
  _.name = _.name + " baz"
  return _
}

Baz.prototype = Object.create(Bar.prototype)

Baz.prototype.sayHello = function() {
  return Bar.prototype.sayHello.call(this) + " Hello again!"
}

let baz = new Baz

console.log(baz.sayHello())

The cool thing about it is that this is as expected inside the prototype methods!

like image 803
trusktr Avatar asked Mar 10 '23 05:03

trusktr


2 Answers

The only three use cases I know of for Reflect.construct are:

  1. Using it within a Proxy construct trap to get the default behavior (or to get slightly-modified default behavior).

  2. Using it to avoid creating and using an iterator when you need to call a constructor function using an array whose elements need to be passed as discrete arguments. You can just do

    t = new Thing(...theArray);
    

    but that involves creating and using an iterator, whereas

    t = Reflect.construct(Thing, theArray);
    

    doesn't use an iterator, which is much less work (not that it usually matters; this is for a situation where you know time is crucial). (Instead of an iterator, construct just uses length and directly accesses the 0, 1, etc. properties.)

    Neither of those options was available before ES2015. Instead, you had to do this:

    t = Thing.apply(Object.create(Thing.prototype), theArray);
    

    which worked with ES5 constructor functions. (It wouldn't work with an ES2015+ constructor function created via class, but you don't need it to — you'd use one of the two options above instead.)

  3. Using it to avoid using class when constructing an instance of a subtype of Error or Array or a web component (some people don't like to use class, and there are good arguments for that in projects that may need to be transpiled). (That said, Reflect.construct can't be perfectly polyfilled, either.)

like image 138
T.J. Crowder Avatar answered Apr 26 '23 01:04

T.J. Crowder


I've been trying to sort out a useful application of the Reflect.construct as well. I think I may have found something but it would be nice to bounce the idea off other people on a similar path.

What I was thinking is that you could use Reflect.construct to wedge a [[Prototype]] between the instantiated object and it's intended [[Prototype]]. This would allow you to shadow properties and methods that belong to the intended [[Prototype]] without being too intrusive.

function Wedge() {};

Wedge.prototype = Object.create(String.prototype);

Wedge.prototype.toUpperCase = function () {
    return "nope";
}

let someString = Reflect.construct(String, ['Foo Bar'], Wedge)

someString.toUpperCase() // "nope"
like image 26
Colin Avatar answered Apr 25 '23 23:04

Colin