I'm trying to understand what actually happens when you create a new instance of a class with ES6. As far as I can tell, a new instance is created with properties as defined in the constructor and then the rest of the methods in the class are actually properties on the prototype object
For example
class Dog {
constructor(name){
this.name = name
}
speak (){
console.log('Woof')
}
}
Would be the equivelant to
function Dog(name){
this.name = name;
}
Dog.prototype.speak = function () {
console.log('Woof');
}
In the class example. If I create an instance of my Dog class, Is the prototype of that method pointing to the Dog class itself or is it a completely new object? Because when I Object.getPrototypeOf(myDog) it returns Dog {}.
They are functionally exactly the same. Here are some interesting properties about prototypes and classes to point out though:
class Dog1 {
constructor(name){
this.name = name
}
speak (){ // adds speak method to the prototype of Dog1
console.log('Woof')
}
}
Dog1.prototype.speak = () => { // overwrites speak method to the prototype of Dog1
console.log('Different Woof');
}
console.log(typeof Dog1); // Class keyword is just syntactic sugar for constructor function
const newDog = new Dog1('barkie');
newDog.speak();
/////////////////////////////
function Dog2(name){
this.name = name;
}
Dog2.prototype.speak = () => {
console.log('Woof');
}
const aDog = new Dog2('fluffy');
aDog.__proto__.speak(); // the __proto__ property references the prototype
aDog.speak(); // the __proto__ property is not necessary, it will automatically climb the protochain if the property is not found on the object itself.
I left some comments in order to point out the quirks which can be a bit hard to grasp. I you have any further questions about it you can leave a comment. Hopefully this is helpful to you.
Related
i am learning about mongoose from the book mastering mongoose by valeri karpov.
he states the following.
// Calling `conn.model()` creates a new model. In this book
// a "model" is a class that extends from `mongoose.Model`
const MyModel = conn.model('ModelName', schema);
Object.getPrototypeOf(MyModel) === mongoose.Model; // true
i dont understand how a class which is as i understand it is a constructor function can have a prototype other than: "Function.prototype". (i am talking about the actual prototype and not the prototype property)
just to make it explicit that MyModel is a class/constructor he goes on to use it like so:
const document = new MyModel();
i have reviewed my understanding of protoypal inheritance in javascript and nothing has came to light that explains this.
can anyone explain what is going on here.
My confusion came from the fact that i never realised a subtle difference between how inheritance is implemented in es6 classes and how 'I' would implement it using constructor functions.
this article helped me find out what i needed to know:
https://www.taniarascia.com/understanding-classes-in-javascript/
borrowing the example from the article:
if we implement inheritance with constructor functions like so
function Hero(name, level) {
this.name = name
this.level = level
}
// Adding a method to the constructor
Hero.prototype.greet = function () {
return `${this.name} says hello.`
}
// Creating a new constructor from the parent
function Mage(name, level, spell) {
// Chain constructor with call
Hero.call(this, name, level)
this.spell = spell
}
// Creating a new object using Hero's prototype as the prototype for the newly created object.
Mage.prototype = Object.create(Hero.prototype)
console.log(Object.getPrototypeOf(Mage))// logs - ƒ () { [native code] }
there is no reason to expect the prototype of mage to be other than - ƒ () { [native code] }
and indeed it is not
however using classes something is done under the hood i was not aware of:
class Hero {
constructor(name, level) {
this.name = name
this.level = level
}
// Adding a method to the constructor
greet() {
return `${this.name} says hello.`
}
}
// Creating a new class from the parent
class Mage extends Hero {
constructor(name, level, spell) {
// Chain constructor with super
super(name, level)
// Add a new property
this.spell = spell
}
}
console.log(Object.getPrototypeOf(Mage)) // logs the hero constructor
In the example below, why is Dog.prototype.constructor = Dog needed? I under we use:
Dog.prototype = Object.create(Animal.prototype) to inherit the sayAnimal() and any other functions added to the Animal prototype but how does that effect the constructor? What would leaving it out do?
function Animal(gender) {
this.gender = gender;
}
Animal.prototype.sayAnimal = function() {
return "I am an animal"
}
function Dog(gender, barkSound) {
Animal.call(this, gender)
this.barkSound = barkSound
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
Users of classes will expect the .constructor property of instances to refer to the constructor of that instance. For example:
class ExtendedArray extends Array {
}
const e = new ExtendedArray();
console.log(e.constructor === ExtendedArray);
If you're using functions and extending manually, then if you don't set the constructor property on the subclass prototype explicitly, the .constructor will refer not to the subclass constructor (as a user of the code would usually expect), but to the superclass:
function Animal(gender) {
}
function Dog(gender, barkSound) {
Animal.call(this, gender)
}
Dog.prototype = Object.create(Animal.prototype)
// oops, this refers to Animal...
console.log(Dog.prototype.constructor);
That said, it probably isn't an issue in most situations.
Dog.prototype = Object.create(Animal.prototype)
Causes the entire prototype object to be replaced with a new instance of Animal.prototype, which is important so that you don't start off with an Animal.prototype instance that has been altered from the original definition. At this point, if you were to create a new Dog, the Animal constructor would fire off and you wouldn't gain any of the characteristics of a Dog in your new instance, you'd just have another Animal.
But, when you add this:
Dog.prototype.constructor = Dog
You are only replacing the constructor portion of the Animal.prototype, so that now when you create a new Dog, you are making a new instance of an Animal first, but with the Dog constructor, so that your Animal can be enhanced and worked with as a more specific type.
I've written a bit more about this here.
// Parent class
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Hello. My name is " + this.name);
};
Person.prototype.sayGoodbye = function() {
console.log("Goodbye!");
};
// Child class
function Student(name, gpa) {
Person.call(this, name);// I'm confused what does keyword 'this' refer to?
this.gpa = gpa;
}
// create child object
let john = new Student("john",3.5);
Does the keyword 'this' refer to object john or Person.prototype?
The new operator preceding Student("john",3.5); creates a new instance object, prototyped on the following function's prototype property, before calling the function with the newly created object as its this value. ( Refer to new operator documentation on MDN for more detail).)
So the this in
Person.call(this, name);
is the student object created and returned by new before being stored in the john variable.
Student code then calls the Person function with its own this value so that the Person constructor can add "Person" type properties to the Student object before returning from Student.
Worth noting:
The example shows how the construction of class objects was undertaken before implementation of the class keyword in JavaScript
This particular example does not implement inheritance of Person.prototype properties by Student objects. This was sometime implemented using a dummy Person object as the prototype of Student, using code similar to:
Student.prototype = new Person();
Student.prototype.constructor = Student;
The shortcomings and limitations of this practice at least contributed to the introduction of class constructor functions in ECMAScript.
Here to make Student a child class you need to extend it to the Person class:
class Person {
var name;
constructor(name) {
this.name = name;
}
sayhello(){
console.log("Hello. My name is " + this.name);
}
sayGoodbye(){
console.log("Goodbye!");
}
}
class Student extends Person {
getName(){
return this.name;
}
}
student = new Student("Jhon");
The 'this' keywords refers to the current object. You can call methods of the parent class from the child class using the child class object itself as you are using extend keywords.
This is a simple example of inheritance.
I'm trying to inherit class "EventEmitter" and a pre defined class "Person", here is the code
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduces() {
return `My name is ${this.name}. I am ${this.age} years old.`;
}
};
\\here comes the mixin part
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if (key !== "constructor" && key !== "prototype" && key !== "name") {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
I intend to create a new class 'PersonWithEmitter', and still call the constructor like below:
class PersonWithEmitter extends mix(Person,EventEmitter){
constructor(name,age){
super(name,age)
\\do something else
}
Here comes the issue, when I create a new instance of 'PersonWithEmitter' like this let someOne = new PersonWithEmitter("Tom",21), will not get what I what, In the new class, I want to use this.name and this.age, which is still undefined.
So how can I change my code, So the new class can both have its parent's methods and only class "Person"'s constructor?
Pardon me for my broken English.
In many cases multiple inheritance in JavaScript indicates wrong design decision. It may result in hacky objects that don't behave as they should. The way it is done should always be determined by particular objects. In some cases a shallow copy of own properties is needed, in another the entire prototype chain should be traversed. Composition over inheritance is often a better choice.
The problem in the code above is that class constructors are not called. Mix has empty constructor. This is the reason why PersonWithEmitter doesn't work as expected.
Multiple constructor function calls can generally be stacked like:
function Foo(...args) {
let _this = this;
_this = Bar.apply(_this, args);
_this = Baz.apply(_this, args);
return _this;
}
This won't work if Bar or Baz is ES6 class because it contains a mechanism that prevents it from being called without new. In this case they should be instantiated:
function Foo(...args) {
copyProperties(this, new Bar(...args));
copyProperties(this, new Baz(...args));
}
Static and prototype properties may also be copied to Foo like is shown in code above.
If the case is narrowed down to Node.js EventEmitter, it can be handled like a special case. Its implementation is certain and stable. It is already known that EventEmitter does initialization in constructor, it has a shallow prototype chain and property descriptors. So it likely should be:
class Foo extends Bar {
constructor(...args) {
super(...args)
EventEmitter.call(this);
// or
// EventEmitter.init.call(this);
}
copyProperties(Foo.prototype, EventEmitter.prototype);
Quite recently I read about JavaScript call usage in MDC
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call
one linke of the example shown below, I still don't understand.
Why are they using inheritance here like this
Prod_dept.prototype = new Product();
is this necessary? Because there is a call to the super-constructor in
Prod_dept()
anyway, like this
Product.call
is this just out of common behaviour? When is it better to use call for the super-constructor or use the prototype chain?
function Product(name, value){
this.name = name;
if(value >= 1000)
this.value = 999;
else
this.value = value;
}
function Prod_dept(name, value, dept){
this.dept = dept;
Product.call(this, name, value);
}
Prod_dept.prototype = new Product();
// since 5 is less than 1000, value is set
cheese = new Prod_dept("feta", 5, "food");
// since 5000 is above 1000, value will be 999
car = new Prod_dept("honda", 5000, "auto");
Thanks for making things clearer
The answer to the real question is that you need to do both:
Setting the prototype to an instance of the parent initializes the prototype chain (inheritance), this is done only once (since the prototype object is shared).
Calling the parent's constructor initializes the object itself, this is done with every instantiation (you can pass different parameters each time you construct it).
Therefore, you should not call the parent's constructor when setting up inheritance. Only when instantiating an object that inherits from another.
Chris Morgan's answer is almost complete, missing a small detail (constructor property). Let me suggest a method to setup inheritance.
function extend(base, sub) {
// Avoid instantiating the base class just to setup inheritance
// Also, do a recursive merge of two prototypes, so we don't overwrite
// the existing prototype, but still maintain the inheritance chain
// Thanks to #ccnokes
var origProto = sub.prototype;
sub.prototype = Object.create(base.prototype);
for (var key in origProto) {
sub.prototype[key] = origProto[key];
}
// The constructor property was set wrong, let's fix it
Object.defineProperty(sub.prototype, 'constructor', {
enumerable: false,
value: sub
});
}
// Let's try this
function Animal(name) {
this.name = name;
}
Animal.prototype = {
sayMyName: function() {
console.log(this.getWordsToSay() + " " + this.name);
},
getWordsToSay: function() {
// Abstract
}
}
function Dog(name) {
// Call the parent's constructor
Animal.call(this, name);
}
Dog.prototype = {
getWordsToSay: function(){
return "Ruff Ruff";
}
}
// Setup the prototype chain the right way
extend(Animal, Dog);
// Here is where the Dog (and Animal) constructors are called
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog
See my blog post for even further syntactic sugar when creating classes. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html
Technique copied from Ext-JS and http://www.uselesspickles.com/class_library/ and a comment from https://stackoverflow.com/users/1397311/ccnokes
The ideal way to do it is to not do Prod_dept.prototype = new Product();, because this calls the Product constructor. So the ideal way is to clone it except for the constructor, something like this:
function Product(...) {
...
}
var tmp = function(){};
tmp.prototype = Product.prototype;
function Prod_dept(...) {
Product.call(this, ...);
}
Prod_dept.prototype = new tmp();
Prod_dept.prototype.constructor = Prod_dept;
Then the super constructor is called at construction time, which is what you want, because then you can pass the parameters, too.
If you look at things like the Google Closure Library you'll see that's how they do it.
If you have done Object Oriented Programming in JavaScript, you will know that you can create a class as follows:
Person = function(id, name, age){
this.id = id;
this.name = name;
this.age = age;
alert('A new person has been accepted');
}
So far our class person only has two properties and we are going to give it some methods. A clean way of doing this is
to use its 'prototype' object.
Starting from JavaScript 1.1, the prototype object was introduced in JavaScript. This is a built in object that
simplifies the process of adding custom properties and methods to all instances of an object.
Let's add 2 methods to our class using its 'prototype' object as follows:
Person.prototype = {
/** wake person up */
wake_up: function() {
alert('I am awake');
},
/** retrieve person's age */
get_age: function() {
return this.age;
}
}
Now we have defined our class Person. What if we wanted to define another class called Manager which inherits some properties from Person. There is no point redefining all this properties again when we define our Manager class, we can just set it to inherit from the class Person.
JavaScript doesn't have built in inheritance but we can use a technique to implement inheritance as follows:
Inheritance_Manager = {};//We create an inheritance manager class (the name is arbitrary)
Now let's give our inheritance class a method called extend which takes the baseClass and subClassas arguments.
Within the extend method, we will create an inner class called inheritance function inheritance() { }. The reason why we are using this inner
class is to avoid confusion between the baseClass and subClass prototypes.
Next we make the prototype of our inheritance class point to the baseClass prototype as with the following code:
inheritance.prototype = baseClass. prototype;
Then we copy the inheritance prototype into the subClass prototype as follows: subClass.prototype = new inheritance();
The next thing is to specify the constructor for our subClass as follows: subClass.prototype.constructor = subClass;
Once finished with our subClass prototyping, we can specify the next two lines of code to set some base class pointers.
subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;
Here is the full code for our extend function:
Inheritance_Manager.extend = function(subClass, baseClass) {
function inheritance() { }
inheritance.prototype = baseClass.prototype;
subClass.prototype = new inheritance();
subClass.prototype.constructor = subClass;
subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;
}
Now that we have implemented our inheritance, we can start using it to extend our classes. In this case we are going to
extend our Person class into a Manager class as follows:
We define the Manager class
Manager = function(id, name, age, salary) {
Person.baseConstructor.call(this, id, name, age);
this.salary = salary;
alert('A manager has been registered.');
}
we make it inherit form Person
Inheritance_Manager.extend(Manager, Person);
If you noticed, we have just called the extend method of our Inheritance_Manager class and passed the subClass Manager in our case and then the baseClass Person. Note that the order is very important here. If you swap them, the inheritance
will not work as you intended if at all.
Also note that you will need to specify this inheritance before you can actually define our subClass.
Now let us define our subClass:
We can add more methods as the one below. Our Manager class will always have the methods and properties defined in the Person class because it inherits from it.
Manager.prototype.lead = function(){
alert('I am a good leader');
}
Now to test it let us create two objects, one from the class Person and one from the inherited class Manager:
var p = new Person(1, 'Joe Tester', 26);
var pm = new Manager(1, 'Joe Tester', 26, '20.000');
Feel free to get full code and more comments at:
http://www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspx