Let's say I have Player object:
var player = function(name) {
this.handlers = {};
}
player.prototype.on = function(event, callback) {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event].push(callback);
}
It works great, I can create players and each will have its own set of handlers. Now suppose I need to inherit from player:
var testPlayer = function(name) {
this.name = name;
};
testPlayer.prototype = new player();
Now when I create testPlayer's, each of them share the same handlers property:
var adam = new testPlayer('Adam');
adam.on('test', function(){});
var eve = new testPlayer('Eve');
// eve.handlers == {'test':<function>}
What am I missing here? I understand than every testPlayer's prototype is the same new player object I create when describing child class. But is there some way for all testPlayers to have their own set of handlers?
That sure looks strange for those used to classical inheritance, but it's how prototypal inheritance works. To have a separate handlers object per instance, you need to specify one on the child constructor. That will shadow the prototype's property with the same name:
var testPlayer = function(name) {
this.name = name;
this.handlers = {};
};
testPlayer.prototype = new player();
Another solution would be to create this shadowing property on-demand, from your on method:
player.prototype.on = function(event, callback) {
// Check for a handlers property on the instance
if(!this.hasOwnProperty('handlers') {
this.handlers = {};
}
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event].push(callback);
}
Interesting fact
This is only a problem if you're modifying properties of an object (or array) on the prototype. If you try to assign to properties that live on the prototype, a local shadowing property will be created automatically (you can't assign to prototype properties from instances).
The problem here is that handlers is a property that was added in the constructor, so when you do
testPlayer.prototype = new player();
you're adding every property of a brand new player object to testPlayer.prototype, and that includes handlers.
So, there's a handlers property in every testPlayer object, and when you add a property to handlers you're adding the property to the object in the prototype, and not of the testPlayer object.
In short, when calling the on method you're adding a property to testPlayer.prototype.handlers, not adam.handlers or eve.handlers.
To be safe, define:
var testPlayer = function(name) {
this.name = name;
this.handlers = {};
};
Related
I recently read that using private method is bad because they are very memory inefficient because a new copy of the method would be created for each instance. In the example given, how is dispalyIncreasedSalary more efficient than increaseSalary?
var Employee = function (name, company, salary) {
this.name = name || ""; //Public attribute default value is null
this.company = company || ""; //Public attribute default value is null
this.salary = salary || 5000; //Public attribute default value is null
// Private method
var increaseSalary = function () {
this.salary = this.salary + 1000;
};
// Public method
this.dispalyIncreasedSalary = function() {
increaseSalary();
console.log(this.salary);
};
};
// Create Employee class object
var emp1 = new Employee("John","Pluto",3000);
// Create Employee class object
var emp2 = new Employee("Merry","Pluto",2000);
// Create Employee class object
var emp3 = new Employee("Ren","Pluto",2500);
In the following example is this.dispalyIncreasedSalary reused for all objects?
No. You are binding it to this. So each gets it's own copy of that. That is what an instance member means. They bind to instance and not to Class/Object.
What about this.name etc. properties?
Same.
Aren't the this.propName not copied for all the instances?
If that happens all instance see the same value and that's a big NO.
You are correct that it is wasteful/inefficient to duplicate what could be a shared function for each instance.
You should investigate how JavaScript prototypes work: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes
So shared methods would be placed onto every object and shared like this:
Employee.prototype.dispalyIncreasedSalary = function() {
increaseSalary();
console.log(this.salary);
}
Why do we need to set prototype object for inheritance when we can directly inherit parent properties if we call the constructor method using this in each child function?
function Employee() {
this.name = "";
this.dept = "general";
}
function Manager() {
Employee.call(this);
this.reports = [];
}
Manager.prototype = Object.create(Employee.prototype);
We can use inheritence even if we don't set prototype of Manager as Employee.
Usually prototypes are used to put functions/methods on them, not properties, because with properties you'll have a single property value shared between all object instances. Also, you may not need to set prototype for inheritance if you add methods inside a constructor. For example:
function Employee(name) {
this.name = "";
this.dept = "general";
this.reportName = function() {return this.name};
}
function Manager(name) {
Employee.call(this, name);
this.reports = [];
}
var manager = new Manager('Peter');
manager.reportName(); // Peter
However, adding methods/function inside object's constructor is inefficient, since every time a constructor is called an instance of a function is created. So usually, all methods, not properties, are allocated on prototype like this:
function Employee(name) {
this.name = "";
this.dept = "general";
}
Employee.prototype.reportName = function() {return this.name};
Now, in this case, simply calling a constructor won't be enough:
function Manager(name) {
Employee.call(this, name);
this.reports = [];
}
var manager = new Manager('Peter');
manager.reportName(); // throws an error
You need to set a prototype:
Manager.prototype = Object.create(Employee.prototype)
var manager = new Manager('Peter');
manager.reportName(); // 'Peter'
So I'm learning prototype using javascript, and tried some code :
function Employee(name) { this.name= name; }
var m = new Employee("Bob");
var working= { isWorking: true };
Employee.prototype = working;
alert(m.isWorking);
Unfortunately, I get an undefined message, instead of the true value. Is there a reason to this result?
I have made several tests. I've concluded that reassigning the prototype object causes any previously created instances of the Employee class to be unable to access any properties found inside the newly assigned prototype. Is this accurate?
Changing the prototype will not affect an already created object. It will only affect the objects created based on that object.
There is a property __proto__ which could be used to change the prototype, but its implementation is not required. ES6 does define setPrototypeOf method to change the prototype, but since it's only in ES6 the support may vary.
First off, you have created an instance of Employee before you set the prototype, so that object will not have inherited the new prototype values.
Next, any objects created after you have set the prototype will inherit the new prototype object.
Lastly, the object will have the isWorking property, rather than a working property.
So to redo your example:
function Employee(name) { this.name= name; };
var m1 = new Employee("Bob");
var working= { isWorking: true };
Employee.prototype = working;
var m2 = new Employee("Sam");
alert(m1.isWorking); // undefined
alert(m2.isWorking); // true
Simple fix is to properly assign it.
function Employee(name) {
this.name = name;
}
var m = new Employee("Bob");
var working = {
isWorking: true
};
Employee.prototype.working = working;
alert(m.working.isWorking);
A better fix for MULTIPLE employees is to make a class, then create instances of that: play around with it here: http://jsfiddle.net/MarkSchultheiss/p6jyqbgv/1/
"use strict";
function makeClassStrict() {
var isInternal, instance;
var constructor = function(args) {
if (this instanceof constructor) {
if (typeof this.init == "function") {
this.init.apply(this, isInternal ? args : arguments);
}
} else {
isInternal = true;
instance = new constructor(arguments);
isInternal = false;
return instance;
}
};
return constructor;
}
var EmployeeClass = makeClassStrict();
EmployeeClass.prototype.init = function(employeeName, isWorking) {
var defaultName = 'notbob';
this.name = employeeName ? employeeName : defaultName;
this.working = !!isWorking;
};
// call this to get the name property
EmployeeClass.prototype.getName = function() {
return this.name
};
//note no "new" needed due to the makeClassStrict that does that
var m = EmployeeClass("Bob");
alert(m.working +":"+ m.name);
m.working = true;
alert(m.working +":"+ m.name);
var notbob = EmployeeClass("Charlie",false);
alert(notbob.working +":"+ notbob.name);
alert(notbob.getName()+ m.getName());
You cannot override the entire prototype property and expect already existing instances to work. JavaScript doesn't work that way. But you can loop through the prototype object and unset anything already set, then loop through your new object, and set it to something else.
function Employee(name) { this.name= name; }
var m = new Employee("Bob");
var working= { isWorking: true };
for(var j in Employee.prototype){delete Employee.prototype[j];}//unset all properties, the same as setting to {}
for(j in working){Employee.prototype[j]=working[j];}//set the properties
alert(m.isWorking);
Is there a better way of having a class inherit prototype methods from another class and still be able to define new prototype methods on the class that inherits than this:
var ParentConstructor = function(){
};
ParentConstructor.prototype = {
test: function () {
console.log("Child");
}
};
var ChildConstructor = function(){
ParentConstructor.call(this)
};
ChildConstructor.prototype = {
test2: "child proto"
};
var TempConstructor = function(){};
TempConstructor.prototype = ParentConstructor.prototype;
ChildConstructor.prototype = new TempConstructor();
ChildConstructor.prototype.constructor = ChildConstructor;
var child = new ChildConstructor();
child.test();
console.log(child.test2)
console.log(child, new ParentConstructor());
Which isn't working, because I lose my test2 prototype method / property when I add the inheritance from my ParentConstructor.
I've tried other ways to extend the prototype methods of a class with some prototype props form other classes but I have failed each time, because I couldn't find a way not to override the previous methods each time.
I have also tried the var Child = Object.create(Parent.Prototype), but when I define new props I lose the parent props.
Setting up inheritance should take place before you define new properties on the prototype of ChildConstructor. And when you define new prototype properties, you also shouldn't override the whole prototype property. Instead, you can simply add new properties, like you already did with the constructor property:
ChildConstructor.prototype = new ParentConstructor();
ChildConstructor.prototype.constructor = ChildConstructor;
ChildConstructor.prototype.test2 = "child proto";
The best example I can think of comes from:
http://robertnyman.com/2008/10/06/javascript-inheritance-how-and-why/
function Being() {
this.living = true;
this.breathes = function () {
return true;
};
}
function Robert() {
// Robert.prototype = new Being(); /* edit */
this.blogs = true;
this.getsBored = function () {
return "You betcha";
};
}
Robert.prototype = new Being();
Robert.prototype.newMethod = function() {
console.log('new method executed');
return this;
}
Note this example, has been updated, the first comment below is directed at the first code I had up, which contained the prototype inside the Robert method.
I am learning the basics of OOP in Javascript and came across an inheritance example which is difference than what I've typically seen.
Typical:
ChildClass.prototype = new ParentClass();
Alternate Method:
function clone(object) {
function OneShotConstructor(){}
OneShotConstructor.prototype = object;
return new OneShotConstructor();
}
SecondClass.prototype = clone(FirstClass.prototype);
Why would the latter be preferred when creating an object whose prototype is another object?
Because you will invoke the constructor of the custom type (a.k.a. class) you are trying to inherit from. And that might have side effects. Imagine the following:
var instancesOfParentClass = 0;
function ParentClass (options) {
instancesOfParentClass++;
this.options = options;
}
function ChildClass () {}
ChildClass.prototype = new ParentClass();
Your counter has been incremented, but you didn't really create a useful instance of ParentClass.
Another problem, is that all instance properties (see this.options) will be present on ChildClass' prototype, and you probably don't want that.
Note: When using constructor, you might have instance properties, and shared properties. For example:
function Email (subject, body) {
// instance properties
this.subject = subject;
this.body = body;
}
Email.prototype.send = function () {
// do some AJAX to send email
};
// create instances of Email
emailBob = new Email("Sup? Bob", "Bob, you are awesome!");
emailJohn = new Email("Where's my money?", "John, you owe me one billion dollars!");
// each of the objects (instances of Email) has its own subject
emailBob.subject // "Sup? Bob"
emailJohn.subject // "Where's my money?"
// but the method `send` is shared across instances
emailBob.send === emailJohn.send // true