I have just started to learn OOP. I am still trying to understand how everything works. I am trying to make a new function that will basically allow me to create a new object by passing the parameters of said class.
Is this even possible, and if so what am I doing wrong?
As stated, still learning so any advice will be appreciated.
class Person {
constructor(name) {
this.persName = name;
}
myName() {
return "My name is " + this.persName;
}
}
function getPers(_perName, fullName) {
_personName = new Person(fullName);
}
$(document).ready(function() {
getPers(John, "John Doe");
});
You can follow the following exapmle. Please note there is not much logic init. The example is just to show you how OOP can work. Of cause, there are many other and better ways to got with that, but for the first try, it should be good to use.
class Person {
//declare the constructor with required name values and optional age value
constructor(firstName, lastName, age = 0) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
//declare a typically setter, e.g. for age
setAge(age) {
this.age = age;
}
//declare a getter for the person with age
getPerson() {
return this.firstName + ' ' + this.lastName + ' is ' + this.age + ' years old.';
}
}
//here you got with some function within you need to define a new person
function getCreatedPerson(firstName, lastName, age = 0) {
//define the person with required firstname and lastname
const person = new Person(firstName, lastName);
//use the setter to set age of person
person.setAge(age);
//return the person by using the person getter
return person.getPerson();
}
//here you can call the createdPerson function
$(document).ready(function() {
console.log(getCreatedPerson('John', 'Doe', 32));
});
Hope it helps a bit to understand how it could work.
Yes, you can take the fullname as a parameter to your function and pass it through to the OOP method or constructor you're calling.
But you can't take a "reference to a variable" as an argument, like you tried with _perName/_personName. Instead, use the return keyword to return the created object:
function createPerson(fullName) {
return new Person(fullName);
}
$(document).ready(function() {
var john = createPerson("John Doe");
… // use john
});
Related
I've been learning about prototype in Javascript from a Pluralsight course. And I have some confusion about it.
Here's the example. I have 2 constructors Person and Student:
function Person(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.getFullName = function() {
console.log(this.firstName + this.lastName)
}
}
function Student(firstName, lastName, age) {
this._enrolledCourses = [];
this.enroll = function (courseId) {
this._enrolledCourses.push(courseId);
};
this.getCourses = function () {
return this._enrolledCourses;
};
}
Then create an instance of Student:
let michael = new Student("Michael", "Nguyen", 22);
Now, in the tutorial, it says that in order for michael to inherit everything from Person, there are 2 steps:
Step 1: Create prototype chain:
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Step 2: call Person inside Student:
function Student(firstName, lastName, age) {
Person.call(this, firstName, lastName, age); <---- this line
this._enrolledCourses = [];
this.enroll = function (courseId) {
this._enrolledCourses.push(courseId);
};
this.getCourses = function () {
f;
return this._enrolledCourses;
};
}
However, if I remove step 1 and only follow with step 2, the result remains the same. michael is still able to inherit everything from Person. The thing is, what's the point of step 1 anyway? If I remove step 2 and only get along with step 1, michael won't be able to inherit anything from Person.
FYI, here's the course url: https://app.pluralsight.com/course-player?clipId=f1feb535-bbdd-4255-88e3-ed7079f81e4e
This is because your constructors are adding all the properties to this, you're not using the prototypes.
Normally, methods are added to the prototype, not each instance, e.g.
function Person(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
Person.prototype.getFullName = function() {
console.log(this.firstName + this.lastName)
}
If you don't create the prototype chain, Student won't inherit a method defined this way.
I was looking into the concepts of inheritance in javascript.
Consider the following snippet:
var Person=function(firstname,lastname,gender){
this.firstname=firstname;
this.lastname=lastname;
this.gender=gender;
this.name=function(){
return this.firstname +" "+this.lastname;
}
}
var Employee = function(firstName, lastName, gender, title) {
Person.call(this, firstName, lastName, gender);
this.title = title;
};
var e = new Employee('Abc', 'efg', 'Tree', 'CEO');
Now, if we check the following:
console.log(e.hasOwnProperty('firstName') ) ==> TRUE
I want to know is there any way so that we can inherit the values from parents completely?
Such that
console.log(e.hasOwnProperty('firstName') ) ==> False
It's important to understand here that there's just one object being created by new Employee. That object has a combination of features defined for it by Person and by Employee, but it's one object. All of the this.x = y; statements in the constructors create own properties of that object. In fact, that particular example doesn't define anything useful for it to inherit, since nothing is put on the objects that will be in its prototype chain. (The prototype chain is also a bit malformed, see my answer here for the correct way to hook up Employee and Person in ES5 and also in ES2015+.)
You can do it differently such that there's an object for the Person parts and another object inheriting from it for the Employee parts, but if you want to go down that route, you probably want to stop using constructor functions (ones you use new with) and use builder functions instead, because the assumptions and standard practices around constructor functions expect a single combined object.
Here's a minimal example of doing it that way:
function createPerson(firstname, lastname, gender) {
const person = {};
person.firstname = firstname;
person.lastname = lastname;
person.gender = gender;
person.name = function() {
return person.firstname + " " + person.lastname;
};
return person;
}
function createEmployee(firstName, lastName, gender, title) {
const person = createPerson(firstName, lastName, gender);
const employee = Object.create(person); // <=== The key bit
employee.title = title;
return employee;
}
const e = createEmployee("Abc", "efg", "Tree", "CEO");
console.log(e.hasOwnProperty("firstname")); // false
console.log(e.hasOwnProperty("title")); // true
e.firstname = "Joe";
console.log(e.hasOwnProperty("firstname")); // true
This key bit:
const employee = Object.create(person); // <=== The key bit
...creates an object that uses person as its prototype. So now the Person stuff is on one object and the Employee stuff is on another object that inherits from it.
But note that bit at the end&nbsb;— if you do:
e.firstname = "Joe";
...it will set the property on e, not its prototype, so at that point e has its own property called firstname. You can fix that using accessor properties:
function createPerson(firstname, lastname, gender) {
const person = {
get firstname() {
return firstname;
},
set firstname(value) {
firstname = value;
},
get lastname() {
return lastname;
},
set lastname(value) {
lastname = value;
},
get gender() {
return gender;
},
set gender(value) {
gender = value;
},
name() {
return person.firstname + " " + person.lastname;
},
};
return person;
}
function createEmployee(firstName, lastName, gender, title) {
const person = createPerson(firstName, lastName, gender);
const employee = Object.create(person); // <=== The key bit
Object.defineProperty(employee, "title", {
get() {
return title;
},
set(value) {
title = value;
},
});
return employee;
}
const e = createEmployee("Abc", "efg", "Tree", "CEO");
console.log(e.hasOwnProperty("firstname")); // false
console.log(e.hasOwnProperty("title")); // true
e.firstname = "Joe";
console.log(e.hasOwnProperty("firstname")); // false
Both styles of using JavaScript (and several others) are perfectly valid. Using constructor functions is very common (and better supported these days via class syntax), but it's not the only way to use JavaScript.
This is completely fine, you are inheriting the properties of the Person through the call to the parent constructor Person.call(this, ...) and you are passing the this of the Employee instance to the Person constructor function.
When you invoke call and pass the this context of the Employee instance, it will actually use the this of the Employee to do these assignment operations:
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
this.name = function(){
return this.firstname +" "+this.lastname;
}
Here this refers to the new Employee instance. Since the above assignment makes these properties appear on the new Employee instance as own properties the hasOwnProperty returns true when you use any of these properties with the hasOwnProperty method.
This is the correct way to do inheritance for data properties in JavaScript. In case of methods we should put them in the prototype.
This is because the data properties should not be shared between the different instances of the Employee but the methods can, as they imply behaviour which can be common.
If you do not put the methods in the prototype and put them as own properties, it would become an overhead as the same logic would be present but the method would be a different instance for every instance of your constructor.
To complete your example, the hasOwnProperty is returning false for the name method as it is on the prototype, while other data properties will return true:
var Person = function(firstname, lastname, gender) {
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
}
Person.prototype.name = function() {
return this.firstname + " " + this.lastname;
}
var Employee = function(firstName, lastName, gender, title) {
Person.call(this, firstName, lastName, gender);
this.title = title;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
var e = new Employee('Abc', 'efg', 'Tree', 'CEO');
//Prints true
console.log(e.hasOwnProperty('firstname'));
//Prints false
console.log(e.hasOwnProperty('name'));
The Function.prototype.call basically uses the supplied this context, it has nothing to do with inheritance in general. Basically you are re-using the Person constructor function logic and populating the data property values of the Employee instance when you do this.
Let's say a simple prototype is defined:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName
this.fullName = first + " " + last;
}
Now, I want to add a new property down the road called nickName. (but I don't want to use a method, that's already well documented)
Person.prototype.nickName = (what to put here to have firstName + first letter of lastName)
I used:
Person.prototype = {
get nickName(){
return this.firstName+ this.lastName.charAt(0);
}
};
But it doesn't work for already created Persons.
I just want to know if there is a way to do it, besides including it in the initial definition.
You can just add a new method to the prototype:
Person.prototype.nickname = function() {
return this.firstName + this.lastName.charAt(0);
}
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.
Let's say I receive some JSON object from my server, e.g. some data for a Person object:
{firstName: "Bjarne", lastName: "Fisk"}
Now, I want some methods on top of those data, e.g. for calculating the fullName:
fullName: function() { return this.firstName + " " + this.lastName; }
So that I can
var personData = {firstName: "Bjarne", lastName: "Fisk"};
var person = PROFIT(personData);
person.fullName(); // => "Bjarne Fisk"
What I basically would want to do here, is to add a method to the object's prototype. The fullName() method is general, so should not be added to the data object itself. Like..:
personData.fullName = function() { return this.firstName + " " + this.lastName; }
... would cause a lot of redundancy; and arguably "pollute" the data object.
What is the current best-practice way of adding such methods to a simple data object?
EDIT:
Slightly off topic, but if the problem above can be solved, it would be possible to do some nice pseudo-pattern matching like this:
if ( p = Person(data) ) {
console.log(p.fullName());
} else if ( d = Dog(data) ) {
console.log("I'm a dog lol. Hear me bark: "+d.bark());
} else {
throw new Exception("Shitty object");
}
Person and Dog will add the methods if the data object has the right attributes. If not, return falsy (ie. data does not match/conform).
BONUS QUESTION: Does anyone know of a library that either uses or enables this (ie makes it easy)? Is it already a javascript pattern? If so, what is it called; and do you have a link that elaborates? Thanks :)
Assuming your Object comes from some JSON library that parses the server output to generate an Object, it will not in general have anything particular in its prototype ; and two objects generated for different server responses will not share a prototype chain (besides Object.prototype, of course ;) )
If you control all the places where a "Person" is created from JSON, you could do things the other way round : create an "empty" Person object (with a method like fullName in its prototype), and extend it with the object generated from the JSON (using $.extend, _.extend, or something similar).
var p = { first : "John", last : "Doe"};
function Person(data) {
_.extend(this, data);
}
Person.prototype.fullName = function() {
return this.first + " " + this.last;
}
console.debug(new Person(p).fullName());
There is another possibility here. JSON.parse accepts a second parameter, which is a function used to revive the objects encountered, from the leaf nodes out to the root node. So if you can recognize your types based on their intrinsic properties, you can construct them in a reviver function. Here's a very simple example of doing so:
var MultiReviver = function(types) {
// todo: error checking: types must be an array, and each element
// must have appropriate `test` and `deserialize` functions
return function(key, value) {
var type;
for (var i = 0; i < types.length; i++) {
type = types[i];
if (type.test(value)) {
return type.deserialize(value);
}
}
return value;
};
};
var Person = function(first, last) {
this.firstName = first;
this.lastName = last;
};
Person.prototype.fullName = function() {
return this.firstName + " " + this.lastName;
};
Person.prototype.toString = function() {return "Person: " + this.fullName();};
Person.test = function(value) {
return typeof value.firstName == "string" &&
typeof value.lastName == "string";
};
Person.deserialize = function(obj) {
return new Person(obj.firstName, obj.lastName);
};
var Dog = function(breed, name) {
this.breed = breed;
this.name = name;
}
Dog.prototype.species = "canine";
Dog.prototype.toString = function() {
return this.breed + " named " + this.name;
};
Dog.test = function(value) {return value.species === "canine";};
Dog.deserialize = function(obj) {return new Dog(obj.breed, obj.name);};
var reviver = new MultiReviver([Person, Dog]);
var text = '[{"firstName": "John", "lastName": "Doe"},' +
'{"firstName": "Jane", "lastName": "Doe"},' +
'{"firstName": "Junior", "lastName": "Doe"},' +
'{"species": "canine", "breed": "Poodle", "name": "Puzzle"},' +
'{"species": "canine", "breed": "Wolfhound", "name": "BJ"}]';
var family = JSON.parse(text, reviver)
family.join("\n");
// Person: John Doe
// Person: Jane Doe
// Person: Junior Doe
// Poodle named Puzzle
// Wolfhound named BJ
This depends on you being able to unambiguously recognizing your types. For instance, if there were some other type, even a subtype of Person, which also had firstName and lastName properties, this would not work. But it might cover some needs.
If you're dealing with plain JSON data then the prototype of each person object would simply be Object.prototype. In order to make it into an object with a prototype of Person.prototype you'd first of all need a Person constructor and prototype (assuming you're doing Javascript OOP in the traditional way):
function Person() {
this.firstName = null;
this.lastName = null;
}
Person.prototype.fullName = function() { return this.firstName + " " + this.lastName; }
Then you'd need a way to turn a plain object into a Person object, e.g. if you had a function called mixin which simply copied all properties from one object to another, you could do this:
//example JSON object
var jsonPerson = {firstName: "Bjarne", lastName: "Fisk"};
var person = new Person();
mixin(person, jsonPerson);
This is just one way of solving the problem but should hopefully give you some ideas.
Update: Now that Object.assign() is available in modern browsers, you could use that instead of writing your own mixin function. There's also a shim to make Object.assign() work on older browsers; see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill.
You should probably not do this.
JSON allows you to serialize a state, not a type. So in your use case, you should do something like this :
var Person = function ( data ) {
if ( data ) {
this.firstName = data.firstName;
this.lastName = data.lastName;
}
};
Person.prototype.fullName = function ( ) {
return this.firstName + ' ' + this.lastName;
};
//
var input = '{"firstName":"john", "lastName":"Doe"}';
var myData = JSON.parse( input );
var person = new Person( myData );
In other words you want to change prototype (a.k.a. class) of existing object.
Technically you can do it this way:
var Person = {
function fullName() { return this.firstName + " " + this.lastName; }
};
// that is your PROFIT function body:
personData.__proto__ = Person ;
After that if you will get true on personData instanceof Person
Use the new-ish Object.setPrototypeOf(). (It is supported by IE11 and all the other browsers now.)
You could create a class/prototype that included the methods you want, such as your fullName(), and then
Object.setPrototypeOf( personData, Person.prototype );
As the warning (on MDN page linked above) suggests, this function is not to be used lightly, but that makes sense when you are changing the prototype of an existing object, and that is what you seem to be after.
I don't think it is common to transport methods with data, but it seems like a great idea.
This project allows you to encode the functions along with your data, but it is not considered standard, and requires decoding with the same library of course.
https://github.com/josipk/json-plus
Anonymous objects don't have a prototype. Why not just have this:
function fullName(obj) {
return obj.firstName + ' ' + obj.lastName;
}
fullName(person);
If you absolutely must use a method call instead of a function call, you can always do something similar, but with an object.
var Person = function (person) { this.person = person; }
Person.prototype.fullName = function () {
return this.person.firstName + ' ' + this.person.lastName;
}
var person = new Person(personData);
person.fullName();
You don't need to use prototypes in order to bind a custom method in your barebone object.
Here you have an elegant example that don't pollute your code avoiding redundant code
var myobj = {
title: 'example',
assets:
{
resources: ['zero', 'one', 'two']
}
}
var myfunc = function(index)
{
console.log(this.resources[index]);
}
myobj.assets.giveme = myfunc
myobj.assets.giveme(1);
Example available in https://jsfiddle.net/bmde6L0r/