There are 4 ways to create new objects in JavaScript:
- Object initializers, also known as literal notation
Object.create- Constructors
- ES6 classes
Depending on which method you choose, the newly created object will have a different prototype chain1.
1. Object initializers
let x = { a: 1 };
Object.prototype.isPrototypeOf(x); // trueObjects created in this manner will have Object.prototype as its top-level prototype:
x => Object.prototypeArrays and functions also have their own literal notation:
let y = [1, 2, 3];
Array.prototype.isPrototypeOf(y); // true
Object.prototype.isPrototypeOf(Array.prototype); // truelet z = () => {}; // ES6 fat arrow syntax
Function.prototype.isPrototypeOf(z); // true
Object.prototype.isPrototypeOf(Function.prototype); // trueIn these cases, y’s and z’s prototype chains will be
y => Array.prototype => Object.prototypeand
z => Function.prototype => Object.prototyperespectively.
2. Object.create
Object.create takes in an arbitrary object (or null) as a first argument, which will be the prototype of the new object2.
let x = {
a: 1,
};
let y = Object.create(x);
y.a === 1; // true
x.isPrototypeOf(y); // true
Object.prototype.isPrototypeOf(x); // trueThus, y’s prototype chain is:
y => x => Object.prototypeObject.create is actually quite special because any arbitrary object can be specified as the prototype, so we can do otherwise nonsensical things such as:
let x = [1, 2, 3];
let y = Object.create(x);
y.forEach; // is valid, returns function forEach()
x.isPrototypeOf(y); // trueIn this case, y’s prototype chain will be:
y => x => Array.prototype => Object.prototype3. Constructors
When a3 function Thing is invoked with the new keyword, as in let x = new Thing(), it behaves as a constructor function, which means the following things will happen:
- A new, empty object is created, whose prototype is Thing.prototype (the prototype object of the
Thingfunction object) - The body of the function
Thingis executed, with itsthisset to the new empty object - The return value of the
Thingfunction is the result of thenew Thing()expression, unless no return value is specified, then the new object is returned
function Thing() {}
let z = new Thing();
Thing.prototype.isPrototypeOf(z); // trueTo highlight the fact that the prototype property object is distinct from the object to which it belongs to, notice the following:
Function.prototype.isPrototypeOf(Thing); // true
Object.prototype.isPrototypeOf(Thing); // true
Function.prototype.isPrototypeOf(Thing.prototype); // false
Object.prototype.isPrototypeOf(Thing.prototype); // trueIf we think of Thing.prototype as simply an object, this shouldn’t come as a surprise. In fact, if we were do something like this:
Object.prototype.a = 1;
Function.prototype.b = 2;
z.a; // 1
z.b; // undefinedThus, z’s prototype chain looks like:
z => Thing.prototype => Object.prototypeand not
z => Thing.prototype => Function.prototype => Object.prototypeES6 Classes
Prototype chains in ES6 classes behave almost exactly like constructors (that is because classes are syntactic sugar around constructors):
class Thing {
a() {
return 1;
}
b() {
return 2;
}
}
class AnotherThing extends Thing {
b() {
return 3;
}
c() {
return 4;
}
}
let x = new AnotherThing();
x.c = () => {
return 5;
};
x.a(); // 1
x.b(); // 3
x.c(); // 5
AnotherThing.prototype.isPrototypeOf(x); // true
Thing.prototype.isPrototypeOf(AnotherThing.prototype); // trueThus, x’s prototype chain is:
x => AnotherThing.prototype => Thing.prototype => Object.prototypeAnd of course, as mentioned earlier, classes really are just syntactic sugar for constructors:
Thing.isPrototypeOf(AnotherThing); // true
Function.prototype.isPrototypeOf(Thing); // true
Function.prototype.isPrototypeOf(AnotherThing); // trueSee footnote4 for a little more detail on how subclassing with extends actually works and how it affects the prototype chain between the subclass and the superclass.
#Footnotes
-
I’ve used
isPrototypeOfhere for better readability, but you can also substitute it for its inverse,__proto__, like↩x.__proto__ === Object.prototype; // true -
And another optional object as a second argument that specifies property descriptors. ↩
-
When I mean “a”, I actually mean any arbitrary function. Of course, functions meant to be used as useful constructors should look a certain way. ↩
-
Part of Babel’s transpiled output for
extendsincludes a_inheritsfunction, the full body of which is below:function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError( "Super expression must either be null or a function, not " + typeof superClass ); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true, }, }); if (superClass) subClass.__proto__ = superClass; }_inheritsexplicitly creates the subclass’s prototype object usingObject.create, specifying the super class’s prototype as its prototype. It also sets the subclass’s__proto’s property to the superclass. ↩