Searching for appropriate answer proved difficult because of the existence of many other problems related to my keywords, so I'll ask this here.
As we know, functions in JavaScript are objects and they have their own properties and methods (more properly, function instances, inherited from Function.prototype).
I was considering adding custom properties for one function (method), let's skip the "why?" part and go straight to the code:
var something = {
myMethod: function () {
if (something.myMethod.someProperty === undefined) {
something.myMethod.someProperty = "test";
}
console.log(something.myMethod);
}
}
When inspected with Firebug's DOM explorer, the property is defined as expected. However, as I don't consider myself a JavaScript expert, I have the following questions:
Can this method be considered "proper" and standards compliant? It works in Firefox but there are many things working as expected in web browsers and aren't by any means standards.
Is this kind of altering objects by adding new properties to them a good practice?
First of all, it's important to realise that standard function properties (arguments, name, caller & length) cannot be overwritten. So, forget about adding a property with that name.
Adding your own custom properties to a function can be done in different ways that should work in every browser.
Adding your own custom properties to a function
Way 1 : adding properties while running the function :
var doSomething = function() {
doSomething.name = 'Tom';
doSomething.name2 = 'John';
return 'Beep';
};
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
Way 1 (alternate syntax) :
function doSomething() {
doSomething.name = 'Tom';
doSomething.name2 = 'John';
return 'Beep';
};
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
Way 1 (second alternate syntax) :
var doSomething = function f() {
f.name = 'Tom';
f.name2 = 'John';
return 'Beep';
};
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
A problem with this strategy is that you need to run your function at least once to assign the properties. For many functions, that's obviously not what you want. So let's consider the other options.
Way 2 : adding properties after defining the function :
function doSomething() {
return 'Beep';
};
doSomething.name = 'Tom';
doSomething.name2 = 'John';
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
Now, you don't need to run your function first before you're able to access your properties. However, a disadvantage is that your properties feel disconnected from your function.
Way 3 : wrap your function in anonymous function :
var doSomething = (function(args) {
var f = function() {
return 'Beep';
};
for (var i in args) {
f[i] = args[i];
}
return f;
}({
'name': 'Tom',
'name2': 'John'
}));
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
Wrapping your function in an anonymous function, you can collect your attributes into an object and use a loop to add those attributes one-by-one within the anonymous function. That way, your attributes feel more connected to your function. This technique is also very useful for when your attributes need to be copied from an existing object. A disadvantage, however, is that you can only add multiple attributes at the same time when you define your function. Also, it doesn't exactly result in DRY code if adding properties to a function is something you want to do often.
Way 4 : add an 'extend' function to your function, that adds the properties of an object to itself one by one :
var doSomething = function() {
return 'Beep';
};
doSomething.extend = function(args) {
for (var i in args) {
this[i] = args[i];
}
return this;
}
doSomething.extend({
'name': 'Tom',
'name2': 'John'
});
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
This way, you can extend multiple properties and/or copy properties from another project at any time. Again, however, your code isn't DRY if this is something you do more often.
Way 5 : Make a generic 'extend' function :
var extend = function(obj, args) {
if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
for (var i in args) {
obj[i] = args[i];
}
}
return obj;
}
var doSomething = extend(function() {
return 'Beep';
}, {
'name': 'Tom',
'name2': 'John'
});
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
A genetic extend function allows for a more DRY approach, allowing you to add the object or any project to any other object.
Way 6 : Create an extendableFunction object and use it to attach an extend function to a function :
var extendableFunction = (function() {
var extend = function(args) {
if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
for (i in args) {
this[i] = args[i];
}
}
return this;
};
var ef = function(v, obj) {
v.extend = extend;
return v.extend(obj);
};
ef.create = function(v, args) {
return new this(v, args);
};
return ef;
})();
var doSomething = extendableFunction.create(function() {
return 'Beep';
}, {
'name': 'Tom',
'name2': 'John'
});
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
Rather than using a generic 'extend' function, this technique allows you to generate functions that have an 'extend' method attached to it.
Way 7 : Add an 'extend' function to the Function prototype :
Function.prototype.extend = function(args) {
if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
for (i in args) {
this[i] = args[i];
}
}
return this;
};
var doSomething = function() {
return 'Beep';
}.extend({
name : 'Tom',
name2 : 'John'
});
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
A great advantage to this technique is that it makes adding new properties to a function very easy and DRY as well as completely OO. Also, it's pretty memory friendly. A downside, however, is that it's not very future proof. In case future browsers ever add a native 'extend' function to the Function prototype, this that could break your code.
Way 8 : Run a function recursively once and then return it :
var doSomething = (function f(arg1) {
if(f.name2 === undefined) {
f.name = 'Tom';
f.name2 = 'John';
f.extend = function(args) {
if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
for (i in args) {
this[i] = args[i];
}
}
return this;
};
return f;
} else {
return 'Beep';
}
})();
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
Run a function once and have it test whether one of its properties is set. If not set, set the properties and return itself. If set, execute the function. If you include an 'extend' function as one of the properties, you can later execute that to add new properties.
Adding your own custom properties to an object
In spite of all these options, I would nevertheless recommend against adding properties to a function. It's much better to add properties to objects!
Personally, I prefer the singleton classes with the following syntax.
var keyValueStore = (function() {
return {
'data' : {},
'get' : function(key) { return keyValueStore.data[key]; },
'set' : function(key, value) { keyValueStore.data[key] = value; },
'delete' : function(key) { delete keyValueStore.data[key]; },
'getLength' : function() {
var l = 0;
for (p in keyValueStore.data) l++;
return l;
}
}
})();
console.log('keyValueStore.get("name") : ' + keyValueStore.get("name"));
console.log('keyValueStore.get("name2") : ' + keyValueStore.get("name2"));
try { console.log('keyValueStore.data["name"] : ' + keyValueStore.data["name"]) } catch { console.error('keyValueStore.data not defined') }
try { console.log('keyValueStore.data["name2"] : ' + keyValueStore.data["name2"]) } catch { console.error('keyValueStore.data not defined') }
keyValueStore.set("name", "Tom");
keyValueStore.set("name2", "John");
console.log('keyValueStore.get("name") : ' + keyValueStore.get("name"));
console.log('keyValueStore.get("name2") : ' + keyValueStore.get("name2"));
try { console.log('keyValueStore.data["name"] : ' + keyValueStore.data["name"]) } catch { console.error('keyValueStore.data not defined') }
try { console.log('keyValueStore.data["name2"] : ' + keyValueStore.data["name2"]) } catch { console.error('keyValueStore.data not defined') }
An advantage to this syntax is that it allows for both public and private variables. For example, this is how you make the 'data' variable private :
var keyValueStore = (function() {
var data = {};
return {
'get' : function(key) { return data[key]; },
'set' : function(key, value) { data[key] = value; },
'delete' : function(key) { delete data[key]; },
'getLength' : function() {
var l = 0;
for (p in data) l++;
return l;
}
}
})();
console.log('keyValueStore.get("name") : ' + keyValueStore.get("name"));
console.log('keyValueStore.get("name2") : ' + keyValueStore.get("name2"));
try { console.log('keyValueStore.data["name"] : ' + keyValueStore.data["name"]) } catch { console.error('keyValueStore.data not defined') }
try { console.log('keyValueStore.data["name2"] : ' + keyValueStore.data["name2"]) } catch { console.error('keyValueStore.data not defined') }
keyValueStore.set("name", "Tom");
keyValueStore.set("name2", "John");
console.log('keyValueStore.get("name") : ' + keyValueStore.get("name"));
console.log('keyValueStore.get("name2") : ' + keyValueStore.get("name2"));
try { console.log('keyValueStore.data["name"] : ' + keyValueStore.data["name"]) } catch { console.error('keyValueStore.data not defined') }
try { console.log('keyValueStore.data["name2"] : ' + keyValueStore.data["name2"]) } catch { console.error('keyValueStore.data not defined') }
But you want multiple datastore instances, you say? No problem!
var keyValueStore = (function() {
var countKVS = 0;
return (function kvs() {
return {
'data' : {},
'create' : function() { return new kvs(); },
'countInstances' : function() { return countKVS; },
'get' : function(key) { return this.data[key]; },
'set' : function(key, value) { this.data[key] = value; },
'delete' : function(key) { delete this.data[key]; },
'count' : function() {
var l = 0;
for (p in this.data) l++;
return l;
}
}
})();
})();
kvs = keyValueStore.create(), console.warn('-- KEY VALUE STORE CREATED --');
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs.countKVS);
kvs.set("Tom", "Baker"), console.warn('-- ADDING KEY VALUE PAIR "Tom, "Baker" --');
console.log('kvs.get("Tom") : ' + kvs.get("Tom"));
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs.countKVS);
kvs2 = keyValueStore.create(), console.warn('-- SECOND KEY VALUE STORE CREATED --');
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs2.countKVS);
console.log('kvs2.count() : ' + kvs2.count());
console.log('kvs2.data : ' + JSON.stringify(kvs2.data));
console.log('kvs2.countKVS : ' + kvs2.countKVS);
kvs.set("Daisy", "Hostess"), kvs2.set("Daisy", "Hostess"), console.warn('-- ADDING KEY VALUE PAIR "Daisy", "Hostess" TO BOTH STORES --');
console.log('kvs.get("Tom") : ' + kvs.get("Tom"));
console.log('kvs.get("Daisy") : ' + kvs.get("Daisy"));
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs2.countKVS);
console.log('kvs2.get("Tom") : ' + kvs2.get("Tom"));
console.log('kvs2.get("Daisy") : ' + kvs2.get("Daisy"));
console.log('kvs2.count() : ' + kvs2.count());
console.log('kvs2.data : ' + JSON.stringify(kvs2.data));
console.log('kvs2.countKVS : ' + kvs2.countKVS);
kvs.delete('Daisy'), console.warn('-- DELETING KEY "Daisy" FROM FIRST STORE --');
console.log('kvs.get("Tom") : ' + kvs.get("Tom"));
console.log('kvs.get("Daisy") : ' + kvs.get("Daisy"));
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs2.countKVS);
console.log('kvs2.get("Tom") : ' + kvs2.get("Tom"));
console.log('kvs2.get("Daisy") : ' + kvs2.get("Daisy"));
console.log('kvs2.count() : ' + kvs2.count());
console.log('kvs2.data : ' + JSON.stringify(kvs2.data));
console.log('kvs2.countKVS : ' + kvs2.countKVS);
Finally, you can seperate the instance and singleton properties and use a prototype for the instance's public methods. That results in the following syntax :
var keyValueStore = (function() {
var countKVS = 0; // Singleton private properties
var kvs = function() {
countKVS++; // Increment private properties
this.data = {}; // Instance public properties
};
kvs.prototype = { // Instance public properties
'get' : function(key) { return this.data[key]; },
'set' : function(key, value) { this.data[key] = value; },
'delete' : function(key) { delete this.data[key]; },
'count' : function() {
var l = 0;
for (p in this.data) l++;
return l;
}
};
return { // Singleton public properties
'create' : function() { return new kvs(); },
'countInstances' : function() { return countKVS; }
};
})();
kvs = keyValueStore.create(), console.warn('-- KEY VALUE STORE CREATED --');
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs.countKVS);
kvs.set("Tom", "Baker"), console.warn('-- ADDING KEY VALUE PAIR "Tom, "Baker" --');
console.log('kvs.get("Tom") : ' + kvs.get("Tom"));
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs.countKVS);
kvs2 = keyValueStore.create(), console.warn('-- SECOND KEY VALUE STORE CREATED --');
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs2.countKVS);
console.log('kvs2.count() : ' + kvs2.count());
console.log('kvs2.data : ' + JSON.stringify(kvs2.data));
console.log('kvs2.countKVS : ' + kvs2.countKVS);
kvs.set("Daisy", "Hostess"), kvs2.set("Daisy", "Hostess"), console.warn('-- ADDING KEY VALUE PAIR "Daisy", "Hostess" TO BOTH STORES --');
console.log('kvs.get("Tom") : ' + kvs.get("Tom"));
console.log('kvs.get("Daisy") : ' + kvs.get("Daisy"));
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs2.countKVS);
console.log('kvs2.get("Tom") : ' + kvs2.get("Tom"));
console.log('kvs2.get("Daisy") : ' + kvs2.get("Daisy"));
console.log('kvs2.count() : ' + kvs2.count());
console.log('kvs2.data : ' + JSON.stringify(kvs2.data));
console.log('kvs2.countKVS : ' + kvs2.countKVS);
kvs.delete('Daisy'), console.warn('-- DELETING KEY "Daisy" FROM FIRST STORE --');
console.log('kvs.get("Tom") : ' + kvs.get("Tom"));
console.log('kvs.get("Daisy") : ' + kvs.get("Daisy"));
console.log('keyValueStore.countInstances() : ' + keyValueStore.countInstances());
console.log('kvs.count() : ' + kvs.count());
console.log('kvs.data : ' + JSON.stringify(kvs.data));
console.log('kvs.countKVS : ' + kvs2.countKVS);
console.log('kvs2.get("Tom") : ' + kvs2.get("Tom"));
console.log('kvs2.get("Daisy") : ' + kvs2.get("Daisy"));
console.log('kvs2.count() : ' + kvs2.count());
console.log('kvs2.data : ' + JSON.stringify(kvs2.data));
console.log('kvs2.countKVS : ' + kvs2.countKVS);
With this syntax, you can have :
multiple instances of an object
private variables
class variables
It's a little bit difficult to give a very meaningful answer to your question, because you've sort of said "Here is my solution, is it OK?" without explaining what problem you are trying to solve (you even said explicitly that you are not going to explain the "why"). Your code looks to be valid JavaScript that will run, but it also looks like a less than optimal way of doing things.
If you explain what you actually want to achieve you may get some good suggestions on better ways to structure your code. Still, I'll give you some kind of answer:
Can this method be considered "proper" and standards compliant? It works in Firefox but there are many things working as expected in web browsers and aren't by any means standards.
Functions are objects (as you've said), and thus it is possible to add properties to them. This isn't really a standards issue as such in that it is a core part of JavaScript that all browsers support.
Is this kind of altering objects by adding new properties to them a good practice?
It's your object, you can add whatever properties you like. The whole point of objects is that they have properties that you can manipulate. I can't really envisage a way of using objects that doesn't involve altering them, including adding, deleting and updating properties and methods.
Having said that, to me it doesn't really make sense to add properties to the myMethod function, it would be more usual to add other properties to your something object (your myMethod function would, if called correctly, have access to the other properties of something via the this keyword).
If you are using a function as a constructor it typically makes sense to add methods to the associated prototype and add (non-method) properties to each instance, but you can do either or both the other way when appropriate. (Noting that a "method" is essentially just a property that happens to reference a function.)
The specific code you have shown doesn't add properties, it tests whether the someProperty property already exists and if so assigns it a new value.
You might benefit from reading some articles such as these at MDN:
Working with Objects
Introduction to Object-Oriented JavaScript
"necromancing" here, but I think every great question needs simple answers:
Yes and Yes*
By attaching the properties to the function you clean up the scope, improve readability and add logical cohesion. An added benefit is that you document the relationship between the function and the variables. I think that's a superior design, much better than adding variables on the scope
Created some fun examples here and here.
HERE
AND HERE
* I think it's worth noting that you probably won't see this very often. most developers probably don't realize it's possible. Some people are crazy about every drop of performance... "JavaScript engines optimize based on the 'shape' of an object'..." blah blah blah...
ut I think you can follow the rule you have for Objects and you'll do fine.
Attaching properties to functions is a beautiful (arguably sluggish/hack-ish) way of overloading the () operator, which in turn is usually used to implement functors: Object types that have one really important job, and all its other functionality (if there is any) is just a bunch of helpers. You could also interpret these functors as, basically, a "stateful" function where the state is public (most inline functions for example, have private state, that is state from the local scope).
This JSFiddle demonstrates how we can use a function with custom properties for a translator function with additional utilities:
/**
* Creates a new translator function with some utility methods attached to it.
*/
var createTranslator = function(dict) {
var translator = function(word) {
return dict[word];
};
translator.isWordDefined = function(word) {
return dict.hasOwnProperty(word);
};
// Add more utilities to translator here...
return translator;
};
// create dictionary
var en2deDictionary = {
'banana': 'Banane',
'apple': 'Apfel'
};
// simple use case:
var translator = createTranslator(en2deDictionary);
var pre = $('<pre>');
$("body").append(pre);
pre.append(translator('banana') + '\n');
pre.append(translator('apple') + '\n');
pre.append(translator.isWordDefined('w00t') + '\n');
As you can see, this is perfect for a translator whose sole purpose is to translate. Of course there are many more examples of theses types of objects, but they are by far not as common as types with diversified functionality, such as the classic User, Animal Car etc. types. To those sort of types, you only want to add custom properties in very few cases. Usually, you want to define those as more complete classes, and have their public properties reachable through this and it's prototype.
I realize I'm years late to this, but thought I'd add this example--requirejs sets a property called "amd" on the define() function, which is quite handy as the UMD pattern uses it to detect that the define() function that's in scope is in fact an AMD define() function.
RequireJS source: http://requirejs.org/docs/release/2.1.9/comments/require.js
UMD pattern showing this usage: https://github.com/umdjs/umd/blob/master/templates/amdWeb.js
If you just want to add custom properties to a function then you only need to add those properties to Function.prototype. For example:
Function.prototype.SomeNewProperty = function () {//Do something awesome here...}
It's perfectly acceptable to add properties or methods to a function object. It's done quite often. The jQuery/$ object is an example of this. It's a function with quite a few methods attached.
When properties are added to a constructor they are called 'static' properties and can be invoked without an an instance of the class. e.g. Object.create.
I don't have enough rep to write a comment so I will say here: It generally considered bad practice to extend the prototypes of built in objects, especially if your code has to play with other people's code. It can have unpredictable consequences that are hard to to track.
I agree that this is a difficult question that could have multiple answers, so I prefer to make an different example:
Let's suppose to have an JavaScript Array, populated by a generator:
var arr = [...new Array(10).keys()];
that is
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Now we want to map this to a new array - same length, applying some function, so we could use the native map function property:
arr = arr.map((value,index) => ++value)
We have just done a value=value+1 and return, so now the array will look like
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Ok, now supposed to have a JavaScript Object like
var obj=new Object()
that was defined like the previous array (for some crazy reason):
arr.forEach((value,index) => obj[value]=value)
i.e.
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}
At this point we cannot apply the same map method since it's not defined for an Object so we have to define it as a new prototype of an Object:
Object.defineProperty(Object.prototype, 'mapObject', {
value: function(f, ctx) {
ctx = ctx || this;
var self = this, result = {};
Object.keys(self).forEach(function(k) {
result[k] = f.call(ctx, self[k], k, self);
});
return result;
}
});
At this point we could do as for the array before:
obj=obj.mapObject((value,key) => ++value )
so that we have:
{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}
You can see that we have updated the values only:
[...Object.keys(obj)]
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
and we can turn back then into the output array:
[...Object.keys(obj).map(k=>obj[k])]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Here it is at work:
// Array.map
var arr = [...new Array(10).keys()];
console.log("array", arr)
arr = arr.map((value, index) => ++value)
console.log("mapped array", arr)
// new property
Object.defineProperty(Object.prototype, 'mapObject', {
value: function(f, ctx) {
ctx = ctx || this;
var self = this,
result = {};
Object.keys(self).forEach(function(k) {
result[k] = f.call(ctx, self[k], k, self);
});
return result;
}
});
// Object.mapObject
var obj = new Object()
arr = [...new Array(10).keys()];
arr.forEach((value, index) => obj[value] = value)
console.log("object", obj)
obj = obj.mapObject((value, key) => ++value)
console.log("mapped object", obj)
console.log("object keys", [...Object.keys(obj)])
console.log("object values", [...Object.keys(obj).map(k => obj[k])])
Possible addition to John Slegers great answer
Isnt it possible that after John Slegers:
Way 2 : adding properties after defining the function
Adding a Way 2.5
function doSomething() {
doSomething.prop = "Bundy";
doSomething.doSomethingElse = function() {
alert("Why Hello There! ;)");
};
let num = 3;
while(num > 0) {
alert(num);
num--;
}
}
sayHi();
sayHi.doSomethingElse();
alert(doSomething.prop);
var ref = doSomething;
ref();
ref.doSomethingElse();
alert(ref.prop);
Putting in both a "variable" property and a function property for completeness sake, straight in the function declaration. Thus avoiding it to be "disconnected". Left the inner default workings of the function (a simple loop) to show that it still works. No?
test = (function() {
var a = function() {
console.log("test is ok");
};
a.prop = "property is ok";
a.method = function(x, y) {
return x + y;
}
return a
})()
test();
console.log(test.prop);
console.log(test.method(3, 4));
Alternatively you have to use getters and setters
var person = {
firstName: 'Jimmy',
lastName: 'Smith',
get fullName() {
return this.firstName + ' ' + this.lastName;
},
set fullName(name) {
var words = name.toString().split(' ');
this.firstName = words[0] || '';
this.lastName = words[1] || '';
}
}
console.log(person.firstName);
console.log(person.lastName);
console.log(person.fullName);
person.fullName = "Tom Jones";
console.log(person.fullName);
Related
This is what i do to reference global variables in a cycle.
_.forEach(myTableName.detailsObjects, function (o, key) {
if (window[o] && window[o].serverSideProcessing == true) {
window[o].prepareData(data, false);
window[o].setDML(data, false);
But what if they are local variables.
o.prepareData and o.setDml dont work.
Thanks
Not sure what is the exact problem you have, but it works with local variables too.
Here are two versions of code, first is pure js using Array.forEach and second uses underscore's _.forEach.
myTableName = {
"detailsObjects": [
{"id":1, "prepareData":true},
{"id":2, "prepareData":false}
]
}
myTableName.detailsObjects.forEach(function(o, key) {
alert('Result: ' + o.id + ': ' + o.prepareData + ' key: ' + key);
})
_.forEach(myTableName.detailsObjects, function(o, key) {
alert('Underscore Result: ' + o.id + ': ' + o.prepareData + ' key: ' + key);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Update
I've come up with a concise solution to this problem, that behaves similar to node's vm module.
var VM = function(o) {
eval((function() {
var src = '';
for (var prop in o) {
if (o.hasOwnProperty(prop)) {
src += 'var ' + prop + '=o[\'' + prop + '\'];';
}
}
return src;
})());
return function() {
return eval(arguments[0]);
}
}
This can then be used as such:
var vm = new VM({ prop1: { prop2: 3 } });
console.assert(3 === vm('prop1.prop2'), 'Property access');
This solution overrides the namespace with only the identifier arguments taken.
Thanks to Ryan Wheale for his idea.
Short version
What is the best way to evaluate custom javascript expression using javascript object as a context?
var context = { prop1: { prop2: 3 } }
console.assert(3 === evaluate('prop1.prop2', context), 'Simple expression')
console.assert(3 === evaluate('(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()', context), 'Complex expression')
It should run on the latest version of node (0.12) and all evergreen browsers at the time of writing (3/6/2015).
Note: Most templating engines support this functionality. For example, Jade.
Long version
I'm currently working on an application engine, and one of its features is that it takes a piece of code and evaluates it with a provided object and returns the result.
For example, engine.evaluate('prop1.prop2', {prop1: {prop2: 3}}) should return 3.
This can be easily accomplished by using:
function(code, obj) {
with (obj) {
return eval(code);
}
};
However, the usage of with is known to be bad practice and will not run in ES5 strict mode.
Before looking at with, I had already written up an alternative solution:
function(code, obj) {
return (function() {
return eval(code);
}).call(obj, code);
}
However, this method requires the usage of this.
As in: engine.evaluate('this.prop1.prop2', {prop1: {prop2: 3}})
The end user should not use any "prefix".
The engine must also be able to evaluate strings like
'prop1.prop2 + 5'
and
'(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()'
and those containing calls to functions from the provided object.
Thus, it cannot rely on splitting the code string into property names alone.
What is the best solution to this problem?
I don't know all of your scenarios, but this should give you a head start:
http://jsfiddle.net/ryanwheale/e8aaa8ny/
var engine = {
evaluate: function(strInput, obj) {
var fnBody = '';
for(var prop in obj) {
fnBody += "var " + prop + "=" + JSON.stringify(obj[prop]) + ";";
}
return (new Function(fnBody + 'return ' + strInput))();
}
};
UPDATE - I got bored: http://jsfiddle.net/ryanwheale/e8aaa8ny/3/
var engine = {
toSourceString: function(obj, recursion) {
var strout = "";
recursion = recursion || 0;
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
strout += recursion ? " " + prop + ": " : "var " + prop + " = ";
switch (typeof obj[prop]) {
case "string":
case "number":
case "boolean":
case "undefined":
strout += JSON.stringify(obj[prop]);
break;
case "function":
// won't work in older browsers
strout += obj[prop].toString();
break;
case "object":
if (!obj[prop])
strout += JSON.stringify(obj[prop]);
else if (obj[prop] instanceof RegExp)
strout += obj[prop].toString();
else if (obj[prop] instanceof Date)
strout += "new Date(" + JSON.stringify(obj[prop]) + ")";
else if (obj[prop] instanceof Array)
strout += "Array.prototype.slice.call({\n "
+ this.toSourceString(obj[prop], recursion + 1)
+ " length: " + obj[prop].length
+ "\n })";
else
strout += "{\n "
+ this.toSourceString(obj[prop], recursion + 1).replace(/\,\s*$/, '')
+ "\n }";
break;
}
strout += recursion ? ",\n " : ";\n ";
}
}
return strout;
},
evaluate: function(strInput, obj) {
var str = this.toSourceString(obj);
return (new Function(str + 'return ' + strInput))();
}
};
UPDATE 3: Once we figured out what you are really asking, the question is clear: you do not do that. Especially in the strict mode.
As an viable alternative to your approach please refer to the documentation on require.js, common.js and other libraries allowing you to load modules in the browser. basically the main difference is that you do not do prop1.prop2 and you do context.prop1.prop2 instead.
If using context.prop1.prop2 is acceptable, see jsfiddle: http://jsfiddle.net/vittore/5rse4jto/
"use strict";
var obj = { prop1 : { prop2: 'a' } }
function evaluate(code, context) {
var f = new Function('ctx', 'return ' + code);
return f(context)
}
alert(evaluate('ctx.prop1.prop2', obj))
alert(evaluate(
'(function() {' +
' console.log(ctx.prop1.prop2);' +
' return ctx.prop1.prop2;' +
'}) ()', obj))
UPDATE: Answer to original question on how to access properties with prop1.prop2
First of all, you can access your variable using dictionary notation, ie:
obj['prop1']['prop2'] === obj.prop1.prop2
Give me several minutes to come up with example of how to do it recursively
UPDATED:This should work (here is gist):
function jpath_(o, props) {
if (props.length == 1)
return o[props[0]];
return jpath_(o[props.shift()], props)
}
function jpath(o, path) {
return jpath_(o, path.split('.'))
}
console.log(jpath(obj, 'prop1.prop2'))
I'm updating my knowledge about JavaScript and I stuck on one lesson task.
I have API that is returning string...
API.workerName = function (worker) {
return worker.firstName + ' ' + worker.lastName;
};
The task is to prefix returning string and not change API, but extend it. I also have to avoid copying & pasting code, because 3rd party code can change. I should re-use it instead.
What I did is change this function after loading API...
API.workerName = function (worker) {
return '(' + worker.position + ') ' + worker.firstName + ' ' + worker.lastName;
};
... but I think I did it wrong.
To extend the method, you should save the old definition and call it from your extension:
API.oldWorkerName = API.workerName;
API.workerName = function(worker) {
return '(' + worker.position + ')' + API.oldWorkerName(worker);
};
Or maybe this is what your lesson is looking for:
API.workerPositionAndName = function(worker) {
return '(' + worker.position + ')' + API.workerName(worker);
};
Another neat way to save the old definition and also make it unavailable to anybody else, would be to do something like this using IIFE to create a closure:
API.workerName = (function() {
var old = API.workerName; // this old version is only available inside your new function
return function(worker) {
return '(' + worker.position + ')' + old(worker);
}
})();
Here's an example:
API = {
workerName: function (worker) {
return worker.firstName + ' ' + worker.lastName;
}
};
API.workerName = (function () {
var old = API.workerName;
return function (worker) {
return '(' + worker.position + ')' + old(worker);
};
})();
alert(API.workerName({firstName: "Joe", lastName: "Blogs", position: "Lackey" }));
This question already has answers here:
How do you find out the caller function in JavaScript when use strict is enabled?
(5 answers)
Closed 2 years ago.
In framework, I'm developing, I've constructed mechanism, that allowed to define private and protected properties and methods.
The only ability, I found in ES5 specifications for doing that was using arguments.callee
like this:
descriptor.method = function () {
if (__callerIsProptected(arguments.callee.caller.caller, cls))
return value.apply(this, __defaults(_.values(arguments), defaults));
throw 'Attempt to call ' + access + ' method "' + cls._name + '::' + name + '"';
};
As far as in strict mode calls to arguments.callee and arguments.caller cause throwing of exceptions are there any convenient alternatives to do that?
Update - added whole called function code
function __descriptor(cls, type, name, descriptor, access) {
//protected private non-function descriptor.value is replaced by get/set pair
if (access !== 'public' && type == 'property') {
delete descriptor.value;
delete descriptor.writable;
_.isFunction(descriptor.get) || (descriptor.get = function () {
return this.__get(name);
});
_.isFunction(descriptor.set) || (descriptor.set = function (value) {
return this.__set(name, value);
});
}
//remove uselesses
if (_.isFunction(descriptor.get) || _.isFunction(descriptor.set)) {
delete descriptor.value;
delete descriptor.writable;
if (!_.isFunction(descriptor.get)) {
descriptor.get = function () {
return this.__get(name);
};
}
if (!_.isFunction(descriptor.set)) {
descriptor.set = function (value) {
return this.__set(name, value);
};
}
} else {
delete descriptor.get;
delete descriptor.set;
}
if (descriptor.get) {
var getter = descriptor.get;
//mutate getter and setter if given respectively to access level
if (access === 'public') {
descriptor.getter = function () {
return getter.apply(this, arguments);
};
} else if (access === 'protected') {
descriptor.getter = function () {
if (__callerIsProptected(arguments.callee.caller.caller, cls))
return getter.apply(this, arguments);
throw 'Attempt to get ' + access + ' property "' + cls._name + '::' + name + '"';
};
} else if (access === 'private') {
descriptor.getter = function () {
if (__callerIsPrivate(arguments.callee.caller.caller, cls))
return getter.apply(this, arguments);
throw 'Attempt to get ' + access + ' property "' + cls._name + '::' + name + '"';
};
}
descriptor.getter._class = cls;
}
if (descriptor.set) {
var setter = descriptor.set;
//mutate getter and setter if given respectively to access level
if (access === 'public') {
descriptor.setter = function () {
return setter.apply(this, arguments);
};
} else if (access === 'protected') {
descriptor.setter = function () {
if (__callerIsProptected(arguments.callee.caller.caller, cls))
return setter.apply(this, arguments);
throw 'Attempt to set ' + access + ' property "' + cls._name + '::' + name + '"';
};
} else if (access === 'private') {
descriptor.setter = function () {
if (__callerIsPrivate(arguments.callee.caller.caller, cls))
return setter.apply(this, arguments);
throw 'Attempt to set ' + access + ' property "' + cls._name + '::' + name + '"';
};
}
descriptor.setter._class = cls;
}
if (descriptor.value !== undefined) {
if (!_.isFunction(descriptor.value)) return descriptor;
var value = descriptor.value;
var defaults = descriptor.defaults || [];
if (access === 'public' && type == 'method') {
descriptor.method = function () {
return value.apply(this, __defaults(_.values(arguments), defaults));
};
} else if (access === 'protected') {
descriptor.method = function () {
if (__callerIsProptected(arguments.callee.caller.caller, cls))
return value.apply(this, __defaults(_.values(arguments), defaults));
throw 'Attempt to call ' + access + ' method "' + cls._name + '::' + name + '"';
};
} else if (access === 'private') {
descriptor.method = function () {
if (__callerIsPrivate(arguments.callee.caller.caller, cls))
return value.apply(this, __defaults(_.values(arguments), defaults));
throw 'Attempt to call ' + access + ' method "' + cls._name + '::' + name + '"';
};
}
descriptor.method._class = cls;
}
return descriptor;
}
Once I was developing same framework (abandoned) and the only way to figure out a caller in strict mode was to actually throw an exception and RegExp caller name from stack trace. As far as I remember it wasn't always precise. Look for example at the code of caller-id script
var Animal = function(config) {
config = config || {};
var name = config.name,
numLegs = config.numLegs,
weight = config.weight,
speed = config.speed,
sound = config.sound
return {
getName: function () {
return name;
},
getNumLegs: function () {
return numLegs;
},
getWeight: function () {
return weight;
},
getSpeed: function () {
return speed;
},
getSound: function () {
return sound;
},
run: function(distance, unit) {
unit = unit || 'miles';
return 'The ' + name + ' ran ' + distance + ' ' + unit;
},
speak: function() {
return 'The ' + name + ' says "' + sound + '"';
}
}
};
function DragonFly(config) {
var me = {},
numWings = config.numWings;
me.prototype = new Animal(config);
me.getNumWings = function() {
return numWings;
};
me.fly = function(distance, unit) {
unit = unit || 'miles';
return 'The ' + me.name + ' flew ' + distance + ' ' + unit;
}
return me;
}
var dragonFly = new DragonFly({
numWings: 2,
name: 'DragonFly',
numLegs: 6
});
Okay, coming from a PHP background, I don't understand inheritance in JavaScript one bit and I'd like some help.
Basically, here's what I'd like to be able to do with an instance of the dragonFly object:
dragonFly.getName(); // 'DragonFly'
dragonFly.fly(1, 'mile'); // 'The dragonfly flew 1 mile';
dragonFly.run(1, 'yard'); // 'The dragonfly ran 1 yard';
I'd also like to know how to override methods and call the parent of those overridden methods. What is wrong with my approach? All the examples above return undefined or throw an error. The main reason I went with the object-literal style is so I could make properties private.
the "fastest" way :
var Animal = function(config) {
config = config || {};
var name = config.name,
numLegs = config.numLegs,
weight = config.weight,
speed = config.speed,
sound = config.sound
return {
getName: function () {
return name;
},
getNumLegs: function () {
return numLegs;
},
getWeight: function () {
return weight;
},
getSpeed: function () {
return speed;
},
getSound: function () {
return sound;
},
run: function(distance, unit) {
unit = unit || 'miles';
return 'The ' + name + ' ran ' + distance + ' ' + unit;
},
speak: function() {
return 'The ' + name + ' says "' + sound + '"';
}
}
};
function DragonFly(config) {
var me = new Animal(config);
var numWings = config.numWings;
me.getNumWings = function() {
return numWings;
};
me.fly = function(distance, unit) {
unit = unit || 'miles';
return 'The ' + me.name + ' flew ' + distance + ' ' + unit;
}
return me;
}
var dragonFly = new DragonFly({
numWings: 2,
name: 'DragonFly',
numLegs: 6
});
You are mixing 2 kind of "inheritance" in your script , the "classical" inheritance and the prototypal inheritance , you cant do that unless you want to be in serious trouble. both work , both have their pros and cons. Stick to the "classical" inheritance , or object augmentation since you began with it.
An object literal doesnt have a prototype , functions have prototypes. That's why in my opinion js isnt "really" object oriented , but it can mimic object oriented langages
A good exercice now would be to try using functions and prototypes , though i'm not sure you could create private fields with that.
Edit : the me.name should be me.getName() since name is "private". i think.