I'm new to JavaScript and ran into an issue creating an array of custom objects.
I'm trying to apply the MVC concept. So in my Model I try to create a 'Node' object like this:
interFace.createNode = function(name) {
debug('createNode');
this.name = name;
this.childNodes = [];
...
return this
};
In my Controller I have a function where I create new nodes dynamically and add them to the childNodes array of the parent node like so:
parent_node.childNodes.push(Model().createNode("Node " + getNodeCount());
Push always returns 1, a subsequent call to length always 0. If I just push an integer value and not a node object the code works fine and the array grows as expected.
Basically what I want is to create Node-objects dynamically (like new Node() in Java) and add those objects to an array of Nodes. But I suspect that my understanding of objects in JS is flawed by my experience in OO programming.
Any help on how to resolve this issue is greatly appreciated.
Thanks in advance.
I believe you want something like this:
interFace.createNode = function(name) {
debug('createNode');
var node = {};
node.name = name;
node.childNodes = [];
...
return node;
};
or, something like this:
interFace.createNode = function(name) {
debug('createNode');
return new interFace.Node(name);
};
interFace.Node = function(name){
this.name = name;
this.childNodes = [];
...
}
There are several ways of creating objects in JS use of OOP principles such as prototypes and literals, etc.
With regard to your question and the task you are going to achieve, following is a literal wrapper where you can populate a node object in static nature.Since you are invoking this method dynamically to create nodes.
var nodeHanlder = {
create : function(name){
return {
name : name,
childNodes : []
}
}
};
var node = nodeHanlder.create("Node 1");
this will return object...
to access properties,
//node.name;
//node.childNodes;
Related
I have two objects inst1, inst2 which are both instances of the same class. If I use
inst2 = JSON.parse(JSON.stringify(inst1));
now if I change values of properties of inst2, values in inst1 do not change. That is great.
But sadly methods of inst2 have disappeared. So if I do
inst2.method1();
I get the error
"inst2.method1 is not a function"
Is there some way I can copy the values in an instance without destroying methods?
(obviously I could laboriously copy each value. I am trying to avoid that because I am lazy.)
I have tried to follow typescript - cloning object but I cannot make it work-
Ok, I have played a little since the provided answers are not 100% clear.
If you want to have a shallow copy and copy the methods too, you can use Object.create.
Again: If your object is simple enough, Object.create will be sufficient for you
const originalPerson = new Person("John");
originalPerson.address = new Address("Paris", "France");
const newPerson = Object.create(originalPerson);
/// this will be true
const isInstanceOf = newPerson instanceof Person;
//this will change the property of the new person ONLY
newPerson.name = "Peter";
//methods will work
newPerson.someMethod();
//methods will work even on nested objects instances
newPerson.address.anotherMethod();
// BUT if we change the city on any of the instances - will change the address.city of both persons since we have done a shallow copy
newPerson.address.city = "Berlin";
I have created typescript playground (just remove the types) to show it works and the drawback with its usage - link to the playground
Another approach is the class itself to have a clone method and to be responsible for its own cloning logic. An example follows, along with a link to another playground
class Address {
constructor(city, country) {
this.city = city;
this.country = country;
}
clone() {
// no special logic, BUT if the address eveolves this is the place to change the clone behvaiour
return Object.create(this);
}
getAddressDetails() {
return `City: ${this.city} country ${this.country}`;
}
}
class Person {
constructor(name, address) {
this.name = name;
this.address = address;
}
clone() {
const newInstance = Object.create(this);
//clone all other class instances
newInstance.address = this.address.clone();
return newInstance;
}
getPersonDetails() {
//calling internally address.getAddressDetails() ensures that the inner object methods are also cloned
return `This is ${this.name}, I live in ${this.address.getAddressDetails()}`
}
}
const originalAddress = new Address("Paris", "France");
const originalPerson = new Person("John", originalAddress);
const clonedPerson = originalPerson.clone();
clonedPerson.name = "Peter";
clonedPerson.address.city = "Berlin";
clonedPerson.address.country = "Germany";
// Log to console
console.log(`Original person: ${originalPerson.getPersonDetails()}`)
console.log(`Cloned person: ${clonedPerson.getPersonDetails()}`)
You should use structured cloning, see this answer:
How do I correctly clone a JavaScript object?
The reason that your current code isn't working is because you are parsing a stringified json object. Json stringify will remove all of the methods of an object and only stringify the objects values.
I came back to this at a convenient point and made quite a bit of progress by combining some of the above answers. The general purpose cloner was getting quite ugly (see below) and still not working (for arrays of class-objects) when I realised that it would be impossible to write a general purpose cloner.
I use the term class-object to mean an object defined by a class.
If a class-object contains a variable which itself is type class-object, call it subObj, then the general purpose cloner cannot know whether 1) it should copy subObj or 2) it should create a new instance of subObj and copy into the sub-properties. The answer depends on the meaning in the class.
In the first case above subObj. is just a pointer to another instance of subObj.
Therefore I strongly agree with the second part of Svetoslav Petkov's answer that the "class itself [should] have a clone method and be responsible for its own cloning logic.".
For what it's worth this is as far as I got with a general purpose cloner (in TypeScript). It is adapted from the other answers and creates new instances of class-objects liberally:
public clone(): any {
var cloneObj = new (this.constructor as any)() as any;
for (var attribut in this) {
// attribut is a string which will take the values of the names of the propertirs in 'this'
// And for example, if aNumber is a property of 'this' then
// this['aNumber'] is the same as this.aNumber
if (typeof this[attribut] === "object") {
let thisAttr = this[attribut] as any;
let cloneAttr = cloneObj[attribut] as any;
if (this[attribut] instanceof Array) {
for (let i in thisAttr) {
cloneAttr[i] = thisAttr[i]; // **** will not work on arrays of objects!!
}
continue; // to next attrib in this
}
if (this[attribut] instanceof Date) {
cloneAttr.setTime(thisAttr.getTime());
continue; // to next attrib in this
}
try {
cloneObj[attribut] = thisAttr.clone();
//cloneObj[attribut] = this.clone(); // with this, (from https://stackoverflow.com/questions/28150967/typescript-cloning-object) stack just gets bigger until overflow
}
catch (err) {
alert("Error: Object " + attribut + " does not have clone method." +
"\nOr " + err.message);
}
} else {
cloneObj[attribut] = this[attribut];
}
}
return cloneObj;
}
I am creating a map to which I am adding a custom class object,
var member_map = {};
var memberAction = new MemberActions(members.data[i].id, members.data[i].name);
member_map[memberAction.id] = memberAction;
Now when I try to get back a MemberActions object from the map using an ID, i get an Object rather than a MemberActions object.
var memberAction = member_map[fetch_id];
How do I cast the object?
EDIT:
My class defn:
class MemberActions {
constructor(id, name) {
this.id = id;
this.name = name;
}
}
JS doesn't have such a thing as object casting.
When you say you've got an Object this may mean when you try to console.log(memberAction) you get told that it's an Object - however it doesn't mean that isn't a MemberActions object.
To check the name of it, try:
console.log(memberAction.constructor.name)
memberAction.constructor should also contain the constructor you've defined.
I have read that private state of instance objects is generally not advised, and I would appreciate the help in pointing out the flaws/shortcomings of the following implementation.
Any advise/critique is greatly appreciated.
var namespace = {};
namespace.parent = {
parent_method1 : function () {},
parent_method2 : function () {}
};
namespace.child = function (properties) {
var private="secret";
this.prototype = {
create : function () {
this.unique = 'base';
this.unique += this.properties;
return this.unique;
},
get_private: function () {
console.log(private);
},
set_private: function (val) {
private = val;
}
};
var proto = Object.create(namespace.parent);
var instance = Object.create(proto);
for (var property in this.prototype) {
if (this.prototype.hasOwnProperty(property)) {
proto[property] = this.prototype[property];
}
}
instance.properties = properties;
return instance;
};
var a = namespace.child("a");
console.log(a.create());
a.get_private();
a.set_private("new_a_secret");
a.get_private();
var b = namespace.child("b");
console.log(b.create());
b.get_private();
a.get_private();
I would appreciate the help in pointing out the flaws/shortcomings of the following implementation.
I don't see anything wrong with your implementation of the var private, provided that it does what you expect it to do.
However, the big flaw of the code is: I don't understand it. What is your implementation supposed to do? It does not follow any of the standard patterns. This might be a shortcoming of the pseudo-methods and clear up when implementing a real world model; however as it stands it's quite confusing. Some documentation comments would help as well.
Specifically:
What does namespace.child do? It looks like a factory function, but I'm not sure what "childs" it does produce.
Also, for some reason it does set the namespace.prototype property to a new object on every invocation, which is then mixed into the instance object proto object. However, it leaves the internals (get_private, set_private) of the last created instance in global scope.
Are a and b supposed to be classes? Why do they have .create() methods, that initialise the class (instance?) - and should rather be called .init() if at all? Why do they return the .unique property value?
Why doesn't the child method initialize those factored objects right away?
What is that .properties field that holds a string value?
Why are you using two-level inheritance for those instance objects? Object.create(namespace.parent) is understandable, it inherits from a static object that is shared. But why is then Object.create(proto) used, copying some properties (create, get_private, set_private) on the proto and some properties (properties, unique) on the instance? Why distinguish between them?
I am new to javascript. As far as I can tell there are 5 ways to make an object (really a struct I guess). I was wondering what the best way is. Thanks.
var makeOption = function(name, dataType){
var option = {
name: name,
dataType: dataType
};
return option;
};
var makeOption2 = function(name, dataType){
this.name = name;
this.dataType = dataType;
};
function makeOption3(name, dataType){
this.name = name;
this.dataType = dataType;
};
var makeOption4 = function makeOption4Name(name, dataType){
this.name = name;
this.dataType = dataType;
};
var v1A = makeOption("hannah", "int");
var v1B = new makeOption("hannah", "int");
//var v2A = makeOption2("hannah", "int"); <- undefined.
var v2B = new makeOption2("hannah", "int");
// var v3A = makeOption3("hannah", "int"); <- undefined.
var v3B = new makeOption3("hannah", "int");
// var v4A = makeOption4("hannah" ,"int"); <- undefined.
var v4B = new makeOption4("hannah" ,"int");
This is what is displayed in the firebug DOM Tab:
I'd go with #3.
#1 does not allow the use of prototype.
#2 is anonymous, and anonymous functions don't help in debugging because you don't see what function is causing problems if it is (the variable name the function is stored is not part of the function, whereas the function's name is).
#4 is confusing - two possibilities to access one function.
Whole chapters of books on JavaScript best practices have been written on this subject. That said: If you aren't concerned about inheritance and aren't going to be creating numerous copies of an object with methods, i.e., you are just creating a "struct", then object literal notation, your first example, is the way to go. In that approach, you are using object literal notation, which is lightweight and fast. It doesn't mess with the object prototype or require the use of the new operator.
Start adding methods to your object, however, and the answer changes to "it depends."
By the way, you left out a couple of ways to create an object:
var o = {};
o.name = "hannah";
o.dataType = "int";
and, not recommended:
var o = new Object();
o.name = "hannah";
o.dataType = "int";
The first one is preferable from design standpoint - based on your name.
makeOption implies that it creates and retuens an object.
All your other solutions do NOT actually return an object and require "new" call. They may have similar/identical technical results when used as pure data structures, but only the first one works as a "object maker", as its name implies.
If you want to use #2/3 (#4 makes no sense - why would you clone the function twice), then you need to name it something else - optionPrototype may be.
I personally use makeoption3 I have tried them all and found that makeoption3 is the cleanest and simplest if you are writing multiple objects. Also it has less code than the others keeping your file size down.
function makeOption3(name, dataType){
this.name = name;
this.dataType = dataType;
};
If you don't need inheritance capabilities, just go with #1 (as you're essentially just using it to build and return an object literal). Otherwise I'd go with #3, as it allows for protoype methods and is also a named function rather than an anonymous one.
Taking this from a Post John Resig made about javascript "Class" instatiation, he points out...
// Very fast
function User(){}
User.prototype = { /* Lots of properties ... */ };
// Very slow
function User(){
return { /* Lots of properties */ };
}
which is what we're talking about he posted this snippet of code...
// makeClass - By John Resig (MIT Licensed)
function makeClass(){
return function(args){
if ( this instanceof arguments.callee ) {
if ( typeof this.init == "function" )
this.init.apply( this, args.callee ? args : arguments );
} else
return new arguments.callee( arguments );
};
}
And then using it, leveraging the speed of using the prototype chain..
var User = makeClass();
User.prototype.init = function(first, last){
this.name = first + " " + last;
};
var user = User("John", "Resig");
user.name
// => "John Resig"
This also takes care of the usage of new it allows the use of the keyword, but doesn't require it.
Link to Original Post
Why don't you want to use object literals? It seems like you're really asking about objects and inheritance..?
If you have an array of product objects created from JSON, how would you add a prototype method to the product objects so that they all point to the same method? How would you train JavaScript to recognize all product objects in an array are instances of the same class without recreating them?
If I pull down a JSON array of Products for example, and want each product in the array to have a prototype method, how would I add the single prototype method to each copy of Product?
I first thought to have a Product constructor that takes product JSON data as a parameter and returns a new Product with prototypes, etc. which would replace the data send from the server. I would think this would be impractical because you are recreating the objects. We just want to add functions common to all objects.
Is it possible to $.extend an object's prototype properties to the JSON object so that each JSON object would refer to exactly the same functions (not a copy of)?
For example:
var Products = [];
Products[0] = {};
Products[0].ID = 7;
Products[0].prototype.GetID = function() { return this.ID; };
Products[1].ID = 8;
Products[1].prototype = Products[0].prototype; // ??
I know that looks bad, but what if you JQuery $.extend the methods to each Product object prototype: create an object loaded with prototypes then $.extend that object over the existing Product objects? How would you code that? What are the better possibilities?
For one, you're not modifying the Products[0].prototype, you're modifying Object.prototype, which will put that function on the prototype of all objects, as well as making it enumerable in every for loop that touches an Object.
Also, that isn't the proper way to modify a prototype, and ({}).prototype.something will throw a TypeError as .prototype isn't defined. You want to set it with ({}).__proto__.something.
If you want it to be a certain instance you need to create that instance, otherwise it will be an instance of Object.
You probably want something like:
var Product = function(ID) {
if (!this instanceof Product)
return new Product(ID);
this.ID = ID;
return this;
};
Product.prototype.GetID = function() {
return this.ID;
};
Then, fill the array by calling new Product(7) or whatever the ID is.
First, one problem is that prototype methods are associated when the object is created, so assigning to an object's prototype will not work:
var Products = [];
Products[0] = {};
Products[0].prototype.foo = function () { return 'hello' } // ***
Products[0].foo(); // call to undefined function
(*** Actually, the code fails here, because prototype is undefined.)
So in order to attach objects, you'll need to assign actual functions to the object:
Products[0].foo = function () { return 'hello'; };
You can create a helper function to do so:
var attachFoo = (function () { // Create a new variable scope, so foo and
// bar is not part of the global namespace
function foo() { return this.name; }
function bar() { return 'hello'; }
return function (obj) {
obj.foo = foo;
obj.bar = bar;
return obj; // This line is actually optional,
// as the function /modifies/ the current
// object rather than creating a new one
};
}());
attachFoo(Products[0]);
attachFoo(Products[1]);
// - OR -
Products.forEach(attachFoo);
By doing it this way, your obj.foos and obj.bars will all be referencing the same foo() and bar().
So, if I'm getting this all correctly, this is a more complete example of KOGI's idea:
// Create a person class
function Person( firstName, lastName ) {
var aPerson = {
firstName: firstName,
lastName: lastName
}
// Adds methods to an object to make it of type "person"
aPerson = addPersonMethods( aPerson );
return aPerson;
}
function addPersonMethods( obj ) {
obj.nameFirstLast = personNameFirstLast;
obj.nameLastFirst = personNameLastFirst;
return obj;
}
function personNameFirstLast() {
return this.firstName + ' ' + this.lastName;
}
function personNameLastFirst() {
return this.lastName + ', ' + this.firstName;
}
So, with this structure, you are defining the methods to be added in the addPersonMethods function. This way, the methods of an object are defined in a single place and you can then do something like this:
// Given a variable "json" with the person json data
var personWithNoMethods = JSON.parse( json ); // Use whatever parser you want
var person = addPersonMethods( personWithNoMethods );
You could do this...
function product( )
{
this.getId = product_getId;
// -- create a new product object
}
function product_getId( )
{
return this.id;
}
This way, although you will have several instances of the product class, they all point to the instance of the function.
Could try doing something like this (without jquery)
Basic prototypal object:
function Product(id){
this.id = id;
}
Product.prototype.getId() = function(){return this.id;};
var Products = [];
Products[0] = new Product(7);
Products[1] = new Product(8);
Products[2] = new Product(9);
alert(Products[2].getId());
IMO I found a pretty good answer right here:
Return String from Cross-domain AJAX Request
...I could serialize my
data in the service as a JSON string
and then further wrap that in JSONP
format? I guess when it comes over to
the client it would give the JSON
string to the callback function.
That's not a bad idea. I guess I would
also have the option of sending a
non-JSON string which might allow me
to just use eval in the callback
function to create new Person objects.
I'm thinking this would be a more
efficient solution in both speed and
memory usage client-side.