Skip to the code
cf.

The new operator

MediumPolyfills

Write myNew(Constructor, ...args), which does what new Constructor(...args) does for a constructor function. Don't use new or Reflect.construct: call Constructor as a plain function, so inside it new.target is undefined. That's enough for ordinary constructor functions; classes, which refuse to run without new, are out of scope.

First, create an empty object whose prototype is Constructor.prototype. If Constructor.prototype isn't an object (someone set it to null or a number), use Object.prototype instead, as new does. Then call Constructor once, with that object as this and the arguments.

If the call returns an object or a function, return that. If it returns anything else (a primitive, null, or nothing at all), return the new object. Throw a TypeError if Constructor isn't a function.

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function () {
  return "Hi, " + this.name;
};
const ada = myNew(Person, "Ada");
ada.greet();           // "Hi, Ada"
ada instanceof Person; // true

What the tests check

  1. creates an object that inherits from Constructor.prototype
    function Person(name) {
      this.name = name;
    }
    Person.prototype.greet = function () {
      return "Hi, " + this.name;
    };
    const ada = myNew(Person, "Ada");
    expect(Object.getPrototypeOf(ada)).toBe(Person.prototype);
    expect(ada.greet()).toBe("Hi, Ada");
    
  2. runs Constructor once, with the new object as this and the arguments
    const Constructor = spy();
    const made = myNew(Constructor, 1, 2);
    expect(Constructor).toHaveBeenCalledTimes(1);
    expect(Constructor).toHaveBeenCalledWith(1, 2);
    expect(Constructor.calls[0].this).toBe(made);
    
  3. gives every call a fresh object of its own
    function Counter() {
      this.count = 0;
    }
    const a = myNew(Counter);
    const b = myNew(Counter);
    expect(a).not.toBe(b);
    a.count++;
    expect(b.count).toBe(0);
    expect(Object.hasOwn(a, "count")).toBe(true);
    expect(Object.hasOwn(Counter.prototype, "count")).toBe(false);
    
  4. returns an object or function that Constructor returns
    const custom = { custom: true };
    function ReturnsObject() {
      this.ignored = true;
      return custom;
    }
    expect(myNew(ReturnsObject)).toBe(custom);
    const helper = () => "helper";
    function ReturnsFunction() {
      return helper;
    }
    expect(myNew(ReturnsFunction)).toBe(helper);
    
  5. ignores a primitive or null return value
    function ReturnsNumber() {
      this.a = 1;
      return 42;
    }
    const fromNumber = myNew(ReturnsNumber);
    expect(fromNumber).toBeInstanceOf(ReturnsNumber);
    expect(fromNumber.a).toBe(1);
    function ReturnsNull() {
      this.b = 2;
      return null;
    }
    const fromNull = myNew(ReturnsNull);
    expect(fromNull).not.toBeNull();
    expect(fromNull.b).toBe(2);
    
  6. uses Object.prototype when Constructor.prototype isn't an object
    function Plain() {
      this.ok = true;
    }
    Plain.prototype = null;
    const first = myNew(Plain);
    expect(Object.getPrototypeOf(first)).toBe(Object.prototype);
    expect(first.ok).toBe(true);
    Plain.prototype = 5;
    expect(Object.getPrototypeOf(myNew(Plain))).toBe(Object.prototype);
    
  7. throws a TypeError when Constructor isn't a function
    expect(() => myNew({})).toThrow(TypeError);
    expect(() => myNew("Person")).toThrow(TypeError);
    
  8. calls Constructor without new, so new.target is undefined
    let seen = "never called";
    function Probe() {
      seen = new.target;
    }
    myNew(Probe);
    expect(seen).toBeUndefined();
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).