Arguments at instantiation of a namespace - javascript

I have a namespace generator that looks like this: (As a requirement, it also needs to have prototype objects extra and default).
function generateNamespace(staticMethods, prototypeMethods){
var namespace = function(){}; //create namespace
/**add staticMehods and prototypeMethods to namespace*/
extend(namespace, staticMethods); //extend function from NojeJS
extend(namespace.prototype, prototypeMethods);
//create extra and default objects.
namespace.prototype.default = namespace.prototype.default || {};
namespace.prototype.extra = namespace.prototype.extra || {};
return namespace;
}
The idea is to generate a namespace like this:
var protObj = {id: function(){
console.log("id");
}
}
var myNameSpace = generateNamespace({}, protObj);
When I instantiate:
var instanceOfmyNameSpace = new myNameSpace();
the result contains the functions id, and the objects default, and extra.
But if I run:
var instance2 = new myNameSpace({test: "foo"});
Then: assert.equal(instance2, instanceOfmyNameSpace) \\=true
so that means(?) that the given object just gets ignored.
What do I have to do to merge that object with the default one, for example? The perfect input/output would be:
//input
var instance2 = new myNameSpace({test: "foo"});
//output
console.log(instance2.default.test) \\-> "foo"
UPDATE, ANSWER
All I needed to do was create the namespace as a constructor, and then add staticMethods and prototypeMethods to it:
var extend = require('util')._extend;
function generateNamespace(staticMethods, prototypeMethods){
var namespace = function(defaults, extras){
this.default = extend({}, this.constructor.default, defaults);
this.extra = extend({}, this.constructor.extra, extras);
}
extend(namespace, staticMethods);
extend(namespace.prototype, prototypeMethods);
return namespace;
}

Your code doesn't show how you are copying prototypeMethods or even staticMethods to namespace which makes answering the question hard. I typically use a copy function to do operations like these. It would work for both the static and prototype methods.
Here is the mixin/copy function:
var mixin = function (obj, mixins) {
var keys = Object.keys(mixins);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
obj[key] = mixins[key];
}
};
And you would use it like:
function generateNamespace(staticMethods, prototypeMethods){
var namespace = function () {}; //create namespace
// copy staticMethods to namespace
mixin(namespace, staticMethods);
// copy prototype members
mixin(namespace.prototype, prototypeMethods);
//create extra and default objects.
namespace.prototype.default = namespace.prototype.default || {};
namespace.prototype.extra = namespace.prototype.extra || {};
return namespace;
}
Also note that with the line: namespace = function(){}; //create namespace you need to add a var as I did when declaring namespace otherwise the variable is made global and could exhibit unexpected behaviors.

Related

Javascript Arrays as an object field

I am running into a problem with using an array as a Javascript field.
var Object = function () {
var admins = [];
this.addAdmin = function(admin){
this.admins.push(admin)
}
}
Normally I would expect admin to be pushed into the array admins but instead I get a 'cannot read property 'push' of undefined'.
If I'm not mistaken when I initialized the Object with new Object(), admins = []; should initialize the array. Is this a limitation of Javascript?
Thank you in advance.
var array creates a local variable. It does not create a property on the object.
You need:
this.admins = [];
or
admins.push(admin) /* without this */
In your function admins is a local variable to the function. You need to declare admins as a property on the instance.
function Obj(){
this.admins = [];
}
Obj.prototype.addAdmin = function(admin){
this.admins.push(admin);
}
obj = new Obj();
obj.addAdmin('tester');
Also, because Object is the global base object, don't create functions or objects named Object.
I suspect you've gotten confused (which is easy :-) ) because you've seen code like this:
class Obj {
admins = [];
addAdmin(admin) {
this.admins.push(admin);
}
}
That uses the modern class and class fields syntax to puts an admins property on the object constructed via new Obj. (Note there's no var before admins = [];.) But in your code, you've used the older function-based syntax. Within your function, var admins = []; just creates a local variable, not a property.
I'd suggest that if you want to create constructor functions, using the new class syntax above is the simpler, more powerful way to do that. If you want to use the older syntax, though, other answers have shown how, but for completeness either make admins a property of the object:
let Obj = function() {
this.admins = []; // ***
this.addAdmin = function(admin){
this.admins.push(admin)
};
};
or perhaps with addAdmin on the prototype:
let Obj = function() {
this.admins = []; // ***
};
Obj.prototype.addAdmin = function(admin){
this.admins.push(admin)
};
or use the fact addAdmins closes over the call to Obj, and thus the local admins:
let Obj = function() {
const admins = [];
this.addAdmin = function(admin){
admins.push(admin) // <=== No `this.` here, you want to close over the
// `admins` local
};
};
I am assumming Object is a placeholder, because it is a reserved keyword.
What is happening is, your variable var admins = []; is created locally and can noot be accesed with the this. as a result when you set the value in this.admins.push(admin) the admins there is undefined. you should modify your function to read this way
var Obj = function () {
this.admins = [];
this.addAdmin = function (admin) {
this.admins.push(admin);
};
};
const object = new Obj();
object.addAdmin(1);
you should not omit the this keyword like this(no pun intended) if you plan to new the function. Stick to the code above.
var Obj = function () {
var admins = [];
this.addAdmin = function (admin) {
admins.push(admin);
};
};
const object = new Obj();
console.log(object)

Adding methods to an JavaScript object

I could create an object with some methods, and later add a property to it as follows:
var myObj = (function () {
var my = {};
my.method1=function(){}
my.method2=function(){}
my.method3=function(){}
return my;
}());
myObj.myProperty=123;
How could I create the object first and add a property, and then later add the methods afterwards?
myObj={};
myObj.myProperty=123;
//How do I add the above methods to myObj?
I guess there are two solutions:
Merge the objects:
var myObj = {...};
// ...
var objWithMethods = (function() { ... }());
Object.assign(myObj, objWithMethods);
(Object.assign is an ES6 methods. A polyfill can be found in the link, libraries often also provide a method with similar behavior).
Pass the object the methods should be assigned to as argument:
var myObj = {};
myObj = (function (obj) {
var my = obj || {};
my.method1=function(){}
my.method2=function(){}
my.method3=function(){}
return my;
}(myObj));
You can do an extend operation using an existing object
var myObj = {...}
var myAdditionalMethods = { someMethod : function(){ } }
//extend the object
for(var i in myAdditionalMethods)
if(!myObj.hasOwnProperty(i))
myObj[i] = myAdditionalMethods[i];
there are a lot of libraries that have this functionality built in, but that is how you would do it without one
Even prototype can add the functions to original object.
var myObj = function() {
this.myProperty = 123;
}
myObj.prototype.method1 = function method1() {
alert("method1")
}
myObj.prototype.method2 = function method2() {
alert("method2")
}
var newObj = new myObj();
newObj.method1();
newObj.method2();
console.log(newObj)

stringify javascript function

I am in the final stages of a game development and i have a bunch of objects like this;
roomBedroom = function () {
this.title = "Bedroom";
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
}
var bedroom = new roomBedroom();
What I want to do now is place all of my game objects into an array;
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
var jsonGame = JSON.stringify(savedGameObjects);
The plan is to then save the savedGameObjects array and then recall it when the user loads the game again.
If I replace savedGameObjects['bedroom'] = bedroom; with savedGameObjects['bed'] = 'slappy'; it works but not when I have the object.
I really need to save the objects in their current state. I'd rather not go through each object saving key pieces of information one by one.
This feels like a bit of a hack, but its the best I can come up with right now
Your serialization/deserializtion utility
This is going to attach obj.constructor.name to obj.__prototype before serialization. Upon deserializing, the prototype will be put back in place.
(function(global) {
function serialize(obj) {
obj.__prototype = obj.constructor.name;
return JSON.stringify(obj);
};
function deserialize(json) {
var obj = JSON.parse(json);
obj.__proto__ = global[obj.__prototype].prototype;
return obj;
}
global.serialize = serialize;
global.deserialize = deserialize;
})(window);
A sample "class"
(function(global) {
function Foo() {
this.a = "a";
this.b = "b";
}
Foo.prototype.hello = function() {
console.log("hello");
}
global.Foo = Foo;
})(window);
Let's try it out
var foo = new Foo();
var json = serialize(foo);
console.log(json);
var newFoo = deserialize(json);
console.log('a', newFoo.a); // a
console.log('b', newFoo.b); // b
newFoo.hello(); // hello
Watch out for some gotchas
If you use an expression to define your "class", you will have a nameless constructor
var Foo = function() {};
var foo = new Foo();
foo.constructor.name; // ""
As opposed to a named function
function Foo() {}
var foo = new Foo();
foo.constructor.name; // Foo
In order for serialize and deserialize to work, you will need to use named functions
Another gotcha
The deserialize method expects your "classes" to exist on the in the same namespace (window in this case). You could encapsulate your game object classes in another way, just make sure that you reconfigure the deserialize method so that it can find the prototypes as needed.
Making this better
Instead of attaching serialize to the global window, you could have serialize live on (e.g.) the GameObject.prototype then your individual classes could inherit from GameObject. Serializing an object would then be as simple as
var json = foo.serialize();
// {"a":"a","b":"b","__prototype":"Foo"}
You could then define deserialize as GameObject.deserialize and restoring foo would be
var foo = GameObject.deserialize(json);
An alternative solution
Instead of implementing a custom serializer and deserializer, you could make very clever use of the Factory Method Pattern.
This might be a little verbose, but it does give you individual control over how a game object should be deserialized/restored.
var savedData = // your normal JSON here
var player = Player.create(savedData.player);
var items = [];
for (var i=0, i<savedData.items.length; i++) {
items.push(Item.create(savedData.items[i]));
}
var map = Map.create(savedData.map);
This was a pretty interesting problem and I'm sure you're not the first to encounter it. I'm really curious to see what other people come up with.
If I run the following code in a browser there is no problem getting the JSON string of the bedroom object, not sure what the problem is.
Note that JSON is data and bedroom is an object, bedroom may have behaviour like turnOffLight() that JSON doesn't have.
roomBedroom = function () {
this.title = "Bedroom";
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
}
var bedroom = new roomBedroom();
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
//logs {"bedroom":{"title":"Bedroom","description":
// "I'm in abedroom","noun":"bed","entities":[]}}
console.log(JSON.stringify(savedGameObjects));
So if you want to re create object instances from JSON data then you can change your constructor:
roomBedroom = function (args) {
//following fails fast and loud, you could silently
//fail by setting args to {}
if(typeof args!=="object")
throw new Error("Have to create roomBedroom by passing an object");
//or do args={} to silently fail
this.title = args.title||"Bedroom";
this.description = args.description||"I'm in a bedroom";
this.noun = args.noun||"bed";
//if entities are objects with behavior
// you have to re create them here passing the JSON data
// as I've done with roomBedroom
this.entities = args.entities||new Array();
}
var jsonString='{"bedroom":{"title":"Bedroom",'+
'"description":"I\'m in a bedroom",'+
'"noun":"bed","entities":[]}}';
var bedroom = new roomBedroom({});
bedroom.entities.push({hi:"there"});
bedroom.title="Master Bedroom";
//serialize bedroom to a json string
var jsonString = JSON.stringify(bedroom);
//create a roomBedroom instance named br2 using
// the serialized string
var br2=new roomBedroom(JSON.parse(jsonString));
//compare if they are the same
console.log(JSON.stringify(bedroom)===JSON.stringify(br2));//true
I have an approach that might work for you. You can see it in action on JSFiddle.
The main point is to use the reviver parameter to JSON.parse to reconstruct your object when it's parsed.
I do this with a general-purpose reviver that can be configured for multiple different types, although here the only one used is the RoomBedroom constructor. This implementation assumes that you have simple copy constructors that create new objects using a reference to an existing one. (For other, more sophisticated possibilities, see an answer to another question I gave in February.) To make it easy to have a copy constructor, I have one more function that accepts a very simple constructor function and a set of default values and builds a copy constructor function for you.
var MultiReviver = function(types) {
return function(key, value) {
var type;
for (var i = 0; i < types.length; i++) {
type = types[i];
if (type.test(value)) {
return new type.constructor(value);
}
}
return value;
};
};
var makeCloningConstructor = (function() {
var clone = function(obj) {return JSON.parse(JSON.stringify(obj));};
var F = function() {};
return function(Constructor, defaults) {
var fn = function(obj) {
Constructor.call(this);
var self = this;
var config = obj || {};
Object.keys(defaults).forEach(function(key) {
self[key] = clone(defaults[key]);
});
Object.keys(config).forEach(function(key) {
self[key] = clone(config[key]);
});
};
F.prototype = Constructor.prototype;
fn.prototype = new F();
fn.constructor = Constructor;
return fn;
};
})();
// Note: capitalize constructor functions
var RoomBedroom = makeCloningConstructor(function RoomBedroom() {}, {
title: "Bedroom",
description: "I'm in a bedroom",
noun: "bed",
entities: [] // Note: use `[]` instead of `new Array()`.
});
RoomBedroom.prototype.toggleLight = function() {
this.lightOn = !this.lightOn;
};
RoomBedroom.prototype.checkLights = function() {
return "light is " + (this.lightOn ? "on" : "off");
};
var bedroom = new RoomBedroom();
bedroom.windowCount = 3; // add new property
bedroom.noun = "king-sized bed"; // adjust property
bedroom.toggleLight(); // create new propery, use prototype function
console.log(bedroom.checkLights());
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
var jsonGame = JSON.stringify(savedGameObjects);
var reviver = new MultiReviver([{
constructor: RoomBedroom,
test: function(obj) {
var toString = Object.prototype.toString, str = "[object String]",
arr = "[object Array]";
return toString.call(obj.title) == str &&
toString.call(obj.description) == str &&
toString.call(obj.noun) == str &&
toString.call(obj.entities) == arr;
}
}]);
var retrievedGameObjects = JSON.parse(jsonGame, reviver);
// data comes back intact
console.log(JSON.stringify(retrievedGameObjects, null, 4));
// constructor is as expected
console.log("Constructor: " + retrievedGameObjects.bedroom.constructor.name);
// prototype functions work
console.log(retrievedGameObjects.bedroom.checkLights());
I don't know if it's precisely what you were looking for, but I think it's at least an interesting approach.
the faster route
It is better — from an optimisation point of view — to do as Adeneo states, which is power each of your Game Objects by an exportable simple object i.e:
roomBedroom = function(){
this.data = {};
this.data.title = 'Bedroom'
/// and so on...
}
These can then be easily stored and re-imported just by JSON.Stringifying and overwriting the data property. For example, you could set-up the system that Maček mentions (+1) which is to give each of your game objects serialize and deserialize functions:
roomBedroom.prototype.serialize = function(){
return JSON.stringify( this.data );
};
roomBedroom.prototype.deserialize = function( jstr ){
this.data = JSON.parse(jstr);
};
the quicker way
However, you can make a simple addition to what you already have using the following:
First enhance your Game Objects with an objectName property. This is because constructor.name and function.name are unreliable and do strange things the further back in time you go, far better to use a string you have set in stone.
var roomBedroom = function ( title ) {
this.objectName = "roomBedroom";
this.title = title;
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
};
Then the additional code to help with storage:
var storage = {};
/// add your supported constructors to this list, there are more programmatic
/// ways to get at the constructor but it's better to be explicit.
storage.constructors = {
'roomBedroom' : roomBedroom
};
/// take an instance and convert to simple object
storage.to = function( obj ){
if ( obj.toStorage ) {
return obj.toStorage();
}
else {
var keep = {};
for ( var i in obj ) {
if ( obj.hasOwnProperty(i) && !obj[i].call ) {
keep[i] = obj[i];
}
}
return keep;
}
}
/// take simple object and convert to an instance of constructor
storage.from = function( obj ){
var n = obj && obj.objectName, c = storage.constructors[n];
if ( n && c ) {
if ( c.fromStorage ) {
return c.fromStorage( obj );
}
else {
var inst = new c();
for ( var i in obj ) {
if ( obj.hasOwnProperty(i) ) {
inst[i] = obj[i];
}
}
return inst;
}
}
else {
throw new Error('`' + n + '` undefined as storage constructor');
}
}
Once you have that you can use it like so:
var savedGameObjects = {};
savedGameObjects['bedroom'] = storage.to(new roomBedroom("bedroom"));
savedGameObjects['bedroom2'] = storage.to(new roomBedroom("bedroom2"));
var jsonGame = JSON.stringify(savedGameObjects);
console.log(jsonGame);
savedGameObjects = JSON.parse(jsonGame);
for( var i in savedGameObjects ) {
savedGameObjects[i] = storage.from(savedGameObjects[i]);
console.log(savedGameObjects[i]);
}
extras
You can also be specific about the way objects get stored/unstored by supplying toStorage and fromStorage methods on your constructed instances and constructors respectively. For example, you could use the following if you only wanted to store titles of roomBedrooms. Obviously this is an unrealistic use-case, you'd more often use this to avoid storing cached or computed sub-objects and properties.
roomBedroom.prototype.toStorage = function( obj ){
var ret = {};
ret.title = obj.title;
return ret;
};
roomBedroom.fromStorage = function( obj ){
var inst = new roomBedroom();
inst.title = obj.title;
return inst;
};
The above also means you can take advantage of improving your Game Object construction by providing parameters, rather than iterating over properties which can be slow and error-prone.
roomBedroom.fromStorage = function( obj ){
return new roomBedroom( obj.title );
};
Or even:
roomBedroom.fromStorage = function( obj ){
return new roomBedroom( obj ); // <-- the constructor processes the import.
};
fiddle
http://jsfiddle.net/XTUdp/
disclaimer
The above code relies on the existence of hasOwnProperty which is not present cross-browser yet, a polyfill should be used until it is... or, if you aren't doing anything complicated with prototype inheritance you don't need to worry and can remove it from the code.
you can declare a big variable like
var world = {};
and each small variable declare as
var bedroom = world.bed = (world.bed || new roomBedroom());
remember never change bedroom to another object, i think this will work fine, but looks too long winded

Can storing large variables in a closure cause problems?

I have a function in which I'm using closure as follows:
function myobject() {
var width=300,
height=400,
bigjsondata = { } // assume this is a big variable ~ 300k
function obj(htmlelement) {
// plot a graph in this htmlelement based on bigjsondata
}
return obj;
}
var plot1 = myobject();
plot1('#holder1');
var plot2 = myobject();
plot1('#holder2');
the variable bigjsondata contains a large dataset. The question is: does it allocate memory for bigjsondata whenever I create a variable var a = myobject() ?
Can it lead to memory problems if a lot of instances are created?
If so what is the best way to load it only once? (bigjsondata does not change)
Edit: At the end I would like myobject to be globally accessible.
not sure what you are trying to achieve, this should provide you with some private storage on different levels:
var privateStorage = function () {
// only 1 copy total
var bigJsonData = {...}
return function() {
// 1 copy for each instance
var instanceData = {...}
return function() {
// something to do many times per instance
return something_useful
}
}
}(); // returns function that privatelly knows about bigJsonData
var a = privateStorage(); // a is now 1st instance of the inner-most function
var b = privateStorage(); // a and b share the SAME bigJsonData object, but use different instanceData objects
a1 = a();
a2 = a();
Generally - yes, you code looks like creating a new instance for the bigjsondata each time you make a new myObject(); To get arround the issue, you can use anonymous initialization function like this:
myObject = null;
(function() {
var bigjsondata = { ... } // construct you large object here;
function myObjectInternal() {
// you can access `bigjsondata` from here.
// do not change `bigjsondata`, since it will now
// use the changed value in all new instances of `myObjectInternal`
}
myObjectInternal.prototype = {
data: function(_) {
// you can access `bigjsondata` from here too
}
};
myObject = myObjectInternal;
})();
This will create an anonymous function that is called immediately and only once (like a singleton). Inside the function, bigjsondata is a a closure to the myObjectInternal function, which is visible only in the anonymous one. That is why you define the outer global variable myObject, to latter make it point to the myObjectInternal function/object.
Define myObjectInternal as you would have myObject and you're good to go. So, in the following code:
var instance1 = new myObject();
var instance2 = new myObject();
it will use the same bigjsondata for instance1 and instance2
I would suggest going for an object oriented approach for this.
function obj (htmlelement)
{
this.htmlelement = $(htmlelement);
}
obj.prototype.htmlelement = null;
obj.prototype.bigjsondata = {};
obj.prototype.width = 300;
obj.prototype.height=400;
obj.prototype.plot = function ()
{
var htmlelement = this.htmlelement;
var bigjsondata = this.bigjsondata;
var width = this.width;
var height = this.height;
//plot graph here;
}
var plot1 = new obj('#holder1');
var plot2 = new obj('#holder2');
plot1.plot();
plot2.plot();
Here, the same bigjsondata will be shared among all objects of obj.

Extend descendent object in javascript

I've been learning more about javascript's prototypal inheritance. I know there is a somewhat fierce debate on whether to extend native objects and I'd like to side step that whole debate entirely in this question.
Is it possible to extend only descendent object in javascript?
To extend all objects I can do this:
Object.prototype.size = function(){
var length = 0;
for(var i in this){
if(this.hasOwnProperty(i)){
length++;
}
}
return this;
}
But the problem is that It extends all objects. What I'd like to do is have this:
var MyNameSpace = function(){
};
MyNameSpace.Object.prototype.size = function(){
var length = 0;
for(var i in this){
if(this.hasOwnProperty(i)){
length++;
}
}
return this;
}
That way I would only be extending the native objects in the scope of my global object.
any suggestions would be great thanks
Update:
In response to a few comments I'm adding more code to clarify what I'm trying to do.
I think i may have not phrased my question correctly, or maybe my thinking is incorrect, but what i'd like to be able to do is this:
var my = new MyNameSpace();
var my.name = {firstName : 'Hello', lastName : 'World'};
var nameCount = my.name.size(); // 2
the code you provided will allow me to get the size of each MyNameSpace object I create, but not the object literals that are properties of the MyNameSpace object
You could use "pseudo-classical" inheritance style to achieve it:
var MyNameSpace = function() {
this.v1 = null;
this.v2 = null;
}
MyNameSpace.prototype.size = function() {
var length = 0;
for(var i in this){
if(this.hasOwnProperty(i)){
length++;
}
}
return this;
}
var my = new MyNameSpace(); // create new object based on MyNameSpace
my.size(); // length would be 2
What you define on a function object's prototype would be inherited by all the function objects created via new operator.
Updated code, according to your added requirements,
var obj = {};
var MyNameSpace = function(props) {
for(var name in props) {
this[name] = props[name];
}
}
MyNameSpace.prototype.size = function() {
var length = 0;
for(var i in this){
if(this.hasOwnProperty(i)){
length++;
}
}
return this;
}
obj.name = new MyNameSpace({firstName : 'Hello', lastName : 'World'});
obj.name.size(); // length would be 2
In your code
var my = new MyNameSpace();
var my.name = {firstName : 'Hello', lastName : 'World'};
var nameCount = my.name.size(); // 2
my.name is obviously traversable from my, but the opposite is not true. That means that properties of my cannot be accessed from my.name, and my is nowhere to be found in the prototype chain of my.name. If you don't want to inherit directly from MyNameSpace you have to explicitly "hang on" whatever functions you would like to inherit.
You could do
my.name.size = my.size;
alternatively (without having to instantiate MyNameSpace):
my.name.size = MyNameSpace.prototype.size;
if you have only few functions to "inherit". Or you could define an inherit function in MyNameSpace as follows:
MyNameSpace.prototype.addToNameSpace = function(obj) {
obj.size = this.size;
// obj.propertyI = this.propertyI, etc.
}
Note that I don't use for..in here as that would add the addToNameSpace function as well.
Hope this helps

Categories

Resources