I have a class Player and want to extend it to have a class Team.
My logic would be:
class Player {
constructor(name, age) { //class constructor
this.name = name;
this.age = age;
}
sayName() { //class method
console.log('Hi, I am ', this.name + '.');
}
sayAge() {
console.log('Hi, I am ', this.age + '.');
}
}
class Team extends Player {
constructor(members) {
var players = [];
for (var i = 0; i < members; i++) {
players.push(super(members[i].name, members[i].age));
}
this.players = players; // to not call "this" before super
}
teamNames() {
console.log(this.players);
}
}
let p = new Player('John', 25);
p.sayName(); // John
var team = [{
name: 'Jonh',
age: 25
},{
name: 'Anna',
age: 27
}];
var A_Team = new Team(team);
A_Team.temNames();
fiddle: http://www.es6fiddle.net/ik5s7ale/
But this gives errors and the class does not work. I understand calling super inside a loop is wrong, but what would be the solution here? Is the relation between these two classes not a "extend" situation, should I use/declare separated classes for this without extending?
This is not a superclass-subclass situation.
If you have a class X, and you want to create a special, enhanced version of X, then you should extend/subclass it. That's not what's going on in your case; you should simply compose the Team class, using the Player class.
class Player {
constructor(name, age, team) { //class constructor
this.name = name;
this.age = age;
this.team = team;
}
sayName() { //class method
console.log('Hi, I am ', this.name + '.');
}
sayAge() {
console.log('Hi, I am ', this.age + '.');
}
}
class Team {
constructor(members) {
this.players = [];
for (var i = 0; i < members; i++) {
this.players.push(new Player(members[i].name, members[i].age, this));
}
}
teamNames() {
console.log(this.players);
}
}
If you want the players to have access to Team methods or constants, you're going to have to give the Player instances a reference to their parent Team instance, and call it as team.teamMethod().
Relation between Team and Player is surely not inheritance.
Team is not the special case of player, but rather Team consists of players.
Correct would be declaring separate classes. You have 2 ways of achieving it, depends of how you are using
Team takes array of players in parameter of constructor - this is good in most of cases.
Team is created "empty" and later you set players. Maybe the Team can create instance of that player based on some parameters ( createPlayer(name, age)
Well this was for your last part of question, now why your code does not work.
When you call super, you are calling the constructor of parent object.
The parant object is only one and super does not return pointer to it (instead it returns undefined), so you can not push it to the array.
Also you had a few typos in your code.
See an updated fiddle
EDIT
I Was writting answer and didn't notice another answer posted in the meantime :-)
Related
I have a Player class that has some properties like name, id, score etc... and few methods that modify the score propertyOver the course of the execution i end up saving few instances of this class to JSON files. Then later on, I import them back into an array called players. In order to retrieve the class' methods in each object in the array, I did the following:
class Player {
constructor(_name,_id) {
this.name = _name;
this.age = _age;
this.score = 0;
}
addScore(x) {this.score += x}
multiplyScore(x) {this.score *= x}
removeScore(x) {this.score -= x}
}
// The function `loadFiles()` returns: [{name:"foo",id:0,score:15},{name:"bar",id:1,score:9}]
const players = loadFiles().map(x => Object.setPrototypeOf(x, Player.prototype));
Normally, when I use the setPrototypeOf() method, it would return an instance of the class I chose with properties values from the targeted object, and then if I do console.log(players[0].constructor.name) it should print out Player except that the objects in players remain exactly the same, the methods aren't added into them and their constructor.name is not Player.
PS: the process works fine if I do this on one object instead of mapping/looping through an array of them.
There is nothing wrong with the code, it is working completely fine.
class Player {
constructor(_name,_id) {
this.name = _name;
this.age = _age;
this.score = 0;
}
addScore(x) {this.score += x}
multiplyScore(x) {this.score *= x}
removeScore(x) {this.score -= x}
}
// The function `loadFiles()` returns: [{name:"foo",id:0,score:15},{name:"bar",id:1,score:9}]
const loadFilesResponse = [{name:"foo",id:0,score:15},{name:"bar",id:1,score:9}];
const players = loadFilesResponse.map(x => Object.setPrototypeOf(x, Player.prototype));
console.log("Score before add: ", players[0].score);
players[0].addScore(3);
console.log("Score after add: ", players[0].score);
It's possible that loadFiles doesn't actually returns the expected Object, for example it could be returning a list of promises (a common mistake when dealing with promises).
You have the data, just create objects from that data instead of doing object prototype hackery?
const players = loadFiles().map(p => {
const { name, age, score } = p;
p = new Player(name, age);
p.score = score;
return p;
});
Although extending that constructor a little would make it less work:
class Player {
constructor(name, age, score = 0) {
this.name = name;
this.age = age;
this.score = score;
}
...
}
...
const players = loadFiles().map(p => {
const { name, age, score } = p;
return = new Player(name, age, score);
});
This you can call the constructor with two or three arguments, and if you call it with two the score argument will simply default to 0. Much easier.
So as I learn more about the Prototypal Inheritance in JS, I read the following in MDN article (read lines just above this link)
Note that when we are calling our constructor function, we are defining greeting() every time, which isn't ideal. To avoid this, we can define functions on the prototype instead, which we will look at later.
The idea recommended is to add properties on Function and methods on the prototype (read here)
Person.prototype.farewell = function() {
alert(this.name.first + ' has left the building. Bye for now!');
};
I read at multiple places that this way
The object creation is faster, since the farewell is not created for every object creation. This is because it is created once and attached to the prototype, and all the objects link to the prototype.
The methods are looked up on the prototypal chain so every object links to the same method on the prototype.
Now comes the ES6 class. I have the following code
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first: first,
last: last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
greeting () {
console.log("Hi! I am", this.name.first);
}
farewell () {
console.log(this.name.first, "has left the building. Bye for now!");
}
}
It seems that with this approach greeting and farewell will be created again (following up on same logic as Functions since class is a syntactic sugar)
So, I changed the class to
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first: first,
last: last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
}
Person.prototype.greeting = function () {
console.log("Hi! I am", this.name.first);
}
Person.prototype.farewell = function () {
console.log(this.name.first, "has left the building. Bye for now!");
}
Question
1. Does the latter way (Adding methods on prototype in ES6 class) is a recommended way?
2. Does the logic of class and traditional new Function to create a new object align? Copy vs Method on Prototype?
Please let me know if there is anything else I am missing
Thanks
UPDATE
After few answers, I retried my examples and confirmed the answers. My code looks like
class Phone {
constructor(company, name, color, price) {
this.company = company;
this.name = name;
this.color = color;
this.price = price;
}
print() {
console.log(this.company, this.name, this.color, this.price);
}
}
class Apple extends Phone {
constructor(name, color, price) {
super("Apple", name, color, price);
this.companyWork = "ClosedSource";
}
}
let iPhone11 = new Apple("iPhone11", "black", 900);
iPhone11.print()
After I ran this code, I can confirm that print() is available on Phone.prototype
As you've already discovered, class definitions put methods on the prototype. You asked for a reference so here's a little code you can run to see for yourself:
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first: first,
last: last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
greeting () {
console.log("Hi! I am", this.name.first);
}
farewell () {
console.log(this.name.first, "has left the building. Bye for now!");
}
}
let p = new Person("Jack", "Beanstalk", 201, "giant", ["crushing things", "stomping things"]);
console.log("Person", Object.getOwnPropertyNames(Person));
console.log("p", Object.getOwnPropertyNames(p));
let p_prototype = Object.getPrototypeOf(p);
console.log("p_prototype === Person.prototype is ", p_prototype === Person.prototype);
console.log("Person.prototype", Object.getOwnPropertyNames(Person.prototype));
That generates this output:
Person [ 'length', 'prototype', 'name' ]
p [ 'name', 'age', 'gender', 'interests' ]
p_prototype === Person.prototype is true
Person.prototype [ 'constructor', 'greeting', 'farewell' ]
So, you can draw these conclusions:
The Person class has only the expected properties for a constructor function and the prototype object, none of the class-defined methods are there.
An instance of the class has only the expected data properties that are assigned in the constructor, none of the class-defined methods are on the object itself.
The prototype you get from an instance is the same as the prototype of the class (thus no new object is created for each instance).
The Person.prototype has the expected methods from the class definition on it plus a property that points to the constructor itself (used in inheriting and calling derived constructor).
So, you can verify from this that methods defined with the class syntax go on the Classname.prototype object and that object is shared with all instances.
AFAIK
No, it's not a recommended way. Class methods are created as methods on prototype. In other words classes are just syntactic sugar for regular prototypal inheritance.
In both cases methods won't be created multiple times if multiple objects are created. Methods will be created only once.
I am trying to emulate polymorphism through ES6 Classes, in order to be able to better understand this theory.
Concept is clear (designing objects to share behaviors and to be able to override shared behaviors with specific ones) but I am afraid my code above is not a valid polymorphism example.
Due to my lack of experience, I appreciate if you answer these questions in a comprehensive way:
The fact that both Classes have an equally named method, and that instances made from each Class get access correctly to their respective methods, makes this a polymorphic example?
In case this does not emulate polimorphism, what changes should be
done in the code for it?
I've tried removing the Employee.prototype = new Person(); line, and it still works. This is why I am afraid I'm not getting this concept.
class Person {
constructor (name, age) {
this._name = name;
this._age = age;
}
}
Person.prototype.showInfo = function(){
return "Im " + this._name + ", aged " + this._age;
};
class Employee {
constructor (name, age, sex) {
this._name = name;
this._age = age;
this._sex = sex;
}
}
Employee.prototype = new Person();
Employee.prototype.showInfo = function(){
return "Im " + this._sex + ", named " + this._name + ", aged " + this._age;
};
var myPerson = new Person('Jon', 20);
var myEmployee = new Employee('Doe', 10, 'men');
document.write(myPerson.showInfo() + "<br><br>"); // Im Jon, aged 20
document.write(myEmployee.showInfo() + "<br><br>"); // Im men, named Doe, aged 10
Every JavaScript object has an internal "prototype" property, often called [[prototype]], which points to the object from which it directly inherits.
Every JavaScript function [object] has a property prototype, which is initialized with an [nearly] empty object. When you create a new instance of this function by calling it as a constructor, the [[prototype]] of that new object will point to the constructor's prototype object.
So, when you write this var myPerson = new Person('Jon', 20);,you have the method showInfo because you have this
Person.prototype.showInfo = function(){
return "Im " + this._name + ", aged " + this._age;
};
With ES6 if you want to see the polymorphism you could do that :
class Person {
constructor (name, age) {
this._name = name;
this._age = age;
}
showInfo () {
return "Im " + this._name + ", aged " + this._age;
}
}
class Employee extends Person {
constructor (name, age, sex) {
super(name,age);
this._sex = sex;
}
showInfo(){
return "Im " + this._sex + ", named " + this._name + ", aged " + this._age;
}
}
var myPerson = new Person('Jon', 20);
var myEmployee = new Employee('Doe', 10, 'men');
document.write(myPerson.showInfo() + "<br><br>"); // Im Jon, aged 20
document.write(myEmployee.showInfo() + "<br><br>"); // Im men, named Doe, aged 10
You are mixing ES5 and ES6. Also, you simply created two classes. Employee does not really inherit from Person. The code you want should look like this:
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
showInfo() {
return `I'm ${this._name}, aged ${this._age}.`;
}
}
class Employee extends Person {
constructor(name, age, sex) {
super(name, age);
this._sex = sex;
}
showInfo() {
return `I'm a ${this._sex} named ${this._name}, aged ${this._age}.`;
}
}
const alice = new Person("Alice", 20);
const bob = new Employee("Bob", 25, "male");
console.log(alice.showInfo());
console.log(bob.showInfo());
So, here's a quick recap of what's changed.
First off, no need to assign a prototype anymore. Instead, you extend the parent class to create a child class. You can call the parent constructor using the super() call in the child constructor - this will set the properties that are handled by the parent ctor. Note that we don't set name or age in the child ctor anymore! This is why your example still worked after removing the line setting the child prototype: you set the properties manually anyway.
Methods are defined inside the class; no need to use the prototype. You override the method in the child class by just declaring it there.
Should you need any additional clarification, please ask in the comment.
I understand the main principle of OOP and I somewhat know how to implement it into JS.
function Person(name) {
this.name = name;
this.speak = function(msg) {
console.log('Person says:' + msg);
}
}
var dad = new Person('David');
dad.speak('I am your dad!');
The script above does nothing more than printing out a message in the console. I don't understand how we approach the DOM with this technique. Maybe something like this?:
function Person(target, name) {
this.target = target;
this.name = name;
this.speak = function(msg) {
this.target.find('.speech-bubble').html(msg);
}
}
var dad = new Person($('#dad'), 'David');
dad.speak('I am your dad!');
Although this doesn't seem like a good approach.
How do we manipulate the DOM with objects, methods, constructors etc. through OO Javascript?
In relation to OO, if you are going to adopt for your DOM facing code, you are not too far of.
I'd say that a class should represent a component/element on the DOM. With it's methods being the state management part. But there is no correct answer here to be honest. This is but one way of designing OO with the DOM facing part.
Example:
const basicClassName = 'component';
const basicTemplate = '<h1>This is my basic component</h1>';
class MyComponent {
constructor(template = basicTemplate, className = basicClassName) {
this.template = template;
this.className = className;
this.element = document.createElement('div');
this.element.className = className;
this.element.innerHTML = template;
this.element.onclick = this.onClick.bind(this);
this.element.style.cursor = 'pointer';
}
onClick() {
this.element.classList.toggle('clicked');
}
}
const component = new MyComponent();
const container = document.querySelector('.container');
container.appendChild(component.element);
body {
font-size: 14px;
}
.component {
display: block;
padding: 1.3em;
box-shadow: 1px 1px 4px lightgray;
}
.clicked {
background-color: papayawhip;
}
<div class="container"></div>
What you need to understand is the concept of the Prototype.
When you create an instance using new, you are constructing an object based upon a prototype.
Consider the following:
function Person(name) {
this.name = name;
this.speak = function (msg) {
console.log('Person says:' + msg);
};
}
var dad = new Person('David');
dad.speak('I am your dad!');
console.log('Is dad.speak equal to dad.speak?', dad.speak === dad.speak);
var mom = new Person('David');
console.log('Is mom.speak equal to dad.speak?', mom.speak === dad.speak);
Each time you construct a new instance of Person, a new speak prototype now floats around somewhere in your logic. This is very inefficient.
To fix this, we need to modify the prototype of our function:
function Person(name) {
this.name = name;
}
Person.prototype.speak = function (msg) {
console.log('Person says:' + msg);
};
var dad = new Person('David');
dad.speak('I am your dad!');
console.log('Is dad.speak equal to dad.speak?', dad.speak === dad.speak);
var mom = new Person('David');
console.log('Is mom.speak equal to dad.speak?', dad.speak === dad.speak);
This way, we only have the function created once, on the prototype which is inherited to all instances. This is easier to maintain and a lot more efficient.
Now we can extend DOM object via their prototype, but it isn't recommended because you start to mess with the web standards, making troubleshooting much more difficult.
Array.prototype.isLengthGreaterThanFive = function(thisArg) {
return this.length > 5;
};
console.log([1, 2, 3, 4].isLengthGreaterThanFive(), [1, 2, 3, 4, 5, 6].isLengthGreaterThanFive());
A better way of handling this is to create extended objects or to simply use functions:
//Using functions
function isLengthGreaterThanFive(array) {
return array.length > 5;
}
console.log(isLengthGreaterThanFive([1, 2, 3, 4]), isLengthGreaterThanFive([1, 2, 3, 4, 5, 6]));
//Using a wrapper class
var MyArray = (function() {
function MyArray(array) {
if (array === void 0) {
array = [];
}
this.array = array;
}
MyArray.prototype.isLengthGreaterThanFive = function() {
return this.array.length > 5;
};
return MyArray;
}());
console.log(new MyArray([1, 2, 3, 4]).isLengthGreaterThanFive(), new MyArray([1, 2, 3, 4, 5, 6]).isLengthGreaterThanFive());
The benefits of using a class is that we can extend upon our idea of the object:
//Base class
function Person(firstname, lastname, says) {
if (firstname === void 0) {
firstname = "Leonado";
}
this.firstname = firstname;
if (lastname === void 0) {
lastname = "Da Vinci";
}
this.lastname = lastname;
if (says === void 0) {
says = "hello";
}
this.says = says;
}
//Base methods
Person.prototype.iAm = function () {
return this.firstname + " " + this.lastname;
};
Person.prototype.Speak = function () {
return this.says + " my name is " + this.iAm();
};
//Extended class
function Warrior(firstname, lastname, says) {
//Call in constructor
Person.call(this, firstname, lastname, says);
}
//Inheriting
Warrior.prototype = Object.create(Person.prototype);
Warrior.prototype.constructor = Warrior;
//Overruling "Speak"
Warrior.prototype.Speak = function () {
return "My name is " + this.iAm() + ", " + this.says;
};
console.log([new Warrior("Peter", "Allan", "Ahoyhoy").Speak(), new Person("Peter", "Allan", "Ahoyhoy").Speak()]);
In the example above we extend the prototype of Person for Warrior so that we retain the functionality of Person, and then simply modify what's different about a Warrior. This way we get to reuse the prototype method iAm, and we can focus on only changing what needs to change in the Speak method.
EDIT 1
I noticed too late that the question had changed a little.
You can treat DOM elements like any other class in JavaScript. The following setup has all Persons sharing a single DIV to speakUp:
var Person = (function () {
function Person(age, firstname, lastname) {
if (age === void 0) { age = 50; }
if (firstname === void 0) { firstname = "Peter"; }
if (lastname === void 0) { lastname = "Venkman"; }
this.age = age;
this.firstname = firstname;
this.lastname = lastname;
}
Person.prototype.speakUp = function () {
Person.bubble.innerHTML = this.firstname + " " + this.lastname + " is " + this.age + " years old";
};
return Person;
}());
Person.bubble = document.createElement("div");
document.body.appendChild(Person.bubble);
setInterval(function () {
var p = new Person(Math.floor(Math.random() * 100));
p.speakUp();
}, 3000);
This could easily become a DIV per Person, or a refereced DOM object (document.getElementById) shared among all Persons.
EDIT 2
In response to your comment:
In JavaScript everything is in essence and object. You create a function it registers an object with the functions name and returns and instance of that object. Everything like Arrays, Strings, DOM elements and custom functions has some master object hidden behind the scenes. Every time a new Array or DOM element or whatever is created, it has a reference to its master object (called the prototype). This is called the prototype chain.
If you look on my second example above, when dad.speak is called JavaScript first searches the instance for a speak property, but it won't find one because we haven't assigned it the way we did in example one were it was instance specific.
JavaScript will then try one level up the prototype chain and here it will find a matching property and use this instead. This way we can alter the default behavior of custom OR existing elements in JavaScript.
The idea being, that if you have some property that all instances of a prototype should have, then we simply modify the prototype once and they will all inherit this property.
Think of it this way. If you were to describe all living things on earth in JavaScript you would want some form of groupings. For instance the first level would be something like an Exists object that carries a property for a name and an id. From here you you could create Plant and Animal and have them both inherit the prototype of Exists. Now we could create a Flower class that inherits Plant and a Rose class that inherits Flower and so on.
The idea is to apply you properties in a way that makes sense to human beings via inheritance (an owl can fly because it is a bird / a shark can swim because it is a fish). Binding them at the level that makes sense, inheriting in a logical pattern and using your time efficiently.
If you are still confused, try looking up prototype tutorials.
Here is a good Youtube video to explain it:
https://www.youtube.com/watch?v=PMfcsYzj-9M
I'm playing around with ES6 classes and my eventual goal is to understand the difference between classes vs constructor functions vs factory functions. My current understanding is that constructor functions and classes are basically using the same design pattern, classes just being the new syntax for the constructor functions. Based on this assumption, I'm trying to create some examples that display the contrast between class/constructor functions and factory functions.
With the examples below I aim to return new objects and some methods that are inherited.
My first example is rather straightforward with the class syntax.
Example #1: Classes
class Person {
constructor(name, age, location, occupation) {
this.name = name;
this.age = age;
this.location = location;
this.occupation = occupation;
}
printDescription() {
console.log(`My name is ${this.name} and I'm ${this.age} years old. I live in ${this.location} and I work as a ${this.occupation}.`);
}
}
const firstUser = new Person('Tom', 30, 'Sydney', 'Teacher');
firstUser.printDescription();
With the second example, I'm trying to replicate the first one with a different approach but I'm not sure if this is factory constructor or a factory function.
Example #2: ?
function PersonMaker (name, age, location, occupation) {
let person = {name, age, location, occupation};
person.printDetails = () => {
console.log(`My name is ${name} and I'm ${age} years old. I live in ${location} and I work as a ${occupation}.`);
};
return person;
}
const secondUser = PersonMaker('Johnny', 25, 'London', 'Driver');
secondUser.printDetails();
I need to clarify the pattern used in the second example and if it's not a factory function, how I could turn it into one?
Since it returns an object its a factory function - it's already explained there.
Constuctor functions behaviour different from this, it doesn't return value:
function Person(name, age, location, occupation){
this.name = name
this.age = age
this.location = location
this.occupation = occupation
}
Person.prototype.printDetails = function(){
console.log(`My name is ${this.name} and I'm ${this.age} years old. I live in ${this.location} and I work as a ${this.occupation}.`);
};
const secondUser = new Person('Johnny', 25, 'London', 'Driver');
secondUser.printDetails();
I used definition of methods by extending prototype just to separate constructor function, and you can still define a methods inside constructor function as well:
function Person(name, age, location, occupation){
this.name = name
this.age = age
this.location = location
this.occupation = occupation
this.printDetails = function(){
console.log(`My name is ${this.name} and I'm ${this.age} years old. I live in ${this.location} and I work as a ${this.occupation}.`);
};
}
const secondUser = new Person('Johnny', 25, 'London', 'Driver');
secondUser.printDetails();
Javascript takes its roots in Prototypal Inheritance:
// Prototype
var ProtoCtr = function ProtoCtr(name, age) {
this.name = name;
this.age = age;
};
ProtoCtr.prototype.printDetails = function printDetails() { console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old`); };
var protoInstance = new ProtoCtr('John', 21);
protoInstance.printDetails();
In short:
Functions expose prototypes
Defines a this context referring to the instance
Use the new keyword to create an instance
Then, with ES6, the language offered the possibility to use the class keyword (to please classical OO developers). It's a syntax which allow to group the declaration of your object in a block, avoiding to extend the prototype "manually". Under the hood, it does the exact same thing than the prototypal model, it's just an other way to write it.
// Class
class ClassCtr {
constructor(name, age) {
this.name = name;
this.age = age;
}
printDetails() {
console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old`);
}
}
var classInstance = new ClassCtr('John', 21);
classInstance.printDetails();
Key concepts are about the same, but you don't use prototype to expose methods to your instance but declare it directly inside the class block.
Then, there is the factory pattern, which is not a language specs but a pattern, a way to do things. It's my personal favorite. The factory function builds and return the instance. With this method, you get rid of the new keyword and don't need the this anymore to refer to you instance:
// Factory
var factoryCtr = function factoryCtr(name, age) {
var instance = {
name: name,
age: age,
printDetails: function printDetails() { console.log(`Hi, I'm ${instance.name} and I'm ${instance.age} years old`); }
};
return instance;
}
var factoryInstance = factoryCtr('John', 21);
factoryInstance.printDetails();
Even if Class is not covered by this talk, I recommand you to watch this video:
https://www.youtube.com/watch?v=ya4UHuXNygM
I hope this helped :)
To avoid passing many arguments, you can do it like this.
const PersonMaker = description => {
const {name, age, location, occupation} = description;
return {
printDetails: () => {
console.log(
`My name is ${name} and I'm ${age} years old. I live in ${location} and I work as a ${occupation}.`,
);
},
};
};
const secondUser = PersonMaker({
name: 'Johnny',
age: '25',
location: 'London',
occupation: 'Driver',
});
console.log(secondUser.printDetails());