JavaScript Prototype - Prototype Chain
Prototype Chain
Searching Data in Prototype Chain
When we access a property in an object, JavaScript engine first searches for an object's own property. If it can't find one, it goes on to look search the object's __proto__
. If there's no match and if there is another __proto__
inside __proto__
, it continues to search __proto__
, until there is no more __proto__
to resort to. This chain of prototypes in which a property is looked for is called the "prototype chain." When properties and methods become automatically available via prototype chain, they are said to be “inherited.” To be precise, what looks like inheritance in object-oriented languages is, under the hood, reference in prototype chain. Even JavaScript class that was introduced in ES6 is syntactic sugar of prototype.
Writing and Deleting Data in Prototype Chain
The prototype chain is only traversed when reading properties. Writing and deleting works directly on instance object and don't affect the prototype, even if there are properties with the same name.
function Car (model) {
this.model = model
}
Car.prototype.mileage = 0
Car.prototype.move = function () {
console.log('moving...')
}
const myCar = new Car('G63')
delete myCar.mileage
myCar.mileage // 0
delete myCar.model
myCar.model // undefined
this
in [INSTANCE].[SETTER]
points to instance, not the prototype.const car = {
_mileage: 0,
get mileage () {
console.log('getting mileage...')
return this._mileage
},
set mileage (mileage) {
console.log('setting mileage...')
this._mileage = mileage
}
}
const myCar = { model: 'GW6' }
Object.setPrototypeOf(myCar, car)
console.log(myCar.mileage)
// getting mileage...
// 0
myCar.mileage = 'string mileage'
// setting mileage ...
console.log(myCar.mileage)
// getting mileage...
// 'string mileage'
Object.getPrototypeOf(myCar).mileage
// 0
Keep in mind that the prototype chain can't go in circles. If so, JavaScript will throw an error.
function Vehicle (energySource) {
this.energySource = energySource
}
function Car (model) {
this.model = model
}
Object.setPrototypeOf(Car, Vehicle)
Object.setPrototypeOf(Vehicle, Car)
// ❌ Uncaught TypeError: Cyclic __proto__ value
Property / Method Override
When traversing the prototype chain, object's own properties and methods are prioritized. If an instance has properties or methods with the same name as its prototype, those of the instance are returned.
function Car (model) {
this.model = model
}
Car.prototype.mileage = 0
Car.prototype.move = function () {
console.log('moving...')
}
const myCar = new Car('G63')
myCar.move() // 'moving...'
myCar.move = function () {
console.log('myCar is moving, Yay!')
}
myCar.move() // 'myCar is moving, Yay!'
Object.getPrototypeOf(myCar) // f () { console.log('moving...) }
In this case, prototype's move
method was overriden.
Here, we should be careful not to confuse 'override' and 'overwrite.' To overwrite is to replace something completely and the thing overwritten is no longer available. On the other hand, to override means to make something operate instead, in specific cases, without destroying the thing overridden. In this case, the prototype's method is intact and we can still access it by Object.getPrototypeOf(myCar)
. It's just that myCar
's own move
was found first when invoking move
method in myCar.move()
.
Prototype Chain of Native Object
The following code
const myArr = [1, 2]
is equivalent to the following:
const myArr = new Array(1, 2)
When we run console.dir()
on myArr
, we see that its prototype is JavaScript's native object Array
.
Then, as we toggle [[Prototype]]
and go far down the line, we see that Array
's [[Prototype]]
references JavaScript's native object Object
.
This is because __proto__
is itself an object and is therefore an instance of Object
.
A visual representation:
In JavaScript, Array
, Function
, Number
, String
, Boolean
are all objects, meaning that their prototypes are Object
. Therefore, they have direct access to Object.prototype
's methods, such as hasOwnProperty()
, isPrototypeOf
, etc.
myArr.hasOwnProperty('length') // true
const myNum = 2, myStr = 'abc', myBool = true
// Note code doesn't break
myNum.hasOwnProperty('length') // false
myStr.hasOwnProperty('length') // true
myBool.hasOwnProperty('length') // false
Object
's static method directly from primitive value throws an error.2.hasOwnProperty('length')
// ❌ Uncaught SyntaxError: Invalid or unexpected token
While numbers, strings, and booleans are primitive data types, not objects, we can still invoke methods. According to the specification, such is possible since internally, even when we assign value literally(2
, abc
, true
), built-in constructors are invoked(String
, Number
, Boolean
) and wrapper objects are created. Those wrapper objects provide methods and disappear. This way we have access to properties in String.prototype
, Number.prototype
, Boolean.prototype
and ultimately to Object.prototype
.
undefined
and null
do not have wrapper objects. Therefore, we cannot invoke any property or method.For this reason, unlike Array.prototype
, Number.prototype
, and so on, Object.prototype
contains methods that can be shared by all other data types, not those restricted to Object
in a narrow sense. Methods that only pertain to Object
are contained in Object
directly as static methods(Object.create()
, Object.freeze()
, etc.), instead of in Object.prototype
. Also, since they are not contained in prototype
, they can't be invoked directly from an object. An object is passed to the static methods as an argument(e.g. Object.freeze(obj)
🟢, obj.freeze()
❌)
Object.getPrototypeOf(Object.prototype)
logs null
, from which we cannot trace back prototype any further.Object.create(null)
creates an object whose prototype is null
, instead of Object.prototype
. An object created this way is lighter for the price of not having access to built-in object methods. Such a trick works best when we want to make a pure dictionary, simply mapping keys to values, and are sure that we don't have to make use of inheritance or native methods. References
- 코어 자바스크립트(Core JavaScript), 재남, 정. 위키북스, 2019. pp. 156-174.
- "Prototypal inheritance," The Modern JavaScript Tutorial
- "Native prototypes," The Modern JavaScript Tutorial
- "Prototype methods, objects without __proto__," The Modern JavaScript Tutorial