The tools
You can make this tree yourself by using a few JavaScript methods.
In this approach, I’m going to use Map a lot, because it allows us to easily map arbitrary values to each other (i.e. keys are not just strings and symbols like in objects), along with Set.
Getting the prototype
Inheritance in JavaScript works through the internal Prototype of an object.
It can be observed with Object.getPrototypeOf.
The prototype property of a derived constructor (function) is an instance of the base constructor (function); and its prototype property is the next step up the prototype chain.
These relationships clarify this:
Object.getPrototypeOf(Node.prototype) === EventTarget.prototype // A Node inherits properties from the EventTarget Prototype (EventTarget is the super-class of Node).
Object.getPrototypeOf(EventTarget.prototype) === Object.prototype // An EventTarget inherits properties from the Object Prototype (Object is the super-class of EventTarget).
Object.getPrototypeOf(Object.prototype) === null // An Object doesn’t inherit properties from anything (Object is a base class).
Note that constructors’ inheritance behavior can be misleading and is not what we’re going to be using:
Object.getPrototypeOf(Node) === EventTarget // This works, doesn’t it?
Object.getPrototypeOf(EventTarget) === Function.prototype // Function is _not_ the super-class of EventTarget; this is just the base-case for a constructor, which is a function.
Object.getPrototypeOf(Object) === Function.prototype // Again, Function is only shown here because the constructor is an instance of it.
The prototype chain ends when trying to read the internal Prototype of an object reports null, which, in Web browsers, initially, only happens at Object.getPrototypeOf(Object.prototype).
This works for all built-in and host-defined constructors, except Proxy, which doesn’t have a prototype property despite being a constructor.
The reason it doesn’t (need to) have one is that a Proxy “instance” (i.e. new Proxy(target, handlers)) gets the Prototype of whatever the first argument (the Proxy target) is upon construction with new.
We’ll leave it out for now.
Getting all classes
Getting all constructors is possible since most built-in and host-defined constructors are global, with the notable exception of TypedArray.
Using Object.getOwnPropertyDescriptors yields all global properties and their descriptors.
(On the web, window can be used instead of globalThis, on Node, it’s global.)
The descriptor contains some settings, e.g. if the property can be seen in a for–in loop, etc.
If the property is a getter / setter pair, you’ll see the corresponding get and set functions.
Any normal property has a value descriptor.
No constructor is a getter / setter pair, so a value must exist, and since all the constructors are global properties, we’re looking for functions.
As mentioned earlier, these constructors must either have a prototype property, or be Proxy.
Object.entries(Object.getOwnPropertyDescriptors(globalThis))
.filter(([_, {value}]) => value === Proxy || typeof value === "function" && value.hasOwnProperty("prototype"))
This gets a list of all constructors, but since Proxy is a special case and Object has a pesky “Null Prototype” to deal with, let’s actually filter them out and deal with them manually.
const allConstructors = Object.entries(Object.getOwnPropertyDescriptors(globalThis))
.filter(([_, {value}]) => value !== Object && typeof value === "function" && value.hasOwnProperty("prototype"));
Generating the tree
We’ll initialize three Maps:
classInheritanceTree is the tree with the inheritance structure.
classInheritanceReferences is a flat structure mapping each constructor to a reference within classInheritanceTree.
constructorNames maps each constructor to any name associated with it.
const classInheritanceTree = new Map([
[
null,
new Map([
[
Object,
new Map()
]
])
],
]),
classInheritanceReferences = new Map([
[ null, classInheritanceTree.get(null) ],
[ Object, classInheritanceTree.get(null).get(Object) ]
]),
constructorNames = new Map([
[
null,
new Set([
"null"
])
],
[
Object,
new Set([
"Object"
])
]
]);
Sure, null isn’t really part of the inheritance tree, but for visualization purposes it serves as a useful tree root.
Note that .constructor.name doesn’t always match the property name on globalThis, e.g. in Firefox 90: webkitURL.name === "URL" and WebKitCSSMatrix.name === "DOMMatrix", and also webkitURL === URL and WebKitCSSMatrix === DOMMatrix.
That’s why the values of constructorNames are Sets containing all aliases.
We populate all three maps simultaneously by iterating all constructors and determining the constructor of their prototypes.
The self-call of the populateInheritanceTree function only ensures that a super-class exists in all Maps before placing its sub-classes into the structure.
classInheritanceTree is only implicitly populated as classInheritanceReferences is populated: the latter contains references to the Maps inside the prior, so by updating one, we mutate the other as well.
allConstructors.forEach(function populateInheritanceTree([name, {value}]){
const superClass = Object.getPrototypeOf(value.prototype).constructor;
// Make sure that the super-class is included in `classInheritanceReferences`;
// call function itself with parameters corresponding to the super-class.
if(!classInheritanceReferences.has(superClass)){
populateInheritanceTree([
superClass.name,
{
value: superClass
}
]);
}
// If the class isn’t already included, place a reference into `classInheritanceReferences`
// and implicitly into `classInheritanceTree` (via `.get(superClass)`).
// Both Map values refer to the same Map reference: `subClasses`.
if(!classInheritanceReferences.has(value)){
const subClasses = new Map();
classInheritanceReferences
.set(value, subClasses)
.get(superClass)
.set(value, subClasses);
}
// Create set for all names and aliases.
if(!constructorNames.has(value)){
constructorNames.set(value, new Set());
}
// Add the property name.
constructorNames.get(value)
.add(name);
// Add the constructor’s `name` property if it exists (it may be different).
if(value.name){
constructorNames.get(value)
.add(value.name);
}
});
Visualizing the tree
Once we have the classInheritanceTree, let’s put them into a <ul>–<li> structure.
We’ll add a data-collapsed attribute to track which elements are expandable, and which of those are expanded and which are collapsed.
const visualizeTree = (map, names) => Array.from(map)
.map(([constructor, subMap]) => {
const listItem = document.createElement("li"),
listItemLabel = document.createElement("span");
listItemLabel.append(...Array.from(names.get(constructor))
.flatMap((textContent) => [
Object.assign(document.createElement("code"), {
textContent
}),
", "
])
.slice(0, -1));
listItem.append(listItemLabel);
if(subMap.size){
const subList = document.createElement("ul");
listItem.setAttribute("data-collapsed", "false");
listItem.append(subList);
subList.append(...visualizeTree(subMap, names));
}
return listItem;
});
document.body.appendChild(document.createElement("ul"))
.append(...visualizeTree(classInheritanceTree, constructorNames));
We sort the list items alphabetically, but list expandable ones first.
The rest is just a bit of UI handling and CSS…
A tree of all constructors (except Proxy) exposed on the Web (normal browser context, not e.g. Worker)
This code puts all the previous steps together.
Click on each expandable item to expand or collapse it.
There’s also a picture of the result at the bottom.
However, I know you’ve asked about the Web APIs or the DOM APIs.
These are difficult to isolate automatically, but hopefully that’s already helpful for now.
Exercise for the reader: automatically include links to MDN for each name in the tree.
"use strict";
const allConstructors = Object.entries(Object.getOwnPropertyDescriptors(globalThis))
.filter(([_, {value}]) => value !== Object && typeof value === "function" && value.hasOwnProperty("prototype")),
classInheritanceTree = new Map([
[
null,
new Map([
[
Object,
new Map()
]
])
]
]),
classInheritanceReferences = new Map([
[ null, classInheritanceTree.get(null) ],
[ Object, classInheritanceTree.get(null).get(Object) ]
]),
constructorNames = new Map([
[
null,
new Set([
"null"
])
],
[
Object,
new Set([
"Object"
])
]
]),
visualizeTree = (map, names) => Array.from(map)
.map(([constructor, subMap]) => {
const listItem = document.createElement("li"),
listItemLabel = document.createElement("span");
listItemLabel.append(...Array.from(names.get(constructor))
.flatMap((textContent) => [
Object.assign(document.createElement("code"), {
textContent
}),
", "
])
.slice(0, -1));
listItem.append(listItemLabel);
if(subMap.size){
const subList = document.createElement("ul");
listItem.setAttribute("data-collapsed", "false");
listItem.append(subList);
subList.append(...visualizeTree(subMap, names));
}
return listItem;
})
.sort((listItemA, listItemB) => listItemB.hasAttribute("data-collapsed") - listItemA.hasAttribute("data-collapsed") || listItemA.textContent.localeCompare(listItemB.textContent));
allConstructors.forEach(function populateInheritanceTree([name, {value}]){
const superClass = Object.getPrototypeOf(value.prototype).constructor;
if(!classInheritanceReferences.has(superClass)){
populateInheritanceTree([
superClass.name,
{
value: superClass
}
]);
}
if(!classInheritanceReferences.has(value)){
const subClasses = new Map();
classInheritanceReferences
.set(value, subClasses)
.get(superClass)
.set(value, subClasses);
}
if(!constructorNames.has(value)){
constructorNames.set(value, new Set());
}
constructorNames.get(value)
.add(name);
if(value.name){
constructorNames.get(value)
.add(value.name);
}
});
document.body.appendChild(document.createElement("ul"))
.append(...visualizeTree(classInheritanceTree, constructorNames));
addEventListener("click", ({target}) => {
if(target.closest("span") && target.closest("li").hasAttribute("data-collapsed")){
target.closest("li").setAttribute("data-collapsed", JSON.stringify(!JSON.parse(target.closest("li").getAttribute("data-collapsed"))));
}
});
ul{
padding-left: 2em;
}
li{
padding-left: .3em;
list-style-type: disc;
}
li[data-collapsed] > span{
cursor: pointer;
}
li[data-collapsed] > span:hover{
background: #ccc;
}
li[data-collapsed='false']{
list-style-type: '▼';
}
li[data-collapsed='true']{
list-style-type: '▶';
}
li[data-collapsed='true'] > ul{
display: none;
}
This is how it looks like on my Firefox Nightly 90.0a1.