0

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?

schen
  • 107
  • 1
  • 2
  • 7
  • Your could probably `return f` from `f`, which makes `fn === f`. But… what's the practical point really? – deceze Sep 29 '20 at 12:05
  • @deceze Hi. `return f` seems to be a nice idea. But still I am curious about the underlying logic of the `new` keyword. Lots of docs described **what** will the engine do but very few talked about **how** the keyword is parsed. And I think somehow this is important considering how basic and widely the keyword is used... – schen Sep 29 '20 at 12:17
  • I'm not sure how [the first thing you've linked to](https://stackoverflow.com/a/3658673/476) doesn't already answer the "how"… "What" vs. "how" seems to be basically the same. – deceze Sep 29 '20 at 12:19
  • 1
    **"when a new keyword is used, the constructor of an class or function is called"** this does not mean the constructor property of the prototype of the function is called when new is invoked. It means the constructor function itself is invoked. In this instance "constructor" is a shorthand for "constructor function". – Ben Aston Sep 29 '20 at 12:20
  • @deceze sorry for the ambiguity. not a good word. The doc describe the result, e.g., the new return an Object, an constructor will be called. But didn't talk about more base level things e.g. "which constructor exactly" is called, or at the base level what happened (this binding, memory allocation... etc.) – schen Sep 29 '20 at 12:24
  • @BenAston Hi, thank you for the explanation. **The new operator works with a specific subset of the callable objects **, do you know any document that specify this behavior or any keywords that might be related? – schen Sep 29 '20 at 12:28
  • Also, I forgot classes. `new` works with classes too, of course. – Ben Aston Sep 29 '20 at 12:37
  • @deceze. hi, the first link explained the relationship between objects, between prototypes and children, but I wish to find out more about a detailed "initialization process". Maybe in particularly, how to affect a child by modifying a constructor of a parent class. – schen Sep 29 '20 at 12:39
  • There is an abstract operation in the spec [`MakeConstructor`](https://tc39.es/ecma262/#sec-makeconstructor). This is used to create new-able objects. The only ways specified in the spec to invoke this operation are to create a function with the `function` keyword, or define a `class`. – Ben Aston Sep 29 '20 at 12:39
  • 1
    @BenAston. Thank you very much! That seems related. I'll dig more into that. – schen Sep 29 '20 at 12:47
  • What do you mean by "*manipulating the function `f`*", changing its code? – Bergi Sep 29 '20 at 13:01
  • 1
    Have a look at [Is a constructor always a function object?](https://stackoverflow.com/q/10393858/1048572), [What values can a constructor return to avoid returning this?](https://stackoverflow.com/q/1978049/1048572) and maybe [How to extend Function with ES6 classes?](https://stackoverflow.com/q/36871299/1048572). It's definitely possible, but you normally shouldn't be creating constructor dynamically, and even if you do then better do it in a factory. – Bergi Sep 29 '20 at 13:11

2 Answers2

1

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
Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • 1
    "*The constructor property of an object created via new is set by new,*" - no it's not. It's not an own property of the created object at all, it's just inherited from the prototype. Omit step 3 from your description. – Bergi Sep 29 '20 at 13:12
  • 1
    Btw I'd recommend to avoid referring to the deprecated `__proto__` getter in explanations. Say "internal prototype", *[[prototype]]*, or use `Object.create` and `Object.setPrototypeOf`. – Bergi Sep 29 '20 at 13:15
  • 1
    @BenAston Hey, thanks a lot for pointing out the keyword and the document. That really helped a lot. – schen Sep 29 '20 at 14:14
  • @Bergi Every property inherited from "constructorFunction".prototype goes to child.[[prototype]], if the constructorFunction returns an implicit or primitive value. Else, (the return value of the constructorFunction) === (the child element) – schen Sep 29 '20 at 14:27
  • @Bergi though I wonder why [[prototype]] is preferred by the official doc since apparently it may introduce another layer of complexity, while by console.dir() the __proto__ is observable. What might be the reason? – schen Sep 29 '20 at 14:31
  • @chnnn I'd say that the `constructor.prototype` goes into the "`this.[[protype]]`", and the `this` object is what the constructor function should work with. The return value is an orthogonal topic. – Bergi Sep 29 '20 at 15:11
  • @chnnn Regarding `__proto__` vs [[prototype]], that's just Chrome devtool's way of displaying it. It's not really an observable property. Firefox devtools display it differently, for instance. – Bergi Sep 29 '20 at 15:12
  • @Bergi ok, thank you. I also found [ECMA262-sec8.6.2](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6.2), seems that the double square bracketed elements do not belong to ECMAScript, however the behavior of them needs to match the description of the doc. So I think the **content** of obj.__proto__ is an implementation, while obj.[[prototype]] or obj.__proto__ is just different terminology used by ECMAScript and browser. See [MDN doc](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto) , obj.__proto__ is originally merely but an browser impl. – schen Sep 29 '20 at 16:09
  • @Bergi since the obj.__proto__ is not in the ECMAScript spec, the obj.[[prototype]] is more preferable and precise in describing the behavior of those listed internal elements. – schen Sep 29 '20 at 16:14
  • @Bergi, and since obj.__proto__ is an implementation, some behavior may vary among implementations. For example, it is said that in some browsers, the obj.__proto__ is writable, while `Object.getPrototypeOf` is guaranteed read-only. – schen Sep 29 '20 at 16:17
  • @chnnn Yes, precisely. Notice that since ES6, `__proto__` is even in the spec (annex b), but [only so that its behaviour *doesn't vary* among implementations](https://stackoverflow.com/a/36061819/1048572) that need to implement it for web-compatibility. – Bergi Sep 29 '20 at 18:35
  • @chnnn I have improved my answer. – Ben Aston Sep 30 '20 at 12:13
  • 1
    @Ben Aston, Thanks. I'll accept this answer because it provide many useful resources and keywords. Search `**[[Construct]]**` for more info in the [ECMA262](https://www.ecma-international.org/ecma-262/) – schen Sep 30 '20 at 12:47
0

The Scope: we exam the new on Function only. because this is the part that confuse the most. Calling new on Class yield results similar to that from other major OOP languages.

The original question could be break down to the following two questions:

  1. What is the detailed construction process, when a new keyword is called?

  2. How does JavaScript decide if an Object is Callable? (Thanks for @BenAston mentioning that the new keywords may only works with a limited set of Objects (e.g., prefixed with Class or Function))


  1. Answer to the first question:

Back to the MDN Document,

When the code new Foo(...) is executed, the following things happen:

  1. A new object is created, inheriting from Foo.prototype.
  2. The constructor function Foo is called with the specified arguments, and with this bound to the newly created object. new Foo is equivalent to new Foo(), i.e. if no argument list is specified, Foo is called without arguments.
  3. The object (not null, false, 3.1415 or other primitive types) returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn't explicitly return an object, the object created in step 1 is used instead.(Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)

The words might be ambiguous, But the PoC code is as follows:

// Case1, function has no return value; 
// A new object is created, f0n.__proto__ === f0.prototype
let f0 = function() {};
f0.prototype.f0p = "f0p";

let f0n = new f0();
console.log(f0n) // f0n is a new Object, inheriting from f0.prototype
console.log(f0n.__proto__.f0p); // "f0p"

// Case3, function has an explicit return value, the value is an object
// (not null, false, 3.1415 or other primitive types); 
// the return value becomes the new object value.
let f3 = function() {
  return {
    "f3": "f3"
  }
};
f3.prototype.f3p = "f3p";

let f3n = new f3();
console.log(f3n) // {f3: "f3"}
// f3n is an Object, the return value of its constructor function `f3`
console.log(f3n.__proto__.f3p); // undefined

// Case4 (or Case1 again), function has an **implicit** return value.
let f4 = function(a) {
  return (a + a)
};
f4.prototype.f4p = "f4p";

let f4n = new f4();
console.log(f4n.__proto__.f4p); // "f4p"

2.Answer to the second question:

I still do not yet know how JavaScript decide if a object is callable. The answer should be hiding in the ECMAScripts spec. (Thanks @BenAston for pointing out)

It might be legit to assume that only Function is callable. And the following post provide a workaround: How to make an object callable

  • extra: how to return an Callable?

Use the Case3, let f = Function(){return Function(){}} Since the return value is an non-primitive explicit Object, it becomes the result of the new directive. The result is a function, which could then be called.

schen
  • 107
  • 1
  • 2
  • 7