I am new to "object-oriented" JavaScript. Currently, I have an object that I need to pass across pages. My object is defined as follows:
function MyObject() { this.init(); }
MyObject.prototype = {
property1: "",
property2: "",
init: function () {
this.property1 = "First";
this.property2 = "Second";
},
test: function() {
alert("Executing test!");
}
}
On Page 1 of my application, I am creating an instance of MyObject. I am then serializing the object and storing it in local storage. I am doing this as shown here:
var mo = new MyObject();
mo.test(); // This works
window.localStorage.setItem("myObject", JSON.stringify(mo));
Now, on Page 2, I need get that object and work with it. To retrieve it, I am using the following:
var mo = window.localStorage.getItem("myObject");
mo = JSON.parse(mo);
alert(mo.property1); // This shows "First" as expected.
mo.test(); // This does not work. In fact, I get a "TypeError" that says "undefined method" in the consol window.
Based on the outputs, it looks like when I serialized the object, somehow the functions get dropped. I can still see the properties. But I can't interact with any of my functions. What am I doing wrong?
JSON doesn't serialize functions.
Take a look at the second paragraph here.
If you need to preserve such values, you can transform values as they are serialized, or prior to deserialization, to enable JSON to represent additional data types.
In other words, if you really want to JSONify the functions, you can convert them to strings before serializing:
mo.init = ''+mo.init;
mo.test = ''+mo.test;
And after deserializing, convert them back to functions.
mo.init = eval(mo.init);
mo.test = eval(mo.test);
However, there should be no reason to do that. Instead, you can have your MyObject constructor accept a simple object (as would result from parsing the JSON string) and copy the object's properties to itself.
Functions can not be serialized into a JSON object.
So I suggest you create a separate object (or property within the object) for the actual properties and just serialize this part.
Afterwards you can instantiate your object with all its functions and reapply all properties to regain access to your working object.
Following your example, this may look like this:
function MyObject() { this.init(); }
MyObject.prototype = {
data: {
property1: "",
property2: ""
},
init: function () {
this.property1 = "First";
this.property2 = "Second";
},
test: function() {
alert("Executing test!");
},
save: function( id ) {
window.localStorage.setItem( id, JSON.stringify(this.data));
},
load: function( id ) {
this.data = JSON.parse( window.getItem( id ) );
}
}
To avoid changing the structure, I prefer to use Object.assign method on object retrieval. This method merge second parameter object in the first one. To get object methods, we just need an empty new object which is used as the target parameter.
var mo = window.localStorage.getItem("myObject");
// this object has properties only
mo = JSON.parse(mo);
// this object will have properties and functions
var completeObject = Object.assign(new MyObject(), mo);
Note that the first parameter of Object.assign is modified AND returned by the function.
it looks like when I serialized the object, somehow the functions get dropped... What am I doing wrong?
Yes, functions will get dropped when using JSON.stringify() and JSON.parse(), and there is nothing wrong in your code.
To retain functions during serialization and deserialization, I've made an npm module named esserializer to solve this problem -- the JavaScript class instance values would be saved during serialization on Page 1, in plain JSON format, together with its class name information:
var ESSerializer = require('esserializer');
function MyObject() { this.init(); }
MyObject.prototype = {
property1: "",
property2: "",
init: function () {
this.property1 = "First";
this.property2 = "Second";
},
test: function() {
alert("Executing test!");
}
}
MyObject.prototype.constructor=MyObject; // This line of code is necessary, as the prototype of MyObject has been overridden above.
var mo = new MyObject();
mo.test(); // This works
window.localStorage.setItem("myObject", ESSerializer.serialize(mo));
Later on, during the deserialization stage on Page 2, esserializer can recursively deserialize object instance, with all types/functions information retained:
var mo = window.localStorage.getItem("myObject");
mo = ESSerializer.deserialize(mo, [MyObject]);
alert(mo.property1); // This shows "First" as expected.
mo.test(); // This works too.
That's because JSON.stringify() doesn't serialize functions i think.
You're right, functions get dropped. This page might help:
http://www.json.org/js.html
"Values that do not have a representation in JSON (such as functions and undefined) are excluded."
Related
I believe that solution to my issue is relatively easy. I just don't see it.
I have an object:
function MyObject(){
this.attr = "anything";
}
MyObject.prototype.doSomething = function(){
// logic
}
I create the object using new MyObject(). I use it and when I want to quit what I do I simply store it into database (mongodb). In mongo it is stored in this way:
{ "attr" : "anything" }
When I load the object from database I only have the plain object literal without any logic. The methods are missing. I can see why ;) but I don't know how to add the logic to the object literal again...
Question
How can I decorate the retrieved object with it's original logic again? So that it look like this again:
{
"attr" : "anything",
"doSomething": doSomething()
}
How to do it simply?
Is there any other approach to this (except for storing the methods and all prototype hierarchy with it)?
Make a load function to load all the properties of the object you get back into your own object.
You can access your objects property names as an associative array index.
so myobj.attr is the same as myobj['attr'].
This helps with dynamically inserting data into your object whilst keeping full control of the data(my personal favorite) :-)
You can add some extra checks to prevent surplus data or do some extra things whatever you want. For example modifying timestamps.
function MyObject(data){
if(typeof data !== 'undefined') {
this.load(data);
}
else {
this.attr = "anything";
}
}
MyObject.prototype.doSomething = function(){
// logic
}
MyObject.prototype.load = function(data) {
for(var key in data) {
if(data.hasOwnProperty(key)) {
this[key] = data[key];
// Just sample validation check. wahtever you want.
if(key == 'timestamp') {
if(this[key] < new Date().getTime()-4000) {
this[key] = new Date().getTime();
}
}
}
}
}
You can use Object.create to create an object from the prototype without calling the constructor, and then use Object.assign to assign the properties from the object that you retrieved, to the newly created object:
var fullObject = Object.assign( Object.create( MyObject.prototype ), retrievedObject );
Example:
function MyObject(){
this.attr = "anything";
}
MyObject.prototype.doSomething = function(){
document.body.innerHTML = this.attr;
}
// Plain object retrieved from database
var retrievedObject = {
"attr" : "foobar"
};
// Object with proper prototype and properties
var myObject = Object.assign( Object.create( MyObject.prototype ), retrievedObject );
myObject.doSomething();
An extension to #paulpro's answer which I had to modify a little this piece of code Object.create( MyObject.prototype ). As I was using an inherited object this way it didn't instantiate private members properly. I simply replaced Object.create( MyObject.prototype ) with new MyObject(). That's it.
UPDATE 5/3/2016
The safest way is to use lodash's assing.
var objectWithLogic = _.assign(new MyObject(), data);
The JSON.stringify behavior can be altered by overriding .toJSON method:
var obj = {toJSON: function() {return [1,2,3];}};
var x = JSON.stringify(dd);
console.log(x); // "[1,2,3]"
JSON.parse(x); // [1,2,3]
I'd like to pass the javascript pseudo-class instances (objects inheriting from other objects). However it doesn't seem possible to add any function call in the data:
function Pseudoclass(x) {
this.x = x;
//More operations
}
If you return function, .stringify fails. Not talking about the fact that it doesn't seem very possible to pass the class properties:
//JSON.stringify(inst) will be undefined
Pseudoclass.prototype.toJSON = function() {
//If converted to string, the function loses variables from this scope
return function() {return new Pseudoclass();};
}
If you return string, it's encoded as string:
//JSON.stringify(inst) returns "\"Pseudoclass.fromJSON(6)\"" if x was 6
Pseudoclass.prototype.toJSON = function() {
return "Pseudoclass.fromJSON("+this.x+")";
}
So does anybody has any hacks in mind? I'd use this to pass prepared class instances to Worker - the only option there is figuring this JSON question or a custom format.
Don't forget that the constructor call may not be the only way. All I need is that the object inherits from my predefined object!
var x = {
name: "japan",
age: 20
}
x.prototype.mad = function() {
alert("USA");
};
x.mad();
The above code does not work.
object literals cannot be extended? or x.mad() not the right way to call.
You can't do it this way. To be able to define object methods and properties using it's prototype you have to define your object type as a constructor function and then create an instance of it with new operator.
function MyObj() {}
MyObj.prototype.foo = function() {
// ...
}
var myObj = new MyObj();
myObj.foo()
If you want to keep using object literals, the only way to attach behaviour to your object is to create its property as anonymous function like so
var myObj = {
foo: function() {
// ...
}
}
myObj.foo();
The latter way is the quickest. The first is the way to share behaviour between mutiple objects of the same type, because they will share the same prototype. The latter way creates an instance of a function foo for every object you create.
Drop the prototype.
Replace:
x.prototype.mad = function() {
With:
x.mad = function() {
This simply adds a mad property to the object.
You dont have .prototype available on anything but function object. So your following code itself fails with error TypeError: Cannot set property 'mad' of undefined
x.prototype.mad = function() {
alert("USA");
};
If you need to use prototype and extension, you need to use function object and new keyword. If you just want to add property to your object. Assign it directly on the object like following.
x.mad = function() {
alert("USA");
}
x.constructor.prototype.mad = function() {
alert("USA");
};
x.mad();
This also works, by the way:
Object.prototype.mad = function() {
alert("USA");
}
var x = {
name: "japan",
age: 20
}
x.mad();
But then, the mad function will be part of any object what so ever,
literals, "class" instances, and also arrays (they have typeof === "object").
So - you'll probably never want to use it this way. I think it's worth mentioning so I added this answer.
For better code structure, I want to use a javascript object holding all properties instead of using multiple vars:
// 1. WAY
// This returns an error, as _inp cannot be accessed by input_value
// Uncaught TypeError: Cannot read property 'value' of undefined
var ref = {
_inp: input.target,
input_value: _inp.value,
....
};
// 2. WAY
// When using this, it works
var ref = {
_inp: input.target,
input_value: input.target.value,
....
};
// 3. WAY
// This obviously works, too.
var
_inp = input.target,
input_value = _inp.value,
My Question is, why does 3. Way works and 1.Way doesnt?
In example 1, _inp will be a property of an object. It isn't a variable. You can only access it from a reference to the object (and it won't be a property of the object until the object exists, which will be after the object literal has been evaluated, see also Self-references in object literal declarations).
Because _inp will only be filled in with the input.target value after passing through the entire var ref = { ... }; statement. This means that when you try to use it, it doesn't exist yet.
The 1st way don't work because you refers to "_inp" which is not an existing var. and the ref object is not fully created (that's why input_value: this._inp.value won't work either)
To create objects and assigning values to its properties, you can use a function (I keep most of your code):
var ref = {
_inp: input.target,
input_value: null,
init: function()
{
this.input_value = this._inp.value;
}
};
ref.init();
console.log(ref.input_value); // will contains the same as input.target.value
but usually, people create objects with all property with default values, and pass an argument to their init function:
var ref = {
_inp: null,
input_value: null,
init: function(input)
{
if (input)
{
this._inp = input.target;
this.input_value = input.target.value;
}
}
};
var input = {target:{value:"foo"}};
ref.init(input);
In JavaScript, I may begin writing a 'library' or collection of functionality using a top level object like this:
window.Lib = (function()
{
return {
// Define Lib here.
//
};
})();
I may also add some functions within Lib which serve to create objects related to it:
window.Lib = (function()
{
return {
ObjectA: function()
{
var _a = 5;
return {
getA: function(){ return _a; }
};
},
ObjectB: function()
{
var _b = 2;
var _c = 1;
return {
getB: function(){ return _b; }
};
}
};
})();
Which would be used like so:
var thing = Lib.ObjectA();
var thing2 = Lib.ObjectA();
var thing3 = Lib.ObjectB();
And I can use the methods within each of those created above to get the values of _a defined within ObjectA() or _b defined within ObjectB():
alert(thing.getA()); // 5
alert(thing3.getB()); // 2
What I want to achieve is this:
Say I want to access the property _c (defined within ObjectB()) but only within the scope of Lib. How could I go about that? By this I mean, I want to make the property readable within any function that I define within the object returned by Lib(), but I don't want to expose those values outside of that.
Code example:
window.Lib = (function()
{
return {
ObjectA: function(){ ... },
ObjectB: function(){ ... },
assess: function(obj)
{
// Somehow get _c here.
alert( obj.getInternalC() );
}
};
})();
Which would work like so:
var thing = Lib.ObjectB();
alert( thing.getInternalC() ) // error | null | no method named .getInternalC()
Lib.assess(thing); // 1
Hope this makes sense.
So you want per-instance protected properties? That is, properties on the instances created by ObjectA, ObjectB, etc., but which are only accessible to the code within your library, and not to code outside it?
You cannot currently do that properly in JavaScript, but you'll be able to in the next version using private name objects. (See "Almost doing it" below for something similar you can do now in ES5, though.)
It's easy to create data that's shared by all code within Lib, but not per-instance properties, like so:
window.Lib = (function()
{
var sharedData;
// ...
})();
All of the functions defined within there (your ObjectA, etc.) will have access to that one sharedData variable, which is completely inaccessible from outside. But it's not per-instance, each object created by ObjectA, ObjectB, etc. doesn't get its own copy.
Almost doing it
If your code will be running in an environment with ES5 (so, any modern browser, where "modern" does not include IE8 or earlier), you can have obscured but not actually private properties, via Object.defineProperty. This is similar to how private name objects will work in ES.next, but not genuinely private:
Live Example | Source
window.Lib = (function() {
// Get a random name for our "c" property
var c = "__c" + Math.round(Math.random() * 1000000);
// Return our library functions
return {
ObjectA: function() {
// Create an object with a couple of public proprties:
var obj = {
pub1: "I'm a public property",
pub2: "So am I"
};
// Add our obscured "c" property to it, make sure it's
// non-enumerable (doesn't show up in for-in loops)
Object.defineProperty(obj, c, {
enumerable: false, // false is actually the default value, just emphasizing
writable: true,
value: "I'm an obscured property"
});
// Return it
return obj;
},
ObjectB: function(){ /* ... */ },
assess: function(obj) {
// Here, we access the property using the `c` variable, which
// contains the property name. In JavaScript, you can access
// properties either using dotted notation and a literal
// (`foo.propName`), or using bracketed notation and a string
// (`foo["propName"]`). Here we're using bracketed notation,
// and our `c` string, which has the actual property name.
display( obj[c] );
},
alter: function(obj, value) {
// Similarly, we can change the value with code that has
// access to the `c` variable
obj[c] = value;
}
};
})();
And use it like this:
// Create our object
var o = Lib.ObjectA();
// Play with it
display("pub1: " + o.pub1); // displays "pub1: I'm a public property"
display("c: " + o.c); // displays "c: undefined" since `o` has no property called `c`
Lib.assess(o); // displays "I'm an obscured property"
// Note that our obscured property doesn't show up in for-in loops or Object.keys:
var propName, propNames = [];
for (propName in o) {
propNames.push(propName);
}
display("propNames: " + propNames.join(","));
display("Object.keys: " + Object.keys(o).join(","));
// Our Lib code can modify the property
Lib.alter(o, "Updated obscured property");
Lib.assess(o);
The object returned by Lib.ObjectA has a property whose name will change every time Lib is loaded, and which is not enumerable (doesn't show up in for-in loops). The only way to get at it is to know it's name (which, again, changes every time Lib is created — e.g., every page load). The code within Lib knows what the property name is, because it's in the c variable which is shared by all of the Lib code. Since you can access properties using bracketed notation and a string, we can use instance[c] to access the property.
You see how these are pretty well obscured. Code outside of Lib don't see the obscured property when enumerating the property in the object, and they don't know the semi-random name we assigned it, so can't find the property. Of course, you could find it via inspection using a debugger, but debuggers can do lots of things.
And in fact, this is how private properties will work in ES.next, except that c won't be a string, it'll be a private name object.
Well, you would "just" need to declare those variables within the Context of Lib
window.Lib = (function()
{
var _c = 42;
return {
};
});
Notice that I removed the automatic invocation of that pseudo constructor function. That means, you would need to create multiple calls to Lib() for multiple instances, each would have its own unique set of values.
var inst1 = Lib(),
inst2 = Lib();
If you only want to have shared access from all child-context's (functions), you can just use the same pattern you already do (only with moving the var declarations to the parent context like shown above).