🧬 Prototype
Every object has a built-in property, which is prototype
. The prototype
itself is an object, so the prototype will have its own prototype, making what's called a prototype chain.
The prototype chain ends when we reach a prototype that has null
for its own prototype.
The standard way to access an object's prototype is Object.getPrototypeOf(obj)
method.
Prototype Chain
When you try to access a property of an object:
- If the property cannot be found on the object itself, the prototype is searched for the property.
- If the property still cannot be found, the prototype's prototype is searched, and so on.
- If the end of the prototype chain is reached, the property still cannot be found, in this case
undefined
is returned.
Let's illustrate the above steps using a concrete example.
const person = {
name: "dan",
sayHello() {
console.log(`Hello mate, my name is ${this.name}`);
},
};
person.sayHello();
console.log(person.toString());
When we call person.toString()
, the JS engine
- Looks for
toString
inperson
object. - Cannot find it, so looks in the prototype of
person
fortoString
. - Finds
toString
in person's prototype, which isObject.prototype
and calls it.
Setting a Prototype
There are many ways of setting an object's prototype in JS.
Object.create(obj)
- constructor
Object.create
The Object.create
method creates a new object and allows you to specify an object that will be used as the new object's prototype.
const personPrototype = {
sayHello() {
console.log(`Hello mate, my name is ${this.name}`);
},
};
const p = Object.create(personPrototype);
p.name = "xiaohai";
p.sayHello();
The above code create a object with personPrototype
as its prototype. We are able to call sayHello()
on the new object because the prototype provides the its implementation.
Constructor
In JS, all functions have a property called prototype
. When you call a function as a constructor, this property (prototype
) is set as the prototype of the newly constructed object. (obj.__proto__
)
const personPrototype = {
sayHello() {
console.log(`Hello mate, my name is ${this.name}`);
},
};
function Person(name) {
this.name = name;
}
console.log(Person.prototype);
Object.assign(Person.prototype, personPrototype);
console.log(Person.prototype);
const p = new Person("dan=dan");
p.sayHello();
Object.getPrototypeOf(p) === personPrototype;
Object.assign
is used to preserve the Person.prototype.constructor
property.
The behavior of instanceof
is controlled by Symbol.hasInstance
, not constructor:
const arr = [];
arr.constructor = String;
arr instanceof String; // false
arr instanceof Array; // true
Own Property
The p
object created above has 2 properties:
- a
name
property, which is set in the constructor, so it appears directly on the object. - a
sayHello
property, which is set in the prototype.
Properties that are defined directly in the object, like name
here, are called own properties. We can check if a property is an own property using Object.hasOwn(obj, prop)
.
const p = new Person("xiaohai");
console.log(Object.hasOwn(p, "name")); // true
console.log(Object.hasOwn(p, "sayHello")); // false