The reason is that the ES5 spec says so:
The Function prototype object is itself a Function object (its
[[Class]] is “Function”) that, when invoked, accepts any arguments and
returns undefined.
Note it’s common in ES5 to make the prototype of some class a member of that class:
Object.prototype
is an Object object.Function.prototype
is a Function object which returnsundefined
when invoked.Array.prototype
is an empty Array object.String.prototype
is a String object whose value is an empty String.Boolean.prototype
is a Boolean object whose value isfalse
.Number.prototype
is a Number object whose value is+0
.Date.prototype
is a Date object whose [[PrimitiveValue]] isNaN
.RegExp.prototype
is a RegExp object whose data properties are likenew RegExp()
‘s ones.Error.prototype
is an Error object.
I think it was standardized as such because the prototype of a class has the intrinsic properties of that class, as the instances of that class. And if it looks like a duck it should behave as a duck. So calling the methods of the prototype on the prototype itself instead of on an instance should work too.
However, ES6 didn’t like this. So it changed the behavior for those:
Boolean.prototype
is an ordinary object with no [[BooleanData]] internal slot.Error.prototype
is an ordinary object with no [[ErrorData]] internal slot.Number.prototype
is an ordinary object with no [[NumberData]] internal slot.Date.prototype
is an ordinary object with no [[DateValue]] internal slot.String.prototype
is an ordinary object with no [[StringData]] internal slot.RegExp.prototype
is an ordinary object with no [[RegExpMatcher]] nor any of the other internal slots of RegExp instance objects.
And also for new “classes” (ES6 objects no longer have a [[Class]]):
Symbol.prototype
is an ordinary object with no [[SymbolData]] internal slot.TypedArray.prototype
is an ordinary object with no [[ViewedArrayBuffer]] nor any other of the internal slots that are specific to TypedArray instance objects.Map.prototype
is an ordinary object with no [[MapData]] internal slot.Set.prototype
is an ordinary object with no [[SetData]] internal slot.WeakMap.prototype
is an ordinary object with no [[WeakMapData]] internal slot.WeakSet.prototype
is an ordinary object with no [[WeakSetData]] internal slot.ArrayBuffer.prototype
is an ordinary object with no [[ArrayBufferData]] nor [[ArrayBufferByteLength]] internal slots.DataView.prototype
is an ordinary object with no [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], nor [[ByteOffset]] internal slots.GeneratorFunction.prototype
is an ordinary object with no [[ECMAScriptCode]] nor any other of the internal slots listed in Table 27 or Table 56.Promise.prototype
is an ordinary object with no [[PromiseState]] nor any of the other internal slots of Promise instances.
However, the old behavior remains for those:
Function.prototype
is itself a built-in function object.Array.prototype
is an Array exotic object and has the internal methods specified for such objects.
So now the reason is backwards compatibility:
The Function prototype object is specified to be a function object to
ensure compatibility with ECMAScript code that was created prior to
the ECMAScript 2015 specification.
Note this doesn’t make Function.prototype
a special function. Only constructors have the prototype
property:
Function instances that can be used as a constructor have a
prototype
property.
There are multiple examples of non-constructor functions apart from Function.prototype
, such as
-
Methods in
Math
object:typeof Math.pow; // "function 'prototype' in Math.pow; // false
-
Some host objects:
typeof document.createElement('object'); // "function 'prototype' in document.createElement('object'); // false
-
In ES6, arrow functions:
typeof (x => x * x); // "function 'prototype' in (x => x * x); // false