In my nodejs program, i have a module called person and it has a prototype object(about) which also has its child method (describe). I am trying to access objects properties but i am getting undefined.
Could someone correct my understanding? what i am doing wrong here?
index.js
var Person = require("./person");
var sam = new Person({name:"Sam",age:23})
sam.about.describe();
person.js
module.exports = (function() {
var person = function(options) {
if (options && options.name) this.name = options.name;
if (options && options.age) this.age = options.age;
}
person.prototype.about = {
describe : function(){
console.log("I am",this.name,"and",this.age,"years old");
}
}
return person;
})();
Expected output: "I am Sam and 23 years old"
Actual output: "I am undefined and undefined years old"
As others have said, this in your example refers to the about object, not the person instance itself.
One way to have the API you want, is to create that about namespace within the constructor, and use bind to set correct context to the describe handler.
module.exports = (function() {
var person = function(options) {
if (options && options.name) this.name = options.name;
if (options && options.age) this.age = options.age;
this.about = {
describe: function () {
console.log("I am",this.name,"and",this.age,"years old");
}.bind(this)
};
}
return person;
})();
This way you can simply call
var Person = require("./person");
var sam = new Person({name:"Sam",age:23})
sam.about.describe();
>>> I am Sam and 23 years old
It's because this refers to the direct parent of describe, which is about:
person.prototype.about = {
describe : function() {
console.log("I am",this.name,"and",this.age,"years old");
}
}
You'd need a weak reference to the main Person object passed to the about namespace for example, and use it in place of this. But I don't like it that way, it is circumvoluted, feels wrong and looks smelly.
Instead, let's just aknowledge the fact that it's a completely new functionality package added to the Person object and let's make a helper for it:
module.exports = (function() {
var PersonHelper = function(person) {
this.person = person;
describePerson: function() {
console.log("I am",this.person.name,"and",this.person.age,"years old");
}
/* more methods */
}
return PersonHelper;
})();
So you could then do:
module.exports = (function() {
var person = function(options) {
if (options && options.name) this.name = options.name;
if (options && options.age) this.age = options.age;
}
return person;
})();
var Person = require("./person");
var PersonHelper = require("./personHelper");
var sam = new Person({name:"Sam",age:23})
var helper = new PersonHelper(person);
helper.describePerson();
If you really want to be able to use sam.about.describe, with an about "namespace" containing methods, then one approach is
Object.defineProperty(person.prototype, 'about', {
get() {
return {
describe: () => console.log("I am", this.name, "and", this.age, "years old");
};
}
})
This works because the context within get is that of the Person instance, and the arrow function will thus refer to it properly.
Related
I have recently watched a video where Douglas Crockford was explaining inheritance patterns of Javascript. The video itself is pretty old - it was filmed 6 years ago - but still useful. In that video he showed one inheritance pattern he kinda invented (although I am not sure who the author is). This is the code using his approach:
// imitation of new operator
function objectConstructor(obj, initializer, methods) {
// create prototype
var func, prototype = Object.create(obj && obj.prototype);
// add methods to the prototype
if(methods) Object.keys(methods).forEach(function(key) {
prototype[key] = methods[key];
});
// function that will create objects with prototype defined above
func = function() {
var that = Object.create(prototype);
if(typeof initializer === 'function') initializer.apply(that, arguments);
return that;
}
func.prototype = prototype;
prototype.constructor = func;
return func;
}
var person = objectConstructor(Object, function(name) {
this.name = name;
}, {
showName: function() {
console.log(this.name);
}
});
var employee = objectConstructor(person, function(name, profession) {
this.name = name;
this.profession = profession;
}, {
showProfession: function() {
console.log(this.profession);
}
});
var employeeInfo = employee('Mike', 'Driver');
employeeInfo.showName(); // Mike
employeeInfo.showProfession(); // Driver
Unfortanately, he didn't show the invocation. So, this part
var employeeInfo = employee('Mike', 'Driver');
employeeInfo.showName();
employeeInfo.showProfession();
is mine. It generally works, but it turns out that I repeat this.name = name; for both "classes" - person and employee. I played around but I didn't manage to make it work properly without that repetition. Seems I cannot get name because such a property isn't contained in the prototypal chain for employee. I didn't succeed either in mixing in stuff like person.call(this, arguments). So, apart from whether it is cool/nice/smart/sensible etc. or not in 2017, how could I remove this.name = name; from employee and get the same result? Or everything is ok and this approach doesn't suppose it?
Here is your snippet with 2 small modifications so that you can do a super(name) type of call.
I've placed comments were I've made the modifications.. with prefix keith:
// imitation of new operator
function objectConstructor(obj, initializer, methods) {
// create prototype
var func, prototype = Object.create(obj && obj.prototype);
// add methods to the prototype
if(methods) Object.keys(methods).forEach(function(key) {
prototype[key] = methods[key];
});
// function that will create objects with prototype defined above
func = function() {
var that = Object.create(prototype);
if(typeof initializer === 'function') initializer.apply(that, arguments);
return that;
}
func.prototype = prototype;
//keith: store the initialization in constructor,
//keith: as func is already creating the object..
prototype.constructor = initializer;
return func;
}
var person = objectConstructor(Object, function(name) {
this.name = name;
}, {
showName: function() {
console.log(this.name);
}
});
var employee = objectConstructor(person, function(name, profession) {
//keith: call our super person(name)
person.prototype.constructor.call(this, name);
this.profession = profession;
}, {
showProfession: function() {
console.log(this.profession);
}
});
var employeeInfo = employee('Mike', 'Driver');
employeeInfo.showName(); // Mike
employeeInfo.showProfession(); // Driver
Since the func constructor completely disregards this, passing any context to it via call or apply will not work. Creating a way to copy over the super class' properties after creating an object is one of the ways you could accomplish your task.
// imitation of new operator
function objectConstructor(obj, initializer, methods) {
// create prototype
var func, prototype = Object.create(obj && obj.prototype);
// add methods to the prototype
if(methods) Object.keys(methods).forEach(function(key) {
prototype[key] = methods[key];
});
// function that will create objects with prototype defined above
func = function() {
var that = Object.create(prototype);
if(typeof initializer === 'function') initializer.apply(that, arguments);
return that;
}
func.prototype = prototype;
prototype.constructor = func;
return func;
}
function copyProperties(source, target) {
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
}
var person = objectConstructor(Object, function(name) {
this.name = name;
}, {
showName: function() {
console.log(this.name);
}
});
var employee = objectConstructor(person, function(name, profession) {
copyProperties(person.apply(null, arguments), this);
this.profession = profession;
}, {
showProfession: function() {
console.log(this.profession);
}
});
var employeeInfo = employee('Mike', 'Driver');
employeeInfo.showName(); // Mike
employeeInfo.showProfession(); // Driver
I'm always getting Cannot set property 'saySomething' of undefined but why?
Am I making a mistake somewhere?
var Person = new Object();
Person.prototype.saySomething = function ()
{
console.log("hello");
};
Person.saySomething();
Debugging tip: You get this ..of undefined errors when you try to access some property of undefined.
When you do new Object(), it creates a new empty object which doesn't have a prototype property.
I am not sure what exactly are we trying to achieve here but you can access prototype of function and use it.
var Person = function() {};
Person.prototype.saySomething = function() {
console.log("hello");
};
var aperson = new Person();
aperson.saySomething();
The prototype property exists on functions, not on instantiated objects.
var Person = new Object();
console.log(Person.prototype); // undefined
var Person2 = function () {}
console.log(Person2.prototype); // {}
This is useful because things put on the prototype of a function will be shared by all object instances created with that function (by using new).
var Person = function() {};
Person.prototype.saySomething = function() {
console.log("hello");
};
console.log(
new Person().saySomething === Person.prototype.saySomething // true. they are the same function
);
If all you want is to add a method to the person object, there's no need for a prototype:
var Person = {};
Person.saySomething = function() {
console.log("hello");
};
Person.saySomething();
You can even use object literal syntax:
var Person = {
saySomething: function() {
console.log("hello");
}
};
Person.saySomething();
i was trying out some code thought of posting it, might help others.
<script>
var MODULE = {};
MODULE = (function (my) {
my.anotherMethod = function () {
console.log("hello ");
};
my.newMethod = function(){
console.log("hi new method ");
}
return my;
}(MODULE));
MODULE.anotherMethod();
MODULE.newMethod();
</script>
And please not var MODULE ={}, if this is not initialized with {} then it give cannot set property.
I know i am late to the party but as you see there is no satisfying answer available to the question so i am providing my own.
In your case when you write
var Person = new Object();
you are creating an instance of Object type.
You can add a property using prototype property to the Object, not to the instance of Object.which you can use by the instance laterly.
so you can define like
Object.prototype.saySomething = function ()
{
console.log("hello");
};
now you can call it like this.
Person.saySomething();
You can check here.
var Person = function(name) {
this.canTalk = true;
this.name = name;
};
Person.prototype.greet = function() {
if (this.canTalk) {
console.log('Hi, I am ' + this.name);
}
};
bob = new Person('bob');
bob.greet();
I've been reading Inheritance and the prototype chain and somewhere it sais :
Bad practice: Extension of native prototypes
One mis-feature that is often used is to extend Object.prototype or
one of the other built-in prototypes.
This technique is called monkey patching and breaks encapsulation.
While used by popular frameworks such as Prototype.js, there is still
no good reason for cluttering built-in types with additional
non-standard functionality.
The only good reason for extending a built-in prototype is to backport
the features of newer JavaScript engines; for example Array.forEach,
etc.
What I'm trying to do in a real example is an angular service to handle http requests that returns a simple object instance with few attributes and 2 methods findAll() and findOne(id) that I'll use one of them (and only once) in my ui-router's resolve method :
resolve: {
Resource: 'Resource',
allImages: function(Resource) {
var images = new Resource('images');
return images.findAll();
},
singleImage: function(Resource) {
var image = new Resource('images');
return image.findOne(4);
}
},
Depending on which of the 2 methods I'll call, I'm trying to extend (or even replace) the whole instance Resource with another predefined one Item or Collection where each have its own structure and methods so in my Controller I can do stuffs like :
if (allImages.existNext()) allImages.nextPage();
var currentPage = allImages.meta.currentPage,
collection = allImages.data;
singleImage.url = 'abc';
singleImage.save();
Now, the only working solution I found so far (represented by a minimal example that have nothing to do with http requests) is this :
var myApp = angular.module('myApp',[]);
myApp.factory('Animal', Animal);
function Animal() {
var Cat = function() {
this.prefferedFood = 'fish';
};
Cat.prototype.sayMiau = function() {
console.log('Miau!')
};
var Dog = function(name) {
this.dogName = name;
};
Dog.prototype.sayGrr = function() {
console.log('Grrr!')
};
function Animal(age) {
this.age = age;
}
Animal.prototype = {
makeItCat: function() {},
makeItDog: function(name) {
Dog.call(this, name);
this.__proto__ = Dog.prototype;
},
};
return Animal;
}
So inside Controller I can do:
var Dan = new Animal(7);
Dan.makeItDog('Dan');
Console.log(Dan.age); // outputs "7"
Console.log(Dan.dogName); // outputs "Dan"
Dan.sayGrr(); // outputs "Grrr!"
And it works as you can see in this jsfiddle.
The Question is :
Is that Correct? I'm not breaking the way how Object.prototype should work or losing the performance gain it should provide? Is there a better way to do it? like maybe using angular.extend or maybe not extending (or replacing) prototype at all and using something like this instead:
var Dog = function(name) {
this.dogName = name;
this.sayGrr = function(string) {
console.log('Grrr!')
}
};
...
Animal.prototype = {
makeItCat: function() {},
makeItDog: function(name) {
Dog.call(this, name);
},
};
I think the way you are doing it may work. I'm not sure about the factory implementation you did. I did something similar once that may help:
View working jsFiddle here
This is your code with slight edits:
var myApp = angular.module('myApp', []);
myApp.factory('AnimalFactory', AnimalFactory);
function AnimalFactory() {
// This is the main class
var Animal = function(age, fullName) {
this.age = age;
this.fullName = fullName;
this.eat = function() {
console.log('I am eating');
};
};
// Dog should inherit from Animal
var Dog = function(age, fullName) {
// Calling the animal constructor
Animal.call(this, age, fullName);
// Augmenting object adding a new method
this.bark = function() {
console.log('I am ' + this.fullName + ' and I bark Woof woof');
};
};
// Setting the Dog prototype to Animal
Dog.prototype = Object.create(Animal);
var Cat = function(age, fullName) {
// Calling the animal constructor
Animal.call(this, age, fullName);
// Augmenting object adding a new method
this.meow = function() {
console.log('I am ' + this.fullName + ' and I meow');
};
};
// Setting the Cat prototype to Animal
Cat.prototype = Object.create(Animal);
function createDog(age, fullName) {
return new Dog(age, fullName);
}
function createCat(age, fullName) {
return new Cat(age, fullName);
}
// Interface returned to use factory
return {
createDog: createDog,
createCat: createCat
};
}
function MyCtrl($scope, AnimalFactory) {
var dan = AnimalFactory.createDog(7, 'Dan');
dan.bark();
console.log(dan);
$scope.dan = dan;
}
I think the above code has a cleaner implementation of prototypes inheritance between your classes. Let me know what you think so we can improve it.
I'm kinda lost in getting the object extending to work. I have read dozens of sites related to this topic, but I'm still no wiser. It seems that everyone uses it's own approach to make this work, and so do I , I'm trying to find the best approach for extending/inheriting objects.
I am also aware that there are tons of frameworks/plugins out there to cover this functionality, but i'd just like to understand how it works in general. Not mentioning that most of these frameworks include lots of other stuff I may never use, hence I'm trying to make my own.
I was able to extend an object , everything seemed to be ok until I started adding methods to the target object. To understand the issue, please see the below example...
or just try this JSFiddle
The thing is, that after initializing the new instance of Rabbit object, I wasn't able to access Rabbit's method changeName.
And I don't understand why it's happening, i.e why it doesn't recognize the method.
[*] Please see my updated code below (also the JFiddle), everything now seems to be working ok.
Can anoyne please advise, if this is a good approach or what am I missing?
var Class = (function(NewClass){
if(NewClass.length != 0){
var extend = function(target, source, args) {
Object.getOwnPropertyNames(source).forEach(function(propName) {
if(propName !== "Extend")
{
Object.defineProperty(
target, propName,
Object.getOwnPropertyDescriptor(source, propName)
);
}
if (typeof source[propName] !== 'undefined'){
delete source[propName];
}
});
return target;
};
var inherit = function(source, args){
var baseClass = Object.getPrototypeOf(this);
baseClass.prototype = extend.call(this, baseClass, source, args);
};
if(NewClass.Extend){
var Class = function(){ //// New Class Constructor ////
if(typeof NewClass.Extend === 'function'){
NewClass.Extend.apply(this, arguments);
inherit.call(this, NewClass.Extend);
console.log(NewClass)
inherit.call(this, NewClass, arguments);
if(NewClass.Initialize){
NewClass.Initialize.call(this, arguments);
}
}
};
Class.prototype.constructor = Class;
return Class;
}
}
});
var Animal =(function(args){//// constructor ////
var self = this;
self.name = typeof args !== 'undefined' ? args.name : null;
self.bags = 0;
});
var Rabbit = new Class({
Extend: Animal ,
Initialize: function(){
console.log(this.name)
},
changeName: function(a){
console.log(this.name)
}
});
var LittleRabbit = new Rabbit({name: "LittleRabbit", type: "None"});
console.log(LittleRabbit instanceof Rabbit)
console.log(LittleRabbit)
LittleRabbit.changeName("alex");
your extend function work wrong, because Object.getPrototypeOf return prototype, so in more cases it object
var extend = function(source, args){
var baseClass = Object.getPrototypeOf(this);
source.apply(this, args);
//so here you just add property prototype to object, and this not same as set prototype to function.
baseClass.prototype = Object.create(source.prototype);
};
So you can fix this like in snippet below:
function Class(args) {
if (arguments.length != 0) {
var C = function() {
if (typeof args.Extend == 'function') {
args.Extend.apply(this, arguments)
}
if (args.Initialize) {
args.Initialize.call(this);
}
};
if (typeof args.Extend == 'function') {
C.prototype = Object.create(args.Extend.prototype);
}
Object.keys(args).filter(function(el) {
return ['Extend', 'Initialize'].indexOf(el) == -1
}).forEach(function(el) {
C.prototype[el] = args[el];
});
return C;
}
};
var Animal = (function(args) { //// constructor ////
var self = this;
self.name = typeof args !== 'undefined' ? args.name : null;
self.bags = 0;
});
var Rabbit = Class({
Extend: Animal,
Initialize: function() {
console.log(this.name);
},
changeName: function(a) {
this.name = a;
}
});
var LittleRabbit = new Rabbit({
name: "LittleRabbit",
type: "None"
});
console.log(LittleRabbit instanceof Rabbit);
console.log(LittleRabbit instanceof Animal);
console.log(LittleRabbit.name);
LittleRabbit.changeName('new little rabbit');
console.log(LittleRabbit.name);
I would suggest reading the MDN article detailing the JavaScript object model. It contains examples of "manually" subclassing:
function Employee() {
this.name = "";
this.dept = "general";
}
function Manager() {
Employee.call(this);
this.reports = [];
}
Manager.prototype = Object.create(Employee.prototype);
function WorkerBee() {
Employee.call(this);
this.projects = [];
}
WorkerBee.prototype = Object.create(Employee.prototype)
Translating your example to this style is simple:
function Animal(name) {
this.name = name;
this.bags = 0;
}
function Rabbit(name) {
Animal.call(this, name);
console.log(this.name);
}
Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.changeName = function(name) {
this.name = name;
};
Then you can easily run your example, modified a bit:
var LittleRabbit = new Rabbit("LittleRabbit");
console.log(LittleRabbit instanceof Rabbit)
console.log(LittleRabbit)
LittleRabbit.changeName("new name");
Once you understand this, I'd recommend not building your own class creation mechanism and just use ES6 classes:
class Animal {
constructor(name) {
this.name = name;
this.bags = 0;
}
}
class Rabbit extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
changeName(name) {
this.name = name;
}
}
You can see this example in the Babel REPL. Some browsers/js runtimes natively support ES6 classes already, but you can use Babel to translate your code to ES5 for environments that don't yet.
As an aside, there is actually more that needs to be done to subclass completely correctly. A more complete example (that may not work in all environments) is this:
function Animal() {}
function Rabbit() {
Animal.call(this);
}
Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;
Rabbit.__proto__ = Animal;
May ES6 class inheritance an option for you:
'use strict';
class Animal {
constructor( name ) {
this.name = name;
}
changeName( name ) {
this.name = name;
}
}
class Rabbit extends Animal {
constructor() {
super( 'rabbit' );
}
}
let littleRabbit = new Rabbit();
console.log( littleRabbit.name ); //log default name
littleRabbit.changeName( 'littleRabbit' ); //executing an method of animal class
console.log( littleRabbit.name ); //log changed name
You don't need the "overhead" for making OOP inheritance for old good javascript because there are "translators" out there which translate your es6 code to es5 code. For Example babel: https://babeljs.io/
I think it is worth to give it a try...
I have some simple OO code I've written that I'm playing with:
//define a constructor function
function person(name, sex) {
this.name = name;
this.sex = sex;
}
//now define some instance methods
person.prototype.returnName = function() {
alert(this.name);
}
person.prototype.returnSex = function() {
return this.sex;
}
person.prototype.talk = function(sentence) {
return this.name + ' says ' + sentence;
}
//another constructor
function worker(name, sex, job, skills) {
this.name = name;
this.sex = sex;
this.job = job;
this.skills = skills;
}
//now for some inheritance - inherit only the reusable methods in the person prototype
//Use a temporary constructor to stop any child overwriting the parent prototype
var f = function() {};
f.prototype = person.prototype;
worker.prototype = new f();
worker.prototype.constructor = worker;
var person = new person('james', 'male');
person.returnName();
var hrTeamMember = new worker('kate', 'female', 'human resources', 'talking');
hrTeamMember.returnName();
alert(hrTeamMember.talk('I like to take a lot'));
Now this is all well and good. But I'm confused. I want to include namespacing as part of my code writing practice. How can I namespace the above code. As it is now I have 2 functions defined in the global namespace.
The only way I can think to do this is to switch to object literal syntax. But then how do I implement the pseudo-classical style above with object literals.
You could for example do following:
var YourObject;
if (!YourObject) {
YourObject = {};
YourObject.Person = function(name, sex) {
// ...
}
YourObject.Person.prototype.returnName = function() {
// ...
}
// ...
}
You don't have to use object literals, at least, not exclusively.
Decide on the single global symbol you'd like to use.
Do all your declaration work in an anonymous function, and explicitly attach "public" methods as desired to your global object:
(function(global) {
// all that stuff
global.elduderino = {};
global.elduderino.person = person;
global.elduderino.worker = worker;
})(this);
I may not be completely understanding the nuances of your issue here, but the point I'm trying to make is that Javascript makes it possible for you to start with your symbols being "hidden" as locals in a function, but they can be selectively "exported" in various ways.