This way of doing inheritance is taken from JS Objects: Deconstruction.
Also check the slides of The four layers of JavaScript OOP.
We define a "class" Person just as an object.
var Person = {
sayName: function () {
return 'My name is ' + this.name;
}
};We create an instance from it, using Object.create.
var jane = Object.create(Person);This created an empty object with Person as its prototype, so janes prototype chain now looks like this:
jane -------> Person
sayName
Now, we give our instance a name.
jane.name = 'Jane';The string 'Jane' is not being stored in Person, but in jane itself. Assignments to objects never go to their prototype. janes prototype chain will now look like this:
jane -------> Person
name sayName
Let's test what we wrote.
jane.sayName(); // -> 'My name is Jane'When we access a field (e.g. name or sayName) of an object, JavaScript will walk up its prototype chain, trying to find the field. First, it will look inside the jane object. Because sayName is not in jane, it will move up and look for the entry in Person.
Note that, even through sayName is being taken from Person, we call it on jane. this in the code of sayName then refers to jane.
Let's create another instance from Person and call it John.
var john = Object.create(Person);
john.name = 'John';johns prototype chain will now look like the one of jane:
john -------> Person
name sayName
Imagine we modify Person (instead of the instances jane and john):
Person.sayName = function () {
return 'I am ' + this.name;
};Because Person is in the prototype chain of each of our instances jane and john, our modification also changes their behavior. Unlike with (real) class-based inheritance, using prototypal inheritance allows for modifying "superclasses" during runtime.
john.sayName(); // -> 'I am John'To create a subclass Student of Person, we just create a new object with Person in its prototype chain and extend it by a function learn.
var Student = Object.create(Person);
Student.learn = function (skill) {
return 'Learning all about ' + skill;
};Remember that the function learn is not stored in Person, but in Student. After we created a new instance lilly of Student,
var lilly = Object.create(Student);
lilly.name = 'Lilly';its prototype chain will look like this:
lilly ------> Student ----> Person
name learn sayName
We can now access the fields name, sayName and learn.
lilly.name; // -> 'Lilly'
lilly.sayName(); // -> 'I am Lilly'
lilly.learn('JavaScript'); // -> 'Learning all about JavaScript'Let's move on to some patterns that make everyday usage easier, but embrace the flexibilty of JavaScript's inheritance mechanism.
So far, we have "customized" a new instance by just assigning to it. In some cases, however, this may be either a lot of work or not sophisticated enough.
To achieve what a constructor usally does, we can define the init function as a standard.
Person.init = function (name, surname) {
this.name = name;
this.surname = surname;
// Do a sophisticated initialization here.
};
var john = Object.create(Person);
john.init('John', 'Doe');In the init function of a subclass, we may call init from the "superclass":
var Student = Object.create(Person);
Student.init = function (name, surname, skill) {
Person.init.call(this, name, surname);
this.skill = skill;
};
var lilly = Object.create(Student);
lilly.init('Lilly', 'Doe', 'JavaScript');We can still access all fields.
lilly.name; // -> 'Lilly'
lilly.surname; // -> 'Doe'
lilly.skill; // -> 'JavaScript'
lilly.sayName(); // -> 'My name is Lilly Doe'
lilly.learn(); // -> 'Learning all about JavaScript'You may have noticed, that creating a subclass with the pattern above is a bit cumbersome, especially if it has many fields, since we need to write Student.… = … every single time.
To get closer to the good old class notation, we can use Object.assign.
var Student = Object.assign(Object.create(Person), {
init: function (name, surname, skill) {
Person.init.call(this, name, surname);
this.skill = skill;
},
learn: function () {
console.log('Learning all about ' + this.skill);
}
// …
});This will create an object Student which contains the fields init and learn, and has a prototype that points to Person.
Student ----> Person
init sayName
learn
Instead of doing a = Object.create(…); a.init(…);, we can define another shorthand once and reuse it for every class.
var createConstructor = function (class) {
return function constructor () {
var instance = Object.create(class);
if (instance.init) instance.init.apply(instance, arguments);
return instance;
}
};We can also define a shorthand create…
var Person = {
create: constructor(Person),
init: function (…) {…},
…
};and then comfortably create an instance of Person:
var john = Person.create('John', 'Doe');
how does
relates to
which implies that Person is defined as
in terms of classes and prototypes? I'm pretty sure there is a difference except object and function, isn't it?
One difference I just found is that the function approach encapsulates the data. I found no way to access the data directly while it's been easy to do so when use a object as base.