javascriptconstructornew-operatorprototype-chain

javascript: what do the new keywords do internally


I know there are maybe a million similar questions out there already, e.g.,

but please hear me out.

The code:

let f = function(){console.log(".f.")};
fn = new f();

// Now:
typeof fn === "object" //true
//! fn() //TypeError: fn is not a function
//! new fn() //TypeError: fn is not a constructor

The general question would be: is it possible to create a "newable" object fn, by manipulating the functionf.

The question breaks down to the internal of the "new" keywords.

My doubt is, according to the MDN document, when a new keyword is used, the constructor of an class or function is called. However, even though fn.__proto__.constructor === f is true like all other javascript functions, fn is of type'object' (can we somehow alter it to 'function'?), and new fn() throws TypeError.

We can even add more to the mix by doing:

fn.constructor = f.constructor 
fn.__proto__ = f.__proto__
fn.prototype = f.prototype
// f.constructor === Function //true
//! fn.call(this) //fn.call is not a function

still, fn() won't work, neither does new fn or new fn().

Why?


Solution

  • The new-able objects in JavaScript are:

    1. Functions created using the function keyword (excluding generator functions)
    2. Classes (which can be treated as functions)
    3. Bound function exotic objects ("bound functions")
    4. Some host objects
    5. Proxies if they are applied to one of the above

    I know this because the only object types the new operator works with are "constructors" (specification term). A constructor is an object with a [[Construct]] internal method, and you can search the ECMAScript specification to find out which kinds of object have a [[Construct]] internal method.

    To make the result of a constructor function new-able, therefore, you must return one of the object kinds listed above.

    Note that the specification specifically says that all constructors are definitionally functions because they must support the [[Call]] internal method (note also the caveat below about host objects).

    If you want to get very advanced, then you may be interested to learn that host objects do not appear to share the ordinary limitations for constructors (presumably for legacy Web compatibility reasons), but these are exceptional.

    Explanation of the .constructor property

    When a new-able function f is declared, two objects are created: the function-object f itself, and a default object on the .prototype own-property of f. The .constructor property of this default .prototype object is automatically set by the runtime to be f. I believe classes work in a very similar fashion. Note that the fact that the name of this property was chosen to be "prototype" makes discussing prototypes quite confusing in JavaScript (as it is distinct from the [[prototype]] of the function).

    This constructor own-property on the object positioned on the .prototype property, is never read by any built-in function or operation (that I know of). I view it as vestigial from the earliest days of JavaScript - it's original intent was as a way to maintain a link between the "class" that constructed an object as a developer affordance. Host environments (eg browsers) sometimes use it to infer the "type" of an object for the purposes of communicating with the user (eg. console output), the property is writeable and therefore unreliable.

    Steps performed by the new operator

    At a high level, when new is invoked against a constructor, the following steps occur (spec contains full details):

    1. A new object o is created
    2. The [[Prototype]] ("the prototype") of o is set to the value of the .prototype property of the constructor (note this means the .constructor property is inherited by the new object)
    3. The target (ie this) of the constructor body is set to o
    4. The constructor is run with the this value defined above
    5. If there is no explicit object-type return value, then o is returned by default