When working with JavaScript I've come across a situation where I'm not sure if what I'm trying to accomplish is possible:
Considering the following object:
var data = {};
Is it possible to modify "data" in a way that when extending it in the following way
data.entry_1 = {
'prop_1': 'set_1',
'prop_2': 'set_2'
};
a new property gets automatically attached to the new object, that is to say
data.entry_1 = {
'prop_1': 'set_1',
'prop_2': 'set_2',
id: 1 // automatically created property
};
Is it possible to accomplish the above without having to use "external" methods, e.g. no data.newEntry(object)?
var data = {
set entry_1 (val) {
for(var i in val) { this[i] = val[i] };
this["id"] = 1;
}
}
Supported in IE9+ and all other modern browsers.
Related
I'm tying to model a custom data type with JS and wondering if there's a more elegant way of doing this.
Ultimately I'd like the data type to essentially behave like an array but with an extended set of methods to meet my specific application and can be seen in the Links class below.
class Node {
constructor() {
this.links = new Links();
}
}
class Links {
constructor(node) {
this.data = [];
}
initialize() {
let initArray = [];
let maximumIncrement = hypotheticalNodeArray.length;
for (let i = 0; i < maximumIncrement ; i++) {
array[i] = 0;
}
this.data = array;
}
// More Methods Here
}
Essentially my issue is if I were to attempt to access the links data with [ node.links ] the entire object would be returned where as I'd love it just to return the data property as if the data had been accessed with [ node.links.data ].
I'm aware that just having to add that extra property isn't the end of the world, but I'd really like it if the object were to behave like an array except with an extended set of methods.
I'm not sure if this possible and I appreciate it's rather pedantic, however it would really clean the code I'm working on up.
You could define a getter that returns the internal array:
class Node {
constructor() {
// renamed “_links” because we want the getter named “links”
this._links = new Links();
}
get links () {
return this._links.data;
}
}
const node = new Node();
node.links.map( ... ) // node.links is node._links.data
I am trying to develop an offline HTML5 application that should work in most modern browsers (Chrome, Firefox, IE 9+, Safari, Opera). Since IndexedDB isn't supported by Safari (yet), and WebSQL is deprecated, I decided on using localStorage to store user-generated JavaScript objects and JSON.stringify()/JSON.parse() to put in or pull out the objects. However, I found out that JSON.stringify() does not handle methods. Here is an example object with a simple method:
var myObject = {};
myObject.foo = 'bar';
myObject.someFunction = function () {/*code in this function*/}
If I stringify this object (and later put it into localStorage), all that will be retained is myObject.foo, not myObject.someFunction().
//put object into localStorage
localStorage.setItem('myObject',JSON.stringify(myObject));
//pull it out of localStorage and set it to myObject
myObject = localStorage.getItem('myObject');
//undefined!
myObject.someFunction
I'm sure many of you probably already know of this limitation/feature/whatever you want to call it. The workaround that I've come up with is to create an object with the methods(myObject = new objectConstructor()), pull out the object properties from localStorage, and assign them to the new object I created. I feel that this is a roundabout approach, but I'm new to the JavaScript world, so this is how I solved it. So here is my grand question: I'd like the whole object (properties + methods) to be included in localStorage. How do I do this? If you can perhaps show me a better algorithm, or maybe another JSON method I don't know about, I'd greatly appreciate it.
Functions in javascript are more than just their code. They also have scope. Code can be stringified, but scope cannot.
JSON.stringify() will encode values that JSON supports. Objects with values that can be objects, arrays, strings, numbers and booleans. Anything else will be ignored or throw errors. Functions are not a supported entity in JSON. JSON handles pure data only, functions are not data, but behavior with more complex semantics.
That said you can change how JSON.stringify() works. The second argument is a replacer function. So you could force the behavior you want by forcing the strinigification of functions:
var obj = {
foo: function() {
return "I'm a function!";
}
};
var json = JSON.stringify(obj, function(key, value) {
if (typeof value === 'function') {
return value.toString();
} else {
return value;
}
});
console.log(json);
// {"foo":"function () { return \"I'm a function!\" }"}
But when you read that back in you would have to eval the function string and set the result back to the object, because JSON does not support functions.
All in all encoding functions in JSON can get pretty hairy. Are you sure you want to do this? There is probably a better way...
Perhaps you could instead save raw data, and pass that to a constructor from your JS loaded on the page. localStorage would only hold the data, but your code loaded onto the page would provide the methods to operate on that data.
// contrived example...
var MyClass = function(data) {
this.firstName = data.firstName;
this.lastName = data.lastName;
}
MyClass.prototype.getName() {
return this.firstName + ' ' + this.lastName;
}
localStorage.peopleData = [{
firstName: 'Bob',
lastName: 'McDudeFace'
}];
var peopleData = localStorage.peopleData;
var bob = new MyClass(peopleData[0]);
bob.getName() // 'Bob McDudeFace'
We don't need to save the getName() method to localStorage. We just need to feed that data into a constructor that will provide that method.
If you want to stringify your objects, but they have functions, you can use JSON.stringify() with the second parameter replacer. To prevent cyclic dependencies on objects you can use a var cache = [].
In our project we use lodash. We use the following function to generate logs. Can be used it to save objects to localStorage.
var stringifyObj = function(obj) {
var cache = []
return JSON.stringify(obj, function(key, value) {
if (
_.isString(value) ||
_.isNumber(value) ||
_.isBoolean(value)
) {
return value
} else if (_.isError(value)) {
return value.stack || ''
} else if (_.isPlainObject(value) || _.isArray(value)) {
if (cache.indexOf(value) !== -1) {
return
} else {
// cache each item
cache.push(value)
return value
}
}
})
}
// create a circular object
var circularObject = {}
circularObject.circularObject = circularObject
// stringify an object
$('body').text(
stringifyObj(
{
myBooblean: true,
myString: 'foo',
myNumber: 1,
myArray: [1, 2, 3],
myObject: {},
myCircularObject: circularObject,
myFunction: function () {}
}
)
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Does not fix functions as requested, but a way to store variables locally...
<html>
<head>
<title>Blank</title>
<script>
if(localStorage.g===undefined) localStorage.g={};
var g=JSON.parse(localStorage.g);
</script>
</head>
<body>
<input type=button onClick="localStorage.g=JSON.stringify(g, null, ' ')" value="Save">
<input type=button onClick="g=JSON.parse(localStorage.g)" value="Load">
</body>
</html>
Keep all variables in object g. Example:
g.arr=[1,2,3];
note some types, such as Date, you'll need to do something like:
g.date=new Date(g.date);
stores locally per page: different pages have different gs
Given a javascript object like this:
var myThing = {};
Object.defineProperty(myThing, 'gen', {
'get' : function() {
// access caller name here, so I can return cool/neat stuff
}
});
I want to be able to get children of myThing.gen, but know what is being asked for in the getter.
for example:
var coolThing = myThing.gen.oh.cool;
var neatThing = myThing.gen.oh.neat;
I want the "oh.cool" or "oh.neat" part in getter, so I can make decisions based on this, and return something specific to it. I am ok with solution not working in IE, or old browsers, as it is primarily for node.
The actual purpose of this is so that I can request myThing.gen.oh.neat and have the myThing.gen getter resolve to require('./oh/neat.js') and return it.
Since require cache's, this is an efficient way to dynamically load modular functionality, and have a tidy interface (rather than just dynamically building the require where needed) without having to know the structure ahead of time.
If there is no introspection-of-name function that can get this for me, I could just do something less elegant, like this:
myThing.gen = function(name){
return require('./' + name.replace('.', '/') + '.js');
}
and do this:
neatThing = myThing.gen('oh.neat');
I don't like this syntax as much, though. I looked at chai's dynamic expect(var).to.not.be.empty stuff, but couldn't figure out how to do it completely dynamically. Maybe there is not a way.
without actually solving the problem of dynamically discovering the caller, I can do this:
var myThing = {};
Object.defineProperty(myThing, 'gen', {
'get' : function() {
return {
'oh':{
'cool': require('./oh/cool.js'),
'neat': require('./oh/neat.js')
}
};
}
});
Is there a way to do this dynamically?
You can't see what the property gen will be used for in the future, so you would need to return an object with properties that react to what the object is used for when it actually happens:
var myThing = {};
Object.defineProperty(myThing, 'gen', {
'get' : function() {
var no = {};
Object.defineProperty(no, 'cool', {
get: function(){ alert('cool'); }
});
Object.defineProperty(no, 'neat', {
get: function(){ alert('neat'); }
});
return { oh: no };
}
});
Demo: http://jsfiddle.net/UjpGZ/1/
Check out this code. This is a very simple JavaScript object which is implemented using Module Pattern (and you can see the live example at this fiddle address)
var human = function() {
var _firstName = '';
var _lastName = ''
return {
get firstName() {
return _firstName;
}, get lastName() {
return _lastName;
}, set firstName(name) {
_firstName = name;
}, set lastName(name) {
_lastName = name;
}, get fullName() {
return _firstName + ' ' + _lastName;
}
}
}();
human.firstName = 'Saeed';
human.lastName = 'Neamati';
alert(human.fullName);
However, IE8 doesn't support JavaScript get and set keywords. You can both test it and see MDN.
What should I do to make this script compatible with IE8 too?
What should I do to make this script compatible with IE8 too?
Change it completely. For example, instead of using accessor properties, use a combination of normal properties and functions:
human.firstName = 'Saeed';
human.lastName = 'Neamati';
alert(human.getFullName());
Somebody else suggested using a DOM object in IE and adding the properties using Object.defineProperty(). While it may work, I'd highly recommend against this approach for several reasons, an example being that the code you write may not be compatible in all browsers:
var human = document.createElement('div');
Object.defineProperty(human, 'firstName', { ... });
Object.defineProperty(human, 'lastName', { ... });
Object.defineProperty(human, 'children', { value: 2 });
alert(human.children);
//-> "[object HTMLCollection]", not 2
This is true of at least Chrome. Either way it's safer and easier to write code that works across all the browsers you want to support. Any convenience you gain from being able to write code to take advantage of getters and setters has been lost on the extra code you wrote specifically targeting Internet Explorer 8.
This is, of course, in addition to the reduction in performance, the fact that you will not be able to use a for...in loop on the object and the potential confusion ensuing when you use a property you thought you defined but was pre-existing on the DOM object.
You cannot (as Andy answered)
The closest alternative would be
var human = function() {
var _firstName = '';
var _lastName = '';
return {
firstName: function() {
if (arguments.length === 1) {
_firstName = arguments[0];
}
else {
return _firstName;
}
},
lastName: function() {
if (arguments.length === 1) {
_lastName = arguments[0];
}
else {
return _lastName;
}
},
fullName: function() {
return _firstName + ' ' + _lastName;
}
};
}();
human.firstName('Saeed');
human.lastName('Neamati');
alert(human.fullName());
Demo at http://jsfiddle.net/gaby/WYjqB/2/
IE8 supports getters and setters on DOM nodes, so if you really want to have getters and setters, you can do this:
var objectForIe8 = $("<div></div>")[0];
Object.defineProperty(objectForIe8, "querySelector", {
get: function() {
return this.name;
},
set: function(val) {
this.name = val+", buddy";
}
});
// notice you can overwrite dom properties when you want to use that property name
objectForIe8.querySelector = "I'm not your guy";
alert(objectForIe8.querySelector);
Note this gives you a somewhat significant performance hit, so I wouldn't use this technique if you need to create thousands of objects like this. But if you're not worried about performance of this particular object, it'll tide you over. And if you couldn't care less about ie8 performance, and just want it to work, use this technique for ie8 only and you're golden : )
Check it on http://robertnyman.com/2009/05/28/getters-and-setters-with-javascript-code-samples-and-demos/
The future, and ECMAScript standardized way, of extending objects in
all sorts of ways is through Object.defineProperty. This is how
Internet Explorer chose to implement getters and setters, but it is
unfortunately so far only available in Internet Explorer 8, and not in
any other web browser. Also, IE 8 only supports it on DOM nodes, but
future versions are planned to support it on JavaScript objects as
well.
You can find the test cases on the same site at http://robertnyman.com/javascript/javascript-getters-setters.html#object-defineproperty
Object.defineProperty(document.body, "description", {
get : function () {
return this.desc;
},
set : function (val) {
this.desc = val;
}
});
document.body.description = "Content container";
Result:
document.body.description = "Content container"
I suppose this could apply to any dynamic language, but the one I'm using is JavaScript. We have a situation where we're writing a couple of controls in JavaScript that need to expose a Send() function which is then called by the page that hosts the JavaScript. We have an array of objects that have this Send function defined so we iterate through the collection and call Send() on each of the objects.
In an OO language, if you wanted to do something similar, you'd have an IControl interface that has a Send() function that must be implemented by each control and then you'd have a collection of IControl implementations that you'd iterate through and call the send method on.
My question is, with JavaScript being a dynamic language, is there any need to define an interface that the controls should inherit from, or is it good enough to just call the Send() function exposed on the controls?
Dynamic languages often encourage Duck Typing, in which methods of the object dictate how it should be used rather than an explicit contract (such as an interface).
This is the same for PHP; you don't really need interfaces. But they exist for architectural needs. In PHP, you can specify type hints for functions which can be useful.
Second, an interface is a contract. It's a formal contract that all objects from this interface have those functions. Better to ensure that your classes meet those requirements than to remember: "mm, this class has isEnabled() but the other one is checkIfEnabled()". Interfaces help you to standardise. Others working on the derived object don't have to check whether the name is isEnabled or checkIfEnabled (better to let the interpreter catch those problems).
Since you can call any method on any object in a dynamic language, I'm not sure how interfaces would come into play in any truly useful way. There are no contracts to enforce because everything is determined at invocation time - an object could even change whether it conforms to a "contract" through its life as methods are added and removed throughout runtime. The call will fail if the object doesn't fulfill a contract or it will fail if it doesn't implement a member - either case is the same for most practical purposes.
We saw a nice implementation in the page below, this is ours (short version of it)
var Interface = function (methods) {
var self = this;
self.methods = [];
for (var i = 0, len = methods.length; i < len; i++) {
self.methods.push(methods[i]);
}
this.implementedBy = function (object) {
for (var j = 0, methodsLen = self.methods.length; j < methodsLen; j++) {
var method = self.methods[j];
if (!object[method] || typeof object[method] !== 'function') {
return false;
}
}
return true;
}
};
//Call
var IWorkflow = new Interface(['start', 'getSteps', 'end']);
if (IWorkflow.implementedBy(currentWorkFlow)) {
currentWorkFlow.start(model);
}
The whole example is at:
http://www.javascriptbank.com/how-implement-interfaces-in-javascript.html
Another alternative to the interfaces is offered by bob.js:
1. Check if the interface is implemented:
var iFace = { say: function () { }, write: function () { } };
var obj1 = { say: function() { }, write: function () { }, read: function () { } };
var obj2 = { say: function () { }, read: function () { } };
console.log('1: ' + bob.obj.canExtractInterface(obj1, iFace));
console.log('2: ' + bob.obj.canExtractInterface(obj2, iFace));
// Output:
// 1: true
// 2: false
2. Extract interface from the object and still execute the functions properly:
var obj = {
msgCount: 0,
say: function (msg) { console.log(++this.msgCount + ': ' + msg); },
sum: function (a, b) { console.log(a + b); }
};
var iFace = { say: function () { } };
obj = bob.obj.extractInterface(obj, iFace);
obj.say('Hello!');
obj.say('How is your day?');
obj.say('Good bye!');
// Output:
// 1: Hello!
// 2: How is your day?
// 3: Good bye!